This is only a preview of the September 2025 issue of Practical Electronics. You can view 0 of the 80 pages in the full issue. Articles in this series:
Items relevant to "Compact Hi-Fi Headphone Amplifier, part one":
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 9: in which we enjoy a crash course in logic
I
bet I know whare you are thinking (you weren’t expecting that,
were you?). Like me, you’ve likely
spent the past month pondering something I wrote in my previous column
pertaining to our new all-singing,
all-dancing software shift registerbased switch debounce solution. I’ll
remind you about that in a moment,
but first…
Don’t blink!
I love Doctor Who. I watched the
first episode from behind the safety
of the sofa when I was six years old.
This aired on Saturday, November
23, 1963, and I remember it as though
it were yesterday. I think I’ve seen
every episode since.
I was saddened when the final episode of the classic series aired on December 6, 1989. I was delighted when
the first episode of the Doctor Who
reboot aired on March 26, 2005. Do
you remember the “Blink” episode,
which graced our screens on June
9, 2007? This introduced the iconic
Weeping Angels, along with the Doctor’s admonition: “Whatever you do,
don’t blink!”
The reason I mention this here is
that our latest and greatest switch
debounce solution introduces a
lag (delay) between a switch being
pressed and our taking some action,
such as lighting one of our lightemitting diodes (LEDs). If you blink,
you’ll miss this delay, but what if
you don’t blink?
And thus, we return to what I said
last month. As you may recall, I wrote:
A delay under 10ms is typically
imperceptible to humans. A delay
of 10-30ms may be slightly noticeable to very sensitive users, especially in contexts that require fast feedback (eg, gamers or musicians), but
it generally still feels instantaneous.
26
A delay of 30-100ms becomes noticeable to the average person, who may
describe the system as feeling “laggy”
or “slow to respond”.
I went on to write, “… as a rule of
thumb, we should aim to keep any
button-to-action delays under 50ms”. I
have faith in this 50ms value because
a grizzled old engineer once told me
so many moons ago, when I was but a
young sprout. And why do you have
faith in this 50ms value? It’s because
I’ve turned into a grizzled old engineer who’s telling you so!
I closed that portion of our discussions with, “However, don’t take my
word for any of this because this is a
test you can easily perform for yourself using your Arduino, pushbuttons
and LEDs.”
Of course, this is when I started
to wonder about the veracity of my
50ms pontification, so I thought we
could check it out together.
We’re short of nothin’ we’ve got
One of my granddad’s go-to sayings
was, “We’re short of nothin’ we’ve
got”, which he deployed when he
had to make do with something he
did have, even if it wasn’t ideally
suited to the purpose.
Now, we could easily build a standalone breadboard-based test rig
using three switches, one LED and
any associated resistors. However,
we already have three momentary
pushbutton switches on our existing
shift register breadboard.
If you wish to refresh your memory,
you can download a copy of the latest
and greatest version of the current
state of our board in the file named
CB-sep25-brd-01.pdf (as usual, all files
mentioned in this column are available as part of the September 2025
download package from the PE website at https://pemag.au/link/ac6z).
Note that we aren’t going to modify
the existing linear feedback shift register (LFSR) circuit on our breadboard
in any way. We’re just going to temporarily change what we do with the
switches in software.
For the purposes of this experiment,
we can use our left-hand switch to
perform the role of “Up” (increase
the delay between the switch being
pressed or released and the LED
changing). Our middle switch can
perform the role of “Down” (decrease
the delay). And we can use the righthand switch to trigger actionable
events, like activating and deactivating our LED, so we’ll call it “Trig.”
The Arduino has an onboard orange
LED connected to digital input/output
(I/O) pin 13, and we can certainly use
this. However, it is a little small, so
we could optionally add a bigger LED
to our breadboard (“I like big LEDs
and I cannot lie” – Sir Blinksalot).
This means that the way we are
currently visualising the switch portion of our breadboard, including the
addition of the optional LED, is as
depicted in Fig.1.
The ironies of life
As you know, we’ve spent the last
two columns developing a superspiffy switch debounce solution that
would make our mothers squeal in
delight (if they cared). So, it’s a tad
ironic that, for this experiment, we
need to regress to employ our first debounce solution code from the June
2025 issue.
This is because we need to trigger
things from the leading (first) edge in
the switch bounce signal. I’ve copied
this program over into the file named
CB-sep25-code-01.txt to make things
easy for you (that’s just the sort of
fellow I am).
Also, we are going to reuse some
Practical Electronics | September | 2025
Optional
Up Down Trig
0V
To shift register
k
AREF
GND
13
12
~11
~10
~9
8
7
~6
~5
4
~3
2
TX-1
RX-0
a
DIGITAL IN/OUT (PWM ~)
Orange LED
Fig.1: we're
adding to and
modifying some
of our existing
breadboard
layout, so we
can reuse what
we already have
to save time and
effort.
Arduino
Green LED
of the definitions and other snippets
from the two versions of the shift
register code we created last month,
which I’ve copied over as files named
CB-sep25-code-02.txt and CB-sep25code-03.txt (you’re welcome).
I then munged bits and pieces of
these three programs together to create
our new test program, which you can
access in the file named CB-sep25code-04.txt
Dissecting our test program
It’s worth explaining this program
in a little detail. As usual, we start
with some definitions that will improve the quality of our lives by
making our code easier to understand
and maintain. These definitions are
shown in Listing 4(a). We are using
the convention that the listing number
(4 in this case) corresponds to the
numerical part of the matching code
file name (“04” here).
The JUST_A_BIT on line 1 is the
delay we use to make sure the switch
has stopped bouncing after we’ve done
whatever we wanted to do. Lines 4
through 9 are where we define the
active and inactive levels associated
with the signals we are reading from
our three switches. Meanwhile, lines
12 through 17 are where we define the
levels associated with the signals we
are using to drive the shift register.
“Hang on!”, I hear you cry, “I
thought we weren’t going to use the
shift register in this experiment.”
You’re right, we aren’t. On the other
Practical Electronics | September | 2025
hand, we don’t want to leave the
control signals to the shift register
floating, because this could potentially cause it to display random, unwanted, and annoying values, so we
will make use of these definitions to
clear the register and ensure it stays
that way.
We use Lines 20 and 21 to define
the active and inactive states associated with the orange LED onboard
the Arduino, along with the optional
LED on the breadboard (I’ve added
one to mine).
Lines 24 through 27 are the definitions associated with the lag (delay)
we are going to introduce between
the pressing (and releasing) of the
switch and the activating (and deactivating) of the LED.
We’ve set the INITIAL_LAG (the one
we’ll use when the program starts
running) to be 50ms. This program
will allow us to experiment with different lags that we can set between
some minimum and maximum values,
which I arbitrarily decided to set to
10 and 1000, respectively.
Based on this, we could have set the
MIN_LAG to 10ms. However, making
it equal to JUST_A_BIT, which we’ve
already defined as being 10ms, means
that we can never cause the minimum lag to be less than the switch
debounce delay.
Last, but not least, we’ve defined
INC_DEC_LAG to be 5ms. This is the
amount by which we will increment
or decrement our lag value when we
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define JUST_A_BIT
10
// SW (switch) signals
#define UP_INACTIVE
LOW
#define UP_ACTIVE
HIGH
#define DOWN_INACTIVE
LOW
#define DOWN_ACTIVE
HIGH
#define TRIG_INACTIVE HIGH
#define TRIG_ACTIVE
LOW
// SR (shift register) signals
#define RCLK_INACTIVE
LOW
#define RCLK_ACTIVE
HIGH
#define SRCLK_INACTIVE LOW
#define SRCLK_ACTIVE
HIGH
#define SRCLR_INACTIVE HIGH
#define SRCLR_ACTIVE
LOW
// Display LED stuff
#define LED_INACTIVE
#define LED_ACTIVE
LOW
HIGH
// Lag from switch to LED stuff
#define INITIAL_LAG
50
#define MIN_LAG JUST_A_BIT
#define MAX_LAG
1000
#define INC_DEC_LAG
5
Listing 4(a): these definitions make the
rest of our code easier to understand.
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Pins from switches
int PinUpFromSw
= 2;
int PinDownFromSw = 3;
int PinTrigFromSw = 4;
// Pins to shift register
int PinSrclrToSr = 5;
int PinSrclkToSr = 6;
int PinRclkToSr
= 7;
// Orange LED on Arduino board
int PinToLed
= 13;
// Declare global variables
int Lag = INITIAL_LAG;
Listing 4(b): these are our pin and global
variable declarations.
press the Up or Down buttons, respectively. If you’re pedantic, you
can make this delay smaller, all the
way down to 1ms, but then it will
take you a lot of pushbutton presses
to get anywhere.
Alternatively, you could make it
larger, like 10ms, for example, but
then you’ll be sacrificing resolution.
Next, as shown in Listing 4(b), we
declare the names we are going to use
for our pins and assign appropriate
pin numbers to them. Also, on line
43, we declare a global variable called
Lag and assign it a starting value of
INITIAL_LAG, which we previously
defined as being 50ms.
In the case of the setup() function
(not shown here), the first thing we
do is initialise serial communications
between our Arduino and host computer using the following statement:
Serial.begin(9600);
We previously introduced these
serial communications in the October
27
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Do this over and over again
void loop ()
{
// Increase the Lag
if (digitalRead(PinUpFromSw) == UP_ACTIVE)
{
if (Lag < MAX_LAG) Lag += INC_DEC_LAG;
DisplayLag();
delay(JUST_A_BIT);
}
// Decrease the Lag
if (digitalRead(PinDownFromSw) == DOWN_ACTIVE)
{
if (Lag > MIN_LAG) Lag -= INC_DEC_LAG;
DisplayLag();
delay(JUST_A_BIT);
while (digitalRead(PinDownFromSw) == DOWN_ACTIVE)
{
}
delay(JUST_A_BIT);
(b) Baud rate
Fig.2: the Arduino Serial Monitor window.
}
BUF
// Respond to trigger switch
if (digitalRead(PinTrigFromSw) == TRIG_ACTIVE)
{
delay(Lag);
digitalWrite(PinToLed, LED_ACTIVE);
while (digitalRead(PinTrigFromSw) == TRIG_ACTIVE)
{
}
delay(Lag);
digitalWrite(PinToLed, LED_INACTIVE);
}
}
Listing 4(c): this main loop is where all the magic takes place.
2023 issue of our Arduino Bootcamp
series. As a brief reminder, the serial
port (usually over USB on most Arduino boards) can be used to send
data to and from the Arduino and our
host computer, often for debugging or
communication with other devices.
The 9600 value is the default baud
rate, the speed at which data is transmitted and received. For our purposes, we can consider the baud rate as
being the number of bits being transmitted per second.
Next, we specify the modes (INPUT
or OUTPUT) associated with our pins,
set the outputs to their inactive states,
and then use the appropriate signals
to clear the shift register on our breadboard so we won’t get distracted by
errant segments being activated on
our 8-segment display.
Finally, we call a little function
called DisplayLag() (not shown here)
that you’ll find at the end of the program. This function uses serial commands to display the current value
of Lag on the screen of our host computer.
Now, let’s turn our attention to
the interesting stuff, which we find
in the loop() function, illustrated in
Listing 4(c).
28
(a) Serial Monitor icon
while (digitalRead(PinUpFromSw) == UP_ACTIVE)
{
}
delay(JUST_A_BIT);
Starting on line 89, we check to see
if our Up switch has been pressed. If
so, we add INC_DEC_LAG to the current Lag value, call our DisplayLag()
function to display the new value on
our host computer, then we wait for
the switch to be released.
Similarly, starting on line 102,
we check to see if our Down switch
has been pressed. If so, we subtract
INC_DEC_LAG from the current Lag
value, display the new value on our
host computer, then we wait for the
switch to be released.
Starting on line 115, we check to
see if the Trig has been pressed. If so,
we pause for the Lag time, then we
activate the LED. Then, starting on
line 120, we wait for the Trig switch
to be released. When this occurs, we
pause for the Lag time, and then we
deactivate the LED.
Running our test program
While we are writing and verifying our programs, the top half of the
screen in the Arduino’s integrated development environment (IDE) shows
our source code. Meanwhile, the
bottom half of the screen is the console
area that typically shows errors and
warning messages when we compile
NOT
y
a
y
a
a
y
a
y
0
1
0
1
0
1
1
0
Fig.3: BUF and NOT (inverter) gates.
our code, along with upload progress
and results, like “Done Uploading”.
Plug the USB cable into the Arduino to power everything up and then
upload our test program. Next, click
the Serial Monitor icon located in the
top right-hand corner of the IDE, as
seen in Fig.2(a).
Also look at the field in the upper
right-hand corner of the console area
and make sure that the host computer’s baud rate is set to 9600 (the same
value we specified in our program),
as in Fig.2(b). Both devices (Arduino and computer or serial monitor)
must use the same baud rate to communicate properly.
You should see something like the
following displayed in your console area:
Lag = 50ms
Press the Up button a few times
and observe the lag value increase
in 5ms increments:
Lag = 50ms
Lag = 55ms
Lag = 60ms
Lag = 65ms
Next, press the Down button the
same number of times to return the
lag value to its original 50ms value.
Practical Electronics | September | 2025
NOT
y
a
Fig.4: two
NOT gates
connected
in series are
equivalent
to a single
BUF gate.
BUF
NOT
≡
y
y
a
a
y
y
a
y
0
1
1
0
0
1
0
1
0
1
a
b
y0 y1 y2 y3 y4 y5 y6 y7 y8 y9 y10 y11 y12 y13 y14 y15
0
0
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
0
1
1
0
0
1
1
0
0
1
1
0
0
1
1
1
0
0
0
0
0
1
1
1
1
0
0
0
0
1
1
1
1
1
1
0
0
0
0
0
0
0
0
1
1
1
1
1
1
1
1
NOR
XOR
XNOR
NAND
AND
OR
Fig.5: a logic gate with two single-bit inputs has 16 potential output permutations.
Now for the fun part. Press and
hold the Trig button and observe the
orange LED light up on the Arduino
board (and the optional big LED on
your breadboard, if you added one).
Then release the Trig button and observe the LED(s) turn off.
Did you perceive any delay between
pressing (or releasing) the button
and change in the LED? Before performing this experiment, I honestly
believed that I’d be able to perceive
a lag of 50ms, but such was not the
case. Maybe I could have done so
when I was a younger man, but the
years have not been kind.
I’m embarrassed to tell you how
high I had to make the lag value
before I could spot a delay between
pushing the button and observing a
response, so I won’t. Now I’m going
to try this on people of varying ages,
including my wife (Gigi the Gorgeous)
and the kids who live next door. I’d
be interested to hear about your own
experiences here; I will report back
further next month.
Homework assignment #1
Since we’re here, why don’t we
create a complementary “Reaction
Tester” experiment using the orange
LED (and optional LED) and one or
more of our pushbutton switches?
The idea is for the Arduino to wait
some random amount of time before
illuminating the LED(s). As soon as
we see the LED light up, we press
the button, and the Arduino displays
the time it took for us to respond
on our host computer. How about if
Practical Electronics | September | 2025
you go first, and then I’ll present my
solution to this assignment in next
month’s column?
That’s logical
Now, hold on to your hat, because
this next part is going to be tremendously exciting (have I ever lied to
you before?). In the July 2025 issue,
I posed a couple of logic problems
pertaining to the implementation of
our LFSR. I’ll prompt you regarding
those posers presently. First, however,
we need to delve a little deeper into
the subject of primitive logic gates.
Let’s start with the low-hanging
fruit, which are gates with only one
input and one output: BUF and NOT
(aka. INV), as illustrated in Fig.3. Observe the ‘bobble’ (or ‘bubble’) on the
output of the NOT gate. This indicates an inverting function.
Also observe that we are using horizontal lines over output signal names
(eg, y) to indicate their inverted attribute. This is called “overbar notation”
or “bar notation”. It’s common to say
“Y-bar” in verbal communication, or
when thinking about it in your head
(which, in my experience, is the best
place to think about things).
An alternative would be to employ
a purely textual terminology, like yb,
where the ‘b’ stands for “bar”.
Remember that the 0s and 1s in
these truth tables equate to voltages
in the real world: 0V and +5V, respectively, in the case of the integrated
circuits (ICs) we are working with.
When I was a young sprog starting out in digital electronics, I really
didn’t have a clue (not that things
are much better now). I remember
how the truth table for the BUF gate
made sense, but I found it hard to
wrap my brain around the truth table
for the NOT. How could a 0 (0V) on
the input result in a 1 (+5V) on the
output, for example?
I didn’t realise that these gates also
have 0V and +5V power rails that are
separate from the inputs and outputs,
but that they are not shown in our
symbols to keep things simple.
It’s not wrong
I’m sure you’ve heard the expression, “two wrongs don’t make a right”
(but three lefts do). Well, that’s as may
be, but it’s certainly true that two
NOTs connected in series (one after
the other) are functionally equivalent
to a BUF (Fig.4).
I don’t know about you, but I tend
to think of non-inverting gates like
BUF as being simpler than inverting
functions like NOT. This may be true
conceptually, but it’s not the case in
actuality.
With the CMOS (complementary
metal-oxide semiconductor) technology that is used to create most of today’s digital ICs, a NOT gate requires
two transistors. A BUF gate is essentially formed from two NOT functions
in series (as in Fig.4), so it requires
four transistors. Furthermore, a NOT
gate has only one stage of delay (so
it’s faster), while a BUF gate has two
stages of delay (so it’s slower).
In fact, it’s generally fair to say
that inverting functions are simpler
and faster than their non-inverting
counterparts in any implementation
technology (electronic, hydraulic,
mechanical, pneumatic…).
If you feel an overwhelming desire
to discover how these gates are built
from transistors, this is one of the
topics I talk about in my book, Bebop
to the Boolean Boogie (https://pemag.
au/link/abzi).
A preponderance of permutations
Now let’s ‘up our game’ by moving
to gates with two inputs and one
output. Let’s call the inputs a and
b. These inputs have 22 = 4 possible
combinations of 0s and 1s: 00, 01, 10,
and 11. This means there are 24 = 16
possible permutations; that is, 16 different ways to assign a 0 or 1 output
to each of the four input combinations. Let’s call these permutations
y0 through y15, as shown in Fig.5.
Depending on what we are trying to
achieve at the time, we could end up
employing any of these permutations
in a design. In practice, however, we
use some more often than others, so
29
AND
a
&
b
OR
a
y
|
b
XOR
a
y
y
^
b
a
AND
NOT
y
&
b
NAND
a
y
≡
y
&
b
a
b
y
a
b
y
a
b
y
a
b
y
y
a
b
y
0
0
1
1
0
1
0
1
0
0
0
1
0
0
1
1
0
1
0
1
0
1
1
1
0
0
1
1
0
1
0
1
0
1
1
0
0
0
1
1
0
1
0
1
0
0
0
1
1
1
1
0
0
0
1
1
0
1
0
1
1
1
1
0
(a) Making a NAND by inverting an AND
NAND
a
a
y
&
b
NOR
a
y
|
b
XNOR
y
^
b
a
a
b
y
a
b
y
a
b
y
0
0
1
1
0
1
0
1
1
1
1
0
0
0
1
1
0
1
0
1
1
0
0
0
0
0
1
1
0
1
0
1
1
0
0
1
NAND
&
b
Fig.6: symbols & truth tables for the named 2-input logic functions.
NOT
AND
a
y
y
≡
y
&
b
a
b
y
y
a
b
y
0
0
1
1
0
1
0
1
1
1
1
0
0
0
0
1
0
0
1
1
0
1
0
1
0
0
0
1
(b) Making an AND by inverting a NAND
remains visible.
Similarly, one Fig.7: inverting AND and NAND logic gates.
way to envisage
what’s happening in Fig.7(b) is that electronic gates are screamingly fast.
we ‘push’ the NOT gate back into the With respect to what we are doing in
NAND, and the two bobbles cancel our experiments, any differences in
Bending over backwards
each other out.
delay between an AND and a NAND
I’m afraid the next bit involves
The funny thing is that Fig.7(a) are infinitesimal and imperceptible.
some mental gymnastics (I’ll bend is the way we usually think about This really becomes of interest only
over backwards to be flexible). We things, but Fig.7(b) is the way things to people designing far more comtake NAND and NOR to mean “not are in the real world. As with NOT plex chips or circuits.
AND” and “not OR,” respectively, and BUF gates, it’s easier to create
Tricky XORs
and this is the way we tend to think an inverting function than its nonOne question many people ask is
about things.
inverting counterpart.
It’s certainly true that we can make
In the case of a NAND gate imple- why we say XNOR (“exclusive NOR”)
a NAND by inverting the output of mented in CMOS, each input requires instead of NXOR (“Not XOR”). In fact,
an AND, as illustrated in Fig.7(a). two transistors, so a two-input NAND some people do use these terms inContrariwise, we can make an AND employs four transistors. Since a terchangeably, but there is a subtle
by inverting the output of a NAND, NOT gate requires two transistors, a distinction.
The problem is that saying NXOR
as illustrated in Fig.7(b). We can do 2-input AND gate, which is formed
the same with OR and NOR gates, from a NAND and a NOT, consumes explicitly emphasises the NOT operation on an XOR. However, we don’t
but I’ll leave that as ‘an exercise for six transistors. Furthermore, a NAND
the reader’.
gate has only one stage of delay (so actually need to apply a NOT gate to
One way to visualise what’s hap- it’s faster), while an AND gate has an XOR gate to generate a NXOR gate
(although we could if we wanted to)
pening in Fig.7(a) is that we ‘push’ two stages of delay.
the NOT gate symbol back into the
This may be a good time to note that because an XNOR is a defined gate
in its own right. To put this another
AND gate symbol until only its bobble ‘slower’ is relative, because all these
way, XOR and XNOR gates use the
same number of transistors, and both
XOR
XOR
a
a
have only one stage of delay.
There are multiple implications
XOR
XOR
^
^
b
b
that stem from this. Let’s start with
y
the fact that it’s possible for AND
XOR
^
^
c
c
and OR (and NAND and NOR) gates
y
to have more than two inputs. Re^
^
d
d
member that each input to a NAND
gate requires two transistors, so a
4-input NAND gate would employ
Fig.8: two ways to implement a 4-input XOR function.
we give these special names: AND,
OR, XOR, NAND, NOR and XNOR.
The symbols and truth tables for
these named 2-input logic functions
are shown in Fig.6.
XOR
^
30
NOT
XOR
≡
^
XOR
≡
^
XNOR
≡
^
Fig.9: 3
ways to
make an
XNOR
using XOR
& NOT.
Practical Electronics | September | 2025
eight transistors, while its 4-input
AND counterpart would consume
ten transistors.
By comparison, physical XOR and
XNOR gates can only ever have two
inputs. So, what do we do if we require an XOR function with more
inputs? Well, we just need to use multiple 2-input XOR gates. For example, two ways to implement a 4-input
XOR function using three 2-input
XOR gates are illustrated in Fig.8.
As we previously discussed, we
can generate a NAND by inverting the
output of an AND, but not by inverting either of its inputs. Similarly, we
can generate a NOR by inverting the
output of an OR, but not by inverting either of its inputs.
By comparison, we can generate an
XNOR from an XOR by inverting its
output or either of its inputs, as per
Fig.9. Similarly, we can generate an
XOR from an XNOR by inverting its
output or either of its inputs.
Parity checking
One of the simplest forms of error
detection used in digital communication and storage systems is known
as ‘parity checking’. This involves
adding an extra bit, called a parity
bit, to a set of other bits to ensure the
integrity of that data during transmission or storage.
There are two types of parity: even
parity and odd parity. Even parity
is where the parity bit is set so that
the total number of 1s in the entire
binary word (including the parity
bit) is even. In the case of odd parity,
the parity bit is set so that the total
number of 1s is odd.
Consider a communications channel. The transmitter will generate
a parity bit for each word it transmits. This bit will be transmitted
along with the word of data. The receiver will also generate a parity bit
for each word it receives, and it will
compare its parity bit to the one from
the transmitter.
If the data is inadvertently altered
(+5V)
VCC
14
6A
13
6Y
12
5A
11
5Y
10
4A
9
4Y
8
(eg, by electromagnetic noise), causing a single data bit or the parity bit
to ‘flip’, the parity check at the receiver will fail, indicating an error.
An 8-bit word is called a byte. A
4-bit word is called a nybble. The
idea that two nybbles make a byte
shows that electronic and computer
engineers do have a sense of humour,
albeit a rudimentary one.
The even and odd parity values associated with a 4-bit data nybble are
shown in Fig.10. How would we go
about generating these values? Well,
let’s start by looking at the top four
values in the even parity column:
0, 1, 1, 0. Hmm, this is the same sequence as the output from a 2-input
XOR. How about the next four values
in the even parity column: 1, 0, 0, 1?
Gosh, that’s the same sequence as for
an XNOR.
It doesn’t take us long to realise
that we can generate an even parity
bit that we can call p by simply feeding the four data bits into either of
the 4-input XOR functions we described in Fig.8.
Furthermore, as per our discussions
pertaining to Fig.9, we could generate
an odd parity bit, that we can call p,
by inverting (adding a NOT gate to)
the output or to any of the inputs to
our 4-input XOR function. Actually,
we can generate the p signal by inverting multiple inputs and outputs
to our 4-input XOR function, just so
long as the total number of inversions is odd.
Little logic gates
There are hundreds of different
7400 device types (you can see a list
at https://w.wiki/EY6R). In the case
of devices containing only primitive logic gates, we have made use of
three types in our experiments thus
far (Fig.11).
As part of our Arduino Bootcamp
columns from January 2023 through
December 2024, we introduced the
74xx04, which contains six NOT
gates, and the 74xx08, which con(+5V)
VCC
14
4B
13
4A
12
4Y
11
3B
10
&
3A
9
3Y
8
Parity
Data
# 1s
Even
Odd
0000
0
0
1
0001
1
1
0
0010
1
1
0
0011
2
0
1
0100
1
1
0
0101
2
0
1
0110
2
0
1
0111
3
1
0
1000
1
1
0
1001
2
0
1
1010
2
0
1
1011
3
1
0
1100
2
0
1
1101
3
1
0
1110
3
1
0
1111
4
0
1
Fig.10: parity values for four-bit data.
tains four two-input AND gates. In
our current Weird and Wonderful
series, we reused the 74xx04 and
added the 74xx86, which contains
four two-input XOR gates.
For the sake of reference, other devices of this ilk include the 74xx00
(quad two-input NAND), 74xx02
(quad two-input NOR) and 74xx32
(quad two-input OR).
Remember that the “xx” in these
part numbers represents the underlying logic technology used inside the
chip. Two commonly used technologies are “LS” (low-power Schottky)
and “HC” (high-speed CMOS).
When building circuits, it’s generally best to stick to a single logic
family. Personally, I prefer the HC
variants because they’re more tolerant
(+5V)
VCC
14
4B
13
&
&
4A
12
4Y
11
3B
10
^
&
3A
9
3Y
8
^
^
^
1
2
3
4
5
6
7
1
2
3
4
5
6
7
1
2
3
4
5
6
7
1A
1Y
2A
2Y
3A
3Y
GND
(0V)
1A
1B
1Y
2A
2B
2Y
GND
(0V)
1A
1B
1Y
2A
2B
2Y
GND
(0V)
SN74xx04
SN74xx08
SN74xx86
Fig.11: three of the 'primitive' logic chips we’ve used before. They each contain multiple gates (four or six) for our convenience.
Practical Electronics | September | 2025
31
of supply voltage variations, both in
terms of ripple and slow drift. That
said, it’s perfectly fine to use HC outputs to drive LS inputs.
However, using LS outputs to drive
HC inputs can be a problem, since
the LS output high voltage may not
meet the HC input threshold for a
logic high. Having said this, there
shouldn’t be any problem mixing
LS and HC devices for what we’re
doing with our specific breadboard
setup, but it would be frowned upon
by professional engineers (we’ll discuss this in more detail next month).
Fig.12: an XNORbased 7-bit LFSR.
XNOR
^
input XNOR gate in our
kit of parts. What we
did have was a two-
input XOR in the form
of our 74xx86 device.
So, we inverted the
output of this XOR
using one of the NOT
gates in our 74xx04
to realise an XNOR,
as shown in Fig.12(b).
SER
This brings us back to the homework assignments I posed in the July
2025 issue. Before we address these,
let’s first remind ourselves that we
started by introducing the concept of
LFSRs. Next, we decided to create a
7-bit LFSR using a single two-input
XNOR gate, as illustrated in Fig.12(a).
The reason for using XNOR rather
than an XOR is that an XOR-based
LFSR cannot cycle through the all-0s
state (it would get locked into the
all-0s value). This is unfortunate
because we start by clearing our
74HC595 shift register to all 0s. Happily, an XNOR-based LFSR can cycle
through the all-0s state (it’s the all-1s
state it doesn’t like).
Sad to relate, we didn’t have a two-
1
2
3
4
5
6
7
(b)
(c)
(d)
(e)
(f)
(g)
(h)
(a) 7-bit LFSR with an XNOR.
NOT
XOR
^
SER
What? No NOTs?
Our XNOR-based 7-bit LFSR
0
(a)
For the first part
of this assignment,
I wrote, “… suppose we had only
our 74xx86 chip containing its four
2-input XOR gates (so, no 74xx04
with its six NOT gates, and no 74xx08
with its four 2-input AND gates). Can
you think of a way to implement the
desired XNOR function?”
This one is easy peasy lemon
squeezy. Observe the truth table for
the 2-input XOR gate in Fig.6. If we
connect one of these inputs to logic
1, the gate will invert the other input,
which means it’s acting as a NOT, as
illustrated in Fig.13(c).
0
1
2
3
4
5
6
7
(a)
(b)
(c)
(d)
(e)
(f)
(g)
(h)
(b) XOR + NOT = XNOR
Fig.14: Implementing the XOR function with four NAND gates.
signment, where I wrote, “… suppose
we had only our 74xx04 with its six
NOT gates and our 74xx08 with its
four 2-input AND gates (so, no 74xx86
with its four 2-input XOR gates). Can
you think of a way to implement the
desired XNOR function?”
There are various ways in which
we can achieve this. For example,
let’s start by implementing an XOR
using four 2-input NAND gates as
shown in Fig.14(b).
Your first thought might be that
this is all very nice, but we don’t
have any NAND gates. My response
would be that we can implement a
NAND using an AND and a NOT, as
illustrated in Fig.7(a). We can think
of this as AND → NOT.
Of course, your next thought will
probably be that it’s all very well our
creating an XOR, but what we really
need is an XNOR, as illustrated in
Fig.12(a). This is where things get interesting. We still have two NOT gates
left, so we could use one to invert the
output y from our final NAND gate to
give the y signal we really desire. We
can think of this as NAND → NOT.
On the other hand, remember that
we are forming our NANDs using
ANDs followed by NOTs. This means
each NAND → NOT is actually implemented as AND → NOT → NOT.
From our previous discussions,
we know that the two NOTs cancel
each other out (logically speaking),
so all we need to do to achieve our
XNOR is to not include the NOT on
the gate driving the y output (this is
great because not doing something
is one of the things I do best). The
result is illustrated in Fig.15.
I know all of this can be a little
brain-boggling at first, but logic design
engineers love this sort of thing. Sad
to relate, most of these manipulations are performed automatically by
design tools these days. On the other
hand, it’s always useful to know how
to do this ourselves, especially when
32
Practical Electronics | September | 2025
What? No XORs?
Things get a little trickier when it
comes to the second part of this as+5V
XNOR
XOR
≡
^
(a) What we
really want
NOT
XOR
XOR
≡
^
^
^
(b) Our original
NOT-based solution
(c) Our new purely
XOR-based solution
Fig.13: implementing the XNOR function with two XOR gates.
(a) What we want
(b) A purely NAND-based XOR
a
d
&
NAND
XOR
a
y
^
b
≡
NAND
c
&
NAND
&
NAND
e
&
b
a
b
y
a
b
c
d
e
y
0
0
1
1
0
1
0
1
0
1
1
0
0
0
1
1
0
1
0
1
1
1
1
0
1
1
0
1
1
0
1
1
0
1
1
0
y
we are working at the 7400-series
logic-chip level.
(a) What we want
(b) NAND (and AND)-based XNOR
a
Homework assignment #2
I recently went to see the latest
Mission Impossible film with a group
of friends. This prompts me to say,
“Your mission, should you choose
to accept it, is to implement the ‘No
NOTs’ and ‘No XORs’ solutions discussed above on your breadboard to
verify that they work as expected.”
d
&
NAND
XNOR
a
y
^
b
≡
AND
c
&
NAND
&
NAND
e
&
b
BASICally speaking
a
b
y
a
b
c
d
e
y
I know we’ve been wandering
around a bit recently, but now we
are quivering in anticipation, poised
to return to the next stage of implementing our retro games console,
which is shown in Photo 1.
In our case, we are planning on
controlling our console with an Arduino. However, as we’ve previously discussed, this processor will be
presented in the form of a “game
cartridge” that plugs into the console (the green board in the upper
left of Photo 1).
This means that other users could
build the same base console while
implementing their games cartridges
using different processors.
In fact, this is the case with the prototype shown in Photo 1, which was
created by my friend Joe Farr. Joe is a
big fan of PIC microcontrollers from
Microchip, so that’s the processor he
used in Photo 1.
The reason I mention this here is
on the off chance you use PICs yourself, or plan on doing so at some
stage in the future. PICs offer a lot
of advantages in terms of size and
cost, but programming them can be
a great big pain.
One solution I totally recommend
involves the Positron compilers, which
were developed by my friend
Les Johnson. These are high-
level compilers designed
to make programming
PIC microcontrollers
easier. They use a language similar to BASIC
and let developers write
clear, structured code
to control the microcontroller hardware.
The reason I wrote
“compilers” (plural) is
that there are two versions:
the Positron8 for 8-bit PIC devices
(like PIC16 and PIC18), and the Positron16 for 16-bit PIC devices (like
PIC24 and dsPIC33).
Happily, this is all largely hidden
from the user by the Positron IDE,
which is called Positron Studio. You
specify the PIC you are using in your
0
0
1
1
0
1
0
1
1
0
0
1
0
0
1
1
0
1
0
1
1
1
1
0
1
1
0
1
1
0
1
1
1
0
0
1
Practical Electronics | September | 2025
y
Fig.15: Implementing the XNOR function with three NAND gates and an AND gate.
code, and this causes
the IDE to automatically employ the appropriate compiler.
There’s also a very
active users forum
where you can ask
questions and share
information.
All I can say is that
Positron Compilers
are fantastic value at
£39.99 (https://pemag.
au/link/ac70).
I've no idea why
they call me
"Max Max"!
Next time
In our next column,
we are going to ramp
up work on our retro games console,
including wiring up the 7-segment
displays and the eight pushbutton
switches on the front panel. We will
then consider what's necessary to
control the whole thing with our
Arduino board.
As always, if you have any thoughts
you’d care to share on anything you’ve
read here, please feel free to email me
PE
at max<at>clivemaxfield.com
Photo 1: an early version of our
retro games console
(Photograph by
Joe Farr).
33
|