Skip to content

Code generator details

JeeH includes a code generation phase, built into PIO's build process via its Python scripting functionality. The generated code ends up inside your own source files and lets JeeH adjust itself to different build requirements. This is somewhat like STM's STM32CubeIDE build environment, but far, fár less intrusive.

Syntax

The code generator in JeeH runs as part of PIO's build process. It looks for files with extension .hpp in the project source folder and scans these for lines starting with //CG (files in subfolders are not considered).

Each line starting with //CG is treated as a code generator directive. There are a few variations of this directive:

Prefix Description
//CG ... first time use, this line stands by itself
//CG1 ... one more line follows, inserted by the code generator
//CG2 ... two more lines follow, inserted by the code generator
//CG3 ... three more lines follow, inserted by the code generator
//CG[ ... more than 3 lines follow, inserted by the code generator
//CG] ... end marker for lines inserted after the last //CG[ marker
//CG: ... same as //CG (when the code generator strips its output)

The //CG lines can be indented as needed to fit the rest of the code. All generated text will be indented by the same amount, for consistency. Only tabs and spaces are recognised as indentation.

Warning

Since the code generator alters your source code files, never edit anything inside the generated output. It's harmless, but your changes will be lost the next time the code generator runs, i.e. in the next build cycle.

The //CG... markers were styled to make it easy to distinguish your code from the generated code, without being overly intrusive.

Build options

Pre-defined macros

The following macros are pre-defined in JeeH's include/jee.hpp header:

Name Example Notes
STM32 #define STM32 1 currently always present and set to 1
STM32F1 #define STM32F1 1 device family: F1, F4, G4, L4, etc
SVDNAME #define SVDNAME "STM32F103xx" base name of the current SVD file
VERSION constexpr auto VERSION = "v7.0.5"; JeeH version string

PIO

If a line "//CG pio" is present in a *.hpp header, it will be expanded using the name of the build environment. If the current build is [env:myapp] for example, then the generated code will be:

//CG2 pio
#define PIO_MYAPP 1
#define PIOENV "myapp"

LEDs

With a line such as "build_leds = C13" in the INI file, the following code can be generated:

//CG1 build leds
#define LED  "C13"

Additional LEDs can also be defined, e.g. build_leds = C13 A5 D2 B11 generates this:

//CG[ build leds
#define LED  "C13"
#define LED1 "C13"
#define LED2 "A5"
#define LED3 "D2"
#define LED4 "B11"
//CG]
The first LED is defined twice to allow either name in the application code.

Hardware peripherals

Hardware configuration choices can be complicated since STM32 peripherals only support certain features on certain pins, with certain peripherals, and using certain DMA channels. All this information needs to be obtained from the Reference Manual, and this will be different for different µC versions.

JeeH's code generator has built-in code expansions for I2C, SPI, and UART peripherals in STM32 µCs. The parameters are defined in the project INI file.

I2C

Code generation for I2C is based on build_i2c = ... and build_i2c<suffix> = ... settings in the INI file. The are four different methods to handle I2C communication: bit-banged (i2c::Gpio), polled (i2c::Poll), synchronous DMA (i2c::Sync), and event-based DMA (i2c::Trig).

Each of these may require a different amount of configuration, with corresponding definitions in the project INI file:

Method Example
GPIO build_i2c = P:B6,B7
POLL build_i2c = P:B6:O4,B7 N:I2C1 F:36
SYNC build_i2c = P:B6:O4,B7 N:I2C1 F:36 D:1 L:Channel T:2 R:3 C:4,4
TRIG build_i2c = P:B6:O4,B7 N:I2C1 F:36 D:1 L:Channel T:2 R:3 C:4,4

I2C uses 2 pins: SDA and SCL (B6 and B7, respectively in this example),

Each definition is compatible with methods above it, i.e. you can specify DMA settings even if the app ends up using polled mode. Unused details will simply be ignored.

These build settings summarise the pin and hardware choices made for a specific build. With GPIO, all you need is the pin choices, whereas the other need at least the hardware peripheral's name and the frequency of the bus it's tied to. For the DMA-based SYNC and TRIG methods, additional information is needed to specify which DMA channels and streams are used.

SPI

Code generation for SPI is based on build_spi = ... and build_spi<suffix> = ... settings in the INI file. The are four different methods to handle SPI communication: bit-banged (spi::Gpio), polled (spi::Poll), synchronous DMA (spi::Sync), and event-based DMA (spi::Trig).

Each of these may require a different amount of configuration, with corresponding definitions in the project INI file:

Method Example
GPIO build_spi = P:A4,A5,A6,A7
POLL build_spi = P:A4:4,A5,A6,A7:P N:SPI1 F:72
SYNC build_spi = P:A4:4,A5,A6,A7:P N:SPI1 F:72 D:1 L:Channel T:2 R:3 C:4,4
TRIG build_spi = P:A4:4,A5,A6,A7:P N:SPI1 F:72 D:1 L:Channel T:2 R:3 C:4,4

SPI uses 4 pins: MOSI, MISO, SCLK, NSEL (A4/A5/A6/A7, respectively in this example),

Each definition is compatible with methods above it, i.e. you can specify DMA settings even if the app ends up using polled mode. Unused details will simply be ignored.

These build settings summarise the pin and hardware choices made for a specific build. With GPIO, all you need is the pin choices, whereas the other need at least the hardware peripheral's name and the frequency of the bus it's tied to. For the DMA-based SYNC and TRIG methods, additional information is needed to specify which DMA channels and streams are used.

UART

Code generation for UART is based on build_uart = ... and build_uart<suffix> = ... settings in the INI file. The are three different methods to handle UART communication: polled (uart::Poll), synchronous DMA (uart::Sync), and event-based DMA (uart::Trig).

Each of these may require a different amount of configuration, with corresponding definitions in the project INI file:

Method Example
POLL build_uart = P:A9:O4,A10 N:UART1 F:72
SYNC build_uart = P:A9:O4,A10 N:UART1 F:72 D:1 L:Channel T:2 R:3 C:4,4
TRIG build_uart = P:A9:O4,A10 N:UART1 F:72 D:1 L:Channel T:2 R:3 C:4,4

UART uses 2 pins: TX and RX (A9 and A10, respectively in this example),

Each definition is compatible with methods above it, i.e. you can specify DMA settings even if the app ends up using polled mode. Unused details will simply be ignored.

These build settings summarise the pin and hardware choices made for a specific build. The UART needs at least the hardware peripheral's name and the frequency of the bus it's tied to. For the DMA-based SYNC and TRIG methods, additional information is needed to specify which DMA channels and streams are used.

Implementation

Custom code generation