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:
LEDs¶
With a line such as "build_leds = C13" in the INI file, the following code
can be generated:
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]
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.