This application note describes how to control the brighness of an LED by implementing a Pulse Density Modulator, or PDM. This note focuses on the XCORE aspects (I/O and timing) and we give a brief explanation on how a PDM is implement it. We describe different, equally valid, methods to implement this on an XCORE. The method one deploys will depend on how it is integrated in a system.
In the code below we use Pulse Density Modulation to glow the LED. In Pulse Density Modulation you, on a clock beat, drive either a high signal or a low signal. You do this so that the average signal over many clock beats is the desired output.
The code we use implements a very basic Pulse Density Modulator that is suitable for glowing an LED. Let us assume that the level that we want the LED to glow at is a real number in the range [0..1] inclusive, where 0 means completely off, and 1 means completely on.
The trick is to maintain a fraction of time that we have yet to be on for. This is an accumulated error. On every clock-beat we add the level that we want to be at to the accumulated error, and if the result is 1.0 or more we switch the LED on for that next beat. Otherwise we switch the LED off for that beat:
Add level to the accumulated error
If the accumulated error is 1.0 or more then the LED must be ON for a clock-beat, and subtract we record this by subtracting 1.0 from the accumulated error
Else LED is OFF for one clock-beat
Assuming that we want to drive 0.65 as a value, we go through the following sequence:
Error
Level
Error+Level
Output
New Error
0.0
0.65
0.65
OFF
0.65
0.65
0.65
1.30
ON
0.30
0.30
0.65
0.95
OFF
0.95
0.95
0.65
1.60
ON
0.60
0.60
0.65
1.25
ON
0.25
0.25
0.65
0.90
OFF
0.90
0.90
0.65
1.55
ON
0.55
0.55
0.65
1.20
ON
0.20
0.20
0.65
0.85
OFF
0.85
..and so on. You can see that the LED seems to be mostly going through a sequence ON ON OFF, (2/3, or 0.667); with the occasional extra OFF it will be on for 13 out of every 20 clock beats
In order to implement this, we have not used real numbers but instead integers in the range [0..100] inclusive, where 0 is OFF, and 100 is OFF. This means that the level can be seen as a percentage:
#include <xcore/port.h>
#include <xcore/clock.h>
#include <platform.h>
port_t led = PORT_LEDS;
void glow_fixed_simple(int percentage) {
int accumulator = 0;
for(int i = 0; i < 10000000; i++) {
int on;
accumulator += percentage;
if (accumulator >= 100) {
on = 1;
accumulator -= 100;
} else {
on = 0;
}
port_out(led, on);
}
}
There is one line in the code above which is not standard C, which is the
function call port_out(led, on). This call outputs (drives) the value
found in the variable on onto the port found in led. In this case,
led is a global variable that refers to the port on which the LEDs are
connected. If on is the value 1, then a high VDDIO signal will be
driven, if on is the value 0, then VSS (0 V) will be driven.
More information on XCORE ports can be found in AN03000: XCORE Input and Output and AN03007: XCORE Ports.
The above modulator switches the LED on and off, but the period of ON and OFF depends on the length of time executed in each of the branches of the IF statement.
We can make this precise by using a timer. We increment the timer at some fixed interval, and we wait for each next interval before we drive the port on or off.
#include <xcore/hwtimer.h>
#include <xcore/port.h>
void glow_fixed_timer(int percentage) {
hwtimer_t tmr = hwtimer_alloc();
int accumulator = 0;
int next_time = hwtimer_get_time(tmr);
for(int i = 0; i < 5000; i++) { // five seconds
int on;
accumulator += percentage;
if (accumulator >= 100) {
on = 1;
accumulator -= 100;
} else {
on = 0;
}
next_time += 100000; // 1 ms
hwtimer_set_trigger_time(tmr, next_time);
(void) hwtimer_get_time(tmr);
port_out(led, on);
}
hwtimer_free(tmr);
}
There are four calls involved in the timer. The first call
hwtimer_alloc() allocates a hardware timer from the pool of timers.
There is an associated
hwtimer_free(tmr) at the end of the function to put the timer back in
the pool.
Timers run at 100 MHz (100,000,000 Hz). The function
hwtimer_get_time(tmr) gets the current value of the timer.
We can add 100,000 to this timer value (100,000/100 MHz = 1 ms) and
then set this time up as the trigger. Now that the trigger is set,
getting the time will wait until the trigger time has been reached, and we
can ignore the result as we know that it is one millisecond later, and we
adjust the LED value. As we only ever add one millisecond at a time to the
timer we never miss any time; this code will, on average, run at exactly 1
kHz, with a few nanoseconds of jitter on each edge.
Timers are documented in lib_xcore documentation.
The final way to glow an LED we discuss is to use the port-clock. Each port has an
associated clock signal that is used to operate the port. The port can only
change on a positive clock edge, and by setting the port to be clocked at a
known future edge we can achieve the same effect as using the timer. We
keep track of the number of the clock edge in a variable clock_edge_number:
#include <xcore/port.h>
#include <stdint.h>
void glow_clocked(int percentage) {
static int accumulator = 0;
static uint16_t clock_edge_number = 0;
accumulator += percentage;
clock_edge_number += 50000;
port_set_trigger_time(led, clock_edge_number);
if (accumulator >= 100) {
accumulator -= 100;
port_out(led, 1);
} else {
port_out(led, 0);
}
}
In this code we have removed the for-loop, and instead glow_clocked()
will have to be called repeatedly. The variables accumulator and
clock_edge_number have been declared static so that their value is
maintained across function calls. We set the clock-edge-number at which we
wish to output using port_set_trigger_time(), and we add 50,000 to the
clock edge number on every iteration. As a port is clocked by default
from a 100 MHz clock, this will result in a PDM that runs at exactly 2 kHz,
with negligible jitter on the edges.
Port edges are only counted in a 16-bit number (the counter is therefore
declared as a uint16_t), so 65,535 is the highest step we can make.
More information on clocked ports can be found in AN03001: XCORE Clocked Input and Output.
In particular, that document
tells you how to obtain the current clock-edge, or how to use clocks that
are not 100 MHz.
The previous two examples had a fixed level (eg, 65), in this example we
pulse the value gently at 1 Hz using a sine waveform. For this we call the
function sinf in order to calculate a raised sine wave. We use the API
we defined earlier and use a clocked port from the previous section for
creating a glow:
#include <math.h>
void pulse() {
for(int i = 0; i < 10000; i++) { // five seconds
glow_clocked(50 + 50 * sinf(i / 2000.0 * 2 * 3.14));
}
}
This application note gives a brief overview of some ways to control the brightness of an LED to illustrate key XCORE port operations: how to create a modulator, how to use a timer, how to use a port-clock, and how to make sure the LED looks pretty.