avr-gcc assembly compile problem (basic)

Go To Last Post
9 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Can anyone help me get this code compiled? It's my first stab at assembly and I'm having a heck of a time.

#include 

#define mp r16

; Until now, no data has been written. Here is where
; the program start address is defined.
; Whenever the AVR is restarted it starts the program
; execution at address 0000.

rjmp main               ; relative jump to main
                        ; relative jump means that the relevent
                        ; distance is added to the current execution
                        ; execution address and the program is executed
                        ; at the new address

; So this is the start (0000)
main:

        ldi     mp,0b11111111       ; put 8 bits (maximum value) into 
                                    ; register r16.
                                    ; 0b denotes binary
                                    ; ldi means "load immediate"
                                    ;     - only works on registers r16 - r31

        out     DDRD,mp             ; write the register value (r16) to port DDRD
                                    ; this will turn on port D for output

loop:
        ldi     mp,0x00             ; load eight zero bits into mp
        out     PORTD,mp            ; copy the register value to PORTD
                                    ; when the zeros are set the LEDs will be _ON_.
                                    ; this is because they are connected to the
                                    ; voltage resistors of 1 k (0 = on, 1 = off).

        ldi     mp,0xFF
        out     PORTD,mp

        rjmp loop

This is basically scrapped together from some examples I've found on the internet.

When I attempt to compile the code I get the following error:

-------- begin --------
avr-gcc (GCC) 4.2.1
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


Assembling: led.S
avr-gcc -c -mmcu=attiny2313 -I. -x assembler-with-cpp -Wa,-adhlns=led.lst,-gstabs  led.S -o led.o

Linking: led.elf
avr-gcc -mmcu=attiny2313 -I. -g -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=led.o  -std=gnu99  led.o  --output led.elf -Wl,-Map=led.map,--cref -lm
/usr/lib/gcc/avr/4.2.1/../../../../avr/lib/crttn2313.o: In function `__bad_interrupt':
../../../../crt1/gcrt1.S:123: undefined reference to `main'
make: *** [led.elf] Error 1

I'm using the avr-gcc toolset under linux and I've been able to get a couple of simple C examples working just fine. It seems like I'm missing something in the linking process but I don't know enough yet to figure it out. Any help would be great!

Thanks,

Ben

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think you've missed a fundamental point about GCC assembler (that is not like Atmel assembler on wchih your example seems to be based). Unless you go out of your way to stop it the build system will link in some library code that is located BEFORE your program to provide a table of interrupt vectors, a default vector handler and ultimately a jump to a lable you provide called "main:". So you don't start your program with a "(r)jmp main" because (a) this will not be positioned at location 0x0000 anway and (b) like I say, there'll be code expecting you to have provided main: anyway.

Your program would have worked as it stands though (though the stuff before main: would effectively be "dead") if only you had also included:

.global main

You need to use .global to make any routine names you provide that are to be called from "outside" the source file visible.

In effect a .S program in GCC is no different to a .c program - both provide a main() or main: that can be called by the pre-amble and it's the linker, not you that decides at what address it wants to position the code you wrote (which is how it's QUITE different to the Atmel assembler which is an absolute assembler, for which you need to use .org's)

BTW, as this is a question about GCC I'm going to move it to that forum.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Cliff,

You're right - that example was taken from an originally Atmel example. I've fixed it up to look like this:

#include 
#define mp r16

.global main

main:
        ldi     mp,0b11111111
        out     DDRB,mp

loop:
        ldi     mp,0x00
        out     PORTB,mp

        ldi     mp,0xff
        out     PORTB,mp

        rjmp loop

It compiled just fine :mrgreen: , Now if I could just figure out how to turn on port B0 - I don't seem to be doing it right up there.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well that code IS turning on PORTB.0 (and all the other bits), then it's turning it off, then on, then off, ad infinitum

If you have an LED connected it should still appear to glow even though it's only switched on half the time (in fact you just reinvented a technique called PWM!)

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ahhh... I thought it was turning them all on but I must not be getting enough voltage to power the LED at all. How do I just turn on B0?

Now I'm going to have to go figure out this PWM :D

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
in mp, PORTB
ori mp, (1<<PB0)
out PORTB, mp

to turn it on and

in mp, PORTB
andi mp, ~(1<<PB0)
out PORTB, mp

to turn it off.

And for a further explanation of bit manipulation (admittedly C, not Asm but, as you'll see, it's actually the same) see:

https://www.avrfreaks.net/index.p...

When you use the C examples in that thread then look at the generated code you may see something like the above. Note also that if PORTB has an IO address 0x00 to 0x1F for your AVR then it's even easier to use SBI and CBI and this is what your C compiler will also choose to use.

In fact all of this does kind of raise the question (that some people here LOVE to debate!) of "why do this in Asm in the first place?" - the C compiler is almost bound to generate the same code anyway.

If this is all about learning the Asm of the AVR, my preferred technique for any new CPU I start to use is to write in C but then debug it with the mixed C/Asm view and see the code the compiler generated and watch what's going on in the registers and and the SFRs as the code is single stepped. I think it's a much easier way to learn a CPUs assembler than digging out the opcode manual and starting from scratch. You might not know from that manual, for example, that for some addresses an SBI is a more efficient mechanism than IN/OR/OUT - but the C compiler will show you what are the ways to do stuff like this in the most efficient way (though you have to trust the C compiler writers a bit - hope that they HAVE used the most efficient techniques available!)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ooh! Now I can't wait to get home from work and try this out. You said that PORTB has an IO address from 0x00 to 0x1F - can you explain this a little bit more in detail? I understand the theory of the bitwise operations but I'm not exactly sure how to apply it to the AVR.

Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For all memory locations in an AVR you could use:

LDS mp,
ORI mp, 
STS ,mp

so you could do all your AND's and OR's on an AVR using this (and if you build your C programs with no optimisation you'll probably see this happening!). The only downside is that the LDS and the STS are both 2 cycle operations and they both occupy 4 bytes of code space. So they aren't the most efficient way to do it.

Now if the address of the Special Function Register (SFR) you are trying to access is between RAM address 0x20 and 0x5F then the AVR provides a more efficient way to read/write those locations using the IN and OUT instructions and these recognise that the frist 0x20 memory locations are actually the AVR registers r0 to r31 so 0x20 is removed from the address to make an IN/OUT range 0x00 to 0x3F which are the 64 memory locations from 0x20 to 0x5F. IN/OUT are limited to this ranged because their opcodes only have 6 bits available to identify the offset and 6 bits gives a 0..63 (0x00..0x3F) range. So you can also use:

IN mp,
ORI mp, 
OUT ,mp

as long as is in the IO 0x00..0x3F range. In the AVR datasheet if you look at "register summary" you'll see that the bottom 64 locations are shown with TWO addresses. The number not in parenthese is the number to be used with IN/OUT while the number to use with LDS/STS is the one in the parentheses (and is +0x20 above the other).

Now, looking at that ssme list you'll also see a THICK line between IO/OUT address 0x1F and 0x20. Anything that is below this line (with IO address 0x00..0x1F or RAM address 0x20..0x3F) can have individual bits set or cleared using SBI and CBI which achieve the equiavalent of IN/OR/OUT in just 2 cycles whereas IN/OR/OUT would have been 3 cycles. So you can set a bit with:

SBI , 

in just that single instruction. To clear a bit it would simply be:

CBI , 

But the downside of these most efficient of instructions are (a) only a single bit can be changed (otherwise IN/OR/OUT should be used) and (b) you are limited to the botttom 32 locations of IO space.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Cliff! You've been more than helpful. I'm going to try to work with this for a while and see what I come up with.