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:
Items relevant to "Variable Speed Drive Mk2 for Induction Motors, Part 1":
Articles in this series:
Articles in this series:
Articles in this series:
Articles in this series:
Articles in this series:
Articles in this series:
|
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
|