Understanding the GEX source code layout is important before attempting to implement any changes or to port it to a different microcontroller model. 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.
After the firmware successfully compiles, we can flash it to the \gls{MCU} using ST-Link by running ``\verb|make flash|'', or through the \gls{DFU} interface with ``\verb|make dfu|''.
The first thing to verify after flashing the firmware is that the debug \gls{UART}'s configuration is correct and we can see the log output, which should be available on pin PA9 at 115200\,baud. Thanks to a generous usage of assertions, most errors should produce helpful log messages in the log. We can then proceed to experiment with the device, testing the features described in \cref{sec:conceptual} while observing the debug log for any anomalies.