Prerequisite Modules
This module teaches some common and fundamental embedded programming skills to work with LEDs (light emitting diodes) connected to the microcontroller. Though the focus is on using the EiE API to control the LEDs, don’t hesitate to look into the source code to get a glimpse of how processor registers are used to turn lines of code in firmware into lights turning on and off on the development board.
LED API
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. The API calls are identical for the ASCII board and dot matrix board, however you must be aware of where the different LED elements are. All the discrete LEDs on the ASCII board are single color: white, purple, blue, cyan, green, yellow, orange, and red (left to right). On the dot matrix board, the four LEDs are all RGB (red-green-blue) LEDs where each element in each LED can be addressed. Therefore each of the four LEDs on the dot matrix LED can easily be made red, blue, green, purple, cyan, yellow, and white. With some effort, you can make ANY color on them. The same is true for the ASCII LCD backlight which is an RGB LED with control over the red, green, and blue. The LCD backlight on the dot matrix board is only white.
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.
use the LED API, you need the names of the types defined for the API. Some of these are defined in leds.h within the “firmware_common” folder because they are generic to the LED API, but some definitions are made in the board-specific firmware files. The most current are:
- ASCII: firmware_ascii\bsp\eief1-pcb-01.h
- DM: firmware_dotmatrix\bsp\mpgl2-ehdw-02.h
Find and open these files in VS Code.
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
- DM: RED0, RED1, RED2, RED3, GREEN0, GREEN1, GREEN2, GREEN3, BLUE0, BLUE1, BLUE2, BLUE3, 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. Smart programmers would make sure function check for this. Maybe smarter programmers wouldn’t have coded it that way in the API. How would you make it better?
- 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:
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.
Examples
These instructions demonstrate the LED driver on the dot matrix development board. Add the following code at the start of UserApp1Initialize(void) in user_app1.c.
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 (“Step In”) to enter the function and run it line-by-line.
As you step over the function calls, the BLUE0 and RED3 LEDs should turn on. Nothing will happen yet after the calls to LedBlink() and LedPWM() because they require the main loop to be running to manage the timing.
Press the “Continue” debug button or press F5 to let the code run full speed and you should see things working properly with a dim BLUE1 LED and GREEN2 blinking.
Note that the code you just wrote suffers from a few potential issues due to assumptions that have been made. The use of “Toggle” is fairly obvious since the user application is assuming that the LED is off. There is a less obvious assumption to do with a parameter of each LED in the driver called the “Mode.” This is necessary to handle static states vs. blinking/PWM states. If you attempt to turn on an LED that is not in the correct mode, it will turn on as soon as LedOn() exits, but will get turned off once the main loop is running. If you read the documentation for the LED driver you would know why.
You could definitely consider this another flaw in the LED driver since it is not intuitive and you have to look at the source code to understand it. OR, you could remember that you should ALWAYS initialize everything in a system to a known starting state.
To be sure, UserApp1Initialize should explicitly set all of the LEDs to the preferred states. For the dot matrix board, this means using LedOn(RED3) instead of LedToggle(RED3) even though in this case the result is the same. For the ASCII dev board, all of the unused LEDs should be explicitly turned off with LedOff(). This treats the system properly and makes no assumptions about the LED start states from the LED driver.
LEDs and System Time
To check if you remember how the firmware system timing works, make the LED you tried to toggle (RED3 in this case) blink every half second or so using the system timing to count out a 250ms period and then calling LedToggle(). Do not use the LedBlink() function – do it manually! Since this code runs continuously, it should be in the Idle state of the user application. Try it yourself before looking at the solution below.
Binary Counter
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 . 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 off the backlight.
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.
If you are new to bit masking, write out a few examples and hand-calculate the bit-wise AND function. 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 colors of the LEDs all the same during each cycle, but then change. 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 “LED Advanced” module).
So how would you get, for example, orange? 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 the simple colors, devise a method to use an array of 7 values (e.g. the 7 available colors) and then index that based on the current cycle (which will be tracked with another counter. Look very carefully at the defintion for LedNameType and make sure you understand what “enum” means / does:
Try to design this now without looking at the solution below. If you are stuck, look at the first part of the solution to see one approach to the solution.
Start with all of the LEDs turned off. Since there are 12 of them, use a loop. This is also good practice to work with the enum to see if you are understanding.
To set the color for each bit, index aau8Color to turn on the red, green, and/or blue segments as necessary. Code for only one of the four bits is shown below. The only thing that changes for the other bits is the mask value for u8Counter, and the value of u8Offset that gets set.
Build and test your code. If anything is not working, try to think through what could be going wrong. Use the debugger to check each loop. Remember the MCU operates very quickly, so sometimes a lot of code is working correctly but a bugs make it look like a disaster.
The full solution can be found online. Each bit is still coded explicitly, but the code footprint could easily be reduced by looping 4 times with adjustment to the bit mask and offset value.
Module Exercise
Part 1: Create a personalized light pattern using the 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 write 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 the backlight LED during initialization. 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: 2024-OCT-16]