Silicon ChipMax’s Cool Beans - December 2025 SILICON CHIP
  1. Contents
  2. Publisher's Letter: The lost art of backward compatibility
  3. Feature: Teach-In 2026 by Mike Tooley
  4. Subscriptions
  5. Project: Variable Speed Drive Mk2 for Induction Motors, Part 1 by Andrew Levido
  6. Feature: Audio Out by Jake Rothman
  7. Feature: Techno Talk by Max the Magnificent
  8. Feature: Max’s Cool Beans by Max the Magnificent
  9. Feature: The Fox Report by Barry Fox
  10. Feature: Circuit Surgery by Ian Bell
  11. Project: Digital Capacitance Meter by Stephen Denholm
  12. Feature: Net Work by Alan Winstanley
  13. Back Issues
  14. Project: Battery-Powered Model Train by Les Kerr
  15. PartShop
  16. Market Centre
  17. Advertising Index
  18. Back Issues

This is only a preview of the December 2025 issue of Practical Electronics.

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

Articles in this series:
  • Teach-In 12.1 (November 2025)
  • Teach-In 2026 (December 2025)
  • Teach-In 2026 (January 2026)
  • Teach-In 2026 (February 2026)
Items relevant to "Variable Speed Drive Mk2 for Induction Motors, Part 1":
  • Mk2 VSD PCB [11111241 or 9048-02] (AUD $15.00)
  • STM32G030K6T6 programmed for the VSD Mk2 [1111124A] (Programmed Microcontroller, AUD $10.00)
  • Firmware for the VSD Mk2 (Software, Free)
  • VSD Mk2 PCB pattern (PDF download) [11111241] (Free)
  • Mk2 VSD drilling & cutting diagrams (Panel Artwork, Free)
Articles in this series:
  • Variable Speed Drive Mk2, Part 1 (November 2024)
  • Variable Speed Drive Mk2, Part 2 (December 2024)
  • Variable Speed Drive Mk2 for Induction Motors, Part 1 (December 2025)
  • Variable Speed Drive Mk2 For Induction Motors, Part 2 (January 2026)
Articles in this series:
  • Audio Out (January 2024)
  • Audio Out (February 2024)
  • AUDIO OUT (April 2024)
  • Audio Out (May 2024)
  • Audio Out (June 2024)
  • Audio Out (July 2024)
  • Audio Out (August 2024)
  • Audio Out (September 2024)
  • Audio Out (October 2024)
  • Audio Out (March 2025)
  • Audio Out (April 2025)
  • Audio Out (May 2025)
  • Audio Out (June 2025)
  • Audio Out (July 2025)
  • Audio Out (August 2025)
  • Audio Out (September 2025)
  • Audio Out (October 2025)
  • Audio Out (November 2025)
  • Audio Out (December 2025)
  • Audio Out (January 2026)
  • Audio Out (February 2026)
Articles in this series:
  • Techno Talk (February 2020)
  • Techno Talk (March 2020)
  • (April 2020)
  • Techno Talk (May 2020)
  • Techno Talk (June 2020)
  • Techno Talk (July 2020)
  • Techno Talk (August 2020)
  • Techno Talk (September 2020)
  • Techno Talk (October 2020)
  • (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)
  • Techno Talk (July 2025)
  • Techno Talk (August 2025)
  • Techno Talk (October 2025)
  • Techno Talk (November 2025)
  • Techno Talk (December 2025)
  • Techno Talk (January 2026)
  • Techno Talk (February 2026)
Articles in this series:
  • Max’s Cool Beans (January 2025)
  • Max’s Cool Beans (February 2025)
  • Max’s Cool Beans (March 2025)
  • Max’s Cool Beans (April 2025)
  • Max’s Cool Beans (May 2025)
  • Max’s Cool Beans (June 2025)
  • Max’s Cool Beans (July 2025)
  • Max’s Cool Beans (August 2025)
  • Max’s Cool Beans (September 2025)
  • Max’s Cool Beans: Weird & Wonderful Arduino Projects (October 2025)
  • Max’s Cool Beans (November 2025)
  • Max’s Cool Beans (December 2025)
  • Max’s Cool Beans (January 2026)
  • Max’s Cool Beans (February 2026)
Articles in this series:
  • The Fox Report (July 2024)
  • The Fox Report (September 2024)
  • The Fox Report (October 2024)
  • The Fox Report (November 2024)
  • The Fox Report (December 2024)
  • The Fox Report (January 2025)
  • The Fox Report (February 2025)
  • The Fox Report (March 2025)
  • The Fox Report (April 2025)
  • The Fox Report (May 2025)
  • The Fox Report (July 2025)
  • The Fox Report (August 2025)
  • The Fox Report (September 2025)
  • The Fox Report (October 2025)
  • The Fox Report (October 2025)
  • The Fox Report (December 2025)
  • The Fox Report (January 2026)
  • The Fox Report (February 2026)
Articles in this series:
  • STEWART OF READING (April 2024)
  • Circuit Surgery (April 2024)
  • Circuit Surgery (May 2024)
  • Circuit Surgery (June 2024)
  • Circuit Surgery (July 2024)
  • Circuit Surgery (August 2024)
  • Circuit Surgery (September 2024)
  • Circuit Surgery (October 2024)
  • Circuit Surgery (November 2024)
  • Circuit Surgery (December 2024)
  • Circuit Surgery (January 2025)
  • Circuit Surgery (February 2025)
  • Circuit Surgery (March 2025)
  • Circuit Surgery (April 2025)
  • Circuit Surgery (May 2025)
  • Circuit Surgery (June 2025)
  • Circuit Surgery (July 2025)
  • Circuit Surgery (August 2025)
  • Circuit Surgery (September 2025)
  • Circuit Surgery (October 2025)
  • Circuit Surgery (November 2025)
  • Circuit Surgery (December 2025)
  • Circuit Surgery (January 2026)
  • Circuit Surgery (February 2026)
Articles in this series:
  • Win a Microchip Explorer 8 Development Kit (April 2024)
  • Net Work (May 2024)
  • Net Work (June 2024)
  • Net Work (July 2024)
  • Net Work (August 2024)
  • Net Work (September 2024)
  • Net Work (October 2024)
  • Net Work (November 2024)
  • Net Work (December 2024)
  • Net Work (January 2025)
  • Net Work (February 2025)
  • Net Work (March 2025)
  • Net Work (April 2025)
  • Net Work (September 2025)
  • Net Work (November 2025)
  • Net Work (December 2025)
Max’s Cool Beans By Max the Magnificent Weird & Wonderful Arduino Projects Part 12: storing, updating and displaying scores R ather than wiffle-waffle, as is my wont, how about we simply plunge headfirst into the fray with gusto and abandon (always with aplomb, of course)? You may recall that we are using 7-segment display modules as part of the retro games console we are constructing. These modules are available with two, three or four digits. I’ve decided to use three two-digit modules for my console. You may opt for an alternative arrangement if you wish, but I’ll assume you are working with the same setup as me for the purposes of these discussions (I’ll note areas you might need to tweak to work with an alternative setup). In our previous column, we plugged one of our two-digit modules into As a result, when we loaded a value into our shift registers—the one on the breadboard and two more on the module—we observed the results depicted in Fig.1(a). The term “Cycle” in this context refers to us loading values into our shift registers in 8-bit ‘chunks’. Also, the digits on the modules are connected from left-to-right. Thus, the left-hand digit on the module corresponds to the value on the 8-segment display, with the right-hand digit lagging by one cycle. You can see a full layout for the existing breadboard setup in the file named CB-dec25-brd-01.pdf. As usual, all files mentioned in this column are available as part of the December 2025 download package from the PE website at https://pemag.au/link/ac8p our breadboard. These modules feature the same 74HC595 8-bit serialin, parallel-out (SIPO) shift registers that we’ve been using in our prototype experiments. One slight ‘gotcha’ we encountered is that I had assumed the 7-segment displays on our modules would be of the common cathode (CC) variety, in which case 0s would turn segments off while 1s would turn them on. I should have known better than to assume anything, because we quickly discovered that my modules boast common-anode (CA) displays. This means that it’s the 0s that turn our segments on, while the 1s turn them off. Although this issue can be easily addressed in software, it’s unfortunate for several reasons. For example, our 8-segment bar graph display is currently configured in a pseudo-CC (b) Pseudo-CA mode, with the shift register driving its anode terminals and all of its cathodes connected (via pull-down resistors) to one of the 0V rails. (a) Pseudo-CC Cleared Cycle 1 Don’t worry, be happy I know that it shouldn’t bother me that an active light-emitting diode (LED) segment on the 8-segment display corresponds to an inactive segment on the Rotate 180° OE SRCLR Move from 0V to 5V Cycle 2 Cycle 3 Cycle 4 74HC595 Cycle 5 Cycle 6 Cycle 7 Cycle 8 From Arduino : : : : Fig.1: pseudo-CC vs pseudo-CA LED bargraphs. 38 Pin 13 = SRCLK Pin 12 = RCLK Pin 11 = SER Fig.2: changing the 8-segment display to a pseudo-CA configuration. Practical Electronics | December | 2025 AREF GND 13 12 ~11 ~10 ~9 8 7 ~6 ~5 4 ~3 2 TX-1 RX-0 L USB DIGITAL IN/OUT (PWM ~) Orange LED TX RX Green LED 7-segment display, and vice versa… but it does. Things like this tend to niggle away at (what remains of) my mind. I have therefore rewired my breadboard so that my 8-segment display is now configured in a pseudo-CA mode. All that is required is to rotate the display by 180°, which means the shift register is now driving its cathode terminals. This also brings the display’s anode terminals into contact with the resistors. Furthermore, we now connect the other ends of the resistors to the +5V rail (thereby modifying their function from acting as pull-down resistors to playing the role of pull-up devices). This new setup is illustrated in Fig.2 (the full layout is available in the file named CB-dec25-brd-02.pdf). Now, when we rerun our latest and greatest program from last month (which I’ve made available in a file named CB-dec25-code-01.txt), we see the results depicted in Fig.1(b). I don’t know about you, but this makes me feel much happier inside. POWER ANALOG IN 3.3V 5V GND GND Vin A0 A1 A2 A3 A4 A5 RESET Power Jack IOREF Serial Comms LEDs Fig.3: the four onboard LEDs on an Arduino Uno. Even though the IDE prints messages like “Compiling” and “Uploading”, the flashing L, RX and TX LEDs are real, physical indicators. They provide tangible reassurance that something is really happening. Flashing LEDs are crude but surprisingly effective diagnostic tools. If the L, RX and TX remain dark when we expect activity, we know something is wrong before the IDE times out. There’s also an aspect of ‘trust but verify’. The text messages in the IDE provide one layer of confirmation, while LEDs offer a second, independent channel. Why am I waffling on about all this here? Well, returning to Fig.2, we observe that the SER, RCLK and SRCLK signals to the shift registers are driven by Arduino pins 11, 12, and 13, respectively. The reason I used pin 13 to drive SRCLK is that this signal will toggle the most when we are loading new values into our 7-segment display modules. Since pin 13 is also connected to the L LED on the Arduino, it provides a palpable indication that something is happening. In reality, things happen so fast in our current code that we can’t perceive anything. I added a new definition called STILL_KICKING, assigning a value of 1, which will be used to add a 1ms (one millisecond) delay to each pulse of the SRCLK. I then added this delay to our LoadSR() (“load shift register”) function, as seen on line 75 in Listing 2(a). Feel free to download the new version of our program (the file named CB-dec25-code-02.txt). Remember, we’re using the convention that the listing number (2 in this case) corresponds to the numerical part of the matching code file name (“02” here). We might change the way we’ve implemented our blinkenlight in the future (or remove it completely) but I’m really enjoying its company for the moment. The Arduino Uno has four onboard LEDs (Fig.3). Thus far, we’ve only mentioned two of them: the power LED (usually green), which is lit whenever power is applied to the board, and the orange (or yellow) LED with the L annotation that’s connected via an onboard resistor to digital input/output (I/O) pin 13. As we’ve seen, we can control the L LED with our programs. The first thing we should do when connecting the USB cable, or otherwise powering the board, is to check that the green LED is glowing. If not, there’s no use proceeding further until this problem is resolved. If you look closely at the two remaining orange (or yellow) LEDs that are mounted close to the USB connector, you’ll see that they have RX and TX annotations next to them. Have you noticed that when you upload a program (sketch) to your Arduino Uno, the L LED flashes first, followed by the RX and TX LEDs flashing repeatedly? What’s happening here? Well, the bootloader (see the panel on page 44) first flashes the L LED as a simple status indicator to show that it’s alive and functioning. It then flashes the RX and TX LEDs to indicate that serial communication is taking place between your computer and the Arduino. Your main computer may be referred to as the ‘host’ because it initiates the communication and controls the process. The use of this in computing (and networking) is derived from the social sense of host; that is, the person who invites guests, provides resources (food, drink, space), and coordinates the event. The device with which the host is communicating (the Arduino in this case) may be referred to as the ‘target’. Just like what your dinner host calls you (I must confess that my dinner parties are a bit odd). Anyway, the RX LED blinks when the Arduino is receiving data from your computer, while the TX LED blinks when the Arduino is transmitting data to your computer. During an upload, both of these LEDs will usu- Staying in character Thus far, we’ve illuminated only ally flash rapidly. Assuming there aren’t any errors in one segment at a time. The next step our code, when we click the “Upload” will be to light up multiple segments icon on the Arduino’s IDE, it will report a 68 // Load 8-bit value into the shift register series of four messag- 69 void LoadSR (uint8_t thisByte) es: Compiling → Done 70 { for (int iSeg = 0; iSeg < NUM_SEGS; iSeg++) Compiling → Load- 71 72 { ing → Done Loading, 73 digitalWrite(PinSER, (thisByte & 1)); after which the program 74 digitalWrite(PinSRCLK, SR_CTRL_ACTIVE); will automatically run. 75 delay(STILL_KICKING); digitalWrite(PinSRCLK, SR_CTRL_INACTIVE); I don’t know about you, 76 thisByte >>= 1; but personally, I like 77 78 } watching the L, TX, and 79 } RX LEDs flash while I’m uploading a program. Listing 2(a): adding a “still kicking” delay. Practical Electronics | December | 2025 39 Ooh, flashy! Hyphen L (Level) F (Fail) A F B G E C D Space P (Player) E (Error) Fig.4: our initial 7-segment character set. simultaneously to form characters, but which characters do we want to represent? For no other reason than ‘just because’, I’ve decided to limit myself to only 16 different characters. Apart from anything else, we can reference 16 characters using a 4-bit field. Having said this, it’s not that we are short of memory (at least, not yet), and there’s no reason why we couldn’t extend our character set in the future if we so desire. Since we are building a retro games console, we know that we will want to display scores, meaning we need to be able to represent the ten numerical characters in the range 0 to 9. This leaves us with six empty ‘slots’ in our 16-character set. Off the top of my head, I think space (blank) and hyphen (minus) characters might come in handy. Also, we could use “P” for “Player” (in multi-player games) and “L” for Level (in multi-level games), along with “E” and “F” for “Error” and “Fail”, respectively (I’m sure we will find a use for these at some stage in the proceedings). Therefore, the character set we will implement at this time is as depicted in Fig.4. Note that we currently have no plans to employ decimal point segments, but that could change later if we need them. Talented tables From previous digit, module, or MCU Index To 7-Segment Display A B C D E F G DP 0 (0x0) 1 (0x1) 2 (0x2) 3 (0x3) 4 (0x4) 5 (0x5) 6 (0x6) 7 (0x7) 8 (0x8) 9 (0x9) 1 1 1 1 1 1 0 0 0 1 1 0 0 0 0 0 1 1 0 1 1 0 1 0 1 1 1 1 0 0 1 0 0 1 1 0 0 1 1 0 1 0 1 1 0 1 1 0 1 0 1 1 1 1 1 0 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 0 1 1 1 1 0 1 1 0 0 1 2 3 4 5 6 7 8 9 10 (0xA) 11 (0xB) 12 (0xC) 13 (0xD) 14 (0xE) 15 (0xF) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 0 1 1 1 0 0 0 0 1 1 1 0 0 1 0 0 1 1 1 1 0 1 0 0 0 1 1 1 0 Space Hyphen P L E F 7-Segment Display 74HC595 SIPO Shift Register A A B C F B D G E F E C G DP D To next digit or module Fig.5: mapping segments to characters. 40 DP From last month’s experiments, we know that when we load an 8-bit byte into the shift register driving a 7-segment display on our modules, the first bit, which we are thinking of as being the least-significant bit (LSB), ends up driving the DP (“decimal point”) segment. Similarly, the last bit, which we are thinking of as being the mostsignificant bit (MSB), ends up driving the A segment. Armed with this knowledge, we can represent everything in tabular form, as in Fig.5. We’ve shown the DP column as being greyed out, reflecting the fact that we have no plans to use this segment at this time (we still need to load a 0 into the shift register bit associated with this segment). Actually, since the 7-segment displays in my modules are of the common anode (CA) flavour, a 0 in the shift register will turn the corresponding segment on, while a 1 will turn it off. So we actually need to load a 1 into the shift register bit to keep the DP off. But I still think of it as 0 = off because that’s more intuitive to me. As we discussed last month, we can represent things this way in our programs, then invert the values at the last moment just before we feed them to the shift register. If we ever find ourselves driving common cath- ode (CC) displays, all we need to do is to omit the final inversion. The next consideration involves how we will load these bit patterns into our shift register chain. Take the character “5”, for example. From Fig.5, we know that the bit pattern associated with this character is 10110110. Remembering that we are inverting everything to accommodate my CA displays, the bit pattern we will need to load into the shift register is 01001001. Additionally, we know that we will start loading from the least significant (right hand) bit. Based on this, we could do things explicitly as follows: • Set SER to 1, pulse SRCLK once • Set SER to 0, pulse SRCLK twice • Set SER to 1, pulse SRCLK once • Set SER to 0, pulse SRCLK twice • Set SER to 1, pulse SRCLK once • Set SER to 0, pulse SRCLK once I don’t know about you, but having to code this sort of thing by hand for each digit in our display would be horribly inefficient and make my brain leak out of my ears. Instead, we will let our Arduino handle all the heavy lifting for us. Updating our program In a moment, we will update our program to present honest-to-goodness characters on our display modules. First, however, we must decide which of the displays will form our least significant digit (LSD) and which will act as our most significant digit (MSD). This is another case that is somewhat arbitrary. For example, since data is loaded from left-to-right into our twodigit modules, some people may prefer to think of the leftmost digit as being the LSD. For myself, given a choice, I always prefer to view the LSD as being the one on the right and the MSD as being the one on the left. This means that Fig.6 depicts the way I view the two-digit module I’m currently working with. As usual, regardless of which approach we decide to take, we can always craft our code to make things function the way we want. Keep this in mind as we consider our new program (in the file named MSD LSD Data from MCU Fig.6: a 1-module (2-digit) display. Practical Electronics | December | 2025 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // Display Stuff #define NUM_SEGS 8 // Segments per display #define NUM_DIGS 2 // Number of digits #define ALL_SEGS (NUM_SEGS * NUM_DIGS) #define NUM_CHARS 16 // Characters #define SPACE #define HYPHEN #define P_CHAR #define L_CHAR #define E_CHAR #define F_CHAR 10 11 12 13 14 15 // Shift Register (SR) Stuff #define SR_CTRL_INACTIVE LOW #define SR_CTRL_ACTIVE HIGH #define SR_DATA_INACTIVE HIGH #define SR_DATA_ACTIVE LOW // Extras #define UPDATE_RATE #define STILL_KICKING 1000 1 Cleared // Digits to be displayed uint8_t DisplayBuffer[NUM_DIGS]; // Bit patterns for characters uint8_t CharSegMap[NUM_CHARS] = { // ABCDEFGDP 0b11111100, // 0 (0x0) 0 0b01100000, // 1 (0x1) 1 0b11011010, // 2 (0x2) 2 0b11110010, // 3 (0x3) 3 0b01100110, // 4 (0x4) 4 0b10110110, // 5 (0x5) 5 0b10111110, // 6 (0x6) 6 0b11100000, // 7 (0x7) 7 0b11111110, // 8 (0x8) 8 0b11110110, // 9 (0x9) 9 0b00000000, // 10 (0xA) Space 0b00000010, // 11 (0xB) Hyphen 0b11001110, // 12 (0xC) P 0b00011100, // 13 (0xD) L 0b10011110, // 14 (0xE) E 0b10001110 // 15 (0xF) F }; Listing 3(b): additional global variables. CB-dec25-code-03.txt). This is based on our previous offering. You may be surprised by the modest modifications required (or not, as the case might be). Let’s start with the definitions as shown in Listing 3(a). Many of these remain unchanged. On line 7, we’ve added NUM_CHARS and assigned it a value of 16. This reflects the number of different characters we wish to depict, as shown in Fig.4. And on lines 9 through 14, we create definitions for the index values associated with our special characters (I’m sure these definitions will come in handy). Next, we add two new global variables. The first, DisplayBuffer[], is an array of unsigned 8-bit values. Each element of the array represents one of the digits on our display. This will act as a buffer to store the bit patterns we wish to upload into our shift register chain. The second, CharSegMap[], is also Practical Electronics | December | 2025 LSD iChar = 0 iChar = 8 iChar = 1 iChar = 9 iChar = 2 iChar = 10 iChar = 3 iChar = 11 iChar = 4 iChar = 12 iChar = 5 iChar = 13 iChar = 6 iChar = 14 iChar = 7 : Listing 3(a): our updated definitions. 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 MSD iChar = 15 : : : : : : (a) (b) (c) (a) (b) (c) : Fig.7: the results of our test code – success! an array of unsigned 8-bit values. In the same character on both digits, there’s always a chance that we’ve this case, we assign it with the bit patterns associated with the various messed things up with respect to loadcharacters we may wish to display, ing our LSD first and our MSD last (stranger things have happened). as depicted in Fig.5. Let’s check. Modify line 80 in The third and final area of change can be seen in our spiffy new loop() Listing 3(c) to change the value function, as seen in Listing 3(c). On being assigned to DisplayBuffer[0] line 78, we declare a for() loop that (which we intend to represent our will cycle through each of our char- LSD) from CharSegMap[iChar] to CharSegMap[HYPHEN]. Now, the reacters. On lines 80 and 81, we load both sults should be as shown in Fig.7(b). Return line 80 to its original state, elements of our display buffer with the bit pattern associated with the then modify line 81 to change the value character currently pointed to by being assigned to DisplayBuffer[1] (which we intend to represent our our iChar variable. Next, on lines 83 through 86, we call our LoadSR() MSD) to CharSegMap[HYPHEN]. This function to copy the two 8-bit pat- should cause the results to appear as terns stored in the DisplayBuffer[ ] shown in Fig.7(c). I love it when a plan comes together. array into our shift register chain. On line 87, we call our LoadOR() (“load output register”) function to Keeping score Let’s move on to something a little copy the contents of the shift registers into their corresponding output regis- more exciting. Depending on the game ters. Finally, on line 88, we pause to we are playing, one of the things we cast our orbs over the characters as will typically want to do is store, manipulate (eg, increment/decrement) they appear on the display. Observe that line 85 is where we and display the player’s current score. To do this, let’s create some new use the ~ (bitwise NOT) operator to invert all the bits (exchanging 0s for functions: one to update the current 1s and 1s for 0s) as we pass them into score while ensuring that it keeps the LoadSR() function. If your display 75 // Do this over and over again modules feature 76 void loop () common cathode 77 { 78 for (int iChar = 0; iChar < NUM_CHARS; iChar++) devices, you need 79 { only delete this 80 DisplayBuffer[0] = CharSegMap[iChar]; single-­c haracter 81 DisplayBuffer[1] = CharSegMap[iChar]; operator for every- 82 for (int iDigs = 0; iDigs < NUM_DIGS; iDigs++) thing to work as de- 83 84 { sired. The results 85 LoadSR(~DisplayBuffer[iDigs]); should be as shown 86 } 87 LoadOR(); in Fig.7(a). Are we sure? 88 89 90 delay(UPDATE_RATE); } } Since we are currently displaying Listing 3(c): our spiffy new loop() function. 41 MSD 5 4 LSD 3 2 1 0 (a) Blank (b) All 0s (c) 999,999 (d) -99,999 (e) 42 (f) L1, 42 (g) E24 Fig.8: various alignments and widths. within the specified bounds, one to load the score into the display buffer, and one to copy the contents of the display buffer to the display itself. Different games will have different requirements. Rather than having to revamp our functions for each new game, it will prove to be much more efficient to develop versatile, generalpurpose functions that can satisfy the needs of multiple games. Let’s start by considering that classic ‘70s and ‘80s games mostly avoided negative scores, ‘clamping’ the lower bound of the score to 0. They did this to keep things simple. However, there are cases where negative scores may be appropriate, including penalties/infractions (deducting points for mistakes, which can result in totals dipping below 0) and using time/energy as a score (overtime or damage could result in a negative tally). Thus, as a starting point, let’s assume that we need to be able to handle both positive and negative score values. Remembering that we are eventually going to have three two-digit displays (although you may opt for fewer or more), the worst-case scenario using all the displays is that we want to be able to store and display values between -99,999 (“-9 99 99”) to 999,999 (“99 99 99”). Are we aligned? Since we will have six digits, we might be tempted to make two assumptions. The first is that we will always use all the digits to present our numbers (scores). The second assumption is that we will always wish to present our numbers right-aligned to our LSD. As we shall see, however, neither of these assumptions holds water. If we were to display six-digit values, 42 some examples could be as shown in Fig.8(b) through Fig.8(e). In the case of Fig.8(e) in particular, observe that I’ve made the decision to always display leading zeros (we aren’t obliged to do things this way, but it does make life easier). However, a little thought reveals that there are other possibilities. Suppose we have a game with nine levels numbered 1 to 9, for example. Further, suppose that we are currently on level 1. In this case, we might wish to use the two leftmost digits to display “L1”, leaving the four remaining digits to present the four-digit score. Assuming a current score of 42, this case is illustrated in Fig.8(f). This means we need the ability to display a 1-digit number ranging from 1 to 9 in the digit 4 position to reflect the current level, while also displaying a 4-digit number right-aligned on the LSD to display our 4-digit score. As another example, suppose we want to display an “Error” value between 0 and 99. In this case, as illustrated in Fig.8(g), we might decide to present an ‘E’ character in the digit 4 position and use digits 2 and 3 to represent our two-digit error code. We don’t wish to limit ourselves to just these cases because we haven’t really thought things through. That is, we don’t know what games we will be creating, and we don’t know how we might wish to display our numbers in the future. Thus, the best thing to do is to say that we wish to have the ability to display numbers with ‘widths’ ranging from one to n digits (where n is 6 in my case), and we want to be able to specify the location of each number’s least significant digit on the display. This is going to be far easier than you might imagine. Updating a score When it comes to storing scores (or any other numbers we might wish to present on our displays), the most straightforward approach is to simply use signed 32-bit integers. This will allow us to employ the Arduino’s regular mathematical operators (addition and subtraction, plus possibly multiplication, division, and modulo). Why do we need 32 bits? Well, an 8-bit field can contain 28 = 256 pat100 101 102 103 104 105 106 107 terns of 0s and 1s, which can represent signed numbers in the range -128 to +127. This isn’t sufficient to accommodate our worst-case score values, which range from -99,999 to +999,999. Similarly, a 16-bit field can be used to represent 216 = 65,536 patterns of 0s and 1s, which can be used to represent signed numbers in the range -32,768 to +32,767. Again, this isn’t sufficient to accommodate our worst-case score values. This is why we must turn to 32-bit fields, which can be used to represent signed numbers in the range -2,147,483,648 to +2,147,483,647. As you will see if you peruse the latest incarnation of our program (the file named CB-dec25-code-04.txt), we’ve added two definitions, MIN_SCORE and MAX_SCORE, to set the bounds of our score. Since we currently have only two digits (we’ll add the remaining four digits next month), we’ll commence by considering only positive scores in the range 0 to 99, so we’ve assigned values of 0 and 99 to MIN_SCORE and MAX_SCORE, respectively. Additionally, we’ve added two more definitions: ZERO and MINUS, to which we’ve assigned values of 0 and 11, respectively. Observe that MINUS has the same value as our existing HYPHEN definition. We will use these as pointers into our CharSegMap[ ] array to make it easier to see what’s going on in our code. When it comes to storing and manipulating scores, we’ve declared three 32-bit integers: P1Score (“player one’s score”), P2Score (“player two’s score”) and DeltaVal (the value to be added to or subtracted from the current score). In the setup( ) function, we assign these variables the values of MIN_SCORE, MIN_SCORE and 1, respectively. Now let’s look at the UpdateScore() function, as illustrated in Listing 4(a). The reason all of our previous function definitions have begun with the void keyword is that we haven’t required them to return a value. In this case, however, we want our function to return a 32-bit integer, which is why we precede the name of the function with int32_t on line 101. This tells the compiler the data type of the value that our function is to return. // Modify a score with a delta (change) value int32_t UpdateScore (int32_t score, int32_t delta, int32_t minv, int32_t maxv) { int32_t tmpv = score + delta; if (tmpv < minv) tmpv = minv; if (tmpv > maxv) tmpv = maxv; return tmpv; } Listing 4(a): updating the score while keeping it in bounds. Practical Electronics | December | 2025 This function accepts four parameters: the current score we wish to use as a starting point, the delta (change) value we wish to apply, the min (“floor”) value we will allow, and the max (“cap”) value we will support. One interesting point to note is that we aren’t limiting ourselves to simply incrementing or decrementing the existing score by +1 or -1, respectively. The delta value can be any positive or negative integer. The rest of this function should be self-explanatory. The only thing we haven’t seen before is the return keyword, which is used to return the designated value to the point from which the function was called (we will see this in action when we call this function from within our loop( ) function). bitmap correspond(a) Number to be ing to the characdisplayed ter we wish to display. We could, if we wish, load cer5 4 3 2 1 0 tain elements in the (b) Display buffer display buffer with the bit patterns associated with characters like “L”, “E”, (c) 7-segment “F” and “-”. displays Last but not least, we have a number 5 4 3 2 1 0 we wish to display. This will be stored MSD LSD as a 32-bit signed integer, as shown in Fig.9: mapping a number onto the display. Fig.9(a). The important points to re- have one fewer digit to display. Thus, member are that this number may be on line 115, we reduce the value of positive or negative, that it can occupy numDigs by 1. On line 116, we load the MSD assoanywhere from one to six digits, and Loading the display buffer that the LSD of the displayed number ciated with this number in our buffer Next, we have the NumberToBuffer() may be offset from the LSD of the (which isn’t necessarily the MSD of the function, as illustrated in Listing 4(b). 7-segment displays. display) with the bitmap associated The reason we called this “number to Returning to Listing 4(a), our func- with a minus sign. Then, on line 117, buffer” rather than “score to buffer” is tion NumberToBuffer( ) accepts three we convert our negative value into its that, as we previously discussed, we parameters: value (the number we positive counterpart for the purposes may wish to display numbers other want to load into the buffer), numDigs of displaying the little scamp. than scores (eg, level and error values). Earlier, we decided that we wished (the number of digits we wish to disActually, this can be a little tricky play) and offset (the number of digits to represent all of our numerical values to wrap one’s brain around, so perhaps by which we wish to offset the value with leading zeros. This is accomone more diagram will help (Fig.9). we display from the LSD on the right plished by the for( ) loop that comLet’s take things in reverse order. Our of the display). mences on line 121. This loop loads end goal is to present characters on If offset is 0, the rightmost digit of the bitmap associated with a “0” charour 7-segment displays. the value to be displayed will be loaded acter into all the digits in our buffer We are assuming six such displays into digit 0 in our buffer and appear that we are using to represent this for the purposes of these discussions, on digit 0 of the display; if offset is 1, number (as specified by numDigs). as depicted in Fig.9(c). Each of these then the rightmost digit of the value to The clever thing here is that, if we displays has an associated shift reg- be displayed will be loaded into digit happen to be displaying a negative ister, which we’ve omitted from this 1 in our buffer and appear on digit 1 number, then the fact that we’ve already diagram to keep things simple. decremented the value of numDigs of the display; and so forth. We have a display buffer (called The first thing we do is use line 113 on line 115 means we won’t overDisplayBuffer[ ]) that’s defined as a to check to see if we are working with write the minus sign we loaded into six-element array. As illustrated in a negative value. If this is the case, we the MSD associated with our number Fig.9(b), each of these elements is know we will want to display a minus in the display buffer. an 8-bit byte that’s used to store the sign. In turn, this means that we will Finally, we load the number into the display buffer. As long as the value 109 // Load a number into the display buffer remains greater than 0 (see the while( ) 110 void NumberToBuffer (int32_t value, int8_t numDigs, int8_t offset) loop that commences on line 127), we 111 { perform three tasks. First, we load the 112 // Handle a negative value display buffer element specified by 113 if (value < 0) 114 { offset with the bitmap associated with 115 numDigs -= 1; // One less digit to display the numerical character correspond116 DisplayBuffer[offset + numDigs] = CharSegMap[MINUS]; ing to the remainder returned when 117 value = -value; // Make the value positive our value is divided by 10. 118 } Second, we divide our value by 10, 119 120 // Preload leading zeros thereby readying ourselves for the next 121 for (int iDigs = offset; iDigs < (offset + numDigs); iDigs++) pass around the loop. Finally, we in122 { crement the offset to point to the next 123 DisplayBuffer[iDigs] = CharSegMap[ZERO]; element in the display buffer. 124 } 125 I must admit that I’m rather proud 126 // Load the value itself of this NumberToBuffer( ) function be127 while (value > 0) cause I wrote it off the top of my head 128 { and it worked the first time (phew!). 129 DisplayBuffer[offset] = CharSegMap[value % 10]; 130 131 132 133 value /= 10; offset += 1; } } Listing 4(b): loading a number into the display buffer. Practical Electronics | December | 2025 Copying the buffer to the display The only remaining function is used to copy the contents of the display buffer to the display itself. This 43 new loop( ) function, as depicted in Listing 4(d). Remember that we’ve currently defined MIN_SCORE and MAX_ SCORE to be 0 and 99, respectively. Also, we’ve initialised the variable P1Score to contain MIN_ Listing 4(c): copying the buffer contents to the display. SCORE and the variable function is shown in Listing 4(c). Pre- DeltaVal to contain +1. viously, we performed this task as On line 92, we load the value stored part of our loop( ) function; however, in P1Score into our display buffer, it makes more sense to have it as a while also specifying that we wish to function in its own right. represent this number using two (2) Remember that this function nei- digits with an offset of zero (0). On ther knows nor cares what’s stored in line 93, we stream the contents of the the display buffer. It just streams the display buffer into the display itself. bitmaps in the display buffer into the On line 94, we pause to peruse what shift register chain formed by the dis- we have wrought. play modules, and “Bob’s your uncle” Now observe line 97. When we call (or aunt, depending on your family our UpdateScore() function, we pass dynamic). in the current value of P1Score (along with the delta, min, and max values). It’s time to rock! The clever thing is that we load the Finally, it’s time to bring every- value returned from this function call thing together by means of our spiffy back into P1Score. 135 136 137 138 139 140 141 142 143 // Display the contents of the buffer void BufferToDisplay () { for (int iDigs = 0; iDigs < NUM_DIGS; iDigs++) { LoadSR(~DisplayBuffer[iDigs]); } LoadOR(); } 88 89 90 91 92 93 94 95 96 97 98 // Do this over and over again void loop () { // Display the current score NumberToBuffer(P1Score, 2, 0); BufferToDisplay(); delay(UPDATE_RATE); // Modify the score P1Score = UpdateScore(P1Score, DeltaVal, MIN_SCORE, MAX_SCORE); } Listing 4(d): orchestrating everything via the loop( ) function. 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 // Do this over and over again void loop () { // Display the current score NumberToBuffer(P1Score, 2, 0); BufferToDisplay(); delay(UPDATE_RATE); // Modify the score P1Score = UpdateScore(P1Score, DeltaVal, MIN_SCORE, MAX_SCORE); } Listing 5(a): this version of the loop( ) function causes the score value to 'ping pong'. Devices and components used in this issue 44 Next time In our next column, we will add the remaining digits to our display and incorporate a full complement of these modules into our games console. Furthermore, despite previous broken promises, we really will add the eight push-button switches to our console’s front panel. As always, if you have any thoughts you’d care to share on anything you’ve read here, please feel free to email me at max<at>clivemaxfield.com PE Putting the boot in // Reverse direction when min or max reached if ((P1Score == MIN_SCORE) || (P1Score == MAX_SCORE)) DeltaVal = -DeltaVal; Arduino Uno R3 microcontroller module Solderless breadboard 4-inch (10cm) jumper wires (optional) 8-inch (20cm) jumper wires (male-to-male) LEDs (assorted colours) Resistors (assorted values) Ceramic capacitors (assorted values) 16V 100µF electrolytic capacitors 8-Segment DIP red LED bar displays 74HC595 8-bit shift registers Dual 7-segment display modules 0.1” pitch header pins Since we’ve currently set DeltaVal to be +1, this means we will see the 00, 01, 02, 03….. 97, 98, 99 sequence appear on our 2-digit display. Additionally, since our UpdateScore() function will cap the score at MAX_SCORE, which is currently defined as 99, we will end up seeing 99, 99, 99… Just for the thrill of it, I’ve created one final (for this column) iteration of our program (in the file named CBdec25-code-05.txt). All I’ve done is to modify the loop( ) function as shown in Listing 5(a). On line 97, we check to see if our score has reached its minimum or maximum value. If so, on line 98, we reverse the polarity of DeltaVal. This means our display will repeatedly count up from 00 to 99 and then back down to 00 again. Although we currently have only two digits, why don’t you experiment by changing things like the value of DeltaVal (eg, making it +2 or -3)? Or how about changing MIN_SCORE to be -9 and MAX_SCORE to be 42, for example? https://pemag.au/link/ac2g https://amzn.to/3O2L3e8 https://pemag.au/link/ac2l https://amzn.to/3O4hnxk https://amzn.to/3E7VAQE https://amzn.to/3O4RvBt https://pemag.au/link/ac2i https://pemag.au/link/ac2j https://pemag.au/link/ac2c https://pemag.au/link/ac1n https://pemag.au/link/ac8i https://pemag.au/link/ac8j I’m constantly thinking of things I want to tell you. Are these things necessary for your continued existence? Probably not, but—in my experience—the more we know about how things work, the happier we are at the end of the day. In this vein, a ‘bootloader’ is a small program stored in a microcontroller’s flash memory. The name comes from ‘bootstrap loader’ (as in “pulling yourself up by your bootstraps”), because it’s the very first piece of code that runs when the chip powers up or resets. Its job is to ‘boot’ the system by loading or launching the main user program. The Arduino Uno ships with a preinstalled bootloader that occupies 512 bytes of the Arduino’s 32,768-byte (32kiB) flash memory. This bootloader enables the chip to receive new programs over the USB-serial link without requiring an external hardware programmer. If the Arduino detects that the Integrated Development Environment (IDE) on your main computer is attempting to upload a program, it writes that program into flash memory; otherwise, it quickly jumps to whatever existing user program is already stored in flash. Practical Electronics | December | 2025