LED Basic Operation

Introducing the LED API [VIDEO]

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

Prerequisite Modules

LED API

The LED API abstracts the hardware to enable easy access to each discrete LED including the LCD back lights on both the ASCII and dot matrix development boards. It also provides some basic LED operation like blinking and dimming (using PWM). For this exercise, checkout the Master repository from Git for the latest code base and blank user application.

Type Definitions
To use the LED API, you need the following names from the types defined in leds.h:

LedNumberType – 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(LedNumberType eLED_) – Set the specified LED to “NORMAL” mode and turn the LED on. LED response is immediate.
  • void LedOff(LedNumberType eLED_) – Set the specified LED to “NORMAL” mode and turn the LED off. LED response is immediate.
  • void LedToggle(LedNumberType eLED_) – Toggle the specified LED. LED response is immediate. LED must be in NORMAL mode.
  • void LedBlink(LedNumberType 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(LedNumberType 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.

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 UserAppInitialize(void) in user_app.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. This is because the LED is not yet in the right mode. By default, all LEDs start in PWM mode with 0% duty cycle, which means even though the LED is toggled on in UserAppInitialize, it is turned off on the first run of the main loop.

LedBasicStart

To solve this, the user app should initialize all of the LEDs it wants to work with to a known state. Update the UserAppInitialize 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 make the LED you tried to toggle blink every second or so using the system timing to count out a 500ms period and then calling LedToggle. Add the following to 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.

First, change UserAppInitialize 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 u8Counter that increments inside the 500ms loop. Make sure to roll the counter back to 0 when it hits 16.

  static u8 u8Counter = 0;

  u16BlinkCount++;
  if(u16BlinkCount == 500)
  {
    u16BlinkCount = 0;
    
    /* Update the counter and roll at 16 */
    u8Counter++;
    if(u8Counter == 16)
    {
      u8Counter = 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 u8Counter onto LEDs. However, we still have to parse out the bits in u8Counter to determine if the corresponding LED should be on or off. 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. A number that has a single bit set (this is the “mask”) is bit-wise ANDed against u8Counter and the result will be 1 if the masked bit is set, or 0 if the masked bit is clear. The binary result conditions an if statement that turns the LED on or off.

BitMask

The basic code to mask off the 4 LSBs in sequence to decide whether or not 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(u8Counter & 0x01)
    {
      LedOn(RED);
    }
    else
    {
      LedOff(RED);
    }

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

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

    if(u8Counter & 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. How would you write the above code using a loop so you would not have to have four discrete if/else structures?

ASCII dev board only
Now do something more interesting and make the LCD back light 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 simple, 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 next module).

RGB Colors

Add another static variable u8ColorIndex that will track the current colors for the RGB back light. 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 u8 u8ColorIndex = 0;
  static u16 u16BlinkCount = 0;
  static u8 u8Counter = 0;
  static u8 u8ColorIndex = 0;
  
  u16BlinkCount++;
  if(u16BlinkCount == 500)
  {
    u16BlinkCount = 0;

    /* Update the counter and roll at 16 */
    u8Counter++;
    if(u8Counter == 16)
    {
      u8Counter = 0;
      
      /* Manage the back light 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(u8Counter == 16) */
    
  } /* end if(u16BlinkCount == 500) */

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

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 LedNumberType 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 u8Counter = 0;
  static u8 u8ColorIndex = 0;
  
  u16BlinkCount++;
  if(u16BlinkCount == 500)
  {
    u16BlinkCount = 0;
    
    /* Update the counter and roll at 16 */
    u8Counter++;
    if(u8Counter == 16)
    {
      u8Counter = 0;
      
      LedOff((LedNumberType)(RED3 + (4 * u8ColorIndex)));
      LedOff((LedNumberType)(RED2 + (4 * u8ColorIndex)));
      LedOff((LedNumberType)(RED1 + (4 * u8ColorIndex)));
      LedOff((LedNumberType)(RED0 + (4 * u8ColorIndex)));
      
      u8ColorIndex++;
      if(u8ColorIndex == 3)
      {
        u8ColorIndex = 0;
      }
    } /* end if(u8Counter == 16) */
    
    /* Parse the current count to set the LEDs.  From leds.h we see the enum for red, green and blue
    are seperated by 4 so use this with u8ColorIndex to */
    
    if(u8Counter & 0x01)
    {
      LedOn(RED3 + (4 * u8ColorIndex));
    }
    else
    {
      LedOff(RED3 + (4 * u8ColorIndex));
    }

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

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

    if(u8Counter & 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?

With all of this knowledge, you should have no problem using the LED API for the remainder of the modules in the program. 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. And 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.

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