Hallo Leute,
ich bin gerade dabei, mich mit C für AVR zu beschäftigen. In C
porgrammiere ich schon lange, allerdings nicht für den AVR. Dort war
bisher Assembler angesagt. Also, folgendes Programm tut nicht das, was
ich erwarte:
#include <avr/io.h>
int main (void)
{
DDRB = 0x02; /* PB1 ist Ausgang */
PORTB = 0x00; /* PB1 ausschalten */
volatile uint8_t i = 0;
/* müsste eigentlich Endlosschleife sein */
while (i < 1)
{
}
/* Programm kommt fälschlicherweise hier an! */
PORTB = 0x02; /* PB1 einschalten */
/* Endlosschleife */
while (1)
{
}
return (0);
}
PB2 wird eingeschaltet, obwohl das Programm eigentlich gar nicht in
der entsprechenden Zeile ankommen dürfte. Prinzipiell funktioniert die
Verwendung der Variablen, z.B. sowas:
i = 0x02;
PORTB = i;
oder
i = 0;
++i;
++i;
PORTB = i;
Compiler ist avr-gcc. Download aus hex-Datei. Die avr-libc wird nicht
eingebunden.
Der vom gcc erzeugte Assemblerquelltext sieht bis auf die beiden .global
am Anfang eigentlich recht übersichtlich aus:
.file "togglefast.c"
.arch atmega48
_SREG_ = 0x3f
_SP_H_ = 0x3e
_SP_L_ = 0x3d
_tmp_reg_ = 0
_zero_reg_ = 1
.global __do_copy_data
.global __do_clear_bss
.text
.global main
.type main, @function
main:
/* prologue: frame size=1 */
push r28
push r29
in r28,__SP_L__
in r29,__SP_H__
sbiw r28,1
in _tmp_reg_,__SREG__
cli
out _SP_H_,r29
out _SREG_,__tmp_reg__
out _SP_L_,r28
/* prologue end (size=10) */
ldi r24,lo8(2)
out 36-0x20,r24
out 37-0x20,__zero_reg__
std Y+1,__zero_reg__
.L2:
ldd r24,Y+1
tst r24
breq .L2
ldi r24,lo8(2)
out 37-0x20,r24
.L4:
rjmp .L4
/* epilogue: frame size=1 */
/* epilogue: noreturn */
/* epilogue end (size=0) */
/* function main size 20 (10) */
.size main, .-main
/* File "togglefast.c": code 20 = 0x0014 ( 10), prologues 10,
epilogues 0 */
Hat jemand eine Idee, was hier nicht funktioniert? Ich freue mich über
jede Rückmeldung. Vielen Dank schon an dieser Stelle.
ciao
Marcus
is schon ne Weile her mein Assembler
std Y+1,__zero_reg__ ' Hier wird in RAM[1] eine 0 geschrieben
.L2:
ldd r24,Y+1 ' RAM[1] auslesen
tst r24 ' testen
breq .L2 ' bei gesetztem Zero-Flag zu L2
sieht auf (meinen) ersten Blick korrekt aus.
hast du den Controllertyp richtig gesetzt (lt. gcc-Output wurde dies für
einen Atmega48 compiliert)
Du kannst dir mal das AVRStudio anschauen, da ist ein Simulator dabei.
Gruß
Roland
Hallo,
bau mal in die while-Schleifen ein asm("nop"); ein.
Unter umständen optimiert der Compiler die Schleife weg, da diese so ja
anscheinend keinen Sinn haben.
Grüße,
Florian
Hallo Andreas,
vielen Dank für Deine Antwort. Nein, die LED passt schon, da sich diese
ja ein- und ausschalten lässt. Andere Testprogramm funktionieren ja auch
wie gewünscht...
ciao
Marcus
Hallo Florian,
vielen Dank für Deine Antwort. Ich werde das mit dem nop mal testen,
allerdings sieht der Assembler-Output des Compilers ja i.O. aus, und
auch die "while(1)"-Schleife funktioniert ja wie gewünscht. Der Compiler
darf ja gerne optimieren, aber durch das Optimieren sollte sich das
Programmverhalten ja eigentlich nicht ändern. Deshalb habe ich mir ja
auch den Assembler-Output angeschaut.
ciao
Marcus
mhh,
ich sehe keinen fehler, auch der ASM-code sieht in meinen Augen ok aus.
ich habe aber schon ewig nichtmehr in ASM gecoded. Da verliert man dann
leicht den Blick für eine Sprache.
> Die avr-libc wird nicht eingebunden.
Mit -nostdlib? Das ist der Fehler.
Die AVR-Libc stellt u.a. den Startupcode bereit, der vor dem Aufruf von
main ausgeführt wird und verschiedene Dinge initialisiert, u.a. den
Stackpointer und statische Variablen. Insbesondere wird auch das
Register R1, das fortan als Nullregister verwendet wird, mit 0
initialisiert. Der Befehl
1
std Y+1,__zero_reg__
benutzt nun genau dieses Register, um sie der Variable i zuzuweisen.
Ist R1 nicht initialisiert, wird i ein undefinierter Wert zugewiesen,
der in deinem Fall offensichtlich von 0 verschieden ist.
Lass das -nostdlib weg, und dein Programm wird das Gewünschte tun.
Hallo yalu,
Bingo! Tja, das sind bei mir typische Startprobleme. Ich starte gerne
minimalistisch, und daraus resultierte auch meine tool chain:
avr-gcc -S (Assembler output)
avr-as
avr-objcopy (elf nach hex)
Das ganze dann mit einem selbst erstellten Programm und passendem
Druckerport-Adapter in den AVR übertragen.
Mich hatten ja die sections
.global __do_copy_data
.global __do_clear_bss
etwas irritiert. Aber ich dachte: "kein Tool meckert, dann wird's schon
passen". Ich hatte mich dann nach meinem erfolgreichen Versuch (mit gcc
direkt das ausführbare Programm erzeugen) noch mal mit meiner ersten
tool chain beschäftigt. Ich sehe nicht, wie ich as oder ld dazu bringen
könnte, mir den passenden Startcode doch noch mit einzubauen. Außerdem
ist mir momentan unklar, woher dieser Startcode kommt. Ich vermute mal
nicht von der avr-glibc, denn die habe ich meinem Compiler (zumindest
nicht wissentlich) bekannt gemacht. Werde aber mal schauen, ob die
avr-glibc-Funktionen doch ohne Fehler übersetzt bzw. belinkt werden.
Ach ja: System ist openSUSE 10.3
Vielleicht kann ja noch jemand ein wenig Licht ins Dunkel bringen.
Ich werde mir dann parallel noch weiter Lektüre zusammensuchen.
Vielen Dank nochmal für alle Antworten.
ciao
Marcus
Der übliche (und sicherste) Weg ist es, für C-programme den Assembler
und Linker über avr-gcc aufzurufen und nicht direkt als avr-as und
avr-ld. Dann werden automatisch das richtige Startup-Modul
(avr?/crt*.o), die richtigen Libraries (avr?/libgcc.a und avr?/libc.a)
und das richtige Linker-Skript (avr?.x*) ausgewählt, passend zu dem mit
der Option -mmcu angegebenen Controllertyp. Beim direkten Aufruf von
avr-ld müssen diese Informationen von Hand zusammengetragen werden, und
die Gefahr ist groß, dass man dabei einen Fehler macht, der vom Linker
nicht gemeldet wird, aber zum Versagen des gelinkten Programms führt.
Wenn dein Programm nur aus einem Modul main.c besteht, sind die Schritte
die folgenden:
1
avr-gcc -mmcu=atmega48 -Os -o main.elf main.c # kompilieren und linken
2
avr-objcopy -O ihex main.elf main.hex # in Hex konvertieren
Besteht das Programm aus mehreren Modulen (main.c, cmod1.c und cmod2.c),
geht es folgendermaßen:
avr-objcopy -O ihex main.elf main.hex # in Hex konvertieren
Beim Linken kommt zusätzlich moch die Option -lm ans Ende der Zeile,
wenn dein Programm Floating-Point verwendet.
Selbstgeschrieben Assembler-Module (amod.S) werden wie folgt
assembliert:
1
avr-gcc -mmcu=atmega48 -c amod.S # assemblieren
und anschließend wie oben bschrieben gelinkt.
Meist werden die genannten Schritte zum einem Makefile zusammengefasst,
das man entweder selbst schreibt oder sich mit entsprechenden Tools
(Mfile) generieren lässt.