This is only a preview of the August 2020 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
Flashing LEDs and drooling engineers – Part 6
W
ell hello there! How nice
it is to see you again. May I
make so bold as to say that, as
a member of the Cool Beans community,
you manage to look both awesome and
highly intelligent. This isn’t a look
many people can carry off successfully,
so kudos to you!
Out of date
As I mentioned in my previous column
(PE, July 2020), my current hobby project is to build a 12 × 12 = 144 array of
ping-pong balls, each containing a tricoloured LED in the form of a WS2818
(aka ‘NeoPixel’). As you may recall, I
also introduced the Seeeduino XIAO
microcontroller with which I plan to
drive my array. The XIAO is only about
the size of a standard postage stamp,
but it boasts a 32-bit Arm Cortex-M0+
running at 48MHz with 256KB of Flash
(program) memory and 32KB of SRAM.
Yummy scrummy! In fact, I’m so enthused
by this little beauty that I was moved to
create a video (https://bit.ly/373vPQC).
One slight complication is that the
XIAO’s input/outputs (I/Os) are 3.3V,
but I need a 5V signal to drive my pixels.
Happily, I found a rather cool hack that
allows me to use a 1N4001 diode and a
‘sacrificial’ pixel to implement a cheapand-cheerful 3.3V-to-5V voltage-level converter (https://bit.ly/3cxMhcV).
In anticipation of finishing my pingpong ball build (and that’s not something
you expect to hear yourself saying very
often), I bounced over to the XIAO’s wiki
webpage (https://bit.ly/30bCyGH) to learn
how to add this little scamp to my Arduino IDE. Next, I created a really simple test
Fig.1. A jig to hold the ping-pong balls.
Practical Electronics | August | 2020
sketch (program) to tickle my pixels, selected the XIAO as my target board, and
hit the ‘Compile’ icon.
Sad to relate, the compilation failed
with multiple errors and grim warnings
of a type I’ve never seen before. ‘Oh dear,’
I said to myself (or words to that effect). I
switched the target board to an Arduino
Uno and the compilation passed as expected. It also passed when I switched
back to the XIAO and commented out
any NeoPixel-related statements. Hmmm.
By this time, I was wearing my sad
face, so I emailed my sketch to the folks
at Seeed Studio explaining my conundrum. The very next morning, I found an
email in my InBox from field applications
engineer (FAE) Anson He, who said that
my test program had compiled for him
with no problem. Anson recommended
that I check to see if I had the latest version of the Arduino IDE (I had) and the
latest version of Adafruit’s NeoPixel library (I hadn’t).
After removing my old NeoPixel library, I followed the instructions on
Adafruit’s NeoPixel Überguide (https://
bit.ly/2XzuqOB) to install the latest and
greatest version of their library. Following
this, everything compiled like a charm,
at which point I dispatched the butler to
fetch my happy trousers (I can’t perform
my Happy Dance without them). Now, all
that remained was to actually construct
the array, which sounds easy if you say
it fast and gesticulate furiously.
experimenting unsuccessfully with a variety of different schemes, I created a jig
by drilling a 6 × 6 matrix of smaller holes
in a piece of scrap board and using this
to position and restrain the balls (Fig.1).
Next, I laid one corner of the main board
on top (Fig.2), used a bit of wooden dowel
to tweak the position of the holes in the
ping pong balls so they pointed straight
up, and used my hot-glue gun to fix everything in place. I then repeated the process for the remaining three quadrants.
As an aside, I’m regarding this 12 × 12
array as being a prototype to ‘iron out the
wrinkles’ in the process. In the fullness
of time, I’m hoping to build a wall-size
display. One thing I’ve decided is that the
next time I do this, I will create smaller 8
× 8 panels and then join them together,
in which case the alignment jig will be
the same size as the front panel.
Hot glue is my friend
The boring bits
While my software gremlins were underway, the hardware construction proceeded apace. Unfortunately, I hadn’t given
as much thought as perhaps I should to
how I was going to precisely position the
ping-pong balls. As you may recall from
my previous column, rather than drilling
the holes in the balls to accommodate the
NeoPixels, I cut them by hand using some
small curved nail scissors. The thing is,
that this was easier to do prior to mounting the balls on the board, but it left me
with the problem of aligning everything.
Prior to this, I had no idea how tricky
it can be to corral 144 ping-pong balls
and bend them to your will. The little
rascals seem determined to escape. After
Every project involves one or more boring
bits, and this is where I now found myself.
Fig.2. Ready to attach the first 36 balls.
Fig.3. Cutting the NeoPixel Segments.
59
The problem here was that there are variations caused by things like the segments
being cut to slightly different lengths and
the holes in the ping pong balls not being
precisely aligned. As a result, some gaps
were smaller, while other gaps were larger.
Rather than custom-create each piece, I decided to make the wire parts long enough
to span the widest gaps, while the insulation parts were short enough to fit in the
narrowest gaps (Fig.4).
So, excluding the ends of the rows, we
have to make 11 groups of 3 connections
on 12 rows, which equals creating 396
little wires (there’s a couple of hours I’m
not going to see again). My reasoning for
leaving a small piece of insulation was
that, while attaching these wires, I didn’t
want my long-nosed pliers to act as heat
sinks resulting in bad joints. In hindsight
(the one exact science), I discovered this
really wasn’t an issue, and I could have
saved myself a lot of effort by simply using
short lengths of uninsulated tinned copper
wire cut from a roll. Be this as it may, I
created videos showing the process of
creating the wire connectors (https://bit.
ly/2MCQ7al) and attaching them to the
array (https://bit.ly/3dHzgPt).
Each NeoPixel contains red, green, and
blue sub-pixels. The data sheet says that,
when full on, each sub-pixel consumes
20mA, so each NeoPixel will consume
60mA. To be honest, when I’ve measured
this in the real world, I’ve never seen more
than 45mA for a fully on NeoPixel, so this
is the value I usually go by myself (not
Fig.4. Creating linking wires to
that I’m recommending this as a practice
accommodate different gap sizes.
for anyone else, you understand).
One further consideration is that, in
It started with snipping out 145 segments
use, it would be rare for us to have all of
from my NeoPixel strip – 144 for the
the elements in the array fully on. Most
array, and the ‘sacrificial’ pixel for the
of the time, I would be surprised if we avvoltage-level converter (Fig.3). This is
eraged more than 20%. Having said this,
less fun than you might expect, because
we should always design for the worstthe strip is delivered wrapped in a procase scenario (I know this goes against
tective plastic cover making the pixels
my using 45mA vs. 60mA. What can I
waterproof so they can be deployed outsay? We live in a crazy mixed up world).
side, and this cover has to be painstakWe have 145 pixels if we include the
ingly removed (not that I’m complaining,
‘sacrificial’ pixel, even if we’re not planbut it all takes time).
ning on using it for anything. The worstThe next step was to use more hot glue
case full-on scenario according to the
to attach these segments to the ping-pong
data sheet would be 145 × 60 = 8.7A. By
balls. Remembering that the pixels are
comparison, the worst-case scenario asgoing to be daisy-chained together, the
suming 45mA per pixel would be 145 ×
way I did this is to align alternate rows
45 = 6.6A.
in different directions, first right-to-left,
I had originally been planning on powthen left-to-right, then right-to-left, and so
ering all of the strips on one side as illusforth. This keeps the signal wires short
trated in Fig.5a (note that the reason the
when connecting the end of one row to
power and ground wires alternate top to
the start of the next.
Feel the power!
bottom from strip to strip is that alternate
In the case of the body of the array, I
This is where things started to get interstrips have their signal paths going rightneeded three short wires to connect adesting again, because I was now on the
to-left and left-to-right, which also swaps
jacent pixels: 5V, 0V, and the data signal.
home stretch of connecting the power.
the orientation of the 5V
and 0V signals.
Unfortunately, the only
solid-core copper wire
that I had to hand in my
treasure chest of bits and
pieces, and that I’m using
to supply the power, is
rated at 3.5A. Of course,
as with anything in electronics, there are myriad
ways in which we could
address this issue. One
( a) P ow ering all the strips on one side
( b) P ow ering all the strips on both sides
( c) D iv iding the strips into groups
way would have been to
stick with the scenario
Fig.5. Different power wiring scenarios.
depicted in Fig.5a, but
11
10
9
8
7
R ow s
6
5
4
3
2
1
0
0
1
2
3
4
5 6
7
C olumns
8
9
10 11
( a) T he w ay I v isualiz e the array look ing at the front.
( b) T he w ay I planned on w iring things
( still look ing from the front) .
( c) T he w ay I actually w ired things
( still look ing from the front) .
Fig.6. When plans meet the real world.
60
Practical Electronics | August | 2020
‘Sacrificial’ pixel
Y = R ow s 0 to 11
1N4001 diode
390Ω buffering resistor
Fig.7. It’s your other left!
to double up on all of the wires. This would give us a 7A capacity, which would satisfy our 6.6A requirements.
An alternative would be to stick with single wires, but to
wire one set on the left of the strips and the other set on the
right as illustrated in Fig.5b. This scenario, which also provides a 7A capacity, has the advantage that, if one of the power
or ground connections in the middle of a strip were to fail (eg,
due to a bad solder joint), then all of the pixels in that strip
would still be powered.
The approach I eventually opted for, and which I documented in a video (https://bit.ly/2Y6L9bh), was to divide the strips
into three groups of four and to power each group independently, as illustrated in Fig.5c, thereby providing me with a
10.5A capability, which more than satisfies even the worst,
worst-case 8.7A requirements associated with all of the pixels
being fully on while each consuming 60mA. Phew!
Your other left!
The way I’ve been thinking about my 12 × 12 array is as a
matrix of 12 rows and 12 columns. If I’m looking at this from
the front, element (0,0) will be in the bottom left-hand corner
as illustrated in Fig.6a. Based on this, I was planning on wiring
things such that – still looking from the front) – the first pixel
in the chain was located in the bottom left-hand corner, as illustrated in Fig.6b.
Unfortunately, when I sat down at the workbench (aka the
kitchen table) and set to connecting all of the wires, I forgot
I was looking at the back of the array and I located the first
pixel in the chain in the nearside left-hand corner (Fig.7). Observe that the lone pixel nearest to us is the ‘sacrificial’ pixel
that’s acting as the voltage-level converter. We can (just) see
the black 1N4001 diode connected between the 5V supply and
the power input to this pixel; also, the 390Ω resistor buffering
the data signal from the XIAO microcontroller. So, as a result
of all these shenanigans, the way I actually ended up wiring
the array is as illustrated in Fig.6c.
Of course, none of this really matters because we’re going
to be finagling things in software anyway, but it still irritates
me that I lost track of things in this way.
Practical Electronics | August | 2020
13 3
13 5 13 6
13 7
13 8
13 9
14 0 14 1 14 2 14 3
10
13 2 13 1 13 0 129
128
127
126
125 124
13 4
14 4
123
122 121
9
109
110 111 112 113
114
115 116
118
119
8
108
107
106
105 104
103
102 101 100 9 9
9 8
9 7
7
8 5
8 6
8 7
8 8
9 0
9 1
9 5
9 6
8 9
9 2
117
9 3
9 4
120
6
8 4
8 3
8 2
8 1
8 0
7 9
7 8
7 7
7 6
7 5
7 4
7 3
5
6 1
6 2
6 3
6 4
6 5
6 6
6 7
6 8
6 9
7 0
7 1
7 2
4
6 0
59
58
57
56
55
54
53
52
51
50
4 9
3
3 7
3 8
3 9
4 0
4 1
4 2
4 3
4 4
4 5
4 6
4 7
4 8
2
3 6
3 5
3 4
3 3
3 2
3 1
3 0
29
28
27
26
25
1
13
14
15
16
17
18
19
20
21
22
23
24
0
12
11
10
9
8
7
6
5
4
3
2
1
0
1
2
10
11
3
4
5
6
7
8
9
X = C olumns 0 to 11
Fig.8. The ordering (numbering) of the NeoPixels in the array.
void LightOneAfterAnother (uint32_t thisColor)
{
for (int iNeo = 1; iNeo < NUM_NEOS; iNeo++)
{
Neos.setPixelColor(iNeo, thisColor);
Neos.show();
delay(TestCycleTime);
}
}
The main program calls this function over and over again, first
setting the colour to red, then green, then blue. If you wish,
you can download the full program to peruse and ponder
(file CB-Aug20-01.txt – available on the August 2020 page of
the PE website). Also, for your delectation and delight, I created a video showing this in action (https://bit.ly/3dIpr3G).
OK, this is where things start to get interesting again. Remember that the way I want to visualise the array – and the way I
want to treat it in my programs – is as 12 rows numbered from
0 at the bottom to 11 at the top, and as 12 columns numbered
from 0 on the left to 11 on the right. In the future, we want to
be able to say things (programmatically speaking) like ‘light the
pixel at column 4 in row 2 with the colour red.’
What we want for our second test is to start with row 0 and
light the pixels in sequence from column 0 to 11, then to repeat
for row 1 and work our way up to row 11. The resulting raster
scan should look like the illustration in Fig.9b.
Depending on one’s background, you may prefer to think of
the column-row combos as X-Y coordinates. In order to achieve
this, I modified my main function, which now appears as follows:
void LightOneAfterAnother (uint32_t thisColor)
{
int iNeo;
for (int yInd = 0; yInd < NUM_ROWS; yInd++)
{
for (int xInd = 0; xInd < NUM_COLS; xInd++)
{
iNeo = GetNeoNum(xInd, yInd);
Neos.setPixelColor(iNeo, thisColor);
Neos.show();
delay(TestCycleTime);
}
}
Testing, testing...
Once the wiring was completed, I was ready to perform my
initial tests. It always pays to keep one’s first test as simple as
possible, so my equivalent of the classic ‘Hello World’ program was to simply light each pixel in sequence. Normally,
our 144 pixels would be numbered from 0 to 143. However,
since we actually have 145 pixels, with the ‘sacrificial’ pixel
occupying location 0, the pixels in our array are numbered
from 1 to 144 (Fig.8).
At the heart of the first test program is a function shown
below. As we would expect, the result is to light the pixels in
a serpentine pattern, commencing with the pixel in the bottom
right-hand corner, and progressing from right-to-left and leftto-right as we work our way through the chain (Fig.9a).
11
}
As we see, we have an outer loop that works its way up the
rows (the Y values), and an inner loop that works its way across
the columns (the X values). The interesting part is where we
61
iNeo = iNeo + (12 - xInd);
}
else
{
// Odd row
iNeo = iNeo + (xInd + 1);
}
return iNeo;
}
( a) First test pattern seq uence
( look ing from the front) .
( b) Second test pattern seq uence
( look ing from the front) .
Fig.9. The results from the first and second tests.
call the GetNeoNum() function, passing
it the (X,Y) values and – hopefully – receiving the number of the corresponding
NeoPixel in the chain.
So, what sort of algorithm could we use
to implement the GetNeoNum() function?
I think that it would be a good exercise for
you to cogitate and ruminate on this before
reading further. Maybe sketch something
out with pencil and paper.
I don’t know about you, but this sort of
thing doesn’t come naturally to me. I’m sure
professional programmers could whip it out
without thinking, but I’m more of a visual
problem solver, so I started off by sketching the number array depicted in Fig.8.
After mulling this over for a while, I
decided that if I’m on row Y, my starting point is to say that I have (Y * 12)
pixels. The next step is to determine if I’m
on an odd or even row. If I’m on an even
row, I need to add (12 - X) pixels; by
comparison, if I’m on an odd row, I need
to add (X + 1) pixels.
The way I determine whether I’m on an
odd or even row is to use the % (modulo)
operator, which returns the integer remainder from an integer division. So, if
I divide row Y by % 2 and the result is 0,
we’re on an even row; if the result is 1,
we’re on an odd row. The code for this
function is as follows:
int GetNeoNum (int xInd, int yInd)
{
int iNeo;
iNeo = yInd * NUM_COLS;
if ( (yInd % 2) == 0)
{
// Even row
Let’s try this out. Suppose we want to light
the pixel located at column 4 in row 2; that
is, (X,Y) = (4,2). First, we multiply
the row by the number of pixels, so (Y *
12) = (2 * 12) = 24. Next, we divide
the row by % 2 to determine that it’s even,
in which case we need to add (12 - X)
= (12 - 4) = 8. So the number of the
pixel in the chain that corresponds to (X,Y)
coordinates of (4,2) is 24 + 8 = 32. Try this
out for yourself using a few sample (X,Y)
values and checking the results using Fig.8.
Once again, if you wish, you can download the full program to peruse and ponder
(file CB-Aug20-02.txt – available on the
August 2020 page of the PE website).
And, once again, I created a short video
showing all of this in action (https://bit.
ly/3cFcfLM).
So, now we’re really ready to rock and
roll. What shall we do first? I have a few
ideas, which I will discuss and demonstrate in my next column. Until then, as
always, I welcome your comments, questions, and suggestions.
Cool bean Max Maxfield (Hawaiian shirt, on the right) is emperor
of all he surveys at CliveMaxfield.com – the go-to site for the
latest and greatest in technological geekdom.
Comments or questions? Email Max at: max<at>CliveMaxfield.com
Max’s Cool Beans cunning coding tips and tricks
Crusty bits
I have a retired friend, who calls himself
‘Crusty’, and who is teaching himself to
program in C. A few weeks ago he emailed
me with a problem. He’d created a program
for his Arduino Uno with a for() loop
that looked something like the following:
for (i = 0; i <= 10, i++)
{
// Do some stuff
}
62
Everything worked as expected with the
loop executing 11 times. This wasn’t the
issue to which I alluded. The problem
arose when Crusty modified his code to
look something like the following:
for (i = 10; i >= 0, i--)
{
// Do some stuff
}
Crusty’s issue is that this loop never ended.
Instead, it kept on executing over and over
again. Any professional programmer will
immediately guess the cause. With my decades of painfully gleaned experience, it
was obvious to me too, but poor old Crusty
simply couldn’t figure it out. Can you?
You’re not my type!
When people are first introduced to the C
programming language, one of the first data
types they meet is the int, which stands
for integer; for example:
Practical Electronics | August | 2020
int MyInt;
Variables declared with this type, like MyInt in this example,
can store positive and negative whole numbers (I know that, by
definition, there aren’t any negative whole numbers, but you
know what I mean). For example, −7, 0, and 42 are all valid int
values. When we see a number like 42, by convention we assume
it represents a positive value without having to explicitly write
+42. Similarly, when we see a variable declared using the int
data type, we assume we’re talking about a ‘signed’ integer that
can represent both positive and negative values. We could also
make this explicit using the following:
sense once you understand how these values are stored, represented, and manipulated inside the computer, but that’s a
discussion for another day.
I explained all this to Crusty. I also asked him to add a Serial.
begin(9600); statement at the beginning of his setup() function, and to modify his for() loop, as shown below:
for (i = 10; i >= 0, i--)
{
Serial.print(“i = “);
Serial.println(i);
// Do some stuff
}
signed int MyInt;
Of course, this leads us to the fact that we can also declare an
integer variable as being unsigned, which means it has no sign
and can represent only positive values; for example:
When Crusty uploaded this new sketch and launched the Serial
Monitor window, the count sequence displayed was as predicted: ‘…3, 2, 1, 0, 65,535, 65,534…’ Of course, this explains why
Crusty’s loop never terminates, because i is always >= 0.
unsigned int MyUint;
A can of worms
Unlike numbers written with a pencil on paper, which can be as
big as we want, limited only by the staying power of our pencil
and the endurance of our hand, the size of numbers stored in a
computer is limited by the amount of memory associated with
the data type we are using to represent them.
Say what?
Now, this is where things start to get a little tricky because – believe it or not – the C standard doesn’t explicitly define the size
of an int. All it says is that an int should be a minimum of two
bytes (16 bits). In the case of an Arduino Uno, the size of an int is
indeed two bytes; in other computers it can by four bytes or more.
A 2-byte (16-bit) field can be used to represent 216 = 65,526 different patterns of 0s and 1s. In the case of a signed int, these
patterns can be used to represent negative and positive values
in the range −32,768 to +32,767 (note that we also have to represent 0, so 32,768 + 32,767 + 1 [to represent 0] equals 65,536).
By comparison, in the case of an unsigned int, these patterns
can be used to represent only positive values in the range 0 to
65,535 (once again, we have to represent 0, so 65,535 + 1 [to represent 0] equals 65,536).
All is revealed!
Returning to Crusty’s problem (and remembering he only sent
me the code for his for() loop), it was obvious to me that he’d
declared the variable i he was using to control his loop as an unsigned data type. I assumed an unsigned int, and this indeed
turned out to be the case.
Since Crusty was thinking that his loop control would only ever
be positive (ie, >= 0), he’d fallen into the trap of thinking ‘bigger
is better,’ opting to use an unsigned int because it could hold
larger positive values, even though he never actually planned on
using anything bigger than 10.
Now, let’s perform a little thought experiment. We know that
an unsigned int on an Arduino Uno can be used to represent
positive values in the range 0 to 65,565. Suppose we were to load
such a variable with 0 and then keep on incrementing (adding
one to) it until it contains its maximum value of 65,565. What do
you think will happen if we try to increment it one more time?
In fact, since the result will exceed this data type’s capacity, it
will overflow and return to containing 0.
Contra wise, the opposite happens in the other direction.
That is, if our unsigned int contains 0 and we attempt to
subtract 1 from this value, the result can’t be −1 because –
by definition – our unsigned int can contain only positive values. Instead, 0 − 1 will result in 65,565. Although this
may not seem particularly intuitive, it actually makes perfect
Practical Electronics | August | 2020
Actually, Crusty’s question has opened up a can of worms – interesting worms, but worms nonetheless – because we also have signed
and unsigned versions of other data types like short and long.
We also have the char data type, whose signed or unsigned
status is not actually specified by the C standard, which means it
can vary from computer to computer or – more correctly – compiler to compiler because the computer just does what it’s told.
(In the case of the Arduino Uno and its compiler, the char is one
byte in size and behaves like a signed 8-bit integer.)
And then there’s the byte data type, which doesn’t actually
exist in standard C, but which the Arduino’s creators decided
to throw into the mix for giggles and grins. I’m sure you will be
delighted to discover that we’ll take a deeper dive into all of this
in my next Tips and Tricks column.
Your best bet since MAPLIN
Chock-a-Block with Stock
Visit: www.cricklewoodelectronics.com
O r phone our friendly kn ow ledgeable staff on 0 2 0 8 4 5 2 0 1 6 1
Components • Audio • Video • Connectors • Cables
Arduino • Test Equipment etc, etc
V i si t ou r S h op , C a l l or B u y on l i n e a t:
w w w . c r i c k l ew ood el ec tr on i c s. c om
0 2 0 8 4 5 2 0 1 6 1
V i si t ou r sh op a t:
4 0 - 4 2 C r i c k l ew ood B r oa d w a y
L on d on NW 2 3 E T
63
|