How to dim LEDs using the Arduino and VEX
|
How to Adjust the Brightness
While being able to turn LEDs on and off is great, there are times when it might be desirable to have various levels of brightness. This could be achieved using multiple pins and various different levels of resistors to adjust the current and voltage flowing through the LED. However, this method requires additional components and costs more money, not to mention that it clutters your breadboard. Fortunately there is a better way of changing the current going to the LED, known as Pulse-Width Modulation, or PWM for short.
What is PWM?
PWM works by rapidly turning the circuit on and off. By adjusting the ratio of the time on and the time off, it is possible to adjust the brightness of the LED. This ratio is called the Duty-Cycle and is the percentage of time the output is high with respect to the time of one cycle (when the output goes high then low then back to high). If the Duty-Cycle is 100% then the output is high all the time and thus the LED would be a full brightness. If the Duty-Cycle is 0%, the output is off all the time, so the LED would just be off. Now if the Duty-Cycle is set to 50%, the output is on 50% of the time and off the other 50%. In this setup, the LED would appear be at 50% of its full brightness and thus appear to be dimmed.
PWM has several other interesting properties that will be discussed in later sections.
PWM on a Microcontroller
Due to the high frequency that PWM operates at, getting an accurate signal using code would basically prevent you from doing anything else with the chip. Fortunately, chip manufacturers included hardware to generate the PWM signal for you. However, not every I/O pin supports PWM output. For the Arduino UNO, only digital pins 3, 5, 6, 9, 10, and 11, support it. This is shown on the Arduino itself with a ~ before the pin number. ROBOTC also has a tab in the Motors and Sensors Setup window that lists the pins that can be used. In addition, it handles all of the complex options that need to be set for the hardware PWM to work.
Programming
In this code, we want the LEDs to dim in and out over a period of 10 seconds.
Configure ROBOTC
Again, we need to start with a new source code file and tell ROBOTC which Arduino board we are using. Then we need to configure the pins by going into the Motors and Sensors Setup. Up to this point everything is the same as for the flashing LED configurations. In order to be able to set PWM output we need to configure pins 5 and 6 each as a Variable Intensity LED. To do this we go into the Motors tab, not the Digital 0-13 tab. We want to configure motor_5 and motor_6, which map to digital pins 5 and 6 respectively. Give motor_5 the name "led1" and motor_6 the name "led2". Now set the type of both to Variable Intensity LED and click OK.
NOTE: At the time of writing, this version of the RobotC Arduino firmware does not work with "Variable Intensity LED". It is possible to make the circuit work by configuring the pin as "Ext. HBridge - Single PWM" until the bug is fixed.
The file should have the following code at the top.
#pragma config(CircuitBoardType, typeCktBoardUNO) #pragma config(UART_Usage, UART0, uartSystemCommPort, baudRate200000, IOPins, dgtl1, dgtl0) #pragma config(Motor, motor_5, led1, tmotorVariableIntensityLED, openLoop, reversed, IOPins, dgtl5, None) #pragma config(Motor, motor_6, led2, tmotorVariableIntensityLED, openLoop, reversed, IOPins, dgtl6, None) //*!!Code automatically generated by 'ROBOTC' configuration wizard !!*// |
Programming task main()
First, add task main() to the code. After that, we will program the desired behaviors.
To tell the Arduino what Duty-Cycle to output to led1, for example, we would use this code:
motor[led1] = level; |
where level is intensity level from -127 to 127, with 0 mapping to 0% and both -127 and 127 mapping to 100%.
To make the LED dim in and out, we will have the code slowly step the values up and down over time. For demonstration purposes we will increase or decrease the Duty-Cycle by 10% (level value: 12.7) every 500ms. Unfortunately, the motor command used for this only accepts whole numbers. Since we are hard coding the values, this is not a problem, as we can round by hand. So after doing the math, we get the following values:
| Time (ms) | led1 value | led2 value |
|---|---|---|
| 0 | 127 | 0 |
| 500 | 114 | 13 |
| 1000 | 102 | 25 |
| 1500 | 89 | 38 |
| 2000 | 76 | 51 |
| 2500 | 64 | 64 |
| 3000 | 51 | 76 |
| 3500 | 38 | 89 |
| 4000 | 25 | 102 |
| 4500 | 13 | 114 |
| 5000 | 0 | 127 |
| 5500 | 13 | 114 |
| 6000 | 25 | 102 |
| 6500 | 38 | 89 |
| 7000 | 51 | 76 |
| 7500 | 64 | 64 |
| 8000 | 76 | 51 |
| 8500 | 89 | 38 |
| 9000 | 102 | 25 |
| 9500 | 114 | 13 |
Combining the times and values with the commands to set the outputs and the pauses, you get
#pragma config(CircuitBoardType, typeCktBoardUNO) #pragma config(UART_Usage, UART0, uartSystemCommPort, baudRate200000, IOPins, dgtl1, dgtl0) #pragma config(Motor, motor_5, led1, tmotorVariableIntensityLED, openLoop, reversed, IOPins, dgtl5, None) #pragma config(Motor, motor_6, led2, tmotorVariableIntensityLED, openLoop, reversed, IOPins, dgtl6, None) //*!!Code automatically generated by 'ROBOTC' configuration wizard !!*// task main() { while(true) //repeat indefinitely { motor[led1] = 127; motor[led2] = 0; wait1Msec(500); motor[led1] = 114; motor[led2] = 13; wait1Msec(500); motor[led1] = 102; motor[led2] = 25; wait1Msec(500); motor[led1] = 89; motor[led2] = 38; wait1Msec(500); motor[led1] = 76; motor[led2] = 51; wait1Msec(500); motor[led1] = 64; motor[led2] = 64; wait1Msec(500); motor[led1] = 51; motor[led2] = 76; wait1Msec(500); motor[led1] = 38; motor[led2] = 89; wait1Msec(500); motor[led1] = 25; motor[led2] = 102; wait1Msec(500); motor[led1] = 13; motor[led2] = 114; wait1Msec(500); motor[led1] = 0; motor[led2] = 127; wait1Msec(500); motor[led1] = 13; motor[led2] = 114; wait1Msec(500); motor[led1] = 25; motor[led2] = 102; wait1Msec(500); motor[led1] = 38; motor[led2] = 89; wait1Msec(500); motor[led1] = 51; motor[led2] = 76; wait1Msec(500); motor[led1] = 64; motor[led2] = 64; wait1Msec(500); motor[led1] = 76; motor[led2] = 51; wait1Msec(500); motor[led1] = 89; motor[led2] = 38; wait1Msec(500); motor[led1] = 102; motor[led2] = 25; wait1Msec(500); motor[led1] = 114; motor[led2] = 13; wait1Msec(500); } } |
Incrementing using a For Loop
It might seem a bit silly to write that much code just to increment LED brightness. Fortunately, there is a way to greatly simplify it, through the use of a for() loop.
#pragma config(CircuitBoardType, typeCktBoardUNO) #pragma config(UART_Usage, UART0, uartSystemCommPort, baudRate200000, IOPins, dgtl1, dgtl0) #pragma config(Motor, motor_5, led1, tmotorVariableIntensityLED, openLoop, reversed, IOPins, dgtl5, None) #pragma config(Motor, motor_6, led2, tmotorVariableIntensityLED, openLoop, reversed, IOPins, dgtl6, None) //*!!Code automatically generated by 'ROBOTC' configuration wizard !!*// task main() { while(true) //repeat indefinitely { for(int i=0; i < 10; i++) { motor[led1] = 127 - (127 * i / 10); motor[led2] = (127 * i / 10); wait1Msec(500); } for(int i=0; i < 10; i++) { motor[led1] = (127 * i / 10); motor[led2] = 127 - (127 * i / 10); wait1Msec(500); } } } |
The for() loop works by defining a variable to store a number, which in this case is "i". The for() loop sets the variable to a starting value, in this case 0 ("int i=0") and will then check that the value of the variable meets the requirements set after the first semi-colon. For this loop, "i < 10" is the requirement (the value of i is less than 10). If the requirements are met, then the code inside the curly brackets will execute. Once that is completed, the for loop looks at the code after the second semi-colon in the parentheses. This code section tells the for loop what to do with the variable between each execution. In this case, it is telling the loop to increment the value of i by 1 ("i++", "++" is a c command to increment a variable by +1).
You will notice that there is what looks like an equation after the equals sign for the motor command. This particular equation is used to calculate the value for the led at each stage of the loop.
ROBOTC follows regular order of operations when doing math, which means that the code equation is almost identical to a normal math equation.
| ROBOTC equation | Math equation |
|---|---|
| 127 - (127 * i / 10) | 127-(127×i/10) |
| (127 * i / 10) | (127×i/10) |
where i is the value of the variable i at the time of the evaluation. To help you understand, here is a table of the values of i and the LED intensities.
| Loop run number | i value before loop | i value after loop | will execute code | led1 value | led2 value |
|---|---|---|---|---|---|
| 0 | 0 | 1 | Yes | 127 | 0 |
| 1 | 1 | 2 | Yes | 114 | 13 |
| 2 | 2 | 3 | Yes | 102 | 25 |
| 3 | 3 | 4 | Yes | 89 | 38 |
| 4 | 4 | 5 | Yes | 76 | 51 |
| 5 | 5 | 6 | Yes | 64 | 64 |
| 6 | 6 | 7 | Yes | 51 | 76 |
| 7 | 7 | 8 | Yes | 38 | 89 |
| 8 | 8 | 9 | Yes | 25 | 102 |
| 9 | 9 | 10 | Yes | 13 | 114 |
| 10 | 10 | N/A | No | N/A | N/A |
