Zur deutschen Version.
Copyright (C) 2019 Dr. Thomas Redelberger, redethogmx.de. All rights reserved.
The information here is provided as-is, with no warranty of any kind. The source code provided may be used freely in any project. The only condition being that my name and e-mail address must be quoted in a publicly available "credits" section of your project and/or product.
While AVRASM is quite a full featured macro assembler, some features are missing:
if ... then ...
else
or a switch
construct similar to the C-language
Furthermore there are often similar tasks that need to be done in most projects, like:
switch/case
constructs similar to the C-language
I have written a set of utility macros to cover those items. They are to be used in AVR assembly language projects. I have documented how to use advanced assembler features.
Quite often, you need to load a register pair with a 16‑bit immediate value, for example a RAM address, like
ldi zl, LOW(address) ldi zh, HIGH(address)
The macro ldiw
allows to shorten this to
ldiw z, address
Thus this looks like the processor had a ldiw
instruction, but of
course after macro expansion this becomes again the two processor
instructions shown above.
The state of RAM in an AVR device is not guaranteed to be zero after power-up let alone after a reset or after other restart conditions (e.g. "brown-out"). Hence you usually need to clear variables in RAM explicitly at program initialisation time.
MtrClrMem
clears 1 to 256 bytes to zero.
MtrClrMem CONSTANT
AVRASM2 calculates the used RAM based on the use of the .byte
assembler directive only. This is explicitly stated in the assembler
listing file. However, to allocate variables using macros ‑ which is
needed in object orientated programming ‑ I often use the .org
assembler directive.
Putting
MtrCheckDSEGspace
at the end of the code will check for the remaining RAM and throw an assembler error message if too much RAM has been allocated.
If ... Then ... Else
constructs are relatively easy to code in
assembly language. However Switch Case
constructs are difficult.
Hence I developed a set of assembler macros to provide Switch Case
functionality.
I wanted to have the same functionality available in assembler like in the C-language:
An example use case is handling the different status register values of the AVR TWI module.
There are different options to implement switch constructs. The fastest but most complex to generate are jump-tables. The most simple are comparisons plus branch instructions.
I opted for the latter, even though it is slower for complex cases.
This is example C-source-code for a switch with three cases and a default case (NA, NB, NC be symbolic constants, for example using the C-pre-processor).
switch variable { case NA: ...Statements A...; break: case NB: ...Statements B...; break; case NC: ...Statements C...; break; default: ...default case Statements...; }
A naive implementation in assembler could look like:
cmp r24, NA breq A cmp r24, NB breq B cmp r24, NC breq C ...default case Statements... rjmp ends A: ...Statements A... rjmp ends B: ...Statements B... rjmp ends C: ...Statements C... ; rjmp ends ends:
This is simple and easy to understand. But it only works like this if
the code fragments can be reached by the breq
instructions i.e. they
are a maximum 64 bytes away.
When the code is longer, you could instead write
cmp r24, NA brne PC+2 rjmp A
But the code gets slower.
To avoid the issue you could write like
cmp r24, NA brne tB ...Statements A... rjmp ends tB: cmp r24, NB brne tC ...Statements B... rjmp ends tC: cmp r24, NC brne default ...Statements C... rjmp ends default: ...default case Statements... ends:
But this is harder to read and understand, especially when the code blocks get longer. The main reason is that the labels do not any more stand for the case code blocks but for the compare instructions.
And this gets worse when you have multiple switch constructs in the same procedure or even nested switch statements.
My main objective for the macro solution was to keep the code readable. For above example it looks like:
MtrSwitch Ident cpi r24, NA MtrCaseEq Ident ...Statements A... MtrCaseEqEndBrk Ident cpi r24, NB MtrCaseEq Ident ...Statements B... MtrCaseEqEndBrk Ident cpi r24, NC MtrCaseEq Ident ...Statements C... MtrCaseEqEndBrk Ident ...default case Statements... MtrSwitchEnd Ident
There are no more labels to clutter the source code. Instead the labels are generated automatically by the macros.
There has to be a unique identifier for the whole switch construct. This is the literal "Ident" in above example. Using such identifiers allows to nest the switch constructs. The identifier has to be unique in the complete source code.