The LED API abstracts the hardware to enable easy access to each discrete LED including the RGB backlights on the ASCII development board. It also provides some basic LED operation like blinking and dimming.
As this is the first firmware module working with the EiE API, you can note that the same format for explaining each module’s API will be used. A short functional description is provided followed by any new data types required for the API. The Public functions are given which detail all of the functionality that the API offers. You do not have to look into the API code, though you are welcome to explore it to see how it works. Remember that all of the API code development is described in the EiE textbook if you want to learn more.
Since the microcontroller uses digital outputs (e.g. logic high or logic low which correspond to 3.3V and 0V, respectively), a technique called “Pulse Width Modulation” (PWM) is used to dim LEDs. The gist of PWM is that the LED is turned on and off quickly enough that your eye cannot detect the discrete on and off transitions. Then, by varying the amount of time the LED is on compared to the total period (called the “duty cycle”), the LED will appear to vary its brightness.
For this exercise, branch the Master repository from Git to an “ledbasic” branch.
To use the LED API, you need the following names from the types defined for the API:
LedNumberType – enumerated type used in all API functions to specify the LED of interest. The values are logical names that are supposed to be intuitive:
- ASCII DEV BOARD: WHITE, PURPLE, BLUE, CYAN, GREEN, YELLOW, ORANGE, RED, LCD_RED, LCD_GREEN, LCD_BLUE
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:
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.
These instructions demonstrate the LED driver on the ASCII development board. Add the following code at the start of UserApp1Initialize(void) in user_app1.c. Feel free to select the LED colors you want.
Build the code and set a breakpoint to the first line of code in UserApp1Initialize. Once the program gets there, step through it to see what happens. You can use “Step Over” (F10) for each function call. This executes all the code in the function without showing the single-steps that would occur if you used F11 to enter the function and run it line-by-line.
However you do it, as you step, the BLUE and PURPLE LEDs should turn on. Nothing will happen yet after the calls to LedBlink() and LedPWM() because they require the main loop to be running.
Let the code run full speed and you should see what you expect.
Note that in some previous versions of the EiE code, the purple LED would turn off once the main loop was running because it was not in the correct “mode.” This provided a nice example of what happens if you do not initialize everything in a system to a known starting state. You likely know to do this for variables when you are programming, but it can apply to higher level code as well. If you think about it, how can you call a “toggle” function when you don’t know the startup state of the LED?
To be sure, UserApp1Initialize should explicitly set all of the LEDs to the preferred states. This treats the system properly and makes no assumptions about the LED start states from the LED driver. Update UserApp1Initialize as follows:
Now to check if you remember how the firmware system timing works, make the LED you tried to toggle (purple in this case) blink every half second or so using the system timing to count out a 250ms period and then calling LedToggle(). Since this code runs continuously, it should be in the Idle state of the user application:
The LED driver and API are simple to understand, so a slightly more difficult program exercise is used to test it out. We will build a 4-bit binary counter displayed on the LEDs using the green, yellow, orange and red LEDs. The count will increment every 250ms. How high can a 4-bit counter count to?
First, change UserApp1Initialize to initialize all discrete LEDs to off and turn the backlight on white:
In the Idle state, keep the 250ms check in place but add in a new static variable called u8Counter that increments inside the 250ms loop. Make sure to roll the counter back to 0 when it hits 16.
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.
The code to mask off the 4 LSBs in sequence to decide whether or not the corresponding LED should be on is shown below.
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?
Colors with RGB LEDs
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).
Add another static variable u8ColorIndex that will track the current color for the RGB backlight. For simplicity, 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 reached. Don’t forget to roll u8ColorIndex each time it gets to 7. This code should live in the same location as the code that’s running your binary counter so that both the counter and the LCD backlight cycling are taking place. The start of the switch statement is shown below.
Build and test the code to ensure the backlight changes every time the binary counter rolls.
If you have coded this successfully and understand what is happening, you should have no problem using the LED API for the remainder of the modules in the program. LEDs are one of the easiest 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. Remember that the blinking and PWM modes supplied by the LED API depend on the 1ms system timing. Setting one LED to blinking at around 250ms period at the start of a new project is a good way to catch if your code has timing errors as it runs since you’ll notice any delays that might happen from the new code.
Part 1: Create a personalized light pattern using the 8 LEDs at the top of the development board. For example, have a light continuously traverse back and forth across all of the LEDs. Before you right any code, create a rough flow chart or write pseudo code for the algorithm you will write for your pattern. Try to think it through, code it, and see if you designed it properly. If not, figure out what you did and what needs to be fixed.
Part 2: Turn on one of the backlight LEDs during initialization so the LCD is a solid color (either red, green, or blue). Then choose either of the remaining two backlight LEDs and write code to fade it on and off by cycling from LED_PWM_100 down to LED_PWM_0 and then back up (repeatedly). Change the PWM rate every 40ms. What do you expect to see?
[LAST UPDATE: 2023-OCT-12]