Electric Endeavor

Posted 2019-10-25 00:42:34 UTC

Updated 2019-11-01 20:17:50 UTC

Series: rgb led strip driver

Part 0

A story about my first real microcontroller project


Backstory

In 2016, I was in a robotics club in high school. This got me more interested in the physical effects that computer programming can have: instead of merely changing the colors of pixels on a screen, it was now possible for me to move chunks of metal around and sense what's going on in the outside world. However, we had to use a weird version of C (and later Java) to program motors and such at a rather high level. I wanted to know more about what's going on between our Lego NXT "robot brain" and our high-level code that tells it what to do, so I went online and bought myself something a little more barebones: an LPC810M021FN8 (starter kit). The LPC810 is a tiny computer with a grand total of six usable pins (two of the eight need to be at ground and voltage so they don't really count). It has approximately the area of a fingernail and it looks like this:

The LPC810

My original plan was to buy another one after I had figured out how to use it and then build a remote control car and the remote control. (The source code from this project can be found here.) I'm very fortunate to have had some extremely good mentors on the robotics team who helped me to learn about electronics, reading datasheets, and bare-metal programming (among many other subjects). It's highly unlikely that I'd know as much now if I didn't have their support back then. Anyway, I got decently far with this project before I realized all the work that this would actually entail: 3D modelling, making an IR protocol, multiplexing analog inputs, and so on. As a result, I gave up had to set this project aside because I had other things I needed to focus on, like playing video games school.

About a year after that, a friend of mine gave me an RGB LED strip (similar to this) which reignited my interest in electronics. I got to work building a circuit that would let me control the colors digitally with some buttons:

Work

Since this project was significantly less complicated than my previous one, I had a prototype working within a few hours (flashing lights warning):

Next, I needed to add the microcontroller into the mix. This is a little more complicated than simply wiring the LEDs to some of the pins and then wiring the buttons to the other pins: the microcontroller is rated for an absolute maximum of 4.6V 100mA, but the LED strip needs 12V 3A. If I were to try giving the microcontroller enough power for its GPIO pins to drive the LEDs, this is roughly what would happen:

This problem can be solved by using transistors with a low-power gate for the microcontroller to control and a high enough source and drain throughput to feed the LEDs. I didn't have any transistors that met this criteria, so I did some research and consulted the robotics mentors and wound up picking these. I ordered one to drive each channel (one for red, one for blue, and one for green) and then promptly got distracted by something else.


Several Years Later

In August 2019, it began slowly dawning on me that I was actually about to go back to school. As part of this realization, I decided I wanted to be able to make friends and impress people with my computer science skills. Unfortunately, most of the programs I've written are only cool on the inside (e.g. the source code is well written) and don't really look like much on the outside (e.g. the user interface is just text). As a result, normal people I've tried to demo my work for have told me that my software is useless since they can't install it on their phone. (This is roughly equivalent to telling someone that their artwork they've spent days working on is genuinely garbage without even bothering to look at it.) The solution I came up with to avoid these problems was to completely remove the idea of a personal computer from the equation: I should finish my RGB LED strip driver project!

In the long years between 2016 and August, I was introduced to and fell in love with a new programming language: Rust. I could go on and on about why I love this language, but the only things that are really relevant here are that I like it more than C and it can compile to the architecture my microcontroller is based on. After some STFW, I had figured out how to get Rust to emit thumbv6m-none-eabi binaries, but I had not yet figured out how to produce working binaries. This problem is decidedly not STFW-able, so I took to asking some friends:

chat part one

chat part two

chat part three

chat part four

In summary, I got confused by the type signatures in the core::ptr::{read_volatile, write_volatile} functions. I didn't know how to make a *mut T other than by taking a mutable reference of a local variable (what Benjamin said in the last image) by doing &mut foo. It turns out that I needed to cast the immediate value as a pointer (as opposed to getting a pointer to it), and the way to do this is to write foo as *mut T (as uelen pointed out). As you can see, that had solved my problems and I now had working code! It looked like this:

#[no_mangle]
pub unsafe extern "C" fn reset() -> {
    write_volatile(0x4000_C1C0 as *mut usize, 0xFFFF_FFFF);

    let state = read_volatile(0xA000_2000 as *mut usize);
    write_volatile(0xA000_2000 as *mut usize, state | (1 << 2));

    loop {
        let mut x = 500_000;
        while x > 0 {
            x -= 1;
        }

        let state = read_volatile(0xA000_2300 as *mut usize);
        write_volatile(0xA000_2300 as *mut usize, state | (1 << 2));
    }
}

It might seem complicated (there's a lot of line noise), but all this actually does is turn an LED on and off over and over. This microcontroller does have actual timers built into it, but I deliberately chose not to use them right now. Reason being, this test code needed to be very simple so that any problems that came up could be isolated more easily. Since the code to set up the timers was too complicated to include here, I just had the CPU count down from 500,000 before toggling the LED. Although computers are fast, they can't do instantaneous computations, so this countdown manages to function as a delay that's long enough to actually see.


The Hardware

One of the mistakes I made during my RC car project was not trying to find out how to version control the circuitry. This time around, however, I knew about a program for designing circuits called KiCad. I learned of this program from Benjamin while I was going to OSU as part of a side project to build a keyboard. (This project was also suspended, but recently there's been some talk of starting to work on it again.) Although I knew of the software, I did not know how to use it. I managed to find a very good tutorial that gave me enough knowledge to find out what other sorts of questions I needed to ask my search engine. After watching several tutorials, I was able to start designing circuits. For example, here's the matching schematic for the above code and breadboard circuit:

blinky schematic

There are a lot more wires and components in the breadboard version because I needed two extra wires (white and green) to write new code to the microcontroller, and also some circuitry for limiting power from the USB cable (top right area). USB delivers 5V, but the microcontroller only needs 3.3V, so I used this voltage regulator (and a 0.1uF and 100uF capacitor because I had them and they were close to what the datasheet recommended) to accomplish that.

At this point, the very next thing I should have done was git commit. However, I didn't want to commit this code and circuit since it's not at all representative of what the final product is going to look like. I also didn't want to the internet to be able to see the stupid mistakes I made, if any. Not commiting was obviously a bad idea: it would have made it much easier to revert code (and the schematic) to a functioning state if I ever changed things too much and got lost, and it also would have made writing this blog post much easier since I'd be able to reference code at specific points in time.

Anyway, after this mild success of using KiCad, I decided to begin modelling the entire circuit board. Before doing that, however, I had to consider what I wanted the final product to actually do. Since I don't have a lot of pins to work with on this microcontroller, I settled for the following functionality:

The LPC810 has the following relevant features:

Some quick maths suggests that I had one pin left over. I decided not to let this pin go to waste, so I added the SWITCH MODE button as seen in the schematic below. The purpose of this extra button is still to be determined, but I figure it could do something like a breathing effect or simply turn the lights on and off while preserving the color state. (As far as I'm aware, the LPC810 doesn't have any on-board EEPROM, so unplugging my device would make it forget the color it was previously set at.) Here's the schematic I came up with:

schematic

One of the things I learned about while laying out the footprints for my circuit was that the trace widths on a circuit board are significant. My basic understanding is that when you want to push lots of amps through a connection, you need a thicker/wider trace. Some STFW suggested that RGB LED strips can draw up to around 1.2A per meter. I guesstimated that my strip was around 4.5 meters long, which gave me a total of 5.4A. Plugging that value into KiCad's trace width calculator gave me annoyingly wide traces, so I decided that 5.4A can't be right.

To resolve this and find out the true current draw once-and-for-all, I needed a multimeter. I spent literally days researching mulitimeters before coming to the conclusion that cheap multimeters are bad and good multimeters cost over $200. I don't like buying things that are bad, but I also don't like spending that much money at once. To solve this problem, I asked around to see if anyone I knew had a multimeter (good or bad) and was willing to let me use it. Eventually, I found someone that did, met up with them (at a talk about making generative art in Rust at the Mozilla office in Portland), and measured a less ridiculous ~3.75A. When I got home, I finished drawing the traces and came up with this:

footprints

KiCad also has a library of 3D models for most of the components (which is super cool), so I was also able to view it like this:

3d model


Temporal Quantification

The next thing I needed to do was get the timers working. I was thinking about different ways I could set up the timer to control the duty cycle of all three channels. There were a number of ways I could have set this up, but the design I went for has one timer that resets all channels to low at the beginning of a cycle, and one timer for each channel to set it high depending on how bright it's supposed to be. By being clever with tracking state, I think it should be possible to do all of this on a single timer instead of four. However, I didn't really want to think too hard about this one particular problem, so I just went the four-timer way.

Or at least, I tried to.

After scouring the aforementioned bigass user manual and the internet in general for days, I couldn't figure out why the timer's interrupt function was never getting called. Coincidentally, I had been planning a trip to Corvallis to visit some friends from OSU, and one of those friends was Benjamin. Deciding I couldn't figure it out on my own, I set the project aside for a few days until I went on my vacation.

On the first day, Benjamin started getting familiar with how I had laid out my code. On the second day, we got distracted setting up an old server from at least 2005. On day three, we figured out that the timer was actually getting started and counting down properly, but we still couldn't figure out why the interrupt wasn't getting called. We tried replacing every entry in the vector table (except for reset) with the timer interrupt in case I put it on the wrong line. We tried looking for other people's code online. We tried looking at the disassembly. We tried doing it in my old C code, which worked, and although that didn't solve our Rust problems, it did tell us that the timer was functional. Then, we tried comparing the disassembly of the non-working Rust code with the working C code.

While Benjamin was scrolling through both lists of instructions, I noticed that the address responsible for enabling interrupts differed between the Rust and C disassembly. I pointed this out, but also suggested that it might just be due to LLVM deciding to use a shifted version of the address and then shift it back when it needs to load it into a register. Looking at the instructions that load the value, Benjamin decides that this is not the case. Getting a sudden sense of impending embarrasment, I open the Rust source code where I declare the address for peripherals, scroll down to the timer interrupt register, and... yep. I had simply written the wrong address for the interrupt controller. In that moment, a week of debugging came to a close with the discovery of a typo. We both immediately burst into laughter and shared an "are you fucking kidding me!?"

We then got to work getting the microcontroller to measure time in units more reasonable than clock cycles: microseconds. Thanks to std::time::Duration, we got implementations for seconds and milliseconds for free. Measuring a blinking LED with a stopwatch (read: stopwatch app on my smartphone), we confirmed that the microcontroller was now able to understand units that humans were familiar with. The code for this conversion is visible here.


Bad News

One of the things that I discovered while searching for other people's timer code was this hackaday article. It turns out that, also in the long years between August 2019 and 2016, the LPC810 had been discontinued. If I were a real engineer (or less confident), I probably would have trashed the current project immediately and restarted with a chip that didn't just get killed off. However, I'm just a hobbyist, and I didn't plan on mass producing this device, nor did I plan on breaking my single microcontroller.

I should have planned on breaking my microcontroller. That's not foreshadowing or anything, it just would have been a smart thing to do...


Not Everything Is Binary

Now that I could keep time, I was able to PWM an LED to have it appear to be a certain brightness. Unfortunately, the only way I could change the brightness at this stage was by reflashing new code with a hardcoded duty cycle percentage. In order to make the brightness modifiable at runtime, I intended to use a potentiometer in combination with the LPC810's internal voltage ladder. After the timer typo debacle, I decided to just copy my old C code nearly verbatim just to get it to work. Somehow, it actually did. Well, mostly. It seemed to be able to distinguish different voltage levels from the potentiometer, but the range of the dial mapped very weirdly to the range of brightness the LED was displaying.

After yet more tinkering and STFW, Benjamin and I determined that I had in fact not been using a potentiometer, but a rheostat. Well, we were using a potentiometer, but it was functioning as a rheostat due to how we had it wired. We had one pin tied to 3.3V and the slider connected to one of the LPC810's analog inputs. Instead, we needed to also wire the third pin to ground. The difference is that a rheostat only varies resistance, and a potentiometer acts as a voltage divider. This is important to know because the analog comparator only compares voltages and not resistance. Grounding the third pin solved our problem, now the LED responded to the dial as expected.

Unfortunately, my vacation came to a close at about the same time that we got this to work.


Hype-Driven Development

The day after I arrived home, I couldn't contain my excitement to have my very own custom circuit boards. So, I spent several hours designing a logo to stick on the board and making sure all the traces were present. Then, I went to buy boards from JLCPCB since I heard that they were inexpensive. I noticed that their boards have lead in them and are very expensive if you opt to not have lead. On top of this, their lead time was about two weeks. School was about to start, and I wanted my hardware now! I took a look at Oshpark, and noticed that although their prices were higher, the boards didn't contain lead, the soldermask was purple by default (cool), and their lead time is less than a week! At this point it was pretty late in the day, so I placed my order and went to sleep.

The next day, I started work on button inputs. Following my schematic from above, I connected a button to a pin I decided would be an input pin, and then connected the button voltage. This way, when I pressed the button, the pin would be pulled high and the microcontroller will read 1, and when I let go of the button, an internal pull-down resistor will make the microcontroller read 0. Except, that didn't actually work. It turns out that I needed the button to go to ground and have the internal resistor set to pull-up. After making this change, it worked!

Unfortunately, I had already ordered PCBs which has the buttons incorrectly going to voltage instead. It turns out, Oshpark will let you cancel your order if it hasn't been assigned to a panel yet! Checking my email, however, informed me that my boards had been assigned to a panel already. Some people on Reddit suggested that one might still have a chance to fix miswired boards post-panel-assignment, but I was too embarrased to attempt emailing them. In retrospect, I should have. There might have been a chance that it was still been saveable, but if there wasn't, at least I'd have tried.

While I was in the headspace of testing things, I decided to try running all three channels at the same time (with hardcoded duty cycles because I didn't have buttons fully implemented yet). I had success with two channels, but one of them wasn't working as expected. I looked at the code to make sure there wasn't anything out of place there, and it looked okay. The next step is to blame the hardware: I grabbed the misbehaving MOSFET and noticed that it was pretty hot to the touch. I pulled it out of the breadboard and replaced it with one of the other two that were working as expected, and suddenly that channel began working. Although my code being correct is a good thing to happen, this means that one of my three MOSFETs is indeed dead. This is too bad because the shipping cost far outweighs the part cost, making it not really worth it to buy just one replacement. Since hindsight is 20/20, what I should have done was buy more parts than I needed initially in the event that this happens.

While explaining the mistake in the board I had made to a friend, I sent them a picture of my schematic. That prompted the following question and realization:

mcu miswiring conversation

So, not only had I miswired both buttons on the PCBs, but I had also switched the power and ground pins of the microcontroller. I fixed those connections in the schematic and then put the project down for a few days while I waited for my PCBs to arrive.


Arrival

About a week after school had started, my PCBs finally arrived. I was so excited, it felt like Christmas. As soon as I got the delivery confirmation email, I sprinted to the mailbox and sprinted back with my new hardware. I tore open the packaging and immediately took pictures to brag to share the excitment with my internet friends that I made custom PCBs!

front and back

hardware fitted

Before actually soldering anything, I needed to disconnect the traces that Josh (The Spanish Inquisition) and I had previously determined were incorrect. The process of disconnecting existing copper traces on a PCB is a very precise and delicate process of taking something relatively sharp and grinding it repeatedly over the connection you want to break until it does. My instrument of choice for this task was a small flathead screwdriver. After going at it for a while, I decided that I had probably cut it, but I wanted to confirm with a multimeter. But, again, I don't have a multimeter.


The Makerspace

Since I was now attending college (again), I decided to see if maybe they had any electronics resources I could borrow. I found out that there are a few makerspaces scattered across the campuses, and that one of them is at the campus that I go to! Their hours are a little unfortunate for me though, I have a class during most of their time on Monday and Wednesday, but luckily they're open for a lot of Thursday and I don't have any classes then. However, I wanted to keep the rate of progression on this project as high as possible, so I wandered in to look around before my next class started.

I was intercepted at the door by some people sitting at a table working on some Arduino stuff. They said hello and asked me if it was my first time and such, and then they gave me a tour the facility. It turns out, they have a lot more than just multimeters: dozens of 3D printers, an oscilloscope or two, a waveform generator, a vinyl printing room, a small machine shop for metal- and wood-working, an unfinished custom CNC machine, and a soldering station. We talked about projects for a little bit after they showed me around, and I thanked them and said I'd be back after class to do some testing.

After my class, I went back to the makerspace and pulled out my PCB I had modifiied. Using one of their multimeters (set to resistance, they didn't have a continuity mode), I confirmed that the necessary cuts had been made. I had also brought some wire with me, so I borrowed their soldering iron and some solder to replace reconnect the broken traces to the correct locations. The final result looks like this:

extremely sketchy

Having done about 15 minutes of extra prodding with the mulitimeter to make sure everything was how it needed to be, I decided that my hack had succeeded and it was time to go home and try it out. For whatever reason, I didn't bring the strip nor the power supply with me, so I couldn't test it while I was there. When I got home, I set everything up to test it, and... it worked! Well, the circuit did, but not all of the code. Since I hadn't yet implemented the glitch filters for the buttons, I was only able to test changing the brightness of a single color.

I had an idea to work around the button glitches though, which is to toggle the color based on whether the button is being held down rather than being pushed once. Trying this, I couldn't seem to get the other color to change. I thought maybe I was trying to use the channel that didn't have its MOSFET installed, but trying the other one didn't work either. It was now time to blame the hardware, so I waited until the makerspace reopened the next day to do more probing with the multimeter.

Among other things, I noticed that the voltage regulator was heating up quite a bit, and it had never done that before. I probed around to make sure there weren't any connections where there shouldn't be, and vice versa. I even went so far as to lift the entire incorrect traces off of the board, so that instead of just a cut in the wire, there was simply no wire. I began probing things again out of sheer confusion, when suddenly both usable channels turned on to full brightness. Spinning the potentiometer had no effect, and neither did pressing any buttons.

Fearing the worst, I unplugged everything and reflashed the simple blinky code to the microcontroller to test it with a single LED in a breadboard. The code flashed successfully, but it didn't seem to run at all. Out of desperation, I tried reflashing and re-running the code at least two more times before declaring my LPC810 dead. That was the only one I had left, and, as established, these chips don't get manufactured anymore. None of the normal sellers had them in stock anymore, either, I checked.

"Well, I guess that's the end of that project," I thought to myself as I left for home.


Moral Of The Story

Axioms:

  1. Experience is necessary for producing successful designs.
  2. Learning from mistakes is one of the most effective ways to learn.
  3. In electronics, making mistakes often directly leads to things ceasing to function correctly.

From these axioms, we can infer that one's experience is positively correlated with the amount of equipment they have converted into scrap. Furthermore, even if this project never reaches a functional state, it will still have been a success. This is because I will have learned from it, and there will be future projects to which I can apply my newly-learned lessons. In fact, I immediately started a discussion about restarting the keyboard project when I got home that day. You might have also noticed that this post is actually the first part in a series...


The Reference

Lessons organized by the section in which I learned them

Backstory:

Temporal Quantification:

Bad News:

Hype-Driven Development:

The Makerspace: