Firmware System Introduction

Platform Test Tools SAM3U2 Firmware nRF51422 Firmware Software
USB RS-232 Converter Master AP2 Emulator IAR

Prerequisite Modules

Learning Objectives

This module gives an overview of the firmware system coded for the EiE development boards. The learning objectives are:

  • Explain the significance of timing in an embedded system
  • Describe the two main section of the EiE firmware system
  • Define the main rules of each section in the EiE system
  • Access and update a UserApp task
  • Write the classic “Hello world” of embedded systems: a blinking LED
  • Download new firmware to the development board and verify operation

To begin, ensure you have a clean version of the SAM3U2 firmware for your board downloaded from the link above or refreshed from Git. Branch this code to a new “firmware_system” branch. Verify your system is up and running:

  1. Connect the development board J-Link USB cable and the USB-to-Serial device to the debug port.
  2. Open IAR, select File > Open > Workspace… and navigate to the firmware_[devboard]\iar_7_20_1 folder to open the .eww file you will see.
  3. Start the debugger (flashes the code) but do not run it yet
  4. Open Tera Term and select the COM port that the USB to Serial enumerates to and ensure the port settings are 115200, 8 bit, no parity, 1 stop bit and no flow control.

Now run the code in the debugger and observe the debug output in Tera Term. After you see “Initialization complete” type “en+c00” in Tera Term and press enter to see the board’s debug menu.


Explore the available command options and use the LED test to try toggling LEDs in the system.

A pseudo operating system

The development board firmware is what is called a “bare metal” system written as a “State Machine Super loop.” We are going to call it a “pseudo operating system.” It does not actually run any operating system, but it strives to offer a lot of the functionality that an operating system provides.

Drivers and applications are added to the system as tasks that are as independent and mutually exclusive as possible. Each task must follow the rules of the system to ensure all the other tasks continue to operate properly. The task may provide services to other tasks through public functions of its Application Programming Interface (API).


  1. What are the main features of an operating system?
  2. What are the trade-offs between choosing an operating system and bare-metal?
  3. Look around the room you are in and find 5 embedded systems. Which ones likely have an operating system, and which ones are probably bare metal?

Like most embedded systems, the main part of the firmware for the development board runs inside an infinite loop. Within this loop, we define two sections: Initialization and the Super Loop. Stripped of all the actual code, it looks like this:

void main(void)
  /* Low level initialization */
  /* Driver initialization */
  /* Application initialization */
  /* Exit initialization */
  /* Super loop */  
    /* Drivers */
    /* Applications */
    /* System sleep*/    
  } /* end while(1) main super loop */
} /* end main() */

To make this system work, we assign each of those two sections rules. Dictating rules in documentation allows us to save a massive amount of coding to otherwise implement those rules in the software. If we wrote a system that did not rely on written rules but provided the multitasking capabilities of this system, we would essentially be writing a full real time operating system.

Initialization code only runs once when the system is powered on or reset. Every task must have an initialize function that is called here. Regardless of what the task does, its initialization function must follow three rules:

  1. It cannot make use of non-initialized system functionality.
  2. When the task’s Initialize function is finished, the task is either ready to run or assigned a safe error state.
  3. Initialize functions may take as long as they want but eventually they must return.

Super loop
Once all tasks are initialized, the system enters the main super loop which is simply a while(1) loop that should never terminate. The loop continuously executes a sequence of function calls that give every task some processor time. This is really the simplest form of an operating system scheduler: a non-prioritized, non-preemptive, round-robin scheduler.

The fundamental difference between this scheduler and that of an operating system is that the processor time allocated to each task is completely up to the programmer. Therefore, we define only one rule that an application must adhere to:

  1. The cumulative execution time of all tasks that run in a single iteration of the super loop shall not exceed one system tick period.

In simple terms, this means that tasks can’t take too long to run code each time they are given the processor. The default system tick period chosen for the EiE system is 1ms. That means that if there are 10 applications in the finished system, each should do what it needs to do each cycle in less than 100us.


  1. What is the average execution time for a task in the EiE system if there are 15 total tasks?
  2. Write a general equation for allowed average execution time for n tasks in the system.

A hundred microseconds might not sound like a lot of time, but for a system running at 48MHz, each task can use 4,800 processor cycles. A line of code averages 1-20 cycles, so worst case that’s 200 lines of code, though keep in mind as soon as you write a loop you can effectively get a lot of code executing.

Another important feature of the EiE system is that every task runs as a state machine. The main loop uses calls to the “RunActiveState” functions of each task since the main loop has no knowledge of what the task is doing or what code needs to run. The task itself keeps track of what state it is in and correctly executes the code for the current/active state. You’ll learn more about this in later modules.

With knowledge about the other tasks that are running in the system, you may be able to safely extend the number of cycles your task uses. If a task takes too long, it will potentially affect timing with other tasks that may or may not be detrimental to the system. However, the system will continue to run as long as your rogue task eventually returns to the main loop to allow the other task to run.

Even though applications are executed linearly each millisecond, the EiE system makes extensive use of interrupts for high-priority, non-deterministic operations like sending and receiving data on the various communication buses. In this way it achieves real-time performance on critical tasks subject only to the user-defined interrupt priority assignments.

The ARM core system tick is the highest priority in the system, so the 1ms tick counter is a very reliable representation of actual time elapsed. This value is maintained globally in the system as G_u32SystemTime1ms so any task can reference it directly and it is always visible in a Watch window since it is always in scope. The Atmel SAM3U2 also has an extensive direct memory access (DMA) architecture that works very well with all the peripherals. The firmware makes use of this whenever it can to avoid using processor resources.

What we end up with is perhaps a surprisingly robust system with essentially a zero-footprint kernel. Granted, it is quite fragile but when used properly it is extremely functional. This system is used for the entirety of the firmware program, so by the end of this you should have a very solid understanding of how it works, how to use it successfully, and what sort of advantages and disadvantages it has overall.

API Introduction

A lot of time was spent developing all of the “low level drivers” to provide easy access to them. Each driver must be carefully designed, coded, and tested. Simple drivers might take a few hours to write, but others can take weeks. The EiE firmware textbook describes the development of every driver in the system in full detail.

Using drivers “abstracts” the details of how the functionality is implemented. It allows applications to be much more portable if the driver interface functions are identical regardless of what processor the code functions on. For example, the code to turn on an LED for the SAM3U2 processor on the EiE development board will be completely different than turning on an LED for the MSP430 processor on the EiE Hardware 1 project board. However, both drivers can offer and “LedOn()” function that can be called by a high-level task. That high-level task could then easily be ported to either development board.


Make a list of the features and trade offs you might consider in writing any API for an embedded system.

The quality of an API is proportional to the design effort and amount of code written to implement it. The decisions on how to do anything in firmware is always a very complex balance of trade-offs.

The EiE API was coded to offer some of the most useful and common functions that you would encounter in products that use hardware similar to what is found on the development board. All of the decisions made in building this code came from experiences from designing real products and features that were coded. In the end, the main super loop provides a complete set of drivers to access all of the board functionality.

/* Super loop */  

  /* Drivers */



  /* Applications */
  /* System sleep */

} /* end while(1) main super loop */


  • Look at the main processor schematic page and guess where each of the functions in the main super loop are going to depend on the hardware. See the example below where the hardware associated with LedUpdate() is shown.
  • For each function, do you think that the signaling need high speed service or is low speed from the 1ms loop enough?


User Application

The firmware build for this module features the full code to run the peripherals on the board. The drivers that provide this functionality are each their own tasks in the system that receive processor time with each iteration through the main loop as described above. In most cases, the tasks are in their “Idle” state. There are three User Application tasks in the loop that are setup for you to add your own firmware into the system. In most cases, the only files you need to edit are userx_app.c and userx_app.h. If you needed to write more tasks, simply follow the instructions at the top of user1_app.c and user1_app.h to make additional user tasks. You can call these tasks anything you want – just be sure to follow the naming conventions.

Hello World

Even veteran developers likely start up a new platform with a simple “Hello world” type application. In the embedded world, this is classically a blinking LED. Though all the EiE board functionality is ready to be used right now, we do not yet know how to use it. So we will add just a small bit of code to create a user_app-driven hello world program. The exercise is an easy introduction to coding your own user application and to see if you understand how the system works. Remember that you are responsible for meeting the rules of this system. Most importantly you must understand that the main system loop executes once every millisecond and thus every task in the system gets processor time every millisecond.

We are going to hijack the Heartbeat LED which by default is used to mark the start and end of a sleep cycle which is also the end of each 1ms period. If you probe the heartbeat LED with an oscilloscope, you will see a low duty cycle 1 kHz square wave. The system is sleeping when the LED is off, and the system is executing code in the main loop when the LED is on. Since the intensity of an LED increases with duty cycle, the heartbeat LED will be brighter when the system is spending less time sleeping. If the frequency ever changes, that indicates that a task is breaking the timing rule, or the cumulative execution time is longer than 1ms.

If you have an oscilloscope or frequency counter available, find the heartbeat signal. What is the frequency, period and duty cycle on your board when it is running?

The heartbeat LED happens to be the only LED that we can access without interfering with the LED driver. This is a bare metal system after all — we have access to any bit of memory and peripheral in the processor so we must be careful. The plan is to use the inherent 1ms system timing to blink the Heartbeat LED at a rate you can actually see.

To do this you will need the following:
1. The function (macro) HEARTBEAT_OFF() to turn off the Heartbeat LED
2. The function (macro) HEARTBEAT_ON() to turn on the Heartbeat LED
3. The C-programming skills you picked up from the previous module.

All of your code should be written in static void UserApp1SM_Idle(void) inside user_app1.c and you probably will add a few things to the user_app1.h header file so that you can easily change the blink rate. Good programming practices will be emphasized from the very beginning!

Turn off the Heartbeat in main
Start by commenting out the code that manages the heartbeat around SystemSleep() in main.c. This should be at line 97 in the code. Build the code and run it just to make sure you no longer see the heartbeat light.

    /* System sleep*/

Simple timing
Open user_app1.c and find the UserAppInitialize() function. You can find it quickly by clicking the small “f()” symbol in the top right of the user_app1.c window.


Initialize the heartbeat LED to the OFF state. For now, ignore the rest of the code in this function.

void UserApp1Initialize(void)

  /* If good initialization, set state to Idle */
} /* end UserApp1Initialize() */

Scroll down to UserApp1SM_Idle. The system will execute the code in this function every millisecond – this is promised by our rules, although never guaranteed. For non-mission critical timing, it is safe to rely on the 1ms period. Therefore, simply set up a counter that increments each time UserApp1SM_Idle is called and check when it hits the trigger value you want.

Define a constant in user_app1.h:

Constants / Definitions
#define U32_COUNTER_PERIOD_MS     (u32)500

Code the checking structure in user_app1.c

static void UserApp1SM_Idle(void)
  static u32 u32Counter = 0;

  /* Increment u32Counter every 1ms cycle and check if it’s done */
  if(u32Counter == U32_COUNTER_PERIOD_MS)
    u32Counter = 0;

} /* end UserApp1SM_Idle() */

Why do we use a static variable for the counter? Build the code and run it. Put a break point where u32Counter is reset back to 0 to make sure everything is working as you expect. Examine u32Counter in a Watch window along with G_u32SystemTime1ms. Run a few times to the breakpoint and observe G_u32SystemTime1ms.


Blink the LED
The last step is to blink the LED. Since you only have the ON and OFF functions to work with, you have to keep track of the last state. Use a static boolean variable for this and call ON or OFF based on the boolean value.

static void UserApp1SM_Idle(void)
  static u32 u32Counter = 0;
  static bool bLightIsOn = FALSE;
  /* Increment u32Counter every 1ms cycle */
  /* Check and roll over */
  if(u32Counter == U32_COUNTER_PERIOD_MS)
    u32Counter = 0;
      bLightIsOn = FALSE;
      bLightIsOn = TRUE;
} /* end UserApp1SM_Idle() */

The code is simple but the concept is essential to understand. If you finish this very quickly, change the code to automatically blink twice as fast every few seconds until you cannot see it blinking anymore, then slow it back down (or reset back to the 1Hz rate and repeat). You might also try changing the blinking duty cycle to something other than 50%.

Note: the Dot Matrix (MPG2 / EIE2) dev boards have a function in their captouch task that blocks for about 25ms every time it reads the sensors. This is part of the Atmel captouch library code that we cannot control. Over time this will impact the LED blinking but for this exercise it is okay as it is a great way to illustrate what happens when you break the rules of the system.

Think about some products or ideas you are interested in. Look at the hardware on the development board and identify what is available that would be involved in those ideas. Start a list of project ideas that you could do for EiE.

2019-OCT-13: Update for EiE textbook firmware
2017-OCT-05: Update all user_app to user_app1 for the latest Master branch; update reference to project directory
2017-APR-19: Header formatting, add main loop task code, minor text edits
2017-FEB-24: Add skill checks
2016-MAR-04: First release