SBASIC Manual
User's Manual
SBasic Compiler Version 2.7
by
Karl E. Lunt
Copyright (c) 1996, 1998, 1999 Bothell, WA
All rights reserved
Printed December 5, 1999
DISCLAIMER
I am releasing the executable for the SBasic compiler, all
supporting library and include files, assorted test cases,
and this document as freeware. Feel free to use SBasic for
whatever non-commercial application seems appropriate.
The SBasic compiler, with or without its attendant support
files, is freeware and in the public domain. You may not charge
for the sale or distribution of SBasic or its distribution files.
If you distribute SBasic to others, please include this manual
in its present form, complete with this disclaimer.
I make no warranty, representation, or guarantee regarding the
suitability of SBasic for any particular purpose, nor do I assume
any liability arising out of the application or use of SBasic,
and I disclaim any and all liability, including without limitation
consequential or incidental damages.
You, as the user, take all responsibility for direct and/or
consequential damages of any kind that might arise from using SBasic
in any fashion.
I do not warrant that SBasic is free of bugs. If you find what
you think is a bug, kindly let me know what it is IN DETAIL, and I'll
certainly consider fixing it in a later release, if there ever is
such.
I developed SBasic as a tool for working with the Motorola 68hc11
and 68hc12 MCUs. If you use SBasic for developing robotics (or other)
application code and find it useful, fine. If you don't find it
suitable in some fashion, then don't use it.
DISCLAIMER
Karl Lunt
116 173rd St., SW
Bothell, WA 98012
SBasic User's Manual SBasic Version 2.7 Page 2
Printed: December 5, 1999
Introduction
This manual describes the use of the SBasic (SB) compiler. SB is a
PC-based cross-compiler for a subset of the Basic language. Source
files containing SBasic statements are compiled into a source file of
assembly language for the target machine. Subsequent assembly of that
file yields an exectuable file for the target machine.
SB creates code for either a 68hc11 or 68hc12 target. For the 68hc11,
SB's output code is compatible with the Motorola FREEWARE asmhc11
assembler, which is bundled with the SBasic distribution files. For
the 68hc12, SB's output code is compatible with the Motorola FREEWARE
as12 assembler, available from several sites on the Internet,
including my web site (http://www.seanet.com/~karllunt).
The SBasic compiler was written in Borland C (version 4.52), and is
compiled as a 32-bit DOS standard app. My SB design is loosely based
on a small Basic interpreter developed by Herbert Schildt and
presented in his excellent book, "The Art of C." (Osborne McGraw-
Hill, Berkeley, CA 94710; ISBN 0-07-881691-2)
SBasic User's Manual SBasic Version 2.7 Page 3
Printed: December 5, 1999
History of SBasic
Version 2.7 fixes a bug in the GOSUB statement that only appeared if
you tried to use a variable as the argument; the code would refer to
either the wrong or an unknown variable. The resulting .asc file
could create errors during the assembler phase.
Version 2.6 fixes a bug that appeared in some programs compiled for
the 68hc12. Some DO-LOOPs or IF-ENDIFs created sections of code so
long that the 68hc12 direct branch instructions failed (target out of
range). This version treats 68hc11 and 68hc12 compilations the same;
DOs and FORs create short branches around JMP instructions. This
version also increases the number of labels, constants, and variables
that your programs can create.
Version 2.5 *finally* kills the last known bug in SB, which corrupted
comparison tests in several different loop constructs. The
distribution file contains a number of test cases that verify the bug
is finally dead (I hope!).
Version 2.4 generates all assembler literals as hex constants, rather
than decimals. It also replaces the JMP *+5 branching construct in
6812 output with a straight branch; the 6812 assembler will
automatically assign the correct branch opcode. It also fixes a bug
in the shift and roll functions, which were generating dead PSHx-PULx
instructions.
Version 2.3 adds the ~ unary operator, which generates the one's
complement of an argument. It also now reports an error if the source
file contains a DO with an UNTIL or WHILE but without a terminating
LOOP. It also now reports an error if an IF statement does not
contain a comparison clause.
Version 2.2 adds the MIN(), MAX(), MINU(), and MAXU() functions. It
also fixes a nasty bug in the assignment operator, which created bad
code if you set a variable equal to an array element.
Version 2.1 adds the COPY statement, for moving blocks of data between
areas of memory. Runtime support relies on two new library files,
COPY11.LIB and COPY12.LIB.
Version 2.0 adds support for the 68hc12 MCUs. Code output does not
take full advantage of the newer instruction set, but it's a start.
Version 1.5 fixes bugs in code generated for certain uses of peek()
and peekb(). Also fixes an obscure bug in certain array operations.
Also allows use of '_' (underscore) in function names; see description
of ASMFUNC statement below. Also cleaned up the array assignment
code; complex array index calculations seem to work fine. If you
notice any that don't, please contact me.
Version 1.4 fixes bugs in code generated for certain uses of multiply.
SBasic User's Manual SBasic Version 2.7 Page 4
Printed: December 5, 1999
Version 1.3 fixes bugs in code generated for certain uses of peek()
and peekb().
Version 1.2 changes the assembly source for variable references. SB
now creates variable references with unique labels, rather than as
offsets to the varbeg address. Added support for inline assembly
language, through the ASM and ENDASM statements. Added support for
the ASMFUNC statement, used to define SBasic entry points into
assembly language routines. Cleaned up the code generated for POKE
and POKEB statements; handles literal target addresses more cleanly.
Fixed bug in POKE and POKEB statements; older versions compiled bad
code for multiplies in the second argument.
Version 1.1a fixes remaining bugs in the code generator regarding
arrays. It also fixes a nasty bug concerning operations such as /
(division), mod, poke(), and pokeb(). The code generated for these
operations was bad if the result was stored into an array element.
Version 1.1 contains fixes to the code generator regarding arrays and
the ADDR() function. In particular, the code generated for array
references is much tighter.
I removed the error message for labels that are defined but not
referenced. This made developing modules of code too difficult; if
you mistype the name of a label, you're going to get other errors,
anyway.
I added the SWAP command, to make it easier to deal with multiple
items on the data stack.
Version 1.0 follows a long succession of beta releases, spanning many
months. In 1.0, SBasic finally reached a point where, like it or not,
I had to call the project done, at least for now.
Version 1.0 supports single-dimension arrays, as well as math
operations in constant declarations. These features, added to those
already existing in the previous beta release, constitute what I
consider to be the bare minimum for a final system.
At this point, however, the code generator is getting kind of fragile,
and needs to be rewritten. This is a large task that I won't take on
easily, but I'll continue to support SBasic for bug fixes.
SBasic User's Manual SBasic Version 2.7 Page 5
Printed: December 5, 1999
Invocation
You execute SB by entering the command:
sbasic infile
where 'infile' contains the path to the source file you wish to
compile. SB writes its output, the assembly language source for the
target, to the standard output, which is normally the screen.
You can redirect the output file to another file by entering the
command:
sbasic infile >outfile
where 'outfile' is the path to an output file to hold the created
source lines.
If SB did not detect any errors in your source file, its output file
should assemble correctly with the appropriate target assembler. If
SB detected errors, it inserts error notices in the output file.
These are almost guaranteed to generate numerous errors if the
resulting output file is assembled.
SBasic supports several command-line options, used to control the
addresses of key elements in the final program. These options will be
explained in detail below.
Upon completion, SB returns an errorlevel that can be used to
determine success or failure of the compilation. If SB successfully
compiled the source program, it returns an errorlevel of 0. If SB
detected one or more errors during compilation, it returns an
errorlevel of 1.
SBasic User's Manual SBasic Version 2.7 Page 6
Printed: December 5, 1999
Command-line options
You can control the placement of variables, code space, and stack
space in the target executable by means of SBasic command-line
options. If you supply any options in your command line, they must
follow the name of the SBasic source file. Refer to the above section
on executing SBasic.
You can control where SBasic places the start of its RAM variables by
using the /v option. The format of this option is:
/vxxxx
where xxxx is a four-digit hexadecimal address that marks the start of
the variable space. SBasic assigns this address to the assembler
label VARBEG; if you do not use the /v option, SBasic assigns a
default value of $0000 to VARBEG.
You can control where SBasic places the beginning of the executable
code by using the /c option. The format of this option is:
/cxxxx
where xxxx is a four-digit hexadecimal address that marks the start of
the code space. SBasic assigns this address to the assembler label
CODEBEG; if you do not use the /c option, SBasic assigns a default
value of $b600 to CODEBEG.
You can control where SBasic places the top of the target's stack
space by using the /s option. The format of this option is:
/sxxxx
where xxxx is a four-digit hexadecimal address that marks the top of
the stack space. SBasic assigns this address to the assembler label
STKBEG; if you do not use the /s option, SBasic assigns a default
value of $00ff to STKBEG.
You can control the type of branch instruction SBasic creates by using
the /b option. The format of this option is:
/b
SBasic normally converts a transfer or jump instruction into two
assembly language source lines. The first line is a relative branch
around the next line, and the second line is a long jump to the target
address. For example:
while
n=3
wend
SBasic User's Manual SBasic Version 2.7 Page 7
Printed: December 5, 1999
contains a branch instruction that tests the value of variable N and
branches back to the WHILE statement if N equals 3.
For the 68hc11 and 68hc12 MCUs, SBasic normally generates code similar
to:
whl000
ldd var001
cpd #3
bne *+5 branch instruction, line 1
jmp whl000 branch instruction, line 2
For short transfers, where the branch target is within the relative
addressing limit of the target MCU, this code is larger and will run
more slowly than necessary.
Using the /b option forces SBasic to generate relative branches
directly to all targets. If the /b option is in effect, SBasic would
generate the following code for the above example:
whl000
ldd var001
cpd #3
beq whl000 branch instruction
Note that the branch has reversed sense, and the JMP instruction has
disappeared.
WARNING: Branches to addresses beyond the target MCU's
relative branch limit will result in assembler errors, even
though SBasic will not report any compilation errors.
SBasic does not maintain an internal program counter, and
will not detect that a branch target is out of range.
Beginning users should omit the /b option, and accept the slight
increase in size and execution times caused by the default branch code
generation.
Experienced users may, however, use the /b option to gain improved
performance. In this case, however, you must carefully monitor the
assembler's output for any errors resulting in out-of-range branches.
If your code generates out-of-range branches using the /b option,
recompile without the option. SBasic currently does not support any
method for selectively compiling direct branches.
You can select the target MCU by using the /m option. The format of
this option is:
/mxxxx
SBasic User's Manual SBasic Version 2.7 Page 8
Printed: December 5, 1999
where xxxx is either 6811 to generate output code for the 68HC11 MCU,
or 6812 to generate output code for the 68HC12 MCU. If you don't
supply the /m option, SB will generate code for the default MCU, the
68HC11.
The differences in output code caused by compiling for the two
machines mostly concerns the library files used. See the discussion
below on library files for more details.
Certain variations of the 68HC12, notably the 68HC912B32, use on-chip
firmware to take over the MCU's interrupt vector table. Similarly,
some 68hc11 chips include masked ROM firmware (such as BUFFALO) that
also takes over the vector table. In both these cases you need to
prevent SBasic from trying to set up a vector table on the target
machine. You can prevent SB from creating an interrupt vector table
by using the /i option. The format of this option is:
/i
If you use the /i option and your SB program must use interrupts, you
will have to add SB code to prepare the appropriate RAM-based jump
table. Refer to the Motorola literature on your target MCU for
details.
Note that the /i option surpresses ALL changes to the vector area,
including the reset vector. SB programs compiled with the /i option
must use some resident firmware, such as BUFFALO, to transfer control
to the start of the program.
SBasic User's Manual SBasic Version 2.7 Page 9
Printed: December 5, 1999
Environment variables
SB supports the use of two DOS environment variables. These variables
can help ease development of multiple projects in SBasic.
When SB begins execution, it checks for the existence of two
environment variables, SB_INCLUDE and SB_LIBRARY. SB assumes
SB_INCLUDE contains the path to a directory containing custom INCLUDE
files. Similarly, SB assumes SB_LIBRARY contains the path to a
directory containing the standard SBasic library files. If either of
these environment variables does not exist, SB defaults to the current
directory when searching for any corresponding files.
You can assign a path to either of these variables in your
AUTOEXEC.BAT file, using DOS' SET command.
Example:
set SB_INCLUDE=C:\MYPROJ\INC
set SB_LIBRARY=C:\SBASIC\LIB
These commands assign paths to the SB_INCLUDE and SB_LIBRARY
environment variables.
SBasic User's Manual SBasic Version 2.7 Page 10
Printed: December 5, 1999
Library files
SB normally compiles all operations into in-line assembly language
source for the target machine. In some cases, however, a function may
translate into so many lines of source code that inserting the code
in-line each time the function is used would yield an unacceptably
large output file.
In these cases, SB automatically appends one or more files of assembly
language source code to the output file. These files, called library
files, contain pre-written source code for performing the
corresponding operation.
For example, most versions of the 68hc11 require several lines of
assembly language code to perform a 16-bit by 16-bit multiplication.
Rather than insert this large section of assembler source every time
your program uses the * operator, SBasic instead compiles a JSR to a
library assembly language subroutine.
At the end of your output file, SBasic then includes the library file
containing the source code for this multiplication subroutine.
SBasic only includes library files when necessary, based on your
source code.
One library file deserves special mention. SB always includes the
library file START11.LIB (START12.LIB for the 68hc12 MCU) during each
compilation. The assembly language source in this file will be
executed each time the target machine begins running your SBasic
program. In fact, the code in this file is executed immediately
following system reset.
If your SBasic application requires changes to the time-sensitive I/O
registers in the 68hc11, you can customize START11.LIB to include
those changes.
Note, however, that you should not change any of the labels provided
in the original versions of START11.LIB and START12.LIB. Other parts
of the SBasic system require that those labels exist, and that they be
named exactly as they are.
SBasic User's Manual SBasic Version 2.7 Page 11
Printed: December 5, 1999
Features
SB is a free-form Basic that supports enough control structures, such
as IF-ELSE-ENDIF, that line numbers should not be necessary. It does
not expect nor support line numbers; if you use them, you will get a
syntax error back.
SB does not support GOTO.
SB generates code that uses the target's largest commonly available
accumulator(s). This means that for the 68hc11 and 68hc12, SB uses
16-bit variables and 16-bit math operations.
SB compiles down to fairly concise assembly language. It does no
optimization from source line to source line. That is, it does not
maintain a history of register usage and attempt to optimize out
redundant operations. Even so, the generated code is quite compact,
and will run fast enough to accomodate most projects.
For those projects that demand higher performance, SB allows you to
embed assembly language source directly in your program. These
assembly statements are passed intact to the target assembler.
SB is case-insensitive with regard to statements, labels, variables,
and constants. The variable FOO may also be referred to as foo, Foo
or fOo.
SB has built-in maximums for several compilation elements such as
variables and labels. These limits are:
Depth of FOR-NEXT nesting is 25. No one should EVER hit this limit.
Compilation parsing stack is limited to 60 atoms. This is an internal
limit of the compiler that determines how complex of a statement the
compiler can parse. Again, no one should ever hit this limit.
Compilation data stack is limited to 40 cells. This should be
sufficient for all programs.
Maximum number of variables that a program can declare is 400. Note
that an array, no matter how large, counts as one variable.
Maximum number of constants is 400. Maximum number of labels is 500.
SB supports the following Basic functions and operators:
rem starts an SBasic comment
' (single quote) starts an SBasic comment
include includes other SBasic source files
org changes location of generated code
data stores 16-bit values in a ROM table
datab stores 8-bit values in a ROM table
SBasic User's Manual SBasic Version 2.7 Page 12
Printed: December 5, 1999
copy copies a block of data between two memory areas
= assignment
+ addition
- subtraction; unary negation
~ 1's complement
* integer multiply
/ integer divide
mod integer modulus
and boolean AND
or boolean OR
xor boolean XOR
= test, equal
> test, greater-than
, > >* test, unsigned greater-than
rshft() shift argument 1 bit to right
lshft() shift argument 1 bit to left
rroll() rotate argument 1 bit to right
lroll() rotate argument 1 bit to left
min() returns smaller of two values (signed)
max() returns larger of two values (signed)
minu() returns smaller of two values (unsigned)
maxu() returns larger of two values (unsigned)
peek() read 16-bit contents of an address
peekb() read 8-bit contents of an address
poke write 16-bit value to an address
pokeb write 8-bit value to an address
swapb exchange bytes
for starts a FOR-NEXT iterative loop
to signed test in a FOR-NEXT loop
to* unsigned test in a FOR-NEXT loop
step optional part of a FOR-NEXT loop
next ends a FOR-NEXT loop
if starts an IF-ELSE-ENDIF structure
else part of an IF-ELSE-ENDIF structure
elseif part of an IF-ELSE-ENDIF structure
endif ends an IF-ELSE-ENDIF structure
while starts a WHILE-WEND structure
wend ends a WHILE-WEND structure
do starts a DO-LOOP structure
while optional part of a DO-LOOP structure
until optional part of a DO-LOOP structure
loop ends a DO-LOOP structure
waitwhile waits while an I/O condition exists
waituntil waits until an I/O condition occurs
select starts a SELECT-CASE structure
case starts a CASE clause within a SELECT-CASE structure
endcase ends a CASE clause
endselect ends a SELECT-CASE structure
exit leaves loop structure early
print output text to the console
SBasic User's Manual SBasic Version 2.7 Page 13
Printed: December 5, 1999
printu output text; numbers print as unsigned
printx output text; numbers print as hexadecimal
inkey() input a character from the console
outch output a character to the console
interrupt marks start of an SBasic ISR
const creates a named constant
declare creates a single 16-bit variable
asm marks start of inline assembly language source
endasm marks end of inline assembly language source
addr() returns address of a label or variable
push pushes a value onto the SB data stack
pop() pops a value from the SB data stack
pull() synonym for pop()
place change an element in the SB data stack
pick() copy an element from the SB data stack
drop remove one or more elements from SB data stack
interrupts enables or disables system interrupts
gosub invokes an SBasic subroutine
usr() invokes an SBasic subroutine, returns one value
return returns from an ISR or subroutine
end ends an SBasic program or ISR
SBasic User's Manual SBasic Version 2.7 Page 14
Printed: December 5, 1999
Remarks
SBasic provides two comment delimiters, for imbedding remarks in your
source files. The traditional REM statement can be used to start a
comment at nearly any point in an SBasic program. You can also use
the newer ' (single-quote). All text following a remark delimiter is
ignored by the SBasic compiler.
You can place a comment at the beginning of any line. You can also
place a comment at the end of any complete SBasic statement. You
cannot place a comment within an SBasic statement.
Example:
rem This is a legal comment
' So is this
a = c + 5 ' this is a legal comment, too
a = c + ' this is illegal!
Note that you can always insert a blank line anywhere in your source;
SBasic always ignores blank lines.
SBasic User's Manual SBasic Version 2.7 Page 15
Printed: December 5, 1999
Include files and the INCLUDE statement
SBasic supports the use of include files to help you organize and
maintain your projects. Include files are simply files containing
SBasic source code for commonly-used functions.
You can insert any include file into your SBasic program file by using
the INCLUDE statement. SBasic will automatically open the named file,
compile the code it contains, then resume compiling your original
file.
For example, you might keep a single file of SBasic code for
controlling servo motors. You can force SBasic to include the code in
this file (call it servo.bas) in your current program file, by using
the INCLUDE statement:
include "servo.bas"
Note the use of double-quotes around the file name.
You can, if you like, supply a full pathname with the file name. For
example:
include "c:\sbasic\inc\servo.bas"
forces SB to search only the supplied path for the file servo.bas. If
SB cannot find the file using this path, it will report an error.
You can also, if you wish, set the DOS environment variable SB_INCLUDE
to contain the full pathname of a directory dedicated to holding your
include files. If SB_INCLUDE exists, SBasic will search that
directory for any files named in INCLUDE statements, provided that the
file name does not itself contain any path information. If SB_INCLUDE
does not exist, SBasic defaults to searching the current directory.
To summarize:
1. If the file name does not contain any path information, SB checks
for the existence of a DOS environment variable, SB_INCLUDE. If
SB_INCLUDE exists, SB searches the path in that variable for the named
file. If SB_INCLUDE does not exist, SB searches the current
directory.
2. If the file name contains path information, SB checks only the
given path, regardless of the existence of SB_INCLUDE.
Rule 2 above means that you can force SB to search the current
directory, even if SB_INCLUDE exists, by using an INCLUDE statement of
the form:
include ".\test.bas"
SBasic User's Manual SBasic Version 2.7 Page 16
Printed: December 5, 1999
Here, the backslash serves as path information, forcing SB to search
the full path given in the INCLUDE statement.
SBasic User's Manual SBasic Version 2.7 Page 17
Printed: December 5, 1999
Labels
SBasic does not support line numbers, but it does support line labels.
Line labels consist of a string of up to 20 characters, ending with a
colon (:). Labels must begin with an alphabetic character or an
underscore ('_'); remaining characters in a label can also include
digits. Any text following a line label definition is ignored.
NOTE: Though legal, starting labels with an underscore can cause
obscure problems if you embed assembly language in your SBasic source
file. See the section below on ASM and ENDASM, regarding references
to SBasic variables from within an ASM block.
Example:
foo: ' define the line label foo
a = 3 ' write a value to A
return ' return from this subroutine
main: ' start of the main program
gosub foo ' execute the subroutine foo
This example shows several important points. Note that labels require
a trailing colon only when they are defined, but not when they are
referenced. Thus, the GOSUB to FOO doesn't need a colon at the end of
FOO.
Also, every SBasic program MUST contain the line label MAIN, even if
it contains no other line labels. The startup code that supports
SBasic on the target system always jumps to the label MAIN: to begin
execution. If your program does not have a MAIN:, the compiler will
report an error.
Note that the line label MAIN does not mark the first line of your
program's code; it only marks the starting point for execution of your
program following reset. You are free to place the line label MAIN
anywhere in your file you deem appropriate.
SBasic User's Manual SBasic Version 2.7 Page 18
Printed: December 5, 1999
Numeric constants
SBasic supports decimal, hexadecimal, and binary numeric constants.
To enter a hexadecimal number in an SB file, preface the number with a
'$'. To enter a binary number in an SB file, preface the number with
a '%'.
Hexadecimal numbers may contain the characters 0-9, A-F, and a-f.
Binary numbers may contain the characters 0 and 1.
The following examples show how to enter different numeric constants:
foo = 1234 ' assigns decimal 1234 to FOO
bar = $1234 ' assigns hexadecimal $1234 to BAR
alpha = %10000 ' assigns decimal 16 to alpha
cat = $12 + 34 ' adds hex $12 to decimal 34
SBasic also supports ASCII character constants. To enter an ASCII
constant, enclose the character in single-quotes. The value used will
consist of the binary equivalent of the quoted character. An ASCII
constant always consists of eight bits; the upper eight bits of the
variable involved will always be 0.
For example:
foo = 'a' ' assigns lowercase-A (97) to foo
SBasic User's Manual SBasic Version 2.7 Page 19
Printed: December 5, 1999
Variables, arrays, and named constants
SBasic requires you to declare the names of all variables used in your
program. You declare variables with the DECLARE command. For
example,
declare foo
creates the SBasic variable FOO.
Variable names must begin with an alphabetic character or an
underscore ('_'); remaining characters in a variable name can also
include digits.
NOTE: Though legal, starting variable names with an underscore can
cause obscure problems if you embed assembly language in your SBasic
source file. See the section below on ASM and ENDASM, regarding
references to SBasic variables from within an ASM block.
All variables use two bytes of RAM. The first variable defined is
always located at assembler address VARBEG. Variables are assigned
addresses based on the order of their declarations.
You must declare a variable before your code can reference that
variable. This means that you will usually place all DECLARE
statements in a block at the beginning of your SBasic source file.
Note that, unlike traditional Basics, SBasic does not automatically
initialize all variables to zero. The value of any variable following
system reset is unknown! Your SBasic program must provide any needed
variable initialization.
SBasic also supports single-dimension arrays, or vectors. Each
element in an array occupies one 16-bit location (two bytes). You use
the DECLARE statement to define an array in much the same way you use
it to define a simple variable. For example:
declare foo(5)
defines the array FOO, consisting of five sequential 16-bit locations.
The first element in any array is always element zero. Thus, FOO in
the above example consists of the five elements named FOO(0) through
FOO(4).
You can use arrays anywhere a variable name would be legal, including
the left side of an assignment operator. For example:
declare foo(5)
foo(2) = 100/n
SBasic User's Manual SBasic Version 2.7 Page 20
Printed: December 5, 1999
SBasic allows you to create named 16-bit constants. You create
constants with the CONST statement. For example:
const bar = 34
creates the SBasic constant BAR with a value of 34. CONST statements
may refer to previously defined constants, and may include any number
of math operations. CONST statements may not, however, refer to
variables, as the contents of variables are not known at compile-time.
If you refer to a variable inside a CONST statement, the compiler will
report an error.
You must create a constant before your code can reference that
constant. This means that you will usually place all CONST statements
in a block at the beginning of your SBasic source file.
Note that named constants do not consume any space in the final
executable file. They only exist as equates in the assembler source
file generated by SBasic.
SBasic User's Manual SBasic Version 2.7 Page 21
Printed: December 5, 1999
Data tables
SBasic allows you to store tables of data in ROM, for access by your
program at run-time. This technique is used often for storing pre-
defined information, such as lookup tables for motor speeds or
mathematical functions.
To store 16-bit values in a data table, use the DATA statement.
Follow the DATA statement with a list of values to be written into
ROM. For example:
data 0, 1, 2, 3 ' store 4 16-bit values in table
SBasic will generate suitable assembly language source to store the
values into code memory at the current location. For the 68hc11, this
example would generate assembly language source similar to:
fdb 0,1,2,3
To store 8-bit values in a data table, use the DATAB statement.
Follow the DATAB statement with a list of values to be written into
ROM. For example:
datab $ff, 123, 256, 'z' ' store 4 8-bit values
SBasic will generate suitable assembly language source to store the
values into code memory at the current location. For the 68hc11, this
example would generate assembly language source similar to:
fcb 255,123,0,122
Note that the third value appears in the SBasic source as 256, but is
converted to 0 in the output source file. This happens because SBasic
only writes the low eight bits of a DATAB list item to the output
file.
In order to access items within a DATA (or DATAB) table, you must
provide a label at the start of the list. Your program can then use
this label to find the first item in the list. For example:
declare n
declare sum
foo:
data 1,2,3,4
data 5,6,7,8
main:
sum = 0
for n = 0 to 7
sum = sum + peek(addr(foo) + n * 2)
next
SBasic User's Manual SBasic Version 2.7 Page 22
Printed: December 5, 1999
end
This code uses the ADDR() function to locate the start of the data
table at label FOO. The list item of interest is found by adding an
offset value (N * 2) to the address of FOO. The PEEK() function then
reads the 16-bit value stored at that address in the table.
To use the above technique with a DATAB table, you must change PEEK()
to PEEKB() and remove the multiplication by 2 in the offset
calculation.
Note that SBasic writes a DATA table in place. That is, the table
appears in exactly the same position in the output file as it appears
in your SBasic source file.
This means you cannot place a DATA table inside a block of executable
code. If you do, the target MCU will eventually try to execute the
DATA table as if it were machine code, and your program will crash.
For that reason, put your DATA statements outside of executable
blocks. A good place to write DATA statements is immediately before
the MAIN: label in your program.
SBasic User's Manual SBasic Version 2.7 Page 23
Printed: December 5, 1999
COPY statement
SBasic provides the COPY statement, used to move data from one memory
area to another. This proves very handy when you need to initialize a
large array. Format of the COPY statement is:
copy from, to, count
where FROM is the address of the data block, TO is the address of the
destination area, and COUNT is the number of BYTES (not variables) to
move.
For example, assume you need to initialize the array FOO with a table
of data, already existing in a block of DATA statements. You would
use statements similar to:
declare foo(10)
table:
data $1234, $5678, $1, $2, $3, $4
data $5, $6, $7, $8
copy addr(table), addr(foo), 20
This code uses the ADDR() function to locate the addresses of the
TABLE data block and the FOO array, then moves 20 bytes of data from
the table to the array.
Note that COPY cannot be used to move data into an area of memory that
overlaps the source area. Should you need to perform this operation,
use two COPY statements, first to move the data into an intermediate
area, then to move it into the final destination area.
SBasic User's Manual SBasic Version 2.7 Page 24
Printed: December 5, 1999
Operators
SBasic supports all of the common arithmetic operators:
= (equal-sign) sets the value of a variable to the result of a
calculation. This is the traditional assignment operator. All
assignments store a 16-bit value into a variable.
+ (plus-sign) performs 16-bit addition.
- (minus-sign) performs 16-bit subtraction. It also acts as the unary
negation operator.
~ (tilde) performs 16-bit one's complement. This is logically
identical to N XOR $ffff.
* (asterisk) multiplies two 16-bit values, yielding a 16-bit product.
Run-time support for SBasic's multiplication operator on a 68hc11 MCU
relies on a library file, included with the SBasic distribution. This
file is automatically added to the assembler source file created by
SBasic, whenever your program invokes the multiplication operator.
Note that this library file is only included if your code uses a
multiply operation. You can create smaller executables by eliminating
any use of the multiplication operator, if appropriate.
For the 68hc12 MCU, multiplication is done with inline assembly using
the EMUL opcode, and executes much faster than it would on a 68hc11.
/ (forward-slash) divides a 16-bit dividend by a 16-bit divisor,
yielding a 16-bit quotient; the remainder is lost.
MOD (modulus) divides a 16-bit dividend by a 16-bit divisor, yielding
a 16-bit remainder; the quotient is lost.
Examples:
alpha = foo + bar ' adds two variables
beta = gamma - $1234 ' subtracts a hex constant
var1 = 3 * var2 ' multiplies a variable
c = delta / 88 ' divides a variable by a constant
m = gamma MOD 10 ' takes the modulus function
SBasic also supports most of the common Boolean operators:
AND performs the logical AND of two 16-bit values.
OR performs the logical inclusive-OR of two 16-bit values.
XOR performs the logical exclusive-OR of two 16-bit values.
SBasic User's Manual SBasic Version 2.7 Page 25
Printed: December 5, 1999
Examples:
alpha = foo AND $7f ' leaves only low 7 bits of foo
beta = alpha OR 255 ' sets all low 8 bits of alpha
gamma = beta XOR $ffff ' inverts all bits in beta
SBasic User's Manual SBasic Version 2.7 Page 26
Printed: December 5, 1999
Comparisons
SBasic supports a wide range of comparisons, for use with control
structures such as IF-ELSE-ENDIF and DO-LOOP. All comparisons test
two 16-bit values and return TRUE if the values meet the comparison
test.
= (equal-to) yields TRUE if the two 16-bit values are equal.
< (less-than) yields TRUE if the first 16-bit value is less than the
second 16-bit value. This is a signed comparison; $8000 is less than
0.
> (greater-than) yields TRUE if the first 16-bit value is greater than
the second 16-bit value. This is a signed comparison; 0 is greater
than $8000.
(not-equal-to) yields TRUE if the two 16-bit values are not equal.
>< (not-equal-to) yields TRUE if the two 16-bit values are not equal.
<= (less-than-or-equal-to) yields TRUE if the first 16-bit value is
less than or equal to the second 16-bit value. This is a signed
comparison.
>= (greater-than-or-equal-to) yields TRUE if the first 16-bit value is
greater than or equal to the second 16-bit value. This is a signed
comparison.
than the second 16-bit value. This is an unsigned comparison; 0 is
less than $8000 unsigned.
>* (greater-than unsigned) yields TRUE if the first 16-bit value is
greater than the second 16-bit value. This is an unsigned comparison;
$8000 is greater than 0 unsigned.
SBasic does not allow you to store the result of a comparison in a
variable. SBasic also does not allow multiple comparisons in a single
operation. Control structures such as IF-ELSE-ENDIF can use only one
comparison in the IF-clause.
SBasic User's Manual SBasic Version 2.7 Page 27
Printed: December 5, 1999
Control structures
SBasic supports several structures for controlling the flow of your
program. Used properly, these control structures can improve the
quality of your program design, making your source file easier to
read, understand, and debug.
Some of the following control structures allow or require a comparison
clause. Such clauses consist of an expression, a comparison operator,
and a second expression. The comparison clause is evaluated as your
program runs and, depending on the evaluation, control transfers
within the control structure.
Example:
while a < 5
a = a + 1
wend
Here, the comparison clause "a < 5" determines whether control remains
in the WHILE-WEND loop or transfers to the line following the WEND
statement.
All comparison clauses may contain one and only one comparison
operator. Multiple comparisons, such as:
while a < 5 and c-1 = 3
are illegal and will generate compilation errors.
Note also that you do not enclose comparisons within parentheses.
Doing so will result in a compilation error.
WHILE-WEND is a conditional loop structure. Control executes all
statements between the WHILE statement and the WEND statement for so
long as the comparison in the WHILE clause is TRUE. When the
comparison becomes FALSE, control exits the loop by transferring to
the line following the WEND statement.
Example:
while a < 500 ' loop while a is less than 500
a = a + 1 ' increment a
wend ' end of loop
This example loops for so long as the value in A is less than
(unsigned) 500.
Note that WHILE-WEND is obsolete, even though SB still supports it.
WHILE-WEND has been replaced by the more flexible DO-LOOP structure.
DO-LOOP is a loop structure that may be either conditional or
unconditional. Control executes all statements between the DO
SBasic User's Manual SBasic Version 2.7 Page 28
Printed: December 5, 1999
statement and the LOOP statement. You may include an optional
comparison clause in either the DO statement or in the LOOP statement.
Examples:
do ' start of a do-loop
gosub process ' do something useful
loop ' end of loop
The above example will loop forever, since it has no comparison
clause. An endless loop is often used as the core of a large program.
do while a < 500 ' loop while a is less than 500
a = a + 1 ' increment a
loop ' end of loop
This example mimics the WHILE-WEND loop above.
do until a > b ' loop until a is greater than b
a = a + c/2 ' change value of a
loop ' end of loop
The above example loops until the value in A is greater (signed) than
the value in B.
do ' start of a do-loop
a = peekb($1000) ' read a value from an I/O port
loop while a = 0 ' loop while a equals 0
The above example loops for so long as the value read from the I/O
port equals 0.
do ' start of a do-loop
a = peekb($1000) ' read a value from an I/O port
loop until a = $ff ' loop until a equals $ff
The above example loops until the value read from the I/O port equals
$ff.
The DO-LOOP provides great flexibility for loop control, because you
can control when the comparison occurs in the loop. If you place the
comparison in the DO statement, the test is performed before the body
of the loop is executed. By placing the comparsion in the LOOP
statement, you can force the body of the loop to execute before the
comparison is performed.
SBasic User's Manual SBasic Version 2.7 Page 29
Printed: December 5, 1999
The FOR-NEXT structure creates an iterated loop. This means that a
selected variable, called the index variable, controls exactly how
many times the loop is executed. Control executes all statements
between the FOR statement and the NEXT statement until the value in
the index variable EXCEEDS a specified limit. The index variable is
always tested at the top of the loop. The comparison is signed for
the usual FOR-NEXT loop, although you can use an unsigned comparison
if necessary.
Example:
for n = 1 to 10
a = a + n
next
Here, the variable N starts with a value of one. The statement inside
the loop is executed ten times, with N incrementing each time the NEXT
statement executes. Eventually, N holds the value 11 when the FOR
statement executes. At this point, the statement inside the loop is
not executed. Instead, control passes directly to the statement
following the NEXT statement.
Sometimes you must use a limit larger than $7fff. Since SBasic uses
16-bit math, numbers larger than $7fff are treated as negative in
signed comparisons. Therefore, the following example:
for n = 1 to $9000
a = a + 1
next
will exit immediately, as SBasic treats $9000 as a negative number,
and 1 is already greater than a negative number.
To change the above example to use an unsigned comparison, use the TO*
operator. The above example becomes:
for n = 1 to* $9000
a = a + 1
next
This loop will execute the expected $9000 times.
Remember to leave room on your limit value so that the index variable
can actually exceed the limit. For example:
for n = 1 to* $ffff
a = a + 1
next
This loop will never end, since the value of N can never exceed the
limit of $ffff.
SBasic User's Manual SBasic Version 2.7 Page 30
Printed: December 5, 1999
Note that it is legal (but not good practice) to modify the index
variable inside the loop.
Normally, the index variable increments by one each time control
reaches the bottom of the loop. You can change the value by which the
index variable increments with the STEP modifier.
Example:
for n = 1 to 10 step 2
a = a + n
next
Here, the variable N starts with a value of one, and increments by two
each time control reaches the NEXT statement. Thus, N takes on the
values 1, 3, 5, 7, 9, and 11. When N becomes 11 at the top of the
loop, control immediately passes to the statement following the NEXT
statement.
The SELECT statment marks the beginning of a SELECT-CASE control
structure. The SELECT-CASE structure allows your code to select one
option out of a list, based on the value of an argument, called the
selector. Only the CASE clause associated with the matching selector
value, if any, is executed.
The general format of a SELECT-CASE structure is:
select foo ' use value of FOO as selector
case 123 ' if FOO = 123...
. ' execute this code
. '
endcase ' end of case 1
case 456 ' if FOO = 456...
. ' execute this code
. '
endcase ' end of case 2
endselect ' end of select structure
This structure replaces the older ON-GOTO construct, and allows
creation of code that is easier to debug and maintain.
The SELECT-CASE structure supports variations for handling special
cases. For example:
select foo + 3*j ' use expression for selector
case 1 ' if selector = 1...
case n ' or N...
case 'X' ' or ASCII character X...
foo = n + 3 ' change FOO
endcase ' end of first case
SBasic User's Manual SBasic Version 2.7 Page 31
Printed: December 5, 1999
case 2 ' if selector = 2...
case 3 ' or 3...
foo = n + 2 ' change FOO
endcase ' end of second case
foo = 4 ' default action...
endselect
This example shows the use of multiple CASE statements within a CASE
clause. This feature comes in handy if your code must perform the
same function for a group of selector values.
This example also shows the method for performing a default action.
The line FOO = 4 executes if no CASE clause matches the selector
value. Note that the default clause does not require an initial CASE
statement or a terminating ENDCASE statement.
Also, this example shows that the selector value for this example is
an expression. You can use any valid algebraic expression as the
selector value in a SELECT statement.
Note, however, that CASE statements only accept a numeric value, a
constant, or a variable as an argument. You cannot use an algebraic
expression as the argument to a CASE statement! Doing so will
generate a compiler error.
Finally, this example shows that you can change the selector value
within a CASE clause, without affecting the actions of the SELECT
statement. This is because control passes to the ENDSELECT statement
immediately after executing the code in any CASE clause. Thus, any
following CASE clauses do not evaluate the changed selector value.
The EXIT statement allows you to leave a looping structure before the
terminating condition, if any, is reached. Control automatically
jumps to the end of the currently active looping structure.
You can also use EXIT to leave a SELECT-CASE structure. In this case,
control will jump to the corresponding ENDSELECT statement.
Example:
for n = 1 to 10
if n = 5
exit
endif
next
Here, control automatically leaves the FOR-NEXT loop when N equals 5.
SBasic User's Manual SBasic Version 2.7 Page 32
Printed: December 5, 1999
Note that you can use EXIT inside DO-LOOP, WHILE-WEND, FOR-NEXT, and
SELECT-CASE structures. You cannot use EXIT outside of these looping
structures.
The IF-ENDIF structure selectively executes a block of statements,
depending on a comparison at the beginning of the structure. If the
comparison is TRUE, the statements are executed, otherwise they are
not.
Example:
if a = $12
b = b * 2
endif
Here, the middle statement is executed only if the value of A is $12.
You can use the ELSE modifier to provide greater flexibility to the
IF-ENDIF structure. Statements between the ELSE statement and the
ENDIF statement are only executed if the comparison in the IF
statement is FALSE.
Example:
if a = $12
b = b * 2
else
b = -b
endif
Here, B is set to -B only if A is not $12.
Note that you do not include parentheses around the comparison clause
of any structure. Doing so will cause SBasic to report an error
during compilation.
You can also use the ELSEIF modifier to simplify nested IF-ENDIF
structures. Consider the following example, in which three different
conditions must be tested:
if a = $12
n = 1
else
if foo = w+2
n = 3
else
if bar = a+w
n = 5
endif
endif
endif
SBasic User's Manual SBasic Version 2.7 Page 33
Printed: December 5, 1999
This type of test sequence can be difficult to decipher. Using the
ELSEIF modifier simplifies the structure:
if a = $12
n = 1
elseif foo = w+2
n = 3
elseif bar = a+w
n = 5
endif
Note that the ELSEIF statement requires the same type of comparison
clause used by the IF statement.
SBasic User's Manual SBasic Version 2.7 Page 34
Printed: December 5, 1999
Functions and statements
SBasic supports several functions and statements useful for embedded
control applications.
Generally speaking, a function returns a value, while a statement does
not. All functions contain parentheses, though not all functions
actually need an argument inside the parentheses.
All statements, however, appear without parentheses.
The SWAPB function exchanges the two bytes of the 16-bit argument and
returns the new value. It does not alter the original argument. This
operation is useful for sending both bytes of a variable to a byte-
wide I/O port, such as the 68hc11's SPI.
Example:
j = $1234 ' prepare j
pokeb spdr, swapb(j) ' send $12 to SPI
The RSHFT and LSHFT functions shift the 16-bit argument one bit
position, either right or left. This operation can be used as a fast
multiply or divide by 2.
Example:
n = %11000011 ' initial value of n
n = rshft(n) ' n now holds %01100001
n = lshft(n) ' n now holds %11000010
Note that the shift functions move the argument one bit in the
specified direction, moving a 0 bit into the vacated position. Thus:
x 0 -> xxxxxxxxxxxxxxx -> x = RSHFT()
The RROLL and LROLL functions rotate the 16-bit argument one bit
position, either right or left. This operation can be used as part of
a pulse-width modulation (PWM) function.
Example:
n = %1111 ' initial value of n
n = rroll(n) ' n now holds %1000000000000111
n = lroll(n) ' n now holds %0000000000001111
Note that the roll functions move the argument one bit in the
specified direction, placing the rotated bit into the position at the
opposite end of the word. Thus:
SBasic User's Manual SBasic Version 2.7 Page 35
Printed: December 5, 1999
+-------------+
| | = LROLL() | | = RROLL()
xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx
The PEEK function returns the 16-bit value stored in a specific
address. This is the usual function for reading 16-bit I/O ports.
Example:
a = peek($1000) ' get 16-bit value from address $1000
You can make your use of PEEK easier to understand if you combine it
with named constants defined with the CONST statement.
Example:
const porta = $1000 ' define address of port A
a = peek(porta) ' get 16-bit value at port A
The PEEKB function returns the 8-bit value stored in a specific
address. This is the usual function for reading 8-bit I/O ports.
Example:
a = peekb($1020) ' get 8-bit value from address $1020
Recall that the = (assignment) operator writes a 16-bit value to a
variable. In the case of the PEEKB function, the top eight bits of
the variable will always be written as 0. Thus, reading a value of
$45 in the above example results in storing $0045 in variable A.
You can make your use of PEEKB easier to understand if you combine it
with named constants defined with the CONST statement.
Example:
const portb = $1020 ' define address of port B
a = peekb(portb) ' get 8-bit value at port B
The POKE statement writes a 16-bit value to a specific address. This
is the usual statement for writing data to 16-bit I/O ports.
Example:
poke $1000, a ' write value in A to address $1000
Note the difference in syntax between the PEEK function and the POKE
statement; PEEK uses parentheses around the address argument, while
POKE does not use parentheses.
SBasic User's Manual SBasic Version 2.7 Page 36
Printed: December 5, 1999
You can make your use of POKE easier to understand if you combine it
with named constants defined with the CONST statement.
Example:
const porta = $1000 ' define address of port A
poke porta, a ' write value in A to port A
The POKEB statement writes a 8-bit value to a specific address. This
is the usual statement for writing data to 8-bit I/O ports.
Example:
pokeb $1000, a ' write low 8 bits in A to addr $1000
Note the difference in syntax between the PEEKB function and the POKEB
statement; PEEKB uses parentheses around the address argument, while
POKEB does not use parentheses.
You can make your use of POKEB easier to understand if you combine it
with named constants defined with the CONST statement.
Example:
const porta = $1000 ' define address of port A
pokeb porta, a ' write low 8 bits in A to port A
The WAITWHILE and WAITUNTIL statements provide high-speed testing
loops, for use with 8-bit I/O ports. You can use these single
statements to replace larger, less efficient wait-loops built from the
PEEKB function.
The WAITWHILE statement loops while the contents of an 8-bit port,
ANDed with an 8-bit mask, yields a non-zero result. The WAITUNTIL
statement does the opposite; it loops UNTIL the contents of an 8-bit
value, ANDed with an 8-bit mask, yields a non-zero result.
Example:
waitwhile $1000, $40 ' wait while bit 6 of $1000 is high
This statement repeatedly reads the 8-bit value at address $1000 and
ANDs that value with $40, for so long as the result is not zero. When
the result equals zero, the loop terminates.
Example:
waituntil j, n ' wait until mask of value at j is not 0
SBasic User's Manual SBasic Version 2.7 Page 37
Printed: December 5, 1999
This statement repeatedly reads the 8-bit value at the address
contained in variable J and ANDs that value with the low eight bits in
variable N, until the result is not zero. When the result is not
zero, the loop terminates.
You must be aware of a few characteristics of the WAIT loops. The
first argument is always treated as an address, even if you use a
variable. In the second example above, the loop does not test the
value in J, it uses the value in J as the address to test.
Second, the WAIT statements always test an 8-bit address; you cannot
test a 16-bit I/O port with these statements.
Also, the WAIT statements always use the low eight bits of the mask
argument. Thus, if you specify a variable as the mask value, the WAIT
statements will automatically use just the low eight bits in the loop
test.
Finally, you can improve the speed of the generated code by using only
constants, numbers, or variables as arguments to these statements.
Using an argument that contains math or logical operations will
generate larger, slower test loops.
Example:
waitwhile j+4, n/q ' this runs slowly
will run slowly, since the two math operations will be performed
inside each test loop. A better way to write this is:
adr = j+4 ' calc the address
mask = n/q ' calc the mask
waitwhile adr, mask ' now do the loop
The ADDR function returns the address of a specified label or
variable.
Example:
a = addr(MyLabel) ' put address of MyLabel in A
You can use the ADDR() function to locate the first element in an
array. To do this, simply leave off the parentheses when supplying
the array's name. For example:
declare foo(5)
a = addr(foo)
causes A to contain the address of FOO(0).
SBasic User's Manual SBasic Version 2.7 Page 38
Printed: December 5, 1999
You cannot use ADDR() to calculate the address of a selected array
element; if you include a subscript after the array name, the compiler
will report an error in the ADDR() function.
This isn't really a problem, though, since all array elements occupy
two bytes. For example:
a = addr(foo) + n * 2
causes A to contain the address of FOO(N). It does this by finding
the address of FOO(0), then adding two to that address for each
element named in N. Thus, if N = 2, A will hold the address of
FOO(2).
See the discussion below on the INTERRUPT statement for a detailed
example of using the ADDR function.
SBasic supports a limited PRINT statement. SBasic's PRINT statement
sends characters to a default output device, based on the target
system. For the 68hc11, this is the Serial Communications Interface,
or SCI. For the 68hc12, this is the first of the asynchronous serial
ports, equivalent to the SCI.
SBasic supports the following variations of the PRINT statement:
print "a constant string followed by a CR"
print "a string followed by a space";
print "a string followed by a TAB character",
print ' prints a blank line
print foo ' prints the value of foo
print "FOO ="; foo ' prints a string, then a value
print a; b; c ' prints three values
Additionally, SBasic supports two statements similar to PRINT that
print values in slightly different formats. The PRINTU statement
prints any values in unsigned format and the PRINTX statement prints
any values in hexadecimal characters. The PRINTU and PRINTX
statements behave exactly the same as the PRINT statement with regard
to spacing, tabs, and quoted strings.
For example:
print "-1 = "; -1 ' prints -1 = -1
printu "-1 = "; -1 ' prints -1 = 65535
printx "-1 = "; -1 ' prints -1 = FFFF
SBasic also supports the C language's escape character for embedding
special characters within a PRINT string. You can embed any of the
following special characters inside a PRINT string:
SBasic User's Manual SBasic Version 2.7 Page 39
Printed: December 5, 1999
"\n" inserts a newline ($0a)
"\r" inserts a carriage-return ($0d)
"\f" inserts a form-feed ($0c)
"\a" inserts an alert ($07)
"\b" inserts a backspace ($08)
"\t" inserts a horizontal tab ($09)
"\v" inserts a vertical tab ($0b)
"\\" inserts a backslash
For example:
print "Hello, world!\n\r\a";
prints the string "Hello, world!" followed by a line-feed, a carriage-
return, and a bell.
Run-time support for SBasic's PRINT statements relies on several
library files, included with the SBasic distribution. These files are
automatically added to the assembler source file created by SBasic,
whenever your program invokes a variation of the PRINT statement.
You may modify the PRINT statement library routines, if desired, to
create support for other output devices. However, you must keep the
names of all subroutines defined inside a library file unchanged.
This is because the assembler source created by SBasic uses fixed
names for library functions. If you change the names of the library
routines, the assembler will report an undefined label error when it
tries to find the library subroutines.
Note that these library files are only included if your code uses a
PRINT statement. You can create smaller executables by eliminating
any use of the PRINT statement, if appropriate.
NOTE: Your code must issue any setup instructions necessary
to prepare the default I/O port for use with the PRINT
statements. For details on setting up the 68hc11's SCI,
consult the section below, "Character I/O on the 68hc11."
SBasic supports a version of the INKEY() function. You can use
INKEY() to receive characters from a default input device, based on
the target system. For the 68hc11, this is the SCI. For the 68hc12,
this is the first asynchronous serial port, similar to the SCI.
Unlike the INKEY() function in traditional Basics, SBasic's version
does not expect or allow an argument.
If no character was received from the console, INKEY() returns 0. If
a character was received, INKEY() returns a value containing the
character in the low eight bits; additionally, INKEY() sets bit 8 of
the returned value. Setting bit 8 allows your program to distinguish
SBasic User's Manual SBasic Version 2.7 Page 40
Printed: December 5, 1999
between receiving a NULL (returned value = $0100) and not receiving
any character (returned value = $0).
For example:
do
n = inkey()
loop while n = 0
n = n and $ff
This code loops until INKEY() returns a valid character from the
console. It then strips off the upper byte, leaving only the received
character in N.
INKEY() sets bit 8 to permit receiving any character, including NULLs,
from the console input device.
The actual code that supports the INKEY() function appears in the
library files INKEY11.LIB (for the 68hc11) and INKEY12.LIB (for the
68hc12). You can edit the supplied INKEYxx.LIB source file to support
using INKEY() with other devices. Take care, however, to ensure your
new version of INKEY() preserves the register usage of the original.
NOTE: Your code must issue any setup instructions necessary
to prepare the default I/O port for use with the INKEY()
function. For details on setting up the 68hc11's SCI,
consult the section below, "Character I/O on the 68hc11."
Sbasic provides the OUTCH statement, used to send an 8-bit character
directly to the console. This statement provides the same
functionality as the usual Basic phrase:
PRINT CHR$(N);
only the OUTCH statement takes much less space, both in the source
file and in the final executable.
For example:
outch n+2
This example adds 2 to the current value in variable N, then sends the
low eight bits of the sum directly to the console device as an ASCII
character.
The actual code that supports OUTCH appears in the library files
OUTCH11.LIB (for the 68hc11) and OUTCH12.LIB (for the 68hc12). You
can edit the supplied OUTCHxx.LIB source file to support using OUTCH
with other devices. Take care, however, to ensure your new version of
OUTCH preserves the register usage of the original.
SBasic User's Manual SBasic Version 2.7 Page 41
Printed: December 5, 1999
NOTE: The routine contained in OUTCHxx.LIB is used by all
console output statements, including all variations of
PRINT. Changing the routine in OUTCHxx.LIB will also change
the behavior of all variations of PRINT.
OUTCH only sends the low eight bits of its argument to the console.
Therefore, OUTCH can be used directly with the value returned by
INKEY(), as shown:
do
n = inkey()
loop while n = 0
outch n
This code loops until INKEY() returns a non-zero value for N,
indicating that a character was entered at the console. The value in
N is then sent back to the console as an echo. Since OUTCH ignores
the upper byte in N, the character echoes properly; your code does not
need to alter the upper byte of N before calling OUTCH.
NOTE: Your code must issue any setup instructions necessary
to prepare the default I/O port for use with the OUTCH
statement. For details on setting up the 68hc11's SCI,
consult the section below, "Character I/O on the 68hc11."
The MIN and MAX functions return the smaller or greater of two
arguments, respectively. Both functions use signed comparisons. You
must supply two arguments for either function, with a comma separator
between arguments. You may use either constants or algebraic
expressions for either or both arguments. For example:
print min(-4, 3); "is smaller than"; max(-4, 3)
prints the correct result, as a signed comparison between -4 and 3
will properly show that -4 is smaller than 3.
The MINU and MAXU functions perform the same operations as MIN() and
MAX, except these functions use unsigned comparisons. For example:
print minu(-4, 3); "is smaller than"; maxu(-4, 3)
will print apparent nonsense, as -4, taken as an unsigned number, is
larger than 3.
SBasic User's Manual SBasic Version 2.7 Page 42
Printed: December 5, 1999
Subroutines, GOSUB, and USR()
SBasic supports the traditional Basic concept of subroutines. A
subroutine is a block of SBasic statements that can be invoked, or
called, from elsewhere in the SBasic program. After these statements
complete execution, control returns to the calling section.
A subroutine contains a line label marking the start of the
subroutine, and at least one RETURN statement, which transfers control
back to the calling section.
The calling section of SBasic code invokes, or calls, a subroutine by
means of the GOSUB statement.
Example:
main:
do ' start of an endless loop
gosub foo ' call subroutine FOO
loop ' loop forever
foo: ' start of subroutine FOO
a = a + 1 ' increment A
return ' return to caller
This example, while not very useful, shows how a subroutine is invoked
and how it is defined. Note that you can use a GOSUB to a subroutine
before that subroutine is defined in your code.
Note that code inside a subroutine has full access to all variables
defined with the DECLARE statement. Changes made to a variable from
inside a subroutine remain in effect when control returns from that
subroutine.
All GOSUBs push a return address onto the target's return stack.
SBasic's data stack resides a fixed distance below the return stack.
Excessive nesting of GOSUBs could clobber values on the data stack.
The address of the subroutine invoked must be either a label or a
variable; you may not use algebraic expressions or functions as
addresses for a GOSUB statement.
GOSUBs may pass one or more arguments to the called subroutine. Your
code should include the arguments after the name of the subroutine
invoked. For example:
main:
do ' start an endless loop
gosub foo, 3, j ' call FOO with two arguments
loop ' loop forever
SBasic User's Manual SBasic Version 2.7 Page 43
Printed: December 5, 1999
SBasic automatically pushes all arguments onto the data stack, then
calls the named subroutine as above. Code in the subroutine can use
the data stack operators, such as PICK(), to test or change the
arguments.
Pay careful attention to the order in which SBasic pushes the
arguments; they are pushed in the order given. For example:
gosub foo, 3, j
^ ^
| +--------- This argument is on top of data stack
+------------ This argument is next on data stack
This method of passing arguments means that a subroutine may be passed
different numbers of arguments at any time. SBasic performs no checks
to see if you have passed the correct number of arguments.
SBasic similarly does not "clean up" the data stack before returning
control to the calling routine. Your code must update the data stack,
if necessary, to remove any arguments. You can choose to do this
within the called routine, or in the calling routine after the
subroutine invocation. Regardless of where you perform this cleanup,
it must be done or your program will eventually corrupt the data
stack, likely crashing.
Note that GOSUBs make no allowance for the called routine returning a
value. Thus, code that uses a GOSUB to invoke a subroutine must rely
on global variables to check the results of the GOSUB. This can
result in awkward code that can prove difficult to maintain.
To return a value to the calling routine, use SBasic's USR() function.
The USR() function is similar to GOSUB, in that it calls an SBasic
subroutine. It can also include one or more arguments.
Unlike GOSUB, however, USR() returns a value from the called routine
for later use. For example:
n = usr(foo, 3) ' call FOO, put returned value in N
j = usr(bar) ' call BAR with no arguments
Note the difference between USR() and GOSUB. The USR() function, like
any other SBasic function, requires parentheses around its list of
arguments.
The address of the subroutine invoked must be either a label or a
variable; you may not use algebraic expressions or functions as
addresses for a USR() function.
USR() functions use the data stack in exactly the same manner
described above for the GOSUB statement. Also, the first argument in
the USR() list is always the address of the called subroutine.
SBasic User's Manual SBasic Version 2.7 Page 44
Printed: December 5, 1999
The RETURN statement
SBasic's RETURN statement serves two functions. You can use it to
return control from a subroutine, and you can use it to return control
from an interrupt. For details on processing interrupts, see the
Interrupts section below.
When used in main-line code (code not in an interrupt service
routine), RETURN generates the proper code to return control from a
subroutine. For the 68hc11 and 68hc12 MCUs, this is an RTS
instruction.
Note that SBasic does not check to see if you are using a RETURN
statement after a label. A RETURN statement always generates a
______
Return-from-Subroutine instruction in main-line code, regardless of
where it occurs.
When used in an interrupt section, RETURN generates the proper code to
return control from an interrupt or exception. For the 68hc11 and
68hc12 MCUs, this is an RTI instruction.
Additionally, a RETURN statement in main-line code may optionally
include a value that will be returned to the calling routine. For
example:
return ' this statement just returns
return j+3 ' this statement returns a value
You may include a RETURN statement with a returned value at any point
in your code, even inside an interrupt section. However, the returned
value will only have meaning if it occurs in main-line code, and even
then only if control is returning to a USR() invocation. Returned
values to a GOSUB are ignored, as are returned values from an
interrupt section.
SBasic User's Manual SBasic Version 2.7 Page 45
Printed: December 5, 1999
Interrupts
SBasic provides support for processing interrupts on the target
system.
You can write interrupt service routines (ISRs) directly in SBasic,
rather than having to drop down into assembly language for the target
machine. However, you must declare a block of SBasic code as an ISR
by using the INTERRUPT statement.
The INTERRUPT statement can accept a single argument, which is the
address on the target system to use as an interrupt vector. SBasic
will determine the address of the ISR code, then write that address to
the vector address you specify.
Later, when your program is running on the target system, an interrupt
will cause control to transfer to the address stored in the vector
address. This in turn starts execution of your SBasic routine.
Example:
interrupt $fff0 ' use $fff0 as the interrupt vector
a = peekb(porta) ' read 8 bits from port A
end ' return from the interrupt
Given the above example, an interrupt that uses $fff0 as its vector
will cause control to jump to the statement containing the PEEKB
function. This small program will read a value from port A, then
return from the interrupt.
Note that your SBasic routine does not get written to address $fff0;
only the address of your routine gets written there. If necessary,
examine the code created by the compiler to help understand how SBasic
interrupts work.
You must use an END statment to terminate all ISR code following an
INTERRUPT statement. When it processes this END statement, SBasic
compiles the proper Return from Interrupt instruction for the target
system.
You sometimes need more than one exit from an ISR. If so, simply use
the RETURN statement to exit the ISR. SBasic will automatically
compile the proper instruction for leaving the interrupt section.
Example:
interrupt $fff0
if n = $66
return
endif
n = $10
end
SBasic User's Manual SBasic Version 2.7 Page 46
Printed: December 5, 1999
In the above example, control leaves the ISR immediately if N contains
the value $66. If not, then N is changed to $10 and control leaves
the ISR through the normal END statement.
In rare cases, you may need to combine a line label with the INTERRUPT
statement. This can happen in some variations of the 68hc11 MCU. The
'a1 variation, for example, often contains a form of the BUFFALO
monitor in on-chip ROM. BUFFALO takes full control of the interrupt
vectors, so SBasic cannot modify any of the vectors.
Instead, BUFFALO expects your program to use specific addresses in on-
chip RAM as jump vectors to reach your ISR code.
To get around this obstacle, you must combine a line label with use of
the ADDR function.
Example:
interrupt ' note that no address is used!
rtiisr:
if u 0 ' if u is not yet 0
u = u - 1 ' decrement u
endif
pokeb tflg2, $40 ' rearm interrupt
end
main:
pokeb $eb, $7e ' write a jump instruction
poke $ec, addr(rtiisr) ' write addr of ISR
Here, the code in MAIN modifies the RAM addresses used by BUFFALO. It
stores a JMP instruction ($7e) followed by the address of the jump
target.
When the RTI interrupt occurs, BUFFALO will jump to address $eb. The
code left there by MAIN will in turn pass control to the ISR at
RTIISR, where the actual interrupt processing occurs.
Note that the above example does not require an argument to the
INTERRUPT statement. This means SBasic will not create an entry in
the target's vector area. The above code, following the label MAIN,
must be used to provide the target processor with access to the ISR.
If necessary, you can use the /i option on SB's command line to
surpress generation of all interrupt vectors, including the reset
vector. You can use this option if the target MCU already contains
firmware for activating your program following reset. Examples of
such a situation include a 68HC11A1 with BUFFALO already in ROM, and
the 68HC912B32 with its on-chip bootloader.
SBasic User's Manual SBasic Version 2.7 Page 47
Printed: December 5, 1999
You can enable or disable system-wide interrupts by using the
INTERRUPTS statment. This statement takes a single argument that is
ON to enable interrupts or OFF to disable them. This statement only
affects system-wide interrupts, and its exact implementation varies,
based on the target system.
For the 68hc11 and 68hc12 MCUs, INTERRUPTS ON is compiled into a CLI
instruction and INTERRUPTS OFF is compiled into a SEI instruction.
Example:
INTERRUPTS ON ' turn on system-wide interrupts
SBasic User's Manual SBasic Version 2.7 Page 48
Printed: December 5, 1999
The ORG statement
Normally, SB generates all code so it occupies sequential addresses on
the target machine, starting at the address named CODEBEG. You can
think of this range of addresses as SB's original code section.
If necessary, you can force SB to compile code at other addresses, by
using the ORG statement. The ORG statement takes one of three forms:
org or
org code or
org code
where is the address where you want subsequent SB code to
compile. You can think of these other address ranges as alternate
code sections. For example:
org $200
causes subsequent SB code to compile in an alternate section, starting
at address $200. SB will continue to compile all code into sequential
addresses, until you end the program or change the compile origin with
another ORG statement.
If you use the keyword CODE as the argument to an ORG statement, SB
resumes compiling at the last address in the original code section.
Perhaps a larger example will clarify this. Assume that the following
program was compiled with a CODEBEG address of $8000:
main:
n = 14 ' this code compiles at $8000
org $400 ' change the origin
table1: ' use a label at new origin
datab 0,1,2,3 ' this code compiles at $400
org $500 ' change the origin
interrupt $fff0 ' RTI ISR compiles at $500
end ' bogus ISR, just for example
org code ' return to original code section
j = addr(table1) ' sets j to $400
end
You may use as many ORG statements, and change between alternate code
sections and the original code section, as often as you want.
In rare cases, you might need to change the address of SBasic's code
section inside your program. Early versions of the 68hc912b32
contained an on-chip bootloader that took over the vector area. To
SBasic User's Manual SBasic Version 2.7 Page 49
Printed: December 5, 1999
force the vectors to appear at the proper locations, and also to force
the library routines to compile in the correct locations, I had to use
the third variation of the ORG statement above.
For example, I compiled the following portions of code using a /c
option of $f700:
org $8000 code ' redefine code section here
main:
n = 14 ' this is the mainline code
... ' rest of mainline code goes here
org $f7e8 ' address of RTI vector
asm ' switch to assembly language
jmp _rtiisr use an assembly JMP instruction
endasm ' back to SBasic
org code ' return to code section
end ' end of program
The /cf700 option started compilation with the code section at $f700.
This caused SBasic to write the startup library code at $f700. The
ORG $8000 statement then moved MAIN down to $8000 and also caused the
code section to move to that address. The rest of the mainline code
(not shown here) compiled from there.
Next, the ORG $f7e8 let me write a JMP instruction into the
bootloader's vector area for use by the RTI interrupt. Finally, the
ORG CODE statement switched back to the code section, now somewhere
above $8000.
This last step is important. SBasic automatically switches to the
code section before adding any library files at the end of the
compilation. If I hadn't moved the code section to $8000, SBasic
would have added the library files at $f700, which was the original
code section. The resulting executable file would have failed.
SBasic User's Manual SBasic Version 2.7 Page 50
Printed: December 5, 1999
The data stack
SBasic supports a data stack, for temporary storage of data. For the
68hc11 and 68hc12 MCUs, the data stack resides about 32 bytes below
the return stack. Both stacks grow downward (towards lower addresses)
as items are pushed onto them.
Do not confuse the use of these two stacks. The return stack holds
all data used by the target MCU in servicing interrupts and subroutine
calls. Items placed on the return stack are not currently accessible
by your SBasic code.
The data stack, however, contains items explicitly placed by your
program. You are free to use the data stack in any manner you like,
and items placed on the stack will remain until your code specifically
removes or modifies them.
You can push items onto the data stack by using the PUSH statement.
The PUSH statement takes a single argument; the 16-bit value of that
argument will be pushed onto the data stack.
Example:
push n+5
adds 5 to the current value of N and pushes the sum onto the data
stack. The value in N does not change.
The size of the data stack depends on where you place the return
stack, using the /S option. The data stack will grow downward in
memory until it runs into whatever values, if any, lie below it.
There are no runtime checks for pushing too many items onto the data
stack.
You can pull (or pop) items from the data stack by using the POP()
function. POP() returns the top (most recent) item pushed onto the
data stack.
Example:
n = pop() + 5
pops the top item off the data stack, adds 5 to that value, and stores
the sum in variable N.
There are no runtime checks for popping too many items from the data
stack. The data stack resides below the return stack, and popping
items causes the data stack pointer to move upwards in memory. If you
pop more items from the data stack than you pushed onto it, you risk
corrupting the return stack. This in turn will cause your program to
crash on a later RETURN statement.
SBasic User's Manual SBasic Version 2.7 Page 51
Printed: December 5, 1999
You can also use the PULL() function to remove items from the data
stack. PULL() works exactly the same was as the POP() function; it is
simply a synonym for POP().
You can copy a value from within the data stack by using the PICK()
function. PICK() locates a specific item within the data, and returns
that value.
Example:
n = pick(2)
copies the third item in the data stack into N. The size of the data
stack does not change, and the value in the third item does not
change.
Note that the item on the top of the stack is item 0; the second item
is item 1. SBasic does not check to see how many items are actually
on the data stack. If you supply an argument to PICK() that is larger
than the current data stack, SBasic will return a bogus but legal
value.
You can alter a value within the data stack by using the PLACE
statement. PLACE stores a 16-bit value into a specified item in the
data stack.
Example:
PLACE 2, n
stores the value in N into the third item in the data stack. The
value in N does not change, and the size of the data stack does not
change.
SBasic does not test the actual size of the data stack before
executing the PLACE statement. Using PLACE to modify an item beyond
the actual data stack will corrupt that location in memory and could
crash your program.
You can combine PICK() and PLACE to create 16-bit variables local to a
section of code, such as a subroutine. For example:
foo:
do
place 0, pick(0) + 1 ' increment the item
loop while pick(0) < 5 ' loop until it hits 5
This code uses an item, already stored on the data stack by previous
code, as a local variable. Changes to this variable occur only in the
data stack, not in a DECLAREd variable.
SBasic User's Manual SBasic Version 2.7 Page 52
Printed: December 5, 1999
In some cases, you want to remove items from the data stack without
actually using the removed values. The DROP statement serves this
purpose. It takes a single argument, which gives the number of items
(NOT BYTES) to remove from the data stack. For the 68hc11, DROP
removes two bytes for each value. For example:
drop 3 ' remove 6 bytes on a 68hc11
SBasic does not check that you are DROPping a valid number of items
from the stack. If you DROP too many items, you risk corrupting the
return stack, located immediately above the data stack on a 68hc11.
If this happens, your program will likely crash.
The SWAP statement makes it easier to manipulate items on the data
stack. It exchanges the values in the topmost and second cells on the
data stack.
Example:
push 2 ' first push a 2
push 3 ' stack has 3, then 2
swap ' now stack has 2, then 3
Note that SWAP does not alter the size of the data stack, only the
contents of the top two cells on the stack. SWAP always uses 16-bit
cells.
Do not confuse the functions of SWAPB, which exchanges bytes within a
variable, and SWAP, which exchanges cells on the data stack.
SBasic User's Manual SBasic Version 2.7 Page 53
Printed: December 5, 1999
ASM and ENDASM
The ASM and ENDASM statements allow you to imbed assembly language
source inside your SBasic program. Assembly language source lines
between these two statements (with one exception) are not processed by
SB; instead, they are passed directly to the output file for
subsequent assembly.
This feature allows you to drop down into assembly language when
necessary, should you need to write code that must run faster or take
up less space. Additionally, imbedded assembly language gives you
direct access to registers on the target system not currently
supported by SBasic. For example, you can gain access to the 68hc11's
hardware stack register (S) by using imbedded assembly language.
The following example shows how to imbed 68hc11 assembly language:
foo:
asm ' switch to assembly language
ldx #$1000 point x at i/o regs
ldd $0e,x get 16-bit counter TCNT
std _time save in variable TIME
endasm ' switch back to SBasic
return ' and return
This example shows an SB routine named FOO that uses imbedded assembly
language to access the 68hc11's TCNT register. The value read from
TCNT is stored in the SBasic variable TIME. The example then uses the
ENDASM statement to switch back to SBasic, where the RETURN statement
returns control to the calling routine.
Note that you will generally use an SBasic label at the start of an
assembly section; other SBasic routines can use this label to pass
control to your assembly section. Note also that imbedded assembly
language lines can (and should) have comments appended to them.
With one exception, all source lines between an ASM and an ENDASM
statement are passed unaltered to the output file for processing by
the assembler. Thus, you cannot use SBasic statements or functions
within an assembly section. Such statements or functions would be
processed by the assembler, not by SBasic, and will result in
assembler errors.
The sole exception to the above involves an underscore character. As
shown in the example above, you can refer to SBasic variables,
constants, or labels by prepending an underscore to the name. Before
SBasic passes each assembly source line to the output file, it scans
the line for any underscores. If SB detects an underscore, the
following characters are parsed and tested against SBasic's list of
SBasic User's Manual SBasic Version 2.7 Page 54
Printed: December 5, 1999
known variables, constants, and labels. If found, the underscore and
following characters are replaced with SBasic's internal name. Since
this internal name is what the assembler will use to resolve operands,
the SBasic name will be understood by the assembler. In the above
example, SBasic would convert the string "_time" to something like
"var009."
SBasic can handle multiple occurences of underscores within a source
line. For example, it will properly resolve a line such as:
ldx #_main+2*_cons0 uses a label and a constant
If SBasic cannot resolve the character string following an underscore
into the name of a variable, constant, or label, the line is passed
unchanged to the output file. This will usually result in an
assembler error message, but it will not cause an SBasic error! This
it will not cause an SBasic error!
means that if you use inline assembly language, you must check not
only for SBasic errors but for assembler errors as well. SBasic will
not know that the assembler could not resolve the label.
It is easy to lose track of which addresses are known to SBasic and
which are known to the assembler. Remember that labels known to
SBasic are automatically converted to an internal label before SBasic
writes them to the output file. Thus, your SBasic source may refer to
a label WAMPUM, but it will be converted to something like lbl010
before the assembler sees it.
To refer to standard assembler labels such as I/O registers, make sure
that you make them known to the assembler by including them within an
ASM section. The above example could have been written:
foo:
asm ' switch to assembly language
iobase equ $1000 address of I/O regs
tcnt equ $0e offset to TCNT reg
ldx #ioregs point x at i/o regs
ldd tcnt,x get 16-bit counter TCNT
std _time save in variable TIME
endasm ' switch back to SBasic
return ' and return
SBasic User's Manual SBasic Version 2.7 Page 55
Printed: December 5, 1999
The ASMFUNC statement
The ASMFUNC statement gives SB access to labels and routines within a
block of assembly language code. See the section above on the ASM
statement. The format for the ASMFUNC statement is:
asmfunc foo
This statement tells the SBasic compiler that subsequent references to
the label FOO must be passed to the output file as FOO, not as a
converted label.
ASMFUNC adds tremendous power to SB, allowing you to write your own
SBasic extensions in assembly language, then use them as if they were
an integral part of SB. For example:
declare stack ' declare a variable
asmfunc getstk ' define an asm entry point
main: ' enter here
stack = getstk(0) ' get addr of return stack
do loop ' silly loop
asm ' switch to assembly language
getstk ' entry point to getstk()
tsx ' move addr of stack to x
xgdx ' move addr of stack to d
rts ' return addr of stack
endasm ' back to SBasic
end
Here, the ASMFUNC statement tells SB that references to the label
GETSTK are to be passed unchanged to the output file. Thus, when the
SB code invokes the function GETSTK() to get the current hardware
stack address, SB generates a JSR to GETSTK, not a JSR to an address
with an internal SB label.
The actual code for subroutine GETSTK exists in the ASM block. GETSTK
moves the stack pointer into the 68hc11's D-register and returns. The
code generated by SB then stores the D-register into variable STACK
and falls into the silly loop at the end of the program.
This example shows how to set up an ASMFUNC statement and its
associated assembly language. It also shows how to use an ASMFUNC
label as a function. In this case, you must adhere to SBasic's
general rules regarding functions. Functions, which return a result
in the D-register, must be called with an argument. Since the GETSTK
routine doesn't need an argument, anything will work, but you must
include an argument of some kind. That's why I show an argument of 0
for GETSTK.
SBasic User's Manual SBasic Version 2.7 Page 56
Printed: December 5, 1999
You can also use ASMFUNC to create statements. Remember that an
SBasic statement doesn't return a value, but simply performs an
operation. For example:
asmfunc setstk ' define an asm entry point
main: ' enter here
setstk $0060 ' change hardware stack addr
do loop ' silly loop
asm ' switch to assembly language
setstk ' entry point to setstk
tsx ' get current stack addr
ldx 0,x ' get return addr for SB in x
jsr _pull ' get new stack addr in d
xgdx ' new stack in x
txs ' move new stack addr to s
xgdx ' put return addr back in x
jmp 0,x ' return through x
endasm ' back to SBasic
end
This example is more advanced and shows how to change the return stack
pointer from inside SBasic. The SB program uses the SETSTK statement
to set the return stack pointer to $60, essentially moving the
hardware stack. The tricky part here is that SB will execute this
statement via a JSR to SETSTK. If the assembly language code simply
moved the new stack pointer into the S-register and returned, the
program would crash since the return address would be undefined. The
code above changes the S-register, but saves the return address in the
X-register. It finally returns by jumping through the X-register.
Here you can see how SBasic processes the arguments to a statement.
The argument $0060 for the SETSTK statement is pushed onto SB's data
stack; it is NOT passed in the D-register. Thus, before the assembly
language code in the SETSTK routine can do anything with the argument,
it must first execute a JSR to the SB internal routine _PULL. _PULL
pulls the top element from SB's data stack and returns it in the D-
register. Refer to the GOSUB statement above for details on how
SBasic parses arguments to statements.
Note that you don't have to use a JSR to _PULL just to get an argument
into the D-register. Advanced programmers can directly access
arguments using offsets to the Y-register. Regardless of how you
access the arguments, however, your assembly language routine MUST
remove all arguments from the data stack before returning! If not,
repeated invocations of your routine will eventually crash the target
system.
This brings up another element of the ASMFUNC usage. SBasic does no
error checking to make sure your program uses an ASMFUNC label
consistently. Thus, you could use the same assembly language routine
SBasic User's Manual SBasic Version 2.7 Page 57
Printed: December 5, 1999
as both a function and a statement, if you so choose. What's more,
you could pass varying numbers of arguments to the same ASMFUNC label
as a statement. You are responsible for ensuring your assembly
language routine behaves properly in all cases. SBasic will blindly
load up the arguments and perform the JSR; your code has to deal
properly with any variations in argument count.
Note that the second example is not completely general, since it can
be called only from the top level (main) of your SB program. If, for
example, you tried to do a SETSTK from within an SB routine, that
routine would crash when it executed a RETURN statement, since its
return address had moved when the stack pointer changed.
When writing assembly language code invoked with ASMFUNC labels,
remember to preserve SB's registers. For the 68hc11 and 68hc12, the
Y-register holds the data stack pointer and the S-register holds the
return stack pointer. Additionally, any argument returned to the
calling routine is passed back in the D-register.
To summarize, you can use an ASMFUNC label as either a statement or a
function. If used as a statement, you can supply zero, one, or more
arguments; any arguments will be pushed left to right onto the data
stack before the JSR to your label is executed. If used as a
function, you MUST supply one and only one argument to the function.
This argument will be passed to the called routine in the D-register,
though your routine can ignore it. Upon returning from your routine,
the contents of the D-register will be used as the returned value of
the function.
Additionally, remember that ASMFUNC labels do not exist as SBasic
labels. You cannot do a GOSUB to an ASMFUNC label, since that label
only exists in the assembly language module.
Since the ASMFUNC statement only affects subsequent references, the
statement must appear in your source file before any references to the
target label appear. Thus, it's best if you put all of your ASMFUNC
statements near the beginning of your source file.
SBasic User's Manual SBasic Version 2.7 Page 58
Printed: December 5, 1999
ASMFUNC, _INKEY, and _OUTCH
Generally, you should not use ASMFUNC to define labels used internally
by SBasic run-time routines. Doing so will cause the assembler that
processes the resulting output file to report an error. This happens
because SBasic will create two identical labels in its output assembly
language file, one label that you defined in your ASM section and a
matching label in an SBasic library file.
The exceptions to this rule involve the labels _INKEY and _OUTCH.
SBasic reserves these labels for internal routines that handle
character I/O. By default, the _OUTCH routine sends the character in
the A-register to the 68hc11's SCI port, and the _INKEY routine
returns a character from the SCI in the D-register. SBasic
automatically appends a library file containing the assembly language
source for these routines whenever it processes a statement that
requires them.
In this one case, SBasic permits your source file to override the
normal inclusion of a library file. For example, if SBasic detects an
ASMFUNC statement defining the label _OUTCH:
asmfunc _outch
SBasic does not append the _OUTCH library file. Instead, SBasic
relies on whatever _OUTCH assembly language routine you define in your
SBasic source file to handle all character output.
This feature allows you to redirect the output from all SBasic PRINT
and OUTCH statements to an alternate device. Similarly, you can
redirect the input for the SBasic INKEY function from an alternate
device. This makes it easy to add SBasic support for formatted output
to devices such as LCDs, or character input from custom keyboards.
For example:
asmfunc _outch
asm
_outch
staa $1004 send char in A to port b
rts
endasm
main:
print "2 + 2 ="; 2+3
end
This sample program sends a mathematical statement to port B that
should convince anyone your computer is loony.
SBasic User's Manual SBasic Version 2.7 Page 59
Printed: December 5, 1999
Note that the labels _OUTCH and _INKEY must appear within an ASM-
ENDASM block. Also note that the argument to these ASMFUNC statements
include the assembly-language (underscored) version of the routine
name, NOT the normal SBasic representation.
SBasic User's Manual SBasic Version 2.7 Page 60
Printed: December 5, 1999
Character I/O on the 68hc11
The PRINT and OUTCH statements generate code for sending characters
and text to some type of host, using library routines. For the
68hc11, this defaults to routines that use the SCI.
Note, however, that SBasic does not actually set up the SCI for serial
transfers. Thus, it is not usually enough to simply PRINT a string;
your code must first (as a minimum) enable the SCI transmitter and set
the SCI baud rate.
This same requirement exists for the INKEY() function. Before your
code can successfully invoke the INKEY() function, it must first
enable the SCI receiver and set the SCI baud rate.
Fortunately, this is a simple task. Assuming the 68hc11 target system
uses an 8.0 MHz crystal, the following statements will set up the SCI
for 9600 baud and enable the SCI receiver and transmitter:
include "regs11.lib"
main:
pokeb baud, $30 ' 9600 baud
pokeb sccr2, $0c ' enable rcvr and xmtr
print "Hello, world!" ' now say something
If your 68hc11 target system uses a crystal frequency other than 8.0
MHz, consult the Motorola technical literature for the proper data
value to POKEB into the BAUD register.
SBasic User's Manual SBasic Version 2.7 Page 61
Printed: December 5, 1999
Table of Contents
Introduction 3
History of SBasic 4
Invocation 6
Command-line options 7
Environment variables 10
Library files 11
Features 12
Remarks 15
Include files 16
Labels 18
Numeric constants 19
Variables, arrays, and named constants 20
Data tables 22
COPY statement 24
Operators 25
Comparisons 27
Control structures 28
Functions and statements 35
Subroutines, GOSUB, and USR() 43
The RETURN statement 45
Interrupts 46
The ORG statement 49
The data stack 51
ASM and ENDASM 54
ASMFUNC 56
ASMFUNC, _INKEY, and _OUTCH 59
Character I/O on the 68hc11 61
Index
$ prefix 19
% prefix 19
/b option 7
/c option 7
/i option 9, 47
/m option 8
/s option 7, 51
/v option 7
68HC118, 11, 12, 22, 25, 39, 40, 41, 45, 47, 48, 51, 54, 58, 61
68HC12 8, 12, 25, 39, 40, 41, 45, 47, 48, 51, 58
Addresses
CODEBEG 7, 49
STKBEG 7
VARBEG 7, 20
Arrays 20, 38
ASCII constants 19
Binary numbers 19
Blank lines 15
BUFFALO 47
Comparisons 27, 28, 30
Data stack 43, 51, 52
ERRORLEVEL 6
Executing SBasic 6, 7
Hexadecimal numbers 19
Include files 16
Keywords
' (single-quote) 15
<= 27
27
= 27, 36
> 27
>* 27
> >= 27
ADDR 23, 38, 47
ASM 54, 56, 59
ASMFUNC 56, 59
CASE 32
CONST 21, 36, 37
COPY 24
DATA 22
DATAB 22
DECLARE 20, 43, 52
DO 27, 28, 29
DROP 53
ELSE 33
ELSEIF 33
END 47
ENDASM 54
ENDCASE 32
ENDSELECT 32
EXIT 32
FOR 30
GOSUB 43, 57
GOTO 12
IF 27, 33
INCLUDE 10, 16
INKEY 40, 42, 59, 61
INTERRUPT 39, 45, 46
INTERRUPTS 48
LROLL 35
LSHFT 35
MAIN 18
MAX 42
MAXU 42
MIN 42
MINU 42
NEXT 31
ORG 49
OUTCH 41, 59, 61
PEEK 23, 36
PEEKB 23, 36
PICK 44, 52
POKE 36
POKEB 37
POP 51
PRINT 39, 42, 59, 61
PRINTU 39
PRINTX 39
PULL 52
PUSH 51
REM 15
RETURN 43, 46, 51
RROLL 35
RSHFT 35
SELECT 31
STEP 31
SWAP 53
SWAPB 35, 53
TO* 30
USR 44
WAITUNTIL 37
WAITWHILE 37
WHILE 28
Labels 18, 23, 43, 47
Library files 9, 10, 11, 25, 40, 41
Operators
* 11, 25
+ 25
- 25
/ 25
= 25
~ 25
AND 25
MOD 25
OR 25
XOR 25
Parentheses 28, 33, 36, 37
SB_INCLUDE 10, 16
SB_LIBRARY 10
START11.LIB 11
START12.LIB 11
Subroutines 43, 45
Variables 20