<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>WS2812 on project392.net</title>
        <link>https://www.project392.net/tags/ws2812/</link>
        <description>Recent content in WS2812 on project392.net</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <lastBuildDate>Sun, 01 Nov 2026 00:00:00 +0200</lastBuildDate><atom:link href="https://www.project392.net/tags/ws2812/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Quick Build: House Number Sign</title>
        <link>https://www.project392.net/p/quick-build-house-number-sign/</link>
        <pubDate>Sun, 01 Nov 2026 00:00:00 +0200</pubDate>
        
        <guid>https://www.project392.net/p/quick-build-house-number-sign/</guid>
        <description>&lt;img src="https://www.project392.net/house-number-project/banner.jpg" alt="Featured image of post Quick Build: House Number Sign" /&gt;&lt;h1 id=&#34;a-party&#34;&gt;A Party!
&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;m coming up, so you better get this party started&lt;br&gt;
— &lt;cite&gt;P!nk&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Friends of mine recently moved house and were kind enough to invite me to their house warming party. I figured that I ought to show some proper house warming party etiquette and bring a gift. It would be inappropriate not to. Certainly now that they have so much extra space to stash away all those gifts.&lt;/p&gt;
&lt;p&gt;But what to bring?&lt;/p&gt;
&lt;p&gt;I decided to go for something that&amp;rsquo;s becoming a bit of a speciality of mine: a colorful, illuminated object. This time said object took the shape of my friends&amp;rsquo; house number. In 3D!&lt;/p&gt;
&lt;h2 id=&#34;quick-build-long-read&#34;&gt;Quick Build, Long Read
&lt;/h2&gt;&lt;p&gt;I tried a new concept with this project. I normally adhere to longer-running projects that take much preparation. As the title suggests, this was to be a quick project. The kind of build that can ideally be completed within a weekend, but no more than two, with minimal preparation beforehand. You&amp;rsquo;ll find out in due course whether or not I succeeded in meeting that timeline. The length of this article might give away the answer already&amp;hellip;&lt;/p&gt;
&lt;p&gt;So, a quick build. The idea is: collect the materials, grab the tools, start making and see what it brings. To some people this is second nature. To me, it&amp;rsquo;s very far from. I crave a detailed plan and prefer a near perfect result. That puts the whole concept firmly outside my comfort zone. Also, as anyone who has ever worked with me will attest: I&amp;rsquo;m a nightmare at planning.&lt;/p&gt;
&lt;p&gt;The reporting for this one will also be of a different kind. Not a multi-part blog series, but a single write-up that covers everything. That does mean it will be longer than what I usually write. I tried not to be too long-winded, but I did want to put in enough details and some background information. If you have a short attention span, this one may not be for you. On a positive note: there are lots of pictures here!&lt;/p&gt;
&lt;h2 id=&#34;the-plan&#34;&gt;The Plan
&lt;/h2&gt;&lt;p&gt;The plan was to make an oversized house number sign, with 3D-printed numbers and addressable &lt;abbr title=&#34;Light Emitting Diode&#34;&gt;LED&lt;/abbr&gt;s. I wanted my friends (or their kids) to be able to change the colors of the LED at their leisure. That meant a microcontroller and some good old push buttons.&lt;/p&gt;
&lt;p&gt;Given the intended time span of four days max, I figured I shouldn&amp;rsquo;t put to much time in devising a software driver for the LEDs. I downloaded an open source driver for that. Having the driver taken care of would allow me to focus on the application for changing the colors. As for the electronics: a piece of perf board with four push buttons and a low cost microcontroller would do.&lt;/p&gt;
&lt;p&gt;Can&amp;rsquo;t be too hard.&lt;/p&gt;
&lt;p&gt;Or can it?&lt;/p&gt;
&lt;h2 id=&#34;the-tools&#34;&gt;The Tools
&lt;/h2&gt;&lt;p&gt;A quick work on the tools used.&lt;/p&gt;
&lt;p&gt;For the 3D-design I used FreeCAD&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; and the software was developed using the Code::Blocks&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;abbr title=&#34;Integrated Development Environment&#34;&gt;IDE&lt;/abbr&gt;. Both are free and open source tool that I highly recommend.&lt;/p&gt;
&lt;p&gt;There were of course some physical tools involved as well, but not many. First of all a 3D-printer (I use an Ender 3 V2 Neo) and some woodworking tools (a router and a table saw). That, and a soldering iron, naturally.&lt;/p&gt;
&lt;h2 id=&#34;bright-leds&#34;&gt;Bright LEDs
&lt;/h2&gt;&lt;p&gt;The LEDs used here are the well known WS2812&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; addressable &lt;abbr title=&#34;Red Green Blue&#34;&gt;RGB&lt;/abbr&gt; LEDs. Also known as Neo-pixels. I had never worked with them before, but after I studied the datasheet I decided that I liked them.&lt;/p&gt;
&lt;p&gt;The WS2812 are LEDs with an integrated driver. They are controlled by a pulse width modulated signal on a single input pin. &lt;abbr title=&#34;Pulse Width Modulation&#34;&gt;PWM&lt;/abbr&gt; is often used as low-cost way to control the brightness of LEDs. However, with the WS2812 the PWM is actually used to modulate the data. What I like about this approach, is that it allows for a brilliantly simple way to send a self-clocked data stream over a single wire. The PWM frequency is approximately 800kHz. A low bit is represented by a short pulse (220 to 380ns), a high bit by a long pulse (580 to 1000ns).&lt;/p&gt;
&lt;p&gt;The datasheet isn&amp;rsquo;t very clear about how the actual brightness control of the LEDs is achieved. However, there is some information in the datasheet of the integrated driver, the WS2811&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. It&amp;rsquo;s not specified very clearly, but it mentions a constant current driver.&lt;/p&gt;
&lt;h2 id=&#34;getting-into-characters&#34;&gt;Getting Into Characters
&lt;/h2&gt;&lt;p&gt;Now lets discuss the project.&lt;/p&gt;
&lt;p&gt;The first stage of the operation was to quickly start 3D-printing. Printing takes time but I can work on other things while it&amp;rsquo;s happening. However, the printer won&amp;rsquo;t do anything without a sliced 3D model. So, drafting the models had to take priority.&lt;/p&gt;
&lt;p&gt;I used the Draft workbench as well as the Part workbench in FreeCAD to design the 3D numbers. I had never worked with text shapes before but I found a useful tutorial&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; by Mango Jelly Solutions&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; at YouTube that demonstrates nicely how it can be done.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/model-154.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Model&#34;
	
	
&gt;&lt;em&gt;Showing the numbers&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The 3D numbers needed a semi-transparent cover to diffuse the light. I wanted to print these covers with a rim that would fit snugly into the lower part of the assembly. That way both parts would self-align, making them easier to assemble.&lt;/p&gt;
&lt;p&gt;Unfortunately this didn&amp;rsquo;t work out the way I envisioned it. I printed the covers from a different material than I used for the numbers. Since I didn&amp;rsquo;t do a flow tuning for this material, the covers turned out to be slightly larger than the numbers themselves and wouldn&amp;rsquo;t fit. I tried scaling the models in the slicer, but TrueType fonts don&amp;rsquo;t scale linearly so that didn&amp;rsquo;t work. I settled with flat covers instead. They were a bit harder to install but worked well enough.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/ones.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ones&#34;
	
	
&gt;&lt;em&gt;It&amp;rsquo;s hard to tell which one is best&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;a-problem-to-address&#34;&gt;A Problem To Address
&lt;/h2&gt;&lt;p&gt;To control the LEDs, I picked the Light WS2812 library&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt; (link below). It&amp;rsquo;s a lightweight C library to send an array of RGB data to a string of LEDs. I was quite impressed to read that this library supposedly works on hardware as low-end as an Attiny at 4MHz. The Light WS2812 library uses &lt;abbr title=&#34;General Purpose Input Output&#34;&gt;GPIO&lt;/abbr&gt; - rather than a PWM peripheral - to generate the PWM data. In order to make this work, it disables all interrupts during data transmission. The timing is critical and the data transfer should not be, well, interrupted.&lt;/p&gt;
&lt;p&gt;For the hardware I opted for a minimalist setup with an Attiny85&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;, running at its internal 8MHz &lt;abbr title=&#34;Resistor Capacitor&#34;&gt;RC&lt;/abbr&gt; oscillator. Given the short timeline for this built I soldered everything (the &lt;abbr title=&#34;Microcontroller Unit&#34;&gt;MCU&lt;/abbr&gt; and some buttons) on a piece of perf board. This took me a surprisingly long time to solder. And the result was&amp;hellip; rather ugly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/perf-board.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ugly perf-board&#34;
	
	
&gt;&lt;em&gt;I hate perf board&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Then came the time to test the library. I had some reservations about an 8MHz Attiny being able to accurately send out an 800kHz data signal. 800kHz May not sound like a lot, but at 8MHz the MCU only gets 10 clock cycles to process one data bit. That&amp;rsquo;s not much. To encode a low bit, the processing time is even less: about two clock cycles.&lt;/p&gt;
&lt;p&gt;For a quick test I modified the demo code that came with the library to turn the first 4 LEDs of my test setup red. This is what I got.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/not-red.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;No, thats not red&#34;
	
	
&gt;&lt;em&gt;It&amp;rsquo;s like red, but not quite&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Not exactly what I wanted to see.&lt;/p&gt;
&lt;p&gt;So what went wrong here? Lets have a look at the signal below. The low bit pulse is an order of magnitude longer than the nominal 300ns. It is also far beyond the pulse length that the WS2812 interprets as a high bit. This means that every pulse is interpreted as a high bit, so the LEDs actually receive RGB data that looks like 0xffffff. If you&amp;rsquo;re not that adept at converting hexadecimal to color: that&amp;rsquo;s white. The LEDs worked perfectly fine, but the data was off.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/attiny-slow-data.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Slow data&#34;
	
	
&gt; &lt;BR&gt;&lt;em&gt;Attiny bit too slow&amp;hellip;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The 10x factor between the expected pulse time and the actual signal is odd to say the least. I checked the timing of the microcontroller beforehand (albeit at a lower frequency) and everything seemed in order. The &lt;abbr title=&#34;Central Processing Unit&#34;&gt;CPU&lt;/abbr&gt; prescaler was also configured to 1x (i.e., running on an undivided clock). I was unable to satisfactorily explain the difference but it fits in my hypothesis that the MCU is simply too slow. But that may be confirmation bias.&lt;/p&gt;
&lt;p&gt;I decided to move on and try a different approach to control the LEDs. In hindsight however, I realized that I could have tried compiling the code with speed optimization enabled. The library documentation does not mention the necessity to speed optimize, though.&lt;/p&gt;
&lt;h2 id=&#34;another-problem-to-address&#34;&gt;Another Problem To Address
&lt;/h2&gt;&lt;p&gt;If the microcontroller is too slow, than a faster one should solve the problem. I happened to have just such a controller sitting on my workbench: an STM32G030&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt; that I&amp;rsquo;m using for another project. I also decided to drop the off-the-shelf library all together. The STM32G030 runs at 64MHz, that&amp;rsquo;s an 8x speed increase from the Attiny85.&lt;/p&gt;
&lt;p&gt;To test the concept, I created a test program that generated a signal to represent one green LED. It worked perfectly fine.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/green-light.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Green LED&#34;
	
	
&gt;&lt;em&gt;We have green light!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I went on to write another test program that takes the RGB data as an array and then sets the pulse length for each bit in an &lt;abbr title=&#34;Interrupt Service Routine&#34;&gt;ISR&lt;/abbr&gt;. (The STM controller can generate an interrupt each time the output compare event occurs, i.e., when the pulse transitions from high to low. The interrupt handler can then calculate the length of the next pulse and write it into the output compare register for the next cycle).&lt;/p&gt;
&lt;p&gt;Like the Attiny program, this one also did not work as expected. It didn&amp;rsquo;t give me white LEDs, but a more or less random color pattern. Very decorative, but not quite what I wanted.&lt;/p&gt;
&lt;p&gt;Time to hook up the oscilloscope once more.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/scrambled-data.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Scrambled data&#34;
	
	
&gt;&lt;br&gt;&lt;em&gt;Scrambling (the data) to keep up&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The plot above was supposed to show a pattern of alternating low and high bits (or, short and long pulses). But that&amp;rsquo;s not what the oscilloscope tells us. If you look at the plot closely, you&amp;rsquo;ll notice that a short pulse is followed by a long pulse, as expected. After the first long pulse however, comes another long pulse. What I expect happens here is this: the controller starts determining the pulse length of the next pulse at the falling edge of the signal. This data has to be written to the output compare register before the next rising edge. This means that the controller has a lot less processing time available to work out the next value after a high bit has been sent.&lt;/p&gt;
&lt;p&gt;To confirm this theory, I increased the Timer prescaler. This results in the timer (and thereby the PWM generator) running at a lower frequency while the CPU remains at the same clock speed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/slow-data.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Scrambled data&#34;
	
	
&gt;&lt;br&gt;&lt;em&gt;Good data, in slow-mo&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s that theory confirmed: another speed issue. The time that&amp;rsquo;s available between pulses is insufficient to calculate the pulse length for the next bit.&lt;/p&gt;
&lt;p&gt;Conclusion: the second approach also failed. Although in hindsight, I should have tried speed optimization.&lt;/p&gt;
&lt;h2 id=&#34;just-ram-that-data-into-place&#34;&gt;Just Ram That Data Into Place
&lt;/h2&gt;&lt;p&gt;With my brilliant (overly optimistic) plan falling to pieces before my eyes, I tried yet another approach: &lt;abbr title=&#34;Direct Memory Access&#34;&gt;DMA&lt;/abbr&gt;.&lt;/p&gt;
&lt;p&gt;Processing each data bit in an interrupt handler adds some overhead to the code execution. For each bit, the CPU has to push its current state to the stack, jump to the ISR, handle the interrupt, clear the interrupt flag, restore the previous state from the stack and then continue from where it was. It would be faster to do the processing first and store the pulse lengths in memory. Then the Timer peripheral can fetch the data when it feels up to it.&lt;/p&gt;
&lt;p&gt;Direct Memory Access is just the thing for that. Each time the output compare register needs updating, it reads the new pulse length directly from memory without making a demand on the CPU. No interrupt handling needed.&lt;/p&gt;
&lt;p&gt;I tested this concept with yet another test program: It worked like a charm. I was so impressed that I forgot to take a picture of the result.&lt;/p&gt;
&lt;p&gt;There was just this one ramification (pun intended): this takes a lot of &lt;abbr title=&#34;Random Access Memory&#34;&gt;RAM&lt;/abbr&gt;. One &lt;em&gt;byte&lt;/em&gt; for every &lt;em&gt;bit&lt;/em&gt;. That&amp;rsquo;s 24 bytes for each LED. There will be 80 LEDs in my build, so 1920 bytes. That&amp;rsquo;s a lot of memory. But I had another concern first.&lt;/p&gt;
&lt;h2 id=&#34;where-did-the-pwm-go&#34;&gt;Where Did The PWM Go?
&lt;/h2&gt;&lt;p&gt;The board I tested the DMA trick on was intended to be used for something else. Luckily, I had a spare board from my &lt;a class=&#34;link&#34; href=&#34;https://www.project392.net/projects/party-lights/&#34; &gt;Party Lights project&lt;/a&gt; and also the microcontroller that lives on it. By a happy coincidence, the &lt;abbr title=&#34;Input Output&#34;&gt;IO&lt;/abbr&gt; pin that controls the serial transmitter in that design can also be used as a PWM output. What&amp;rsquo;s more, It has a level translator. That&amp;rsquo;s fortunate, because the WS2812 is a 5V device while the MCU runs on 3.3V.&lt;/p&gt;
&lt;p&gt;The board needed some small modifications, like jumpers and a series termination resistor for the output signal.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/control-board.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Modified control board&#34;
	
	
&gt;&lt;em&gt;The light controller rides again!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I made a few modification to make the code work on the new MCU, an STM32F042&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a href=&#34;#fn:10&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt;. That involved changing the IO configuration, Timer settings and DMA setup. Then I reconnected the LED strip and powered up.&lt;/p&gt;
&lt;p&gt;Nothing happened whatsoever.&lt;/p&gt;
&lt;p&gt;It looked like a classic case of &amp;lsquo;It worked on my machine&amp;rsquo;. But where did it go wrong?&lt;/p&gt;
&lt;p&gt;In search of the culprit I figured I should start with the basics: does it do any PWM at all? It turned out it didn&amp;rsquo;t. But why? The timer appeared to be set up correctly and was running (it triggered an interrupt). But no PWM.&lt;/p&gt;
&lt;p&gt;The reason, it turned out, was in the type of Timer peripheral. STM32 MCUs have different Timer/Counter peripherals and there are some distinctions between those. I tested my program on a &lt;em&gt;General Purpose&lt;/em&gt; timer. When I migrated to the STM32F42 however, the timer associated with my IO pin of choice was an &lt;em&gt;Advanced&lt;/em&gt; timer. And those need some extra configuration.&lt;/p&gt;
&lt;p&gt;Advanced Timers on the STM32 family have extra features that allow them to be used in motor controllers. This involves some safety features like the ability to disable the PWM outputs when a safety signal (or Break Input) is received. This can be used to implement an emergency stop.&lt;/p&gt;
&lt;p&gt;It so happens that the PWM outputs on an Advanced Timer peripheral are disabled by default. To enable them, the Master Output Enable (or MOE) bit has to be set. This is frustratingly hard to find in the reference manual and STMicroelectronics don&amp;rsquo;t feel like emphasizing the importance of this single control bit. I read about it in a blog post at EmbeddedExpert.io&lt;sup id=&#34;fnref:11&#34;&gt;&lt;a href=&#34;#fn:11&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;11&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;With the Master Output Enable bit set, the pulse width modulation was alive again.&lt;/p&gt;
&lt;h2 id=&#34;speed-is-everything&#34;&gt;Speed Is Everything
&lt;/h2&gt;&lt;p&gt;Software engineers always warn against a thing called early optimization. The idea is not to optimize anything until everything works and there is time to spare. And also not to optimize unless you completely understand what you&amp;rsquo;re doing.&lt;/p&gt;
&lt;p&gt;I however, am not a software engineer. And that data buffer just shy of 2kb felt horrendously inefficient. I wanted to fix that. Surely that couldn&amp;rsquo;t be too hard.&lt;/p&gt;
&lt;p&gt;The DMA controller in an STM32 can generate an interrupt twice every transfer: one when the transmission is half way, and another upon completion. This way a circular buffer can be implemented. I choose a 48 byte buffer to store the pulse lengths for two LEDs. While the data for one LED gets transmitted, the data for the next gets calculated. The CPU works while the DMA controller takes care of the transmission. This skips the overhead for the interrupt handler.&lt;/p&gt;
&lt;p&gt;In the next version of the software I implemented this method.&lt;/p&gt;
&lt;p&gt;It didn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;What I got instead was the same garbled frame that I got with my earlier STM32 test. Probably a timing problem, again. Apparently the processing time to generate the pulse lengths for one LED takes more time than transmitting the data. Even without the interrupt handling overhead.&lt;/p&gt;
&lt;p&gt;One PWM pulse takes 1.25us. So, transmitting 24 bits takes 30us. The processing time is hard to estimate, but can be measured externally. I toggled a spare IO pin at the beginning and end of the data processing routine. This way the oscilloscope can show the time in between. It&amp;rsquo;s the green curve in the plot below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/processing-time.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Too slow&#34;
	
	
&gt;&lt;br&gt;&lt;em&gt;Just too late delivery&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s approximately 50us. Or 20us too long.&lt;/p&gt;
&lt;p&gt;And then I remembered speed optimization.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/processing-time-optimized.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Too slow&#34;
	
	
&gt;&lt;br&gt;&lt;em&gt;-O my!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Nailed it at last! The humble -O3 flag brought a stunning 2.5x improvement in processing time. Seriously though, is my programming that bad?&lt;/p&gt;
&lt;h2 id=&#34;customizing-the-colors&#34;&gt;Customizing The Colors
&lt;/h2&gt;&lt;p&gt;With communication now in place, I was able to whip up an application that allows the user to set the colors of the individual LEDs.&lt;/p&gt;
&lt;p&gt;The final version of the hardware got 3 buttons. An OK button to enter edit mode or confirm a setting, and up/down buttons to select a value.&lt;/p&gt;
&lt;p&gt;In edit mode the user can first select a region of consecutive LEDs that they want to edit. After confirming their selection they can select one of 32 preset colors. The drill repeats until all the LEDs are set.&lt;/p&gt;
&lt;p&gt;When not in edit mode, pushing the up/down buttons sets the brightness of the LEDs.&lt;/p&gt;
&lt;p&gt;There was only this one inconvenience: power cycling the controller would revert everything to the default color.&lt;/p&gt;
&lt;h2 id=&#34;not-a-storage-problem-too&#34;&gt;Not a Storage Problem, Too!
&lt;/h2&gt;&lt;p&gt;The Attiny MCU that I initially wanted to use comes with non-volatile data storage in the form of an on-chip &lt;abbr title=&#34;Electrically Erasable Programmable Read-Only Memory&#34;&gt;EEPROM&lt;/abbr&gt;. EEPROM is of course a bit old-fashioned and flashy microcontrollers like the STM32F042 don&amp;rsquo;t have that anymore. So where to store the beautiful color sequence that the user so elaborately entered with just those three buttons?&lt;/p&gt;
&lt;p&gt;The good news is that we don&amp;rsquo;t need a dedicated storage device. The Flash memory that holds the program is self-writable. This means that the program can make modifications to it while it runs. This is often used to allow the firmware to install updates. The program can also use any excess storage space to store data. This is of course provided that the memory location in question does not hold the program code.&lt;/p&gt;
&lt;p&gt;As I had at this point already overrun my targeted completion date, I didn&amp;rsquo;t feel like reinventing the wheel for data storage. I had after all already reinvented the wheel for LED control. Luckily the developers of the LibopenCM3&lt;sup id=&#34;fnref:12&#34;&gt;&lt;a href=&#34;#fn:12&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;12&lt;/a&gt;&lt;/sup&gt; &lt;abbr title=&#34;Hardware Abstraction Layer&#34;&gt;HAL&lt;/abbr&gt; that I use for STM32 microcontrollers offered a helping hand. They provide numerous code examples for their library. Among which is a flash storage example&lt;sup id=&#34;fnref:13&#34;&gt;&lt;a href=&#34;#fn:13&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;13&lt;/a&gt;&lt;/sup&gt; that was ready to use.&lt;/p&gt;
&lt;p&gt;I implemented the flash storage for the color pattern and brightness, reloaded the application and changed some colors. Then I power cycled the controller and I got this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/stoic-pixel.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Stoic pixel&#34;
	
	
&gt;&lt;em&gt;One stoically unchanged pixel&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The very last LED of the sequence kept returning to its default color (which is red, in case you missed it). But why?&lt;/p&gt;
&lt;p&gt;I checked for bugs in my application and found none. Then I checked for bugs in the Flash store and recall functions that I got from LibOpenCM3. Found no bugs there either. But it did provide an important hint as to what I was doing wrong: it works with 32 bit words. This is kind of obvious because the STM32 is a 32 bit microcontroller so the registers are 32 bits wide. The data that I tried to store however, were a bunch of 8 bit integers. In order to store those, they have to align in 32 bit blocks.&lt;/p&gt;
&lt;p&gt;What I tried to do was store eighty color values (8 bits each; just the hue for each pixel), plus one 8 bit integer for the brightness. That&amp;rsquo;s eighty-one bytes, or twenty 32 bit words plus that remaining byte. The storage function fetches four 8 bit chunks each and writes them to Flash. The remaining one byte, however, gets ignored. Since that final byte represents the color of the last LED (the brightness value is in the first element of the array), that data was not being saved.&lt;/p&gt;
&lt;p&gt;The solution was to simply increase the length of the data array so that it wholly consists of 32 bit blocks (i.e., a number of bytes that&amp;rsquo;s divisible by 4).&lt;/p&gt;
&lt;h2 id=&#34;all-aboard&#34;&gt;All Aboard!
&lt;/h2&gt;&lt;p&gt;Right. On to the easy part!&lt;/p&gt;
&lt;p&gt;The 3D-printed numbers needed to be mounted to something rigid, and that something also had to hide all the wiring. I choose two sheets of 9mm plywood, glued together to form - you guessed it - an 18mm board. To hide the wires to the LEDs, I routed a groove into one of the sheets. Masking tape kept the wiring in position during construction. After that I hoped the universe would not present me with any broken wires once the glue dried.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/groovy.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Groovy!&#34;
	
	
&gt;&lt;em&gt;Groovy!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After applying a layer of varnish, the numbers were ready to be mounted.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/numbers-mounted.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Duct Tape!&#34;
	
	
&gt;&lt;em&gt;No quick build without duct tape&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The addressable LEDs were mounted with doubled sided adhesive tape. I also added some aluminium tape opposite the LEDs to act as a reflector. The LED strips also got a small (1uF) capacitor to stabilize their supply voltage.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/reflector.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Reflection·&#34;
	
	
&gt;&lt;em&gt;Upon reflection, the aluminium does improve the light quality&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The last remaining step of the assembly was an enclosure for the controller. It&amp;rsquo;s a simple box that&amp;rsquo;s screwed onto the back of the plywood board. A spacer holds the circuit board in place after assembly. The push buttons just stick out of the front on the box. Power is provided by an off-the-shelf USB-C receptacle that slides into a slot at the side of the box.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/box-assembled.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Controller Box&#34;
	
	
&gt;&lt;em&gt;No quick build without hot glue&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To prevent the whole assembly from only being used face down, I printed a stand for the panel as well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://www.project392.net/house-number-project/on-stand.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Stand&#34;
	
	
&gt;&lt;em&gt;The project as it stands&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And with that, I had my present ready at last!&lt;/p&gt;
&lt;h2 id=&#34;wrap-it-up&#34;&gt;Wrap It Up
&lt;/h2&gt;&lt;p&gt;Time to wrap up. Literally.&lt;/p&gt;
&lt;p&gt;I have to say it was quite the journey to get this &lt;em&gt;quick build&lt;/em&gt; built. In hindsight the goal of making the whole thing in one weekend sounds ludicrously over optimistic. But then again, I&amp;rsquo;m a nightmare at planning so, what should I have expected?&lt;/p&gt;
&lt;p&gt;How long did the project end up taking? About 6 six days in total. That&amp;rsquo;s not including any documentation or this blog post. It could definitely have been done in two weekends, had I not run into so many issues.&lt;/p&gt;
&lt;p&gt;On the other hand, the build not being all smooth sailing made it all the more educative. I learned how control the WS2812 LEDs and discovered the DMA controller on the STM32 microcontroller. I also learned that those software developers who are reluctant to optimize prematurely may have a point after all&amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;repository&#34;&gt;Repository
&lt;/h2&gt;&lt;p&gt;The firmware for this project is available from my Codeberg &lt;a class=&#34;link&#34; href=&#34;https://codeberg.org/project392/ws2812-controller&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;repository&lt;/a&gt; as always. I also included a reference design for the hardware. The printed numbers and the controller enclosure are not included as they are too specific to be of any relevance for others.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.freecad.org&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;FreeCAD&lt;/a&gt; Parametric &lt;abbr title=&#34;Computer Aided Design&#34;&gt;CAD&lt;/abbr&gt; tool.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://codeblocks.org&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Code::Blocks&lt;/a&gt; IDE&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://duckduckgo.com?q=ws2812&amp;#43;datasheet&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;WS2812 Datasheet&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://duckduckgo.com?q=ws2811&amp;#43;datasheet&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;WS2811 Datasheet&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.youtube.com/watch?v=yUVWQxITn1o&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;FreeCAD : Quick and Easy Text / Font Effects Emboss, Combine and Outline&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.youtube.com/@MangoJellySolutions&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Mango Jelly Solutions for FreeCAD&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/cpldcpu/light_ws2812&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Light WS2812&lt;/a&gt; library&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://duckduckgo.com?q=attiny85&amp;#43;datasheet&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Attiny85 Datasheet&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://duckduckgo.com?q=STM32G030&amp;#43;datasheet&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;STM32G030 Datasheet&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:10&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://duckduckgo.com?q=STM32F042&amp;#43;datasheet&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;STM32F042 Datasheet&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:10&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:11&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.embeddedexpert.io/?p=2055&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;STM32 Advanced Timers Part 1&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:11&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:12&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://libopencm3.org/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;LibOpenCM3&lt;/a&gt; Open source HAL&amp;#160;&lt;a href=&#34;#fnref:12&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:13&#34;&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/libopencm3/libopencm3-examples/blob/master/examples/stm32/f1/stm32-h107/flash_rw_example/flash_rw_example.c&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;LibOpenCm3 Flash example&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:13&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
        </item>
        
    </channel>
</rss>
