This is only a preview of the July 2023 issue of Practical Electronics. You can view 0 of the 72 pages in the full issue. Articles in this series:
|
Max’s Cool Beans
By Max the Magnificent
Arduino Bootcamp – Part 7
I
can’t believe we are already
slip-sliding our way into the second
half of the year. Sad to relate, I haven’t
prepared a speech and I don’t have anything appropriate to wear. When I was a
kid, it seemed elderly folks liked nothing
better than to endlessly babble about how
time seems to pass faster the older you
get. You can only imagine my surprise to
discover that the ancients actually had
a clue as to what they were droning on
about. Now, when I blink, another day
disappears. When I sneeze, I lose another
week. And if I… but we digress.
Round, square, curly…
I really enjoy receiving emails from members of the PE community, especially
communiques that commence by conveying how wonderful I am (hint, hint).
By some strange quirk of fate, I recently
received such a missive from someone
we’ll call Alan (because that’s his name).
After making mention of how much he
was enjoying our Arduino Bootcamp
series (well played Alan), he continued
as follows: ‘Whilst I have done some
programming in the past, including BBC
BASIC and assembly language for use
on PIC micros, C/C++ is new to me, so I
appreciate the way in which your series
explains how the programs are built up.
Having said this, I do have difficulty with
some of the syntax, especially when to
use curly brackets.’
‘Well, strike me with a kipper,’ I thought
(I think this a lot, but don’t ask me why),
‘if there’s one topic I love to waffle about
endlessly, that topic is brackets in general and curly brackets in particular’ (I
really do need to get out more). As you
are doubtless aware, there are four primary types of brackets (Fig.1). In British usage they are known as: ‘round
brackets’ (or simply ‘brackets’), ‘square
brackets’, ‘curly brackets’ (known to
some as ‘squiggly brackets’), and ‘angle
brackets’; in American usage these are
()
[]
{}
<>
Round
brackets
or
parentheses
Square
brackets
or
brackets
Curly (squiggly)
brackets
or
braces
Angle
brackets
or
chevrons
Fig.1. Different types of brackets.
56
respectively known as: ‘parentheses’,
‘brackets’, ‘braces’, and ‘chevrons’.
Different programming languages make
use of some or all of these symbols for
diverse purposes. In fact, the same language can employ the same symbols
in multiple roles. Don’t panic! This is
nothing we’re not used to. Consider the
artful apostrophe, for example, which
can serve to indicate possessive cases
(Harry’s hat), contractions (Harry’s lost
his hat), and omitted letters (‘Arry’s ‘at)
in regular written English.
Squiggly wiggly
As we previously discussed in a couple
of Coding Tips and Tricks columns (PE,
April and June 2020), in the case of C/
C++, one use of curly (squiggly) braces is
to denote a block of code in which variables can be declared. The most obvious
example of this is as part of a function
declaration and definition, for example:
void MyFunction () // Declaration
{
// Zero or more
Definition
// statements
}
Remember that, unlike some languages,
C and C++ really don’t care overmuch
about whitespace characters such as the
space, tab (ie, horizontal tab), vertical tab,
line feed (newline), carriage return and
form feed. Having said this, my personal
preference is for { } pairs to be vertically
aligned, as shown above, because this
makes our code easier to parse and debug.
Also, if I’m using a simple editor, such
as the one found in Version 1 of the Arduino’s integrated development environment (IDE), I prefer to use 4-space
indentation. In the case of a more sophisticated editor, such as the one found
in Arduino IDE V2 (the version we decided we were going to use at the beginning of this series), which automatically
indicates indented blocks of code, I use
2-space indentation to match what the
editor wants to do.
Observe the comment in the above code
saying, Zero or more statements.
Why would we wish to create a function with nothing in it? Well, as we
noted in an earlier column (PE, April
2023), one way to approach creating a
new program is to start off with a skeleton (bare bones) framework, and this
can include using one or more stub
(empty) functions whose purpose is
to check that they can be called from
elsewhere in the program and that everything hangs together. We can return
to fill in their contents later.
When we talk about the ‘scope’ of a
variable, we are referring to the extent
that this variable can be ‘seen’ by various
portions of the program. For example,
suppose we declare two integer variables
as illustrated below:
int MyGlobalInt;
void MyFunction ()
{
int myLocalInt;
// More statements
}
In this case, MyGlobalInt is said to be
a ‘global variable’ because it’s declared
at the top level of the program outside of
any functions. This means that its scope
is the entire program, and it can be seen
and modified by any of the functions
forming the program.
By comparison, myLocalInt is said to
be a ‘local variable’ because it’s declared
inside MyFunction(). In this case, its
scope is localised to MyFunction()
and it cannot be seen or modified by
any other functions. It also means that
this variable is instantiated (brought into
existence) when the function is called,
and it ‘evaporates into the ether’ when
the function terminates and returns control to whatever called it. This explains
how we can declare variables of the same
types and/or names in multiple functions
without them being related to each other
in any respect.
As a little reminder, I use the camel
case naming convention for my function and variable names. With this convention, which is also known as Pascal
case, multiple words are joined together
without spaces. When it comes to global
variables and function names, I use UpperCamelCase in which the first letter
Practical Electronics | July | 2023
of every word is capitalised. By comparison, with local variables, I use lowerCamelCase in which the first word is
all lowercase, including the first letter.
Using this convention makes it easy to
differentiate between global and local
entities when reading and writing code.
and c. Somewhere in the function we
might have a series of three statements
as follows:
Building blocks
If you wish, you can use pairs of curly
brackets inside a function to gather groups
of statements together to make it clear
to yourself and anyone else that you
consider these statements to be related.
These are often referred to as ‘blocks,’
and using this technique may be referred
to as ‘block programming.’
Of particular interest to us here is that
variables can be declared within a block,
in which case the scope of those variables
is limited to the block in which they are
declared. Consider the following code
snippet, for example:
Observe how each of these statements is
terminated by a semicolon. Now suppose
we decide to gather these statements together in a block as shown below:
void MyFunction ()
{
int myLocalIntA;
// More statements
{ // Start of block
int myLocalIntB;
// More statements
} // End of block
}
In this case, the scope of myLocalIntA
is the entire function, which means it can
also be seen and modified from within
our block. By comparison, the scope of
myLocalIntB is limited to the block
within which it is declared.
Furthermore, this also means that myLocalIntB is instantiated (brought into
existence) when it is declared at the beginning of the block and it disappears at the
end of the block. A little thought brings
us to the realisation that we can have multiple blocks in the same function and (if
we so desire) we can declare variables of
the same type and/or name in two or more
of these blocks, where these variables will
be totally distinct from each other.
As one final point, we can have nested
curly brackets, for example { { } }, { {
{ } } }, { { } { } { } } and so on,
which means we can have blocks within
blocks ad infinitum.
Making a statement
Computer programs are made up of a
series of statements. Each statement instructs the computer to perform a specific
action. Different programming languages
do things in different ways. In C/C++, a
statement is terminated with a semicolon ‘;’ character.
Let’s assume we’ve already created a
function, as part of which we’ve declared
three local integer variables called a, b,
Practical Electronics | July | 2023
a = 6;
b = 4;
c = a + b;
{
a = 6;
b = 4;
c = a + b;
}
In this case, observe that we still need to
terminate each of the individual statements
with semicolons, but we don’t need (cannot
use) a semicolon following the ‘}’ that terminates the block. Similarly, we don’t need
(cannot use) a semicolon following the ‘}’
that terminates the function itself.
The word ‘compound’ is defined as
something that is composed of two or
more separate elements. This explains
why another name for blocks is ‘compound statements’ because they are composed of two or more simple statements.
There’s a condition
We introduced the concept of if() conditional statements earlier in this series
(PE, March and April 2023). Our newfound knowledge regarding curly brackets
and compound statements (blocks) should
serve to clarify our previous discussions.
For example, assuming we’ve declared
some local integer variables called a, b,
c, d and e, we could have an if() associated with a single statement as follows:
if (a > b) c = 10;
In English this equates to ‘If a is greater
than b then make c equal to 10.’ As we
noted earlier, the C/C++ languages don’t
care overmuch about whitespace characters, so some programmers would write
this statement as follows:
if (a > b)
c = 10;
The main thing to note is that, in both
of the above cases, the entire statement
is terminated by a semicolon. Alternatively, we could associate a compound
statement with our condition as follows:
if (a > b)
{
c = 6;
d = 4;
e = c + d;
}
As usual, each of the simple statements is
terminated with a semicolon, but don’t need
(cannot use) a semicolon following the ‘}’
that terminates the compound statement.
It’s also worth reminding ourselves that,
even if we have only a single statement
associated with an if() condition, my
personal preference is to still employ a
{ } block as illustrated below:
if (a > b)
{
c = 10;
}
Although this may appear to be a case
of ‘overkill,’ it makes things a whole lot
clearer when you have multiple levels
of if() … else if() … else() conditions. Also, it makes one’s life a whole
lot simpler when adding (and later removing) additional temporary statements
while debugging recalcitrant code.
An array of confusion
I feel confident that things would remain
relatively clear if we could terminate our
discussions of curly brackets at this point.
Unfortunately, these little scamps are
used for additional purposes, such as the
initialisation of arrays (also the initialisation of enum and struct constructs,
which we have not yet introduced).
We said hello to arrays in an earlier
column (PE, February 2023). Suppose we
declare a 3-element integer array called
x, as illustrated below:
int x[3];
Observe the use of the square brackets to
(a) indicate that this is an array and (b)
specify the size of the array. Remember
that we always start counting from zero,
so the elements of this array will be numbered (indexed) as 0, 1, and 2. Later in
the program, we might load values into
our array as follows (these values have no
meaning beyond serving as an example):
x[0] = 7;
x[1] = 2;
x[2] = 5;
In this case, we are using square brackets to indicate elements of interest. Alternatively, we can use curly brackets to
initialise our array as part of its declaration as illustrated below:
int x[3] = {7, 2, 5};
There are several points to observe here.
First, the initialisation values are commaseparated. Second, no comma is required
(or allowed) after the final value. Third,
since this entire construct is a statement,
it’s terminated with a semicolon, which
appears outside the closing ‘}’ character.
57
5V
10kΩ
10kΩ
A0
A1
S W0
S W1
GND
To the Arduino’s A0 analogue pin
To the Arduino’s A1 analogue pin
Fig.2. Adding a second pushbutton switch.
The fact that a semicolon is required
here but not to terminate a compound
statement can be endlessly confusing to
beginners, but it all makes sense when
you think about it. Trust me, have I ever
lied to you before? You don’t know? Well,
that just goes to show how good I am.
As you may recall, we’ve seen something like this before when we first declared and initialised the array defining
the segments that correspond to the numerical characters 0 to 9 on our 7-segment display (PE, April 2023):
byte DigitSegs[NUM_DIGITS] =
{
// ABCDEFGDP
B11111100, // 0
B01100000, // 1
B11011010, // 2
B11110010, // 3
B01100110, // 4
B10110110, // 5
B10111110, // 6
B11100000, // 7
B11111110, // 8
B11110110 // 9
};
Note the lack of a comma after the final
value and the presence of a semicolon
after the closing ‘}’ character. Now everything starts to make more sense, doesn’t it?
Multidimensional madness
While we are here, we may as well consider the concept of multidimensional
arrays in C/C++. The simplest form is a
two-dimensional (2D) array, but 3D, 4D,
5D, and higher dimensions may also be
employed as required. For example, supposing we wish to declare and initialise a
2D integer array called x with three rows,
each comprising five columns, we could do
so as follows (again, these values have no
meaning beyond serving as an example):
int x[3][5] =
{
{10, 12, 99, 42, 45},
{18, 15, 21, 36, 54},
{72, 91, 14, 62, 77}
};
58
In this case, our three rows are numbered 0, 1, and 2, while our five columns
are numbered 0, 1, 2, 3, and 4. If we subsequently wished to change the value
of the fourth element in the second row
(indicated in red) from 36 to 63, for example, we could do so as follows:
x[1][3] = 63;
Observe how we use nested { } character
pairs in the initialisation example above. In
this case, the outer pair is associated with
the entire array, while the three inner pairs
are each associated with a row. In particular, observe how the elements in a row
encapsulated by { } characters are commaseparated with no comma after the final
element. Also, how the rows themselves
are comma-separated with no comma after
the final row. Once again, this can be confusing to beginners. And, once again, it
all makes sense when you think about it.
Just for giggles and grins, we should
probably note that it is also possible to
initialise our array using a single pair of
{ } characters as follows:
int x[3][5] =
{
10, 12, 99, 42, 45, 18, 15, 21,
36, 54, 72, 91, 14, 62, 77
};
It’s all Alan’s fault
To be honest, I really hadn’t intended
to take such a diversion into the coding
side of things. I know that our illustrious
publisher, Matt Pulzer, is keen for us to
perform as much nitty-gritty ‘hands-on’
experimentation as possible. However,
everything we’ve discussed here will
serve us well in the future.
Also, I think it’s only fair to note that
any blame for this excursion into the
world of coding can be fairly and squarely
laid at the feet of the aforementioned PE
community member, Alan, who presented this poser in the first place.
Clickerty switch
OK, are you ready for some hands-on experiments? You are? Good. I am too. Two
columns ago (PE, May 2023), I asked you
to add a second switch to your breadboard (Fig.2). In our previous column (PE,
June 2023), we added a passive piezoelectric buzzer and used it to present a
more pronounced ‘click’ sound when
we clicked our first switch. At the end
of that column, I left you with two missions, the first being to add this click
effect to our ‘count up’ code.
Just to make sure we are all tap-dancing in synchronisation, as it were, you
can download a copy of our current
breadboard layout showing both switches, our piezoelectric buzzer, our 7-segment display, and the connections to
our Arduino Uno (file CB-Jul23-01.
pdf). As usual, all of the files mentioned
in this column are available from the
July 2023 page of the PE website at:
https://bit.ly/pe-downloads
After presenting our ‘HELLO’ message,
our code currently commences by displaying 0. Pressing our first pushbutton
causes the count to increment by 1. When
the count reaches 9, the next press of the
first pushbutton returns the count to 0.
You can download the program containing
the most recent incarnation of our code,
including the Click() function that
causes the piezoelectric buzzer to make
our ‘click’ sound (file CB-Jul23-02.txt).
The main function of interest to us
at the moment is our GetNewDigit()
function (Listing 1). As we discussed in
our previous column, a lot of this code is
there to make sure our program doesn’t
inadvertently respond to switch bounce
(ie, multiple events caused by the switch’s
electromechanical contacts making and
breaking several times before settling in
their new state).
Just to remind ourselves, the PinSw
variable is associated with the Arduino’s
A0 pin, which is connected to the first
switch. On Line 95 we read the value
on this pin and store it in a local integer
variable called swVal.
The portion of the code of interest to
us here commences with the test on Line
97. If this test returns true, this means
the switch has been pressed, in which
case we execute the code in the associated compound statement (Lines 98 to
103). Look at us, saying things like ‘compound statement’ and actually having a
clue what we’re talking about – my mum
is going to be so proud.
Observe the call to the Click() function on line 101. Also, observe Line 99,
which is where the real magic takes place.
The ‘+ 1’ part is where we increment the
count. The % NUM_DIGITS part is where
we make sure that, when the count value
reaches 10, it’s returned to 0 again (we
discussed this in excruciating exhilarating detail in PE, April 2023).
Crash and burn
This brings us to your second mission,
which was to implement the code to get
our second switch working. In this case,
pressing the second pushbutton should
cause the count to decrement by 1. Furthermore, if the count is at 0, then the
next press of the second pushbutton
should return the count to 9.
The first thing you should have done
is replace the PinSw variable associated with the Arduino’s A0 pin with two
variables called something like PinSw0
and PinSw1, which should be associated
with pins A0 and A1, respectively. As
a related item, in the setup() function
you should have replaced the existing
Practical Electronics | July | 2023
Listing 1. Counting up.
pinMode(PinSw, INPUT) statement
with two new statements reflecting the
fact we now have two switches. Also,
you should have replaced our global
LookFor variable with two new variables
Listing 2. Counting up and down (first pass).
Practical Electronics | July | 2023
called something like LookForSw0 and
LookForSw1.
Of course, your main task would have
been to modify the GetNewDigit() function to accommodate both switches. For
example, you may have captured some
code looking something like that shown
in Listing 2.
You can download this to peruse and
ponder at your leisure (file CB-Jul23-03.
txt). Actually, now that I come to think
about it, it would have made more sense
to have named our pins PinSwUp and
PinSwDn, or at least add comments in the
code saying which switch is supposed
to do what, but I’m not going to go back
and change things now (let’s just treat
this as a learning experience).
As usual, there are several things wrong
with this code. Take a moment to see if
you can spot any obvious areas of concern.
The first point is that we’ve totally divorced our handling of the two switches.
We start by checking to see if our first
switch has been pressed, in which case
we respond accordingly, and then we
move on to consider our second switch.
What would happen if both switches were
pressed at the same time? What should
happen if both switches are pressed at
the same time? Having both switches
being activated or deactivated simultaneously may be unlikely (in some applications and implementations it may
be impossible), but such a circumstance
should at least be considered in the system’s specification and – if necessary –
accounted for in the code.
However, the real problem is located
on Line 118, which is where we are attempting to decrement the count. As you
can see, all we’ve done (I know it’s me
who did this, but I’m assuming you attempted something similar) is to take our
increment count statement from Line 104
and replace the +1 with –1, which – as
it turns out – is not a good idea.
The funny thing is that this will actually
work some of the time, thereby making it
a ‘close, but no cigar’ solution. Try running the program, wait for the initial ‘0’
to appear on the 7-segment display, and
then click the ‘up’ count button (our first
switch) five times, thereby leaving ‘5’ on
the display. Now try clicking the ‘down’
count button (our second switch) five times,
thereby returning the displayed value to ‘0’.
The problem arises when we click the
‘down’ count button one more time. We
want this to take us to a displayed value
of ‘9’, but instead... Well, see what happens for yourself.
Don’t be negative
What’s gone wrong? Well, this is all to
do with the way in which computers
represent negative numbers. Do you remember when we introduced the concept
of hexadecimal (PE, May 2023)? At that
time, we noted that a 4-bit nybble can
represent 24 = 16 different patterns of 0s
and 1s from 0000 to 1111, and that we
can use these patterns to represent positive integers in the range 0 to 15. At that
59
time, we failed to make mention of how we can also use these
patterns to represent both positive and negative values. In fact,
there are multiple methods available to us, two of the most
common being known as ‘sign-magnitude’ and ‘signed binary.’
Let’s first remind ourselves that, in the case of an unsigned
binary 4-bit nybble (Fig.3a), commencing with the rightmost
bit, which is known as the least-significant bit (LSB), the
column weights are 20 = 1, 21 = 2, 22 = 4, and 23 = 8. We determine the value of an unsigned binary number by multiplying
each bit by its corresponding column weight and summing
the results. For example, 0011 is (0 x 23) + (0 x 22) + (1 x 21)
+ (1 x 20), which is the same as saying (0 x 8) + (0 x 4) + (1 x
2) + (1 x 1) = 3. Similarly, 1011 is (1 x 23) + (0 x 22) + 1 x 21)
+ (1 x 20), which is the same as saying (1 x 8) + (0 x 4) + (1 x
2) + (1 x 1) = 11.
By comparison, in the case of sign-magnitude binary (Fig.3b),
the leftmost bit, which is known as the most-significant bit
(MSB) doesn’t have a weight associated with it. Instead, it is
used to represent the sign of the number, where 0 and 1 indicate positive and negative values, respectively. For this
reason, not surprisingly, the MSB is also known as the ‘sign
bit.’ Meanwhile, the three remaining bits continue to have
the same weights as before. This means that, in the case of a
nybble considered to represent sign-magnitude values, 0011
represents +3, while 1011 represents –(+3) = –3.
On the one hand, the sign-magnitude form is intuitive and
balanced (we can use it to represent values from –7 to +7),
and it is certainly employed for specific applications. On the
other hand, we end up with two types of zero, –0 and +0, and
we just know that this is going to be problematic at some stage
in the game (what happens if we compare variables containing –0 and +0 values to see which is bigger, for example?).
And thus we come to signed binary (Fig.3c), which may
appear to be a tad tricky at first, but which boasts an underlying elegance and sophistication (I’m a big fan). In this case,
the MSB represents a negative quantity, which is –23 = –8 in
the case of a 4-bit nybble. Once again, the three remaining bits
continue to have the same positive weights as before. On the
one hand this results in an unbalanced range of –8 to +7. On
the other hand, we now have only a single zero, which has to
make our lives easier in the long run.
Let’s think this through. In this case, 0011 is (0 x –23) + (0
x 22) + (1 x 21) + (1 x 20), which is the same as saying (0 x –8)
+ (0 x 4) + (1 x 2) + (1 x 1) = 3. However, 1011 is (1 x –23) + (0
x 22) + 1 x 21) + (1 x 20), which is the same as saying (1 x –8)
+ (0 x 4) + (1 x 2) + (1 x 1), which boils down to –8 + 3 = –5.
If this is the first time you are seeing this, you may be forgiven
for thinking ‘What?’ and ‘What??’ and even ‘What???’ At first
glance, signed binary might appear to be an exceedingly convoluted solution to a relatively trivial problem. All I can ask is that
you stand firm and keep the faith because there is much more
to the signed binary format than meets the eye, not least that
it dramatically eases the way in which we assemble the logic
functions that allow a digital computer to perform its magic.
Binary
Dec
Hex
0 0 0 0
0
0
0 0 0 1
1
1
0 0 1 0
2
0 0 1 1
Binary
Dec
Hex
0 0 0 0
+0
0
0 0 0 1
+1
1
2
0 0 1 0
+2
3
3
0 0 1 1
0 1 0 0
4
4
0 1 0 1
5
0 1 1 0
Hex
0 0 0 0
+0
0
0 0 0 1
+1
1
2
0 0 1 0
+2
2
+3
3
0 0 1 1
+3
3
0 1 0 0
+4
4
0 1 0 0
+4
4
5
0 1 0 1
+5
5
0 1 0 1
+5
5
6
6
0 1 1 0
+6
6
0 1 1 0
+6
6
0 1 1 1
7
7
0 1 1 1
+7
7
0 1 1 1
+7
7
1 0 0 0
8
8
1 0 0 0
–0
8
1 0 0 0
–8
8
1 0 0 1
9
9
1 0 0 1
–1
9
1 0 0 1
–8 + 1 = –7
9
1 0 1 0
10
A
1 0 1 0
–2
A
1 0 1 0
–8 + 2 = –6
A
1 0 1 1
11
B
1 0 1 1
–3
B
1 0 1 1
–8 + 3 = –5
B
1 1 0 0
12
C
1 1 0 0
–4
C
1 1 0 0
–8 + 4 = –4
C
1 1 0 1
13
D
1 1 0 1
–5
D
1 1 0 1
–8 + 5 = –3
D
1 1 1 0
14
E
1 1 1 0
–6
E
1 1 1 0
–8 + 6 = –2
E
1 1 1 1
15
F
1 1 1 1
–7
F
1 1 1 1
–8 + 7 = –1
F
± 22 21 20
(a) Unsigned binary
https://amzn.to/3Tk7Q87
Components from Part 6
Passive piezoelectric buzzer
60
https://amzn.to/3KmxjcX
0
(c) Signed binary
Ah, that explains it!
This is probably a good time to mention that, in the case of
the Arduino Uno microcontroller which we are using for our
experiments, the int (integer) data type is a 16-bit field. Also,
we can declare integers as being signed (ie, signed binary) or
unsigned, for example:
signed
int a;
unsigned int b;
If we simply declare a variable as being of type int, then the
compiler will treat it as being signed by default. The really
–2
Components from Part 2
Momentary pushbutton switches
1
More than a nybble
Did you observe that the hexadecimal values remain unchanged,
irrespective of whether we are considering our nybble to represent unsigned, sign-magnitude, or signed binary values (Fig.3)?
This is because hexadecimal values are no more than a oneto-one mapping to their binary counterparts.
But what about values larger than a 4-bit nybble (Fig.4a)? Well,
in the case of an 8-bit byte (Fig.4b), the MSB would represent
–27 = –128, while all the other bits continue to have positive
weights. In turn, this means an 8-bit signed binary number can
be used to represent values in the range –128 to +127. Similarly,
in the case of a 16-bit word (Fig.4c), the MSB would represent
–215 = –32,768, which means a 16-bit signed binary number can
be used to represent values in the range –32,768 to +32,767.
The big point here is that, in the case of the signed binary
format, all 1s values always represent –1. By comparison, if
we were considering these fields to represent unsigned binary
values, then their MSBs would represent +23, +27, and +215,
and their all 1s values would represent 15, 255, and 65,535 for
our nybble, byte, and word examples, respectively.
LEDs (assorted colours)
https://amzn.to/3E7VAQE
Resistors (assorted values)
https://amzn.to/3O4RvBt
Solderless breadboard
https://amzn.to/3O2L3e8
Multicore jumper wires (male-male) https://amzn.to/3O4hnxk
Components from Part 5
2
Fig.3. Two ways of representing negative numbers in binary.
–2
https://amzn.to/3Afm8yu
3
–2 2 2 2
(b) Sign-magnitude binary
Components from Part 1
7-segment display(s)
Binary
Decimal
23 22 21 20
–2
15
7
3
20
HEX
DEC
0 1 1 1
7
(+7)
1 0 0 0
8
(–8)
1 1 1 1
F
(–1)
0 1 1 1 1 1 1 1
7F
(+127)
1 0 0 0 0 0 0 0
80
(–128)
1 1 1 1 1 1 1 1
FF
(–1)
0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
7FFF (+32,767)
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
8000 (–32,768)
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
FFFF
(a) 4-bit nybble
(b) 8-bit byte
(c) 16-bit word
(–1)
Fig.4. Signed binary nybbles, bytes, and words.
Practical Electronics | July | 2023
FFFF (–1) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
+ 0001 (+1) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
= 0000
(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0000
(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ FFFF (–1) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
9
(a)
0
1
8
2
7
3
6
(b)
= FFFF (–1) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
5
4
Fig.6. Circular
reasoning.
Fig.5. Operations of interest with signed binary.
interesting thing is that signed and unsigned integers are stored
and manipulated inside the computer in the same way – the only
thing that’s different is the values we consider them to represent.
We’re almost there. The penultimate thing to consider is what
is going to happen if an integer variable already contains all 1s
and we add 1 to it. In this case, whatever 16-bit hardware (register, memory location...) is being used to store this value will
overflow and return to containing all 0s, irrespective of whether
we are considering it to represent signed or unsigned values.
Bearing this in mind, if we are considering our variable to represent signed binary values, which means an all 1s value represents –1, it should come as no surprise that –1 + 1 = 0 (Fig.5a).
Contrariwise, what happens if our variable contains all 0s
and we subtract one from it. In this case we have to remember that 0 – 1 is the same as saying 0 + (– 1). And, once again,
since we know that all 1s represents –1, it should come as no
surprise that 0 + (–1) = –1 (Fig.5b).
All of this goes to explain why our counting down code
went pear-shaped. The easiest way to wrap our brains around
all of this is to employ a little circular reasoning. Let’s envisage our ten digits (0 to 9) as being arranged in a circle (Fig.6).
Let’s start by reminding ourselves what happens when we count
up using the statement on Line 104 of Listing 2 (remember that
the modulo % operator returns the remainder from an integer division and that we’ve defined NUM_DIGITS to have a value of 10):
tmpDigit = (DigitToDisplay + 1) % NUM_DIGITS;
If DigitToDisplay is 0 to 8, adding 1 will take us in a clockwise direction, returning 1 to 9, respectively, and this is the
remainder value that will be returned by the % 10 (ie, % NUM_
DIGITS) operation. By comparison, when DigitToDisplay
is 9, adding 1 will give us 10, and 10 % 10 will return 0,
which is what we want.
Now, let’s consider what happens when we count down
using the statement on Line 118 of Listing 2:
JTAG Connector Plugs Directly into PCB!!
No Header!
No Brainer!
Our patented range of Plug-of-Nails™ spring-pin cables plug directly
into a tiny footprint of pads and locating holes in your PCB, eliminating
the need for a mating header. Save Cost & Space on Every PCB!!
Solutions for: PIC . dsPIC . ARM . MSP430 . Atmel . Generic JTAG . Altera
Xilinx . BDM . C2000 . SPY-BI-WIRE . SPI / IIC . Altium Mini-HDMI . & More
www.PlugOfNails.com
Tag-Connector footprints as small as 0.02 sq. inch (0.13 sq cm)
tmpDigit = (DigitToDisplay + NUM_DIGITS - 1)
% NUM_DIGITS;
In this case, if DigitToDisplay is 9 to 1, adding 10 will give
19 to 11, respectively; subtracting 1 will give 18 to 10, respectively; and performing % 10 will result in 8 to 0 as before. Furthermore, when DigitToDisplay is 0, adding 10 will give
10, subtracting 1 will give 9, and performing % 10 will result
in a remainder value of 9, which is what we want.
As usual, I feel a little ‘Tra-la’ is in order.
Homework
I’m sorry we’ve spent so much of our time in this column contemplating things that make our heads ache, but everything we’ve
tmpDigit = (DigitToDisplay - 1) % NUM_DIGITS;
learned here is going to come in very, very useful in the future.
What I’d like you to do now is to think of at least three difIn this case, if DigitToDisplay is 9 to 1, subtracting 1 will
ferent applications we can implement with our setup so far,
take us in a anticlockwise direction, returning 8 to 0, respecwhich is two buttons, a piezoelectric buzzer, and our 7-segtively, and this is the remainder value that will be returned
ment display. I’ll even start the ball rolling by suggesting one
by the % 10 operation. By comparison, when DigitToDispossible application, which would be to implement a form
play is 0, subtracting 1 will give us -1, but this is represented
of electronic dice. Every time we press a button, our program
by all 1s in signed binary, and your guess is as good as mine
could display a random number from 1 to 6.
as to what the % 10 operation will do with this (suffice it to
How about we jazz this up a little by generating a series
say that it won’t return a value of 9).
of random numbers before settling on the final value? What
Happily, there is a solution. Suppose we start by adding 10
about making the random numbers change quickly at first
to whatever value is currently stored in DigitToDisplay,
and then gradually slow down before eventually settling on
which will take us a full turn clockwise round the dial in
a final value? Might we want to arrange things such that each
Fig.6. This means our updated version Line 118 in Listing 2
number is accompanied by a click on our buzzer and a merry
will be as follows (file CB-Jul23-04.txt):
little jingle plays when the final value is displayed?
This counts as just one application. I’m sure you can come up with
Cool bean Max Maxfield (Hawaiian shirt, on the right) is emperor of all he
more (feel free to email me to let me
surveys at CliveMaxfield.com – the go-to site for the latest and greatest
know what you’re thinking, and I’ll
in technological geekdom.
respond by sharing my own ideas).
Comments or questions? Email Max at: max<at>CliveMaxfield.com
Until next time, have a good one!
Practical Electronics | July | 2023
61
|