Skip to content

Add support for multiple configuration to deduplicate examples - #1118

Merged
salkinium merged 83 commits into
modm-io:developfrom
salkinium:feature/dedup_examples
Oct 17, 2026
Merged

salkinium merged 83 commits into
modm-io:developfrom
salkinium:feature/dedup_examples

Conversation

@salkinium

@salkinium salkinium commented Jan 6, 2024 •

Copy link
Copy Markdown
Member

Depends on #1387, #1389 and #1393 all merged.

This flips the example matrix from "board -> use case" to "use case -> board": an example now lists all the boards it runs on in its project.xml and the CI compiles it for every one of them. Turns out we had a lot of copy-pasted examples, whoops.

The syntax is based on XML comments, so that the example remains usable manually and is hopefully self-explanatory:

<library>
  <extends>modm:nucleo-l476rg</extends>
  <!-- <extends>modm:blue-pill-f103</extends> -->
  <!-- <extends>modm:hosted</extends> -->
  <modules>
    <!-- Required for modm:nucleo-l476rg -->
    <module>modm:platform:adc:1</module>
    <!-- Required for modm:blue-pill-f103 -->
    <!-- <module>modm:platform:adc:2</module> -->
    <module>modm:build:scons</module>
  </modules>
</library>
  • Every <extends>, commented or not, is one configuration.
  • A comment that names configurations scopes the <option>, <collect> and <module> lines directly below it to these configurations. Everything else applies to all of them.
  • examples_compile.py copies the example into the build folder once per configuration, flips the comments and compiles it. Relative includes still resolve to the original folder.
  • There is now a modm:hosted board with the versions linux-x86_64, linux-arm64, darwin-x86_64, darwin-arm64 and windows, defaulting to your computer. Its configurations moved from tools/devices/hosted to src/modm/board/hosted, and it has a logger and no-op LEDs and buttons, so the generic examples (blinky, logger, printf, fiber, ...) run on hosted without any #ifdef. So no special casing of hosted-* in the scripts.

Since the board is now just a configuration, sorting the examples by board makes no sense anymore, so they are sorted by what they show:

Folder Examples Content
core 21 Fibers, assertions, fault handling, allocators, internal flash, DMA, watchdogs, debug channels.
gpio 5 Blinking LEDs, buttons, ports, external interrupts.
analog 27 Internal ADCs, DACs and comparators.
timer 11 Timers and PWM.
logging 5 Logging and printing via UART.
can 3 CAN and FDCAN peripherals.
driver 92 External devices, sorted like modm:driver: sensors, displays, radios, LEDs and more.
ui 6 Graphics, GUI and animations on the displays of the boards.
communication 6 Protocols: AMNB, SAB, XPCC, Ethernet.
ext 22 External libraries: FreeRTOS, TinyUSB, LVGL, ETL, nanopb and more.

This is the layout from #1118 (comment). Almost half of all examples show an external device, so the driver folder has the same categories as src/modm/driver: driver/inertial/bmi270, driver/display/ssd1306, driver/radio/nrf24 and so on. If you know where the driver lives, you know where its example is, and the bus it uses does not matter. A suffix like analog/dac_dma_g4 names the family that an example is specific to.

A bunch of "examples" were really tests or benchmarks that nobody would use as a starting point. These moved to test/integration and all end with a INTEGRATION TEST PASSED or FAILED line on the logger (and the exit code on hosted), so that we can eventually run them automatically on real hardware.

Some numbers:

develop this PR
Example projects 431 198 + 38 integration tests
Configurations 431 509 + 146
Lines +4349 −19105

So half the projects, but more builds, since a lot of examples now also compile for boards that never had them. That found a few boards that were missing clocks, connections or aliases, which is fixed here too.

The commits are sorted so that moves and changes are separate and you can review them one by one:

  1. Preparation: the hosted board, the scripts and removing all build paths. The board fixes and connections moved to Add board connections, hosted configuration and fixes for deduplicated examples #1387 and Fix bugs found by running the examples on hardware #1393.
  2. Moving the integration tests out of the examples. This commit only moves files.
  3. One commit per integration test that adds the pass result or merges several tests.
  4. Sorting the remaining examples into the new folders. This commit only moves files.
  5. One commit per merged example, which lists the folders it replaces, then one commit per category for the remaining small changes.

Only the last commit is guaranteed to compile, since the scripts and CI expect the final layout from the start.

The CI now uses all 20 concurrent jobs and splits the configurations in a stable pseudo-random order (sorted by hash) instead of contiguous chunks, so that every part gets a similar mix of examples and targets. The Cortex-M examples and the HAL compile are matrix jobs now and the STM32 unit tests have their own job, so the check names changed.

  • Rebased on develop.
  • Multi-configuration syntax in examples_compile.py with --target REGEX filter and --split/--part over configurations.
  • New folder layout, near-duplicates merged into the most complete example of the group.
  • Integration tests moved to test/integration with a pass result.
  • examples_check.py checks for unknown configurations, boards without any example, and targets that no CI job compiles. No more silent failures.
  • CI workflows select the examples by target instead of by folder.
  • Syntax and noteworthy examples documented in examples/README.md.
  • Another sweep through all examples: removed the ones that showed nothing new (can/app_can2usb didn't even use CAN, lol), merged the remaining near-duplicates (L3GD20, LSM303, DOGM128, servo PWM, LVGL, encoder, TCS3472, nRF logging) and cleaned up test benches and dead code in the rest, e.g. driver/pressure/bme280 read four sensors on two buses.
  • CI partitioned into 20 jobs: 7x Cortex-M examples, 7x HAL compile, STM32 unit tests, AVR, hosted, docs, macOS and Windows.
  • Homepage lists the number of examples per folder instead of every single example. The descriptions come from the table in examples/README.md.
  • The Windows fixes that only showed up because Windows compiled a pseudo-random part of all examples moved to Fix build system on Windows and test it deliberately #1389, which is merged. The Windows job now compiles the explicit list of examples from there with the new paths.
  • test/integration/flash and spi_flash generate the CompilationDB again, so that it keeps getting compiled on Windows.
  • Examples and tests use the board logger only, no __has_include guessing: if a board has no logger, the example does not list it. Same for Board::i2c, Board::spi and the Board::D* aliases instead of hardcoded pins.
  • All build artifacts go into the top-level build/ folder, also for the integration tests, so cleaning up is a single rm -rf build.
  • Integration tests run on hardware, see below.
  • driver/inertial/lis3mdl now compiles for all 39 boards with Board::i2c, driver/pwm/sk6812 only for the 6 boards whose SPI can do exactly 3 MHz.
  • CI passed after the rebase.
  • Compile-all job passed.
  • Do we need negative filters? no
  • Do we want config globbing support? no, --target takes a regex instead.

I ran all examples and integration tests that need no external hardware on every bare dev board I had in my box, 22 boards in total: NUCLEO-C031C6, -F103RB, -F207ZG, -F401RE, -F429ZI, -F722ZE, -G071RB, -H563ZI, -H723ZG, -L152RE, -L432KC, -L476RG, -U083RC, -U385RG-Q, -U575ZI-Q, DISCO-F072RB, -F303VC, -F407VG, -F469NI, -F746NG, rp-pico and a Mega 2560 Pro. That's all STM32 families except G4 and all cores from M0 to M7 and M33.

ran on hardware
Integration tests 28/30 (93%), missing fdcan_samv71 and spi_loopback (no SAM boards)
Examples 36/79 that need no external hardware (45%)

Every test passes on every board it fits on, but only after fixing a bunch of real bugs, whoops. The ones in modm itself went into #1393 (F4 dual-bank flash sectors, H5 and L4 delay tuning, L4 boot frequency, ARMv8-M stack overflow detection, recursive static init without fibers, newlib malloc asserting instead of returning nullptr, the monochrome display buffers), the ones in the tests and examples are fixed here (e.g. the flash test divided by zero when the H7 verified 512kB in less than 1ms, lol).

Also missing: examples/analog is still a mess with one example per family and macros in the merged ones. The ADC drivers have ten different APIs with nine initialize() signatures between them, so this needs fixing in the drivers first and not in the examples. I'm leaving the folder as is, its shape alone is a good reminder to fix this later, lol.

cc @rleh @chris-durand

@salkinium

Copy link
Copy Markdown
Member Author

Ok, so in order to deduplicate more examples we need Board interfaces for setting up a default I2C and SPI connection similar to Board::initializeUsbFs() and the LEDs and Button. Then it should also be much easier to anyone to test any sensor example on the hardware they have at hand instead of having to port examples to their board first.

@salkinium

Copy link
Copy Markdown
Member Author

I've prototyped this for some NUCLEO-32 boards, I think it's a good idea to deduplicate all the I2C/SPI examples too.

@salkinium
salkinium force-pushed the feature/dedup_examples branch 4 times, most recently from 2afca50 to 87a6802 Compare January 13, 2024 00:04
@salkinium
salkinium force-pushed the feature/dedup_examples branch from 87a6802 to 6221a7b Compare February 11, 2024 19:25
@salkinium
salkinium force-pushed the feature/dedup_examples branch 2 times, most recently from 3125e63 to 7e9a13f Compare April 6, 2024 19:51
@salkinium

salkinium commented Apr 7, 2024 •

Copy link
Copy Markdown
Member Author

ok, so I think the deduplication is a good thing, but I don't like the idea of putting everything into the example/generic folder.
Here is the new structure, which basically flips the example on the head: first use-case, then board.
What do you think about this, @rleh @chris-durand ?

  • core
    • custom_allocator
    • delay
    • exceptions_rtti
    • fiber
    • flash
    • hard_fault
    • itm
    • multi-core
    • resumable
    • rtt
    • threadsafe_statics
    • tlsf_allocator
    • unaligned_access
    • vector_table_ram
  • gpio
    • blinky
    • exti
    • gpio
    • ports
  • analog
    • adc_dma
    • comp
    • dac
  • timer
    • encoder_input
    • pwm
    • timer
  • logging
    • logger
    • printf
    • uart
  • ui
    • game_of_life
    • graphics
    • lvgl
  • driver
    • can
    • i2c
    • single wire
    • spi
  • communication
    • amnb
    • ethernet_freertos
    • nrf24
    • ros
    • sab
    • xpcc
  • ext
    • cmsis_dsp
    • etl
    • freertos
    • nanopb
    • usb
    • usb_dfu
    • usbfatfs
  • board
    • super custom board-specific examples
    • ideally we get rid of this section

@rleh

rleh commented Apr 7, 2024

Copy link
Copy Markdown
Member

What about a more flat structure?
E.g. with the LIS3 driver/examples it would be nice to have lis3_spi/ and lis3_i2c/ examples located next to each other.

I would prefer no subdirectories over non-obvious subdirectories.

@salkinium

Copy link
Copy Markdown
Member Author

it's flat like that, the second indentation is already the example folder. I just didn't list all the i2c drivers.

@salkinium

salkinium commented Apr 7, 2024 •

Copy link
Copy Markdown
Member Author

ah, you mean completely flat? I think it's just too much, particularly because not everything can actually be deduplicated. I currently have 285 examples in this PR (down from 374 on develop), The analog examples can be fairly device specific, so they would be multiple ones like examples/analog/adc_f1, examples/analog/adc_f3, examples/analog/adc_h7 etc. I've now roughly used the same folder names as in src/modm to reflect the top-level use cases.

@rleh

rleh commented Apr 7, 2024

Copy link
Copy Markdown
Member

I think tags instead of categories are more useful for users (including me) to find an example. But that is not possible to implement using filesystem structures anyway, but we could generate a table of all examples inside examples/README.md with tag/keywords and maybe even a short description.

@rleh

rleh commented Apr 7, 2024 •

Copy link
Copy Markdown
Member

I'm also a bit skeptical about the impact of deduplication, use of scripting and undocumented magic XML comment syntax for new modm users. The examples are most users' first exposure to modm, and I find it unfavorable to add unnecessary complexity.
The project.xml files of the examples are already special: The <repositories>…</repositories> block is missing and <extends>modm:some-configuration</extends> is used. From my experience, that makes the step from a functional modm-example to an own modm-project complicated for new users.

What do we think about generating the examples completely in a separate repo (e.g. https://github.com/modm-io/modm-examples) for all boards, including the modm repo as submodules and a project.xml file which would be at most similar to the one in a custom project?

@salkinium

Copy link
Copy Markdown
Member Author

To get users started with their own projects, I created the modm-template repo once. Back then GitHub template repos didn't support submodules, but that has been fixed now. We should get that working, including how to setup multiple lbuild repos, CI for building, using our unit test system, documentation etc. We can document this in Readmes, which we can then also put onto the website like our other readmes.

I hope users are not even gonna notice that there's magic XML syntax in the project.xml (I'll document it, don't worry). It will look just like more documentation. This is mostly just an internal refactoring to make it more maintainable and not copy around blinky so much and to actually force us to design more APIs to be compatible across devices by actually testing it.

@rleh

rleh commented Apr 7, 2024 •

Copy link
Copy Markdown
Member

(deleted)

@salkinium
salkinium force-pushed the feature/dedup_examples branch 2 times, most recently from 2935385 to a17f93b Compare April 12, 2024 21:27
@salkinium
salkinium force-pushed the feature/dedup_examples branch 2 times, most recently from b6e6323 to 45bf5de Compare October 4, 2026 22:00
Replaces:
- examples/stm32f3_discovery/accelerometer
Replaces:
- examples/rp_pico/rtc_mcp7941x
Replaces:
- examples/stm32f072_discovery/tmp102
- examples/stm32f746g_discovery/tmp102
Replaces:
- examples/nucleo_f103rb/rtos
- examples/nucleo_f303k8/rtos
- examples/nucleo_f303re/rtos
- examples/nucleo_f411re/rtos
- examples/nucleo_f767zi/freertos_blink
- examples/nucleo_g071rb/rtos
- examples/nucleo_l552ze-q/freertos_blink
Replaces:
- examples/nucleo_l452re/lvgl
Replaces:
- examples/weact_u585ci/usb
Replaces:
- examples/nucleo_f429zi/usbfatfs
Replaces:
- examples/feather_m4/usbserial
- examples/nucleo_f446ze/usbserial
- examples/samd21_xplained_pro/usbserial
- examples/same54_xplained_pro/usbserial
- examples/samg55_xplained_pro/usbserial
Replaces:
- examples/arduino_uno/basic/blink
- examples/avr/mega_pro
- examples/black_pill_f103/blink
- examples/black_pill_f401/blink
- examples/black_pill_f411/blink
- examples/blue_pill_f103/blink
- examples/feather_m4/blink
- examples/feather_rp2040/blink
- examples/nrf51422-dk/blink
- examples/nrf52840-dk/blink
- examples/nrf5340-dk/blink
- examples/nucleo_c031c6/blink
- examples/nucleo_f031k6/blink
- examples/nucleo_f042k6/blink
- examples/nucleo_f072rb/blink
- examples/nucleo_f091rc/blink
- examples/nucleo_f103rb/blink
- examples/nucleo_f207zg/blink
- examples/nucleo_f303k8/blink
- examples/nucleo_f303re/blink
- examples/nucleo_f334r8/blink
- examples/nucleo_f401re/blink
- examples/nucleo_f411re/blink
- examples/nucleo_f429zi/blink
- examples/nucleo_f439zi/blink
- examples/nucleo_f446re/blink
- examples/nucleo_f446ze/blink
- examples/nucleo_f722ze/blink
- examples/nucleo_f746zg/blink
- examples/nucleo_f767zi/blink
- examples/nucleo_g070rb/blink
- examples/nucleo_g071rb/blink
- examples/nucleo_g431kb/blink
- examples/nucleo_g431rb/blink
- examples/nucleo_g474re/blink
- examples/nucleo_h503rb/blink
- examples/nucleo_h533re/blink
- examples/nucleo_h563zi/blink
- examples/nucleo_h723zg/blink
- examples/nucleo_h743zi/blink
- examples/nucleo_l031k6/blink
- examples/nucleo_l053r8/blink
- examples/nucleo_l152re/blink
- examples/nucleo_l432kc/blink
- examples/nucleo_l452re/blink
- examples/nucleo_l476rg/blink
- examples/nucleo_l496zg-p/blink
- examples/nucleo_l552ze-q/blink
- examples/nucleo_u083rc/blink
- examples/nucleo_u385rg-q/blink
- examples/nucleo_u575zi-q/blink
- examples/olimexino_stm32/blink
- examples/rp_pico/blink
- examples/samd/blink
- examples/samd21_xplained_pro/blink
- examples/same54_xplained_pro/blink
- examples/same70_xplained/blink
- examples/samg55_xplained_pro/blink
- examples/samv71_xplained_ultra/blink
- examples/srxe/blink
- examples/stm32f030f4p6_demo_board/blink
- examples/stm32f072_discovery/blink
- examples/stm32f0_discovery/blink
- examples/stm32f1_discovery/blink
- examples/stm32f3_discovery/blink
- examples/stm32f401_discovery/blink
- examples/stm32f407vet6_devebox/blink
- examples/stm32f429_discovery/blink
- examples/stm32f469_discovery/blink
- examples/stm32f746g_discovery/blink
- examples/stm32f769i_discovery/blink
- examples/stm32h750vbt6_devebox/blink
- examples/stm32l1_discovery/blink
- examples/stm32l476_discovery/blink
- examples/thingplus_rp2040/blink
- examples/weact_g0b1cb/blink
- examples/weact_h503cb/blink
- examples/weact_h523ce/blink
- examples/weact_h562rg/blink
- examples/weact_u585ci/blink
Replaces:
- examples/avr/logger
- examples/nrf51422-dk/uart_logging
- examples/nrf52840-dk/uart_logging
- examples/nrf5340-dk/uart_logging_net
- examples/rp_pico/logger
Replaces:
- examples/arduino_nano/printf
- examples/linux/printf
Replaces:
- examples/stm32f072_discovery/uart
- examples/stm32f401_discovery/uart
Replaces:
- examples/samv71_xplained_ultra/pwm
Replaces:
- examples/nucleo_g474re/servo_pwm
Replaces:
- examples/same70_xplained/timer
Replaces the table of every single example, which is not useful anymore with
examples that run on many boards.
@salkinium
salkinium force-pushed the feature/dedup_examples branch from c1994ac to 27c16b8 Compare October 17, 2026 14:54
@salkinium
salkinium marked this pull request as ready for review October 17, 2026 14:59
@salkinium

Copy link
Copy Markdown
Member Author

@chris-durand Good point, that's how it's sorted now: device drivers are in examples/driver/<category>/ like src/modm/driver (e.g. driver/inertial/lis3mdl), and the peripheral examples are in analog/, timer/, can/ etc. The bus doesn't matter for the folder anymore.

This PR is now complete: I reviewed it myself and it was tested extensively on hardware, way more than ever before (22 boards, see the description). So I'm going to merge it now.

@salkinium
salkinium merged commit 8dcc5e2 into modm-io:develop Oct 17, 2026
20 checks passed
@salkinium
salkinium deleted the feature/dedup_examples branch October 17, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants