PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Atmega32 mit dem AVR-GCC in Assembler programmieren



-schumi-
02.09.2011, 15:32
Hallo zusammen,

ich möchte jetzt, nach dem ich immer nur C programmiert habe, mich auch einmal mit Assembler auseinandersetzen. Und dafür den AVR-GCC, der ja schon installiert ist, verwenden.

Code hab ich folgenden:
main.S


#include <avr/io.h>


.text ; was nun folgt, gehört in den FLASH-Speicher


.global main ; main ist auch in anderen Modulen bekannt


main: ; zu 'main' wird nach Reset hingesprungen
LDI R24, 255 ; der wert "255" = FF ins allgemeine Register R24
OUT DDRD, R24 ; das kommt ins Controll-register f. PortD
; dadurch ist das Port auf OUTPUT gestellt
Hauptschleife: ; ein Sprunglabel
LDI R24, 255 ; der wert "255" = FF ins allgemeine Register R24
OUT PORTD, R24 ; schreiben wir nach PortD
rjmp Hauptschleife ; immer wiederholen



Zum übersetzen benutze ich folgendes:


avr-gcc -o main.elf -mmcu=atmega32 main.S
avr-objcopy -O ihex -j .text -j .data main.elf main.hex

Läuft ohne Fehler durch. Das ganze wird dann mit AVRDUDE auf den Atmega32 geklatscht.

Der Controller sitzt auf einem AVR-Funk-Evaluationsboard von Pollin. Dort sind LED1 und LED2 an PD5 und PD6 angeschlossen.

Die sollten jetzt eigentlich auch Leuchten. Tun sie aber nicht! :-(

Weis jemand Rat?

Viele Grüße und vielen Dank schon mal
-schumi-

PS: OS = Ubuntu 11.10; AVR-GCC-Version = gcc-Version 4.5.3 (GCC)

Kampi
02.09.2011, 16:33
Was meinst du mit "allgemeines Register"? Ich denke mal du meinst den Akkumulator. Und wenn du diesen Akkumulator mit 255 lädst und das dann ins PortD Register schreibst steht der Wert immernoch im Akumulator d.h. du musst ihn nicht mehr mit 255 laden.
Wie sind die LEDs an den Controller angeschlossen? Kann es vielleicht sein das die nur leuchten wenn der Pin 0 ist, also Active Low.

-schumi-
02.09.2011, 16:56
Danke für deine Antwort, Kampi :-D


Was meinst du mit "allgemeines Register"? Ich denke mal du meinst den Akkumulator.
Ja, schätz ich mal. Hab mich noch kaum mit Assembler auf dem AVR beschäftigt, hab das Beispiel aus irgend einem Wiki gezogen...


Und wenn du diesen Akkumulator mit 255 lädst und das dann ins PortD Register schreibst steht der Wert immernoch im Akumulator d.h. du musst ihn nicht mehr mit 255 laden.
Das ist mir klar, aber ich hatte es zuvor mit 0 statt 255 probiert, und dann wieder in 255 geändert. Deswegen steht das da^^

[EDIT] Oder meinst du, dass er in der Hauptschleife ständig reingeschrieben wird? Ja, die zwei Zeilen könnte man auch vor die Hauptschleife hinstellen, aber es bleibt sich ja Wurscht...


Wie sind die LEDs an den Controller angeschlossen?
High active, habe aber trotzdem schon beide Varianten 0/255 probiert.


Heist das denn jetzt, das der Code eigentlich gehen müsste, aber irgendetwas mit dem GCC o.ä. nicht stimmt?

Hardwareschaden schließe ich aus, in C kann ich die LEDs einwandfrei ansteuern

Vielen Dank nochmal
-schumi-

Kampi
02.09.2011, 17:29
Probier mal das hier aus.
Statt:

LDI R24, 255

Das hier:

LDI R24, 0

In einem Turtorial haben die den Pin als Eingang geschaltet.

-schumi-
02.09.2011, 17:53
Hulft nicht, das hatte ich auch schon probiert...

Aber Not macht erfinderisch:
Man macht sich eine minimale C-Datei, die genau das macht was man will:

#include <avr/io.h>
void main(void) {
DDRD = 255;
PORTD = 255;
}

Macht damit nen ASM-Dump:


GAS LISTING /tmp/cc7Qz3w0.s page 1




1 .file "main.c"
2 __SREG__ = 0x3f
3 __SP_H__ = 0x3e
4 __SP_L__ = 0x3d
5 __CCP__ = 0x34
6 __tmp_reg__ = 0
7 __zero_reg__ = 1
8 .text
9 .global main
10 .type main, @function
11 main:
12 0000 DF93 push r29
13 0002 CF93 push r28
14 0004 CDB7 in r28,__SP_L__
15 0006 DEB7 in r29,__SP_H__
16 /* prologue: function */
17 /* frame size = 0 */
18 /* stack size = 2 */
19 .L__stack_usage = 2
20 0008 81E3 ldi r24,lo8(49)
21 000a 90E0 ldi r25,hi8(49)
22 000c 2FEF ldi r18,lo8(-1)
23 000e FC01 movw r30,r24
24 0010 2083 st Z,r18
25 0012 82E3 ldi r24,lo8(50)
26 0014 90E0 ldi r25,hi8(50)
27 0016 2FEF ldi r18,lo8(-1)
28 0018 FC01 movw r30,r24
29 001a 2083 st Z,r18
30 /* epilogue start */
31 001c CF91 pop r28
32 001e DF91 pop r29
33 0020 0895 ret
34 .size main, .-main
GAS LISTING /tmp/cc7Qz3w0.s page 2




DEFINED SYMBOLS
*ABS*:00000000 main.c
/tmp/cc7Qz3w0.s:2 *ABS*:0000003f __SREG__
/tmp/cc7Qz3w0.s:3 *ABS*:0000003e __SP_H__
/tmp/cc7Qz3w0.s:4 *ABS*:0000003d __SP_L__
/tmp/cc7Qz3w0.s:5 *ABS*:00000034 __CCP__
/tmp/cc7Qz3w0.s:6 *ABS*:00000000 __tmp_reg__
/tmp/cc7Qz3w0.s:7 *ABS*:00000001 __zero_reg__
/tmp/cc7Qz3w0.s:11 .text:00000000 main


NO UNDEFINED SYMBOLS


Kopiert sich das relevante in die eigenen main.S und schmeißt alles was komisch aussieht raus:


#include <avr/io.h>
.text
.global main
main:
push r29
push r28


ldi r24,lo8(49)
ldi r25,hi8(49)
ldi r18,lo8(-1)
movw r30,r24
st Z,r18
ldi r24,lo8(50)
ldi r25,hi8(50)
ldi r18,lo8(-1)
movw r30,r24
st Z,r18
pop r28
pop r29
ret
Hauptschleife:
rjmp Hauptschleife




Und Thada, schon leuchten die 2 LEDs. Jetzt muss ich nur noch Stück für Stück den Code auseinander nehmen und gucken wie der GCC das anstellt, dass die 2 leuchten...

Besserwessi
02.09.2011, 17:59
Wie sieht denn das .lss File aus. Da sollte das vollständige ASM File einschließlich dem Startcode von GCC und der Sprungtabelle drin stehen. Daran solle man sehen ob GCC hier irgendwas unerwartetes gemacht hat.

Besserwessi
02.09.2011, 18:09
Das Problem liegt darin wie die SFR namen definiert sind. Für C sind da die Adressen für den Zugriff als Speicher (per LDS / STS und ähnlich) angegeben. Für die Benutzung in ASM mit den Befehlen IN und OUT muss man davon noch 32 abziehen. Eine Erklärung findet sich im File sfr_defs.h.

-schumi-
02.09.2011, 18:11
Ich bin jetzt nicht sicher ob ich das richtige gemacht habe. Ist das das was du sehen möchtest?


simon@simon-TravelMate-5735Z:~/Arbeitsfläche/test$ avr-objdump -d main.elf


main.elf: file format elf32-avr




Disassembly of section .text:


00000000 <__vectors>:
0: 0c 94 2a 00 jmp 0x54 ; 0x54 <__ctors_end>
4: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
8: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
c: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
10: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
14: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
18: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
1c: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
20: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
24: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
28: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
2c: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
30: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
34: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
38: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
3c: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
40: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
44: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
48: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
4c: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>
50: 0c 94 34 00 jmp 0x68 ; 0x68 <__bad_interrupt>


00000054 <__ctors_end>:
54: 11 24 eor r1, r1
56: 1f be out 0x3f, r1 ; 63
58: cf e5 ldi r28, 0x5F ; 95
5a: d8 e0 ldi r29, 0x08 ; 8
5c: de bf out 0x3e, r29 ; 62
5e: cd bf out 0x3d, r28 ; 61
60: 0e 94 36 00 call 0x6c ; 0x6c <main>
64: 0c 94 47 00 jmp 0x8e ; 0x8e <_exit>


00000068 <__bad_interrupt>:
68: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>


0000006c <main>:
6c: df 93 push r29
6e: cf 93 push r28
70: cd b7 in r28, 0x3d ; 61
72: de b7 in r29, 0x3e ; 62
74: 81 e3 ldi r24, 0x31 ; 49
76: 90 e0 ldi r25, 0x00 ; 0
78: 2f ef ldi r18, 0xFF ; 255
7a: fc 01 movw r30, r24
7c: 20 83 st Z, r18
7e: 82 e3 ldi r24, 0x32 ; 50
80: 90 e0 ldi r25, 0x00 ; 0
82: 2f ef ldi r18, 0xFF ; 255
84: fc 01 movw r30, r24
86: 20 83 st Z, r18
88: cf 91 pop r28
8a: df 91 pop r29
8c: 08 95 ret


0000008e <_exit>:
8e: f8 94 cli


00000090 <__stop_program>:
90: ff cf rjmp .-2 ; 0x90 <__stop_program>
simon@simon-TravelMate-5735Z:~/Arbeitsfläche/test$

-schumi-
02.09.2011, 18:23
Das Problem liegt darin wie die SFR namen definiert sind. Für C sind da die Adressen für den Zugriff als Speicher (per LDS / STS und ähnlich) angegeben. Für die Benutzung in ASM mit den Befehlen IN und OUT muss man davon noch 32 abziehen. Eine Erklärung findet sich im File sfr_defs.h.
Hat jetzt zwar ein bischen gebraucht, aber ich glaub ich habs gecheckt:

Im Datenblatt steht:


$12 ($32) PORTD PORTD7 PORTD6 PORTD5 PORTD4 PORTD3 PORTD2 PORTD1 PORTD0 65
$11 ($31) DDRD DDD7 DDD6 DDD5 DDD4 DDD3 DDD2 DDD1 DDD0 65


Die $32 und $31 (jeweils zweite Zahl von Links) sind die Adresse, die der GCC benutzt (umgerechnet von Hex nach Dez sind das nämlich 50 und 49 - die sich auch im Quellcode wiederfinden)

Wenn ich jetzt 50-32 = 18 Rechne, und in 18 und auch 17 (49 - 32) jeweils 255 mit OUT reinschreibe, leuchten die LEDs!

Und Praktischerweise sind die 12 und 11 im Datenblatt (jeweils erste Zahl) von hex nach dez umgerechnet auch 18 und 17, ich brauch also auch nicht jedesmal mühsam -32 rechnen :-D


Vielen vielen Dank Besserwessi und Kampi! :-D


Viele herzliche Grüße
-schumi-

[EDIT]
Hier noch der aktuelle Quellcode:


#include <avr/io.h>
.text
.global main
main:

LDI r24, 255
OUT DDRD-32, r24
LDI r24, 255
OUT PORTD-32, r24

Hauptschleife:
rjmp Hauptschleife

Kampi
02.09.2011, 18:42
Deine Erklärung check ich jetzt nicht ganz :D

-schumi-
02.09.2011, 18:50
Der GCC benutzt DDRD. Aus irgend einem Grund, den ich noch nicht kenne, ist aber die Adresse, die sich hinter "DDRD" verbirgt für "OUT" und "IN" um 32 zu groß. -> 32 abziehen und passt.

Und im Datenblatt is die Adresse eben zweimal angegeben, erst ohne Klammern, so wie sie für "OUT" und "IN" passt und dann nochmal mit klammern, so wie sie sich hinter "DDRD", "PORTD" usw. verbirgt...

Was das jetzt alles auf sich hat -> Ich weis es (noch) nicht... Aber damit funktioniert es zumindest schonmal so weit...

-schumi-
02.09.2011, 19:10
Ok, hab mir jetzt mal die sfr_defs.h angeschaut, oben steht:


The \c <avr/sfr_defs.h> file is included by all of the \c <avr/ioXXXX.h>
files, which use macros defined here to make the special function register
definitions look like C variables or simple constants, depending on the
<tt>_SFR_ASM_COMPAT</tt> define. Some examples from \c <avr/iocanxx.h> to
show how to define such macros:


\code
#define PORTA _SFR_IO8(0x02)
#define EEAR _SFR_IO16(0x21)
#define UDR0 _SFR_MEM8(0xC6)
#define TCNT3 _SFR_MEM16(0x94)
#define CANIDT _SFR_MEM32(0xF0)
\endcode


If \c _SFR_ASM_COMPAT is not defined, C programs can use names like
<tt>PORTA</tt> directly in C expressions (also on the left side of
assignment operators) and GCC will do the right thing (use short I/O
instructions if possible). The \c __SFR_OFFSET definition is not used in
any way in this case.


Define \c _SFR_ASM_COMPAT as 1 to make these names work as simple constants
(addresses of the I/O registers). This is necessary when included in
preprocessed assembler (*.S) source files, so it is done automatically if
\c __ASSEMBLER__ is defined. By default, all addresses are defined as if
they were memory addresses (used in \c lds/sts instructions). To use these
addresses in \c in/out instructions, you must subtract 0x20 from them.


For more backwards compatibility, insert the following at the start of your
old assembler source file:


\code
#define __SFR_OFFSET 0
\endcode

Und es funktioniert sogar :-D



#include <avr/io.h>
#define _SFR_ASM_COMPAT 1
#define __SFR_OFFSET 0
.text
.global main
main:

LDI r24, 255
OUT DDRD, r24

LDI r24, 255
OUT PORTD, r24

Hauptschleife:
rjmp Hauptschleife

radbruch
02.09.2011, 19:48
Hallo

Ich find das voll clever wie du das anpackst.

/* Port D */
#define PIND _SFR_IO8(0x10)
#define DDRD _SFR_IO8(0x11)
#define PORTD _SFR_IO8(0x12)

Das findet man in der Datei iom32.h die von io.h eingebunden wird. Find ich eh ziemlich "unsportlich":

#include <avr/io.h>

Gruß

mic

-schumi-
02.09.2011, 20:08
Ich find das voll clever wie du das anpackst.
Vielen Dank! :-D


/* Port D */
#define PIND _SFR_IO8(0x10)
#define DDRD _SFR_IO8(0x11)
#define PORTD _SFR_IO8(0x12)

Das findet man in der Datei iom32.h die von io.h eingebunden wird. Find ich eh ziemlich "unsportlich":

#include <avr/io.h>

Du meinst also, ich sollte am Anfang meiner main.S anstatt #include <avr/io.h> die defines du du eben genannt hast verwenden?

Ich müsste das dann aber für alle Register machen, also für alle Ports und was halt noch so zusammenkommt... Da nutze ich lieber die Vorteile die der AVR-GCC bietet und schummle ein bischen :-D

(Hab auch schon versucht die delay.h für Wartezeiten zu benutzen, aber der schmeisst schon beim #include <avr/delay.h> Fehler ohne Ende^^)

Kampi
02.09.2011, 20:14
Ach komm stell dich nicht so an :D
Timer in ASM sind doch leicht. Musst nur 1x die richtigen Bits in die Register schieben und das war es schon. Das einziger schwere ist wieder die ISR wegen Registerinhalte retten usw. aber das ist bei jeder ISR so.

-schumi-
02.09.2011, 20:48
Ich hab jetzt ein Programm geschrieben, das folgendes macht: Normal leuchtet LED1 und solang man Taster1 drückt leuchtet LED2

Code:


#define PIND 0x10
#define DDRD 0x11
#define PORTD 0x12


#define PINB 0x16
#define DDRB 0x17
#define PORTB 0x18

.text
.global main
main:

LDI r24, 255
OUT DDRD, r24

LDI r24, 0
OUT DDRB, r24

Hauptschleife:
SBIC PINB, 1
LDI r24, 0b00100000
SBIS PINB, 1
LDI r24, 0b01000000
OUT PORTD, r24
rjmp Hauptschleife


Und funzt auch... :-D

Digital-Ghost
02.12.2011, 09:26
Ich habe mal eine abweichende Frage dazu und zwar ist es mit dem gcc auch möglich die AT&T Syntax zu benutzen?