Ich habe ein sehr seltsames Problem einer möglichen Speicherbeschädigung bei der Verwendung globaler Variablen und eines Timer-Überlauf-Interrupts auf dem ATTiny10 (mit avr-gcc 4.9.2). Ich kann keinen Sinn daraus machen, habe es aber geschafft, es auf die Reproduktion mit einem sehr einfachen Programm einzugrenzen:
#include <avr/io.h>
/* Timer overflow counter */
volatile unsigned int ovrf = 0;
/* Some global variables used in main() */
/* (MOVING THESE INTO main() FIXES THE ISSUE) */
unsigned long foo;
unsigned int bar;
int main(void) {
/* Fast PWM 8 Bit Mode */
TCCR0A |= _BV(WGM00);
TCCR0B |= _BV(WGM02);
/* Enable Timer Overflow Interrupt */
TIMSK0 |= _BV(TOIE0);
/* /8 prescaler */
TCCR0B |= _BV(CS01); //
/* PB0 as output */
DDRB |= _BV(PB0);
/* Enable interrupts */
sei();
for (;;) {
/* Some random code that uses the global vars */
/* (REMOVING THIS FIXES THE ISSUE) */
if (foo > bar) {
foo = 0;
}
}
}
ISR(TIM0_OVF_vect) {
ovrf++;
/* Toggle LED (about once per second) */
if ((ovrf / 500) % 2 == 0) {
PORTB &= ~(_BV(PB0));
} else {
PORTB |= _BV(PB0);
}
}
Alles, was es tut, ist Folgendes:
ovrf
) erhöht und eine LED basierend auf dem Wert dieses Zählers ein- und ausschaltet.Ich würde erwarten, dass die LED regelmäßig blinkt, was beweist, dass der Interrupt funktioniert und der Zähler korrekt inkrementiert wird. Aber es schaltet sich nicht ein -- oder wenn das Programm leicht modifiziert wird, zB mehr Code oder main()
mehr Variablen hinzugefügt werden -- blinkt es unregelmäßig oder mit einer sehr schnellen Rate. Daraus gehe ich nach vielen Tests und dem Versuch, jede andere Erklärung auszuschließen, davon aus, dass der Zähler ( ovrf
) irgendwie von der Hauptschleife beschädigt wird.
Ich habe mehrere Änderungen gefunden, die das Problem verschwinden lassen können:
foo
und bar
) inmain()
foo
inint
-O0
(der Standardwert war -Os
), aber das macht den Code ~1,6x größer.Aber ich kann immer noch keine Erklärung für die tatsächliche Ursache sehen. Übersehe ich etwas Offensichtliches völlig ...? Mir gehen die Ideen aus und mir fällt nichts anderes ein als ein Compiler-Fehler, aber das ist sehr unwahrscheinlich, da dieses Beispiel so einfach ist.
AKTUALISIEREN
Basierend auf dem Vorschlag von @MarkU habe ich versucht, mit verschiedenen Optimierungseinstellungen zu spielen, um die genaue Option zu finden, die das Problem verursachen könnte:
Auch probiert -O1
, hilft aber auch nicht
Ich fand, dass das -Os -fno-toplevel-reorder
auch das Problem behebt! -- Ich vermute jedoch, dass dies nur ein zufälliger Effekt sein könnte:
In meinem ursprünglichen Programm (sehr ähnlich dem obigen vereinfachten Beispiel), in dem ich das Problem gefunden habe, hilft keines der oben genannten Dinge (nicht einmal -O0
). Dort habe ich eine weitere globale Variable (a bool
), und das einzige, was zu helfen scheint, ist, eine anfängliche Zuweisung (z. B. "bool ledOn = true;" --> "bool ledOn;"
) zu entfernen.
Es hat also definitiv etwas damit zu tun, wie Variablen zugewiesen werden, aber nicht nur mit ihrer Gesamtgröße. (Es gibt keine weiteren Abhängigkeiten, keine Funktionsaufrufe etc.)
AKTUALISIERUNG 2
Dem Rat von @Curd folgend, habe ich auch versucht, ovrf / 500
durch zu ersetzen ovrf >> 9
(ungefähr dasselbe, das genaue Timing interessiert mich hier sowieso nicht). Dadurch wurde der Code um 74 Bytes (!) reduziert – und dies behebt auch das Problem!
Ich habe mir den disassemblierten Code für die ISR angesehen: Diese Änderung reduziert die Anzahl der Bytes pushed
am Anfang von 13 auf 7, was erklären könnte, warum es hilft!
(Dies ovrf / 500
sollte nur ein schneller und einfacher Test sein, um zu überprüfen, ob die ISR funktioniert, aber ich wusste nicht, dass es in der tatsächlichen Implementierung überhaupt nicht so einfach ist! In meinem ursprünglichen Programm gibt es keine Division, ich behalte eine ungefähre Millis zählen, indem Sie einfach ovrf
mit 2 multiplizieren.)
Ich habe auch den zerlegten Code für -Os
("schlecht") und -Os -fno-toplevel-reorder
("gut") verglichen, aber abgesehen davon, dass der Code am Anfang neu geordnet wird, main()
scheinen sowohl der Inhalt von als auch die ISR gleich zu sein (gleiche Anzahl von Pop/Pushes usw.)
--
Anscheinend kann ich das Problem in diesem konkreten Beispiel mit einem der oben genannten Workarounds beheben, aber ich fühle mich immer noch unwohl, wenn ich die eigentliche Ursache nicht wirklich verstehe und nicht weiß, wie ich dies im allgemeinen Fall vermeiden kann. Und ich weiß nicht genug über Assembler, um den generierten Code zu analysieren.
Vielleicht sollte ich auch einige dieser Fragen stellen:
Ist diese Art von Trial-and-Error-Prozess "normal", wenn C für ATTiny10 verwendet wird? (Ich meine: nicht annähernd genug Ressourcen und / oder unzureichende Compiler-Unterstützung, um dies zuverlässig zu machen - erwarten Sie also nicht, dass es funktioniert, und kehren Sie einfach zur Assembly zurück, wenn dies nicht der Fall ist?)
Gibt es etwas, das generell vermieden werden sollte (z. B. keine Verwendung globaler Variablen oder Optimierung)?
AKTUALISIERUNG 3
Vielen Dank für all die Kommentare und Antworten, sie enthalten viele nützliche Vorschläge, die es wert sind, alle für alle zu überprüfen, die auf ein ähnliches Problem stoßen!
Ich hatte noch ein weiteres "Mysterium" mit meinem ursprünglichen Programm, bei dem das Ersetzen von a bool ledOn = true;
durch bool ledOn;
die einzige Lösung war.
Jetzt, da ich mehr verstehe, habe ich mir die generierte Assembly und die Speichernutzung noch einmal angesehen: Es stellt sich heraus, dass die Initialisierung den Compiler dazu bringt, ein .data
Segment zu produzieren und ein weiteres Byte im Speicher zugewiesen wird, was knapp über der Grenze liegt, um eine Kollision mit zu verursachen Stapel. Obwohl (glaube ich) am Ende ein Register für diese Variable verwendet wird, genau wie im Fall "keine explizite Initialisierung", sollte die zusätzliche Zuordnung nicht erforderlich sein. Ich denke, der Compiler hat einfach keine Optimierung für diesen Extremfall mit so wenig RAM.
Mit nur 32 Byte Speicher (wie von MarkU in einem Kommentar erwähnt) ist der Speicher auf dem ATtiny10 unglaublich knapp. Der AVR-GCC-Compiler bietet keine Tools zur Stack-Überprüfung und generiert gerne Code, der den Stack überläuft. Hier ist zum Beispiel, was es für den Prolog zu Ihrer ISR generiert hat:
000000ba <__vector_4>:
ba: 1f 93 push r17
bc: 0f 93 push r16
be: 0f b7 in r16, 0x3f ; 63
c0: 0f 93 push r16
c2: 10 e0 ldi r17, 0x00 ; 0
c4: 4f 93 push r20
c6: 5f 93 push r21
c8: 6f 93 push r22
ca: 7f 93 push r23
cc: 8f 93 push r24
ce: 9f 93 push r25
d0: af 93 push r26
d2: bf 93 push r27
d4: ef 93 push r30
d6: ff 93 push r31
Ich zähle 13 push
es drin. Dadurch wird der Stapel allein auf fast die Hälfte des Speichers Ihres Geräts erweitert. In Kombination mit einem anderen rcall
im Hauptteil der ISR sowie ein paar push
es und rcall
s im Prolog von main
wird der ISR-Stack mit dem Speicher kollidieren, der zum Speichern Ihrer globalen Variablen verwendet wird, und sie mit unerwarteten Daten überschreiben.
Der ATtiny10 ist kein gutes Ziel für einen C-Compiler. Wenn Ihre Anwendung einen etwas größeren Mikrocontroller unterstützen kann, ist möglicherweise ein Upgrade auf die tiny25/45/85-Familie gerechtfertigt. Ansonsten würde ich empfehlen, dieses Gerät mit Montage anzustreben.
ISR_NAKED
.main()
.volatile unsigned int ovrf = 0;
unsigned long foo;
unsigned int bar;
+8 Byte RAM
int main(void) {
+2 Bytes oder RAM, Attribut verwenden ((OS_main))
ISR(TIM0_OVF_vect) {
Wenn Sie nichts angeben, wird ein Stackframe für einen Funktionsaufruf verschoben. Das werden ungefähr 14 Bytes sein, wenn ich mich richtig an meine ATTiny10-Kämpfe erinnere. Die Rücksendeadresse (2 Bytes) und einige Register.
Mit nur 32 Bytes können Sie einen vollständigen Funktionsaufruf durchführen. Wenn Sie mehr wollen, müssen Sie __attribute__((naked))
Assembler verwenden und schreiben.
if ((ovrf / 500) % 2 == 0) {
Dies ruft wahrscheinlich Bibliotheksfunktionen auf, deren Stackframe Sie nicht speichern können.
Und es gibt auch nur 1024 Byte Programmspeicher. Dies ist sehr klein für C, ~100 Zeilen klein.
Ignacio Vazquez-Abrams
Ahorn
MarkU
-O0
als auch-Os
auf die Einstellungen - ich vermute, dass eines der Optimierungsflags (wie-fcaller-saves
?) den Code verstopft, der die Status- oder Indexregister innerhalb der Interrupt-Serviceroutine speichert/wiederherstellt. Siehe Atmel ATtiny10 Datenblatt Abschnitt 5.8 Reset und Interrupt Handling. Siehe auch Optimierungsoptionen von gcc-4.9.2MarkU
pdenes
avr-objdump -D
Ich fürchte, ich bin mir nicht sicher, wonach ich suchen soll, ich weiß nicht viel über Assembler ... Ich habe mir die Ausgabe von und-S
für die.hex
und die.elf
Dateien angesehen , und ich kann Unterschiede erkennen, einige machen Sinn, aber ich kann dem Geschehen nicht wirklich folgen. (Ich wollte nicht die gesamte Ausgabe posten, mache das aber gerne - oder einen Teil davon, wenn das hilft!)pdenes
long
s ändere, ist das Problem immer noch da ... also ist es nicht das, aber wahrscheinlich etwas über den Zugriff auf diese Variablen in der Hauptschleife. (Ich bin jedoch verwirrt, da ich dort definitiv nichts anschreiben werde ...)pdenes
pdenes
Quark
ovrf / 500
? Der ATtiny hat nicht einmal eine Anweisung zum Multiplizieren; ganz zu schweigen von der Teilung! Dieser Test kann deutlich mikrocontrollerfreundlicher implementiert werden. Möglicherweise dauert es mehr Zyklen, um die ISR zu verarbeiten, als Sie das Timer-Intervall konfiguriert haben. Übrigens: Ich sehe das Timer-Intervall nicht initialisiert!pdenes