Skip to content

Timers, PWM and the RGB LED

The little computer of part 2 talks to the world through a handful of devices: eight LEDs, a button, four digits, a timer and a tone. This part gives it more, one page at a time, each a small piece of gateware sitting on the bus, with a few registers the CPU reads and writes. By the end it reads buttons and a knob, talks over a serial line and MIDI, drives a screen and plays music through a sound chip of its own, all on the UPduino’s FPGA.

This first page is about how a device joins the bus at all, and about the first new one: a light whose brightness the CPU can set, by switching it on and off very fast.

1 · A device is a few registers on the bus

Section titled “1 · A device is a few registers on the bus”

From the CPU’s side, a device is nothing but addresses. Writing FFtoFF to 8000 lights the LEDs; reading $8001 says whether the button is held. The CPU does exactly what it does for memory, and something on the bus decides that this address is a device’s.

That something is address decoding, as on Memory and buses: a little logic that looks at the address and says which part of the computer it belongs to. The memory map gives the I/O space, $8000–$BFFF, to devices. Part 2’s devices take the first sixteen addresses; this part’s go above them, each with a block of its own:

AddressesDevicePage
$8000–$800Fpart 2’s: LEDs, button, digits, timer 1, toneMemory and buses, Jumps, the stack and interrupts
$8010–$8012the RGB LEDthis one
$8020–$8023buttons and a knobButtons and knobs
$8030–$8034a serial portA serial port
$8040–$8044MIDIMIDI
$8050–$8053SPISPI
$8060–$8062I²CI²C
$8070–$8072the one-bit DAC (the audio out)A one-bit DAC
$8100–$8112, $9000–$A3FFvideoVideo out
$8400–$841Cthe sound chipA sound chip

In the gateware, machine.v (the computer of part 2) decodes its own sixteen addresses and passes everything else in the I/O space out on an expansion bus: the address, the byte being written, whether it’s a write, and the clock enable (the CPU takes a step on one clock in twelve, so from the board’s 12 MHz it runs at 1 MHz, and everything on the bus acts on that same clock). board.v decodes each device’s block from there:

wire s_rgb = ext && addr[15:4] == 12'h801; // $8010–$801F: the RGB LED
wire s_btn = ext && addr[15:4] == 12'h802; // $8020–$802F: buttons and the knob

Each device receives its decoded line as an input called sel (short for “selected”), which is high while the CPU is talking to it. The low bits of the address bus are passed to the device too, and say which of its own registers is being addressed. A device needs three things:

  • Registers the CPU writes: flip-flops that take the data bus’s byte on a clock where ce, sel and the write signal are all high.
  • A way to be read: a multiplexer that puts the chosen register’s byte out. The bus collects every device’s byte and passes on the one whose sel is high. (On Memory and buses that was tri-state outputs on shared wires. Inside an FPGA there are no tri-state wires, so it’s a multiplexer: the same idea, the choosing done by logic instead.)
  • Its job, whatever it does with those registers, all the time, whether the CPU is looking or not.

Here’s the RGB LED’s gateware, exactly as it goes on the board. Its three registers are the brightness of red, green and blue; its job is pulse-width modulation, the subject of the next section.

rgb.v, simulated gate by gate. To write a register, as the CPU would: set a to 0 (red), d to a small level such as 3, sel, we and ce to 1, and click Clock; then set sel, we and ce back to 0 and keep clocking. red is on while count is below the level, so for the first 3 counts of every 256. And q is always what a read of register a would give back.

An FPGA pin is either high or low: there’s nothing in between. So how does a digital circuit dim a light?

It switches it fully on and fully off, so fast that the eye can’t follow, and varies how long it’s on. A counter runs round from 0 to 255; the output is on while the counter is below the level, and off from there to the end. The level is how many of every 256 clocks the light is on. That’s pulse-width modulation (PWM): the information is in the width of the pulses.

The counter (top) goes round 0 to 255 over and over, each step a clock of 12 MHz; the output (bottom) is on while it’s below the level. Move the level and the pulses widen and narrow; the dashed line is the average, the share of the time the output is on. The square at the bottom right is that average, as the eye would see it. A period is 256 clocks, 21.33 µs: 46,875 of them a second.

At 46,875 periods a second, an eye sees only the average. So does almost anything slow: a motor turns at a speed set by the average voltage; a speaker cone can’t follow 47 kHz and moves with the average; and the beeper of part 2 had a DUTY register for the same reason. To get the average out as a real voltage, put the output through a resistor and a capacitor (a low-pass filter): that’s a crude digital-to-analogue converter, and a page of its own later, A one-bit DAC.

Here’s a catch. The PWM’s level sets an amount of light: level 128 is half the light of level 255. But the eye doesn’t see amounts of light evenly. It’s far more sensitive to differences between dim lights than between bright ones: going from 1% to 2% of the light looks like a big step; from 90% to 91%, nothing at all.

Top: eight steps even in the PWM’s level (0, 36, 73 … 255): the steps look bunched up at the bright end, and the dim end jumps. Bottom: eight steps even to the eye, each level 255 × (n / 7)^2.2, rounded: 0, 4, 16, 40, 74, 122, 182 and 255. Each swatch is drawn as the light that level would give, so these are what the LED would look like.

So to fade a light smoothly, the levels have to grow slowly at first and quickly later. A good match to the eye is the level going as the step to the power 2.2, a curve called gamma. (Screens use the same curve, for the same eye: a pixel’s number isn’t the amount of light either. That’s why the swatches above are drawn by converting each level to a screen number first, n^(1/2.2).) A program does it with a table: 32 steps, worked out once, read with LDA table,X.

The UPduino has an RGB LED: three LEDs, red, green and blue, in one package, so close together that from a step back they’re a single light whose colour is their mixture. Three PWM outputs, one each, give it any colour: the RGB LED device above, three counters’ worth of comparing and one shared counter.

The UP5K drives that LED in a way most FPGAs can’t: three of its pins have a constant-current LED driver behind them, which sinks a set current through each LED when its PWM signal is on. So the LED needs no resistors, and its brightness doesn’t change with the supply voltage. board.v connects the three PWM outputs to it.

The computer with the RGB LED at $8010–$8012. Mix a colour writes three levels and stops: change them and run again. Fade up and down fades all three together through a table of 32 gamma steps, one every 20 ms, timed by polling timer 1; change gamma,X to linear,X to see steps even in the duty instead. Colour wheel moves round the colours on timer interrupts, 100 a second: each colour follows the same wave, a third of the way round from the others. The LED is drawn the way the levels’ light would look.

Notice what the CPU does in the colour wheel: almost nothing. Between interrupts it sits in a loop that jumps to itself, and each interrupt is a few dozen instructions. The PWM, 46,875 periods a second for each colour, is all in the gateware.

The colour wheel and the fade both use timer 1, from Jumps, the stack and interrupts. A timer is the simplest peripheral of all: a counter that counts clock cycles down from a period the CPU sets, and when it runs out sets a flag, starts again and, if asked, interrupts. In machine.v it’s this (the register writes left out):

wire t1_out = t1_run && t1_period != 16'd0 && t1_count <= 16'd1; // runs out this cycle
always @(posedge clk) if (ce) begin
if (t1_out) // start again from the period
t1_count <= t1_count + t1_period - 16'd1;
else if (t1_run && t1_period != 16'd0)
t1_count <= t1_count - 16'd1;
if (t1_out) t1_flag <= 1'b1; // seen by reading T1STAT, or as an interrupt
end

A timer is how a program knows how long anything took, or makes anything happen on time. Without one, it counts its own instructions in a delay loop, as part 2’s first programs did, and the count goes wrong the moment the loop changes or an interrupt arrives. A timer counts the clock itself, whatever the CPU is doing.

And it’s worth seeing what having PWM in gateware saves. Here’s PWM done by the CPU instead, with timer 1 interrupting every 100 cycles, the handler counting round 16 steps and switching LED 0:

PWM in software: LED 0 on a quarter of the time, switched by a timer interrupt every 100 cycles. The main program does nothing but count its loops (at $0002–$0003, in the memory view). Measured on this emulator, it gets 51% of the CPU’s time: the other 49% goes on the interrupts. And this PWM has only 16 steps, at 625 periods a second, slow enough to flicker.

Half the CPU, for one light, done badly. The gateware’s PWM does three, with 256 steps each at 46,875 periods a second, and costs the CPU nothing: 89 logic cells of the FPGA. That’s the case for peripherals in a nutshell: anything that has to happen often and on time is cheaper as a little circuit than as a program.

  • A device is registers at addresses: flip-flops the CPU writes, a multiplexer for reading them back, and the job it does with them. Address decoding gives each device its block of the I/O space.
  • PWM dims a light (or slows a motor, or makes a voltage) by switching it fully on and off, quickly, for a share of the time: a counter compared with a level.
  • The eye sees brightness unevenly, so even-looking steps need levels that grow as a power: gamma.
  • The UP5K’s RGB LED has its own current drivers; three PWMs mix any colour.
  • A timer counts clock cycles, so a program can do things on time without counting instructions; and work that has to happen often and on time is cheaper in gateware than in software.
  • Charles Poynton, Digital Video and HD: Algorithms and Interfaces: the chapter on gamma, the clearest account of why light and numbers differ.
  • Lattice Semiconductor’s iCE40 LED driver usage guide: the UP5K’s LED driver and its current settings.