Die Verwendung von avr-gcc _delay_ms führt zum Einfrieren des Chips

Ich habe einen ATmega328-PU-Chip, der so eingerichtet ist, dass er den internen Oszillator verwendet, ohne die Taktrate durch 8 zu teilen. Das erste, was ich tat, war, ein wirklich einfaches Programm zu verwenden, das ich hochgeladen hatte; ein Programm, das alle Pins auf PORTB ein- und ausschaltet. So:

#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/delay.h>

int main(void) {
    DDRB = 0xFF;
        PORTB = 0xFF;

    while(1) {
            PORTB = 0x00;
            _delay_ms(50);
            PORTB = 0xFF;
            _delay_ms(50);
    }

    return 0;
}
  • Update: Ich habe versucht, was Jon L vorgeschlagen hat. Immer noch das gleiche Ergebnis.
  • Update 2: Ich habe versucht, was Kevin Vermeer vorgeschlagen hat. Der Chip scheint einzufrieren, wenn er in die erste Schleife eintritt. Die Verwendung des Timers mit einem Wert < 65000 lässt die LED direkt einschalten, mit einem Wert > 65000 führt dies dazu, dass die LED nie einschaltet.
  • Update 3: Ich habe das Folgende mit einem neuen identischen Chip mit den gleichen Ergebnissen versucht
  • Update 4: Ich habe simulavr und avr-gdb angeschlossen, um zu sehen, ob ich etwas gefunden habe, dies war die Ausgabe:

    memory.c:267: WARNUNG: * * Versuch, ungültige IO-Registrierung zu schreiben: ADCL bei 0x0024 memory.c:267: WARNUNG: * * Versuch, ungültige io-Registrierung zu schreiben: ADCH bei 0x0025 memory.c:267: WARNUNG: * * Versuch, eine ungültige IO-Registrierung zu schreiben: ADCH bei 0x0025 decoder.h:59: WARNUNG: Unbekannter Opcode: 0xffff

Dann wird der unbekannte Opcode für immer wiederholt.

Wenn ich das Programm jedoch hochgeladen habe, erreicht es die zweite Anweisung in main und friert dann ein. Lassen Sie alle PORTB-Pins auf HIGH. Bisher habe ich versucht:

  • Verschiedene ms zwischen 10 - 1000, um zu sehen, ob es einige Werte gibt, die nicht verwendet werden können
  • F_CPU auf 1000000UL geändert, falls es CKDIV8 verwenden würde.
  • Schleife _delay_ms(1) in einer separaten Funktion, bis es bis zum angegebenen Betrag iteriert
  • Mehrfach neu kompiliert und hochgeladen
  • Mehrfaches Zurücksetzen versucht
  • Verwendet verschiedene PORTB-Pins
  • Definierte F_CPU aus den Compiler-Argumenten-DF_CPU=8000000

Warum ich hier frage und nicht stackoverflow.com, ist, weil ich denke, ich sollte anfangen, Fehler auf der niedrigsten Abstraktionsebene zu beseitigen, das heißt; Hardware.

Was könnte also das Problem sein?

Hier ein paar Informationen zu meinem Setup:

  • Betriebssystem: OS X 10.7.3
  • Programmierer: AVRisp MKII
  • Uploader: avrdude
  • Compiler: avr-gcc
  • Bin 2 hex: avr-objcopy

Sicherungseinstellungen:

avrdude: safemode: lfuse reads as E2
avrdude: safemode: hfuse reads as D9
avrdude: safemode: efuse reads as 7

Eingabe in den AVR-Sicherungsrechner :

Sicherungseinstellungen im AVR-Sicherungsrechner

makefile:

main:
        avr-gcc -g -Os -Wall -mmcu=atmega328 -c ../src/example.c

hex:
        avr-objcopy -j .text -j .data -O ihex example.o example.hex

dump:
        avr-objdump -h -S example.o > example.lst

upload:
        avrdude -p m328 -c avrispmkII -P usb -U flash:w:example.hex

clean:
        rm -f *.o
        rm -f *.hex
        rm -f *.lst

Auf dem Chip verwendete Pins:

  • Pin 7 (VCC): 5-Volt-Versorgung
  • Pin 8 (GND): Masse
  • Pin 14 (PB0): Widerstand und LED
...Sie müssen auch sicherstellen, dass Sie Größenoptimierungen aktiviert haben, damit die util/delay.h-Funktionen korrekt funktionieren.
Ja, das ist aktiviert. Sehen Sie sich das Makefile unter an main:, wo Sie sehen können, dass das -Osverwendet wird.

Antworten (3)

Ich vermute, das Problem liegt nicht an Ihrem C-Code, sondern an Ihrem Makefile.

Die folgenden Zeilen in Ihrem Makefile erzeugen eine example.oObjektdatei.

main:
    avr-gcc -g -Os -Wall -mmcu=atmega328 -c ../src/example.c

Die erstellte .oDatei enthält nur die Symbole und den Code von example.c, nicht die zusätzliche Quelle, die erforderlich ist, um sie tatsächlich auf einem Zielsystem auszuführen, wie z. B. Interrupt-Vektorsprungtabellen und Code zum Initialisieren des BSS-RAM-Segments auf Nullen und zum Laden Ihrer initialisierten Datenabschnitte.

Sie müssen eine zusätzliche Zeile wie diese hinzufügen, um den Linker auszuführen und ein Ausgabeobjekt zu erzeugen, das zum Herunterladen in den AVR-Teil geeignet ist. Verwenden Sie alternativ avr-ld, aber Sie müssen alle erforderlichen Linkeroptionen ausarbeiten.

main.elf: example.o
    avr-gcc example.o -o main.elf

Sie können avr-objdump --disassemble-all <filename>beide example.ound main.elfsich selbst verwenden, um den unterschiedlichen Inhalt jeder Datei zu überprüfen.

Es ist immer eine gute Idee, zu versuchen, Ihr Problem schrittweise auf ein möglichst einfaches Beispiel zu reduzieren. In diesem Fall würde es wahrscheinlich bedeuten, in die AVR Studio-Software zu wechseln und ein Projekt zu erstellen, das auf dem Simulator mit ihrem verwalteten Build-Prozess ausgeführt wird. Von dort aus können sie das Makefile exportieren, das von ihrem Build-Prozess verwendet wird, indem sie die Menüoption „Makefile exportieren“ verwenden. Das generierte Makefile könnte dann mit Ihrer Version verglichen werden.

Tatsächlich ist es wahrscheinlich eine gute Idee, ein Makefile zu verwenden, das dem von AVR Studio generierten ähnlich ist, da die richtigen Regeln bereits definiert sind. Sie müssen nur einige Variablen in Bezug auf die zu generierenden Objekte und die endgültige Zieldatei einrichten Name.

Das klingt wirklich vernünftig! Daran habe ich noch gar nicht gedacht. Das ist der eine Teil, den ich total übersehen habe. Ich werde mich über die Verwendung des Linkers informieren und das ausprobieren. Danke für die andere Herangehensweise! :)
Sie, Sir, haben mir den Tag versüßt. Es funktioniert wie erwartet! Ich fügte die Elfen-Erstellung zu meiner Make-Datei hinzu und führte Kompilierung, Elfen-Erstellung, Hex-Erstellung und schließlich Hochladen durch.

Für Ihre CPU-Geschwindigkeit:

#define F_CPU 80000000UL

Hast du da extra 0drauf? Das liest 80 MHz. Ich denke, Sie wollen 8 MHz.

BEARBEITEN: Nachdem Sie das Datenblatt überprüft haben, scheint Ihr lfuseWert in Ordnung zu sein, wenn Sie den internen Oszillator mit deaktiviertem CKDIV8 verwenden möchten.

Gut erkannt! Obwohl, wenn Sie Recht haben, würde die Verzögerungsschleife nicht 5 Sekunden dauern, also würden Sie etwas Aktivität sehen ...
@ JobyTaffey, ja ... Ich suche auch woanders nach Problemen.
Danke, dass du das entdeckt hast! Gibt es sonst noch etwas, das ich beachten und verbessern sollte? :) Ich wollte den Chip ausprobieren, aber ich habe keinen externen Oszillator und keine Kappen, die ich daran anschließen kann, deshalb habe ich den internen Oszillator verwendet.
@rzetterberg, das ist wahrscheinlich überflüssig, aber ich habe in einem meiner alten Makefiles bemerkt, dass ich dieses Flag nach avr-gcc hatte: "-DF_CPU=8000000" - wieder, da Sie dies im Code definiert haben, denke ich, dass dies überflüssig ist, aber Ich versuche mich zu erinnern, warum ich das überhaupt hier drin hatte ... könnte es wert sein, darauf einzugehen.
@ Jon L: In Ordnung, ich werde den Wert korrigieren und versuchen, die Frequenz in beide Richtungen bereitzustellen, und sehen, ob das einen Unterschied macht.
@rzetterberg, sind Sie auch sicher, dass dies ../src/example.cder richtige Ort ist und dass Sie alle Ihre Objektdateien bei jedem Versuch bereinigt haben? Vielleicht lassen Sie das Makefile einfach auf sein eigenes Verzeichnis verweisen, nur um es vorerst zu vereinfachen. Will nur Unfälle ausschließen...
@ Jon L: Ja, ich bin mir sicher. Ich habe versucht, ein Programm hochzuladen, das nur alle Ports auf HIGH setzt, und eines, das die auf LOW setzt, um sicherzustellen, dass das Programm korrekt kompiliert und hochgeladen wird. Aber ich werde das noch einmal überprüfen, damit ich wirklich sicher bin.
@rzetterberg, hast du den Chip jemals mit aktiviertem CKDIV8 mit dem Standardwert von 1 MHz ausprobiert?

Ihr Programm sollte in der Lage sein _delay_ms(), , aber es gibt andere Möglichkeiten, eine Verzögerung zu erreichen.

_delay_ms()ruft nur auf _delay_us(), was delay_basic.h_delay_loop_1() aufruft . , wie der Name schon sagt, einfach Schleifen. Es verwendet 3 Anweisungen pro Schleife (für Vergleiche, Inkremente und Sprünge), und die Zeit, die für jede Anweisung benötigt wird, ist konstant und bekannt, sodass es für eine bestimmte Zeit verzögert werden kann, indem es einfach in einer Schleife ausgeführt wird._delay_loop_1()

Um jegliche Verwirrung über Ihre Programmstruktur und Ihren F_CPUSuchpfad zu beseitigen, könnten Sie die Funktion (ganz grob) in der Hauptfunktion wie folgt neu erstellen:

#include <stdint.h>  // Typedefs `unsigned long` or something to `uint16_t`,
                     // a 16-bit unsigned number
#include <avr/io.h>
int main(void) {
    uint16_t counter1, counter2;

    DDRB = 0xFF;
    PORTB = 0xFF;

    while(1) {
            PORTB ^= 0; // Use XOR to toggle
            for (counter1 = 0; counter1 < 10; counter1++) { 
                for (counter2 = 0; counter2 < 50000; counter2++) { /* Do nothing */ }
            }
    }

    return 0;
}

Die andere Methode besteht darin, die integrierten Timer-Peripheriegeräte zu verwenden, anstatt einen Timer mit einer Schleife zu implementieren. Sie können diese Peripheriegeräte auf viele Arten verwenden, aber ich werde die zwei einfachsten Möglichkeiten unten beschreiben. Erstens können Sie den Timer aus einer Schleife abfragen und etwas tun, wenn der Wert einen bestimmten Punkt erreicht. Zweitens können Sie den Timer einen Interrupt ausgeben lassen, wenn er einen bestimmten Punkt erreicht, und Ihren Code nichts tun lassen.

Um aus einer Schleife abzufragen, würden Sie etwa so vorgehen:

#include <avr/io.h> 

int main (void) 
{ 
   DDRB = 0xFF; // Set port as output 
   TCCR1B |= _BV(CS10); // Set up timer 

   while(1)    { 
      // Check timer value in if statement, true when count matches 1/20th of a second 
      if (TCNT1 >= 49999) 
      { 
         PORTB ^= 0xFF; // Toggle the port 

         TCNT1 = 0; // Reset timer value 
      } 
   } 
}

So geben Sie einen Interrupt aus:

#include <avr/io.h> 
#include <avr/interrupt.h> 

int main (void) 
{ 
   DDRB = 0xFF; // Set LED as output 
   TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode 
   TIMSK |= (1 << OCIE1A); // Enable CTC interrupt 
   sei(); //  Enable global interrupts 
   OCR1A   = 10000; // Set CTC compare value, about 50ms.
   TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 

   while(1) { 
       // Do nothing, the interrupt is taking care of it.
   } 
} 

ISR(TIMER1_COMPA_vect) 
{ 
   PORTB ^= 0xFF; // Toggle the LED 
}

Die obigen Codebeispiele wurden aus dem exzellenten Timer-Tutorial zu AVR Freaks übernommen , das auch auf Deans Website hier im PDF-Format verfügbar ist .

Darüber hinaus schaltet Ihr Code (und jedes der obigen Beispiele) den gesamten Port um. Ihre LED ist auf PB0; Sie können nur diesen Pin umschalten, indem Sie jede Zuweisung zu PORTB durch ersetzen

PORTB ^= _BV(PB0);

was PORTB nur mit vergleicht 0x0000 0001.

Danke für den Beitrag und die Alternativen. Ich werde die verschiedenen Methoden morgen ausprobieren :)
+1 für den Versuch, den Include-Pfad auszuschließen
@Jon - Zugegeben, ich habe immer noch Sachen eingefügt, also habe ich nur die CPU-Geschwindigkeitsdefinition eliminiert.
@ Kevin Vermeer: ​​Ich habe versucht, was Sie vorgeschlagen haben, siehe meine Bearbeitung der Frage. Warum ich alle Ports gleichzeitig eingeschaltet habe, liegt daran, dass ich die Möglichkeit ausschließen wollte, dass ich die LED auf den falschen Pin gesteckt hatte.