Writing C on Raspberry Pi with Test-Driven Development

The Raspberry Pi is a great product. Undeniably, it presents us tinkerers with unlimited possibilites to get our hands dirty writing custom programs and bringing the Pi to life.

If you want to write a program to run on the Pi, you have a lot of languages to choose from. Being an embedded developer, I thought it would be fun to experiment with C on Raspberry Pi.

Options for Writing C on Raspberry Pi

I haven’t actually developed an official product on the Pi (yet!), but I couldn’t help but wonder what my development approach would be if I had to write a C application specifically for the Raspberry Pi. I came up with this short list of things I feel would be important to efficiently develop an application.

  1. Develop primarily on my laptop. Yes, you can install text editors, version control systems, etc. on the Pi and do all the development on it, but I already have my laptop set up just the way I like it, and it would be more convenient to work on my code on my blazing fast development machine.
  2. Maintain a test-driven development pattern.
  3. Easily run tests locally and be able to run them on the Pi.

In order to achieve this, there are a couple approaches you could take. You could download a cross-compiler for the Pi and keep the source code only on your development machine. Then when you wanted to run code/tests on the Pi, you would use the cross-compiler to produce an output that could run on the Pi, transfer the executables to the Pi, and return the results back to the development machine. This approach would probably be quite fast, and if your project contains many files, it might be a good way to go about it. Setting up a cross-compiler isn’t the simplest thing to do, but there are many documented cases online of people who have already done it.

The other approach would be to develop the source code on your development machine but build the code for the Pi on the Pi itself. This removes the need to set up a cross-compiler and it makes getting the test results back to your development machine very simple. This approach sounded more interesting to me (and I was curious how fast the Pi is at compiling C code), so I decided to go down this route.

I will use my favorite text editor to develop the code on my development machine. I will use rsync to transfer my source files to the Raspberry Pi. Finally, I will install Ruby and Ceedling (a C unit testing tool) on my development machine and on the Pi to assist in running tests. Here’s how to make it all happen.

1. Set Up SSH Keys

This step is important because it allows you to transfer files from your development machine to the Pi and execute commands remotely without having to type in a username and password every time. First, make sure you have an SSH key generated on your development machine. If you don’t, or if you’re not sure, check out this excellent GitHub article that explains how to generate one.

Now if you open up your ~/.ssh (or your/user/directory/.ssh on Windows) directory on your development machine, you should have a file called rd_isa.pub. This is the “public” piece of your SSH key. You need to transfer this file to the Raspberry Pi so that it can recognize you as an approved user. Do that with the following command:

scp ~/.ssh/id_rsa.pub user@remote.host:pubkey.txt

Make sure to replace ‘user’ with a username on the Raspberry Pi and ‘remote.host’ with the IP address of the Pi.

Once you’ve done that, you need to append the key to the “authorized_keys” file on the Pi. To do so you will need to SSH into the Pi and manually edit/create the file. That can be done as follows:

scp ~/.ssh/id_rsa.pub user@remote.host:pubkey.txt
ssh user@remote.host
mkdir ~/.ssh
cat pubkey.txt >> ~/.ssh/authorized_keys
rm ~/pubkey.txt

2. Install ‘rsync’

The next step is to install rsync, a utility that allows you synchronize directories between two computers. When we make changes on our local machine, rsync will transfer those changes to the Pi for testing. rsync is smart enough to only transfer files that have been updated since the last transfer, which will speed up the process. For rsync to work, it must be installed on both your development machine and the Raspberry Pi. To install it on the Pi execute the following command.

sudo apt-get install rsync

The process for installing rsync on your development machine will vary greatly depending on which OS you are running. On the Mac, it’s already installed. Some Linux distros come with it as well. Windows, on the other hand, is a little behind the game. Search Google for “Installing rsync on Windows” for instructions on getting it setup.

3. Install Ruby

Ruby is another component that needs to be installed on by the development machine and the target. Ruby is a scripting language that Ceedling uses to automate unit test execution. Again, refer to the all-wise Google for instructions on installing the latest version on your dev machine. To install Ruby on the Raspberry Pi use the following command:

sudo apt-get install ruby

4. Install Rake

Rake is a Ruby gem (package) that provides build automation support similar to ‘make’. Once you have Ruby installed, Rake is as simple to install as typing the following:

sudo gem install rake

5. Setup a Ceedling Project

I am not going to go into all the details of how to use Ceedling because it is beyond the scope of this article. If you’re new to Ceedling, head over to throwtheswitch.org and read through some of the documentation.

All you need to do is install the Ceedling ruby gem on you dev machine (gem install ceedling) and then create a new project (rake ceedling new project_name).

6. Bring it all Together

Finally, we’re to the interesting part! We can already write code locally and execute tests on our development machine using the command “rake test:all”.  

The final thing we need to do is set up a custom rake task that will run tests on the Pi without having to manually SSH into it. Look in the root directory of your Ceedling project and you will see a file named Rakefile.rb. This is where we will put our custom rake task. Add the following to the bottom of the file:

desc "Run rake test:all on RPi with latest changes"
 
desc "Update the RPi with the latest changes on dev machine."
task :update_pi_source do
 
  #send the latest changes to the pi
  puts cmd = "rsync -r -v . #{REMOTE_RPI_USER}@#{REMOTE_RPI_IP_ADDR}:#{REMOTE_RPI_PROJ_ROOT} --exclude=#{PROJECT_BUILD_ROOT}"
  system(cmd)
end
 
desc "Run rake test:all in the project directory on the pi"
task :run_all_tests_pi do
 
  #execute tests on the pi
  puts cmd = "ssh #{REMOTE_RPI_USER}@#{REMOTE_RPI_IP_ADDR} "cd #{REMOTE_RPI_PROJ_ROOT} && rake test:all""
  system(cmd)
end
 
task :pi_test_all > [:update_pi_source, :run_all_tests_pi] do
end

This actually defines three rake tasks. The first one, update_pi_src, is the task that uses rsync to update the source code on the Pi. The second one, run_all_tests_pi, uses SSH to execute the necessary command to compile the code and run the tests on the Pi. The third task, pi_test_all, is just a wrapper that combines the first two.

If you’re observent, you noticed that the tasks above reference some defines for the IP address of the Pi, the username, and the remote source directory. If we want this to work, we need to add those definitions. The main configuration file for a Ceedling project is the project.yml file that lives in the project root directory. Add the following to the end of the project.yml file but change the values to reflect whatever is necessary for your project.

:remote_rpi:
  :ip_addr: 192.168.11.117
  :user: pi
  :proj_root: ~/CeedlingRPi/

7. Run the Tests

That’s it! The last thing to do is try running the tests. Type rake pi_test_all and watch as the Raspberry Pi is updated with the latest changes to your source code, compiles the code, and runs all your unit tests. When it’s finished, the build output will be printed to your console!