I figured out how to assemble without error messages.
The error messages you see are caused by the fact that avr-as
does not invoke the C-preprocessor and therefore the #include
lines are read as regular comments.
Create a file ledON.S
and notice the CAPITAL S
in the filename. The capital S
indicates that the C-preprocessor must be invoked first. Create the file with the following content:
#include <avr/io.h>
init: sbi _SFR_IO_ADDR(DDRB),0x05 ; Configure port B pin 5 as output
ret
.global main
main:
call init
loop:
sbi _SFR_IO_ADDR(PORTB),0x05 ; Toggle output pin HIGH
cbi _SFR_IO_ADDR(PORTB),0x05 ; Toggle output pin LOW
rjmp loop
The main
part is required in the source code, this is where usually the main program is located. If you remove it, no real code is executed ever and thus the compiler will complain about that. Code in a routine called init
should be explicitly called from main
. Although the pin toggles, in practice this will be too fast to see with the bare eye. If you check with an oscilloscope you'll see a square wave (I estimate at 25% duty cycle).
Then assemble the source code with:
avr-gcc -mmcu=atmega168a ledON.S -o ledON.o
Again avr-gcc
is required to invoke the C-preprocessor.
To check the result of the assembled program run the following command:
avr-objdump -C -d ./ledON.o
And the resulting disassembly listing looks like this:
./ledON.o: file format elf32-avr
Disassembly of section .text:
00000000 <__vectors>:
0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
4: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
8: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
10: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
14: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
18: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
1c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
20: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
24: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
28: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
2c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
30: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
34: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
38: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
3c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
40: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
44: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
48: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
4c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
50: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
54: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
58: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
5c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
60: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
64: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
00000068 <__ctors_end>:
68: 11 24 eor r1, r1
6a: 1f be out 0x3f, r1 ; 63
6c: cf ef ldi r28, 0xFF ; 255
6e: d4 e0 ldi r29, 0x04 ; 4
70: de bf out 0x3e, r29 ; 62
72: cd bf out 0x3d, r28 ; 61
74: 0e 94 42 00 call 0x84 ; 0x84 <main>
78: 0c 94 47 00 jmp 0x8e ; 0x8e <_exit>
0000007c <__bad_interrupt>:
7c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>
00000080 <init>:
80: 25 9a sbi 0x04, 5 ; 4
82: 08 95 ret
00000084 <main>:
84: 0e 94 40 00 call 0x80 ; 0x80 <init>
00000088 <loop>:
88: 2d 9a sbi 0x05, 5 ; 5
8a: 2d 98 cbi 0x05, 5 ; 5
8c: fd cf rjmp .-6 ; 0x88 <loop>
0000008e <_exit>:
8e: f8 94 cli
00000090 <__stop_program>:
90: ff cf rjmp .-2 ; 0x90 <__stop_program>
INTERMEZZO
You'll notice the assembler will automatically initialize the stack pointer
and status register in __ctors_end
.
Also it will automatically add a rjmp
at the end of the code.
Default behaviour of a program assembled by gcc-avr when it ends is:
- turn off interrupts (
_exit
, cli
)
- infinite empty loop that does nothing (
__stop_program
, rjmp .-2
)
In assembly language, the term addressing mode
doesn't mean taking the address of a variable; but rather how information is encoded into the instruction code. For example, instruction code 0xE8 0x4D is the mov A,0x4D
instruction using direct addressing mode: acc A gets the contents of iram address 0x4D. But instruction code 0x74 0x4D is mov A,#0x4D
: acc A gets the actual literal value 0x4D. Both instructions are the same length and use similar encoding, but the meaning of the 0x4D depends on which addressing mode was used.
Even if you define a symbol LetterM equ 0x4D
, this makes no difference to the 8051 -- mov A,LetterM
assembles as 0xE8 0x4D and mov A,#LetterM
assembles as 0x74 0x4D. The 8051 itself never sees the assembly source code, only the object code. So the only reason punctuation like #
or @
matters at all, is to ensure that the assembler uses the right addressing mode to encode each instruction.
For loading the 16-bit data pointer register, mov dptr,#data16
only supports immediate addressing mode
. Usually you want to use dptr to point to the memory address of an object, so immediate addressing mode is what you need. To your question, mov dptr,#Z
considers mov dptr,#data16
as the immediate addressing mode, and Z
as the address of symbol Z -- which you're using to store a variable.
If you already understand C language pointers, the dptr register is a pointer to an 8-bit value in memory, and the movc A, @A+DPTR
instruction is what de-references the pointer value that's in the dptr register. So dptr always gets the address where the variable lives, and the indirect addressing mode (@) uses the value currently in dptr to get the value that dptr is pointing at.
// some C pseudocode to help explain 8051
uint8* dptr_register; // 8051 built-in DPTR register
uint8 acc_a; // 8051 built-in accumulator register A
static uint8 myVarZ = 123; // myVarZ: db 123
dptr_register = &myVarZ; // mov dptr,#myVarZ ; address of myVarZ
acc_a = *dptr_register; // movc A,@A+DPTR (assuming A is 0) ; get value of myVarZ
The 8051 is a little bit more complicated though, because it has more than one address space. Code address 0 is different than internal RAM address 0, which is also different than external RAM address 0. This is why there are several different instructions that use dptr (`movc', 'mov', 'movx'). Real C compilers for 8051 (like Keil C51 or SDCC) have a few extra, non-standard keywords to distinguish between these different address spaces.
Other microcontrollers have slightly different instruction sets and use slightly different punctuation, but they're all similar enough that one you learn one, you can easily pick up the others.
Best Answer
In VMOV, the F32 format expects the following argument as immediate value:
You have the following immediate values:
$$ \text{0x}419C0000 = 4199\times2^{18} \\ n = 4199 \quad r = -18 $$ and $$ \text{0x}41B40000 = 4205\times2^{18} \\ n = 4205\quad r = -18 $$
In both instructions, the immediate values are clearly out of the expected range. You should scale these parameters beforehand.