A.k.a. “There is no kill like overkill.”
I’m actually quite dissatisfied with many of the examples included with the Arduino IDE. In particular, Blink without Delay is often referenced as an example of the “nonblocking” coding technique. While it works for doing one thing (toggling an LED) at regular intervals, it is very simply written and hard to generalize from. What if you want different on and off times? Or a more complex sequence like Morse code with a series of short and long flashes? I’ve wanted for quite a while to write up my own example that is more complicated, but better shows off the flexibility of the techniques. I’ve gone a bit overboard with that ambition. The sketch is attached in the .zip file below.
My sister and I build a POV fan display based on this Great Scott video here. The only changes we made to the hardware were to use 7 LEDs instead of 5 and adding a TP4056 battery charger. I went with 7 LEDs in order to use the same bitmaps as 5x7 character LCDs. Behold! I present…the Drama Queen!
Video link. Swoon at my sexy voice.
The code he posted to the Instructables however, offends my delicate sensibilities. delay()? NO!!! MY EYES!!
So I rewrote it all from scratch. The result, I think, is a thing of beauty. It also has two legitimate technical improvements over Great Scott’s code: my version can dynamically adapt to changes in fan speed, so that the display is stable no matter which setting you have the fan switch on. It also reverses the letters on the bottom so that they appear in the right order and the right orientation to be readable.
Did I overengineer this sketch just to get those improvements? Some might say so, but I say NAY! Anything worth doing is worth overdoing, and anything worth overdoing is worth really overdoing. I have poured all of the C++ skills I have honed over the years into crafting this sketch, and I offer this up to the community for its further education. Almost everything is original code written by me, though significant parts of it are influenced by the conventions of the Standard Template Library. Also my PMEMRef and PMEMPtr classes are basically ripped off from the Arduino EEPROM library, adapted to use PROGMEM variables instead of EEMEM. (I can only hope that many of you will do the same with my code) The biggest benefit of this is that all of the libraries I’ve created here are reusable in other projects, and they are just a portion of the goodies that I have created in my utilslib folder.
Underneath the apparent obscene complexity, the task the program is performing is very straight forward. The row of LEDs is lit up based on a timed sequence. A magnet is detected by a Hall sensor to mark the start of the sequence, and the interval between marks is measured to adjust the sequence speed. For the bottom half of the rotation, a lot of things had to be reveresed so the letters appear in the right way. That last part might seem easy, but I needed some inspiration to come up with a way to do it in a fairly elegant way with a minimum of repeated code.
Please forgive the stylistic faux pas I have commited by mixing CamelCase and underscore_notation together. And of course, not every sketch needs this level of complexity. Hopefully it can provide inspiration for how some of these techniques can be used in other projects. Pick and choose as you please.
This sketch contains examples of the following advanced techniques:
Non-blocking timing and sequencing
The Hall sensor must be monitored at all times, and the sequence must be able to be reset at any time, even if it is not finished when the next trigger comes (such as if the fan is speeding up). This precludes the use of delay() to time the LED sequence. This technique is seen in two timing objects that are used to time the traversal of a column’s worth of arc and also the transition from the top section to the bottom.
The essence of non-blocking timing and flow control is to use data to control the execution of your code, and not just lists of program statements. Study how the sequence and timer objects interact in order to execute a multi-stage sequence instead of just toggling a single output.
Separation of functionality into classes
Known in the jargon as encapsulation, seperating independent bits of functionality into classes and functions can reduce clutter in you loop function. It also makes it easier to adjust the interaction between different parts with minimal effort. While trying to track down a bug, I wanted to replace the Hall Sensor trigger with a timer so that the code would move through the sequences even without spinning the fan. With this code structure, I only needed to substitute in one line.
Sophisticated class behavior with operator overloading
Instead of verbose function names, a great deal of conciseness can be gained by providing behavior for the various operators in the language, particularily for things like arithmetic( + - * /), comparison(== < > != <= >=) and assignment (=). The dereference and access operators (* ->) can be used to give your type a pointer-like behavior, useful for types that are meant to iterate over a collection of some kind (like the sequence types).
Template functions and classes, with specialization of methods
Templates allow you to use the same function with a variety of different types. They are essentially a way to use types as a variable as long as the function code is valid once the type you actually use is subsituted in. There are some restrictions to using tmeplates, but they are a powerful tool for writing flexible code that can be adapted to many situations.
The PMEMRef class contains specializations for some of its methods to optimize its handling of some of the unique types.
"Magic value" avoidance
A “magic number” is when a literal value is used within code with no explanation or context for why it is that value. I’ve taken care to minimize the amount of literal numbers in the actual code by giving constants meaningful names. It is also beneficial to use expressions to calculate dependant values. This prevents mistakes, and also requires less things to be changed if you want to adjust your code. For exmaple, I’ve defined that each letter’s worth of arc be divided into 5 sections to display the LED matrix in, and 2 blank sections to provide a reasonable amount of space between letters. Rather than hard coding the CHARACTER_WIDTH constant to 7, it is defined as the sum of the previous two constants. This makes the compiler do the math for me, and means I only have to change 1 thing.
Because this is a guideline and not a rule, it is open to some interpretation. One thing that might not be understood is that HIGH and LOW are also magic numbers. It is true that they are already named constants, but they are not meaningful names. HIGH and LOW do not have any intrinsic meaning on their own, because it depends on the circuit that is connected to the pin. For example, the LEDs in my circuit are activated when a HIGH is written to their pin, but when the Hall sensor is activated by a magnet, it pulls the pin LOW. Giving meaningful names (HallActiveLevel and LEDActiveLevel) to replace HIGH and LOW makes it easier to understand the code’s intention.
Most people probably just know about the #include and #define directives, but you can do some magic with the preprocesor. #define isn’t just for static symbols, it can also be used to define function-like macros, like with my DEBUG #defines toward the top of the sketch. Combined with the #if-#else-#endif directives that let you conditionally add or exclude based on conditions, you can quickly shift between a “debug” version of your code (that might use Serial prints, different inputs or outputs, or other changes) to a “production” version with all the test code stripped out.
You can see the remains of my efforts to debug a problem in my load_leds() function left in the sketch.
There’s some other things in here that are pretty gratuitous right now (like some abstract classes I inherit from), but playing around with that and learning the effects of all the different parts is where all the fun is! I also include the Excel sheet I used to generate the font tables, showing how formulas and other labor saving activities can make it easy to generate pre-computed tables of values.
Feel free to ask any questions. I’m sure there will be many.
POV_Fan_Display_Reverse_Bottom.zip (76.1 KB)