Go Down

Topic: Advanced methods in a simple sketch (Read 4641 times) previous topic - next topic

Jiggy-Ninja

Aug 04, 2017, 05:05 am Last Edit: Aug 06, 2017, 11:36 pm by Jiggy-Ninja Reason: Changed zip file
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.

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.

Whandall

#1
Aug 04, 2017, 06:51 am Last Edit: Aug 04, 2017, 06:56 am by Whandall
Feel free to ask any questions. I'm sure there will be many.
Proprocessor directives
Why do you call the preprocessor proprocessor?
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

GolamMostafa

#2
Aug 04, 2017, 06:54 am Last Edit: Aug 04, 2017, 07:25 am by GolamMostafa
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.

GolamMostafa

Quote
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.            

Jiggy-Ninja

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.

Quote
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.

BulldogLowell

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!

gfvalvo

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).
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

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:
Code: [Select]

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.
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

Jiggy-Ninja

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:
Code: [Select]

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

Jiggy-Ninja

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.

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

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?

Robin2

#12
Aug 06, 2017, 03:48 pm Last Edit: Aug 06, 2017, 03:51 pm by Robin2
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
Two or three hours spent thinking and reading documentation solves most programming problems.

kenwood120s

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.

[gumby]I've got my head stuck in the cupboard[/gumby]

Robin2

#14
Aug 06, 2017, 09:06 pm Last Edit: Aug 06, 2017, 09:07 pm by Robin2
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"  :)


...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Go Up