This may be a little dry but let's go through how/why I designed the software for the clock in the way I did. Keep in mind I'm definitely not a pro software developer so the way I ended up doing things are very likely not optimized or conventional. With that disclaimer out of the way let's get into it.
For starters I needed a way to autonomously scan the 12x10 led matrix so my main portion of code could talk with the RTC chip, generate animations, etc. To do this I've opted to use an interrupt that triggers at something like 4kHz. Each time the interrupt executes it draws the current column of LEDs (stored as a global array) and then increments the column counter so the next time it can draw the next one until it reaches the last column and starts all over again. So you can see how with this method I can update the display image by writing to the display array (acting as a crude display buffer) while the interrupt worries about how to draw it. Additionally by choosing to skip cycles or not it can even make the display look brighter or dimmer!
One small complication though, in order to make the board easier to wire I ended up assigning all the rows and columns to seemingly random I/O (and not neatly/organized in pin order like I normally would). This means that I cant just do a simple quick port write and update all row/column values correctly. To get around this I created a pin map and associated function that takes the desired row and column states and toggles all the pins correctly despite them being out of order. This function basically ended up being a big switch statement.
Now how is the display actually multiplexed/scanned? Well to start an entire column is lit at once. To do this the vertical strip of LEDs in the column have their anodes driven in the pattern we want them to light up. To actually select the correct column we then set the pin attached to all of the cathodes of the LEDs in that column as an output and pull it low, while at the same time setting every other column pin to an input (high impedance so no current can flow). Simple right? There are tons of examples and schematics that explain it much better than me if you google something like "how a led matrix works".
So from the perspective of the programmer, all you have to do to write to the display is set the pixels stored in a display buffer array and they seemingly magically light up the corresponding LED! Cool, right!
So next step is handling button inputs. Well I just used another interrupt that gets triggered when a button input pin changes states (known as pin change interrupt, a feature the PIC supports on certain pins). When my interrupt sees a button pressed it sets a global flag that any part of my program can read, act on, and then clear to let every other program know that the button press has been serviced.
The reason I used an interrupt for the display scanning was to save me from having to manually do it within my main loop and for an asynchronous button input because sitting there and repeatedly reading a button waiting for it to be pressed is a waste of clock cycles. So interrupts handle both of these tedious tasks and leave the main part of my program enough computational freedom to actually do something interesting like telling the time or displaying neat animations.
So how does it keep track of time then? Well I took the easy road and used a DS1302 serial real time clock chip to do it for me. It can even charge a supercap to keep time for awhile when power is removed. I talk to the RTC over a three wire serial interface that is basically SPI, using a soft serial library I wrote so I can use any GPIO pins I want.
The final thing I had to contend with was adding animation modes. So far it has seven modes: chase, random, twinkle, pong, rain, bouncing ball, and ripple. Chase lights up an LED that runs left to right and top to bottom. Random hypnotically fills the screen with randomly lit pixels. Twinkle randomly turns on and off random pixels all over the screen. Pong is basically a one paddle game of pong where a ball bounces around and hits a paddle at the bottom that follows it. Rain is a randomly generated storm of pixels falling down (almost reminiscent of the infamous Matrix falling letters animation). Bouncing ball is basically Pong but without the paddle and added randomly changing speed each time the ball hits a wall. Ripple is a randomly generated spreading wave (not unlike drops of water disturbing the surface of a puddle of water).
You may have caught on that many of the descriptions for these animations use the word random. I wanted to display many of the animations differently each time they are displayed but how to do that with a microcontroller. Random for computer systems is pretty tough to pull off without reading some kind of random input. Well once again I cheated a little. I used rand(), a standard C function to generate a pseudo-random number.
I say pseudo-random because it is actually deterministic (by merit of it being a linear shift feedback computation), but for short enough runs a normal person wont be able to notice that it isn't really very random. The issue is if you just use rand() then every time the micro turns on it will generate the same exact series of "random" numbers ... this random function is starting to really sound not very random!
Well to fix that we can "seed" it with different starting numbers using the function srand (srand = seed+rand ... very creative ...). But to make sure the seed is then not always the same we take advantage of the fact that this device is a clock and when the user starts an animation is not likely to be at exactly the same time as the last time. So we set our seed to a function of the current time (I basically concatenated the hours, minutes, and seconds into a large number).
So that is pretty much an overview of all the semi-interesting bits of the software. I still have a few tiny bugs to work out related to one or two of the animations getting stuck randomly (I'd place a bet it is something related to the random aspect of them) but I'm happy to say that time display and setting modes work 100%. In the next project update I'll show the design and results for the 3D printed light box, but for now I'll get back to bug hunting till everything works perfectly.