summaryrefslogtreecommitdiff
path: root/content/blog/multichannel-led-driver/index.rst
blob: c4e17b85ef7d04d61d119897143eee2d8685a749 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
---
title: "32-Channel LED tape driver"
date: 2018-05-02T11:31:14+02:00
---

Theoretical basics
==================

Together, a friend and I outfitted the small staircase at Berlin's Chaos Computer Club with nice, shiny RGB-WW LED tape
for ambient lighting. This tape is like regular RGB tape but with an additional warm white channel, which makes for much
more natural pastels and whites. There are several variants of RGBW tape. Cheap ones have separate RGB and white LEDs,
which is fine for indirect lighting but does not work for direct lighting. Since we wanted to mount our tape in channels
at the front of the steps, we had to use the slightly more expensive variant with integrated RGBW LEDs. These are LEDs
in the 5050 (5.0mm by 5.0mm) form factor common with RGB LEDs that have a small section divided off for the white
channel. The red, green and blue LED chips sit together in the larger section covered with clear epoxy and the white
channel is made up from the usual blue LED inside a yellow phosphor in the smaller section.

Since we wanted to light up all of 15 steps, and for greatest visual effect we would have liked to be able to control
each step individually we had to find a way to control 60 channels of LED tape with a reasonable amount of hardware.

LED tape has integrated series resistors and runs off a fixed 12V or 24V constant-voltage supply. This means you don't
need a complex constant-current driver as you'd need with high-power LEDs. You can just hook up a section of LED tape
to a beefy MOSFET to control it. Traditionally, you would do *Pulse Width Modulation* (PWM) on the MOSFET's input to
control the LED tape's brightness.

Pulse Width Modulation
----------------------

`Pulse Width Modulation`_ is a technique of controlling the brightness of a load such as an LED with a digital signal.
The basic idea is that if you turn the LED on and off much too fast for anyone to notice, you can control its power by
changing how long you turn it on versus how long you leave it off.

PWM divides each second into a large number of periods. At the beginning of each period, you turn the LED on. After
that, you wait a certain time until you turn it off. Then, you wait for the next period to begin. The periods are always
the same length but you can set when you turn off the LED. If you turn it off right away, it's off almost all the time
and it looks like it's off to your eye. If you turn it off right at the end, it's on almost all the time and it looks
super bright to your eye. Now, if you turn it off halfway into the cycle, it's on half the time and it will look to your
eye as half as bright as before. This means that you can control the LED's brightness with only a digital signal and
good timing.

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/pwm_schema.jpg" alt="A visualization of PWM at different duty cycles.">
        <figcaption>Waveforms of two PWM cycles at different duty cycles.</figcaption>
    </figure>

PWM works great if you have a dedicated PWM output on your microcontroller. It's extremely simple in both hardware and
software.  Unfortunately for us, controlling 32 channels with PWM is not that easy. Cheap microcontrollers only have `a
handful of hardware PWM outputs`_, so we'd either have to do everything in software, bit-banging our LED modulation, or
we'd have to use a dedicated chip.

Doing PWM in software is both error-prone and slow.  Since the maximum dynamic range of a PWM signal is limited by the
shortest duty cycle it can do, software PWM being slow means it has poor PWM resolution at maybe 8 bits at most.  Poor
color resolution is not a problem if all you're doing is to fade around the `HSV rainbow`_, but for ambient lighting
where you *really* want to control the brightness down to a faint shimmer you need all the color resolution you can get.

If you rule out software PWM, what remains are dedicated `hardware PWM controllers`_. Most of these have either of three
issues:

* They're expensive
* They don't have generous PWM resolution either (12 bits if you're lucky)
* They're meant to drive small LEDs such as a 7-segment display directly and you can't just hook up a MOSFET to their
  output

This means we're stuck in a dilemma between two poor solutions if we'd want to do PWM. Luckily for us, PWM is not the
only modulation in town.

.. _`Pulse Width Modulation`: https://en.wikipedia.org/wiki/Pulse-width_modulation
.. _`a handful of hardware PWM outputs`: https://www.nxp.com/parametricSearch#/&c=c731_c380_c173_c161_c163&page=1
.. _`HSV rainbow`: https://en.wikipedia.org/wiki/HSL_and_HSV
.. _`hardware PWM controllers`: http://www.ti.com/lit/ds/symlink/tlc5940.pdf

Binary Code Modulation
----------------------

PWM is the bread-and-butter of the maker crowd. Everyone and their cat is doing it and it works really well most of the
time. Unbeknownst to most of the maker crowd, there is however another popular modulation method that's mostly used in
professional LED systems: Enter `*Binary Code Modulation* (BCM) <http://www.batsocks.co.uk/readme/art_bcm_1.htm>`_.

BCM is to PWM sort of what barcodes are to handwriting. While PWM is easy to understand and simple to implement if all
you have is a counter and an IO pin, BCM is more complicated. On the other hand, computers can do complicated and BCM
really shines in multi-channel applications.

Similar to PWM, BCM works by turning on and off the LED in short periods fast enough to make your eye perceive it as
partially on all the time. In PWM the channel's brightness is linearly dependent on its duty cycle, i.e. the percentage
it is turned on. In PWM the duty cycle D is the total period T divided by the on period T_on. The issue with doing PWM
on many channels at once is that you have to turn off each channel at the exact time to match its duty cycle.
Controlling many IO pins at once with precise timing is really hard to do in software.

BCM avoids this by further dividing each period into smaller periods which we'll call *bit periods* and splitting each
channel's duty cycle into chunks the size of these bit periods. The amazingly elegant thing in BCM now is that as you
can guess from the name these bit periods are weighted in powers of two. Say the shortest bit period lasts 1
microsecond. Then the second-shortest bit period is 2 microseconds and the third is 4, the fifth 8, the sixth 16 and so
on.

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/bcm_schema.jpg" alt="A visualization of BCM at different duty cycles.">
        <figcaption>Waveforms of a single 4-bit BCM cycle at different duty cycles. This BCM can produce 16 different
        levels.</figcaption>
    </figure>

Staggered like this, you turn on the LED for integer value of microseconds by turning it on in the bit periods
corresponding to the binary bits of that value. If I want my LED to light for 19 microseconds every period, I turn it on
in the 16 microsecond bit period, the 2 microsecond bit period and the 1 microsecond bit period and leave it off for the
4 and 8 mircosecond bit periods.

Now, how this is better instead of just more complicated than plain old PWM might not be clear yet. But consider this:
Turning on and off a large number of channels, each at its own arbitrary time is hard because doing the timing in
software is hard. We can't use hardware timers since we only have two or three of those, and we have 32 channels.
However, we can use one hardware timer to trigger a really cheap external latch to turn on or off the 32 channels all at
once. With this setup, we can only controll all channels at once, but we can do so with very precise timing.

All we need to do is to set our timer to the durations of the BCM bit periods, and we can get the same result as we'd
get with PWM with only one hardware timer and a bit of code that is not timing-critical anymore.

Applications of Binary Code Modulation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

BCM is a truly wondrous technique, and outside of hobbyist circles it is in fact very widely known. Though we're using
it to control just 32 channels here, you can do much more channels without any problems. The most common application
where BCM is invariably used is *any* kind of LED screen. Controlling the thousands and thousands of LEDs in an LED
screen with PWM with a dedicated timer for each LED would not be feasible. With BCM, all you need to dedicate to a
single LED is a flipflop (or part of one if you're multiplexing). In fact, there is a whole range of `ICs with no other
purpose than to enable BCM on large LED matrices <http://www.vabolis.lt/stuff/MBI5026.pdf>`_. Basically, these are a
high-speed shift register with latched outputs much like the venerable 74HC595_, only their outputs are constant-current
sinks made so that you can directly connect an LED to them.

.. _74HC595: http://www.ti.com/lit/ds/symlink/sn74hc595.pdf

Running BCM on LED tape
~~~~~~~~~~~~~~~~~~~~~~~

In our case, we don't need any special driver chips to control our LED tape. We just connect the outputs of a 74HC595_
shift register to one MOSFET_ each, and then we directly connect the LED tape to these MOSFETs. The MOSFETs allow us to
drive a couple of amps into the LED tape from the weak outputs of the shift register.

The BCM timing is done by hooking up two timer channels of our microcontroller to the shift registers *strobe* and
*reset* inputs. We set the timer to PWM mode so we can generate pulses with precise timing. At the beginning of each
bit period, a pulse will strobe the data for this bit period that we shifted in previously. At the end of the bit
period, one pulse will reset the shift register and one will strobe the freshly-reset zeros into the outputs.

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/olsndot_output_schematic.jpg" alt="From left to right, we see the STM32, one of the shift
        registers, and the LEDs and MOSFETs. The LED tape is driven to ground by the MOSFETs, which are in turn directly
        driven from the shift register outputs. The shift register is wired up to the STM32 with its clock and data
        inputs on SCK and MOSI and its RESET and STROBE inputs on channel 2 and 3 of timer 1.">
        <figcaption>
            The schematic of a single output of this LED driver. Multiple shift register stages can be cascaded.
        </figcaption>
    </figure>


Our implementation of this system runs on an STM32F030F4P6_, the smallest, cheapest ARM microcontroller you can get from
ST. This microcontroller has only 16kB of flash and 1kB of RAM, but that's plenty for our use. We use its SPI controller
to feed the modulation data to the shift registers really fast, and we use two timer channels to control the shift
registers' reset and strobe.

We can easily cascade shift registers without any ill side-effects, and even hundreds of channels should be no problem
for this setup. The only reason we chose to stick to a 32-channel board is the mechanics of it. We thought it would be
easier to have several small boards instead of having one huge board with loads of connectors and cables coming off it.

The BOM cost per channel for our system is 3ct for a reasonable MOSFET, about 1ct for one eighth of a shift register
plus less than a cent for one resistor between shift register and MOSFET. In the end, the connectors are more expensive
than the driving circuitry.

.. _MOSFET: https://en.wikipedia.org/wiki/MOSFET
.. _STM32F030F4P6: http://www.st.com/resource/en/datasheet/stm32f030f4.pdf

Hardware design
===============

From this starting point, we made a very prototype-y hardware design for a 32-channel 12V LED tape driver. The design is
based on the STM32F030F4P6_ driving the shift registers as explained above. The system is controlled through an RS485_
bus that is connected up to the microcontroller's UART using an MAX485_-compatible RS485 transceiver. The LED tape is
connected using 9-pin SUB-D_ connectors since they are cheap and good enough for the small current of our short segments
of LED tape. The MOSFETs we use are small SOT-23_ logic-level MOSFETs. In various prototypes we used both International
Rectifier's IRLML6244_ as well as Alpha & Omega Semiconductor's AO3400_. Both are good up to about 30V/5A.  Since we're
only driving about 2m of LED tape per channel we're not going above about 0.5A and the MOSFETs don't even get warm.

.. _RS485: https://en.wikipedia.org/wiki/RS-485
.. _MAX485: https://datasheets.maximintegrated.com/en/ds/MAX1487-MAX491.pdf
.. _IRLML6244: https://www.infineon.com/dgdl/?fileId=5546d462533600a4015356686fed261f
.. _AO3400: http://aosmd.com/pdfs/datasheet/AO3400.pdf
.. _SUB-D: https://en.wikipedia.org/wiki/D-subminiature
.. _SOT-23: http://www.nxp.com/documents/outline_drawing/SOT23.pdf

Switching nonlinearities
------------------------
During testing of our initial prototype, we noticed that the brightness seemed to jump around when fading to very low
values. It turned out that our extremely simple LED driving circuit consisting of only the shift register directly
driving a MOSFET, which in turn directly drives the LED tape was maybe a little bit too simple. After some measurements
it turned out that we were looking at about 6Vpp of ringing on the driver's output voltage. The picture below is the
voltrage we saw on our oscilloscope on the LED tape.

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/driver_ringing_strong.jpg" alt="Strong ringing on the LED voltage waveform edge at about
            100% overshoot during about 70% of the cycle time.">
        <figcaption>Bad ringing on the LED output voltage caused by wiring inductance. Note that the effect on the
        actual LED current is less bad than this looks since the LED's V/I curve is nonlinear.</figcaption>
    </figure>


Dynamic switching behavior: Cause and Effect
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A bit of LTSpice_ action later we found that the inductance of the few metres of cable leading to the LED tape is the
likely culprit. The figure below is the schematic used for the simulations.

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/driver_output_ltspice_schematic.jpg" alt="The LTSpice schematic of one output of the driver,
        taking into account the shift register's output ESR and the wiring ESL.">
        <figcaption>The schematic of the simulation in LTSpice</figcaption>
    </figure>

As tested, the driver does not include any per-output smoothing so the ~.5A transient on each BCM cycle hits the cable
in full.  Combined with the cable inductance, this works out to a considerable lag of the rising edge of the LED
current, and bad ringing on its falling edge. Below is the voltage on the LED output from an LTSpice simulation of our
driver.

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/overshoot_sim_r0.svg" alt="The result of the LTSpice simulation of our driver output. The LED
        current shows similar ringing to what we measured using the oscilloscope. Interestingly, the gate voltage shows
        strong ringing, too.">
        <figcaption>The result of our LTSpice simulation. This simulation assumes 1µH of wiring inductance and 50Ω of
        output impedance on the part of the shift register. The ringing at the gate visible in the gate voltage graph is
        due to feed-through of the ringing at the output through the MOSFET's parasitic Cgd.</figcaption>
    </figure>

We were able to reduce the rining and limit the effect somewhat by putting a 220Ω series resistor in between the shift
register output and the MOSFET gate. This resistor forms an RC circuit with the MOSFET's nanofarad or two of gate
capacitance. The result of this is that the LED current passing the wire's ESL rises slightly more slowly and thus the
series inductance gets excited slightly less, and the overshoot decreases. Below is a picture of the waveform with the
damping resistor in place and a picture of our measurement for comparison. The resistor values don't agree perfectly
since the estimated ESL and stray capacitance of the wiring is probably way off.

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/driver_ringing_weak.jpg" alt="Weak ringing on the LED voltage waveform edge at about 30%
        overshoot during about 20% of the cycle time.">
        <figcaption>Adding a resistor in front of the MOSFET gate to slow the transition damped the ringing somewhat,
        but ultimately it cannot be eliminated entirely. Note how you can actually see the miller plateau on the
        trailing edge of this signal.
        </figcaption>
    </figure>

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/overshoot_sim_r100.svg" alt="The result of the LTSpice simulation of our driver output with an
        extra 100 Ohms between shift register output and MOSFET gate. Similar to the oscilloscope measurement the
        ringing is much reduced in its amplitude.">
        <figcaption>The LTSpice simulation result with the same parameters as above but with an extra 100Ω between the
        shfit register's output and the MOSFET's gate.</figcaption>
    </figure>

A side effect of this fix is that now the effective on-time of the LED tape is much longer than the duty cycle at the
shift register's output at very small duty cycles (1µs or less). This is caused by the MOSFET's `miller
plateau`_. For illustration, below is a graph of both the excitation waveform (the boxy line) and the resulting LED
current (the other ones) both without damping (top) and with 220Ω damping (bottom). As you can see the effective duty
cycle of the LED current is not at all equal to the 50% duty cycle of the excitation square wave.

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/asymmetric_iled.svg" alt="The result of an LTSpice simulation of the LED duty cycle without and
        with damping. Dampening widens the LED current waveform from 50% duty cycle with sharp edges to about 80% duty
        cycle with soft edges.">
        <figcaption>Simulated LED duty cycle with and without damping. The damping resistance used in this simulation
        was 220Ω.</figcaption>
    </figure>

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/asymmetric_vgate.svg" alt="The gate voltages in the spice simulation above. The undamped
        response shows sharp edges with the miller plateau being a barely noticeable step, but with strong ringing on
        the trailing edge. The damped response shows RC-like slow-edges, but has wide miller plateaus on both edges
        adding up to about 50% of the pulse width.">
        <figcaption>The MOSFET gate voltage from the simulation in the figure above. You can clearly see how the miller
        plateau (the horizontal part of the trace at about 1V) is getting much wider with added damped, and how the
        resulting gate charge/discharge curve is not at all that of a capacitor anymore.</figcaption>
    </figure>



In conclusion, we have three major causes for our calculated LED brightness not matching reality:

* Ringing of the equivalent series inductance of the wiring leading up to the LED tape
* Miller plateau lag
* The damping resistor and the MOSFET gate forming an RC filter that helps with wire ESL ringing but worsens the miller
  plateau issue and deforms the LED current edges.

Added up, these three effects yield a picture that agrees well with our simulations and measurements. The overall effect
is neglegible at long period durations (>10µs), but gets really bad at short period durations (<1µs). The effect is
non-linear, so correcting for it is not as simple as adding an offset.

.. _LTSpice: http://www.analog.com/en/design-center/design-tools-and-calculators/ltspice-simulator.html
.. _`miller plateau`: https://www.vishay.com/docs/68214/turnonprocess.pdf

Measuring LED tape brightness
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In order to correct for the nonlinearities mentioned above, we decided to implement a lookup table mapping BCM period to
actual timer setting. That is, each row of the table contains the actual period length we need to set the
microcontroller's timer to in order to get our intended brightness steps.

To calibrate our driver, we needed a setup for reproducible measurement of the relative brightness of our LED tape at
different settings. Absolute brightness is not of interest to us as the eye can't perceive it. To perform the
calibration, the LED driver is set to enable each single BCM period in turn, i.e. brightness values 1, 2, 4, 8, 16 etc.

The setup we used to measure the LED tape's brightness consists of a bunch of LED tape stuck into a tin can for
shielding against both stray light and electromagnetic interference and a photodiode looking at the LED tape. We used
the venerable BPW34_ photodiode in our setup as I had a bunch leftover from another project and because they are quite
sensitive owing to their physically large die area.

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/linearization_setup.jpg" alt="The led measurement setup consists of several PCBs and a
        breadboard linked with a bunch of wires and a big tin can to shield the LEDs and the photodiode. A large sub-D
        connector is put into the top of the tin can as a feed-through for the LED tape's control signals and the
        photodiode signal. In the background the control laptop is visible.">
        <figcaption>The LED brighness measurement setup. The big tin can contains a bunch of LED tape and the
        photodiode. The breadboard on the right is used for the photodiode preamplifier and for jumpering around the LED
        tape's channels. The red board next to it is the buspirate used as ADC. The board on the bottom left is a
        TTL-to-RS485 converter and the board in the middle is the unit under test.</figcaption>
    </figure>

The photodiode's photocurrent is converted into a voltage using a very simple transimpedance amplifier based around a
MCP6002_ opamp that was damped into oblivion with a couple nanofarads of capacitance in its feedback loop. The MCP6002_
is a fine choice here since I had a bunch and because it is a CMOS opamp, meaning it has low bias current that would
mess up our measurements. For many applications, opamp bias current is not a big issue but when using the opamp to
directly measure very small currents at its input it quickly swamps out the signal for most BJT-input types.

The transimpedance amplifier's output is read from the computer using the ADC input of a buspirate USB thinggamajob. In
general I would not recommend the buspirate as a tool for this job since it's ADC is not particularly good and it's
programming interface is positively atrocious, but it was what I had and it beat first wiring up one of the dedicated
ADC chips I had in my parts bin.

The computer runs a small python script cycling the LED tape through all its BCM period settings and taking a brightness
measurement at each step. Later on, these measurements can be plotted to visualize the resulting slope's linearity, and
we can even do a simulation of the resulting brightness for all possible control values by just adding the measured
photocurrents for a certain BCM setpoint just as our retinas would do.

.. raw:: html
    
    <figure data-pagefind-ignore>
        <img src="images/driver_linearity_raw.svg" alt="">
        <figcaption>
            A plot of the measured brightness of our LED tape for each BCM period. The brightness values are normalized
            to the value measured at the LSB setpoint (brightness=1/65535). Ideally, this plot would show a straight
            line with slope 1. Obviously, it doesn't. The bend in the curve is caused by the above-mentioned duty cycle
            offset adding an offset to all brightness values. Shown is both the raw data (light), which has essentially zero
            measurement error and a linear fit (dark).

            The plot is in log-log to approximate how the human eye would perceive brightness, i.e. highly sensitive at
            low values but not very sensitive at all at large values.
        </figcaption>
    </figure>

While it would be possible to fully automate the optimization of BCM driver lookup tables, we needed only one and in the
end I just sat down and manually tweaked the ideal values we initially calculated until I liked the result. You can see
the resulting brightness curve below.

.. raw:: html
    
    <div class="subfigure" data-pagefind-ignore>
        <figure>
            <img src="images/uncorrected_brightness_sim.svg" alt="">
            <figcaption>
                Calculated brightness curve for the uncorrected BCM setup. As you can see, at low setpoints the result
                is about as smooth as sandpaper, which is well in line with our observations. At high setpoints the
                offset gets swamped out and the nonlinearity in the low bits is not visible anymore.
            </figcaption>
        </figure>
       <figure>
            <img src="images/corrected_brightness_sim.svg" alt="">
            <figcaption>
                Brightness curve for the corrected BCM setup extrapolated using actual measurements. Looks as buttery
                smooth in real life as it does in this plot.
            </figcaption>
            </figcaption>
        </figure>
    </div>

.. _BPW34: http://www.vishay.com/docs/81521/bpw34.pdf
.. _MCP6002: http://ww1.microchip.com/downloads/en/DeviceDoc/21733j.pdf

Controlling the driver
----------------------

Now that our driver was behaving linear enough that you couldn't see it actually wasn't we needed a nice way to control
it from a computer of our choice. In the ultimate application (our staircase) we'll use a raspberry pi for this. Since
we already settled on an RS485_ bus for its robustness and simplicity, we had to device a protocol to control the driver
over this bus. Here, we settled on a simple, COBS_-based protocol for the reasons I wrote about in `How to talk to your
microcontroller over serial <serial-protocols>`_.

To address our driver nodes, we modified the Makefile to build a random 32-bit MAC into each firmware image. The
protocol has only five message types:

1. A 0-byte *ping* packet, to which each node would reply with its own address in the
   first 100ms after boot. This can be used to initially discover the addresses of all nodes connected to the bus. You'd
   spam the bus with *ping* packets, and then hit reset on each node in turn. The control computer would then receive
   each device's MAC address as you hit reset.
2. A 4-byte *address* packet that says which device that the following packet is for. This way of us using the packet
   length instead of a packet type field is not particularly elegant, but our system is simple enough and it was easy to
   implement.
3. A 64-byte *frame buffer* packet that contains 16 bits of left-aligned brightness data for every channel
4. A one-byte *get status* packet that tells the device to respond with...
5. ...a 27-byte status packet containing a brief description of the firmware (version number, channel count, bit depth
   etc.) as well as the device's current life stats (VCC, temperature, uptime, UART frame errors etc.).

Wrapped up in a nice python interface we can now easily enumerate any drivers we connect to a bus, query their status
and control their outputs.

.. _COBS: https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing

Conclusion
----------

.. raw:: html

    <div class="subfigure" data-pagefind-ignore>
        <figure>
            <a href="images/olsndot_schematic.png">
                <img src="images/olsndot_schematic.png" alt="A picture of the LED driver schematic">
            </a>
            <figcaption>The LED driver <a href="images/olsndot_schematic.png">schematic</a></figcaption>
        </figure>
        <figure>
            <a href="images/olsndot_pcb.png">
                <img src="images/olsndot_pcb.png" alt="A picture of the LED driver PCB layout">
            </a>
            <figcaption>The LED driver <a href="images/olsndot_pcb.png">PCB layout</a></figcaption>
        </figure>
    </div>

Putting some thought into the control circuitry and software, you can easily control large numbers of channels of LEDs
using extremely inexpensive driving hardware without any compromises on dynamic range. The design we settled on can
drive 32 channels of LED tape with a dynamic range of 14bit at a BOM cost of below 10€. All it really takes is a couple
of shift registers and a mildly bored STM32 microcontroller.

Get a PDF file of the schematic and PCB layout `here <olsndot_v02_schematics_and_pcb.pdf>`__ or download the CAD files
and the firmware sources `from github <https://github.com/jaseg/led_drv>`_. You can view the Jupyter notebook used to
analyze the brightness measurement data `here <http://nbviewer.jupyter.org/github/jaseg/led_drv/blob/master/doc/Run_analysis.ipynb>`__.