The AVR assembler in the IRTC will allow you to do most that a conventional assembler will, and something's that it may not. It is a single pass interpreted assembler with conditional statements, such as
IF...ELSE...THEN
BEGIN...UNTIL
BEGIN...WHILE...REPEAT
BEGIN...AGAIN etc.
The syntax of the assembler is not that of Forth, Reverse Polish or Postfix notation but standard Prefix. This means that the source looks like standard assembly but with space delimiters. e.g.
LDI R26,0x01 becomes
LDI TOSL , $01
As all assemblers this checks the validity of the addressing mode for the instruction, and the size of the data. This is accomplished by flags that are set to indicate the various addressing modes.
Note: The only forward referencing available in the assembler is with the structures above and the skip instructions of the AVR
The conditionals require a flag test, CS, 0=, 0<, 0>, >=, HS, TS, OV or IE before their use. Each may be followed by a NOT to produce the inverse of the test. e.g.
0= IF - branch if non zero.
0= NOT IF - branch if zero.
The AVR register use is described in the TLM-Forth section. Here is a résumé of that usage:
YReg - Data Stack Pointer
XReg - Top of the Data Stack
SP - Return Stack Pointer
R22,23 - UP, Task Area
R16-21 - N, ScratchFile Use
Because of the way Forth and the Assembler work it is possible to define Macros for use in the assembly process. If we make a Host definition which contains assembler words, when interpreted at compile time, the definition will execute these words. This will place the assembler code directly into the Target. An example of this technique is:
XASM DEFINITIONS
MACRO: PUSHT ST Y+ , TOSL
ST Y+ , TOSH
MACRO;
IN-META
Using this technique it is possible to construct repetitive code for insertion in your machine code programs
Note: You may also produce the same result with an H: definition and M[ .. ]M e.g.
H: PUSHT M[ ST Y+ , TOSL ST Y+ , TOSH ]M ;
Four other macros are pre-compiled to assist with stack control.
PUSHT places the TOS contents onto the data stack with the following code;
ST -Y , TOSL
ST -Y , TOSH ;
POPT gets the data stack into TOS whith the following;
LD TOSH , Y+
LD TOSL , Y+ ;
PUSHS places the SEC contents onto the data stack with the following code;
ST -Y , SECL
ST -Y , SECH ;
POPS gets the data stack into TOS whith the following;
LD SECH , Y+
LD SECL , Y+ ;
If you wish to manipulate the stack you should do so with these macros. If you write in-line code and use these macros the optimiser will try to elliminate unnecessary stack use.
The AVR has many and varied addressing modes. By no means all are used in the TLM, but many are. To understand their use it is strongly recommended that you read the ATMEL data book for a full explanation. Here we will consider the addressing mnemonic used in the IRTC assembler.
The same syntax that appears in the ATMEL data book has been used for register identification. A upper case 'R' for a register, 0-31, and a upper case 'P' for a I/O register, 0-63. Register pairs are indicated by 'RR' and only apply to R24, 26, 28 and 30. For convenience and improved readability the registers, register pairs and I/O registers are pre-defined, R4, X, Z+, -Y, Z+#, BIT0 and SREG etc. These may all be constructed but with Forth space delimited format e.g.
4 R 26 RR 0 BIT 63 P
The pre-defined versions are much more readable. Also all the I/O registers and the flags within them are predefined by name e.g.
SIB UCR , RXEN
Will enable the UART reciever.
Note: When using the I/O Registers in a high level definition they must be preceeded by I/O. e.g.
$51 I/O PORTB C!
Note: When using the Register flags in a high level definition they must be preceeded by FLAG to generate a mask e.g.
FLAG CS00 I/O TCCR0 ON
To differentiate between destination and source operands a space delimited comma is used e.g.
ADIW TOS , 2
BEWARE: of the use of a Forth , while in the assembler as very odd results will occur.
The operand value used by the instruction is the value supplied by the operand field itself. The hashmark (#), optional, is used to distinguish data from an absolute address and may only be in the source field.
Examples:
LDI R26 , # 04
Adds 4 to the value originally contained in register R26.
SBIW TOS , # 3
Subtracts 3 from the top stack item.
In this mode a register is be addressed by using its absolute address in the register file.
Example:
ROL R4
Rotates the contents of register number 4 left one bit.
In this mode the address of the data does not appear in the instruction, but is located in a register pair, X, Y or Z.
Example:
LD TOSL , X
CLR TOSH
This is equivalent to a C@ in Forth, X being the top stack item holds the address of the data byte.
The indirect working register acts as a base or starting value to which is added an immediate offset, 0-63, to point to the data. The offset value is the immediate value given in the instruction while the index value is given by the contents of a register pair.
Example:
LDD R10 , Y+# 5
If Y contains 55 then the contents of 60 (55+5) will be loaded into register 10.
Here the destination or source addresses are given by the contents of a register pair, X, Y or Z, which are then post-incremented.
Example:
LD TOSH , Y+
LD TOSL , Y+
In IRTC Y contains the Data Stack pointer, DP. The above example is equivalent to a DROP.
This is similar to Data Indirect with Post Increment, except that the indirect register pair is decremented BEFORE the data is accessed.
Example:
LD R7 , -Z
In the Direct Bit addressing mode, any bit in any register or I/O can be addressed and potentially modified.
Examples:
SBR R7 , BIT3
This instruction sets bit 3 in working register 7.
CBI PORTC , BIT4
This instruction clears bit4 in I/O portc.
This mode addresses the specific location within the data memory directly. It only needs the absolute address value.
Example:
STS COUNTER , R9
The data memory location COUNTER is loaded with the contents of the register 9.
LDS R9 , COUNTER
The register R9 is loaded with the contents of the data memory location COUNTER.
This mode uses the Z register pair to hold the absolute code memory address.
Example:
LPM
The code memory addressed by Z is loaded into R0 only.
Note: The address is the byte address, the LSB specifies the high or low byte.
The program execution continues at the address contained in the instruction.
Example:
JMP ' PAUSE
Jumps directly to PAUSE. The ' is used in Forth to find the CFA of the following word. If the word was created by a LABEL the ' is not required.
CALL ' STOP
Calls the subroutine STOP.
The program execution is continued at the address contained in Z.
Examples:
IJMP
Jumps to the address in Z.
ICALL
Calls the address in Z.
The offset, -2048 to 2047, is held in the instruction causing the execution to continue from the Program Counter plus the signed offset.
Example:
RJMP ' TEST
Jumps to TEST relative from the location of the instruction.
RCALL ' TEST
Calls TEST relative
The AVR instruction set has four skip instructions for bit testing in the register and I/O space. New opcodes have been created to make use of these to reduce code and increase speed in conditional assembly. The new opcodes are;
IF-BRS - Skips one instruction if bit in register set
IF-BRC - Skips one instruction if bit in register clear
IF-BIS - Skips one instruction if bit in I/O set
IF-BIC - Skips one instruction if bit in I/O clear
SBAK - Skips back one instruction
The IF- instructions are combined with ELSE and THEN to create conditional assembly. They may also be used instead of WHILE in a BEGIN...WHILE...REPEAT structure. The SBAK may be used to create a fast loop waiting for a bit to set or clear. See (OUT) in the AVRPROG3 file.
To define a Forth code word the assembler must be invoked to allow the instruction words to be found. Three words will do this; CODE, LABEL and INTERRUPT.
CODE creates a Header for the word following and causes the assembled machine code to be run when the word is executed. This is just like a Forth : definition only in code.
LABEL creates a Header for the following word, that only leaves the address of the assembled machine code on the Host stack when the word is executed. This is often used as a reference for jumps or branches within a CODE definition.
All assembler definitions must end with END-CODE or C;. These words terminate the assembler and test the Host stack for irregularities. CODE definitions will usually end with RET.
As AVR Forth is subroutine threaded and produces machine code, it is possible to put code fragments into a high level definition. The code is encompassed with C[ and ]C, for example;
: TEST C[ ADIW TOS , 4 ]C ;
This adds 4 to the item on top of the stack.
If you are only using assembler code you may still use TLM to test your routines.
TLM expects the CFA it receives to be that of a subroutine. If you have used CODE definitions for your routines these may be executed by name from the command line. If they are defined by LABELs then a stub is required e.g.
CODE TST JMP ' <name> C;
This assumes <name> is a routine ending in a return opcode and that it does not corrupt the TLM file space or stack.
To test parts of your code it may be necessary to insert return opcodes into the code at strategic points and then create stubs to test that section.
Interrupts in the AVR are done via vectors in the first 48 bytes of the program memory space. To enable these vectors to be changed during development the TLM has 96 locations programmed to jump indirectly to locations at the start of the development code RAM. These RAM locations may then be programmed with an absolute jump address to the interrupt routine. This adds about 3.5uSecs to the interrupt latency at 6MHz. The Forth word VECTOR! takes a vector number, 1-24, and the interrupt address to automatically create the jump in the RAM location.
To return from a RAM vectored interrupt you should use the VEC-RETURN function that pops the N, ZL, ZH, and SREG that were saved as part of the (VECTOR) execution.
Note: This is only possible in the larger AVR devices with JMP instructions, that allocate 4 bytes for the interrupt vectors from $0000. In the smaller devices it is necessary to change the interrupt vector and REPROGRAM.
The word INTERRUPT may be used to define an interrupt routine instead of CODE or LABEL. The associated interrupt vector address must preceed INTERRUPT and it will create a JMP or RJMP to the routine in the code vectors. This will only take effect when the whole image is REPROGRAMed. The function VEC-SAVE may be used to push N, ZL, ZH, and SREG registers so that the VEC-RETURN may pop correctly.
Use: TIM0-OVF INTERRUPT <name>
VEC-SAVE
....... interrupt code
JMP VEC-RETURN C;
or END-INTERRUPT
During development the above interrupt would be coded as;
CODE <name>
\ TIM0-OVF INTERRUPT <name>
\ VEC-SAVE
....... interrupt code
JMP VEC-RETURN C;
or END-INTERRUPT
: SET-TIM0-OVF-INT [ XASM 17 ' <name> ] LITERAL LITERAL VECTOR! ;
The SET-TIM0-OVF-INT is run during the initialisation to set the RAM vector. The interrupt may then be tested and when operational the CODE <name> replaced by the TIM0-OVF INTERRUPT <name> and VEC-SAVE . If your interrupt code may be coded with only the use of the N, ZL, ZH, and SREG registers you do not need to save any other resourses.
To assist with code definitions in particular, memory dump utilities DUMP, RDUMP, IDUMP, FDUMP and EEDUMP are available. DUMP, FDUMP and EEDUMP require a start address and the number of bytes to dump.
DUMP is for the code memory e.g.
$0000 50 DUMP
The register file space may also be dumped by using REG-DUMP.
This shows the contents of all the file registers from R0 to R31.
The I/O file space may also be dumped by using IO-DUMP.
This shows the contents of all the I/O registers from 0 to 63.
The Data memory may be dumped by FDUMP. As the registers and I/O are also in the Data Memory you may use this to do part of the dumps shown by REG-DUMP and IO-DUMP.
The data in the E2PROM may be dumped using EEDUMP.