Starting a new embedded project can become a time-consuming process, especially when setting up your build system with make or CMake. I have no real issue with the build utilities, they just leave me wanting a bit more. I recently started playing around with Ceedling, a build system developed by the group ThrowTheSwitch built around Rake, similar to Make but with some intresting automation tools.

Straight off the bat, Ceedling is made of 4 parts

  • Unity: A C unit testing module
  • CMock: A mocking Module system for interactive testing
  • CException: A Lightweight exception handler for C
  • Ceedling: the build system that ties it all together

One of the things I love about Ceedling is the ability to automate building new modules, and with this, the documentation that goes into it.

Unity was my first exposure to unit testing in general. In a short amount of time, I could see the limitation of solely relying on Unity for embedded projects. Using Unity on its own required me to create and compile a “test_” file for each module I wanted to test. After 2 or three of these, I started to see how it thing could become difficult to track. On top of that, I had no way to check inter-module interactions, which is where Cmock came into play.

ThrowTheSwitch did a great job at making Ceedling accessible, with two steps you’re ready to test your first project.

In this post, I wanted to cover a particular use case. I needed to use a special version of AVR-GCC and added some command-line hooks. These hooks could convert the final executable to a programable hex file and then upload it to the microcontroller.

So while there’s enough post out there talking about testing features of Ceedling, I want to talk more about the configuration file and how it can assist with cross-platform development.

Build New Ceedling Project

Once Ceedling has been installed on your system starting a project as simple as

$ ceedling new PROJECT_NAME

If you already have a project, you can integrate ceedling into it by replacing PROJECT_NAME with the existing folder.

Ceedling Plug-ins

Any configuration related work will be done in the project.yml configuration file, which is written in YAML, a “human-friendly data serialization standard” similar to JSON or … XML

Two of the plugins I found useful are the module_generator and command_hooks. I’ll describe the module_generator’s functionality in the next section and save the command_hook for later in the post.

Including plugins is as simple as adding them in the config file under the plugin key, for more information on the different plugins check out the documentation here

    - "#{Ceedling.load_path}"
    - module_generator
    - command_hooks

Modifying Project Structure

Ceedling will create a default project structure, source and header file in one directory, test files in another, and a build folder used by Ceedling

Personally, I prefer to keep my source and header files separate, and so begins automation benefits of ceedling

As mentioned above, the module_generator plugin allows you to configure how you want to create new modules, a module consisting of a header, source and test file. You can instruct ceedling where you want to put the different files and add boilerplate text to each.

  :project_root: ./
  :src_root: src/
  :inc_root: include/
  :tst_root: test/
    :src: |
      Source file boiler plate line 1
      Source file boiler plate line 2
    :inc: |
      Header file boiler plate line 1
      Header file boiler plate line 2
    :test: |
      Test file boiler plate line 1
      Test file boiler plate line 2

Note If you’re going to modify the default folder structure you’ll have add those paths to the paths key

    - +:test/**
    - -:test/support
    - src/**
    - include/**
    - test/support

Project Environment Setup

For further configuration of your development enviroments, environment variable can be set and used later in the build process

  - :f_cpu: 16000000UL
  - :mcu: atmega4809
  - path:
    - "/home/luke/Documents/avr8-gnu-toolchain-linux_x86_64/bin:"
    - "#{ENV['PATH']}"

These variables are used later in the config file with the following syntax.


To verify your environment variables have been entered correctly, you can run the environment task from the terminal to print out all available variables

$ ceedling environment

Release Compiler Setup

This is where I needed to used a custom version of Avr-GCC provided by Microchip. Because the compiler shared the same name as the default version, and because I didn’t necessarily want to install it into my default search path, I needed to instruct ceedling where too look for this compiler. Which is why its search path must be the first one in the list. If you didn’t want to do it this way, you could have renamed the compiler and put it in the default search directory.

    :executable: avr-gcc
      - ${1}
      - -DTARGET
      - -DF_CPU=#{ENV['F_CPU']}
      - -mmcu=#{ENV['MCU']}
      - -Iinclude/
      - -Wall
      - -O1
      - -c
      - -o ${2}
      - -B ~/Documents/ATmega_DFP/1.4.351/gcc/dev/atmega4809 

Release Linker Setup

After the source files have been compiled into thier respective object files, they can now be linked together to create an final executable.

    :executable: avr-gcc
      - -g
      - -mmcu=#{ENV['MCU']}
      - ${1}
      - -o ${2}
      - -B ~/Documents/ATmega_DFP/1.4.351/gcc/dev/atmega4809

Post Linker Setup

The default executable from the linker stage, in this case, was an Executable and Linkable Format (.elf) file, and because I’m using AVRDUDE to program my device, I’ll need to convert to a .hex file. This is where the command hook plugin comes into play.

Command hooks allow you to insert executable sections of code or scripts into various points of the build cycle.

    :executable: avr-objcopy
      - -j .text 
      - -j .data 
      - -O ihex 
      - build/release/blink.elf
      - build/release/blink.hex

Post Release Setup

Finally, the converted hex file above can be uploaded to the device using AVRDUDE or whichever programmer you use.

   :executable: avrdude
    - -c usbtiny
    - -p "#{ENV['MCU']}"
    - -e
    - -i build/release/blink.hex

For a copy of this config file click here