This is only a preview of the May 2017 issue of Silicon Chip. You can view 39 of the 112 pages in the full issue, including the advertisments. For full access, purchase the issue for $10.00 or subscribe for access to the latest issues. Items relevant to "Turn your 10MHz counter into a 6GHz+ counter":
Articles in this series:
Items relevant to "The Microbridge: universal PIC32 programmer plus!":
Items relevant to "New Marine Ultrasonic Anti-Fouling Unit":
Items relevant to "Micromite BackPack V2 with touch-screen and USB":
Purchase a printed copy of this issue for $10.00. |
Getting Started
with the Micromite,
Part 3
by Geoff Graham
So far, we have covered some of the basic concepts involved with programming
in MMBasic such as input/output commands, making decisions, looping and
drawing graphics. Now we will move on to more advanced subjects such as
data types, arrays and drawing text on the LCD screen.
S
o far in this series, all the numbers and variables that we have
used were floating point types. Just
to re-cap, a floating point number can
contain a decimal point. For example,
123.45 is a floating point number; so
is 17.0 (or even 17). Often the term
“floating point” will be abbreviated
to just “float” and MMBasic uses that
abbreviation also.
Most numbers that we use in
everyday life (and programming) can
be expressed as floats and so they are
the default in MMBasic if you do not
specify a number’s type.
However, the limitation of floating
point is that it stores numbers as an
approximation with an accuracy of
only 6 or 7 digits. For example, if you
stored the number 1234.56789 in a
floating point variable then printed it
out, you would get 1234.57.
You can try it out for yourself:
a = 1234.56789
PRINT a
1234.57
Usually this is not a problem but
there are some cases where you need
to accurately store large numbers.
Examples include tracking a GPS
latitude/longitude to specify a location
40 Silicon Chip
on the planet’s surface, the number of
seconds since midnight on January 1st
1970, or interfacing with digital frequency synthesisers.
As another example, say you want
to store a colour value in a variable. We covered LCD panels and
colours last month but as a quick
reminder, MMBasic uses a 24-bit number to represent colour.
The top eight bits are the intensity of
the red colour (decimal 0 to 255), the
middle eight bits represents the intensity of green and the bottom eight the
blue colour. Any one of the 16 million possible colours can be specified
using this single 24-bit number.
Last month, we also described the
RGB() function which can be used to
generate this 24-bit number. It looks
like this:
RGB(red, green, blue)
Where red is the intensity of the red
colour (0 to 255) and similar for green
and blue.
Also, as explained last month, you
could fill the screen with a colour by
using the CLS command.
For example:
CLS RGB(255, 0, 255)
Fills the screen with purple (generated by mixing red and blue but not
green):
All well and good but you might
want to store this colour in a variable
called “purp” and then use that variable instead of the long RGB function.
So now your program is:
purp = RGB(255, 0, 255)
CLS purp
If you run this program, you will
find that the screen is filled with a
funny pink colour, not purple. Try it
yourself, type in the above two fragments of code and see. What is going
on here?
The answer is that we tried to store
a 24-bit number (which has eight
decimal digits) in a floating point
variable which is only good for holding
six or seven significant digits.
The floating point variable “lost”
the least significant four or five
bits which are part of the eight bits
that define the intensity of the blue
component. This is where integer variables come in.
Integer variables
An integer variable in MMBasic
takes up 64 bits (8 bytes) of RAM
siliconchip.com.au
and can accurately hold numbers
up to 9,223,372,036,854,775,807
(or 19 digits), which is a very large
number indeed – roughly the number
of grains of sand on planet Earth. So
an integer in MMBasic is big enough
to hold a 24-bit number representing
colour.
It is easy to create integer variables;
just add the percent symbol (%) as a
suffix to a variable name. For example,
“purp%” is an integer variable. So the
above program to fill the screen with
purple becomes:
purp% = RGB(255, 0, 255)
CLS purp%
This works perfectly.
The downside of an integer is that
it cannot store fractions (ie, numbers
after the decimal point). Any calculation that produces a fractional result
will be rounded up or down to the
nearest whole number when assigned
to an integer.
You can mix integers and floating
point values within a program and
MMBasic will make the necessary
conversions, but if you want to maintain the full precision of integers you
should avoid mixing the two.
Strings
Strings are another variable type
(like floating point and integers).
Strings are used to hold a sequence of
characters. For example, in the command:
equal), < (less than), etc. Comparisons like less than or greater than test
for string sort order, so for example,
“Abc” < “Abd” will test as true. Another example:
IF Car$ = "Audi" OR Car$ =
"BMW" OR Car$ = "Mercedes"
THEN PRINT "German"
String handling is one of MMBasic’s
strengths and there are many ways to
join, pull apart and generally manipulate strings using specialised string
functions.
For example, INSTR() will search a
string to see if it contains a particular
sub-string, MID$() will extract part of
a string from another and VAL() will
convert a string of digits into a numeric value that can be stored in a float or
integer variable.
For more details on these functions,
refer to the Functions section of the
Micromite User Manual.
Displaying text on an LCD
Last month we explained how to
draw lines, circles etc on an LCD
screen but equally important is the
ability to display text on screen. This is
done with the TEXT command which
has the following syntax:
TEXT x, y, string, justification,
font, scale, colour, back-colour
This has a lot of parameters and the
following description might sound
confusing but we will go through it
in stages. First, x and y are the coordinates (in pixels) of where the text
is to be positioned on the screen and
string is the text that you want to display. justification is a two-letter code
which specifies how to align the text,
eg, whether it is left justified, centred,
right justified etc (more on this later).
Editor’s Note: left and right justified
are not the correct terms. The proper
terms would be left aligned or ragged
right and right aligned or ragged left.
These terms describe which side of the
text is flush and which is “ragged”.
font is the font number that should
be used (the Micromite can have up
to 16 fonts installed) and scale is the
magnification factor applied; 1 is the
normal font size, 2 doubles its height
and width, 3 triples it etc.
The last two parameters should
be obvious; colour is the colour of
the text itself and back-colour is the
background colour for the text, ie, the
colour for the pixels surrounding the
letters.
Most of these parameters are
optional so you can just use the
following to print the word “Hello”
near the centre of the screen:
TEXT 160, 120, "Hello"
PRINT "Hello"
“Hello” is a string constant. Note
that string constants are always surrounded by double quotes. String variables names use the dollar symbol ($)
as a suffix to identify them as a string
instead of a normal floating point variable and you can use ordinary assignment to set their value. Here are some
examples:
Greeting$ = "Hello there"
Car$ = "Holden"
You can also join strings using the
plus symbol (operator):
Word1$ = "Hello"
Word2$ = "World"
Greeting$ = Word1$ + " " + Word2$
PRINT Greeting$
As you may have figured out, this
will print “Hello World”.
Strings can also be compared using
operators such as = (equals), <> (not
siliconchip.com.au
Fig.1: when you run the demonstration text program, this is what you should
see. The word “Hello” is displayed in all four corners of the screen using font 1
(the default built-in font) doubled in size. It demonstrates how the justification
parameter can be used to position text.
May 2017 41
The justification defaults to left-top
(as explained below), the font defaults
to font #1, the scale to 1, the colour to
white and the background to black.
Note that the current default font and
colours can be changed in your program to avoid you needing to provide
them to every TEXT command.
The justification code consists of
zero, one or two letters. The first letter can be L, C or R. These specify that
the text should be horizontally positioned such that the left edge, centre
or right edge is at the specified x coordinate. The second letter is the vertical placement around the y coordinate
and can be T for top, M for middle or
B for bottom.
For example, to perfectly centre the
text on a 320x240 pixel screen you
can use:
TEXT 160, 120, "Centred", CM
The 28 and 44-pin Micromite each
come with one default font installed
while the 64-pin and 100-pin Micromite Plus come with eight fonts.
On all of these devices, you can
embed additional fonts in your
BASIC program, up to a maximum of
16 total. The fonts are numbered from
1 to 16 and this is the number that you
use in the TEXT command.
The standard font on the 28-pin
Micromite (font 1) is rather tiny so you
will normally scale it by two or three
times. For example, this is the previous
example with the text tripled in size:
TEXT 160, 120, "Centred", CM, 1, 3
Just to bring this together, the following will print the word “Hello” in
all four corners of the screen using font
1 doubled in size (see Fig.1):
TEXT 0, 0, "Hello", , 1, 2
TEXT 320, 0, "Hello", R, 1, 2
TEXT 0, 240, "Hello", B, 1, 2
TEXT 320, 240, "Hello", RB, 1, 2
Note that the TEXT command only
accepts a string parameter for the text,
so if you want to display a number (integer or float), you most convert it to a
string first. The most convenient way
to do this is with the STR$() function.
For example, the following will display 123 in the centre of the screen:
spd = 123
TEXT 160, 120, STR$(spd), CM
You can always join strings together
using the plus character (+) and this is
handy when you want to build a string
42 Silicon Chip
for the TEXT command. For example:
spd = 123
TEXT 160, 120, "Speed: " +
STR$(spd), CM
Arrays
Arrays are something which you
will probably not think of as useful
at first glance but when you do need
to use them, you will find them very
handy.
An array is best thought of as a
large number of variables which are
created at the same time with each
variable being identified by a number, which is called the index. A good
way to think of an array is like the
mailbox for an apartment building,
where each box is numbered starting
from one and each box is identical.
An array is created by the DIM command, for example:
DIM n(300)
This creates an array of 301 elements. Note that in MMBasic, array
elements are numbered starting at
zero, so this is why there seems to be
an extra element, making the total 301.
If you want to set element number 100
in this array to (say) the number 876,
you would do it this way:
n(100) = 876
Arrays can contain floating point
numbers, integers or even strings. The
index used to access elements of the
array need not be a constant number
as shown above, it can be a variable
which is changed to access different
array elements.
As an example of how you might
use an array, consider the case where
you would like to record the temperature for each day of the year and, at
the end of the year, calculate the overall average.
You could use ordinary variables
to record the temperature for each
day but you would need 365 of them
and that would make your program
very unwieldy indeed. Instead, you
could define an array to hold the
values like this:
DIM daily_temp(365)
Every day you would need to save
the temperature in the correct location in the array (“day” is variable set
to the day number):
daily_temp(day) = temperature
At the end of the year, it is simple
to calculate the average for the year:
sum = 0
FOR day = 1 TO 365
sum = sum + daily_temp(day)
NEXT day
PRINT "Average is: " sum/365
This is much easier that adding up
and averaging 365 individual variables!
The above arrays have a single
dimension but you can have multiple
dimensions if you wish. Going back
to the mailbox analogy, this is similar to each row of mailboxes being for
the apartments on a single floor and
then having multiple rows, one for
each floor.
This is similar to a two-dimensional
array, where you can identify a
single mailbox using two numbers;
the floor number and the apartment
door number.
For example, if you wished to record
the temperature over five years you
could dimension the array like this:
DIM daily_temp(365, 5)
The first index is the day in the year
and the second is a number representing the year, between 1 and 5.
The first element in an array
You may note that above, we
explained that MMBasic arrays
start with element 0 but in the last
example, we started indexing the
array at index number one.
Traditionally, in BASIC, the first
element of an array is number one.
But in more advanced programming
languages, for many good reasons, the
first element is normally numbered
0 instead.
You can ignore element 0 and use
only elements starting with 1, as we
did above. However, this is a little
wasteful as memory is allocated for
element 0, whether or not you use it.
If you consistently access array
elements starting with index 1, you
can save this memory by using the
command “OPTION BASE 1” at
the top of your program. Accessing
element 0 in your program will then
cause an error.
The DIM command
We have mentioned the DIM
command above for defining arrays
but it can also be used to create
ordinary variables. For example,
siliconchip.com.au
Sample Program: Twinkle Twinkle Little Star
The following is a fun little program that fills the LCD screen with a thousand and one twinkling multicoloured points of
light (like twinkling stars). It is also a useful demonstration of how arrays can be used.
The idea is that we want to fill the screen with lots of illuminated pixels but not too many. If we kept turning on pixels,
eventually all of them will be turned on and the screen would look a mushy grey. This means that we must limit the number of pixels on at any time by turning off old pixels to make way for the new ones. And that in turn means that we must
track the location of each pixel that we have turned on; a perfect job for arrays. Here it is:
CLS
DIM X(1000), Y(1000)
DO
FOR idx = 0 TO 1000
PIXEL X(idx), Y(idx), RGB(BLACK)
X(idx) = RND * 320
Y(idx) = RND * 240
R = CINT(RND) * 255
G = CINT(RND) * 255
B = CINT(RND) * 255
PIXEL X(idx), Y(idx), RGB(R, G, B)
NEXT idx
LOOP
We track the coordinate of each pixel that
has been turned on using two arrays, “X”
for the horizontal coordinates and “Y” for
vertical. The variable “idx” is used to step
through the elements of each array. The
program first turns off the pixel identified
by “idx” (sets its colour to black) and then
it generates a new pair of random coordinates which are stored in the same location Fig.2: this the result of running the program described in the text, which
in the arrays (ie, it overwrites the old pair).
fills the LCD screen with a thousand and one twinkling multicoloured
These coordinates are then used to turn points of light (like twinkling stars). It is a useful demonstration of how
on the pixel at those coordinates with a ran- arrays can be used.
dom colour. The method of generating the
random coordinates and colours was described in last month’s tutorial.
The FOR loop will increment “idx” from zero to 1000, stepping through all the elements (ie, stars) in the arrays. When
the FOR loop has finished, the endless DO-LOOP which encapsulates it will then restart the process, erasing the last
1001 pixels set (one at a time) and turning on a new pixel as it erases an old one. This means that the program will run
forever turning on and off pixels (remember that you can use CTRL-C to halt the program).
When an array is created, MMBasic will automatically set each value to zero. Accordingly, for the first run through the
FOR loop, the program will repeatedly set the pixel at coordinates 0, 0 (upper left corner) to black. However, that is not
an issue because it was already black and with subsequent loops the program will run as expected, turning off pixels
that were previously illuminated. Refer to Fig.2 to see what the result looks like.
you can create a number of string
variables like this:
DIM STRING Car, Name, Street, City
Note that because we defined these
variables as strings using DIM, we do
not need the $ suffix; the definition
alone is enough for MMBasic to identify their type. When you use these
variables in an expression you also do
not need the type suffix, for example:
City = "Sydney"
You can also use the keyword
INTEGER to define integer variables
and FLOAT to do the same for floatsiliconchip.com.au
ing point variables. This type of notation can also be used to define arrays.
For example:
DIM INTEGER seconds(200)
The advantage of defining variables
in this way is that they are clearly defined (generally at the start of the program) and their type (float, integer or
string) is not subject to misinterpretation. You can strengthen this by using
the following commands at the very
top of your program:
OPTION EXPLICIT
OPTION DEFAULT NONE
The first specifies to MMBasic that
all variables must be defined using
the DIM command before they can be
used. The second specifies that the
type of all variables must be specified
when they are created.
Why are these commands
important?
They avoid common programming
errors, for example, if you accidentally
misspell a variable’s name. Say your
program has the current temperature
saved in a variable called Temp but at
one point you misspell it as Tmp. This
will cause MMBasic to automatically
May 2017 43
Now that you know how to write text on the touchscreen, next month we’ll explain how to
create on-screen buttons and how to build graphical user interfaces using what you’ve learnt
so far. This, plus what you’ve already learnt, will allow you to build projects with more
intuitive controls.
create a variable called Tmp and set
its value to zero.
This is obviously not what you intended and it could introduce a subtle error which could be hard to find –
even if you were aware that something
was not right. On the other hand, if you
used the OPTION EXPLICIT command
at the start of your program, MMBasic
would refuse to automatically create
the variable and instead would throw
an error, thereby saving you from a
probable headache.
For small, quick and dirty programs, it is fine to allow MMBasic to
automatically create variables but in
larger programs you should always
disable this feature with OPTION EXPLICIT.
When a variable is created, it is set to
zero (for float and integers) or an empty string (ie, contains no characters –
“”) for a string variable. You can set its
initial value to something else when
it is created using DIM. For example:
DIM FLOAT nbr = 12.56
DIM STRING Car = "Holden", City
= "Adelaide"
Subroutines
A subroutine is a block of program
code that is treated as a module and
can be called from anywhere within
your program.
This is effectively equivalent to copying and pasting that code to the location where it is called, except that
if you did that, you would have to
maintain multiple copies of the code
44 Silicon Chip
(and it would waste valuable flash
space). A subroutine acts like a builtin command and can be used in the
same manner.
For example, let’s say you need a
command that would drive pin number 14 high for 10ms and then return
it to a low state. MMBasic already has
a command for this (called PULSE)
but let’s say that, for the sake of argument, it didn’t. You could define the
subroutine like this:
SUB PulsePin14
SETPIN 14, DOUT
PIN(14) = 1
PAUSE 10
PIN(14) = 0
END SUB
This first command sets the I/O pin
as an output (which defaults to being
low), then sets it high, waits for 10ms,
sets it low again and the subroutine
terminates. It does not matter that pin
14 might have already been set to an
output (SETPIN will not complain) so
this subroutine can be used multiple
times without error.
In your program, you just use the
command PulsePin14 whenever you
want to, like a built in MMBasic command. For example:
IF A > B THEN PulsePin14
The definition of the PulsePin14
subroutine can be anywhere in the
program but typically it is at the start
or end. If MMBasic runs into the definition while running your program, it
will simply skip over it.
This is handy enough but it would
be better if you could use it on any
I/O pin rather than being limited to
pin 14. This can be done by passing a
number to the subroutine as an argument (sometimes called a parameter).
In this case, the definition of the subroutine would look like this:
SUB PulsePin PinNbr
SETPIN PinNbr, DOUT
PIN(PinNbr) = 1
PAUSE 10
PIN(PinNbr) = 0
END SUB
Now, when you call the subroutine,
you can supply the pin number on the
command line. For example:
PulsePin 3
PulsePin 14
PulsePin (x + 1) * 2
This way, the subroutine becomes
more generalised and you can use it
on multiple I/O pins as we did above.
A subroutine can have any number of arguments which can be float,
integer or string, with each argument
separated by a comma.
To define an integer argument, add
the suffix % to the argument name and
$ for a string (just like when you define variables).
Within the subroutine, the arguments act like ordinary variables but
they exist only within the subroutine
and vanish when the subroutine ends.
If any variables with the same name
have been defined in the main program, they are simply hidden while
siliconchip.com.au
LOOKING FOR A
PCB?
PCBs for most recent (>2010)
SILICON CHIP projects are
available from the SILICON CHIP
On-Line Shop – see the On-Line Shop
pages in this issue or log onto
siliconchip.com.au/PCBs
You’ll also find some of the
hard-to-get components to build
your SILICON CHIP project, back
issues, software, panels, binders,
books, DVDs and much more!
the subroutine is running (effectively overridden by the parameters) and
they re-appear with their previous values when it finishes.
Variable name clashes like this are
best avoided, however, as it may confuse you and make debugging more
difficult.
By the way, if you pass a variable
to a subroutine and the subroutine
changes its value, the change occurs
for the calling code too (this is known
as “passing arguments by reference”).
Local variables
Inside a subroutine, you will need
to use variables for various tasks. You
do not want to accidentally change the
value of a variable in the main program if you have forgotten that there
is a variable with that name already.
To this end, you can define a LOCAL
variable within the subroutine. The
syntax for LOCAL is identical to the
DIM command, which means that the
variable can be an array, you can set
the type of the variable and you can
initialise it to some value.
For example, taking our PulsePin
command defined above, we might extend it so that it will generate a number of 10ms pulses, each separated by
20ms. Using a local variable, the new
subroutine could look like this:
SUB PulsePin PinNbr, NbrPulses
LOCAL count
SETPIN PinNbr, DOUT
FOR count = 1 TO NbrPulses
PIN(PinNbr) = 1
PAUSE 10
PIN(PinNbr) = 0
PAUSE 20
NEXT count
END SUB
The variable “count” is declared as
local within the subroutine, which
siliconchip.com.au
means that (like the argument list) it
only exists within the subroutine and
will vanish when the subroutine exits.
You can have a variable called “count”
in your main program and its value
will not be affected when you use the
PulsePin subroutine. Using this new
version of our subroutine is similar to
the previous examples:
IF A > B THEN PulsePin 14, 5
This will generate five pulses on
I/O pin number 14 if the value of A is
greater than B.
You should always use local variables for operations within your subroutine because they help make the
subroutine self-contained and portable
and you avoid accidentally “clobbering” (unintentionally changing the value of) “global” (ie, non-local) variables.
Functions
Functions are similar to subroutines
with the main difference being that a
function can be used in an expression
as it evaluates to something (returns
a value, such as a number or string).
For example, if you wanted a function
to select the maximum of two values
you could define:
FUNCTION Max(a, b)
IF a > b
Max = a
ELSE
Max = b
ENDIF
END FUNCTION
Then you could use it in an expression:
x = 21
y = 25
PRINT "The highest number is "
Max(x, y)
The rules for the argument list in
a function are similar to that for subroutines. The only difference is that
brackets are required around the argument list when you are defining or
calling a function (they are optional
for subroutines).
To return a value from the function, you assign that value to an implicit variable with the same name as
the function. If the function’s name
is terminated with a type suffix (eg, $
or %) the function will return an integer or string respectively, otherwise
it returns a float. For example, if you
wanted a function to return the word
“high” or “low” for the current state
of an I/O pin configured as an input,
you could define a function like this:
FUNCTION PinState$(PinNbr)
IF PIN(PinNbr) = 0 THEN
PinState$ = "low"
ELSE
PinState$ = "high"
ENDIF
END FUNCTION
As you can see, the function name
is defined like a string and is used as
an ordinary string variable inside the
subroutine. It is only when the function
returns that the value assigned to the
function name is made available to the
expression that called it. For example:
TEXT 160, 120, "Pin 14 is " +
PinState$(14)
If pin 14 was low, this would display on the LCD screen the message
“Pin 14 is low”.
“Black Box” components
The above code examples illustrate
one of the important benefits of using
subroutines and functions – ie, when
written and fully tested, they can be
treated as a trusted “black box” which
does not need to be opened.
For example, once you have tested
the PulsePin subroutine, you can
ignore what is going on inside it and
simply use it. Even better, you can
copy it to another program and use it
there without concern.
Subroutines and functions have
one entry point and a limited number
of exits so they are much easier for a
programmer who is not familiar with
the program to understand. Remember
that after just a few months, this programmer could be you!
So you should use subroutines and
functions to “package up” portions of
code, even if they are only called once
in the program.
A good example of this is the code
needed to set up everything before the
maim program starts. If you put this in
a subroutine called SetUp it would be
obvious to another programmer what
it does and he/she can more easily
verify that the code in the subroutine
is doing what is required.
By now you should be well on your
way to writing your own programs for
the Micromite but we still have a little
more to explain. Next month, we delve
into creating on-screen buttons, interrupts and introduce some special but
handy features of the Micromite. SC
May 2017 45
|