LED Advanced Operation

LED Advanced Operation[VIDEO]

Platform Test Tools SAM3U2 Firmware nRF51422 Firmware Software
MPG1
MPG2
USB RS-232 Converter MasterUser Code
LED Advanced
AP2 Emulator TeraTerm

Prerequisite Modules

Getting Started

This module builds on your experience with the LED API to create an attractive color-cycling display using the RGB LEDs on the development boards. For the ASCII LCD board, the LCD backlight will color cycle; on the dot matrix LCD board, all four of the discrete RGB LEDs will cycle.

Pulse-width-modulation is a constant frequency, variable duty cycle signal applied to “emulate” an analog voltage. The main points to understand are shown below.

PWM

Driving an LED with a square wave will make the LED appear brighter or darker to the eye depending on the duty cycle. A rough approximation is that the effective voltage seen by the circuitry is the peak source voltage x the duty cycle. For the development boards, the LED supply rail is 5V, so if an LED is driven at 50% duty cycle, it will effectively see 2.5V. We can also roughly approximate that at half the voltage, the LED will be half as bright.

Red, green and blue are the primary colors of light. By mixing different intensities of these three, any color can be achieved. If all three colors are on at equal intensity, white light is visible. At the extremes where one or two segments are off, an RGB LED will appear as blue, red, green, yellow, purple and cyan. The LED driver provides 5% steps in duty cycles, so each of the three colors can have 20 different intensities. Therefore we have 20 x 20 x 20 = 8000 possible colors even though your eye cannot really distinguish that many colors. However having that many options when cycling through will allow seamless-looking transitions. You can liklye find many examples of this very thing happening in lighting all around you.

Exercise

In this exercise we will write an algorithm that sequentially cycles the RGB LEDs so the whole visible spectrum is cycled through. All code is written in user_app.c/h. We start by breaking it down into sections we need to manage:

  1. Red to yellow: red 100%, ramp green from 0 to 100%
  2. Yellow to green: green 100%, ramp red from 100% to 0
  3. Green to cyan: green 100%, ramp blue from 0 to 100%
  4. Cyan to blue: blue 100%, ramp green from 100% to 0
  5. Blue to purple: blue 100%, ramp red from 0 to 100%
  6. Purple to red: red 100%, ramp blue from 100% to 0

Notice that in any given step of the sequence, only one LED is changing duty cycle. We have 5% steps available, so it makes sense just to sit in a loop for a period of time at each step and make slight adjustments at the end of each loop. The only difference in each phase is the LED that we are modulating and whether or not it is increasing or decreasing duty cycle. With this in mind, the code can be written quite efficiently using a few array tricks for looping.

We have to be careful to consider how changing the duty cycle will impact the LED driver. This is not something you would think about right away, and you need knowledge of how the LED driver modulates the LEDs. As it turns out, to avoid any visible glitches that could result from interrupting the PWM function of the LED driver, the dwell time at each duty cycle needs to be a multiple of 20ms. We will write the code so this is ensured and build in a scaling value to speed up or slow down the overall sequence time.

First, define an array that will manage the LED that is being modulated for each of the required sections. For the dot matrix LCD board, we need four of these arrays to work with all four RGB LEDs.

#ifdef MPG2
  static LedNumberType aeCurrentLed[]  = {GREEN0, RED0, BLUE0, GREEN0, RED0, BLUE0};
  static LedNumberType aeCurrentLed1[] = {GREEN1, RED1, BLUE1, GREEN1, RED1, BLUE1};
  static LedNumberType aeCurrentLed2[] = {GREEN2, RED2, BLUE2, GREEN2, RED2, BLUE2};
  static LedNumberType aeCurrentLed3[] = {GREEN3, RED3, BLUE3, GREEN3, RED3, BLUE3};
#endif /* MPG2 */

#ifdef MPG1
  static LedNumberType aeCurrentLed[]  = {LCD_GREEN, LCD_RED, LCD_BLUE, LCD_GREEN, LCD_RED, LCD_BLUE};
#endif /* MPG 1 */

Define a second array that will tell the loop if the LED of interest is increasing (TRUE) or decreasing (FALSE) its duty cycle. The extra spaces make the arrays line up.

  static bool abLedRateIncreasing[]   = {TRUE,      FALSE,   TRUE,     FALSE,     TRUE,    FALSE};

Track the currently active LED and the PWM level it is currently at with u8s. Also track which duty cycle step we are on because that is used to trigger when we move on to the next phase. A 16-bit counter tracks the time between cycles. All variables must be static so they persist between task switches.

  static u8 u8CurrentLedIndex  = 0;
  static u8 u8LedCurrentLevel  = 0;
  static u8 u8DutyCycleCounter = 0;
  static u16 u16Counter = COLOR_CYCLE_TIME;

COLOR_CYCLE_TIME should be a #define in user_app.h. Remember this must be a multiple of 20. If a value above 80 is chosen, some of the steps will start to be noticeable. Test this out to see.

/******************************************************************
Constants / Definitions
******************************************************************/
#define COLOR_CYCLE_TIME   (u16)60    /* Time to hold each color */

Add code to UserAppInitialize() to ensure the LEDs are starting in the desired state:
ASCII LCD board

  /* Start with red LED on 100%, green and blue off */
  LedPWM(LCD_RED, LED_PWM_100);
  LedPWM(LCD_GREEN, LED_PWM_0);
  LedPWM(LCD_BLUE, LED_PWM_0);

Dot matrix LCD board

  LedPWM(RED0,   LED_PWM_100);
  LedPWM(GREEN0, LED_PWM_0);
  LedPWM(BLUE0,  LED_PWM_0);

  LedPWM(RED1,   LED_PWM_100);
  LedPWM(GREEN1, LED_PWM_0);
  LedPWM(BLUE1,  LED_PWM_0);

  LedPWM(RED2,   LED_PWM_100);
  LedPWM(GREEN2, LED_PWM_0);
  LedPWM(BLUE2,  LED_PWM_0);

  LedPWM(RED3,   LED_PWM_100);
  LedPWM(GREEN3, LED_PWM_0);
  LedPWM(BLUE3,  LED_PWM_0);

Most of the code runs only when it is time to change the current duty cycle. Then we reset the counter for the next cycle, determine the next level to set to the LED, and set the value using the familiar call to the LED API. The arguments reference the aeCurrentLed[] that is being tracked by u8CurrentLedIndex and the duty cycle is just u8LedCurrentLevel.

  u16Counter--;
  /* Check for update color every COLOR_CYCLE_TIME ms */  
  if(u16Counter == 0)
  {
    u16Counter = COLOR_CYCLE_TIME;

    /* Update the current level based on which way it's headed */

    /* Update the value to the current LED */   
    LedPWM( (LedNumberType)aeCurrentLed[u8CurrentLedIndex], (LedRateType)u8LedCurrentLevel);
  } 

The code to check the current level uses abLedRateIncreasing[] to determine if u8LedCurrentLevel is increasing or decreasing. u8DutyCycleCounter counts how many times this adjustment has been made. Since there are twenty steps for all the available duty cycles, watch for this to reach 20 and then adjust variables accordingly.

    /* Update the current level based on which way it's headed */
    if( abLedRateIncreasing[u8CurrentLedIndex] )
    {
      u8LedCurrentLevel++;
    }
    else
    {
      u8LedCurrentLevel--;
    }

    /* Change direction once we're at the end */
    u8DutyCycleCounter++;
    if(u8DutyCycleCounter == 20)
    {
      u8DutyCycleCounter = 0;
      
      /* Watch for the indexing variable to reset */
      u8CurrentLedIndex++;
      if(u8CurrentLedIndex == sizeof(aeCurrentLed))
      {
        u8CurrentLedIndex = 0;
      }
      
      /* Set the current level based on what direction we're now going */
      u8LedCurrentLevel = 20;
      if(abLedRateIncreasing[u8CurrentLedIndex])
      {
         u8LedCurrentLevel = 0;
      }
    }

Build and test the code to ensure everything is working.

A nice feature to add would be pressing BUTTON0 to stop the cycling and hold the current color. All we have to do is wrap the u16Counter decrement in a boolean that is toggled with BUTTON0. Add the static bool bCyclingOn and the button check at the end.

  static bool bCyclingOn = TRUE;

  /* Advance the cycle only if bCyclingOn */
  if(bCyclingOn)
  {
    u16Counter--;
  }
  /* Check for update color every COLOR_CYCLE_TIME ms */  
  if(u16Counter == 0)
  {
    ...
  }

  /* Toggle cycling on and off */
  if( WasButtonPressed(BUTTON0) )
  {
    ButtonAcknowledge(BUTTON0);
    bCyclingOn = (bool)!bCyclingOn;
  }

} /* end UserAppSM_Idle() */

Efficient use of arrays to control variables in loop structures is clearly a great way to minimize code space consumed by an application. This code is relatively short, but you now have a great application that can be inserted into any of your other designs.

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