Versuche gerade mich in C zurechtzufinden.
Dabei kam nachfolgendes Programm heraus (s. u.)
Problem: Eine der Variablen wird konsequent abgeschossen,
d.h. auf 0 gesetzt.
Welche Variable es erwischt, hängt davon ab, wie diese
Variablen benannt werden, manchmal entscheidet auch die Reihenfolge,
in der die Variablen definiert werden.
Aber, heißt eine der Variablen 'Minuten', dann liegt die
Wahrscheinlichkeit bei 95%, daß es genau diese Variable erwischt.
Unabhängig davon, wie die anderen Variablen benannt werden.
Einige weitere Hinweise im Quelltext.
Frage an die Erfahrenen hier:
Was ist der Grund für diesen Effekt und wie kann man das abstellen?
Bei größeren Projekten sucht man sich bei sowas 'nen Wolf.
Und weil ich gerade dabei bin, ist es eigentlich normal,
daß bei Fließkomma-Arithmetik nur 6 Stellen Genauigkeit hinter
dem Komma erzielt wird? ( Math.h / libm.a / Variablen als Double dekl.)
GCC ist 4.1.2
AVR-LIBC ist 1.46
das Ganze aus dem letzten WinAVR-Paket und über AVRStudio bedient.
Alle Einstellungen auf Defaulteinstellungen,
Optimization steht auf -Os
Controller ist ein ATMega16 mit JTAG-Fuse aus und externem Quarz.
Die LCD-Routinen habe ich mal nicht mitgeliefert,
sind 1:1 aus dem AVR-GCC-Tutorial abkopiert.
1
// Testdatei
2
// CPU = ATMega16
3
4
#include<avr/io.h>
5
#include"lcd-routines.h"
6
#include<avr/pgmspace.h>
7
#include<stdio.h>
8
#include<avr/interrupt.h>
9
10
charStringImFlash[]PROGMEM="FRG7-UniCounter";// im "Flash"
Die Ansteuerung eines LCD Displays in der Interrupt Routine ist immer
eine schlechte Idee.
ISR's sollen kurz sein, vielleicht ein paar Flags setzen und dann die HW
neu aufsetzten.
So Funktionen wie lcd_data(), set_cursor() usw dauern sicher immer lang,
und haben intern irgendwelche Warteschleifen in denen sie auf das busy
Signal warten.
Dies gehört niemals in eine ISR.
Schreib dein Programm so um, dass die Ausgabe in der Hauptschleife
erfolgt, am besten nur dann, wenn sich irgendetwas ändert, und die
Timer-ISR macht nur das hochzählen.
Nunja, wie lange das Displayupdate dauert, ist hier relativ egal, da der
Interrupt nur 1* pro Sekunde aufgerufen wird. Displayupdate ist aber
auch schnell genug, daß der Interrupt 250 mal die Sekunde einlaufen
darf, ohne daß es zu einem Rückstau kommt. Davon ab, zumindest wenn ich
es in Assembler mache, habe ich die Wahl: Entweder ich packe die
LCD-Ansteuerung komplett in die Interrupts oder ich verzichte ganz auf
Interrupts. Weil, Interrupts und Displayupdate im Hauptprogramm führt
dazu, daß das Display langsam aber sicher mit Müll gefüllt wird.
Gruß
Jadeclaw.
( not logged in )
>Weil, Interrupts und Displayupdate im Hauptprogramm führt>dazu, daß das Display langsam aber sicher mit Müll gefüllt wird.
die Aussage verstehe ich nicht
aber schreib doch mal
volatile char Numout[3];
das wird dein Problem lösen
Walter
Für den Fall das Du ein Scope hast:
Vielleicht solltest Du zum prüfen,
ob der IRQ wirklich nur 1 mal die Sekunde
kommt, am Anfang er Interrutroutine einen
Portpin togglen. Das schaust Du Dir dann mit dem
Scope an.
wie Walter schon schrieb:
volatile char Numout[3];
in deiner print funktion willst du eine Dezimalzahl mit 2Stellen
ausgeben. Im C gibt es aber immer eine 0x00 als Stringendekennzeichnung.
Der braucht auch auch ein Byte Speicherplatz.
Jörg
@Gast: Der kommt wirklich nur 1 mal pro Sekunde, es ist Timer2 im Output
Compare Mode, da laß ich ihn OC2 (Pin21) toggeln.
Prescaler = 128, OCR2 = 255. Macht 32768 bei einem 32768Hz-Uhrenquarz.
Walter schrieb:
>> Weil, Interrupts und Displayupdate im Hauptprogramm führt>> dazu, daß das Display langsam aber sicher mit Müll gefüllt wird.> die Aussage verstehe ich nicht.
Das stellte sich so dar, daß während die normalen Daten ausgegeben
wurden, auf den freien Bereichen des Displays zufällige Zeichen
erscheinen, bis so langsam das ganze Display voll ist. Schalte ich
Interrupts ab, bleibt das Display sauber, verteile ich die
Displayupdates auf die Interrupts und fasse das Display nach der
erstmaligen Initialiserung im Hauptprogramm nicht mehr an, bleibt das
Display ebenfalls sauber. Getestet in Assembler auf ATMega8 und
ATTiny2313.
Walter schrieb:
> aber schreib doch mal> volatile char Numout[3];> das wird dein Problem lösen
jL schrieb:
> wie Walter schon schrieb:> volatile char Numout[3];> in deiner print funktion willst du eine Dezimalzahl mit 2Stellen> ausgeben. Im C gibt es aber immer eine 0x00 als Stringendekennzeichnung.> Der braucht auch auch ein Byte Speicherplatz.
Je mehr ich darüber nachdenke, um so logischer erscheint mir das.
Mit anderen Worten: Der Compiler knallt mir das Terminierungsnullbyte in
eine andere Variable rein, weil ich das Numout zu kurz gemacht habe.
Gut, probiere ich das mal heute Abend aus, bin gerade außer Haus.
Bleibt noch die Frage nach der schlechten Genauigkeit im
Fließkommabereich. Erwarte ich da zuviel?
Gruß
Jadeclaw.
Jadeclaw wrote:
>> wie Walter schon schrieb:>> volatile char Numout[3];>> in deiner print funktion willst du eine Dezimalzahl mit 2Stellen>> ausgeben. Im C gibt es aber immer eine 0x00 als Stringendekennzeichnung.>> Der braucht auch auch ein Byte Speicherplatz.>> Je mehr ich darüber nachdenke, um so logischer erscheint mir das.> Mit anderen Worten: Der Compiler knallt mir das Terminierungsnullbyte in> eine andere Variable rein, weil ich das Numout zu kurz gemacht habe.> Gut, probiere ich das mal heute Abend aus, bin gerade außer Haus.
Was du aus dieser Erfahrung mitnehmen solltest:
* Es ist meistens keine gute Idee, Character Arrays die Strings
aufnehmen sollen, zu klein zu dimensionieren. Lieber ein
paar Bytes mehr reservieren.
Grund: Irgendwann dreht man mal am Format String (in diesem
Fall) und wenn man dann keine Reserven mehr hat, dann knallts
ganz gewaltig.
* Wenn irgendwie möglich, verwende keine Funktionen, die einen String
erzeugen und in deren Argumentliste es keinen Parameter gibt, der
die Größe des empfangenden Character Arrays angibt. Wenn es eine
derartige Funktion gibt, dann sollte die Version ohne diesen Parameter
tabu sein.
Im gegenständlichen Fall: snprintf verwenden!
1
snprintf(Numout,sizeof(Numout),"%02d",Stunde);
>> Bleibt noch die Frage nach der schlechten Genauigkeit im> Fließkommabereich. Erwarte ich da zuviel?
Ja. Aus 4 Byte ist nunmal nicht mehr herauszuholen.
Jadeclaw wrote:
> Das stellte sich so dar, daß während die normalen Daten ausgegeben> wurden, auf den freien Bereichen des Displays zufällige Zeichen> erscheinen, bis so langsam das ganze Display voll ist.
Ja, woher soll denn das arme Display wissen, ob die Daten nun vom
Interrupt oder Main kommen.
Das Main schreibt was, nun haut der Interrupt dazwischen und schreibt
auch was. Im günstigsten Fall hast du nun den halben Text des Main, dann
den vom Interrupt und dann den Rest des Main. In der Regel hast Du aber
Zeichensalat.
Ausgaben dürfen entweder nur vom Main oder nur vom Interrupt erfolgen.
Oder das Main sperrt die Interrupts für die komplette Dauer der Ausgabe.
Das gleiche gilt natürlich auch für alle anderen Arten Interfaces (UART,
SPI, I2C).
Und auch Timerzugriffe beinhalten Stolperfallen, wenn der Interrupt
nicht das temporäre High-Register sichert.
Wenn es sich nicht vermeiden läßt, eine Peripherie im Interrupt und Main
gleichzeitig zu benutzen, immer dran denken, daß ein Interrupt das Main
an jeder beliebigen Stelle unterbrechen kann.
Peter
Peter Dannegger schrieb:
> Ausgaben dürfen entweder nur vom Main oder nur vom Interrupt erfolgen.
Genau das ist der Fall.
Ausgabe erfolgt NUR über den Main, die Interrupts fassen das Display
nicht an, nicht mal irgendeinen noch freien Pin am Displayport, dennoch
füllt sich das Display mit Müll. Interrupts aus = alles Ok.
Ausgabe NUR über Interrupt, auch über verschiedene Vektoren = alles ok.
Gruß
Jadeclaw.
( not logged in )
Jadeclaw wrote:
> Peter Dannegger schrieb:>> Ausgaben dürfen entweder nur vom Main oder nur vom Interrupt erfolgen.> Genau das ist der Fall.> Ausgabe erfolgt NUR über den Main, die Interrupts fassen das Display> nicht an, nicht mal irgendeinen noch freien Pin am Displayport, dennoch> füllt sich das Display mit Müll.
Der LCD Funktion ist es sch...egal, wie gross du das Array
definiert hast, die interessiert sich ganz einfach nicht dafür.
Diese Funktion bekommt eine Startadresse im Speicher. Von dort
holt sie Byte für Byte und malt das entsprechende Zeichen auf
das Display. Solange bis das nächste Byte im Speicher den Inhalt
0 hat.
Du kannst dein char Array definieren wie du lustig bist,
interessiert die Ausgabefunktion nicht die Bohne. Die
hört erst dann mit der Ausgabe von Zeichen aus, bis sie ein
0 Byte im Speicher vorfindet. Wenn da kein 0 Zeichen ist,
dann gibt diese Funktion auch 2 Gigabyte aus (ok, auf einem
AVR nicht, weil der nicht genug Speicher hat :-), bis sie
dann das erste mal auf ein 0 im Speicher trifft.
Wie sich dein Müll auf dem LCD konkret aufbaut, hängt
nicht unwesentlich von der zeitlichen Abfolge der
Voorgänge ab. Wenn die lcd_ausgabe losläuft, noch
ehe der sprintf die Chance hatte, das 0 Byte zu schreiben,
tja, dann passiert sowas schon mal.
Was da konkret passiert, kann man nur vermuten wenn man
das konkrete Programm sieht. Aber irgendwo gibt es mit
ziemlicher Sicherheit eine Race-Kondition, so dass
die Ausgabefunktion das abschliessende 0 nicht sieht,
weil sie zu spät generiert wird.
In deinem fall kommt natürlich noch erschwerend dazu, dass
du zumindest im Original einen Buffer-Overflow hattest.
sprintf( NumOut, "%2d", Stunden );
schreibt nun mal 3 Zeichen und überschreibt damit eine Variable.
schlägt jetzt der Interrupt zwischen dem sprintf und dem
nachfolgenden lcd_string zu, so verändert die InterruptRoutine
diese Variable und zerstört dadurch das den String abchliessende
0 Byte. Woraufhin lcd_string auf der Suche nach dem 0 Byte
den halben Speicher ausdumpt.
Wenn das Display wirklich von unterschiedlichen Programmteilen, die
teilweise im Interrupt laufen, mit Daten versorgt werden muss, dann
nutze ich einen Buffer.
In einem Timer-Interrupt wird dieser Buffer regelmäßig zum Display
geschickt, die einzelnen Programmteile greifen dann nur auf den globalen
Buffer zu. Dadurch kann man Datensalat verhindern und man verhindert
Delays in den Interrupts.
@Marvin: Ich hatte sowieso vor, Programm und die ganze Ausgabemimik
voneinander zu trennen, damit das Ganze leichter wartbar wird und ein
Displaytypwechsel schmerzlos vonstatten geht. Ich bin mit mir noch nicht
ganz einig, ob es beim LC-Display bleibt. Das Ganze wird ein größeres
Gerät, welches zum einen ein brauchbares Meßgerät abgeben soll, zum
anderen einen vorhandenen KW-Empfänger um diverse Dinge erweitern soll
(Frequenzanzeige, RTTY-Terminal, Beacon-Timer, Audiofiltersteuerung).
Die Idee mit dem Displaybuffer erscheint mir auch am sinnvollsten, da
ich dann am Rest nichts mehr umbauen muß, wenn ich das Display wechsle.
@Karl heinz Buchegger: Die Sache mit dem Müll im Display hat nichts mit
dem GCC-Problem zu tun, sondern war eine frühere Beobachtung, bei der
ich mir auch einen Wolf gesucht habe und habe ich als Antwort auf die
Anmerkung von Klaus Falser von 8:25 Uhr erwähnt.
Gruß
Jadeclaw.
( not logged in )
@ Marvin M. (Gast)
>Wenn das Display wirklich von unterschiedlichen Programmteilen, die>teilweise im Interrupt laufen, mit Daten versorgt werden muss, dann>nutze ich einen Buffer.
GENAU!
Interrupt
MFG
Falk
>Ausgabe erfolgt NUR über den Main, die Interrupts fassen das Display>nicht an, nicht mal irgendeinen noch freien Pin am Displayport, dennoch>füllt sich das Display mit Müll. Interrupts aus = alles Ok.
in deinem Programm erfolgt die Ausgabe aber im Interrupt!
Walter schrieb:
> aber schreib doch mal> volatile char Numout[3];> das wird dein Problem lösen
jL schrieb:
> wie Walter schon schrieb:> volatile char Numout[3];> in deiner print funktion willst du eine Dezimalzahl mit 2Stellen> ausgeben. Im C gibt es aber immer eine 0x00 als Stringendekennzeichnung.> Der braucht auch auch ein Byte Speicherplatz.
Jupp, das war es auch. >in die Tischplatte beiß<
Da hätte ich auch selbst drauf kommen müssen.
Karl heinz Buchegger schrieb:
>> Bleibt noch die Frage nach der schlechten Genauigkeit im>> Fließkommabereich. Erwarte ich da zuviel?>Ja. Aus 4 Byte ist nunmal nicht mehr herauszuholen.
Nur 4 Byte für double? Dann brauche ich mich ja nicht zu wundern.
Dann ist hier wohl Handarbeit angesagt.
Walter (Gast) schrieb:
> in deinem Programm erfolgt die Ausgabe aber im Interrupt!
Der Zumüllungseffekt hat mit diesem Programm nichts zu tun.
Das war eine andere Sache.
Gruß
Jadeclaw.
Jadeclaw Dinosaur wrote:
>>Ja. Aus 4 Byte ist nunmal nicht mehr herauszuholen.> Nur 4 Byte für double? Dann brauche ich mich ja nicht zu wundern.> Dann ist hier wohl Handarbeit angesagt.
Jep, sizeof(float) == sizeof(double) == 4 beim avr-gcc.