NOCC AVR Assembler
The AVR assembler in NOCC provides a fairly straightforward way of generating code for Atmel's (http://www.atmel.com/) AVR 8-bit microcontroller devices. The assembler syntax mostly follows the style of that presented in the AVR documentation. Some rudimentary high-level things are available, such as macros, functions and some conveniences for dealing with 16-bit quantities. I'm using this to program Arduino boards at the moment, so mainly ATmega328s (Arduino Uno) and ATmega1280s (Arduino Mega). The current compiler framework (r4436+) generates two Intel hex-style image files as output: one for the flash, the other for the EEPROM.
Contents
Language lexicon
The source language is intended to be straightforward, and moderately easy to program. Register names are expressed as r0 through r31 (or as the X,Y,Z combo registers where appropriate); constant values are written as-is; some built-in expression evaluation to make programming a little more palatable.
Assembler directives
These are used to do just about everything that isn't an instruction or label.
Directive |
Arguments |
Description |
Example |
.const |
expr [ , expr [ ... ] ] |
Constant data (in text or eeprom sections), given as a comma-separated list of 8-bit values). |
.const 5, "hello", 0x00 |
.const16 |
expr [ , expr [ ... ] ] |
Similar to .const, but writes in 16-bit quantities rather than 8-bit ones. |
.const16 0xbeef, 0xf00d |
.data |
|
Indicates that what follows should go into the data (SRAM) section. Note that no code or constant data can be placed here (not building-in SRAM initialisation, yet). |
.data |
.def |
name = reg-expr |
Defines (specifically) a register name, that will undergo literal substitution (for reg-expr). |
.def XH = r27 |
.endmacro |
|
End of macro definition. |
.endmacro |
.equ |
name = expr |
Defines an assembler program constant, that will undergo literal substitution for the given expression expr. |
.equ RAMSTART = 0x100 |
.eeprom |
|
Indicates that what follows should go into the eeprom (EEPROM) section. |
.eeprom |
.include |
string |
Includes another file at the indicated point. The compiler's IPATHs will be searched for this file by default. |
.include "atmega328.inc" |
.macro |
name [ ( param-name [ , ... ] ) ] |
Start of macro definition. |
.macro BitSet (Port, Bit) |
.mcu |
string |
Sets the MCU type (string), case insensitive match against a list of MCUs the compiler knows about. |
.mcu "atmega328" |
.org |
uint16 |
Specifies the origin (address) for what follows. |
.org 0 |
.space |
uint16 |
Reserves the specified amount of space (in bytes). In the text section, this will be padded to 16-bits. |
.space 2 |
.text |
|
Indicates that what follows should go into the text (code) section. |
.text |
Values (constants)
The AVR assembler's lexer understands various constant number bases:
hexadecimal |
0x100 |
decimal |
42 |
binary |
01100101b |
8-bit ASCII character |
'z' |
Characters can be escaped in the usual C-style (newline, etc.). When specifying constant data (.const), literal strings are permitted, equivalent to a list of individual characters. Such string constants are not null-terminated by default. References to labels (that are not the target of a branch of call instruction) are also treated as constant values.
Expressions
The assembler/compiler understands a simple C-style expression syntax. In the majority of cases, such expressions must be constant (i.e. the compiler can work out what the value is). Parsing is done strictly left-to-right, so unbracketed expressions will be parsed as-is (no operator precedence). The inclusion of expressions in the language is largely to make configuration constants readable, e.g.:
1 .equ SPCR_MSTR_BIT = 4
2 .equ SPCR_DIV_4 = 0x00
3 .equ SPCR_DIV_SHIFT = 0
4
5 ; stuff
6
7 ldi r16, (1 << SPCR_MSTR_BIT) | (SPCR_DIV_4 << SPCR_DIV_SHIFT)
8 out SPCR, r16
The supported expressions are:
+ , - , * , / , % |
integer operators |
<< , >> |
bitwise shifts |
& , | , ^ |
bitwise and, or and exclusive-or |
~ |
bitwise not |
hi(...) |
hi-byte of the given value |
lo(...) |
lo-byte of the given value |
Labels
Labels are introduced by writing the name followed by a colon, and are referred to just by name, e.g.:
1 the_label:
2 ldi r16, lo(the_label) ; load (flash memory) address of the label
3 ldi r17, hi(the_label)
4
5 rjmp the_label ; jump to the label
Labels defined in this way have global scope, and must be unique. Numbered local labels (such as those supported by the GNU assembler) can be defined with .LN:, with no upper-limit on N. Such labels are referred to by the relative position in the source file, forwards (2f) or backwards (1b). E.g.:
1 .L0:
2 ; some code
3 cp r3, r8
4 brlt 1f ; branch if (r3 < r8)
5 rjmp 0b ; loop
6 .L1:
7 ; some more code
Macros
Macros work in the same way as pre-processor macros in C/C++, i.e. undergo literal substitution before any sort of semantic processing. Actual parameters must be valid expressions, however. For example:
1 .macro ShortWait
2 nop
3 nop
4 nop
5 nop
6 .endmacro
7
8 .macro BitSet (Port, Bit)
9 sbi Port, Bit
10 .endmacro
11
12 ; ...
13
14 reset:
15 ShortWait
16 BitSet (PORTD, 5)
17
Quick start
Assuming you have an Arduino Uno (ATmega328p-based board), NOCC successfully compiled and a tool such AVRDUDE for talking to the board, write a simple blinkenlight program (or download test_avr25.asm):
1 .mcu "atmega328"
2
3 .include "atmega328.inc"
4
5 .text
6 .include "atmega328-imap.inc"
7
8
9 VEC_reset:
10 ; setup stack-pointer
11 ldi r16, hi(RAMEND)
12 out SPH, r16
13 ldi r16, lo(RAMEND)
14 out SPL, r16
15 sei
16
17 sbi DDRB, 5 ; PB5 output
18 cbi PORTB, 5 ; PB5 low (LED off)
19
20 loop:
21
22 ldi r16, 0x10
23 .L0:
24 ldi r17, 0xff
25 .L1:
26 ldi r18, 0xff
27 .L2:
28 nop
29 nop
30 nop
31 nop
32 dec r18
33 brne 2b ; inner loop
34 dec r17
35 brne 1b ; middle loop
36 dec r16
37 brne 0b ; outer loop
38
39 sbic PORTB, 5 ; skip if PB5 low
40 rjmp 3f
41 ; if here, means LED was off, so turn on
42 sbi PORTB, 5
43 rjmp loop
44 .L3:
45 ; if here, means LED was on, so turn off
46 cbi PORTB, 5
47 rjmp loop
Compile this into binary images with something along the lines of:
/path/to/nocc -c test_avr25.asm
If everything worked okay (you may see some debugging output from NOCC) there should be two new files, test_avr25.flash.hex and test_avr25.lst. The second of these is uninteresting (and doesn't contain very much at present). The first is the flash image that can be programmed with:
/path/to/avrdude -F -v -p m328p -P/dev/ttyACM0 -carduino -b 115200 -D -Uflash:w:tests/test_avr25.flash.hex:i
Depending on your particular system, you may want/need to substitute /dev/ttyACM0 for the port where the Arduino Uno's USB serial interface appears. If the download worked, your Arduino Uno should be blinking its "L" LED .