Advanced methods in a simple sketch

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. :slight_smile:

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.

Preprocessor directives
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)

Jiggy-Ninja:
Feel free to ask any questions. I'm sure there will be many.

Jiggy-Ninja:
Proprocessor directives

Why do you call the preprocessor proprocessor?

Apart from high technical value, it is an Art that brings beauty to mind and eyes!

Appreciation to the artists who spent their valuable skills of programming, workmanship, manual labor, and sleepless nights.

Art is hardly reproduceable even by the artist himself/herself. It is possible that he/she will create a better one, but not this one.

Feel free to ask any questions. I'm sure there will be many.

The message Drama Queen is not there as a static entity. As I have understood from the You Tube, you have created an illusion from a rotating bar containing LEDs. You have said that you have poured your whole skills of C/C++ to craft this project, and yet you have to depend on some kind of inspiration to finish it.

Naturally, it is not so easy to line up the thoughts of how this thing works. Few LEDs are rotating, and at the same time it is sustaining an almost a static message -- it is totally an act of hypnotize! But, we know that there are solid science and technology behind this. It is an expectation that the creator (s) of this artistic piece would describe the working principles using what we call 'Popular Science.' Albert Einstein, in response to the requests of his friends, wrote few Popular Sciences on his great theory of Relativity.

GolamMostafa:
The message Drama Queen is not there as a static entity. As I have understood from the You Tube, you have created an illusion from a rotating bar containing LEDs.

That is called persistence of vision, which is what the POV stands for. This has 1 line of 7 LEDs.

You have said that you have poured your whole skills of C/C++ to craft this project, and yet you have to depend on some kind of inspiration to finish it.

The "inspiration" was for the load_leds() function. I wanted to keep the whole sequence array in readable order ("Drama Queen" instead of "Drama neeuQ"), and I wanted to reuse the bitmaps I defined in the font.h file so I didn't have to make special tables of reverse letters. That means I needed to come up with a way to create one function that would work one way when in the top section, and when in the bottom section it worked off the opposite end of the sequence, moved backwards through the sequence, read the columns from the table in reverse order, and also reversed the order of the LED pins they're written to.

That's where the seqArr (Sequence Array), seqAdvArr (Sequence Advance Array), and colTransArr (Column Translation Array) come in. The rotHalf state variable keeps track of whether the LEDs are in the top or bottom part of the rotation. That is used as the index to the arrays to chose which sequence to read from, and also which operations to perform on the sequence and the values loaded. seqAdvArr chooses whether to go forward or backward in the sequence, and colTransArr determines whether the letter columns and LEDs are normal or reversed.

It's probably not the best way that could be done, but it's better than the other methods I came up with before.

Jiggy-Ninja:
It's probably not the best way that could be done, but it's better than the other methods I came up with before.

nice work

now add Wifi and get messages!

Thanks. I’ve been looking for motivation to buckle down and learn C++ (more than 30 years a C programmer). And, I’ve also wanted to explore POV. This might help with both. Although, the C++ aspects are probably too advanced for me at the moment, I’ll go through my book, look at the code, and then go back to the book(s).

As implied by my previous post, my knowledge of C++ is near nil. So, I'll just ask if anyone can enlighten me on why this won't compile for Teensy 3.2:

Build options changed, rebuilding all
In file included from C:\Users\tr001221\AppData\Local\Temp\arduino_build_257254\sketch\wrapper.h:21:0,

                 from C:\Users\tr001221\Documents\Portable Arduino 1.8.2\arduino-1.8.2\portable\sketchbook\POV_Fan_Display_Reverse_Bottom\POV_Fan_Display_Reverse_Bottom.ino:2:

mrd_int_traits.h:79: error: 'signed_full_type' in 'struct mrd::int_traits<unsigned int>' does not name a type
                                         typedef typename int_traits<uintptr_t>::signed_full_type signed_full_type;

                                                                                 ^

mrd_int_traits.h:81: error: incomplete type 'mrd::int_traits<unsigned int>' used in nested name specifier
                                         static const uintptr_t max    = int_traits<uintptr_t>::max;

                                                                         ^

mrd_int_traits.h:82: error: incomplete type 'mrd::int_traits<unsigned int>' used in nested name specifier
                                         static const uintptr_t min    = int_traits<uintptr_t>::min;

                                                                         ^

mrd_int_traits.h:83: error: incomplete type 'mrd::int_traits<unsigned int>' used in nested name specifier
                                         static const uint8_t bit_size = int_traits<uintptr_t>::bit_size;

                                                                         ^

mrd_int_traits.h:84: error: incomplete type 'mrd::int_traits<unsigned int>' used in nested name specifier
                                         static const bool is_signed   = int_traits<uintptr_t>::is_signed;

                                                                         ^

mrd_int_traits.h:85: error: in argument to unary !
                                         static const bool is_unsigned = !is_signed;};

                                                                          ^

'signed_full_type' in 'struct mrd::int_traits<unsigned int>' does not name a type

Compiles fine for Uno.

gfvalvo:
As implied by my previous post, my knowledge of C++ is near nil. So, I'll just ask if anyone can enlighten me on why this won't compile for Teensy 3.2:

Build options changed, rebuilding all

In file included from C:\Users\tr001221\AppData\Local\Temp\arduino_build_257254\sketch\wrapper.h:21:0,

from C:\Users\tr001221\Documents\Portable Arduino 1.8.2\arduino-1.8.2\portable\sketchbook\POV_Fan_Display_Reverse_Bottom\POV_Fan_Display_Reverse_Bottom.ino:2:

mrd_int_traits.h:79: error: 'signed_full_type' in 'struct mrd::int_traits' does not name a type
                                        typedef typename int_traits<uintptr_t>::signed_full_type signed_full_type;

^

mrd_int_traits.h:81: error: incomplete type 'mrd::int_traits' used in nested name specifier
                                        static const uintptr_t max    = int_traits<uintptr_t>::max;

^

mrd_int_traits.h:82: error: incomplete type 'mrd::int_traits' used in nested name specifier
                                        static const uintptr_t min    = int_traits<uintptr_t>::min;

^

mrd_int_traits.h:83: error: incomplete type 'mrd::int_traits' used in nested name specifier
                                        static const uint8_t bit_size = int_traits<uintptr_t>::bit_size;

^

mrd_int_traits.h:84: error: incomplete type 'mrd::int_traits' used in nested name specifier
                                        static const bool is_signed  = int_traits<uintptr_t>::is_signed;

^

mrd_int_traits.h:85: error: in argument to unary !
                                        static const bool is_unsigned = !is_signed;};

^

'signed_full_type' in 'struct mrd::int_traits' does not name a type




Compiles fine for Uno.

Apparently it doesn't like the uintptr_t type. It must not be defined for Teensy. Try changing all the uintptr_t references to intptr_t.

Although even if you fix that it won't work on a Teensy 3.2 since I use PROGMEM for the font tables. This sketch is made for AVRs, specifically the ATmega328P since that's what's on the Pro Mini.

umm.
Start by putting some of the time you spent writing your post on putting comments in the code.
After looking at the code for several minutes, I can't even figure out what "mrd" stands for. :frowning:

The "standard operator definitions for any template" seems like something that might have a "standard" implementation elsewhere (although perhaps not suitable for something tiny like an Arduino.) Did you look?

Reply #10 caused me to download and look briefly at the ZIP file in the Original Post

@Jiggy-Ninja, may I first make a plea that when you create a ZIP file ensure that all of it extracts into a single directory to avoid your files getting mixed up with other files on the user's PC. The way it is packaged at the moment there are two directories and 11 files (if I have counted correctly).

From my quick look at the .ino file I have no idea what it is all about - even after speed reading your Original Post.

In your Original Post you say that the example illustrates several techniques. IMHO it would be much better if you first had a tutorial on each technique on its own. And only after that show a combined example.

And I strongly share the sentiment which I believe underlies Reply #10 that is, that an example without an extensive explanation is a poor teaching tool. From my own experience of trying to figure out examples it is often very difficult to know what elements are specifically required and what are open to variation by the user.

Finally I think there is a huge difference between the techniques that are useful to a regular programmer - such as a professional working in a team - and an occasional hobby programmer like myself, or the many Arduino users that may ever only write one or two programs.

My own programming "policy" is to write code that I can understand with a single read-through after not seeing it for 6 months. I don't often achieve my target, but it is what I aim for so I prefer to be obvious rather than clever.

...R

westfw:
Start by putting some of the time you spent writing your post on putting comments in the code.

OP subscribes to the "it was difficult to write, so it should be difficult to read" programming paradigm.

kenwood120s:
OP subscribes to the "it was difficult to write, so it should be difficult to read" programming paradigm.

I am not commenting on the OP when I say that it seems to me there are programmers who come at it from the other end - "How can I write it so that it is difficult to read in order to make me look like an expert"

Note, especially, the words "look like" :slight_smile:

...R

westfw:
umm.
Start by putting some of the time you spent writing your post on putting comments in the code.
After looking at the code for several minutes, I can't even figure out what "mrd" stands for. :frowning:

MRD are my initials. I use it as the namespace for all of the stuff I've made so there's no chance of it ever conflicting with a library I download.

I have put quite a lot of comments in the main sketch and I tried to comment my library pieces, but I'm coming at this from a different level than my target audience so I'm not sure how much exposition in the comments is necessary.

westfw:
The "standard operator definitions for any template" seems like something that might have a "standard" implementation elsewhere (although perhaps not suitable for something tiny like an Arduino.) Did you look?

If you're talking about class_arithops.h and class_relops.h, there's similar stuff in the STL. There's a downloadable ArduinoSTL library, but I went implemented the same thing in a slightly different way.

Robin2:
@Jiggy-Ninja, may I first make a plea that when you create a ZIP file ensure that all of it extracts into a single directory to avoid your files getting mixed up with other files on the user's PC. The way it is packaged at the moment there are two directories and 11 files (if I have counted correctly).

The whole zip file is the sketch folder, I just zipped it up from inside the folder instead of outside. I fixed it like you suggested.

From my quick look at the .ino file I have no idea what it is all about - even after speed reading your Original Post.

I'm not sure what you're asking about. It's the code used to display "Drama Queen" on the fan POV display.

In your Original Post you say that the example illustrates several techniques. IMHO it would be much better if you first had a tutorial on each technique on its own. And only after that show a combined example.

The trouble is I'm not that good at that. I've tried typing up some individual tutorials, but I ended up continually revising and rewriting and never really being happen with what I wrote down.

And I strongly share the sentiment which I believe underlies Reply #10 that is, that an example without an extensive explanation is a poor teaching tool. From my own experience of trying to figure out examples it is often very difficult to know what elements are specifically required and what are open to variation by the user.

That's why I tried to split as much stuff as possible (timing, sequencing, index wrapping) into separate classes. If you've got ideas for examples to write up that would be better, I could write up sketches for this.

The thing I wanted to avoid is a toy example that is simplified to the point of uselessness, like Blink without Delay. It showcases the technique in the simplest example possible, and doesn't provide many hints for how to generalize it to more complicated sequencing. Even your Several Things At a Time demo only uses two states for the blinking LEDs. I think it would be much improved by making one LED blink out as SOS pattern or some other slightly more complicated pattern than just a repetitive ON-OFF.

Finally I think there is a huge difference between the techniques that are useful to a regular programmer - such as a professional working in a team - and an occasional hobby programmer like myself, or the many Arduino users that may ever only write one or two programs.

I'm actually not a professional programmer, and I went to school to learn hardware, not software. I'm almost completely self-taught. The few programming classes I had in school didn't really teach me anything that I didn't already know. I just happen to really love this stuff. Microcontrollers are definitely not an "occasional" hobby for me, if you know what I mean.

My own programming "policy" is to write code that I can understand with a single read-through after not seeing it for 6 months. I don't often achieve my target, but it is what I aim for so I prefer to be obvious rather than clever.

...R

I agree. That's why I've tried to write my own libraries that I can be familiar with instead of relying on other people's code. I definitely study other people's code, since I said I lifted my PMEM code right from the Arduino EEPROM library and improved it to suit my tastes.

The only part of this code that I think counts as "clever" enough to cause confusion in 6 months time is using the function arrays in the load_leds() function. I already confessed that that's the part I'm least confident in.

kenwood120s:
OP subscribes to the "it was difficult to write, so it should be difficult to read" programming paradigm.

Just because you can't understand it doesn't mean it's difficult to understand. If you have specific complaints I'd love to hear them.

The first two words in this thread's title are "Advanced Methods". What kind of sketch were you expecting to see here? I specifically mentioned in the OP that this is overboard. Simpler methods can be used, but there are benefits to this way. The primary one is reusability. All of the header files can be (and are) reused in other sketches I've written. It can also be reworked to add or change functionality. Will I? Maybe. I'm thinking of using APA102 LEDs in version 2.

Jiggy-Ninja:
I agree. That's why I've tried to write my own libraries that I can be familiar with instead of relying on other people's code. I definitely study other people's code, since I said I lifted my PMEM code right from the Arduino EEPROM library and improved it to suit my tastes.

The only part of this code that I think counts as "clever" enough to cause confusion in 6 months time is using the function arrays in the load_leds() function. I already confessed that that's the part I'm least confident in.

If I have misunderstood the intention then I apologize.

My understanding is that you used your Drama Queen program as a demo from which others can learn programming techniques.

Of course someone can take the code and use it to reproduce your project, but I don't think that was your principal purpose for posting it.

...R

Robin2:
My understanding is that you used your Drama Queen program as a demo from which others can learn programming techniques.

That is the intention, I don't know what you think I'm saying. I've improved by studying code that's better than mine, so I'm hoping other people can learn some of these techniques by seeing them actually used to do something instead of in a contrived example.

I did it with this project because the fundamental operation of it is not that complicated. There is one input to mark the start of the sequence, then the outputs are written to in a fixed, timed sequence.

When there's an active edge on the Hall input (falling edge when the magnet comes near), the series are reset, the state is changed to TOP, the timer intervals are recalculated based on the amount of time since the last active edge, and the timers are restarted.

When the half-rotation timer interval passes, the state is changed from TOP to BOTTOM, and the LED timer is restarted.

When the LED timer interval passes, a new LED pattern is loaded from the sequence and the position is advanced.

Since the sketch doesn't have a whole lot going on, that puts the focus on the techniques instead of complexities in the sketch logic.