Raspberry Pico: Unit Test Framework for Your Projects

The Raspberry Pico is a new microcontroller launched in February 2021. The community was excited about this new board, and several people started amazing projects. I grabbed two boards early on, and while still working on my Arduino based robot, did the usual blinking led and potentiometer tutorials.

The Pico captured me, I wanted more than just run demos. So, I decided to start library development for a shift register and a temperature sensor. When developing a library, I want to have tests for several reasons. First, I like to use TDD and start with writing a test that will cover a n new feature before its implementation. Second, once you have a substantial test suite, it helps you to keep the library in a working shape when you refactor its code base.

In this article, I will show how to install and use the unit testing framework cmocka. We will see the basic boilerplate code and an example for testing a Raspberry Pico program.

This article originally appeared at my blog.

Installation

Grab the CMocka source from the official cmocka mirror. Then, extract the tar, compile and install. The steps in a nutshell:

wget https://cmocka.org/files/1.1/cmocka-1.1.5.tar.xz
tar xvf cmocka-1.1.5.tar.xz
cd cmocka-1.1.5
mkdir build
cd build
cmake ..
make

The make step should show this output:

Scanning dependencies of target cmocka
[ 4%] Building C object src/CMakeFiles/cmocka.dir/cmocka.c.o
[ 9%] Linking C shared library libcmocka.so
[ 9%] Built target cmocka
Scanning dependencies of target assert_macro_test
[ 13%] Building C object example/CMakeFiles/assert_macro_test.dir/assert_macro.c.o
...
[ 95%] Building C object example/mock/uptime/CMakeFiles/uptime.dir/uptime.c.o
[100%] Linking C executable uptime
[100%] Built target uptime

If all goes well, you can install the compiled libraries in your system.

sudo make install[  9%] Built target cmocka
...
[100%] Built target uptime
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/lib/pkgconfig/cmocka.pc
-- Installing: /usr/local/lib/cmake/cmocka/cmocka-config.cmake
-- Installing: /usr/local/lib/cmake/cmocka/cmocka-config-version.cmake
-- Installing: /usr/local/include/cmocka.h
-- Installing: /usr/local/include/cmocka_pbc.h
-- Installing: /usr/local/lib/libcmocka.so.0.7.0
-- Installing: /usr/local/lib/libcmocka.so.0
-- Installing: /usr/local/lib/libcmocka.so

The files will be installed at /usr/local/lib.

Unit Test Example

Let’s write a very basic unit test example.

/*
* ---------------------------------------
* Copyright (c) Sebastian Günther 2021 |
* |
* devcon@admantium.com |
* |
* SPDX-License-Identifier: BSD-3-Clause |
* ---------------------------------------
*/
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
static void test_integers(void** state) {
assert_int_equal(1,1);
}
int main(int argc, char* argv[]) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_integers),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

The important things here:

  • Always include all four libraries: <stdarg.h>, <stddef.h>, <setjmp.h>, <cmocka.h>
  • Define test cases as functions that receive an argument void** state
  • The test functions include different type of assert statements, shown here is assert_int_equal see the official documentation for the full list of asserts.
  • In the main function, add all defined test functions to the struct CMUnitTest tests[]

Running Tests

To invoke that test on the CLI, you will need to add CMocka installation path to the environment variable export LD_LIBRARY_PATH.

export LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH}

Then, run your compiler and link to the CMocka library. I’m using clang in the following example.

clang -std=c18  -l cmocka simple.test.c -o tests.bin

Finally, you can run the test, and see formatted output that shows which tests were successful.

$> ./test.bin[==========] Running 1 test(s).
[ RUN ] test_integers
[ OK ] test_integers
[==========] 1 test(s) run.
[ PASSED ] 1 test(s).

Testing a Pico Program

Now that we have setup the testing framework, let’s use it to write tests for our Pico programs. At the time of writing this article, I was developing a library for working with shift registers. The library exposes a struct object that defines the pin layout, and several functions for setting bits or a bitmask to the shift register. I will not cover the entire library, but just highlight two test cases that show the essential how-to. Go to Github to see the entire rp2040-shift-register-74HC595 library.

ShiftRegister Struct: Definition and Testing

The shift register is controlled by three input pins:

  • Serial (SER): Set a single bit, low or high
  • Serial Clock (SRCLK): Send a clock signal that will write the active SER bit to the shift register
  • Register Clock (RCLK): Send a clock signal to copy the contents of the shift register into the storage register

These pins are defined in the following struct object.

typedef struct ShiftRegister
{
u_int8_t SERIAL_PIN;
u_int8_t SHIFT_REGISTER_CLOCK_PIN;
u_int8_t STORAGE_REGISTER_CLOCK_PIN;
} ShiftRegister;

The first test is about initializing a shift register and see that it’s defined pined are correctly defined inside the struct. We will use the familiar assert_int_equal test.

void test_shift_register_config(void **state)
{
ShiftRegister reg = {14, 11, 12};
assert_int_equal(reg.SERIAL_PIN, 14);
assert_int_equal(reg.SHIFT_REGISTER_CLOCK_PIN, 11);
assert_int_equal(reg.STORAGE_REGISTER_CLOCK_PIN, 12);
}

Running the tests gives this output:

Runing Tests
[==========] Running 1 test(s).
[ RUN ] test_shift_register_config
[ OK ] test_shift_register_config
[==========] 1 test(s) run.
[ PASSED ] 1 test(s).

Writing a single bit

The most basic function is to write a single bit into the shift register. To keep track of this, the register object holds two state variables: The serial_pin_state and the shift_register_state. If a new bit is written with the write_bit function, the state will be updated accordingly.

To implement this, we first add the state variables to the ShiftRegister.

typedef u_int8_t bitmask;typedef struct ShiftRegister;
{
bool serial_pin_state;
u_int8_t shift_register_state;
} ShiftRegister;

Then, we implement the write_bit function. This function sets the serial_pin_state to the given bit. If this bit is a 1, shift_register_state will shift right and add a 1, if the bit is a 0, it will just shift right.

bool write_bit(ShiftRegister *reg, bool b,)
{
reg->serial_pin_state = b;
(b) ? (reg->register_state += 0b10) : (reg->register_state <<= 0b01);
return b;
}

For testing, we will write two bits: 1 followed by 0. After each step, we test the pin_state is set correctly. Finally, we test that the resulting bitmask is correct. To receive the bitmask representation of the shift register, the method print_shift_register is called, and its compared to a string object. The test method uses assert_memory_equal, a convenient test method to test that any types are equal.

void test_write_bit(void **state)
{
ShiftRegister reg = {14, 11, 12};
write_bit(1, &reg);
assert_int_equal(reg.serial_pin_state, 1);
write_bit(0, &reg);
assert_int_equal(reg.serial_pin_state, 0);
printf("Shift Register: %s\n", print_shift_register(&reg));
assert_memory_equal(print_shift_register(&reg), ®"01000000", 8);
}

All tests are passed:

Running Tests
[==========] Running 2 test(s).
[ RUN ] test_shift_register_config
[ OK ] test_shift_register_config
[ RUN ] test_write_bit
Shift Register: 01000000
[ OK ] test_write_bit
[==========] 2 test(s) run.
[ PASSED ] 2 test(s).

Conclusion

This article introduced the CMocka unit testing framework for C programs. I showed how to compile, install and use it. Then, two examples were shown. The first example showed the necessary boilerplate code that you need to run a CMocka test. The 2nd example showed how to use CMocka for testing Pico code, but with a grain of salt: At the time of writing, I had no experience how to test that the hardware signals were transmitted from the Pico. In a future article about library design, I will cover this issue and detail how to test both the library function “as-is” and the hardware side. In my opinion, unit-testing helps you to write better code: By writing a test before the implementation, you structure the feature upfront, and when you have a substantial test suite, you can better maintain and refactor your code.

--

--

--

IT Project Manager & Developer

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Spring Message Channel Design

One Simple Trick to Writing and Understanding SQL Better

A Deep Diven Into Microservices vs. Monolith Architecture

Presentation Tips 101

A successful presentation :]

Discrete Wavelet Transform (DWT), Multiresolution Analysis (MAR), and Haar wavelet

Optimism + scaffold-eth 🏗 [Draft]

Blue Green Deployment on Amazon AWS.

Chainlink ETH node failover proxy

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Sebastian

Sebastian

IT Project Manager & Developer

More from Medium

How to manage and use Nginx Virtual host in Ubuntu

Simple Raspberry Pi Lite setup with SSH and VS Code from Linux Mint

Linux Kernel Development Model

Scheduling Algorithms in OS