variables except that it happens within the bounds of the
subroutine, function, or task (more on tasks in a minute).
As with regular program variables, we can define bits,
bytes, arrays of bytes, and words. For example:
‘ Use: PRINT_STARS count
‘ — prints “count” stars (*)
SUB PRINT_STARS
starCnt VAR Byte
starCnt = __PARAM1
DO WHILE starCnt > 0
TX_BYTE “*”
DEC starCnt
LOOP
ENDSUB
For the PRINT_STARS subroutine, the user will pass a
byte value defining the number of asterisks to be printed
via TX_BYTE. In the past, we would typically use one of
our temporary variables — those that consume general-purpose RAM — to hold this value. By defining a local
variable as we do above, we are using the stack and not
cutting into the general-purpose RAM space; this leaves
more variable space for the main part of our program. As
you can see, this subroutine uses one byte from the stack.
When the subroutine exits, that stack space will be restored
to the level it was when it entered the subroutine.
A note of caution: Local variables cannot use the
same name as normal program variables. The names of
local variables can be the same from one routine to
another, but I don’t recommend this; use unique names
through your program to prevent ambiguity. There is one
noteworthy limitation with locals: Since the stack is an
array and array elements cannot be used as an index into
another array, we cannot use a local variable as an array
index. A simple solution is to create a variable in the
general-purpose RAM space that will be used for array
indexing. We can save the value of that variable when
coming into a subroutine and then restore it on the way
out. For example:
‘ Use: PRINT_BUF count
‘ — prints “count” characters from buffer()
SUB PRINT_STARS
bCount VAR Byte
bChar VAR Byte
saveIdx VAR Byte
bCount = __PARAM1
saveIdx = idx
FOR idx = 0 TO bCount
bChar = buffer(idx)
TX_BYTE bChar
NEXT
idx = saveIdx
ENDSUB
In this example, the global variable idx is saved to a
local variable, is then used as an index into the buffer()
array, and then restored before the subroutine terminates.
In addition to local variables, SX/B 2.0 has some
interesting memory management features — some
specifically targeting advanced users who may have used
Assembly in the past and are accustomed to very flexible
memory manipulation. For example, arrays in the SX28 are
no longer limited to 16 bytes. We can also force an array
to be aligned with the beginning of a bank by using the
ALIGN modifier with the array declaration. One of the
nice new features of SX/B 2.0 is that it includes a very
detailed memory use map at the end of the List file — this
is quite handy. To be candid, most of our programs will
not need or use SX/B’s advanced options, but it is nice to
have them as our programs — and our programming skills
— become more complex and sophisticated.
SX/B AS TASK MASTER
The biggest and most involved update to SX/B 2.0 is
task management. Using tasks allows us to set up and
schedule automated processes; these are like subroutine
but are called by a task scheduler instead of by us. For
example, let’s say we want to check a sensor every 100
milliseconds no matter what else is going on in the
program. With tasks, we can do that pretty easily. Now,
this all sounds really neat and very cool and in fact it is;
that said, it takes a little bit of setup to get to this level
of automation.
First things first. To use tasks, we must declare an
interrupt. Task timing will be a derivative of the interrupt
rate. As an example, we’ll create a simple interrupt that
does nothing but allow us to run tasks with a base task
“tick” timing of one millisecond — we’d do it like this:
‘ ===========================
INTERRUPT 1_000
‘ ===========================
Mark_ISR:
isrFlag = 1
Schedule_Tasks:
TASKS RUN, 1
RETURNINT
Here we’ve set the interrupt to run every millisecond
and, as we have in the past, set a flag on entry for use by
external processes. The TASKS RUN section sets the task
tick timing to one interrupt cycle, so in this case a task
tick will be one millisecond. Note that this does
not actually launch any tasks; it simply sets up the
mechanisms to handle any tasks we define.
Okay, how about an automated blinker; something we
might have a use for as an annunciator in a factory
process. Let’s say we want it to toggle its state every 250
milliseconds. As with subroutines and functions, we have
to define the task name before using it:
blink_LED Task
November 2008 23