Construction (Sort Of)

In Part 1 we learned about the requirements, and in Part 2 we talked about the design.  Now the rubber meets the road.  I did the research and the design.  Now I needed to manufacture some printed circuit boards and put the design to the test.

Laying out the PCB was a little cumbersome since all the chips wouldn’t fit neatly on a normal sized Arduino shield, so I had to make the board a little larger than “normal”.  Not a big deal, but a little less ideal than the perfectionist in me wants.

After the boards were manufactured, I order some solder stencils and set out to solder everything.  Since the board is almost exclusively surface mount, this would be my first foray into assembling a surface mount device (SMD) PCB on my own.  I’ve done it professionally, but in that case I paid other professionals to do it for me.  I’m way too cheap to pay someone to do it, so I did it myself.  I’ll probably write about that experience separately – it worked out very well and I am super pleased with the results.

Driver Shield and LED Strip PCB

Driver Shield and LED Strip PCB

Driver Shield

Driver Shield

Once the boards were done I had to write all the software for the Arduino.  Writing the initial code was fairly easy, but getting it work properly was a huge pain.  The design / build process was orderly – test the driver first, then test the columns, then put the pieces together.  All went well.  Once I could turn LEDs on and off using the full complement of capabilities, I turned my focus to the multiplexing an driving a 48×16 array.  This is it got really complicated and really frustrating.

I spent many hours futzing with Arduino timers, optimizing the software, and really worrying about how many CPU cycles each routine was using.  In the end, I had to collapse most of the time critical code into a single routine and give up any hope of controlling the full array using the full depth of intensity settings

Embedded Software Design 501

Yes, I said 501.  This is not 101 territory.

This design contains a matrix that tis 48×16, which is 768 “nodes” individually addressable.  Each node has a 12-bit pixel depth, or intensity level.  Microcontrollers don’t understand 12 bit, so we get to round up to 16-bits, or 2 bytes. Easy enough – 48x16x2 = 1,536 bytes to fully control the entire matrix.  Oh, good software engineering says we double buffer the bit maps to avoid any “hiccups” with display intensity, so we need 3,072 bytes.

Now, I spent nearly a decade designing and implementing embedded solutions professionally, so I was very conscience of how much memory my program used.  However, I did not set down in advance and calculate it out like I did above.  Had I done that, I would have predicted, and avoided, many, many hours of exasperation.  You see, the Arduino microprocessor only has 2K of SRAM, and approximately 500 bytes is used by the bootloader, leaving only ~1500 byte free for the user.  So, I had approximately 50% of the RAM available to me that I needed.

Rather than the compiler flagging me and saying “hey, idiot, you have allocated more than than is available”, the code uploaded fine and operated fine.  For a while.  Then it started behaving oddly.  It didn’t stop working – it just stopped working properly.  More importantly, code that used to work, stopped working properly.

**THE MOST FRUSTRATING PART OF EMBEDDED PROGRAMMING EVER**

So after many hours of adding print statements, changing code, commenting things in and out, I finally narrowed the issue.  When I commented some code out, it worked fine; when I commented it in, it failed.  First and most classic sign of a memory problem.  When and finally sat down and did the math.  Duh! I am an **idiot**.

(For the record, adding print statements actually made the problem worse by using up even more RAM.  Great, eh?)

Compromise time.

I needed the double buffering, so my only choice was to narrow the intensity density.  But cutting it in half still pushed the memory requirements to ragged edge.  Remember when I said the boot loader uses “approximately” 500 bytes.  Yeah, well, that approximately is super important when literally every byte matters.  After a lot of debugging, it turns out I was using about 20 more bytes than were available, **if** I called more than 2 subroutines.  And which subroutine I called changed how much memory was required.

This my friends is called the “stack size” and it is really important.  When you push something on to the stack, it uses memory.  When that memory is used by something else, like say a variable in your interrupt service routine, it tends to, ummm, f*ck sh*t up.  Bad.

Ok, so that’s the lesson learned about advanced embedded system programming.

  1. Know exactly how much memory you need
  2. Know exactly how much memory you have
  3. Only use how much memory you have

Seems basic.  But without an embedded debugger and a super expensive logic analyzer, it can take you days to figure out.  Days and days and day.

In the end, I had to combine a number of functions breaking all normal OO programming “rules”, use direct bit manipulation instead of the stock Arduino functions, and count the number of CPU cycles for certain operations before the code worked properly.

Once I got the code to work properly, I had to assemble the entire thing on the Power Tower itself, which led to much fun and amusement.  All of which will be documented in the next article, Part 4.