Silicon ChipMax’s Cool Beans - March 2021 SILICON CHIP
  1. Outer Front Cover
  2. Contents
  3. Subscriptions: PE Subscription
  4. Subscriptions: PicoLog Cloud
  5. Back Issues: PICOLOG
  6. Publisher's Letter
  7. Feature: The Fox Report by Barry Fox
  8. Feature: Techno Talk by Mark Nelson
  9. Feature: Net Work by Alan Winstanley
  10. Project: Nutube Guitar Overdrive and Distortion Pedal by John Clarke
  11. Project: Programmable Thermal Regulator by Tim Blythman and Nicholas Vinen
  12. Project: Tunable HF Preamplifier with Gain Control by Charles Kosina
  13. Feature: Circuit Surgery by Ian Bell
  14. Feature: Make it with Micromite by Phil Boyce
  15. Feature: PICn’Mix by Mike Hibbett
  16. Feature: Max’s Cool Beans by Max the Magnificent
  17. Feature: Max’s Cool Beans cunning coding tips and tricks by Max the Magnificent
  18. Feature: AUDIO OUT by Jake Rothman
  19. PCB Order Form
  20. Advertising Index: TEACH-IN by Max the Magnificent

This is only a preview of the March 2021 issue of Practical Electronics.

You can view 0 of the 72 pages in the full issue.

Articles in this series:
  • (November 2020)
  • Techno Talk (December 2020)
  • Techno Talk (January 2021)
  • Techno Talk (February 2021)
  • Techno Talk (March 2021)
  • Techno Talk (April 2021)
  • Techno Talk (May 2021)
  • Techno Talk (June 2021)
  • Techno Talk (July 2021)
  • Techno Talk (August 2021)
  • Techno Talk (September 2021)
  • Techno Talk (October 2021)
  • Techno Talk (November 2021)
  • Techno Talk (December 2021)
  • Communing with nature (January 2022)
  • Should we be worried? (February 2022)
  • How resilient is your lifeline? (March 2022)
  • Go eco, get ethical! (April 2022)
  • From nano to bio (May 2022)
  • Positivity follows the gloom (June 2022)
  • Mixed menu (July 2022)
  • Time for a total rethink? (August 2022)
  • What’s in a name? (September 2022)
  • Forget leaves on the line! (October 2022)
  • Giant Boost for Batteries (December 2022)
  • Raudive Voices Revisited (January 2023)
  • A thousand words (February 2023)
  • It’s handover time (March 2023)
  • AI, Robots, Horticulture and Agriculture (April 2023)
  • Prophecy can be perplexing (May 2023)
  • Technology comes in different shapes and sizes (June 2023)
  • AI and robots – what could possibly go wrong? (July 2023)
  • How long until we’re all out of work? (August 2023)
  • We both have truths, are mine the same as yours? (September 2023)
  • Holy Spheres, Batman! (October 2023)
  • Where’s my pneumatic car? (November 2023)
  • Good grief! (December 2023)
  • Cheeky chiplets (January 2024)
  • Cheeky chiplets (February 2024)
  • The Wibbly-Wobbly World of Quantum (March 2024)
  • Techno Talk - Wait! What? Really? (April 2024)
  • Techno Talk - One step closer to a dystopian abyss? (May 2024)
  • Techno Talk - Program that! (June 2024)
  • Techno Talk (July 2024)
  • Techno Talk - That makes so much sense! (August 2024)
  • Techno Talk - I don’t want to be a Norbert... (September 2024)
  • Techno Talk - Sticking the landing (October 2024)
  • Techno Talk (November 2024)
  • Techno Talk (December 2024)
  • Techno Talk (January 2025)
  • Techno Talk (February 2025)
  • Techno Talk (March 2025)
  • Techno Talk (April 2025)
  • Techno Talk (May 2025)
  • Techno Talk (June 2025)
Max’s Cool Beans By Max the Magnificent Flashing LEDs and drooling engineers – Part 13 E eek! I can’t believe that we are already at Part 13 of this magnificent mini-mega-series. Hmmm, ‘thirteen’. I have a friend who refuses to leave his house on a Friday, 13th, although this number is ‘Lucky for some,’ as they say. Are you confused? When we have pixels (in the form of tricolor LEDs, in our case) arranged in rings, one of the things we commonly want to do is to cycle around, turning a new pixel on and an old pixel off at the same time. We’ve talked about various techniques we can use to achieve this in previous columns. Sad to relate, however, several readers have emailed me to say that they remain baffled, confounded, confused, and perplexed – possibly because we couldn’t restrain ourselves from introducing and investigating a cornucopia of alternative approaches as we meandered along. So let’s take a moment to gather all of these tactics together in one place and do our best to demystify things. In the case of my Prognostication Engine project, the rings each have 16 pixels. However, as I noted in a previous column (PE, January 2021), when I’m trying to wrap my brain around something, I first like to simplify the problem, which – in this case – means reducing the number of pixels. Given a choice, we want to come up with a generic solution that can work for any number of pixels, so I typically opt for a small prime number on the basis that, if our solution works for this, it will work for anything. Thus, purely for the purposes of our thought experiments, let’s assume we have a 5-pixel ring and that our pixels are numbered clockwise, starting with 0 at the top (Fig.1a). Sometimes we may wish to set a single pixel racing round the ring in a clockwise direction (Fig.1b); other times, we may opt for a widdershins (a.k.a. counterclockwise a.k.a. anticlockwise) rotation (Fig.1c). To keep these discussions general, let’s assume that that the number of pixels we are playing with is defined as NUM_P, which will be 5 in the case of this example. Also, since our pixels are numbered from 0 to 4, let’s also assume we’ve defined MAX_P as being (NUM_P - 1), because this will save us from having to subtract 1 all the time. As we’ve discussed in earlier columns, if we are implementing a clockwise rotation, and if we are currently poised to activate pixel P, then most of the time we want to deactivate pixel (P - 1). It’s only when we wish to activate pixel 0 that we need to deactivate pixel MAX_P. Similarly, if we are implementing an anticlockwise rotation, and if we are currently about to activate pixel P, then most of the time we want to deactivate pixel (P + 1). It’s only when we wish to activate pixel MAX_P that we need to deactivate pixel 0. Time Tricky transition 0 4 1 3 (b ) Clockwise rotation 2 (a) P ix el numb ers (c) Anticlockwise rotation Tricky transition Testing times One solution is to perform a simple test to determine which pixel is to be deactivated. Assuming a clockwise rotation, our main loop() function could be written as follows (where the test is shown in bold). void loop () { for (int iPix = 0; iPix <= MAX_P; iPix++) { // Turn new pixel on Neos.setPixelColor(iPix, COLOR_ON); // Turn old pixel off if (iPix = 0) Neos.setPixelColor(MAX_P, COLOR_OFF); else Neos.setPixelColor((iPix - 1), COLOR_OFF); // Display the result Neos.show(); delay(PadDelay); } } It’s probably worth noting that in previous programs we’ve used the equivalent of iPix < NUM_P (which equates to iPix < 5 in this example) as the test condition in our for() loop. The reason we are using iPix <= MAX_P (which equates to iPix <= 4) as the test condition in this example is that it will better complement the experiments that are to come. The main point to note here is that they both result in the same actions, as seen by the person watching the ring perform its magic. If you wish to peruse and ponder this in more detail, the full sketch (program) is presented in file CB-Mar21-01.txt (it, and any other files associated with this article, are available on the March 2021 page of the PE website: https://bit.ly/3oouhbl). If you do look at the full sketch, you’ll see that we’ve set the cycle time – that is, the time taken to perform a complete revolution of the ring – to be 1,000ms (milliseconds) – ie, one second. We also make the assumption that it takes 1ms to perform any calculations and upload new values for each pixel position. Based on this, we calculate an appropriate value for the PadDelay variable in our setup() function. We could use a similar testing approach to perform a counterclockwise rotation, as illustrated below (file CB-Mar21-02. txt). In this case, we’ve highlighted any changes to our previous code in bold. void loop () { for (int iPix = MAX_P; iPix >= 0; iPix--) { // Turn new pixel on Neos.setPixelColor(iPix, COLOR_ON); Fig.1. Clockwise and anticlockwise rotations. 58 Practical Electronics | March | 2021 // Turn old pixel off if (iPix == MAX_P) Neos.setPixelColor(0, COLOR_OFF); else Neos.setPixelColor((iPix + 1), COLOR_OFF); // Display the result Neos.show(); delay(PadDelay); } Similarly, if we wish to perform an anticlockwise rotation (Fig.2b), then for each step around the ring, we know the number of the new pixel to be turned on and we need to calculate the number of the old pixel to be turned off. In this case, if we add 1 to the number of the pixel we just turned on and then perform a modulo division with NUM_P (the number of pixels), we end up with the value we desire. The code for this is shown below with the interesting parts highlighted in bold (file CB-Mar21-04.txt). void loop () { int tPix; } Before we move on, it’s worth reminding ourselves that iPix 1 or iPix + 1 works just fine most of the time. The only reason we have to perform the aforementioned tests is to handle the boundary values that occur at the beginning of the loop (or the end of the loop, depending on your point of view). for (int iPix = MAX_P; iPix >= 0; iPix--) { // Turn new pixel on Neos.setPixelColor(iPix, COLOR_ON); Magnificent modulos Using tests (as discussed above) certainly provides serviceable solutions. On the other hand, they feel a little ‘messy’ somehow. As you may recall, the modulo operator % returns the integer remainder from an integer division. Well, this is one of those cases where the modulo operator really gets a chance to ‘strut its stuff,’ as it were. Since I’m not a natural programmer, I typically sketch things out on paper first. Let’s start with a clockwise rotation (Fig.2a). Based on our earlier sketches, we know that – for each step around the ring – what we’ve got is the number of the new pixel to be turned on, while what we want is the number of the old pixel to be turned off. If we add MAX_P (which is the number of pixels minus one) to the number of the pixel we just turned on, and then perform a modulo division with NUM_P (the number of pixels), we end up with the value we desire. The code for this is shown below with the interesting parts highlighted in bold (file CB-Mar21-03.txt). void loop () { int tPix; for (int iPix = 0; iPix <= MAX_P; iPix++) { // Turn new pixel on Neos.setPixelColor(iPix, COLOR_ON); // Turn old pixel off tPix = (iPix + MAX_P) % NUM_P; Neos.setPixelColor(tPix, COLOR_OFF); // Display the result Neos.show(); delay(PadDelay); } W hat we’ ve got W hat we’ ve got W hat we want W hat we want Old P ix el 0 1 4 3 2 2 0 1 4 3 Calculation (0 + (1 + (2 + (3 + (4 + 4 )% 4 )% 4 )% 4 )% 4 )% 5= 5= 5= 5= 5= // Display the result Neos.show(); delay(PadDelay); } } It’s rude to point I don’t know about you, but when I was a kid and I tried to direct my mother’s attention to something of interest (like an old lady with a huge wart on the end of her nose, just to pick an example out of thin air), she would inform me by means of a menacing whisper that ‘It’s rude to point,’ after which we would suddenly discover that our presence was required elsewhere. Of course, my mother wasn’t into programming, or so I assume, but – now I come to think about it – I didn’t even know she spoke fluent French and German until after I’d left home, so maybe she programs like a diva. Be that as it may, pointers can be jolly useful in the context of programs. Now, I should point out that I’m not talking about true C pointers, which – although they are extremely efficacious – are a completely different kettle of fish and a topic in their own right. For the purposes of this column, I’m visualising a simple pseudo pointer implemented using an integer. As usual, we’ll start with a clockwise rotation. Let’s assume that we declare a global integer variable called NewP (‘new pixel’); also, that we initialise this to contain 0. In this case, the code for our main loop() function could be as follows (file CB-Mar21-05.txt). void loop () { int oldP; } N ew P ix el // Turn old pixel off tPix = (iPix + 1) % NUM_P; Neos.setPixelColor(tPix, COLOR_OFF); N ew P ix el Old P ix el 4 0 2 3 4 0 1 3 2 1 0 3 2 1 W hat we’ ve got W hat we’ ve got N umb er of pix els – 1 (MAX_P) Add 1 Modulo operator Modulo operator N umb er of pix els (NUM_P) N umb er of pix els (NUM_P) R esult is what we want R esult is what we want (a) Clockwise rotation Calculation (4 + (3 + (2 + (1 + (0 + 4 1) % 1) % 1) % 1) % 1) % 5= 5= 5= 5= 5= // Turn old pixel off oldP = (NewP + MAX_P) % NUM_P; Neos.setPixelColor(oldP, COLOR_OFF); 0 4 3 2 1 // Display the result Neos.show(); delay(PadDelay); // Increment the pointer NewP = (NewP + 1) % NUM_P; (b ) Anticlockwise rotation Fig.2. Using the modulo operator to give us what we want. Practical Electronics | March | 2021 // Turn new pixel on Neos.setPixelColor(NewP, COLOR_ON); } 59 Observe that we no longer require the for() loop. Also, that we are using exactly the same modulo operation to calculate the number of the old pixel to be turned off (we just tweaked the name of the variable). The only other modification is that we now need to increment the value of our integer pointer at the end of the loop. As we see, we are using the modulo operator once again to ensure that NewP follows the sequence 0, 1, 2, 3, 4, 0, 1, 2... Of course, if we were to perform an anticlockwise rotation, we would want NewP to follow the sequence 4, 3, 2, 1, 0, 4, 3, 2... Just for giggles and grins, why don’t you try creating the counterclockwise version of this technique for yourself – even if only as a pencil and paper exercise – before we proceed further. What? You’ve finished already? I’m proud of you! As you may have come to realise, if we are performing an anticlockwise rotation, then when it comes to decrementing the pointer at the end, we can’t use anything along the lines of NewP = (NewP - 1) % NUM_P. This is because when you get to NewP being 0, then NewP - 1 will equal –1, and we don’t want to dive into the morass of complications and tribulations that will ensue if we apply the modulo operator at this point (see also this month’s Tips and Tricks). The solution I came up with is as follows (file CB-Mar21-06. txt). The part where we turn the old pixel off is based on our previous anticlockwise routine. The part that is of interest is where we decrement our pointer at the end. void loop () { int oldP; // Turn new pixel on Neos.setPixelColor(NewP, COLOR_ON); // Turn old pixel off oldP = (NewP + 1) % NUM_P; Neos.setPixelColor(oldP, COLOR_OFF); // Display the result Neos.show(); delay(PadDelay); // Decrement the pointer NewP = (NewP + MAX_P) % NUM_P; } The key point to note – apart from the fact it works – is the fact that we are using (NewP + MAX_P) means that we always have a positive number to feed to our modulo operator. It might be useful for you to jot down what you think is going on, and then take a look at Fig.3 to see how I visualise this. Did you spot anything interesting about Fig.3? How about the fact that this calculation is identical to that shown in Fig.2a (it’s just that we’ve reordered things a little)? Of course, W hat we’ ve got this makes total sense W hat we want when you think about it. NewP NewP Previously, we were using Calculation (P re-Dec) (P ost-Dec) our equation to calculate 4 3 (4 + 4 ) % 5 = 3 3 2 (3 + 4 ) % 5 = 2 the old pixel to turn off 2 1 (2 + 4 ) % 5 = 1 while performing a clock1 0 (1 + 4 ) % 5 = 0 0 4 (0 + 4 ) % 5 = 4 wise rotation. Now, we are using the same equation W hat we’ ve got to calculate the new pixel N umb er of pix els – 1 (MAX_P) to turn on while performModulo operator ing an anticlockwise rotaN umb er of pix els (NUM_P) tion. Scary as it seems, it’s R esult is what we want almost as if we had a clue Fig.3. Modulo everywhere you look. what we were doing. 60 Chickens and eggs All sorts of things keep popping into my head while I’m writing this. For example, our previous examples have involved first turning the current pixel on and then calculating the old pixel to be turned off. We could, of course, do things the other way around – that is, turn the current pixel off and then calculate the new pixel to be turned on. Let’s take our previous clockwise rotation example (file CB-Mar21-05.txt) and modify it to reflect this new scenario (file CB-Mar21-07.txt). In this case, our main loop() function might appear as follows: void loop () { // Turn old pixel off Neos.setPixelColor(CurrentP, COLOR_OFF); // Increment the pointer CurrentP = (CurrentP + 1) % NUM_P; // Turn new pixel on Neos.setPixelColor(CurrentP, COLOR_ON); // Display the result Neos.show(); delay(PadDelay); } If you compare the two solutions, you will observe that our new version is a tad more concise and correspondingly more efficient. Previously, we had to calculate the number of the old pixel and also the value of the new pointer, which therefore required two modulo operations. By comparison, our new incarnation requires only a single modulo operation. Tra-la! Now see if you can work out the equivalent solution for an anticlockwise rotation, and compare it to my version (CB-Mar21-08.txt). Tortuous tables Another pointer-based approach – still using our simplistic integer pointers as opposed to real C pointers – is to construct a look-up table in the form of an array whose size reflects the number of pixels we are playing with. We then load our table (array) with the calculated values for the adjacent clockwise (cWise) and anticlockwise (acWise) pixels (Fig.4). Even though we are currently thought-experimenting with a 5-pixel ring, we want to make our implementation easily scalable to larger rings with more pixels. We’ll start by taking our previous ‘Chickens and eggs’ clockwise rotation (file CB-Mar2107.txt), spinning off a new version (file CB-Mar21-09.txt), and defining a structure called CwAcwPair, as shown below. typedef struct { int cWise; int acWise; } CwAcwPair; MaxP Current P ix el 0 cWise P ix el 1 2 1 3 2 4 3 0 4 acWise P ix el 4 0 1 2 3 PixelPtrs array Remember that we introduced typedef (type defi- Fig.4. Using a look-up table. nitions), enum (enumerated types), and struct (structures) in an earlier Tips and Tricks (PE, December 2020). Next, we are going to declare our look-up table as an array of these structures as follows: CwAcwPair PixelPtrs[NUM_P]; Last, we will add a for() loop to our setup() function to load the cWise and acWise values into our PixelPtrs[] array as follows: Practical Electronics | March | 2021 for (int iPix = 0; iPix < NUM_P; iPix++) { PixelPtrs[iPix].cWise = (iPix + 1) % NUM_P; PixelPtrs[iPix].acWise = (iPix + MAX_P) % NUM_P; } and modify it to use this new technique (file CB-Mar21-11.txt). As a result, our main loop() function will now be as follows: void loop () { uint32_t currentTime = millis(); Observe that we are using exactly the same modulo-based operations to load this table that we used in our previous sketches. Based on this, we can rewrite our main loop() function as follows: // Is it time to do something? if ( (currentTime - TimeOfLastChange) >= PadDelay) { // Turn old pixel off Neos.setPixelColor(CurrentP, COLOR_OFF); void loop () { // Turn old pixel off Neos.setPixelColor(CurrentP, COLOR_OFF); // Update the pointer CurrentP = PixelPtrs[CurrentP].cWise; // Update the pointer CurrentP = PixelPtrs[CurrentP].cWise; // Turn new pixel on Neos.setPixelColor(CurrentP, COLOR_ON); // Turn new pixel on Neos.setPixelColor(CurrentP, COLOR_ON); // Display the result Neos.show(); // Display the result Neos.show(); delay(PadDelay); TimeOfLastChange = currentTime; } } } How about if we wish to modify this latest version of our program to perform an anticlockwise rotation? Nothing could be simpler. All we have to do is modify the pointer update statement in our main loop() to read as follows (file CB-Mar21-10.txt): And, as before, if we wish to modify this latest version of our program to perform an anticlockwise rotation, then all we have to do is modify the pointer update statement in our main loop() to read as follows (file CB-Mar21-12.txt): CurrentP = PixelPtrs[CurrentP].acWise; One disadvantage of using a table as we are doing here is that it consumes memory space, but so does any code we use to perform the same calculations multiple times. The advantages of the table approach are that we only perform our calculations one time and that the body of the program is easier to understand. Now, you might say that, as part of our ‘Chickens and eggs’ discussions, we’ve already seen how to boil things down to a single calculation, but that’s true only if we wish to turn an individual pixel off and another pixel on. If we were to opt for a more sophisticated effect, like a trailing fade that involves lighting the new pixel at 100% brightness and having the three trailing pixels set to 75%, 50%, and 25%, respectively, then we would find that this table-based approach makes our lives a lot easier. Ah, delay(), I knew you well In my column, Is Time Truly an Illusion? (https://bit.ly/38mwa3k), I posed the question: ‘Did time exist before the Big Bang, was time an emergent property of the Big Bang, is time just something that keeps everything from happening at once, or does time as a fundamental property simply not exist at all?’ The reason I mention this here is that all of the programs we’ve looked at thus far have employed the delay() function to keep everything from happening at once. However, as we discussed in the Dump the Delay topic in a previous Cool Beans (PE, December 2020), the delay() is a blocking function, which means it completely ties up the processor, thereby preventing (or blocking) anything else from happening. While the processor is executing a delay(), it can’t respond to changes on any of its inputs, it can’t perform any calculations or make any decisions, and it can’t change the state of any of its outputs. One technique to replace the delay() is to cycle around checking the system clock to find when it’s time to act. What we are going to do is take our table-based clockwise rotation program (CB-Mar21-09.txt) Practical Electronics | March | 2021 CurrentP = PixelPtrs[CurrentP].acWise; And the winner is… So, out of all of the techniques we’ve shown above – and remembering that there’s more to come in this month’s Tips and Tricks – which one is the best? Well, that’s a bit like asking ‘How long is a piece of string?’ In reality, it all depends on what we are attempting to achieve. If all we want to do is turn one pixel off and another one on, or vice versa, then we may opt for the solutions presented in the Chickens and eggs section. If we wish to add some slightly more sophisticated effects, then the programs provided in the Tortuous tables topic might offer the best option. Alternatively, if we want to be performing additional tasks while flashing our pixels – like checking the states of switches and performing complex calculations, for example – then the solutions offered in the Ah, delay(), I knew you well section would probably be the way to go. What? No GOL? I feel like an old fool (but where are we going to find one at this time of the day?). For the past two columns I’ve been promising to implement a version of Conway’s Game of Life (GOL) (https://bit.ly/pe-jan21-cgol) on my 12x12 ping-pong-ball array. Almost unbelievably, however, I allowed myself to become sidetracked once again (I’m as shocked as you). But dry those tears and start flaunting your happy face once again. Everything we’ve covered in this month’s column regarding the use of the modulo operator is going to come into play when we implement the GOL, which we will do next month or my name’s not Max the Magnificent! Until that frabjous day, as always, I welcome your comments, questions, and suggestions. Cool bean Max Maxfield (Hawaiian shirt, on the right) is emperor of all he surveys at CliveMaxfield.com – the go-to site for the latest and greatest in technological geekdom. Comments or questions? Email Max at: max<at>CliveMaxfield.com 61