Unfortunately the AVR IDE do not support libraries and the macro capabilities of the assembler in AVR Studio are very limited. So instead of building a real library I just wrote a collection of files I could include in the projects as I needed then. You need this include directory for most of my AVR projects.
Here you can download the Include Library
My includes started with the monitor. During the development of AVR projects I often thought, that it would be cool to have some sort of monitor program I could call and have an interactive console to look into the processor or to have a command line so I could call some sub‐routines to test things and gain experience with the AVR processors.
So I have written a small monitor program that is pretty much a copy of the Apple II monitor program as it was included in the original Apple II, of course there are some differences. E.g. there is no G (Go) command.
You need to include three modules. The first include defines all the necessary labels for the character table. Like the Apple II monitor each character is equivalent to a command.
This moves memory block from
0x1000. In other words it just
moves RAM. Other commands are inspect
<CR>, verify, deposit, add, subtract etc.
For convenience I have added my own standard commands P (pattern) and x (hexdump).
After this you can define further command characters. In case you want a control
sequence to act as a command character you just need to
and it with
Of course you cannot use the characters A-F as they are used as hex digits.
After your own command characters you must include the second module this then defines the length of the character table. Followed by the addresses of the action routines of your own commands. Make sure the amount and order are in sync with the command character table.
.include "../include/monitor-chartbl-v1-3.asm" .db 0, '?' .db 0, 'O' ; Toggle Motor On .db 0, 'S' ; Toggle Drive Select .db 0, 'H' ; Toggle Head Select .db 0, 'T' ; Seek .db 0, 'R' ; Read Track .db 0, 'K' ; Analyze Track and show info .db 0, 'W' ; Generate Write Pattern .db 0, 'Q' ; Read Track using CPLD .db 0, 'I' ; .db 0, 'I' & 0x1F ; .db 0, 'U' ; Write using CPLD .db 0, 'J' ; Print IDs .db 0, 'N' ; Make CRC Table .db 0, 'Y' ; CRC calculate aaaa.bbbby .db 0, 'Z' ; CRC calculate optimized ; ; AVR Monitor, inlcude the action routine address table of the default ; commands followed by the addresses of the application specific ; command routines. Both tables must be in sync of course... ; .include "../include/monitor-subtbl-v1-3.asm" .dw helpscreen .dw togglemot .dw toggleds .dw togglehs .dw seek .dw readtrack .dw analyzetrack .dw writepattern .dw quickread .dw indexx .dw initmfm .dw writecpld .dw printids .dw crcmaketable .dw crcupdate .dw crcupdate2 .include "../include/monitor-v1-6.asm"
Interface to Main Program
In order to interface with the monitor you need to define a serial input and output
routine. They are called
serout. The routines are expected to
preserve all registers. It’s up to you to initialise any IO device that will
source and accept the input and output character. Mostly this will be one of the
UARTs of the AVR processors.
|serin||Serial Input routine, the monitor expects a new characeter in register `char`|
|serout||Serial Output routine, the monitor puts the characeter in register `char`|
To enter the monitor just jump to the label
mon. This will first issue a prompt
and then ask for input terminated by a
|mon||The monitor defines this label. If your main program just wants to call the monitor just jmp to this label. The monitor will never exit unless you build your own exit coammnd and routine|
In case you want to call the monitor from your own interactive interface you can
do so by calling `fakemon`. This entry assumes that the Y register points to a
buffer with the monitor commands terminated by a `
Normally all addresses are treated as memory address. That is the monitor just uses the `lds` and `sts` instructions to load and store the appropriate value. This also gives access to the registers and the IO addresses. See the respective documentation of the microcontroller you are using. In case you want ot access different things (e.g. flash memory). you can write your own load and store routines. Just use the following labels.
|loaduser||Takes precedence over the monitor load routine.|
|storeuser||Takes precedence over the monitor store routine.|
You need to define two registers
temp which support immediate
constants, i.e. registers r16 to r25.
|char||Holds the character for serial input or output|
|zero||This register must hold the constant 0x00|
The following registers are destroyed and must be saved by the caller.
xl, xh, yl, yh, zl, zh, r0
When writing your own action routines you can access the parameters you have previously entered.
There are several RAM locations defined by the monitor which are filled with the parameters. For each parameter there are 3 bytes, the lower 8 bits, the upper 8 bits and the bank. The bank address is entered and used as in the Apple IIgs monitor. On a AVR with only 16-bit addresses this is normally not required, but in the case I wanted to test the RLV12 Emulators DMA I required at least 22-bit addresses.
a1l, a1h, a1b
a3l, a3h, a3b
a4l, a4h, a4b
malloc and free
For a larger project I needed to dynamically allocate and dispose memory blocks.
So I wrote my version of
In my programs I often create debug or log messages. For a long time they all
were customized routines. But then I decided to write a generalized
sub routine. It is not as sophisticated as
sprinft() and has a very special
encoding of dynamic values. Directly following the call to the print routine
you add the text string with the format codes
call print .db "Text", 0x81, 0x80, 0x0d, 0x0a, 0x00
Format codes are defined as byte values with the MSB set. The upper nibble defines the format and the lower nibble is the index into a 16-byte storage area
.dseg pprint: .byte 16
The format codes are
|0x8n||Convert the byte at offset to two hex digit|
|0x9n||Copy the character at offset|
|0xAn||Convert the 16-bit integer at the offset to octal ASCII with leading zeroes|
|0xBn||Copy the 24-bit integer at the offset to octal ASCII with leading zeroes|
|0xCn||Conver the 16-bit integer at the offset to decimal ASCII with leading blanks|
|0xDn||Convert the 32-bit integer at the offset to decimal ASCII with leading blanks and thousands delimiter|
The print routine directly calls
serout to send the characters to the UART with
the user interface
There are macros I use in almost every AVR project.
Starting with the RLV12 emulator I was looking for a decent user command line interface. Inspired by $TPARSE of the PDP-11 MACRO library I have written my own table driven parser.
SD Card Routines
My attempt to write low-level access routines for SD-Cards. It says v2-0 but it still is work in progress.
Over the time I standardised how I’m doing certain things in AVR assembler projects, mostly for may own sake.
Whenver possible I work with register definitions. Whenever possible I avoid to
use the register names directly. With some exception of
my standard 32-bit integer which is using
.def miss = r8 ; Missed Interval Counter .def ff = r9 ; Constant 0xFF .def one = r10 ; Constant 0x01 .def zero = r11 ; Constant 0x00 .def crcl = r12 ; Used to calculate CRC .def crch = r13 ; " .def crc3 = r14 .def crc4 = r15 .def temp = r16 ; Temporay register with immediate always destroyed! .def char = r17 ; The character or byte .def count = r18 ; General purpose counter ;
0x00, 0x01 and 0xFF are very often used as constants throughout my programs. In order to avoid using a temporary register and also to speed things I almost always define registers that are preset to one of these values. As the lower 16 registers of an AVR CPU cannot be used with immediate values they often are not used that often so you always have some spare register that as well can hold a constant themselves. Even the C-compilers use this.
|zero||This is a register definition and is set to 0x00 during program initialisation, often used to initialise data or to set the direction register of an 8-bit port especially when I need a bidirectional port without the external memory interface|
|one||This is a register definition and is set to 0x01 during program initialisation, a value I use surprisingly often|
|ff||This is a register definition and is set to 0xFF during program initialisation, often used to set the direction of an 8-bit port|
General Purpose Registers
The following registers hold certain values that are typically required in all sections and routines.
|char||Holds the character for serial input or output, but later I use it as the register that holds an input or output byte. For example when I need to transfer a block of data or when I'm reading or writing data to a external device|
|temp||Temporary Register. You always need a temporary register that is never guaranteed to be preserved and can always be used as temporary storage.|
|count||There are so many places you need a loop counter so I decided to have one register being dedicated to this type of requirement|
Reserved Interrupt Registers
Instead of saving all registers and the processor status on the stack I often
dedicate a few register to interrupt service routines. As the AVR per default
has only one interrupt level there is no conflict with multiple interrupts.
Typically I have a
isreg and a
itemp register used to save the status
SREGand the temporary register at interrupt level.
Usage in Projects
I collect all my AVR assembler projects in a AVR projects folder. Typically I create
a folder for every AVR assembler project. The include files are all located in
include folder located in the AVR projects folder.
/Dokumente/AVR-Projekte/ |-- AVRIML |-- CF-Card-8-Bit |-- CONSOLE11V2 |-- DMATest |-- MFMEmulator |-- MFMReader |-- MFV11 |-- MFV11B |-- MFV11v1 |-- MXV11 |-- Qbridge11 |-- Qbridge11-v2 |-- Romulus1st |-- Romulus2nd |-- include |-- malloc `-- vgacontrollerIV