STM32 Blue Pill — Unit Testing with Qemu Blue Pill Emulator

Emulate STM32 Blue Pill","type": "shell","options": { "cwd": "${workspaceFolder}"},"windows": { "command": "bash", "args": [ "-c", ".

/qemu_stm32/arm-softmmu/qemu-system-arm -M stm32-f103c8 -semihosting -kernel .


bin" ]},"osx": { "command": ".

/qemu_stm32/arm-softmmu/qemu-system-arm -M stm32-f103c8 -semihosting -kernel .


bin"}From the Task definition, we see that the command line for starting Qemu looks like this….

/qemu_stm32/arm-softmmu/qemu-system-arm -M stm32-f103c8 -semihosting -kernel .


bin1️⃣ qemu-system-arm: This is the Windows or macOS executable that emulates the Blue Pill.

It’s a highly complex Windows or macOS program that can execute Blue Pill machine code (Arm Cortex-M3), read and write to simulated RAM and ROM, and access Blue Pill peripherals (like the Blue Pill onboard LED) simulated in software.

2️⃣ -M stm32-f103c8: Emulate the Blue Pill microcontroller (STM32F103C8)3️⃣ -semihosting: The Logging library in our program uses Arm Semihosting to display console messages.

This option enables the display of Arm Semihosting messages.

4️⃣ -kernel .


bin: Specifies the Blue Pill ROM image that will be “flashed” into the emulated Blue Pill ROM for execution.

The firmware.

bin file contains the executable code and data of our Blue Pill program, produced by the gcc compiler during the PlatformIO Build step.

Qemu Blue Pill Emulator is still under development but it’s already great for running computation-based Unit Tests.

What about other types of Unit Tests with sensors and actuators?Beware Of Side EffectsWhen a Blue Pill program accesses some sensor or actuator, we say that the program causes a Side Effect.

Programs with Side Effects are harder to test because we need to reset the sensor and actuator back to their original states before starting the test.

Just imagine how we would test a program that uses a real BME280 Humidity Sensor… Before every test we would have to reset the Humidity Sensor to a fixed value, because it affects the outcome of the test.

Automating a test that causes Side Effects is no longer possible if somebody needs to tweak the sensor or actuator by hand.

Qemu Blue Pill Emulator may not be 100% accurate when emulating Timers, GPIOs, UART, I2C, SPI, … So beware if we’re using these in our Unit Test.

If our Unit Test is simply based on some computation without Side Effects, it should run fine on the Qemu Emulator.

nano-float unit tests automatically extracted from the nano-float source code.

From https://docs.


com/spreadsheets/d/1Uogm7SpgWVA4AiP6gqFkluaozFtlaEGMc4K2Mbfee7U/edit#gid=1740497564How I used Qemu Emulator for Unit TestingThe Test Cases we have seen in the demo above are the real-life Test Cases I used for testing nano-float, a floating-point math library that’s optimised for Blue Pill, taking up only a tiny fraction of the ROM space compared with the standard math library.

I wrote about nano-float in my previous article.

With over 200 Test Cases, writing the Unit Test code by hand will be tedious and hard to maintain.

That’s why I created a Google Sheets spreadsheet that generates the Unit Test code…nano-float Unit TestExport Test,// Unit Tests are autogenerated.

Do not update here,Function,VarX,X,VarY,Y,VarResult,Result //…docs.


comThe spreadsheet automatically extracts the Test Cases that I have embedded inside the nano-float library source code like this…nano-float library source code and the unit test cases below.

From https://github.


c#L555-L585The spreadsheet parses the Test Cases and generates Test Suites in this familiar format…Test Suite generated by the spreadsheet.

From https://github.



build_flags = ; Enable unit test.

Comment this line to disable unit test.



000001 -D UNITY_OUTPUT_CHAR=unity_output_char -D UNITY_INCLUDE_DOUBLE -D UNITY_EXCLUDE_SETJMP_H -D UNITY_EXCLUDE_MATH_HFloating Point PrecisionThe above gcc compiler options were configured in platformio.




How do they affect the Unit Testing?.If you remember this test case…x = 2205.

1969; y = 270.

8886; r = __wrap___aeabi_ddiv(x, y); TEST_ASSERT_EQUAL_DOUBLE( 8.

140604292687105 , r );It divides two doubles and compares the result of the division to 8.


But in reality, there will be some loss of precision when we store a floating-point number as float or double.

As we have seen in our previous article, floats can store up to 6 significant decimal digits and doubles can store up to 15 significant decimal digits.

So the result of our division may actually be 8.

14060 if we store the result as a float.

That’s why we have defined UNITY_FLOAT_PRECISION and UNITY_DOUBLE_PRECISION as 0.

000001… It means that we are only interested in comparing the first 6 decimal places for floats and doubles.

Any discrepancy beyond the 6th decimal place should be ignored.

Why have we set UNITY_FLOAT_PRECISION and UNITY_DOUBLE_PRECISION to the same precision?.The nano-float library downscales the precision of all math computations from double to single.

This was done to reduce the math library code size to fit on Blue Pill.

So for this specific library it makes sense to set both precisions to be the same.

For other libraries, probably not.

Test CoverageAt the end of the Unit Testing, we saw the message All Functions Called followed by a list of numbers…> Executing task: bash -c '.

/qemu_stm32/arm-softmmu/qemu-system-arm-M stm32-f103c8 -semihosting -kernel .


bin' <Starting.










45 Tests 0 Failures 0 IgnoredAll functions calledUsage: 01 > 04 / 02 > 04 / 03 > 04 / 04 > 04 .

DoneWhen we run Unit Tests, we should ensure that all the functions that we’re testing (aeabi_ddiv, aeabi_dmul) are actually called.

It’s possible that we may have omitted some compiler option (like wrap) or preprocessor symbols (like UNIT_TEST) that could cause the functions being tested to be called incorrectly.

In Unit Testing, the Test Coverage info tells us the functions that have been called when we run Unit Tests.

For our simplified Test Coverage, we used an array of counters to track the functions called…// We count the number of times each function was called.

// So we can check whether our unit tests cover all functions.


static uint8_t float_usage[LAST_FLOAT_USAGE_INDEX];double __wrap___aeabi_ddiv(double n, double d) { float_usage[USAGE_AEABI_DDIV]++; return qfp_fdiv_fast(n, d); }float_usage_index is an array of counters that count how many times each function was called.

When our Unit Test code shows the message…All functions called…It means that the code has verified that every counter in the float_usage_index array was one (or greater).

So all our functions have indeed been called and we have 100% Test Coverage.

Below that we see…Usage: 01 > 04 / 02 > 04 / 03 > 04 / 04 > 04 .

This is a diagnostic message for Test Coverage that says…Function 01 (aeabi_ddiv): Called 04 timesFunction 02 (aeabi_dmul): Called 04 timesFunction 03 (aeabi_dadd): Called 04 timesFunction 04 (aeabi_dsub): Called 04 times.

And that’s all you need to get your Unit Tests running on Blue Pill.

Build your Unit Tests early, and run them often!The Big PictureThis is the third article in a series of articles that explain how I ported and optimised the MakeCode visual programming tool for Blue Pill…1️⃣ The first article explains RAM and ROM memory optimisation…STM32 Blue Pill — Analyse and Optimise Your RAM and ROMTips and tools to prevent Blue Pill Bloatmedium.

com2️⃣ The second article explains floating-point math optimisation…STM32 Blue Pill – Shrink your math libraries with QfplibFloating-point math libraries can bloat your Blue Pill code.

Here’s one way to fix thatmedium.

comIn the next article we’ll finally integrate all the tools and techniques from the three articles and learn about the MakeCode Bootloader, an incredibly complex Blue Pill program that does so much and still fits in 64 KB ROM!.

. More details

Leave a Reply