MCU Driver Development that Doesn't Suck

All I’m going to say here is to make your bed before you get in and close the barn before the horse puts in his two weeks notice.

Writing MCU device drivers can suck. It can suck for many reasons. As someone who spent best part of a week tracking down an installation and manufacturing issue that actually turned out to be a clocking issue in the UART driver I feel qualified to write this to remind my future self and possibly you too (not that I’d accuse you of doing anything quite so brain dead).

Here’s the method I use after the initial version hasn’t worked and I’ve wasted a week in the lab. This is the method I need to force myself to use from the beginning even when “the sample code is working”.

Step One: Set up (or Denial)

A good setup is key. Most of this article is stuff I want to remember to do regardless of the circumstance and this is the biggest thing for me. Usually I’m wanting to get something working quickly. The actual goal however, is to get something working properly. I have in the past burned time I didn’t have breaking parts I needed because it was going to be quicker without the setup. That sucks, don’t do that.

  • Build a jig that has acceptable signal fidelity and strain relieved wires.
  • Take a picture of the setup, this will let you get back to where you were quickly if you change something.
  • Label both ends of chords and wires if you are using ones that look alike.
  • Check that your power supply is what you think it is.
  • Set up an oscilloscope early in the process. It’s easy to blame your software when it’s really the hardware. If you’re like me you blame the part you didn’t build, it helps check that too.
  • If you have multiple boards or bulky cables invest the time to connect everything to a substrate. A piece of wood is perfect, 1/4" craft plywood is my go to.
  • If you’re lucky enough to have a board designed for debug then you’re ready to start, otherwise you’ll need to add test points and headers where there aren’t any.Super glue, 0.1" headers, and wire wrap wire makes connecting a logic analyzer or scope easier.

Step Two: Goals and Diagrams (Anger)

State the goals of the driver, don’t add things you don’t need. If you’re not building a general purpose kernel driver then it doesn’t have to be one. Have it do what you need and do it well. I get much more out of an elegant design than an overengineered one regardless of initial intent.

Gather the timing diagrams both of the MCU/FPGA/555 you’re using and the device you’re talking to. Get the datasheets ready too; if you’re doing it right most of writing drivers is reading datasheets. If you’re doing it wrong then most of the work is yelling into the void and then reading the datasheet.

Get the errata sheet for all the chips you’re using or are likely to use. Know what is creatively broken about your devices before debugging the wrong thing for hours.

Plan out how to duplicate the timing diagrams. When doing mechanical design or EDA footprints I find it’s helpful to duplicate the drawing in the documentation. If all the values are identical then you’re done (provided the drawing was halfway decent to begin with). It’s the same with timing diagrams. If you can duplicate the timing diagrams then your peripheral control is most of the way done.

Step Three: Demos & Recreating Timing Diagrams (Bargaining)

Build up the driver step by step. Write a new program for each of the different driver sections (this might be a main.c, main.py, library function, or remote execution exploit). Connect up a logic analyzer and make a diagram for each of the sections you care about. At first leave off the controlled hardware. First make sure the controller is correct by checking the output with a scope and logic analyzer. Using the hardware you’re testing as your logic analyzer, waiting it to respond leads to pain, lots of pain. Check with the scope and logic analyzer, make sure you’re doing what you think you’re doing first.

Store the diagrams and add a README file with what you’ve learned. There’s always oddities and undocumented (or completely incorrect) behaviour you learn about. Refer to the data sheet and errata often.

Once you’re generating the correct signals you can limit the things you need to check when the inevitable happens. Keep the problem o(n) not o(n^n).

If there is more to be done than twiddling hardware bits use test driven design (TDD) to build them. Use the tests to confirm the logic is correct before complicating things with hardware. If you find issues that could be software try writing a test for it. Decoupling the buggy software from the buggy hardware makes the process more repeatable. You might be faster one day cutting corners but some days you won’t. When you come back to the project you’ll get going much faster.

Step Four: Integration & Debug (or Depression)

Once the controller is confirmed to be behaving hook up the controlled device. Keep the logic analyzer installed and check the expected behaviour is occurring. Check the logic analyzer and compare to the expected results from the timing diagrams. Recreate the controlled devices diagrams.

If the device isn’t coming up check the logic thresholds and noise with the scope. Check the setup and hold times, min/max clock rates, power supplies, and enable pins.

It’s common to have issues getting these devices to behave. When debugging do it systematically: never skip a known issue without fixing it. Those are often breadcrumbs at least.

It is often faster to make different programs focusing on different behaviour but not always. Writing control software may make the debug easier by allowing you to script behaviour in a more granular manner. Don’t be worried about switching hardware to come at the issue from a new direction. Using a device like a bus pirate https://dangerousprototypes.com/docs/Bus_Pirate can sometimes decouple issues.

Step Five: Acceptance

  • Combine the code you’ve written into a clean interface
  • Iterate with testing to get the structure you need
  • Add notes and pictures to your README, put in what you wish you have remembered, known, or found earlier.

Conclusion

I’d be dramatic and say “better a pint of sweat than gallon of blood” but this is only MCU drivers. Doing the prep correctly the first time makes this all suck less. You also get to build something instead of banging on code which can be therapeutic.