Skeleton project for development in embedded C using the approach outlined in Test Driven Development for Emedded C with testing done via CppUTest and build done using CMake
The demo source code used here (e.g. LedDriver
example) was taken from the code from the book Test Driven Development for Emedded C. There isn't very much code, but I just used it as a simple demonstration of a cross-platform build environment that promotes tdd in embedded c.
Goals
- Support dual targeting local dev machine + raspberry pi embedded target...CHECK
- Support cross-compiler builds...CHECK
- Support build platform independence...works on Mac OS X and Linux (see below). have not tested Windows
- Provide a good environment for getting started on a new project...SUBJECTIVE CHECK
- Out Of Source Build...CHECK
Why? I'm going to build a Raspberry Pi project, and compiling on the target is painful for a lot of reasons. I'd prefer to develop, build, & test on a development machine, but still have the capability of building and running tests on the target. This setup ought to work similarly for any target - not just the Raspberry Pi. You can use it and modify it to suit your needs.
I typically use Mac OS or Linux (Ubuntu) to develop with. Technically this should port to Windows with something like Cygwin, but I haven't bothered to try.
NOTE I'm using Mac OSX and homebrew which makes installation a bit simpler. I also use Sublime Text 2 as my editor along with the SublimeClang plugin. I've already configured the cmake-tddc.sublime-project
file so stuff "should just work" in the editor (including intellisense and auto-compilation on save).
Assume the basic GNU toolchain (i.e. make, gcc, g++, etc.)
Install CMake
With homebrew (Mac): brew install cmake
With aptitude (Ubuntu): sudo apt-get cmake
Useful links:
Install CppUTest
With git: git clone git://github.com/cpputest/cpputest.git && cd cpputest && make
With homebrew: brew install cpputest
Now, create an environment variable that points to it. Add the following line to .bash_profile
or similar: export CPPUTEST_HOME=/path/to/cpputest
$ mkdir build && cd build
$ cmake ..
$ make
~/git/cmake-cpputest$ cd build && ./bin/RunAllTests
......!.........
OK (16 tests, 15 ran, 20 checks, 1 ignored, 0 filtered out, 0 ms)
These 16 tests are for LedDriver
which is sample code from the book. There's even an example of using a stub via the linker (RuntimeErrorStub.c
).
I gave up trying to do this on the Mac. It might be possible, but I couldn't find anything decent on how to setup Gnu compiler toolchain for the Arm/Pi hardware like there is for Linux. So, the path I'm taking is to use Linux to cross-compile when I need to, but I can still develop and test on the Mac using tdd/mocks. Only if I want to cross-compile for the Pi and push the binaries will I need to switch.
So, this step is for Ubuntu only; I grab the pre-built toolchain for Pi:
$ mkdir -p ~/git/raspberrypi/ && cd ~/git/raspberrypi
$ git clone git://github.com/raspberrypi/tools.git
Now, create an environment variable that points to the tools. Add the following line to .bashrc
or similar: export PI_TOOLS_HOME=~/git/raspberrypi/tools
this is my path, yours may be different
Now, edit the file Toolchain-raspberrypi-arm.cmake
and make sure the paths are consistent with $PI_TOOLS_HOME
. This is the cross-compiler configuration for CMake.
In order to build, you'll also need to cross-compile a version of CppUTest, and you'll have to override the environment variable $CPPUTEST_HOME
with the Arm version of the library.
Build as follows (create a different build directory for the Arm stuff so we don't intermix them)
$ mkdir pibuild && cd pibuild
$ cmake -DCMAKE_TOOLCHAIN_FILE=../Toolchain-raspberrypi-arm.cmake ..
With fingers crossed, it will build ok.
Actually, instead of cross-compiling the CppUTest library, I chose to build it with the QEMU emulator.
It seemed easier than messing with the cross-compiler. This is a one-time build, since the code for CppUTest will not change often. I wanted to setup the Emulator
anyway with QEMU, so it kills two birds with one stone.
You might ask why not just build the whole source - including CppUTest in QEMU or on the Pi itself, and the reason is that it is slow. It took about 5 minutes to compile CppUTest. I want a dev. environment that is fast and allows me to mock the hardware and dependencies.
Install QEMU
$ sudo apt-get install qemu qemu-system
Download the Raspbian Wheezy Image
$ wget http://files.velocix.com/c1410/images/raspbian/2012-08-16-wheezy-raspbian/2012-08-16-wheezy-raspbian.zip
$ unzip ./2012-08-16-wheezy-raspbian.zip
Download the QEMU Kernel Image
$ wget http://xecdesign.com/downloads/linux-qemu/kernel-qemu
Launch the image. Consult QEMU docs or online tutorials for tweaks. I had to use the -cpu arm1136-r2
target.
$ qemu-system-arm -kernel ./kernel-qemu -cpu arm1136-r2 -m 256 -M versatilepb -no-reboot -serial stdio -append "root=/dev/sda2 panic=1" -hda ./2012-08-16-wheezy-raspbian.img
Now, in the emulator, grab the source for CppUTest from git and build it
$ mkdir ~/git && cd ~/git
$ git clone git://github.com/cpputest/cpputest && cd cpputest
$ make
Now, kill the emulator, mount the filesystem and copy the library to the host. Run fdisk
to figure out the start sector.
$ mkdir -p /mnt/pi
$ fdisk -lu ./2012-08-16-wheezy-raspbian.img
Disk ./2012-08-16-wheezy-raspbian.img: 1939 MB, 1939865600 bytes
255 heads, 63 sectors/track, 235 cylinders, total 3788800 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000108cb
Device Boot Start End Blocks Id System
./2012-08-16-wheezy-raspbian.img1 8192 122879 57344 c W95 FAT32 (LBA)
./2012-08-16-wheezy-raspbian.img2 122880 3788799 1832960 83 Linux
$ sudo mount -t ext4 -o loop,offset=$((512*122880)) ./2012-08-16-wheezy-raspbian.img /mnt/pi
Now, create a directory on the host to hold the Arm library and include files.
$ mkdir ~/cpputest-arm
# Assume you git cloned the ccputest source to ~/git/cpputest
$ cp -R ~/git/cpputest/include ~/cpputest-arm
$ mkdir ~/cpputest-arm/lib
$ cp /mnt/pi/home/pi/git/cpputest/lib/libCppUTest.a ~/cpputest-arm/lib
$ sudo umount /mnt/pi
Now, if you want to build for the Pi target, including the test binary, you update CPPUTEST_HOME
and use the CMake toolchain file for the Pi.
$ export CPPUTEST_HOME=~/cpputest-arm
$ cd ~/git/cmake-cpputest/pibuild
$ cmake -DCMAKE_TOOLCHAIN_FILE=../Toolchain-raspberrypi-arm.cmake ..
$ make
Outputs in bin
and lib
directories. You can re-mount the Pi filesystem, copy them over, restart emulator and validate that they work ok (they do for me).