Firmware System Introduction

Understand the firmware system and add a custom application to the system [VIDEO]

Platform Test Tools SAM3U2 Firmware nRF51422 Firmware Software
MPG1
MPG2
USB RS-232 Converter MPG User Code
SOLUTION
AP2 Emulator TeraTerm

Prerequisite Modules

Start

This module takes you through an overview of the firmware system coded for this program. We conclude with the classic “Hello world” of embedded systems: a blinking LED.

To begin, ensure you have the correct base firmware for your board downloaded from the link above. 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_mpgx\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.

mpg1_startup_debug

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).

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 */  
  while(1)
  {
    /* 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
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.

The default system tick period 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. A hundred microseconds probably does not sound like a lot of time, but for a system running at 48MHz each task can use 4,800 processor cycles. 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.

The system also makes extensive use of interrupts for high-priority, non-deterministic operations like sending and receiving data from the various communication buses. In this way it achieves real-team 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 of the peripherals, and the firmware makes use of this whenever it can.

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.

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 idle. There is also a User Application task in the loop that is setup for you to add your own firmware into the system. In most cases, the only files you need to edit are user_app.c and user_app.h. If you needed to write several tasks, simply follow the instructions at the top of user_app.c and user_app.h to make additional user tasks.

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 of the board functionality is ready to be used, we do not yet know how to use it. So instead 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 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.

However, 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 UserAppSM_Idle(void) inside user_app.c and you probably will add a few things to the user_app.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*/
    //HEARTBEAT_OFF();
    SystemSleep();
    //HEARTBEAT_ON();

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

IarFunctionShortcut

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

void UserAppInitialize(void)
{
  HEARTBEAT_OFF();

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

Scroll down to UserAppSM_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 UserAppSM_Idle is called and check when it hits the trigger value you want.

Define a constant in user_app.h:

/******************************************
Constants / Definitions
*******************************************/
#define COUNTER_LIMIT_MS        (u32)500

Code the checking structure in user_app.c

static void UserAppSM_Idle(void)
{
  static u32 u32Counter = 0;

  /* Increment u32Counter every 1ms cycle */
  u32Counter++;
  
  /* Check and roll over */
  if(u32Counter == COUNTER_LIMIT_MS)
  {
    u32Counter = 0;
  }
} /* end UserAppSM_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.

Counter

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 UserAppSM_Idle(void)
{
  static u32 u32Counter = 0;
  static bool bLightIsOn = FALSE;
  
  /* Increment u32Counter every 1ms cycle */
  u32Counter++;
  
  /* Check and roll over */
  if(u32Counter == COUNTER_LIMIT_MS)
  {
    u32Counter = 0;
    
    if(bLightIsOn)
    {
      HEARTBEAT_OFF();
    }
    else
    {
      HEARTBEAT_ON();
    }
    bLightIsOn = !bLightIsOn;
  }
  
} /* end UserAppSM_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) 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.

[STATUS: RELEASED. LAST UPDATE: 2016-MAR-04]

*** ENGENUICS WILL NOT BE ABLE TO SHIP PRODUCTS BETWEEN MAY 26 AND JUNE 26, 2017 ***