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:
|
Make it with Micromite
Phil Boyce – hands on with the mighty PIC-powered, BASIC microcontroller
T
he two LED 8×8 matrix
modules added last month provide
the Micromite buggy with a nicelooking pair of eyes. They enable you
to give your robot some personality,
especially when they are animated. For
example, why not make the eyes appear
to look left when the robot is turning left,
or possibly make them blink at random
intervals. Maybe the eyes could close
when the robot has not moved for a while,
giving the appearance of it snoozing. They
could then open immediately before the
robot does anything, making it look as if
it has just woken up.
An MMBasic program has been written
to make all the above possible; so this
month we are going to explore the detail
of how to use the code. By the way, why
not use these eyes as the basis for some
other fun projects. For example, add a
PIR sensor to create a spooky Halloween
ghost that appears to stare at you as you
move around the room!
This month, we will also discuss how
to provide basic control of your robot
with an infrared (IR) remote transmitter.
We will use the 44-button IR transmitter
from the Micromite Moodlight in Part 13
(PE, February 2020). This will allow you
to move the robot forward and back, turn
it left and right, and also adjust its speed.
Naturally, these actions will be combined
with eye animations.
MRB Chassis
Before we go any further, several readers
have asked for the MRB chassis dimensions
so they can make their own – all is revealed
in Fig.31 (see end of article). If you have
access to a laser cutter or CNC machine
then you will find these measurements
will give you a perfectly accurate result.
Be sure to use a material that is from
3mm to 6mm in thickness. Acrylic or
modelling plywood is perfect; you could
52
use aluminium, but in this scenario it
should be painted (at least two coats) to
avoid potential shorts with the underside
of the MRB daughterboard.
If you intend to cut the chassis by hand,
then the hole positions for the wheel
mounts and motor mounts are critical
(as are the hole diameters). If they are
positioned too far apart, then the siliconetracks will be too tight. This will then apply
sideways tension to the motor spindles
resulting in the motors being over-worked
(shortening their life-span). On the other
hand, if the wheel and motor mounts are
positioned too close together, then the
silicone tracks will be too loose and will
keep falling off the toothed wheels they
are wrapped around.
For the three slots for the MKC/Bluetooth
module, and the two slots for the motors;
there is a fair bit of tolerance in the size
and positions of all of these, so cutting by
hand shouldn’t be too much of an issue.
If you do make your own chassis, then
please do send in pictures – we are always
interested in seeing how you get on.
Thanks to all of you who have done just
that – there are some great MRB builds
out there.
Animated eyes – theory
If you loaded last month’s test program
then you have probably taken a quick
look at the code to try and understand
how the animated eyes work. If not, then
don’t worry – all will be revealed below.
Even if you did look at the code, then
this month we have added some new
functionality; so let’s work through what
is involved, step-by step. It is a lot easier
than you may imagine. A summary of the
individual steps is as follows:
Define eyeball shape (8×8)
Overlay pupil pattern (2×2)
Overlay any blinking (by row(s))
Display result on LEDs (anywhere)
x = 1
x = 2
x = 3
x = 4
x = 5
x = 6
x = 7
x = 8
Part 19: Controlling your MRB with an IR transmitter
and animating its eyes
y= 1
y= 2
y= 3
y= 4
y= 5
y= 6
y= 7
y= 8
eye(1)=&b00111100
eye(2)=&b01111110
eye(3)=&b11111111
eye(4)=&b11111111
eye(5)=&b11111111
eye(6)=&b01111110
eye(7)=&b00111100
eye(8)=&b00000000
Fig.32. The values in the Leye() and
Reye() arrays determine the shape of
the two solid eyeballs.
Note that both eyes (left and right) are
independently controllable. This allows
for a lot more character to be added
compared to if we were only able to
display the same image on both eyes.
All variables are prefixed with either
an L or an R (you can work out the
logic there!) – and any variable in the
following discussion without an L or an
R prefix is just to make the diagrams and
explanations less congested.
The eyeball
The whole animated eye process begins
by defining the shape of each eyeball; ie,
the solid ‘white’ part of the eye. In our
application, the eyeball will be a solid
colour of whatever LED matrix colour
you have – here we are using red.
Typically, the two eyeballs are either
identical (symmetrical), or are a ‘mirror’
image of each other (but, this does not
have to be the case). In Fig.32 you can
see that the eyeball is a pattern no bigger
than 8×8 pixels. Here we have defined
an eyeball that is 8 pixels wide, and 7
Micromite code
The code in this article is available
for download from the PE website.
Practical Electronics | August | 2020
Fig.33. The values in the Lpupil()
and Rpupil() arrays define how each
2×2 pupil will look. There are 16 different
permutations for each pupil.
high. This pattern is stored in an array
named eye(). So Leye(1) contains the
required pixel pattern of the top row of
the left eye, and Reye(8) stores the pixel
pattern of the bottom row of the right eye.
The 8 bits that define the pixel pattern in
a horizontal row translate nicely into an
8-bit binary value and hence we are using
the MMBASIC &b binary prefix to show
how these values are easily generated
from the required pattern (1 represents
ON, and 0 represents OFF). An important
point to note here is that each 8×8 eyeball
pattern is not defining the ON/OFF LEDs
on a matrix, it is just the 8×8 eyeball pixel
image that we will ultimately be able to
position anywhere on the 8×8 matrix (as
we shall see in the last step).
So this step has simply defined the
eyeball shape as a set of ON pixels,
stored in the Leye() and Reye() arrays.
Behind the scenes in the code, the eye()
arrays are stored into temporary arrays:
LeyeTemp() and ReyeTemp(). These
two temporary arrays allow the overall
eye images to be ‘constructed’.
The pupil
Referring to Fig.33, you will see that the
eye’s ‘pupil’ is a 2×2 block of pixels. Again,
we will use the &b binary prefix to make
things easier to understand. The concept
here is to use OFF pixels to represent the
pupil shape. The binary notation shown
here uses a 1 to represent a dark part of
pupil (ie, pixel off) and a 0 to represent ‘no
change’ to the background. This allows us
to have any shape pupil in the 2×2 block
(yes, that’s 16 different pupil shapes if
you do the maths!). Here we are showing
a pupil shape with the top-right corner
not darkened.
You may be wondering why we are
saying ‘no change’ rather than simply
turning ON the pixel (bearing in mind the
eyeball comprises of pixels that are ON).
Well, the pupil can be placed anywhere
over the eyeball image, and if it were to
be placed next to the edge of the eyeball
(great for ‘sad’ eye impressions) then we
don’t want to affect the shape of the eyeball
by turning ON ‘un-darkened’ pupil pixels
that hang outside the eyeball shape.
So pupil(1) contains the top row of
the 2×2 block, and pupil(2) contains the
bottom row. A 1 represents a dark pixel in
the pupil, and a 0 represents no change.
Now that we have defined the pupil
shapes, we need to position them within
the eyeball image (stored in LeyeTemp()
Practical Electronics | August | 2020
and ReyeTemp() – see above). This is
just a matter of defining where the topleft pupil pixel is positioned within the
eyeball 8×8 image by using a simple x,y
co-ordinate system. The top left corner
of the eyeball is defined as x=1, y=1. So
Lx=4 and Ly=4 would position the 2×2
left pupil in the central position of the 8×8
left eyeball. Similarly, Rx=3, Ry=5 would
position the right pupil near the lowerleft corner of the right eyeball.
So this step has simply defined the
pupil shapes as a set of OFF pixels stored
in the Lpupil() and Rpupil() arrays;
and positioned both pupils into the
correct positions within LeyeTemp()
and ReyeTemp() by defining Lx and Ly,
and Rx and Ry values.
Blinking
First, a bit of background. We are currently
working through how to construct a static
image of two eyes to be displayed on
the two LED matrix modules. Consider
the result as a ‘frame’ image. If we were
to display one frame, followed in quick
succession by another frame (that has
a slight difference in content), then
continuing this process we would end
up with animated eyes – you can liken
this to the early days of film animation.
So a closing-eye effect can be generated
by effectively switching OFF appropriate
rows of LEDs in each frame and displaying
them in quick succession. However, you
can’t simply switch the LEDs back ON
again to simulate an opening-eye effect
otherwise you will end up with all LEDs
being turned ON.
By using LeyeTemp() and ReyeTemp()
each and every time we draw a frame,
we can overcome this opening-eye issue.
The eyeball shape, and pupil shape, are
stored in the eyeball() and pupil()
arrays, and the image is constructed in
the eyeTemp() arrays (piece by piece) to
create the single ‘frame’. To create a single
frame within a blinking effect (sequence of
frames), we ideally need a method to turn
off the pixels in any row(s) in the current
eyeTemp() arrays (currently containing
the eyeball, and correctly positioned
pupil). We could have simply defined new
x = 3
pupil(1)=&b10
pupil(2)=&b11
y= 4
Fig.35. The eye image after defining the
eyeball, the pupil, and any Blink setting.
Values of Lx/Rx=3 and Ly/Ry=4 define
the pupil position (shown highlighted).
eye() arrays, but this would destroy the
original eyeball pattern stored. We have a
better method as we will now see.
Referring to Fig.34 you will see that
the Blink variable is used to store a
value to represent any complete row(s)
of pixels that we wish to turn OFF in
the eyeTemp() arrays. Once again,
binary notation is used to simplify the
explanation. A 0 bit in Blink means that
the associated row is unaffected (and
will be displayed as currently defined
in eyeTemp(), and a 1 means that a row
will be switched OFF in the displayed
image. The 8-bit value contained in
Blink uses one bit per row of pixels –
the most-significant bit relates to the top
row, and the least-significant bit relates
to the bottom row. Note once again, this
is not referring to the rows on the matrix
module, it is referring to the built up
image in the eyeTemp() arrays (which
can ultimately be positioned anywhere
on the LED matrix modules). Here we are
effectively switching OFF the top and
bottom rows of our 8×7 eyeball image.
So this step has simply defined which
rows of pixels we wish to switch OFF in
order to create a frame within a sequence
of frames that can be used to create a
blinking effect.
At this stage it is worth seeing the result
stored in the eyeTemp() arrays from
the values shown in Fig.32, 33, and 34.
Referring to Fig.35, you can see the result
of using x=3 and y=4 as the pupil position
values. Here, the (circled) pixel at 3,4 is
defining the top-left pixel of the pupil.
–
–
Blink=&b10000011
ShiftX=-1
ShiftY=1
x
+
y
+
Fig.34. The values in Blink determine
which row(s) of pixels in the eye image
need to be switched off. Here, the top
and bottom rows are switched off in our
8×7 eyeball.
Fig.36. The LShiftX / LShiftY, and
RShiftX / RShiftY variables determine
where the eye image is positioned on each
LED matrix. Here they are moved one pixel
to the left, and one down.
53
Lpupil(1)=&b10
Lpupil(2)=&b11
Lx=3
Ly=4
LShiftX=0
LShiftY=0
eye(1)=&b00111100
eye(2)=&b01111110
eye(3)=&b11111111
eye(4)=&b11111111
eye(5)=&b11111111
eye(6)=&b01111110
eye(7)=&b00111100
eye(8)=&b00000000
Rpupil(1)=&b01
Rpupil(2)=&b11
Rx=5
Ry=4
RShiftX=0
RShiftY=0
Fig.37.The demo program (MRB_SampleEyes.txt) is configured to output the image
shown here. Try altering variables in the code to see how they affect the displayed image.
The other circled pixel at 1,1 is used to
define where this 8×8 image is positioned
on the 8×8 matrix module (explained in
the next step).
Displaying the eyes
As mentioned several times already, the
image contained in the eyeTemp() arrays
can be positioned anywhere on the LED
matrix. This is where two more variables
come into play for each eye: ShiftX and
ShiftY. Referring to Fig.36, you can see
how by setting these values, you can place
the image wherever you like on the LED
matrix modules. Here we have ShiftX=1, which means the image is positioned
1 pixel over to the left; and ShiftY=1,
which means it is also positioned one
pixel down. The dotted line in Fig.36
represents the 8×8 image stored in the
eyeTemp() arrays with its top-left pixel
circled. ShiftX and ShiftY simply give
coordinates relative to the top-left pixel
on the LED matrix module. Note that
values bigger than 8, or smaller than -8
will make the image disappear.
Now that ShiftX and ShiftY have
been defined, we need to call a subroutine
to actually draw the eyes onto the LED
matrix modules. This subroutine is simply
called DrawEye and will work out all
the logic in order to create the desired
result. By altering the ShiftX and/or the
ShiftY values, the eyes can be made to
scroll. Note that there is no wrap-around
when scrolling.
A quick example
To demonstrate all of the above, we will
now draw a pair of simple eyes on the robot
(ie, draw a single frame). This is easy; it’s
really just a matter of loading the variables
and arrays, and then calling the DrawEye
subroutine. All the information required is
shown in Fig.37; and you can download
the code (MRB_SampleEyes.txt) from the
August 2020 page of the PE website.
To begin with, the Leye(1) to Leye(8)
and Reye(1) to Reye(8) arrays are
loaded with the eyeball pattern as shown.
Next, the pupils are defined by loading
arrays Lpupil(1) and Lpupil(2), and
Rpupil(1) and Rpupil(2). To position
the pupils, load the variables Lx, Ly and
Rx, Ry. Here, we are not setting LBlink
or RBlink (but you can set them to 0 for
54
now so you can see the effect later). To
finish defining the ‘frame’, set LShiftX
and LShiftY, and RShiftX and RShiftY
as shown. Nothing will appear on the LEDs
until you call the subroutine DrawEye –
so go ahead and do this to see the result.
If all has been typed in correctly, you
should see the image (as shown in Fig.37)
displayed on the robot’s eyes.
Now have a play by altering some of
the above values and see how it affects
what is displayed on the LEDs. This is the
best way to learn. If you look at the code,
there are also some DO…LOOPs contained in
subroutines, which when called, will show
a sequence of frames – in other words, will
show you some eye animations. Now have
a go at creating your own animations!
IR control – theory
Last month, the MRB had IR functionality
added by implementing just a single
component – the now-familiar TSOP IR
receiver. As we have seen in previous
articles, MMBASIC makes it very easy to
add IR control into program code. As a
reminder, we use the IR command to define
an interrupt subroutine (SUB) that will then
automatically get called whenever an IR
signal is detected. MMBASIC passes two
variables into the interrupt subroutine:
the KeyCode (representing the unique
button number that has been pressed),
and the DeviceCode (representing which
IR transmitter is being used).
Within the IR interrupt subroutine, we
can simply use the command structure:
for each CASE to simply alter the values
of certain variables; variables that you
are using (and continually checking) in
your main program. We will now show
you this in practice.
Bringing it all together
Now that you have an understanding
of how to create, draw, and animate
some eye effects, let us quickly show
you how to apply them to some robot
movements – all controlled by the 44button IR remote control.
The program code we’ll be using (MRB_
IR_Control.txt) is available for download
from the August 2020 page of the PE
website. We are not going to go into all
the specific detail here because the code
is commented throughout. Instead, we
want to provide you with an overview
of the techniques being used. There is
nothing new, and a lot of the topics have
already been covered. I hope the code
will inspire you to create your own much
better program, which is customised to
your own needs.
Fig.38 provides an overview of the
code. It has four sections, with which
you should now be familiar:
Set up
Main program
Subroutines
Interrupt subroutines.
The Set up code just sets a few things that
need setting – take a look at the code and I
promise that if you’ve followed this series
then it will all make sense.
The Main program comprises of a DO…
LOOP and works through two main blocks
Set up
C
C
D
D
Main program
DO
onfigure O P T I O Ns
onfigure pins
eclare v ariables
efine eyeball pattern
T est MR B move ment va riables
to set motors as req uired
( direction and speed) or ( off)
U pdate T FT
SELECT CASE KeyCode … CASE x …
CASE y … CASE z… END SELECT
where x, y, and z are the values of specific
buttons on the IR transmitter that we
wish to respond to. So in order to make
the robot do something useful on specific
button presses, we will need to write some
code for each CASE.
As with any interrupt subroutine,
it is good practice that each CASE in
the IR subroutine contains minimal
code. That way, the time spent in the
interrupt routine is minimised, and
hence the program will always be able
to respond to other interrupts – in this
case, immediately respond to other button
presses. The best way to minimise code is
LOOP
Subroutines
I nitialise 8 x8 matrix modules
Load eye pattern
( left, right, straight)
E ye animations ( blink)
D raw eyes
IR
SELECT CASE KeyCode
interrupt SU B
Set relev ant MR B
move ment va riables
D isplay releva nt eye
pattern/ animation
END SELECT
Fig.38. The IR controller program
used this month (MRB_IR_Control.txt)
comprises four simple sections. Do read
the comments contained in the code to
understand things better.
Practical Electronics | August | 2020
in the appropriate action. For example, a
button can be made to act as an immediate
brake (if the robot is about to crash!) by
simply setting MRB_Move=0 and MRB_
Turn=0. Another example is a specific
button that could call a subroutine that
runs a sequence of frames to provide
a blinking effect (try it by pressing the
‘Flash’ button).
That is all the detail we are going to
discuss here. Have a play and see which
buttons respond on the remote. Comments
are in the code to guide you.
the TFT. Basically, it displays the values
of the above variables, converting some of
them into a more ‘human-friendly’ format
(for example MRB_Turn is displayed as the
word ‘Left’ rather than the value -1). The
presence of the DO…LOOP in the code means
the Main program is continually using the
values of all the variables. Any change in
value in a variable will result in a nearinstant change in what the robot is doing.
The Subroutines are used to make the
Main program easier to follow. If you take
a look at each of them in the program, they
are all commented to guide you through
what they do. The most complex one is
DrawEye so do not panic if you don’t
follow the logic. It comprises a lot of logical
operations on the variables discussed
above. The point is, you know how to
load the variables, and what effect each
one has on the displayed output.
Last, the IR interrupt subroutine alters
the function the robot is performing. By
simply changing the appropriate variable
values in the code for each CASE, the
change in value of any variable(s) will be
picked up in the Main program, resulting
of code. The first uses some variables that
define what the motors are doing:
MRB_Move defines whether the robot
is moving or not (0=no, 1=yes).
M R B _ D i r defines the direction
(-1=backwards, 1=forwards).
MRB_Turn defines which way it is
turning (-1=left, 0=not turning, 1=right).
MRB_TurnDuration defines how long
to turn for before stopping (there are
two turn buttons for each direction; one
does a large step, the other a smaller
step (to fine tune).
MRB_Speed defines the speed of the
robot. This is a PWM percentage and
varies between 60% (slow) and 100%
(fast) in steps of 5%. The speed is
displayed on the TFT screen.
There is a series of IF…THEN…ELSE…END
IF statements that use all of the above
variables to control the four I/O pins
connected to the two motors. In essence,
the end result is that the robot moves,
turns or stops as required.
The other block of code in the Main
program puts relevant information onto
Next month
To enable our MRB to roam independently
(without crashing into anything) it will
need to have some form of collision
detection. Next month we will show
how easy it is to implement this feature
by adding a low-cost ultrasonic distance
sensor to the front of your MRB.
Questions? Please email Phil at:
contactus<at>micromite.org
14 5
A 1- 4 : φ 2. 5
B1- 4 : φ 3
C 1- 8 : φ 1. 5
D 1: 3 x 12
D 2: 3 x 12
D 3 : 3 x 3 5
D 4 : 3 x 3 7
D 5: 3 x 10
4 7
4 7
Notes
1) View from above MR B
2) A ll dimension in mm
3 ) B3 and B4 are NO T
ve rtically co- linear
4 ) Low er side of D 4 and upper
side of D 5 are co- linear
3 7
26
18
12
3 7
26
18
12
8
21
22
3 8
22
24
26
A 3
A 1
B1
D 1
B3
3 8
3 9
D 2
A 4
A 2
107
123
D 3
B2
C 1- 4
11. 7 5
13
D 4
B4
3 7
D 5
11. 7 5
C 5- 8
14
16
4 . 25
4 . 25
18
18
23
23
55
3 0
6 0
4 5
Fig.31. The MRB chassis dimensions – a PDF of this diagram is available for download from the August 2020 page of the PE website.
Practical Electronics | August | 2020
55
|