The Double Compilation Strategy
A major bottleneck in embedded development is the compile-flash-debug cycle. Flashing code to a physical microcontroller takes time, and debugging directly on hardware can be difficult due to limited hardware breakpoints and slow serial interfaces.
To solve this, we use a double compilation strategy:
- Target Environment: The compiler targets the microcontroller architecture (e.g., AVR, ARM) to compile the production binary.
- Native PC Environment: The compiler compiles the code for the host development machine (e.g., x86_64, Apple Silicon) to run unit tests in milliseconds.
However, native PC compilers cannot compile code that includes microcontroller hardware register files (like <avr/io.h>) because these registers do not exist on the PC. We must isolate our hardware drivers.
PlatformIO Directory Classification
PlatformIO structures its projects to enable this target isolation out-of-the-box. Code files are divided into three directories:
1. The lib/ Directory (Shared Business Logic)
Contains hardware-independent logic (the Unit Under Test, or UUT) and the public interface (.h files) of low-level drivers.
- Example:
room.c,room.h(checks temperature thresholds and makes decisions), andtempDriver.h(declares the sensor read function). - Compilation: Compiled for both the target microcontroller and the native host PC runner.
2. The src/ Directory (Target-Only Implementation)
Contains target-specific driver code that manipulates physical hardware registers, along with main.c (the microcontroller entry point).
- Example:
tempDriver.c(reads the physical microcontroller ADC register likeADMUX) andmain.c. - Compilation: Compiled only for the target microcontroller.
3. The test/ Directory (PC-Only Test Runner)
Contains unit test suites and mocked driver implementations.
- Example:
test_room.c(Unity assertions) and a mock implementation oftempDriver.hbuilt using a mocking framework. - Compilation: Compiled only for the native host PC.
project/
├── lib/ <-- Shared logic (UUT) & interface .h files
│ ├── room/
│ │ ├── room.h
│ │ └── room.c
│ └── tempSensor/
│ └── tempSensor.h
├── src/ <-- Target-only register drivers & main.c
│ ├── tempSensor.c <-- (Includes <avr/io.h>)
│ └── main.c
└── test/ <-- PC-only unit tests & ffff mocks
└── test_room/
└── test_room.c
If you are writing a register-level driver file called 'led_driver.c' that references 'PORTB' directly, where should you place it in a PlatformIO project to support PC unit testing?
Identify Native Test Folder
// Complete the path of the PlatformIO folder compiled only on the native host PC for testing: project//test_suite.c
References & Further Reading
- PlatformIO Core Community. PlatformIO Library Dependency Finder (LDF) & Unit Testing Guide. PlatformIO Docs.
- VIA University College: Embedded Software 1 (ESW1) Lecture Notes - Section 1 & Section 7 (TDD and Build Process).