Silicon ChipMax’s Cool Beans - December 2020 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: Clever Controller for a Dumb Battery Charger by JOHN CLARKE
  11. Project: LFSR Random Number Generator Using Logic ICs by Tim Blythman
  12. Project: HIGH-POWER 45V/8A VARIABLE LINEAR SUPPLY by Tim Blythman
  13. Feature: Building a Hi-Fi amp on the cheap by Julian Edgar
  14. Feature: AUDIO OUT by Jake Rothman
  15. Feature: Make it with Micromite by Phil Boyce
  16. Feature: Circuit Surgery by IAN BELL
  17. Feature: Max’s Cool Beans by Max the Magnificent
  18. PCB Order Form: Max’s Cool Beans by Max the Magnificent
  19. Advertising Index: Electronic Building Blocks by Julian Edgar

This is only a preview of the December 2020 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 W Flashing LEDs and drooling engineers – Part 10 ell hello there. I hope you’re having as awesome a day when you read this as I’m having while I write it. Just to make sure we are all tap-dancing to the same ‘skirl of the pipes*,’ let’s briefly remind ourselves that we are currently playing with a 12x12 array of ping-pong balls, each containing a WS2812-based tricolour LED (*I know whereof I speak, because my dear old dad was a dancer on the variety hall stage prior to WWII, and he was in the Reconnaissance Unit of the 15th Scottish Infantry Division during WWII, as part of which he earned many beers performing Scottish sword dances to the sound of the bagpipes). For the past few columns, we’ve been experimenting with ‘virtual drips’ randomly falling on, and lighting up, pixels in our array. At the end of our previous column (PE, November 2020), we noted that – up until now – we’ve worked with only a single drip at a time (Fig.1a). We also conjectured that it would be more exciting if we were to allow multiple drips to be active concurrently, and for their start and end times to be randomly determined such that they overlap in interesting and unpredictable ways (Fig.1b). Like most things, of course, implementing a cornucopia of contemporaneous drips sounds easy if you say it quickly and gesticulate furiously. Sad to say, however, the underlying way in which we’ve been implementing things in our code thus far will prove to be rather limiting. But turn that frown upside down and into a smile, because we won’t let anything prevent us from achieving our multi-drip extravaganza, or my name isn’t Max the Magnificent. In a bit of a state Consider the following interpretation of the main loop() function used in the Arduino’s classic ‘Blink’ sketch (program). Let’s assume we are using this program to control a yellow LED. In this particular example, we are cycling around turning the LED on and off at a frequency of 1Hz (one cycle per second). void loop() { digitalWrite(PinLed, LOW); delay(500); digitalWrite(PinLed, HIGH); delay(500); } The term ‘finite-state machine’ (FSM), or simply ‘state machine’, refers to a mathematical model of computation. The underlying idea is that we have an abstract machine that can be in only one of a finite number of states at any particular time. The reason I mention this here is that the code presented above might be considered to implement a rudimentary state machine, whose operation we could depict graphically as illustrated in Fig.2a. Now, suppose we decide to add a red LED, and have the two LEDs turning on and off at different rates. Let’s say the red LED has a frequency of 1Hz, while the yellow LED has a frequency of 2Hz. The main loop() code for this could be as follows, with a graphical equivalent as depicted in Fig.2b (the full sketch is 58 presented in file CB-Dec20-01.txt – it and the other files associated with this article, are available on the December 2020 page of the PE website). void loop () { // State 0 digitalWrite(PinRedLed, digitalWrite(PinYellowLed, delay(250); LOW); LOW); // State 1 digitalWrite(PinRedLed, LOW); digitalWrite(PinYellowLed, HIGH); delay(250); // State 2 digitalWrite(PinRedLed, HIGH); digitalWrite(PinYellowLed, LOW); delay(250); // State 3 digitalWrite(PinRedLed, HIGH); digitalWrite(PinYellowLed, HIGH); delay(250); } In a classic FSM, we would have some way to remember the current context (state) of the machine. This could be a register containing the state variables in the case of a hardware implementation, or an enumerated type in the case of a software realisation (see this month’s Tips and Tricks column for more information on enumerated types). By comparison, when it comes to our example code shown above, apart from using comments, we don’t have any way to explicitly define the current state. Instead, the state is implied by where we are in the code. In order to illustrate why this is a problem, let’s suppose I were to ask you to add a green LED with a frequency of 3Hz into the mix. Take a moment to think about how you would implement the ( a) One drip at a tim e ( b) M ultiple drips at the sam e tim e Fig.1. Single versus multiple drips. Practical Electronics | December | 2020 code for this. I can imagine you smiling because, even though you know that everything is so intertwined it will undeniably make things trickier, you are sure that – if push came to shove – you could do this. How about if, instead of simply turning the three LEDs on and off, I ask you to fade them on, hold them steady, and fade them off, with each fade taking 10 steps over 100 milliseconds (ms). You aren’t smiling now, are you? When we come to think about it, this is pretty much where we are with our existing drip programs. Although it’s true that we’ve implemented some very tasty fading effects using different colours, we’ve only achieved this with one drip at a time. We’re going to have to adopt a new approach if we wish to have multiple drips active concurrently in random relationships to each other. milliseconds that have passed since the Arduino powered up and the program started running. void loop () { uint32_t currentTime = millis(); if ( (currentTime - LastTimeRedLedChanged) >= OnOffDelayRedLed ) { if (StateRedLed == LED_OFF) { StateRedLed = LED_ON; } else { StateRedLed = LED_OFF; } Dump the delay()! The delay() function shown in the code examples above 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. The bottom line is that, in order to achieve multiple drips, we need to dump the delay() and implement our code using some other approach. One technique we can employ is to cycle around checking the system clock to determine when it’s time to act. Let’s look at a simple example of this in action. What we are going to do is create a new version of our 2-LED program using this new method. As you will see if you look at the code (file CB-Dec20-02.txt), we start by defining LED_OFF and LED_ON as LOW and HIGH, respectively. We also declare two global variables StateRedLed and StateYellowLed to hold the current states (LED_OFF or LED_ON) of their respective LEDs. For the purposes of these examples, each LED has a 1:1 markspace ratio, which means it’s on for the same amount of time as it’s off. Since we wish the red LED to have a frequency of 1Hz, which equates to a period of 1,000ms, this means it will alternate between being on for 500ms and off for 500ms. Similarly, as we wish the yellow LED to have a frequency of 2Hz, which equates to a period of 500ms, this means it will alternate between being on for 250ms and off for 250ms. All of this explains why we declare a global variable called OnOffDelayRedLed, which we set to 500ms, and a global variable called OnOffDelayYellowLed, which we set to 250ms. Furthermore, we also declare two global variables LastTimeRedLedChanged and LastTimeYellowLedChanged, which – as their names suggest – we will use to keep track of the S tate S tate last time their asL ed = Off L ed = On 0 1 sociated LEDs changed state. S 0 S 1 S 0 S 1 S 0 The code for L ed the first half of ( a) S im ple 2 - state F S M the main loop is shown below. We start by loadL ed1 = Off S tate L ed1 = Off S tate 0 1 L ed2 = Off L ed2 = On ing the local variable currentT i m e with the L ed1 = On S tate L ed1 = On S tate value returned 3 2 L ed2 = On L ed2 = Off from the Arduino’s millis() S 0 S 1 S 2 S 3 S 0 S 1 S 2 S 3 S 0 S 1 S 2 L ed1 function, which L ed2 will be a 32-bit unsigned inte( b) S im ple 4 - state F S M ger representing the number of Fig.2. Simple state machines Practical Electronics | December | 2020 digitalWrite(PinRedLed, StateRedLed); LastTimeRedLedChanged = currentTime; } // More code goes here } Next, we perform a test to see if the current time minus the last time the red LED changed is greater than or equal to the red LED’s on/off delay, which we previously set to 500ms. If not, we don’t do anything. However, if it has been 500ms or more since the red LED changed, we flip its state (from off to on, or vice versa), then we write this new state to the pin driving the red LED and we reset the variable storing the last time this LED changed state to be the current time. Your first reaction may be to scream ‘Arrgggh!’ Your second reaction may be to say in menacing tones, ‘Forgive me for saying so, but this appears to be a tad more complicated than simply using calls to the delay() function.’ Well, yes and no. Although this takes a little more effort to set up, it makes our lives a lot easier in the long run. For example, the code to handle the yellow LED (which will appear where we show the ‘// More code goes here’ comment) is simply a modified copy of the if () statement we used to handle the red LED. Similarly, if we decided to add a green LED with a frequency of 3Hz, all we would need to do would be to add StateGreenLed, OnOffDelayGreenLed, and LastTimeGreenLedChanged global variables and also add a new if () statement into our main loop. Trust me – the more you think about this, the easier it gets. My register floweth over Earlier, we noted that the Arduino’s millis() function returns a 32-bit unsigned integer representing the number of milliseconds that have passed since the Arduino powered up and the program started running. This value is stored in a 32-bit counter/timer register buried deep in the Arduino’s internal architecture. One question you were doubtless asking yourself is, ‘What happens when this register overflows?’ By this we mean that when we power up the Arduino, this register contains 0 (or 0x00000000 in hexadecimal). If we keep on incrementing this register every millisecond, then it will eventually contain 232 = 4,294,967,296 (or 0xFFFFFFFF in hexadecimal). How long will this take and what happens next? Well, since the register increments every millisecond (one thousandth of a second), we can divide 4,294,967,296 by 1,000 to get seconds, then divide by 60 to get minutes, and by 60 again to get hours, and by 24 to get days. By this, we discover that it will take close to 50 days before the register fills up. 59 N o drip D rip fades on DRIP_WAITING says that we’ve scheduled this drip to commence at some time in the future, and DRIP_RISING, DRIP_SUSTAINING, and DRIP_FALLING govern the pixel fading up, holding, and fading away again, respectively. Next, we declare a structure called Pixel, which contains all of the attributes we wish to associate with each of our pixels: D rip fades off ( a) R udim entary drip effec t N o drip D rip fades on S plash fades on D rip fades off S plash fades off typedef struct Pixel { PixelState currentState; uint32_t waterColor; uint32_t oldColor; uint32_t newColor; int numSteps; int currentStep; }; ( b) A ugm enting eac h drip with an assoc iated splash Fig.3. Rudimentary drip effect compared to a ‘drip plus splash’ effect. Once the register contains 4,294,967,296 (0xFFFFFFFF), the next tick of the millisecond clock will cause it to overflow and return to containing 0 (0x00000000), and we start all over again. So, when we pass through this wraparound case, what will happen to our test (currentTime - LastTimeRedLedChanged) >= OnOffDelayRedLed)? Might we see a glitch or something worse? On the one hand, it’s unlikely that we will be running our drip program for 50 days or more at a stretch. Also, the world wouldn’t end if there were a glitch in an application of this ilk. On the other hand, suppose we wished to use a similar technique to control a safety-critical or mission-critical system in which any form of glitch, no matter how slight, would not be considered to be a good thing to occur? Well, due to the magic of binary numbers and operations, our code will happily continue to perform its task of flashing the LEDs without any change in delay or any other disruption, even when the millis() register overflows back to 0. The reasoning behind all this takes a bit of time to digest and we don’t want to delve into it here. Happily, I wrote two columns some time ago that discuss all of this in excruciating detail (https://bit. ly/3cPSSBo and https://bit.ly/2GrlNQ2). A deluge of drips Our first incarnation of a multi-drip program just focuses on the drips themselves. There’s quite a lot to this, so I really do advise you to download the text version of this program and print it out so you can follow along (file CB-Dec20-03.txt). When you peruse this program, you will see many familiar faces in the form of the little utility functions we created in earlier drip sketches, such as GetNeoNum(), CrossFadeColor(), BuildColor(), GetRed(), GetGreen(), and GetBlue(). In fact, apart from these functions and our setup() and loop() functions, we have only two other functions: StartNewDrip() and ProcessDrips(). Before we look at these new functions in a little more depth, there are some new constructs and definitions we need to consider in the form of typedef (type definitions), enum (enumerated types), and struct (structures). The nitty-gritty of these constructs is explored in more depth in this month’s Tips and Tricks column. For our purposes here, all we need to know is that we’ve declared an enumerated type called PixelState as follows: typedef enum PixelState { NONE, DRIP_WAITING, DRIP_RISING, DRIP_SUSTAINING, DRIP_FALLING }; These are the states that we are going to associate with each of our pixels: NONE says that this pixel is currently inactive, 60 Observe that the first of these attributes is the state of the pixel. We will commence with all of the pixels having a state of NONE, where these values are assigned as part of our setup() function. There are many different ways in which we might decide to implement our program. One realisation might involve including a lastTimeLedChanged field in our Pixel structure (similar in concept to the way in which we implemented our 2-LED program earlier in this column). As we will see, however, I decided to adopt a slightly different approach. The final piece of this portion of the puzzle is where we declare an array called Pixels[][] of our Pixel structure, as shown below: Pixel Pixels[NUM_COLS][NUM_ROWS]; Although it may take a bit of effort to wrap our brains around all this, it’s really not as bad as it seems. If we look at things in reverse order, we have an array called Pixels[][] that contains the data associated with each our pixels. This data includes things like the state of the pixel, the colour associated with the pixel, and so on. As we see below, the loop() function is actually simpler than the one we employed in our 2-LED program: void loop () { uint32_t currentTime = millis(); if ( (currentTime - LastTickTime) > TICK) { StartNewDrip(); ProcessDrips(); Neos.show(); LastTickTime = currentTime; } } As you may recall from previous programs, we are using a master clock whose TICK is set to 10ms. This means that every ten milliseconds we call our StartNewDrip() function followed by our ProcessDrips() function, after which we display the current values of our pixels and update the variable storing the current time. If you look at the code, you will see that the StartNewDrip() function doesn’t always initiate a new drip. We have a global variable NumActiveDrips, which stores the number of active drips, and we have a constant NUM_MAX_DRIPS, which defines the maximum number of drips that can be active at any Practical Electronics | December | 2020 particular time. Our StartNewDrip() function will only initiate a new drip if we aren’t already fully loaded and – even then – it will schedule the new drip to commence at some random time in the future. Meanwhile, the cunning way in which we’ve architected the ProcessDrip() function means that every stage of the drip is implemented in the same way, fading from one colour to another over a series of steps. When we are waiting for a drip to drop, for example, we spend our time fading from black to black, which – not surprisingly – ends up looking like black. When we fade a pixel up, we fade from black to the randomly selected colour for that pixel. When we hold a pixel in its current colour, we actually fade from that colour to itself. And when we fade a pixel down, we fade from its randomly selected colour back to black. We aren’t going to examine this code in any more detail here. Suffice to say, we can feast our eyes on all of this in action in a video I just captured (https://bit.ly/33ufl3V). Galoshes on! pixels at the outside edges of the array, thereby relieving us of having to perform any jiggery-pokery with regard to any splash pixels that might otherwise appear outside of the array. The next change is that we’ve added some additional states to our PixelState enumerated type (the new states are shown in bold): typedef enum PixelState { NONE, DRIP_WAITING, DRIP_RISING, DRIP_SUSTAINING, DRIP_FALLING, SPLASH_WAITING, SPLASH_RISING, SPLASH_SUSTAINING, SPLASH_FALLING }; We’ve also added a FadeColor() function that we use to take Thus far, we’ve been experiencing only rudimentary drip effects the main drip colour and fade it down to a specified percentage (Fig.3a). The final step on our trek through driptopia, the land of of its original value – this muted version is what we use for our drips – at least for the moment – is to add the concept of a splash splash pixels. (Fig.3b). The idea here is that shortly after a primary drip drops, a Last but not least, we’ve modified the StartNewDrip() funcmuted version of the drip colour will appear in the pixels to the tion to also launch any associated splash pixels, and we’ve augnorth, south, east, and west. These muted versions will persist mented the ProcessDrips() function to display these splash for a short time after the primary drip fades, after which they too pixels. As you will see, the cunning way in which we architectwill fade away (‘All we are is drips in the wind,’ as the progresed the original (pre-splash) version of our program means that sive rock band Kansas might have sung). adding the splash effect is really not as difficult as you might Once again, I strongly advise you to download the text verhave supposed. Once again, we can feast our orbs on all of this sion of this program and print it out so you can follow along in action in a video I just captured (https://bit.ly/3ljkcer). (file CB-Dec20-04.txt). The first change to the previous program is that we’ve modified our MIN_XY and Cool bean Max Maxfield (Hawaiian shirt, on the right) is emperor MAX_XY definitions from 0 to 1 and 11 of all he surveys at CliveMaxfield.com – the go-to site for the to 10, respectively. We did this to ensure latest and greatest in technological geekdom. that our StartNewDrip() function won’t Comments or questions? Email Max at: max<at>CliveMaxfield.com launch any primary drips in any of the Max’s Cool Beans cunning coding tips and tricks I n this month’s main Cool Beans column, we employed some new concepts in the form of typedef (type definitions), enum (enumerated types), and struct (structures). Let’s look at these in a little more detail. Enumerated types (enum) currentState = NONE; And we can perform tests like: if we wish to implement a finite-state machine (FSM), we will need some way to store its current context (state). One way to do this would be to identify a set of states and associate them with numbers using a set of #define statements: #define NONE #define DRIP_WAITING #define DRIP_RISING #define DRIP_SUSTAINING #define DRIP_FALLING Later, we might declare a variable called currentState as being of type int (integer), after which we can perform assignments like: 0 1 2 3 4 Practical Electronics | December | 2020 if (currentState == NONE) { // More stuff goes here } This technique is fine and it’s not difficult to add more states. However, if you are anything like me, you can easily end up spending a lot of time reorganising things and changing the numbers associated with different states because you want things to be ‘just so.’ 61 The enum keyword allows us to create a user-defined type comprising a set of named constants called enumerators: myFavoritePixel.currentState = NONE; myArrayOfPixels[6].currentState = NONE; enum PixelState { NONE, DRIP_WAITING, DRIP_RISING, DRIP_SUSTAINING, DRIP_FALLING }; Observe that when we are dealing with an array, as in the second example, we also have to provide an integer index to specify which element of the array we are talking about (element 6 in this example). Observe that no comma is required after the final enumerator, but a semicolon is required after the ‘}’ (that is, the closing curly bracket). By default, the enumerators are assigned integer values by the compiler starting with 0. This means that, in the above example, NONE will be assigned a value of 0, DRIP_WAITING will be assigned a value of 1, and so forth. It’s also possible for us to assign our own values. It’s even possible for multiple enumerators to be assigned the same value, but that’s beyond the scope of our discussions here. Once we’ve defined an enum, we can declare one or more variables of this enum type: Type definitions (typedef) The typedef keyword is used to assign alternative names to existing data types. If we really dislike the int keyword, for example, we could use the following statement, where int is the existing data type name and simon is the alias: typedef int simon; After this, we can declare new variables with a data type of simon if we wish. Obviously, this particular example is a tad nonsensical, but using typedef with existing data types can be useful on occasion. Where the typedef keyword really comes into its own is when it’s used in conjunction with user-defined enum and struct statements. Let’s start by using a typedef in conjunction with an enum: enum PixelState oldState = NONE; enum PixelState newState = DRIP_RISING; typedef enum PixelState { NONE, DRIP_WAITING, DRIP_RISING, DRIP_SUSTAINING, DRIP_FALLING }; Elsewhere in our program, we can assign new values to these variables as we wish: Now, when we come to declare one or more variables of this enum type, we can simply say something like: oldState = DRIP_RISING; newState = DRIP_SUSTAINING; PixelState oldState; PixelState newState; Structures (struct) The struct keyword is used to define a collection of data items, each of which may have its own type: Compare this to our earlier example where we had to reuse the enum keyword. Next, let’s use a typedef in conjunction with a struct: struct Pixel { PixelState currentState; uint32_t waterColor; uint32_t oldColor; uint32_t newColor; int numSteps; int currentStep; }; typedef struct Pixel { PixelState currentState; uint32_t waterColor; uint32_t oldColor; uint32_t newColor; int numSteps; int currentStep; }; Observe that semicolons are required both after the final field and after the closing curly bracket. Once we’ve defined a struct, we can declare one or more variables of this struct type, where these variables may be scalar values or arrays: Now, when we come to declare one or more variables of this struct type, we can simply say something like: enum PixelState oldState; enum PixelState newState; Also, we can assign values as part of the declaration; for example: struct Pixel myFavoritePixel; struct Pixel myArrayOfPixels[100]; Observe that the second example declares an array with 100 elements numbered from 0 to 99. In the case of our multidrip programs, we actually declared multi-dimensional arrays of these structures, because that’s just the sort of guys and gals we are. Unlike arrays, the individual fields (items) in a struct are accessed by name instead of using an integer index: 62 Pixel myFavoritePixel; Pixel myArrayOfPixels[100]; Compare this to our earlier example where we had to reuse the struct keyword. But wait, there’s more... As always, we’ve really only scratched the surface with regard to the way in which the enum, struct, and typedef keywords can be combined and deployed, but I think we can all bask in the glow of knowing that we now know enough to be just a little bit dangerous. Practical Electronics | December | 2020