This is only a preview of the November 2025 issue of Practical Electronics. You can view 0 of the 80 pages in the full issue. Articles in this series:
Items relevant to "3D Printer Filament Drying Chamber, Part 2":
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 11: connecting the console's 7-segment displays
A
fter all sorts of interesting
discussions about boolean
logic and such over the last
few issues, it’s time for the other shoe
to drop. I fear we’ve dillied and dallied in recent columns, meandering
our way through a morass of teasingly tempting topics.
You may well wonder if we’ve lost
sight of our original goal, which is to
construct a retro games console that
will be the object of desire and the
envy of all who see it (see Photo 1).
“No!” I cry, “A thousand times no!”
In this column, we will take the
first tentative steps toward adding
the 7-segment displays at the top of
the console’s front panel (visible at
upper left in Photo 1). In next month’s
column, we will complete this process and also (hopefully) add the eight
push-button switches at the bottom
of the console’s front panel (at lower
right in Photo 1).
Initially, we will connect all these
components directly to our Arduino
Uno to make sure everything functions as planned.
Holding it together
I’m hoping that, like me, you’re
working with a laser-cut front panel.
I’m also hoping that, in the fullness
of time, you plan on mounting this
panel in a custom 3D-printed enclosure.
However, if you don’t have access
to a laser cutter and a 3D printer (or a
well-equipped friend), you can always
attach everything to something like a
piece of hardboard (aka pressboard),
or ¼-inch (6.35mm) thick plywood
and call it a prototype.
Always remember that a hot glue
gun is a tool of champions. That said,
never apply hot glue to electronic
components while the circuit is powered, as the heat can cause damage
or even trigger thermal runaway in
sensitive devices. I learned this the
hard way several years ago with some
tricolour LEDs in Adafruit NeoPixel
rings. I won’t do that again.
One step at a time
One of the lessons we’ve (hopefully) learned during our time together
is that when we’re working on a new
circuit, or moving from one version to
the next, it’s almost always better to
change just one thing at a time. This
is one of those engineering wisdom
things that applies to the prototyping and debugging of both hardware
and software.
Why? Well, if we tweak a bunch of
things at the same time, and whatever we’re working on starts to misbehave, we won’t know exactly what
caused the problem. That can send us
down a rabbit hole of time-consuming
guesswork.
Alternatively, if we take things
step by step, every change teaches
us something. We’ll spot problems
immediately, avoid breaking something that was already working, and
develop a better understanding of
how our hardware or software behaves. Working this way might feel
slower in the moment, but it saves
a ton of time (and frustration) in the
long run.
Taking our first step
Photo 1: an early
version of our retro
games console
(Photograph by Joe Farr).
Thus far, we’ve been using a
74HC595 8-bit serial-in, parallelout (SIPO) shift register as part of
our breadboard-based experiments (Fig.1).
Different data sheets may
refer to the outputs from this
device as a to h, A to H, QA to QH,
or Q0 to Q7. In our case, we may
switch between referring to things
like “bit 0”, “output 0” or “output
a”, depending on what works best
in the given context.
As a brief reminder, the shift register device contains two 8-bit registers. We refer to one of these as
the ‘shift register’ (which can be a
26
Practical Electronics | November | 2025
SRCLR
b
1
16
VCC
c
2
15
a
d
3
14
SER
SER
e
4
13
OE
RCLK
f
5
12
RCLK
g
6
11
SRCLK
h
7
10
SRCLR
GND
8
9
h’
(a) Package
SRCLK
0 1 2 3 4 5 6 7
Shift
register
h’
0 1 2 3 4 5 6 7
Output
register
OE
Connection
No connection
a
b
c
d
e
f
g
h
(b) Block diagram
Fig.1: the pinout and internal structure of a 74HC595 serial-to-parallel shift register.
Also observe that since we are no
longer controlling the shift register’s active-low SRCLR input with
the Arduino, we’ve added a 10kΩ
pull-up resistor connecting this pin
to the +5V rail, placing it in its inactive state.
You can see a full breadboard layout
in the file named CB-nov25-brd-01.
pdf. As usual, all files mentioned in
this column are available as part of
the November 2025 download package from the PE website at https://
pemag.au/link/ac8h
available to us. This means that when Make it count!
How about we create a quick prowe wish to clear the device, we will
gram that loops around counting from
need to shift in a series of 0 values.
Additionally, we began our experi- 0 to 255 (that’s 00000000 to 11111111
ments by connecting the SER input to in binary or 00 to FF in hexadecilogic 1. Later, when we implement- mal), displaying each value on our
ed a Johnson counter, followed by a 8-segment display and pausing for
linear feedback shift register (LFSR), one second between values? “That’s
we used the output from another a great idea”, I hear you say. You’re
logic device to feed the SER input. right, it is, so you go first, and we’ll
Now, we’re going to drive this input compare our code at the end.
You’re ready? Wow, that was fast!
directly from our Arduino.
In our most recent experiments, we You’re getting good at this.
This program is short enough that
used Arduino digital input/output
(I/O) pins 5, 6, and 7 to drive the it’s probably worth going through in
shift register control inputs SRCLR, its entirety, not least because it will
SRCLK and RCLK, respectively. This soon form the basis for driving the
made sense at the time, but this pin displays on our games console. You
group is slap-bang in the middle of can download my version in the file
the digital pins. Since we are soon
named CB-nov25-code-01.txt.
going to be using all of these pins,
As usual, we start with definithat’s a tad inconvenient.
tions, then declare and assign pins
First, we'll remove
all the extraneous
OE
SRCLR
parts from our breadboard, leaving only
the 74HC595 shift
register, 8-segment
LED bar graph display, and any associated resistors and
wires. We will use
74HC595
Arduino pins 11, 12,
and 13 to drive the
shift register’s SER,
RCLK and SRCLK
inputs, respectively.
You should aim to
achieve a layout that
resembles the one illustrated in Fig.2.
Observe that the
Pin 13 = SRCLK
10kΩ pull-down rePin 12 = RCLK
sistor connecting the
Pin 11 = SER
shift register’s activelow OE to the 0V rail
stays as-is. This is because, for our purposes, we always want
Arduino
DIGITAL IN/OUT (PWM ~)
this pin to be in its
active state, thereby
Orange LED
Green LED
ensuring that the shift
register’s outputs are
always enabled.
Fig.2: our new cut-down breadboard setup.
Practical Electronics | November | 2025
27
AREF
GND
13
12
~11
~10
~9
8
7
~6
~5
4
~3
2
TX-1
RX-0
tad confusing), and the other as the
‘output register’. The operation of
this component can be summarised
as follows.
A logic 0 on the active-low SRCLR
(shift register clear) input loads all
the bits in the shift register with 0.
A rising 0 → 1 transition on the
positive-edge-triggered SRCLK (shift
register clock) input causes the contents of the shift register to be shifted
one bit (to the right in this illustration).
Whatever 0 or 1 value that’s currently on the SER (serial data in)
input is copied into bit 0. At the same
time, the original contents of bit 0
are copied into bit 1, the contents of
bit 1 are copied into bit 2, and so on
down the line. The original contents
of bit 7 conceptually ‘fall off the end’
(or form the input to the next shift
register device in the chain).
A rising 0 → 1 transition on the
positive-edge-triggered RCLK (output
register clock) copies all the bit values
in the shift register into the corresponding bits in the output register.
Finally, a logic 0 on the shift register’s active-low OE (output enable)
input allows whatever values are
stored in the output register to be
made available on the 74HC595’s
output pins. By comparison, applying a logic 1 to this input causes the
output pins to be placed in a tri-state
(high-impedance) condition, effectively disconnecting the output buffers from the outside world.
It’s important to note that the OE
signal does not affect the values stored
in the output register, only the values
visible to the outside world.
By some strange quirk of fate, the
74HC595 is the same device (albeit
in a smaller, surface-mount package)
that’s featured in the display modules we’re going to add to our games
console, but we’re going to do a few
things differently from them.
For example, thus far, we’ve been
taking advantage of the active-low
SRCLR input to clear our shift register.
However, the creators of the display
modules have not made this signal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Display binary count on SR
// Display Stuff
#define NUM_SEGS
8
// Shift Register (SR) Stuff
#define SR_INACTIVE
LOW
#define SR_ACTIVE
HIGH
// Extras
#define UPDATE_RATE
1000
// Declare pins driving the SR
const int PinSER
= 11;
const int PinRCLK = 12;
const int PinSRCLK = 13;
// Declare global variables
uint8_t CountVal = 1;
Listing 1(a): definitions and declarations.
and global variables, as shown in
Listing 1(a). Remember that we are
using the convention that the listing
number (1 in this case) corresponds
to the numerical part of the matching
code file name (“01” here).
On line 4, we define NUM_SEGS (the
number of segments in our bar graph
display) to be 8. We’ve seen this before.
The only reason I mention it here is
that this will also work with (apply to)
our 7-segment displays in the future.
Recall that these have seven main segments along with an extra decimal
point segment.
Since all the shift register’s activelow inputs are now hard-wired to their
appropriate logic values, I’ve decided
to simplify things by defining common
active and inactive states on lines 7
and 8. I’ve also defined the update
(refresh) rate as being 1000ms (one
second) on line 11.
On lines 14, 15, and 16, we declare
the names of the variables we’re going
to use to drive the shift register’s SER,
RCLK and SRCLK inputs, and assign
them pin numbers 11, 12 and 13, respectively. On line 19, we declare an
8-bit unsigned integer called CountVal and assign it an initial value of 1
(we introduced fixed-width data types
like uint8_t in the May 2025 column).
Earlier, we stated that our counter
will loop around, counting from 0 to
255, and it will certainly do so on subsequent iterations. We’ve initialised
CountVal with a value of 1, meaning
we will count from 1 to 255 on the first
iteration, because this will facilitate
comparisons between the actions of
this program and those of our followup experiment.
Now let’s consider the three utility
functions shown in Listing 1(b). We’ll
commence with the ClearSR (clear
shift register) function, which starts
on line 48. This accepts an 8-bit unsigned integer parameter called numBits, which contains the number of
bits in the shift register.
28
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// Clear the shift register
void ClearSR (uint8_t numBits)
{
// Set serial data to 0
digitalWrite(PinSER, SR_INACTIVE);
for (int iBit = 0; iBit < numBits; iBit++)
{
digitalWrite(PinSRCLK, SR_ACTIVE);
digitalWrite(PinSRCLK, SR_INACTIVE);
}
}
// Load 8-bit value into the shift register
void LoadSR (uint8_t thisByte)
{
for (int iSeg = 0; iSeg < NUM_SEGS; iSeg++)
{
digitalWrite(PinSER, (thisByte & 1));
digitalWrite(PinSRCLK, SR_ACTIVE);
digitalWrite(PinSRCLK, SR_INACTIVE);
thisByte >>= 1;
}
}
// Copy shift register into output register
void LoadOR ()
{
// Load the output register
digitalWrite(PinRCLK, SR_ACTIVE);
digitalWrite(PinRCLK, SR_INACTIVE);
}
Listing 1(b): some utility functions for manipulating the shift register IC.
Since we are currently using a single
‘595 integrated circuit (IC or ‘chip’) to
implement our shift register, we know
that it contains only eight bits. The
reason for specifying the number of
bits as a parameter is to future-proof
this portion of our code, allowing it to
support a chain of multiple cascaded
‘595 chips without modification.
On line 51, we set the shift register’s SER input to 0. On lines 53 to
57, we apply the required number of
positive-going pulses to the shift register’s SRCLK input to load all its bits
with 0s.
Now let’s turn our attention to the
LoadSR() (load shift register) function,
which starts on line 61. This accepts an
8-bit unsigned integer called thisByte,
containing the value we wish to display.
On line 63, we declare a for() loop that
will execute eight times (because 8 is the
value we’ve assigned to NUM_SEGS).
Each time around this loop, we perform four tasks. On line 65, we use
the & (AND) operator to mask out the
least-significant bit (LSB) of thisByte.
You can think of this as (thisByte &
0b00000001). The result, 0 or 1, corresponds to the value in the LSB. This is
written to the shift register’s SER input.
On lines 66 and 67, we apply a positive-going pulse to the shift register’s
SRCLK input, shifting the value on the
SER input into the register. Then, on
line 68, we shift the value stored in
thisByte one bit to the right, discarding
the LSB we just used and bringing the
next bit into the LSB position, ready
for the next pass through the loop.
Consider the LoadOR( ) (load the
output register) function that commences on line 73. This simply applies
a positive-going pulse to the shift register’s RCLK input, copying the contents of the shift register into the output
register, at which point they become
visible to the outside world.
Now that we have all this under our
belt, we can turn our attention to the
setup() and loop() functions, illustrated
in Listing 1(c).
The workings of both of these functions are reasonably self-explanatory.
In the case of the setup( ) function, we
begin by initialising the pins driving
the shift register. Then we call our
ClearSR( ) function on line 34 to clear
all the shift register bits to 0, followed
by a call to our LoadOR( ) function on
line 35 to copy the bits from the shift
register into the output register.
At this point, they will appear on
our display.
When it comes to the loop( ) function, on line 41, we call our LoadSR( )
function to load the shift register with
the current value in CountVal. On line
42, we call the LoadOR( ) function to
copy this value into the output register, at which point it will appear on
our display.
On line 43, we increment (add one
to) the value in CountVal. Since this
is an 8-bit unsigned number, when
it reaches 255 (11111111 in binary),
the next increment will cause it to
‘roll over’ and contain 0 (00000000 in
binary). Finally, on line 44, we pause
for a moment to allow us to peruse
Practical Electronics | November | 2025
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
27 = 128
// Do this one time
void setup ()
{
// Initialize the SR pins
pinMode(PinSER,
OUTPUT);
pinMode(PinRCLK, OUTPUT);
pinMode(PinSRCLK, OUTPUT);
(Bit 7) MSB
digitalWrite(PinSER,
SR_INACTIVE);
digitalWrite(PinRCLK, SR_INACTIVE);
digitalWrite(PinSRCLK, SR_INACTIVE);
// Clear the shift register
ClearSR(NUM_SEGS);
LoadOR();
LSB (Bit 0)
Cleared
=0
=
0
Cycle 1
=1
=
1
Cycle 2
=2
=
2
Cycle 3
=3
=
4
Cycle 4
=4
=
8
Cycle 5
=5
= 16
Cycle 6
=6
= 32
Cycle 7
=7
= 64
Cycle 8
=8
= 128
}
// Do this over and over again
void loop ()
{
LoadSR(CountVal);
LoadOR();
CountVal += 1;
delay(UPDATE_RATE);
}
Listing 1(c): the setup() and loop() functions of our test sketch.
35
36
37
38
39
40
41
42
43
44
45
46
47
20 = 1
// Do this over and over again
void loop ()
{
uint8_t tmpVal = 0b00000001;
for (int iSeg = 0; iSeg < NUM_SEGS; iSeg++)
{
LoadSR(tmpVal);
LoadOR();
delay(UPDATE_RATE);
tmpVal <<= 1;
}
:
Listing 2(a): implementing a sliding 1 display scheme.
Mental gymnastics
To be honest, wrapping one’s brain
around the way in which all this works
can require some mental gymnastics, so
you might want to perform some limbering-up exercises before we proceed
into the full intellectual floor routine.
In the case of a general-purpose register or a binary data value, the domiPractical Electronics | November | 2025
:
(b) Walking 1
Fig.3: the bargraph display with two test sketches.
}
and ponder the segments on the display before proceeding to process the
next count value.
It’s important to wrap our brains
around what we are doing here, which
is to first load all eight bits into the
shift register, and only then copy the
contents of the shift register into the
output register.
If we loaded the output register every
time a new bit was loaded into the shift
register, this would result in ‘ghosting’
on the 8-segment display. That will be
due to brief, unintended flashes where
incorrect segments appear momentarily before the full, correct pattern is
established.
Run this program and observe the
binary count being presented on the
8-segment display as illustrated in
Fig.3(a). I don’t know about you, but
I could happily watch this for hours.
:
(a) Binary count
nant convention in illustrations and
our mental models is to consider the
least-significant bit (LSB) to be on the
right and the most-significant bit (MSB)
to be on the left. This is how we think
about our CountVal variable, as illustrated in Fig.4(a), and our 8-segment
display, as shown in Fig.4(c).
Unfortunately, things become a little
fuzzier in the case of shift registers
like the 74HC595. This is one of those
areas where context and convention
can collide.
In shift register datasheets, manufacturers typically avoid explicitly using
the terms LSB or MSB, as these depend
on the user’s shifting convention and
wiring arrangement. The data sheets
just show QA to QH and the movement
of data.
This means that whether QA is treated as the LSB and QH is treated as the
MSB, or vice versa, depends on how
we choose to shift the data in (LSB-first
or MSB-first), and how we decide to
employ the outputs.
In our case, the LoadSR( ) function
shifts the CountVal value into the shift
register starting with the LSB first. This
means the LSB of CountVal ends up on
output QH, and the MSB of CountVal
ends up on output QA. Since we’ve
connected QH and QA to our display’s
LSB and MSB, respectively, everything
works as we want it to (hurrah!).
Slip-sliding along
For reasons that will become apparent shortly, we are going to modify
our program to display what we might
think of as a “Walking 1” (or a “Sliding
1”) pattern, as illustrated in Fig.3(b).
This is easy-peasy lemon squeezy;
you can access the new version of this
program in the file named CB-nov25code-02.txt. We no longer need our
global CountVal variable, so we can
remove it. Everything else remains
the same, except for our loop( ) function, which has been updated to the
new version shown in Listing 2(a).
MSB
LSB
7 6 5 4 3 2 1 0 (a) CountVal
0 1 2 3 4 5 6 7
(b) Shift Register
a b c d e f g h
(c) Display
MSB
LSB
Fig.4. this can make your head hurt.
29
We start on line 38 by declaring an
8-bit unsigned integer called tmpVal
and loading it with binary 00000001.
We could have simply said tmpVal = 1,
but the binary representation helps
us better visualise what’s going on.
On line 44, we declare a for( ) loop
that will execute eight times (because 8 is the value we’ve assigned
to NUM_SEGS).
Each time around the loop, we
perform four tasks. On line 42, we
call our LoadSR( ) function to load
the current value in tmpVal into our
shift register. On line 43, we call the
LoadOR( ) to copy the bits from the
shift register into the output register,
at which point they will appear on
our display.
On line 44, we pause to ponder
the segments on the display. Finally,
on line 45, we shift the contents of
tmpVal one bit to the left. This will
automatically shift a 0 into the LSB,
while the MSB will conceptually ‘fall
off the end’ and be discarded.
The result is a classic “Walking 1”
pattern, in which our 1 travels from
right to left on our display. If we ever
wanted to reverse direction and have
the 1 move from left to right, we could
do so by changing the assignment
value on line 38 to 0b10000000, and
modifying line 45 to implement
a shift right (tmpVal >>= 1;).
We c o u l d h a v e s i m p l y
clocked a logic 1 across the
display ‘by hand’, as it were.
To do this, we would first
place a 1 on the SER input,
clock it in with SRCLK,
and load the output register with RCLK. Then we
would place a 0 on
the SER input and
repeatedly clock
SRCLK and
RCLK to walk
the 1 across
the display.
Yo u c o u l d t r y
doing this yourVCC (+5V)
VCC
self. For our purposGND
(0V)
GND
es here, however, I
SDI
SDO
felt it was better to
SCLK
SCLK
stick with our utiliLOAD
LOAD
ty functions because
these are what we
will be using in the
future. After all, the Fig 5: the pinout of a two-digit 7-segment display module.
more we use them, the more familiar we become with the way in
which they perform their magic.
Run this program and verify that
you see the sequence illustrated
in Fig.3(b) on your display before
proceeding to the exciting part.
Soldered
pins
Have you got the modules?
Header
I can’t help myself. I’m thinking
pin
of the old comedy duo Morecambe
and Wise (Eric Morecambe looked
just like my dear old dad). In parFig 6: adding header pins to our module.
ticular, the sketches where Ernie
associated current-limiting resistors
Wise would ask a question along the
and the 74HC595 shift register chips to
lines of, “Have you got the scrolls?”
drive them. However, it’s much easier
(or “modules,” in our case), and Eric
to work with pre-built modules that
would respond, “No, I always walk
already have the components mountthis way!” (I’m easily amused).
ed on a small circuit board.
As we discussed in our January
Modules of this type are available
2025 column, when it comes to the
7-segment displays used on our games
from multiple suppliers, but we would
console, we could opt to use individbe hard-pressed to beat the prices on
ual devices, along with
AliExpress (https://pemag.au/link/
ac8i). These modules are available with
two, three or four digits (see Photo 2).
I opted to use three two-digit modules to match the prototype console
that was constructed by my friend
Joe Farr (shown in Photo 1). However, you can choose to use as few or
as many of these two-, three- or fourdigit modules as you wish.
A close-up view of one of my twodigit modules is shown in Fig.5. The
SDI (serial data in), SCLK (shift register clock) and LOAD signals are equivalent to our existing SER, SRCLK, and
RCLK signals, respectively. The SDO
(serial data out) pin can be used to
feed the next module in the chain.
Mounting the components
Photo 2. multi-digit 7-segment
LED display modules (source: AliExpress).
Just to make sure we know what
we’re doing (and I use this phrase in
the loosest possible sense), we will
add five 0.1-inch (2.54mm) pitch
header pins (https://pemag.au/link/
ac8j) to the input side of one of our
modules (the side with the SDI input),
as illustrated in Fig.6.
Next, we will ensure that everything is powered down and then
attach this module to our breadboard,
as illustrated in Fig.7. Observe that
we’ve added red and black wires to
connect the +5V and 0V pins on the
module to the +5V and 0V rails on
the breadboard, respectively.
30
Practical Electronics | November | 2025
OE
SRCLR
74HC595
Pin 13 = SRCLK
From Arduino
Pin 12 = RCLK
Pin 11 = SER
Fig 7: adding the module to our breadboard.
3
4
5
6
// Display Stuff
#define NUM_SEGS
8 // Segments per display
#define NUM_DIGS
2 // Digits (displays)
#define ALL_SEGS (NUM_SEGS * NUM_DIGS)
Listing 3(a): some additional definitions near the top of our final test sketch.
Additionally, we’ve added pink
wires to extend the SER, SRCLK,
and RCLK signals from the ‘595 chip
on our breadboard to also feed the
SDI, SCLK and LOAD pins on the
module. A full breadboard layout
can be found in the file named CBnov25-brd-02.pdf.
The time has come!
We’re poised to start driving our
7-segment display module. First, we
need to make a minor tweak to our
program, shown in Listing 3(a). A full
listing is provided in the file named
CB-nov25-code-03.txt.
As we noted earlier, our original
NUM_SEGS definition on line 4 remains relevant because it applies both
to our old bar graph display and to
each of our new 7-segment displays,
which include an eighth decimal point
(DP) segment.
Shift direction
A
F
Our new NUM_DIGS definition
specifies the number of 7-segment
digits we’re working with (currently
two). This leads us to the ALL_SEGS
definition, which specifies the total
number of segments in our 7-segment
display chain. The C/C++ preprocessor will automatically evaluate this to
be 8 × 2 = 16.
While the compiler doesn’t enforce it, it’s imperative to include the
( ) round brackets (parentheses) in
this type of macro definition. Otherwise, strange things can
happen when we least
Cleared
expect them.
If you look at the program listing, you’ll see
Cycle 1
that we pass ALL_SEGS
as an argument into the
Cycle 2
call to the ClearSR( )
function from the setup()
function.
Cycle 3
B
Cycle 5
A B C D E F G * Display
Cycle 6
0 1 2 3 4 5 6 7 Shift
a b c d e f g h Register
Cycle 7
G
E
C
(c)
D
(a)
DP
I didn’t see that coming!
I wasn’t sure whether to expect
the sequence shown in Fig.9(b) or
Fig.9(c). You can only imagine my
surprise at seeing the sequence depicted in Fig.9(d)!
Cycle 4
0 1 2 3 4 5 6 7 Shift
a b c d e f g h Register
(b)
Let’s pause for a moment to consider
what we expect to see before running
this new version of our program. I’ll
give you a clue. We don’t know how
the makers of our 7-segment display
modules decided to connect the display chips to the shift register chips.
From our Arduino Bootcamp columns, we know that the segments
on the displays are referred to as A,
B, C, D, E, F, G and DP, as illustrated
in Fig.8(a).
What we don’t know in the case of
our modules is whether output a from
the shift register drives segment A on
the display, as shown in Fig.8(b). In
this case, we would expect to see results as per Fig.9(b). Alternatively, if
output h from the shift register drives
segment A on the display, like in
Fig.8(c), we would expect to see results like in Fig.9(c).
This is another example of a situation that requires some mental agility on our part. Remember that we
aren’t shifting bits individually. The
way our program is written, we shift
an entire byte into the shift register
before loading (copying) it into the
output register. Apart from anything
else, this explains why the right-hand
digit in the display pair lags the lefthand digit by one cycle.
When you’re ready, run the program
and observe the results.
Cycle 8
* G F E D C B A Display
* = Decimal Point (DP)
Fig.8: how are our shift registers connected?
Practical Electronics | November | 2025
:
:
(a)
:
(b)
:
(c)
:
(d)
Fig.9: what’s connected to what?
31
Multiple anodes
A
A
B
C
D
E
F
G
DP
F
G
DP
(b)
F
B
G
E
Common cathode CC) 0V
C
D
(a)
Common anode (CA) 5V
DP
(c)
A
B
C
D
E
Multiple cathodes
This means that the shift registers in my modules are connected to
their 7-segment displays as shown in
Fig.8(b). However, the displays themselves aren’t the common cathode (CC)
types we grew to know and love in
our Arduino Bootcamp columns, like
in Fig.10(b). No siree Bob! These displays are of the common anode (CA)
persuasion, per Fig.10(c).
In the case of common cathode
displays, which I initially thought I
was using, a logic 1 (HIGH) switches
the corresponding segment on, while
a logic 0 (LOW) turns it off. Things
work the other way around with the
common anode displays, which I
find I’m actually using. In this case,
a logic 1 switches the segment off
while a logic 0 lights it up.
Spoiled for choice
Fig.10: my displays are of the common anode type.
Fig.11: three different ways to resolve the CA vs CC LED array problem.
There are multiple ways that we can
resolve the CA vs CC issue. One approach would be to change our “Walking 1”, shown in Fig.11(a), into a “Walking 0”, as depicted in Fig.11(b). This
would involve two tweaks to our code.
The first occurs on line 41, where
we change our initial assignment from
0b00000001 to 0b11111110. The second
occurs on line 48, which is where we
perform our shift.
The problem here is that the shift
operator will automatically cause a 0
to be inserted into the least-significant
(right-hand) bit. In this case, however,
we wish to insert a 1. To achieve this,
we perform the shift, which inserts a 0,
and then use the | operator to bitwiseOR the shifted value with 0b00000001,
which will force the least significant
bit to be 1.
Different people approach these
things in various ways. Personally, I
dislike the “Walking 0” solution because my own mental model equates 1
with on and 0 with off. For this reason,
I prefer to stick with using a “Walking
1” but invert the 8-bit tmpVal value just
before passing it into our LoadSR( )
function, as illustrated on line 45 in
Fig.11(c).
Note that, while this causes an inverted copy of tmpVal to be passed
into the function, it has no effect on
the contents of tmpVal itself.
In this case, I’m using the idiomatic
~ bitwise-NOT operator to invert the
bit pattern stored in tmpVal. It swaps
each 0 for 1 and vice versa.
We could achieve the same effect
in other ways. For example, we could
subtract the value in tmpVal from 255
in decimal (0b11111111 in binary or
0xFF in hexadecimal). For example,
42 = 00101010 in binary, and 255 – 42
= 213, which is 11010101 in binary (I
feel a little “tra-la” is called for).
32
Practical Electronics | November | 2025
35
36
37
38
39
40
41
42
43
44
45
46
47
// Do this over and over again
void loop ()
{
uint8_t tmpVal = 0b00000001;
for (int iSeg = 0; iSeg < NUM_SEGS; iSeg++)
{
LoadSR(tmpVal);
LoadOR();
delay(UPDATE_RATE);
tmpVal <<= 1;
}
}
Iterations
0: 00000001
1: 00000010
2: 00000100
3: 00001000
4: 00010000
5: 00100000
6: 01000000
7: 10000000
(a) Option 1: original "walking 1" code.
35
36
37
38
39
40
41
42
43
44
45
46
47
// Do this over and over again
void loop ()
{
uint8_t tmpVal = 0b11111110;
for (int iSeg = 0; iSeg < NUM_SEGS; iSeg++)
{
LoadSR(tmpVal);
LoadOR();
delay(UPDATE_RATE);
tmpVal = (tmpVal << 1) | 0b00000001;
}
}
Iterations
0: 11111110
1: 11111101
2: 11111011
3: 11110111
4: 11101111
5: 11011111
6: 10111111
7: 01111111
(b): Option 2: employ a "walking 0" strategy.
35
36
37
38
39
40
41
42
43
44
45
46
47
// Do this over and over again
void loop ()
{
uint8_t tmpVal = 0b00000001;
for (int iSeg = 0; iSeg < NUM_SEGS; iSeg++)
{
LoadSR(~tmpVal);
LoadOR();
delay(UPDATE_RATE);
tmpVal <<= 1;
}
(~tmpval);
or
(255 - tmpVal)
or
(tmpVal^0xFF)
}
(c) Option 3: invert the value passed to the LoadSR() function.
Devices and components used in this issue
Cleared
https://pemag.au/link/ac2g
https://amzn.to/3O2L3e8
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
Cycle 1
Cycle 2
Cycle 3
Cycle 4
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
Cycle 5
I've no idea
why they
call me
"Max Max"!
Cycle 6
Cycle 7
Cycle 8
:
:
:
Fig.12: finally, what I was expecting!
Yet another alternative is to use the ^
bitwise-XOR operator, ie, (tmpVal ^ 255)
or (tmpVal ^ 0xFF) or (tmpVal ^
0b11111111). This will cause each bit
in tmpVal to be XORed with 1, thereby
inverting it. You’ll see why this works
if you look at the XOR truth tables we
showed in earlier columns.
I’ve created an updated version of
our program that continues to use a
“Walking 1” and inverts the value as
it’s passed into the LoadSR( ) function.
A full listing is provided in the file
named CB-nov25-code-04.txt.
When I ran this latest incarnation of
my program, I saw the sequence shown
in Fig.12. Hooray! Now we’re really
cooking with a low-density flammable hydrocarbon!
It’s necessary to note that, in the
case of the cheap-and-cheerful display modules we’re using here, any
datasheet documentation may be sadly
lacking. This means that the modules
you purchase could be wired as shown
in Fig.8(b) or Fig.8(c). Also, they may
turn out to be either CC or CA types,
as depicted in Fig.10(b) or Fig.10(c),
respectively.
You may not discover the nitty-gritty
details until you have these modules in
your hot little hands, so you will need
to perform your own tests and modify
your programs accordingly. We’ll talk
more about this in our next column,
speaking of which…
FIND ALL YOUR ELECTRONIC
COMPONENTS IN ONE PLACE
Next time
In our next column, we will begin
displaying real alphanumeric values
on our 7-segment displays. Additionally, we will incorporate a full
complement of these modules into
our games console. There’s also a
batting chance we’ll be adding the
eight push-button switches to the
console’s front panel.
As always, if you have any thoughts
you’d care to share on anything you’ve
read here, feel free to drop me an email
PE
at max<at>clivemaxfield.com
BASIC MICRO
E L E CT R O N I C S C O M P O N E N T S U P P L I E R
w w w . basicmicro . co . u k
High-quality, genuine parts
Practical Electronics | November | 2025
33
|