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
??? war das LOL wegen des Schreibfehlers? 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.
Gibt's keine Fehlermeldungen oder Warnings beim compilieren...?
> 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:
1 | avr-gcc -mmcu=atmega48 -Os -c main.c # kompilieren |
2 | avr-gcc -mmcu=atmega48 -Os -c cmod1.c # kompilieren |
3 | avr-gcc -mmcu=atmega48 -Os -c cmod2.c # kompilieren |
4 | avr-gcc -mmcu=atmega48 -Os -o main.elf main.o cmod1.o cmod2.o # linken |
5 | 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.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.