stm32-data
is a project aiming to produce clean machine-readable data about the STM32 microcontroller
families, including:
:heavy_check_mark: = done, :construction: = work in progress, :x: = to do
The generated JSON files are available here in the stm32-data-generated
repo.
If you're looking for the API docs for stm32-metapac
customized to a
particular chip, they are available here: https://docs.embassy.dev/stm32-metapac
The generated PAC is available here in the stm32-data-generated
repo.
These are the data sources currently used.
For register blocks, YAMLs are intially extracted from SVDs, manually cleaned up and committed. From this point on, they're manually maintained.
We don't maintain "patches" for registers unlike the stm32-rs
project. This has two advantages:
i2c_v2.yaml
and ensure it is good, vs trying to patch wildly differing SVDs for what's an identical IP block into being consistent). The stm32-rs
project doesn't have this as a goal, while we do. Writing a single HAL for all stm32 chips (such as the embassy-stm32
HAL) is impossible otherwise.stm32-metapac
crateRun ./d download-all
This will download all the required data sources into
sources/
.
Run cargo run --release --bin stm32-data-gen
This generates all the intermediate JSON's in
build/data/
.Assignments of registers to peripherals is done in a perimap and fixes to registers can be done in the files located in
data/registers
.
Run cargo run --release --bin stm32-metapac-gen
This generates the
stm32-metapac
crate intobuild/stm32-metapac/
.
This will help you add support for a new peripheral to all STM32 families. (Please take the time to add it for all families, even if you personally are only interested in one. It's easier than it looks, and doing all families at once is significantly less work than adding one now then having to revisit everything later when adding more. It also helps massively in catching mistakes and inconsistencies in the source SVDs.)
./d install-chiptool
./d extract-all LPUART1
. This'll output a bunch of yamls in tmp/LPUART1
. NOTE sometimes peripherals have a number sometimes not (LPUART1
vs LPUART
). You might want to try both and merge the outputted YAMLs into a single directory.data/registers/lpuart_vX.yaml
lpuart_v1.yaml
and lpuart_v2.yaml
. If one is missing enums or descriptions, copy it from another.perimap
, see below.data/chips/*.yaml
has the right block: lpuart_vX/LPUART
fields.cd build/stm32-metapac
cargo build --features stm32u5a9nj,memory-x --target thumbv8m.main-none-eabihf
Please separate manual changes and changes resulting from regen in separate commits. It helps tremendously with review and rebasing/merging.
SVDs have some widespread annoyances that should be fixed when adding register YAMLs to this repo. Check out chiptool
transforms, they can help in speeding up the cleanups.
RNG
peripheral are named RNG_FOO
, RNG_BAR
, the RNG_
peripheral is not conveying any useful information at all, and must go.xxEN
and xxIE
fields. If a field says "enable foo" and is one bit, it's obvious "true" means enabled and "false" means disabled.xxIF
fields.DeleteEnums
chiptool transforms.FOO0 FOO1, FOO2, FOO3
) to arrays FOO[n]
.
MakeRegisterArray
, MakeFieldArray
chiptool transforms.chiptool fmt
on each of the register yamls.NOTE: At the time of writing all families are supported, so this is only useful in particular situations, for example if you want to regenerate an RCC register yaml from scratch.
Adding support for a new familiy is mostly a matter of adding support for RCC.
Now extract the RCC peripheral registers: ./d extract-all RCC --transform ./transform-RCC.yaml
Note that we have used a transform to mechanically clean up some of the RCC
definitions. This will produce a YAML file for each chip model in ./tmp/RCC
.
Sometimes the peripheral name will not match the name defined in the SVD files, check the SVD file for the correct peripheral name.
RCC registers are usually identical within a family, except they have different subsets of the enable/reset bits because different chips have different amounts of peripherals. (i.e. one particular bit position in one particular register is either present or not, but will never have different meanings in different chips of the same family.)
To verify they're indeed subsets, choose the model with the largest peripheral set possible (e.g. the STM32G081) and compare its YAML against each of the other models' to verify that they are all mutually consistent.
Finally, we can merge
./merge_regs.py tmp/RCC/g0*.yaml
This will produce regs_merged.yaml
, which we can copy into its final resting
place:
mv regs_merged.yaml data/registers/rcc_g0.yaml
To assign these newly generated registers to peripherals, utilize the mapping done in parse.py
.
An example mapping can be seen in the following snippet
('STM32G0.*:RCC:.*', 'rcc_g0/RCC'),
such mapping assignes the rcc_g0/RCC
register block to the RCC
peripheral in every device from the STM32G0
family.
The stm32-data-gen
binary has a map to match peripherals to the right version in all chips, the perimap.
When parsing a chip, for each peripheral a "key" string is constructed using this format: CHIP:PERIPHERAL_NAME:IP_NAME:IP_VERSION
, where:
CHIP
: full chip name, for example STM32L443CC
PERIPHERAL_NAME
: peripheral name, for example SPI3
. Corresponds to IP.InstanceName
in the MCU XML.IP_NAME
: IP name, for example SPI
. Corresponds to IP.Name
in the MCU XML.IP_VERSION
: IP version, for example spi2s1_v3_3_Cube
, Corresponds to IP.Version
in the MCU XML.perimap
entries are regexes matching on the above "key" string. First regex that matches wins.
The IP version is particularly useful. It is an ST-internal "IP version" that's incremented every time changes are made to the peripheral, so it correlates very well to changes in the peripheral's register interface.
You should prefer matching peripherals by IP version whenever possible. For example:
('.*:SPI:spi2s1_v2_2', 'spi_v1/SPI'),
('.*:SPI:spi2s1_v3_2', 'spi_v2/SPI'),
Sometimes it's not possible to map by IP version, and we have to map by chip name. For example:
('STM32H7.*:FLASH:.*', 'flash_h7/FLASH'),
('STM32F0.*:FLASH:.*', 'flash_f0/FLASH'),
('STM32F1.*:FLASH:.*', 'flash_f1/FLASH'),
('STM32F3.*:FLASH:.*', 'flash_f3/FLASH'),
('STM32F4.*:FLASH:.*', 'flash_f4/FLASH'),
...etc
Sometimes even the same IP name+version in the same chip family has different registers (different instances of the IP are configured differently), so we have to map by chip name AND peripheral instance name. This should be the last resort. For example:
('STM32F7.*:TIM1:.*', 'timer_v1/TIM_ADV'),
('STM32F7.*:TIM8:.*', 'timer_v1/TIM_ADV'),
('.*TIM\d.*:gptimer.*', 'timer_v1/TIM_GP16'),
The versions of peripherals can be found in the table here.