In my last blog post, I discussed why embedded developers should not use IDEs. To summarize:
- IDEs slow down development and make it too easy to develop code without knowing what you’re actually doing.
- Developing outside of an IDE
- allows you the opportunity to work with tools that were actually designed for highly-efficient coding,
- forces you to get up-close and personal with the build tools, and
- allows you to easily incorporate Test Driven Development (TDD) practices into your development process.
In this post, I’ll walk through the process of setting up a development environment for Microchip PIC microcontrollers. As an end result, you will be able to write code, write unit tests, compile tests, run them on Microchip’s simulator, and compile your release executable — all without ever opening an IDE.
Why Microchip? Because I have a lot of experience with their products, and I know a lot of you are using them as well. I will be demonstrating the setup on a Windows. I chose Windows because I believe it is the most common environment for embedded developers. If you are developing on Linux or a Mac, you’re in luck. All of the tools we will be using have cross-platform support, including Microchip’s compiler, linker, and simulator.
1. Install the Compiler
The first thing we need to do is install a compiler. Microchip has several available, as do other vendors that support Microchip devices. For this example, I will be using a PIC24HJ128GP202 device, so I installed Microchip’s XC-16 compiler. This is a 16-bit C compiler. You should, of course, install the compiler that is appropriate for your device. After you’ve installed the compiler, add the bin path of the compiler to the Windows PATH environment variable. This site explains how to edit environment variables for several versions of Windows.
2. Install a Text Editor
All operating systems come with a text editor, but they are usually very basic. I would recommend downloading a full-featured text editor to allow you to write code more productively. My editor of choice is Sublime Text 2. It is free to download and has a lot of excellent features that make it a joy to use.
3. Install a Build Automation Tool
You could build your code by manually typing the instructions provided by the compiler’s command-line interface. However, as most projects will include many source files, typing in these commands would take a lot of time. Build automation tools simplify this process by wrapping up all of the necessary commands required to build a project into a single task.
Ceedling is a great build automation tool that incorporates support for Test-Driven Development (TDD). If you are unfamiliar with TDD, I would suggest that you take a look at throwtheswitch.org and read through the documentation.
Ceedling uses a scripting library called Ruby to automate many tasks. If you do not already have Ruby installed, go to RubyInstaller.org and install it. Once the installation is finished, make sure you add the Ruby bin directory to your system PATH environment variable. To verify that Ruby is installed and the PATH variable is set up correctly, open command prompt and type ruby -v. You should see the version number of Ruby printed to the console.
Now that Ruby is installed, you can use its package management system, RubyGems, to install Ceedling. Open command prompt and type gem install ceedling. This will download the latest version of the Ceedling gem and place it inside your Ruby installation directory.
4. Create a New Project
Ceedling is a very flexible tool. It does not enforce any particular directory structure for your source code and can be configured to work with any existing project. For this example, we will create a new project using a handy ceedling task. In the command prompt, navigate to a directory where you want to create the new project and enter ceedling new PIC_Demo.
The new task will create several folders and files in our project directory. If you downloaded Sublime Text, open it, then drag the new project directory from Windows Explorer into the Sublime Text window. This will open the entire directory structure for you to view. Let’s look at what’s inside.
- build – This is where Ceedling will place intermediate files produced while building, as well as the final executable.
- src – This is where Ceedling expects to find source code.
- test – This is where Ceedling expects to find test files.
- vendor – This is where the Ceedling tools exist in the project. Open vendor/ceedling/docs to view the documentation included with Ceedling.
- project.yml – This is the main configuration file for Ceedling.
- rakefile.rb – This is where you can set up additional extra tasks if needed.
5. Add Files to the Project
Explaining the entire process of TDD is beyond the scope of this post. To move forward with this tutorial, we will need some source files and test files to compile for this demonstration. I have created some sample files that define a simple led controller. Add the following files to the src directory of your project.
- main.c and main.h
- system.c and system.h
- gpio_access.c and gpio_access.h
- led_control.c and led_control.h
Then, add the following files to the test directory in your project.
6. Configure the Project
By default, Ceedling will attempt to build the project using GCC. We want to use Microchip’s compiler, so we need to modify the project configuration file. Open the project.yml file. (For a full explanation of how to configure Ceedling, read the documentation in the docs folder. For now, I’ll just point out some important changes.)
First we need to define a test compiler and linker so that Ceedling knows what compiler to use to build the tests. We also have to specify the command line arguments to be passed to these tools. Read your compiler’s documentation to determine which compiler arguments are important for your device. Add the following section to the bottom of the project.yml file.
:tools:
:test_compiler:
:executable: xc16-gcc
:arguments:
- -mcpu=24HJ128GP202
- -x c
- -c
- "${1}"
- -o "${2}"
- -D$: COLLECTION_DEFINES_TEST_AND_VENDOR
- -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR
- -Wall
- -Os
- -mlarge-code
- -mlarge-arrays
:test_linker:
:executable: xc16-gcc
:arguments:
- -mcpu=24HJ128GP202
- ${1}
- -o "./build/release/TestBuild.out"
- -Wl,-Tp24HJ128GP202.gld,-Map=./build/release/TestOutput.map,--report-mem
After making that change, we can try to build and run the tests. In the command prompt, navigate to the project’s root directory and type rake test:all. You should see the following error:
p24HJ128GP202.h: No such file or directory
The error is produced because the file p24HJ128GP202.h is referenced in our project, but the compiler can’t find it. The file exists inside the compiler’s support directory. We’ll add an include section to the paths section of the configuration file and add the directory that contains this file. This will tell Ceedling to instruct the compiler to include this directory when it compiles.
:include:
- "C:/Program Files (x86)/Microchip/xc16/v1.10/support/PIC24H/h"
The p24HJ128GP202.h file contains a check that makes sure that a compiler symbol is defined that matches the processor type. We need to add a define so that the check doesn’t fail. We also need to add a few other defines to alter the functionality of Unity, the unit testing framework, and CMock (the mocking framework). By default, these frameworks assume the code is targeting a 32-bit device. Our device is 16-bit, so we’ll need to update the common defines section of the config file as shown below:
:commmon: &common_defines
- __PIC24HJ128GP202__
- UNITY_INT_WIDTH=16
- CMOCK_MEM_INDEX_TYPE=uint16_t
- CMOCK_MEM_PTR_AS_INT=uint16_t
- CMOCK_MEM_ALIGN=1
- CMOCK_MEM_SIZE=4096
After making these changes, try running the tests again with the rake test:all command. All of the files now compile and link successfully but we still get an error message.
ERROR: Test executable "test_gpio_access.out" failed.
> Produced no final test result counts in $stdout: 'build\test\out\test_gpio_access.out' is not recognized as an internal or external command, operable program or batch file.
After the test files are compiled and linked, the test fixture attempts to run the executable that was produced. The problem is that this executable is compiled for a 16-bit PIC architecture, and our development machine’s architecture is completely different. It doesn’t recognize the output file as something it can run.
This is where a simulator comes into play. A simulator can load a hex file produced for a PIC device and execute the instructions just as a real device would. The Microchip compiler’s include a command line simulator called sim30.exe. We need to configure Ceedling to run the test on this simulator.
Inside the test directory, create a new folder named simulation, then add a file to it called sim_instructions.txt. Paste the following into this file:
LD pic24super
LC build/release/TestBuild.out
IO NULL test/simulation/out.txt
RP
E
quit
These are the instructions must be passed to the simulator so that it will load and run our code. They tell the simulator to:
- Run as a PIC24 device.
- Load a hex file from build/release/TestBuild.out.
- Output stdout data to test/simulation/out.txt.
- Reset, execute, and quit.
Next, add another file to the simulation folder named sim_test_fixture.rb. Paste the following in this file:
OUT_FILE = "test/simulation/out.txt"
File.delete OUT_FILE if File.exists? OUT_FILE
IO.popen("sim30 test/simulation/sim_instructions.txt")
sleep 1
if File.exists? OUT_FILE
file_contents = File.read OUT_FILE
file_contents.gsub!("\n", "")
print file_contents
end
This file contains a Ruby script that will be used as our test fixture. It deletes any previous output files, calls the simulator with the instruction file, and then prints out results to stdout.
The last thing we need to do is add the test fixture to our project.yml file so that Ceedling knows to use it. Add the following to the project.yml file, below the test_linker section.
:test_fixture:
:executable: ruby
:name: "Microchip simulator test fixture"
:stderr_redirect: :win #inform Ceedling what model of $stderr capture to use
:arguments:
- test/simulation/sim_test_fixture.rb
Finally, run the tests again. This time the process should succeed and you will see the following test summary printed to the console.
-------------------------
OVERALL UNIT TEST SUMMARY
-------------------------
TESTED: 25
PASSED: 25
FAILED: 0
IGNORED: 0
Congratulations! You now have a fully-functional TDD environment. To add support for a release build, copy the test_compiler and test_linker sections of the project.yml file and paste them at the bottom. Change their names to release_compiler and release_linker, and you should be good to go. When you execute the command rake release, Ceedling will build the entire project, using just the source code (not tests), and produce your release executable.
You can download the source code for this demo project at GitHub.
Had to set :use_test_preprocessor: in project.yml to FALSE since the configuration file lacks the preprocessor invocations and my system has no gcc (other than xc16-gcc) installed, which seems to be the default for undefined compiler settings.
Thanks for this great article!
@markus: Thanks for the feedback. I think you can configure Ceedling to use xc16-gcc as your preprocessor. Just use the compiler argument -E with tells xc16 to Preprocess Only.
Jordan.. I couldn’t get past loading ruby on my Windows 7 laptop. When I run “gem install ceedling” I get: ERROR: While executing gem … No such file or directory – c:/spb_data
Any idea?
I sure hope this works. I’m very interested in using ceedling to help with TDD for a medical device.
Brian
Hi thanks for this great article.
Couldn’t make it work, i’m on a linux machine (ubuntu 12.04), after compiling when running test_main.out, i get:
ERROR: Internal sanity check for test fixture ‘test_main.out’ finds that Unity’s final test count (2) does not match Ceedling’s summation of all test cases (0).
following markus i had to set :use_test_preprocessor: in project.yml to FALSE.
Any ideas?
Thanks in advanced
i believe the problem is cmock.c because when compiling i have this warning: warning: cast to pointer from integer of different size
I changed the cmock.c file as in
https://github.com/ThrowTheSwitch/CMock/commit/ff4966bf425663f3be3b34cc44a8c3f7d644fabc, the warnings are gone now but still the same error
I am running into the same problem. Anyone have any suggestions for a fix?
Please report bugs/issues related to Ceedling on the GitHub issues page (link below) and they will get addressed as soon as possible. https://github.com/ThrowTheSwitch/Ceedling/issues
Thanks!
So, I got past all errors and issues. I was able to create the whole setup for building release artifacts with xc16-gcc. However, I had one question (didn’t know if this is an issue to be reported or not so I wanted to ask first): every time I build using “rake release” the compilation happens in a different order (seems random or I’m missing something), and it seems that the linking happens with the *.o files in the same order as the compilation. So, depending on the order of compilation the linking phase will fail because of dependencies (it reports undefined references to functions). Is this expected behavior? How does Ceedling determine the order in which the files are compiled? I tried modifying the project’s YAML file to explicitly list the source directories in the order I wanted them to be built but it didn’t seem to matter.
Any help/guidance is appreciated.
Thanks.
Mohammad,
I am glad to hear you tried this out and you are having some success! What did you have to do to fix your earlier issues?
As for the order of compilation, I always thought it happened alphabetically. I am not positive about that though so I will look into this. The order of your files in the project yaml file should not matter at all. In fact, the order of compilation should not matter either. Try running “rake clobber” before doing your build. If you are automating this you could run it all in one command with, “rake clobber release”. Clobber will delete all object files that were produced by a previous build. If you don’t clobber, and Ceedling detects that a particular source file did not change since the last build, it will not recompile that file.
The fact that your linking step is failing is concerning. If you look at the command that links your object files, you will see that it includes every object file as arguments to a single command. The order of those input object files should make no difference. If you are getting a link error, I would guess that there there is another issue. Try using “rake clobber release” and let me know if that helps clear things up. If the linker is still reporting missing functions, try running the build with the verbosity turned up. You can specify verbosity as follows: “rake verbosity[4] clobber release”. This will print out the exact commands that ceedling is using for every step of the build. Look at the link command and make sure that all of the object files that you expect to be be included are actually being included.
Let me know if this helps.
Best regards,
Jordan
Jordan,
Thanks very much for the prompt response.
To fix my previous issues, I started from a clean project.yaml and only added what I needed – mostly stealing from the example one posted above :) Then, since I’m using a different PIC, I started adding and replacing the existing release compilation options with the ones I see from the output of MPLAB X. Then everything worked smoothly.
I tried what you suggested and I still saw the same problem. I also captured the verbose output in the case where it fails and in the case where it succeeds. What’s more bizarre is that after comparing the two linking commands, they are exactly the same ! One exception is the order of the *.o files listed (which I guess shouldn’t make a difference). It’s just interesting that the order of the files being compiled is not the same and is “random” every time I build (even after a rake clobber or rake clean).
Please let me know if you have any other suggestions. Thanks for your help.
– Mohammad
Hi Jordan,
So I thought I’d update you since I was just able to figure out the problem. Apparently in our source files hierarchy we had two files that had identical names but one was lower case and one was upper case (i’m working on a mac). So when I changed the file names to not match it all started working.
Thanks again for your help earlier.
-Mohammad
Oh interesting. I hadn’t thought of that. Glad you figured it out. Thanks for the update!
Jordan,
Thanks for this article. It’s just what I needed.
I did initially have an issue with ceedling not being able to parse test results from $stdout. I would get results like this:
ERROR: Test executable “test_BufferTools.out” failed.
> Produced no final test result counts in $stdout:
test_BufferTools.c:27:test_Pack_uint16:PASS———————–1 Tests 0 Failures 0 IgnoredOK
> And exited with status: [0] (count of failed tests).
> This is often a symptom of a bad memory access in source or test code.
Commenting this line from sim_test_fixture.rb completely fixed the issue:
file_contents.gsub!(“\n”, “”)
It appears that ceedling can’t digest the results without the newlines. If that’s the case, why did you need to remove them? I wonder what’s different about my case. I’m running on Win7 64-bit.
Thanks again for sharing this technique.
Bill,
Awesome. I’m glad you got it working. That is a strange issue. I think I added that line because my test output had extra lines inserted between each test result. I would bet that something is different with either the version of the simulator that you are using or the version Ruby. If I get a chance I’ll update mine to the most recent and check if I see the same results. I am using the same OS that you are.
Jordan,
I wanted to follow up with another issue I found and overcame.
I have a few tests that take longer than the 1s wait time specified in sim_test_fixture.rb. I wasn’t comfortable just increasing the wait an arbitrary amount, so some quick googling got me an acceptable answer. Assign a variable to the IO.popen() call, and then call readlines() on it. readlines() requires ‘out’ to have a value, and so waits for the process to close.
This is working well for me, and I thought I’d share it.
OUT_FILE = “build/test/out/TestBuildOutput.txt”
File.delete OUT_FILE if File.exists? OUT_FILE
out = IO.popen(“sim30 test/simulation/sim_instructions.txt”)
out.readlines #waits for the process to finish
if File.exists? OUT_FILE
file_contents = File.read OUT_FILE
print file_contents
end
Regards,,
Bill
Hi thanks for this great article. Couldn’t make it work,
Errors:
ruby 2.2.4p230 (2015-12-16 revision 53155) [i386-mingw32]
C:\Users\otm>cd C:\Users\otm\MyNewProject
C:\Users\otm\MyNewProject>rake test:all
rake aborted!
NoMethodError: undefined method `flatten’ for #
C:/Users/otm/MyNewProject/vendor/ceedling/lib/ceedling/configurator.rb:327:in `e
val_path_list’
C:/Users/otm/MyNewProject/vendor/ceedling/lib/ceedling/configurator.rb:217:in `b
lock in eval_paths’
C:/Users/otm/MyNewProject/vendor/ceedling/lib/ceedling/configurator.rb:217:in `e
ach_pair’
C:/Users/otm/MyNewProject/vendor/ceedling/lib/ceedling/configurator.rb:217:in `e
val_paths’
C:/Users/otm/MyNewProject/vendor/ceedling/lib/ceedling/setupinator.rb:28:in `do_
setup’
vendor/ceedling/lib/../lib/ceedling/rakefile.rb:46:in `’
vendor/ceedling/lib/ceedling.rb:66:in `load’
vendor/ceedling/lib/ceedling.rb:66:in `load_project’
C:/Users/otm/MyNewProject/rakefile.rb:4:in `’
(See full trace by running task with –trace)
any ideas??
Thanks in advanced
While simulating, got following error
test/simulation/sim_test_fixture.rb:3:in `popen’: No such file or directory – C:/Program Files (x86)/Microchip/xc16/v1.24/bin/sim30.exe test/simulation/sim_instructions.txt (Errno::ENOENT)
from test/simulation/sim_test_fixture.rb:3:in `’
Please help us to solve issue.
It’s great to know how to write tests for Microchip MCUs. Two gripes though: IDEs are not what they used to be. Good ones are true productivity tools, end of. Use a good one properly and there are no downsides. (Although whether MPLAB X counts is another story.) Secondly, it would have been nice if this article was split into two. Ceedling and Ruby are quite an aside, and a fast reference for automated testing with PIC MCUs might have done without it – especially since alternatives exist, and for the ultra-impatient like myself, picking apart two unknowns, ceedling and MDB when I’m only interested one is unwelcome. Nonetheless, a good post, thank you.