With the use of a customized 6811 assembler program, IC allows the use of machine language programs within the C environment. There are two ways that machine language programs may be incorporated:
When operating as a function, the interface between C and a binary program is limited: a binary program must be given one integer as an argument, and will return an integer as its return value. However, programs in a binary file can declare any number of global integer variables in the C environment. Also, the binary program can use its argument as a pointer to a C data structure.
Special keywords in the source assembly language file (or module) are used to establish the following features of the binary program:
To explain how these features work, let's look at a sample IC binary source program:
/* Sample icb file */ /* origin for module and variables */ ORG MAIN_START /* program to return twice the argument passed to us */ subroutine_double: ASLD RTS /* declaration for the variable "foo" */ variable_foo: FDB 55 /* program to set the C variable "foo" */ subroutine_set_foo: STD variable_foo RTS /* program to retrieve the variable "foo" */ subroutine_get_foo: LDD variable_foo RTS /* code that runs on reset conditions */ subroutine_initialize_module: LDD #69 STD variable_foo RTS
The first statement of the file ("ORG MAIN_START
") declares
the start of the binary programs. This line must precede the code
itself itself.
The entry point for a program to be called from C is declared with a special
form beginning with the text subroutine_
. In this case, the
name of the binary program is double
, so the label is named
subroutine_double
. As the comment indicates, this is a program
that will double the value of the argument passed to it.
When the binary program is called from C, it is passed one integer argument. This argument is placed in the 6811's D register (also known as the "Double Accumulator") before the binary code is called.
The double
program doubles the number in the D register. The
ASLD
instruction ( "Arithmetic Shift Left Double
[Accumulator]") is equivalent to multiplying by 2; hence this doubles
the number in the D register.
The RTS
instruction is "Return from Subroutine." All binary
programs must exit using this instruction. When a binary program exits,
the value in the D register is the return value to C. Thus,
the double
program doubles its C argument and returns it to C.
The label variable_foo
is an example of a special form to
declare the name and location of a variable accessable from C. The
special label prefix "variable_
" is followed the name of the
variable, in this case, "foo
."
This label must be immediately followed by the statement FDB
<number>
. This is an assembler directive that creates a two-byte
value (which is the initial value of the variable).
Variables used by binary programs must be declared in the binary file. These variables then become C globals when the binary file is loaded into C.
The next binary program in the file is named "set_foo
." It
performs the action of setting the value of the variable foo
, which
is defined later in the file. It does this by storing the D register into
the memory contents reserved for foo
, and then returning.
The next binary program is named "get_foo
." It loads the D
register from the memory reserved for foo
and then returns.
The label subroutine_initialize_module
is a special form used
to indicate the entry point for code that should be run to initialize the
binary programs. This code is run upon standard reset conditions: program
download, hardware reset, or running of the main()
function.
In the example shown, the initialization code stores the value 69 into the
location reserved for the variable foo
. This then overwrites the 55
which would otherwise be the default value for that variable.
Initialization of globals variables defined in an binary module is
done differently than globals defined in C. In a binary module,
the globals are initialized to the value declared by the FDB
statement only when the code is downloaded to the 6811 board (not upon
reset or running of main, like normal globals).
However, the initialization routine is run upon standard reset conditions, and can be used to initialize globals, as this example has illustrated.
Interrupt-driven binary programs use the initialization sequence of the binary module to install a piece of code into the interrupt structure of the 6811.
The 6811 has a number of different interrupts, mostly dealing with its on-chip hardware such as timers and counters. One of these interrupts is used by the 6.270 software to implement time-keeping and other periodic functions (such as LCD screen management). This interrupt, dubbed the "System Interrupt," runs at 1000 Hertz.
Instead of using another 6811 interrupt to run user binary programs, additional programs (that need to run at 1000 Hz. or less) may install themselves into the System Interrupt. User programs would be then become part of the 1000 Hz interrupt sequence.
This is accomplished by having the user program "intercept" the original 6811 interrupt vector that points to 6.270 interrupt code. This vector is made to point at the user program. When user program finishes, it jumps to the start of the 6.270 interrupt code.
The top picture depicts the interrupt structure before user program installation. The 6811 vector location points to system software code, which terminates in a ``return from interrupt'' instruction.
The bottom picture illustrates the result after the user program is installed. The 6811 vector points to the user program, which exits by jumping to the system software driver. This driver exits as before, with the RTI instruction.
Multiple user programs could be installed in this fashion. Each one would install itself ahead of the previous one. Some standard 6.270 library functions, such as the shaft encoder software, is implemented in this fashion.
* icb file: "sysibeep.asm" * * example of code installing itself into * SystemInt 1000 Hz interrupt * * Fred Martin * Thu Oct 10 21:12:13 1991 * #include <6811regs.asm> ORG MAIN_START subroutine_initialize_module: #include <ldxibase.asm> * X now has base pointer to interrupt vectors ($FF00 or $BF00) * get current vector; poke such that when we finish, we go there LDD TOC4INT,X ; SystemInt on TOC4 STD interrupt_code_exit+1 * install ourself as new vector LDD #interrupt_code_start STD TOC4INT,X RTS * interrupt program begins here interrupt_code_start: * frob the beeper every time called LDAA PORTA EORA #%00001000 ; beeper bit STAA PORTA interrupt_code_exit: JMP $0000 ; this value poked in by init routine
The above program installs itself into the System Interrupt. This program toggles the signal line controlling the piezo beeper every time it is run; since the System Interrupt runs at 1000 Hz., this program will create a continous tone of 500 Hz.
The first line after the comment header includes a file named "
6811regs.asm
". This file contains equates for all 6811 registers and
interrupt vectors; most binary programs will need at least a few of
these. It is simplest to keep them all in one file that can be
easily included. (This and other files included by the as11
assembler are located in the assembler's default library directory,
which is /mit/6.270/lib/as11/
on the MIT Athena system.)
The subroutine_initialize_module
declaration begins the
initialization portion of the program. The file "
ldxibase.asm
" is then included. This file contains a few lines of
6811 assembler code that perform the function of determining the base
pointer to the 6811 interrupt vector area, and loading this pointer
into the 6811 X register.
The following four lines of code install the interrupt program
(beginning with the label interrupt_code_start
) according
to the method described above.
First, the existing interrupt pointer is fetched. As indicated by the comment, the 6811's TOC4 timer is used to implement the System Interrupt. The vector is poked into the JMP instruction that will conclude the interrupt code itself.
Next, the 6811 interrupt pointer is replaced with a pointer to the new code. These two steps complete the initialization sequence.
The actual interrupt code is quite short. It toggles bit 3 of the 6811's PORTA register. The PORTA register controls the eight pins of Port A that connect to external hardware; bit 3 is connected to the piezo beeper.
The interrupt code exits with a jump instruction. The argument for this jump is poked in by the initialization program.
The method allows any number of programs located in separate files to attach themselves to the System Interrupt. Because these files can be loaded from the C environment, this system affords maximal flexibility to the user, with small overhead in terms of code efficiency.
The source file for a binary program must be named with the .asm
suffix. Once the .asm
file is created, a special version of the
6811 assembler program is used to construct the binary object code.
This program creates a file containing the assembled machine code plus
label definitions of entry points and C variables.
S116802005390037FD802239FC802239CC0045FD8022393C S9030000FC S116872B05390037FD872D39FC872D39CC0045FD872D39F4 S9030000FC 6811 assembler version 2.1 10-Aug-91 please send bugs to Randy Sargent (rsargent@athena.mit.edu) original program by Motorola. subroutine_double 872b *0007 subroutine_get_foo 8733 *0021 subroutine_initialize_module 8737 *0026 subroutine_set_foo 872f *0016 variable_foo 872d *0012 0017 0022 0028
The program as11_ic
is used to assemble the source code and
create a binary object file. It is given the filename of the source
file as an argument. The resulting object file is automatically given
the suffix .icb
(for IC Binary). The binary object file that is
created from the testicb.asm
example file is shown above.
Once the .icb
file is created, it can be loaded into
IC just like any other C file. If there are C functions that are to be
used in conjunction with the binary programs, it is customary to put them
into a file with the same name as the .icb
file, and then use
a .lis
file to loads the two files together.
A pointer to an array is a 16-bit integer address. To coerce an array pointer to an integer, use the following form:
array_ptr= (int) array;
where array_ptr
is an integer and array
is an
array.
When compiling code that performs this type of pointer conversion, IC must be used in a special mode. Normally, IC does not allow certain types of pointer manipulation that may crash the system. To compile this type of code, use the following invokation:
ic -wizard
Arrays are internally represented with a two-byte length value followed by the array contents.