LED Basic Operation

Introducing the LED API

Platform Test Tools SAM3U2 Firmware nRF51422 Firmware Software
ASCII
DOT MATRIX
USB RS-232 Converter Master
SOLUTION
AP2 Emulator IAR
TeraTerm

Prerequisite Modules

Learning Objectives

This module introduces the LED driver for the EiE development boards. The learning objectives are:

  • Locate the LED driver files and related code
  • Build your ability to read and understand how to use an EiE API
  • Find the LED API functions and know how to use each one
  • Use system timing with the LED driver to make an LED blink
  • Develop a user application to implement a 4-bit counter

LED API

The LED API abstracts the hardware to enable easy access to each discrete LED including the LCD backlights on both the ASCII and dot matrix development boards. It also provides some basic LED operation like blinking and dimming (using PWM). Use the Master repository from Git for the latest code base. All code should be written in user_app1.c/.h files.

Type Definitions
To use the LED API, you need the following names from the types defined in the board header file (e.g. eief1-pcb-01.h). All development boards in the EiE system will have these definitions in their corresponding headers.

LedNameType – enumerated type used in all API functions to specify the LED of interest.

  • ASCII DEV BOARD: WHITE, PURPLE, BLUE, CYAN, GREEN, YELLOW, ORANGE, RED, LCD_RED, LCD_GREEN, LCD_BLUE
  • DOT MATRIX BOARD: BLUE0, BLUE1, BLUE2, BLUE3, GREEN0, GREEN1, GREEN2, GREEN3, RED0, RED1, RED2, RED3, LCD_BL

LedRateType – for blinking and PWM functionality, these values set the modulation rate. Note that PWM values in the enum are sequential, so incrementing or decrementing a PWM variable of LedRateType by one will select the next PWM level. This is not always valid for an enumerated type so it should not be assumed in other cases. Decrementing past LED_PWM_0 or incrementing past LED_PWM_100 is undefined.

  • Blinking rates: LED_0_5HZ, LED_1HZ, LED_2HZ, LED_4HZ, LED_8HZ
  • PWM rates (duty cycle): LED_PWM_0, LED_PWM_5, …, LED_PWM_95, LED_PWM_100

For example you can change from 5% duty cycle to 10% duty cycle like this:

LedRateType eCurrentRate = LED_PWM_5;
eCurrentRate++; /* eCurrentRate is now LED_PWM_10 */

Public Functions
The following functions may be used by any application in the system. There is no protection that prevents two tasks from using the same LED.

  • void LedOn(LedNameType eLED_) – Set the specified LED to “NORMAL” mode and turn the LED on. LED response is immediate.
  • void LedOff(LedNameType eLED_) – Set the specified LED to “NORMAL” mode and turn the LED off. LED response is immediate.
  • void LedToggle(LedNameType eLED_) – Toggle the specified LED. LED response is immediate. LED must be in NORMAL mode.
  • void LedBlink(LedNameType eLED_, LedRateType eBlinkRate_) – Sets an LED to BLINK mode. BLINK mode requires the main loop to be running and makes use of the system’s 1ms period.
  • void LedPWM(LedNameType eLED_, LedRateType ePwmRate_) – Sets up an LED for PWM mode. PWM mode requires the main loop to be running and makes use of the system’s 1ms period. The PWM feature is particularly susceptible to any situations where other tasks violate the 1ms rule. You will notice this on the dot matrix development board because the captouch task must block for a short period resulting in a noticeable glitch in the PWM. If your application requires glitch-free LED dimming, the easiest workaround is to disable the captouch task in the main loop. If you need the captouch drivers, there are more creative ways to work around it.

SKILL CHECK
Look at the simplified code below. What is the code trying to do? Explain why it will not work as expected.

void main(void)
{
  /* 1 ms main loop */
  while(1)
  {
    UserApp1();
    UserApp2();
    Sleep();
  }
}

void UserApp1(void) // Coded by Joe Smith
{
 /* Show RED LED when BUTTON1 is pressed */
 if("Is BUTTON1 pressed?")
   LedOn(RED);
 else
   LedOff(RED);
}

void UserApp2(void) // Coded by Jane Doe
{
 /* Show RED LED when BUTTON2 is pressed */
 if("Is BUTTON2 pressed?")
   LedOn(RED);
 else
   LedOff(RED);
}

Examples

These instructions demonstrate the LED driver on the ASCII development board. The solution on Github supports both the ASCII and dot matrix development boards.

Add the following code at the start of UserApp1Initialize(void) in user_app1.c. Feel free to select the LED colors you want.

/* Turn on an LED using the ON function */
LedOn(BLUE);

/* Turn on an LED using the TOGGLE function */
LedToggle(PURPLE);

/* Set an LED to blink at 2Hz */
LedBlink(RED, LED_2HZ);

/* Set an LED to the dimmest state we have (5% duty cycle) */
LedPWM(WHITE, LED_PWM_5);

Build the code and step through it to see what happens. As you single-step, the BLUE and PURPLE LEDs should turn on. Nothing will happen yet after the calls to LedBlink() and LedPWMI() because they require the main loop to be running.

Let the code run full speed and you should see what you expect in 3 of the cases, however the PURPLE LED will turn off.

LedBasicStart

The purple LED did not work properly when this code was run on an older version of the LED driver because the LED was not initialized properly. If you didn’t read the function definition closely and assumed LedToggle() would work, you got a surprise (it turned off as soon as the main loop started). That was updated, but it’s a good opportunity to talk about initializing EVERYTHING you want to work with. Making assumptions when coding is a terrible thing to do!

The user app should initialize all the LEDs it wants to work with to a known state. Update the UserApp1Initialize code like this:

  /* Initialize all unused LEDs to off */
  LedOff(CYAN);
  LedOff(GREEN);
  LedOff(YELLOW);
  LedOff(ORANGE);
  
  /* Turn on desired LEDs using the ON function */
  LedOn(BLUE);
  LedOn(PURPLE);

  /* Set an LED to blink at 2Hz */
  LedBlink(RED, LED_2HZ);

  /* Set an LED to the dimmest state we have (5% duty cycle) */
  LedPWM(WHITE, LED_PWM_5);

Now, manually make the LED you toggled before blink every second or so. Do this using the system timing to count out a 500ms period and then call LedToggle().


SKILL CHECK
Draw a quick flowchart of the toggling operation. Make sure you identify the two important steps that must be taken whenever the timer hits 500ms.

Code your solution to see if you get it right!


The solution is shown here: the code must be in the “Idle” state of the user application:

static u16 u16BlinkCount = 0;

u16BlinkCount++;
if(u16BlinkCount == 500)
{
  u16BlinkCount = 0;
  LedToggle(PURPLE);
}

Binary Counter

Now we will build a 4-bit binary counter displayed on the LEDs. For the ASCII board, use the green, yellow, orange and red LEDs. For the dot matrix board, use the red segments of the RGB LEDs and note that RED3 will be the LSB in the count. The count will increment every 500ms. The assumption is that you know how to count from 0 to 15 in binary.


SKILL CHECK
Write the decimal, hex and binary numbers from 0 to 15 as quickly as possible. If you are in class, have a competition to see who can write them the fastest.


First, change UserApp1Initialize to initialize all discrete LEDs to off and turn the back light on white.

  /* All discrete LEDs to off */
  LedOff(WHITE);
  LedOff(PURPLE);
  LedOff(BLUE);
  LedOff(CYAN);
  LedOff(GREEN);
  LedOff(YELLOW);
  LedOff(ORANGE);
  LedOff(RED);
  
  /* Backlight to white */  
  LedOn(LCD_RED);
  LedOn(LCD_GREEN);
  LedOn(LCD_BLUE);

In the Idle state, keep the 500ms check in place but add in a new static variable called u8BinaryCounter that increments inside the 500ms loop. Make sure to roll the counter back to 0 when it hits 16.

  static u8 u8BinaryCounter = 0;

  u16BlinkCount++;
  if(u16BlinkCount == 500)
  {
    u16BlinkCount = 0;
    
    /* Update the counter and roll at 16 */
    u8BinaryCounter++;
    if(u8BinaryCounter == 16)
    {
      u8BinaryCounter = 0;
    }
    ....

There are several ways to figure out what LEDs should be on to display the current count value which in this case is simply displaying the binary value of u8BinaryCounter onto LEDs. However, we still must parse out the bits in u8BinaryCounter to determine if the corresponding LED should be on or off.


SKILL CHECK
Explain how bits in firmware become voltage high and low signals in hardware. Then come up with an idea about how to take the value in u8BinaryCounter and get it to show on the LEDs.


The concept of “masking” bits will be used as it is very common in embedded systems. Each bit of u8 counter needs to be displayed on the LEDs, so each bit must be tested to turn on or turn off the corresponding LED.


SKILL CHECK
Explain the difference between bitwise and logical operations. Write examples of bitwise AND, bitwise OR, logical AND, and logical OR.


To implement the masking operation, a temporary number that has a single bit of interest is set (the “mask”) and bitwise ANDed against u8BinaryCounter. The result will be 1 if the masked bit is set, or 0 if the masked bit is clear. This binary result is used as the condition of an if statement that turns the corresponding LED on or off. Carefully follow through the table below to understand it.

BitMask

The basic code to mask off the 4 LSBs in sequence to decide if the corresponding LED should be on is shown below.

   /* Parse the current count to set the LEDs.  
      RED is bit 0, ORANGE is bit 1, 
      YELLOW is bit 2, GREEN is bit 3. */
    
    if(u8BinaryCounter & 0x01)
    {
      LedOn(RED);
    }
    else
    {
      LedOff(RED);
    }

    if(u8BinaryCounter & 0x02)
    {
      LedOn(ORANGE);
    }
    else
    {
      LedOff(ORANGE);
    }

    if(u8BinaryCounter & 0x04)
    {
      LedOn(YELLOW);
    }
    else
    {
      LedOff(YELLOW);
    }

    if(u8BinaryCounter & 0x08)
    {
      LedOn(GREEN);
    }
    else
    {
      LedOff(GREEN);
    }

Test the code to ensure it builds and the counter is working as expected. Step through the masking operation and observe the registers to ensure you understand what is happening.


SKILL CHECK
How would you write the above code using a loop so it could be easily scaled for any number of LEDs? Write this code and present your solution showing a 6-bit counter.

How could you implement a 5-bit counter using only the existing LED API functions? Hint: it takes just 5 lines of code in UserApp1Initialize() to do!


ASCII dev board only
Now do something more interesting and make the LCD backlight color change every time the counter rolls. We will also mix colors together to go beyond just red, green and blue. Note the “easy” colors to make with an RGB LED are red, yellow, green, cyan, blue, purple and white as they involve just fully-on combinations of 2 or more of the RGB segments. If you modulate the intensity of at least one of the LEDs, you can achieve any color you want (see the LED Advanced module for more about that).

RGB Colors

Add another static variable u8ColorIndex that will track the current colors for the RGB backlight. For simplicity, we will use a brute-force 7-case switch statement to look at u8ColorIndex and determine how to set the red, green and blue RGB back lights to get the 7 available colors. The default case should be off, which should not be hit. Don’t forget to roll u8ColorIndex each time it gets to 7.

  static u16 u16BlinkCount = 0;
  static u8 u8BinaryCounter = 0;
  static u8 u8ColorIndex = 0;

  u16BlinkCount++;
  if(u16BlinkCount == 500)
  {
    u16BlinkCount = 0;

    /* Update the counter and roll at 16 */
    u8BinaryCounter++;
    if(u8BinaryCounter == 16)
    {
      u8BinaryCounter = 0;
      
      /* Manage the backlight color */
      u8ColorIndex++;
      if(u8ColorIndex == 7)
      {
        u8ColorIndex = 0;
      }

      /* Set the backlight color: white (all), 
      purple (blue + red), blue, cyan (blue + green), 
      green, yellow (green + red), red */
      switch(u8ColorIndex)
      {
        case 0: /* white */
          LedOn(LCD_RED);
          LedOn(LCD_GREEN);
          LedOn(LCD_BLUE);
          break;

        case 1: /* purple */
          LedOn(LCD_RED);
          LedOff(LCD_GREEN);
          LedOn(LCD_BLUE);
          break;
          
        case 2: /* blue */
          LedOff(LCD_RED);
          LedOff(LCD_GREEN);
          LedOn(LCD_BLUE);
          break;
          
        case 3: /* cyan */
          LedOff(LCD_RED);
          LedOn(LCD_GREEN);
          LedOn(LCD_BLUE);
          break;
          
        case 4: /* green */
          LedOff(LCD_RED);
          LedOn(LCD_GREEN);
          LedOff(LCD_BLUE);
          break;
          
        case 5: /* yellow */
          LedOn(LCD_RED);
          LedOn(LCD_GREEN);
          LedOff(LCD_BLUE);
          break;
          
        case 6: /* red */
          LedOn(LCD_RED);
          LedOff(LCD_GREEN);
          LedOff(LCD_BLUE);
          break;
          
        default: /* off */
          LedOff(LCD_RED);
          LedOff(LCD_GREEN);
          LedOff(LCD_BLUE);
          break;
      } /* end switch */
    } /* end if(u8BinaryCounter == 16) */
    
  } /* end if(u16BlinkCount == 500) */

Build and test the code to ensure the backlight changes every time the binary counter rolls.

With all this knowledge, you should have no problem using the LED API for the remainder of the modules in EiE. LEDs are one of the easiest and therefore best ways to provide feedback as to how a program is running, so be sure to use them not only to meet the needs of your projects, but also for debugging.

Always remember that the blinking and PWM modes supplied by the LED API are dependent on maintaining the 1ms system timing. Setting one LED to blinking at the start of a new project is a good way to catch if your code has timing errors as it runs.

**** Dot matrix board ONLY ****
Make the counting color of the LEDs change every time the counter rolls. To keep this simple, only cycle from red to green to blue and then repeat. Since we can look at the definition of LedNameType we can see that the green elements are +4 indices away from red, and the blue elements are +4 indices away from green. So we will “hack” our code using this which will accomplish what we want but also create a nice learning opportunity.

Add another static variable u8ColorIndex that will track the current color for the LED. We will then use u8ColorIndex to generate an offset to the red LED segments that are being set or cleared from part 1 of the exercise e.g (RED3 + (4 * u8ColorIndex)). Make sure u8ColorIndex wraps back to 0 every time it reaches 3.

  static u16 u16BlinkCount = 0;
  static u8 u8BinaryCounter = 0;
  static u8 u8ColorIndex = 0;
  
  u16BlinkCount++;
  if(u16BlinkCount == 500)
  {
    u16BlinkCount = 0;
    
    /* Update the counter and roll at 16 */
    u8BinaryCounter++;
    if(u8BinaryCounter == 16)
    {
      u8BinaryCounter = 0;
      
      LedOff((LedNameType)(RED3 + (4 * u8ColorIndex)));
      LedOff((LedNameType)(RED2 + (4 * u8ColorIndex)));
      LedOff((LedNameType)(RED1 + (4 * u8ColorIndex)));
      LedOff((LedNameType)(RED0 + (4 * u8ColorIndex)));
      
      u8ColorIndex++;
      if(u8ColorIndex == 3)
      {
        u8ColorIndex = 0;
      }
    } /* end if(u8BinaryCounter == 16) */
    
    /* Parse the current count to set the LEDs.  From the LedNameType enum for red, green and blue we see each color is separated by 4.  Use this with u8ColorIndex to access the correct LED. */
    
    if(u8BinaryCounter & 0x01)
    {
      LedOn(RED3 + (4 * u8ColorIndex));
    }
    else
    {
      LedOff(RED3 + (4 * u8ColorIndex));
    }

    if(u8BinaryCounter & 0x02)
    {
      LedOn(RED2 + (4 * u8ColorIndex));
    }
    else
    {
      LedOff(RED2 + (4 * u8ColorIndex));
    }

    if(u8BinaryCounter & 0x04)
    {
      LedOn(RED1 + (4 * u8ColorIndex));
    }
    else
    {
      LedOff(RED1 + (4 * u8ColorIndex));
    }

    if(u8BinaryCounter & 0x08)
    {
      LedOn(RED0 + (4 * u8ColorIndex));
    }
    else
    {
      LedOff(RED0 + (4 * u8ColorIndex));
    }
    
  } /* end if(u16BlinkCount == 500) */

Build the code and note the warnings that the compiler generates regarding using enumerated types like this.

RGBWarnings

Understand what is happening and why using (or abusing) enumerated types like this is not a good idea.

  • How could you prevent those warnings from appearing?
  • Why do the lines 306 – 309 not generate the same warning?
  • Even though it works, why is what we did on lines 306-309 and poor programming practice?
  • If it is poor programming practice, why did we code it this way?

Revision History
2019-OCT-13: Update for EiE book firmware.
2017-OCT-10: Update platform to ASCII/DOT MATRIX
2017-APR-29: Update the RED LED skill check; add additional notes around purple toggle problem.
2017-FEB-23: Added Skill Checks; fixed some code formatting.
2016-MAR-03: First release.