@ -4,16 +4,14 @@ The firmware is built around a \textit{core framework} which provides services t
\section{Internal Structure Block Diagram}
\section{Internal Structure Block Diagram}
The data flows and other internal logic of the firmware are depicted in \cref{fig:gexinternal}, with more explanation following in this chapter. The interchangeable role of the three communication interfaces can be clearly seen in the diagram, as well as the central role of the message queue, which decouples interrupts from the processing thread.
The data flows and other internal logic of the firmware are depicted in \cref{fig:gex_internal}, with more explanation following in this chapter. The interchangeable role of the three communication interfaces can be clearly seen in the diagram, as well as the central role of the message queue, which decouples interrupts from the processing thread.
\caption{\label{fig:gexinternal}Block diagram showing the internal logic in the GEX firmware}
\caption{\label{fig:gex_internal}Block diagram showing the internal logic in the GEX firmware}
\end{figure}
\end{figure}
The core framework forms the skeleton of the firmware and usually does not need any changes when new user-facing features are added. When the firmware is ported to a different STM32 microcontroller, the framework is not difficult to adjust and the whole process can be accomplished in a few hours. The porting of units to a different platforms is significantly more challenging.
\noindent
\noindent
The framework provides the following services to units:
The framework provides the following services to units:
@ -68,9 +66,9 @@ The resources used by the core framework are taken by a virtual unit \verb|SYSTE
\caption{\label{fig:settings_storage}Structure of the settings subsystem}
\caption{\label{fig:settings_storage}Structure of the settings subsystem}
\end{figure}
\end{figure}
The system and unit settings are written, in a binary form, into designated pages of the microcontroller's Flash memory. The unit settings serialization and parsing is implemented by the respective unit drivers.
The system and unit settings are stored, in a binary form, in designated pages of the microcontroller's flash memory. The serialization and parsing of unit settings is implemented by the respective unit drivers. \Cref{fig:settings_storage} shows the organization of the settings subsystem; note that the ``Settings manager'' block has been omitted in \cref{fig:gex_internal} for clarity, to better represent the data flows.
As the settings persist after a firmware update, it is important to maintain backward compatibility. This is achieved by prefixing the settings block of each unit with a version number. When the settings are loaded by a new version of the firmware, it first checks the version and decides whether to use the old or new format. When the settings are next changed, the new format will be used.
The settings persist in the flash memory even after a firmware update, which might add or remove some data fields, or otherwise rearrange the binary structure. Assuming the general layout remains unchanged, this mainly concerns the data areas holding unit (and system) settings. Backward compatibility is achieved by prefixing each storage area with its version number. When the settings are loaded by an updated firmware, it always first checks the version field and decides which format to use to parse the saved data.
\iffalse
\iffalse
The INI files, which can be edited through the communication \gls{API} or using a text editor through the virtual mass storage, are parsed and generated on demand and are never stored in the Flash or \gls{RAM}, other than in short temporary buffers. The INI parser processes the byte stream on-the-fly as it is received, and a similar method is used to build a INI file from the configured units and system settings. \fi% said elsewhere
The INI files, which can be edited through the communication \gls{API} or using a text editor through the virtual mass storage, are parsed and generated on demand and are never stored in the Flash or \gls{RAM}, other than in short temporary buffers. The INI parser processes the byte stream on-the-fly as it is received, and a similar method is used to build a INI file from the configured units and system settings. \fi% said elsewhere
@ -78,7 +76,7 @@ The INI files, which can be edited through the communication \gls{API} or using
One of the key functions of the core framework is to deliver messages from the host \gls{PC} to the right units. The \textit{TinyFrame} protocol is used, described in detail in \cref{sec:tinyframe}; it is represented by the blocks ``TinyFrame Parser'' and ``TinyFrame Builder'' in \cref{fig:gex_internal}.
One of the key functions of the core framework is to deliver messages from the host \gls{PC} to the right units. The \textit{TinyFrame} protocol is used, described in detail in \cref{sec:tinyframe}; it is represented by the ``TinyFrame Parser'' and ``TinyFrame Builder'' blocks in \cref{fig:gex_internal}\footnote{The framing library is not split into those two blocks in the source code, but the parts are functionally independent}.
Two groups of messages exist: \textit{system messages} and \textit{unit messages}. System messages can, for instance, access the INI files, or request a list of available units. Unit messages are addressed to a particular unit, and their payload format is defined by the unit's driver. An incoming message is inspected and delivered to the appropriate recipient, or responded to with an error message.
Two groups of messages exist: \textit{system messages} and \textit{unit messages}. System messages can, for instance, access the INI files, or request a list of available units. Unit messages are addressed to a particular unit, and their payload format is defined by the unit's driver. An incoming message is inspected and delivered to the appropriate recipient, or responded to with an error message.
@ -114,61 +112,10 @@ The firmware is built around FreeRTOS (\cref{sec:freertos}) and a number of its
\subsection{Message and Job Queue}
\subsection{Message and Job Queue}
The message and job queue, seen in \cref{fig:gexinternal}, is used to decouple asynchronous interrupts from message transmission. All three communication interfaces use interrupts for the asynchronous handling of incoming messages. The same interrupt handler receives an event after a transmission was completed. The queue ensures that messages can be received during the transmission of a large response that demands the use of multiple transmissions.
The message and job queue, seen in \cref{fig:gex_internal}, is used to decouple asynchronous interrupts from message transmission. All three communication interfaces use interrupts for the asynchronous handling of incoming messages. The same interrupt handler receives an event after a transmission was completed. The queue ensures that messages can be received during the transmission of a large response that demands the use of multiple messages.
The ``transmission complete'' interrupt signals this fact to the message processing task using a binary semaphore. The semaphore is released in the \gls{ISR} and taken when a new block of data is to be transmitted. If more data needs to be transmitted, the queue task waits for the semaphore and enters a Blocked state until the semaphore becomes available again.
Two mutexes are used in the firmware: one that guards access to TinyFrame until the previous message was fully transmitted, and one to guard a shared memory buffer (reused in several different places to save memory and avoid its re-allocation). The hardware resource registry (explained in \cref{sec:res_allocation}) does not need mutexes for individual resources, as concurrent access to those fields can never happen; resources are always taken or released sequentially by the same task.
\section{Source Code Layout}
\begin{wrapfigure}[22]{r}{0.4\textwidth}
\scriptsize\vspace{-3em}
\begin{verbatim}
├── build
│ ├── firmware.bin
│ └── firmware.dfu
├── Drivers
│ ├── CMSIS
│ │ └── Device / ST / STM32F0xx
│ └── STM32F0xx_HAL_Driver
├── Middlewares / Third_Party / FreeRTOS
├── Src
│ └── main.c
├── User
│ ├── USB / STM32_USB_Device_Library
│ │ ├── Class
│ │ │ ├── CDC
│ │ │ ├── MSC
│ │ │ └── MSC_CDC
│ │ └── Core
│ ├── platform
│ │ ├── plat_compat.h
│ │ └── platform.c
│ ├── units
│ │ ├── adc
│ │ ├── digital_out
│ │ ...
│ ├── FreeRTOSConfig.h
│ └── gex.mk
└── Makefile
\end{verbatim}
\vspace{-1em}
\caption{\label{fig:repo_structure} The general structure of the source code repository}
\end{wrapfigure}
Looking at the GEX source code repository (\cref{fig:repo_structure}), at the root we'll find the device specific driver libraries and support files provided by ST Microelectronics, the FreeRTOS middleware, and a folder called \verb|User| containing the GEX application code. This division is useful when porting the firmware to a different microcontroller, as the GEX folder is mostly platform-independent and can be simply copied (of course, adjustments are needed to accompany different hardware peripheral versions etc.). The GEX core framework consists of everything in the \verb|User| folder, excluding the \verb|units| directory in which the individual units are implemented. Each unit driver must be registered in the file \verb|platform.c| to be available for the user to select. The file \verb|plat_compat.h| includes platform-specific headers and macros, defining parameters such as pin assignments or the clock speed.
The \gls{USB} Device library, which had to be modified to support a composite class, is stored inside the \verb|User| folder too, as it is compatible with all STM32 microcontrollers that support \gls{USB}.
\subsection{Lock Objects}
The ``transmission complete'' interrupt signals the readiness to transmit more data to the message-sending task using a binary semaphore. This semaphore is taken by the task before sending a block of data, and released by the \gls{ISR}, ensuring that the task waits for the transmission to complete before attempting to send more data.
Two mutexes are used in the firmware: one that guards access to the TinyFrame Builder until the previous message was fully transmitted, and one to guard a shared memory buffer (reused in several places to save memory and avoid its re-allocation). The hardware resource registry (explained in \cref{sec:res_allocation}) does not need mutexes for individual resources, as concurrent access to those fields can never happen thanks to the way the system is organized; resources are always taken or released sequentially by the same task.
\caption{\label{fig:repo_structure} The general structure of the source code repository}
\end{wrapfigure}
Understanding the GEX source code layout is important before attempting to implement any changes or to port it to a different microcontroller type. The directory layout is shown in \cref{fig:repo_structure}.
The GEX core framework resides in the User folder, and units are defined in User/units. Each unit driver must be registered in the file \verb|platform.c|. The header file \verb|plat_compat.h| defines platform-specific constants and macros, defining parameters such as pin assignments or the clock speed. The User folder is actually a Git submodule called ``gex-core'' and is kept as a separate project; platform-specific customizations are managed using compile flags passed from the Makefile.
\section{Porting to a New Platform}
When porting GEX to a new platform, the basis of the project can be generated by the STM32CubeMX code generator \cite{cubemx}, using the Makefile output preset. We have to enable FreeRTOS, select a USB class (the choice does not matter, e.g., \gls{CDCACM} can be used), and configure the system clock.
The configuration dialog gives a choice between the LL (Low Level) and HAL driver libraries; the HAL library uses a lot of program memory and often contains software bugs, while the LL library is leaner but harder to use. The LL library was used in the STM32F072 port for its smaller size.
Some files generated by STM32CubeMX were moved into the User folder (e.g., the FreeRTOS configuration and initialization, or the system time base generation), as they are mostly platform-independent. The modified \gls{USB} Device library was copied here as well, as it had to be modified to support the definition of a composite class with custom descriptors.
The rest of the porting process, after generating the base project, can be summarized in the following bullet points. These are written as a checklist for the developer working on a new port; an existing, functional port may be consulted as a reference during the porting process.
\begin{itemize}
\item Initialize the project folder as a Git repository.
\item Add the ``gex-core'' Git submodule as the \verb|User| folder, and create a Git branch in it for the new platform.
\item Fix the Makefile generated by STM32CubeMX; it usually contains duplicate entries in the file lists and other errors. Ensure the build (``\verb|make|'' invocation in the terminal) succeeds before making any other changes.
\item Delete the USB Device library from the \verb|Middlewares/ST| folder; GEX uses the modified version included in \verb|User/USB|.
\item Move the \gls{GPIO}, FreeRTOS, USB, and other peripheral initialization from \verb|Src| and \verb|Inc| aside for later reference; the code in those folders should only configure the system clock, call the GEX initialization function ``\verb|GEX_PreInit()|'', and start FreeRTOS with ``\verb|MX_FREERTOS_Init()|'' and ``\verb|osKernelStart()|''.
\item Add ``\verb|include User/gex.mk|'' and ``\verb|GEX_PLAT=MYPLATFORM|'' at the top the Makefile, with the desired platform name in place of ``\verb|MYPLATFORM|''; the name must be a valid C identifier.
\item Add ``\verb|GEX_CFLAGS|'', ``\verb|GEX_SOURCES|'', ``\verb|GEX_INCLUDES|'', ``\verb|GEX_CDEFS|'' into the appropriate file lists in the Makefile; those variables are exported from \verb|User/gex.m| and contain lists of GEX source files and compiler flags.
Use ``\verb|$(foreach x,$(GEX_SRC_DIR),$(wildcard $(x)/*.c))|'' to include all source files from ``\verb|GEX_SRC_DIR|'' in the ``\verb|C_SOURCES|'' list.
\item Remove all definitions of ``\verb|Error_Handler()|'', ``\verb|FULL_ASSERT|'' and ``\verb|assert_param()|'' from the files left inside \verb|Inc| and \verb|Src|, and add ``\verb|#include "stm32_assert.h"|'' to \verb|Src/main.h|. GEX uses the functions declared in \verb|User/stm32_assert.h| for assertions.
\item Update \verb|User/FreeRTOSConfig.h| and \verb|User/platform/plat_compat.h| for the new platform. Preprocessor directives like ``\verb|#ifdef|'' are used to define configuration applicable only to one platform, without affecting others.
\item Update \verb|User/platform/platform.c| to register the initially supported units; a good choice is the DO (Digital Output) unit that is straightforward to update and can be used to verify the platform's functionality.
Define a flag like ``\verb|UNIT_DO=1|'' at the top of the Makefile for each registered unit. \verb|User/gex.m| may be used as a reference for the expected variable names. This is used to conditionally enable or disable the inclusion of the particular unit's source files in the compilation.
\item Update the USB functions in the \verb|User/USB| folder according to the ones previously generated by STM32CubeMX. The USB Device library uses these as an interface to the different \gls{USB} peripheral versions.
\item Update other platform-dependent code (such as the debug \gls{UART} configuration). The compiler should warn about those occurrences when trying to build the project.
\item Try to build the project by running ``\verb|make|'' in the terminal. After all errors are corrected, the firmware may be flashed using ST-Link by running ``\verb|make flash|'', or through the \gls{DFU} interface with ``\verb|make dfu|''.
\end{itemize}
After succeeding with the compilation, we can test the firmware image. It is highly probable that there will be errors that need correcting.
A good first step is to ensure the debug \gls{UART}'s configuration is correct so that we can see the log output. It should be available on the pin PA9 at 115200\,baud. Thanks to the generous usage of assertions, most errors should produce helpful log messages, leading us to their probable cause. We proceed to experiment with the device, trying the features described in \cref{sec:conceptual}, while observing the debug log for any anomalies.