Forum: Mikrocontroller und Digitale Elektronik C: Variablenwert verändert (aus heiterem Himmel) :-S


von Hendrik M. (do1hrm)


Lesenswert?

Hallo allesamt!

Ich bin neu hier im Forum und habe auch gerade letzte Woche frisch mit 
der Microcontroller-Programmierung angefangen. Allerdings stoße ich bei 
meinem Microcontroller nun zum zweiten Mal auf ein seltsames Verhalten, 
das für mich jeglicher Logik zu widersprechen scheint.

Zu meinem aktuellen Problem (wesentliche Programmteile unten 
abgebildet):
Ich habe erstmal als kleinen Versuch nen ganz simples (und angesichts 
von _delay_ms() auch zeitlich fehlerhaften) Countdown-Script in C 
geschrieben.
Unten in der main-Methode wird erstmal die int-Variable duration mit 
1234 definiert und im weiteren Verlauf auch nicht mehr von mir 
angepasst. Es erfolgt ein Aufruf der Funktion startCountdown(), 
übergeben wird hierbei auch duration mit dem Wert 1234. Zum Debuggen 
sind nun zwei Ausgaben einprogrammiert, welche den Wert von duration 
anzeigen sollen. Die erste kommt direkt am Ende der Funktion 
startCountdown, hierbei wird als Wert noch wie gewünscht 1234 angezeigt. 
Danach müsste er ja (direkt nach dem delay zur besseren ablesbarkeit vom 
display) zurück in die main()-funktion springen und dort nun gleich 
erneut den Wert von duration zurückliefern. Das tut er auch, allerdings 
wird nun plötzlich der Wert 1024 angezeigt.

Diese Befehle folgen ja im Grunde direkt aufeinander und es wird 
nirgendwo diese Variable verändert... Wie kann das sein?

Vielen Dank für alle Ratschläge, ich bin allmählich wirklich ratlos...
1
void startCountdown(int duration) {
2
   char lcd_output[4];
3
   for(int i=duration;i>0;i--) {
4
      led_printNumber(i);
5
      sprintf(&lcd_output, "%04d", i);
6
      lcd_gotoxy(0,1);
7
      lcd_puts(lcd_output);
8
      _delay_ms(1000);
9
   }
10
   led_printNumber(duration); _delay_ms(2000);  // Output: 1234
11
}
12
13
[...]
14
15
int main (void) {
16
   init();
17
   int duration = 1234;
18
   lcd_puts("Countdown:\0");
19
   while(1) {
20
      duration = setNumber(duration);
21
      startCountdown(duration);
22
      led_printNumber(duration); _delay_ms(2000); // Output: 1024
23
      alarm();
24
   }
25
   return 0;
26
}

von Bernd (Gast)


Lesenswert?

Hendrik Motza schrieb:
> Unten in der main-Methode wird erstmal die int-Variable duration mit
> 1234 definiert und im weiteren Verlauf auch nicht mehr von mir
> angepasst

Hendrik Motza schrieb:
> duration = setNumber(duration);

und was ist das?

von g457 (Gast)


Lesenswert?

ich tippe auf Puffer- oder Speicherüberlauf. Wundert mich dass das..
1
   char lcd_output[4];
2
[..]
3
   sprintf(&lcd_output, "%04d", i);
..überhaupt geht.

HTH

von Marvin M. (Gast)


Lesenswert?

....
char lcd_output[4];
....
sprintf(&lcd_output, "%04d", i);
....

Eine Zeichenkette wird immer durch eine 0x00 abgeschlossen. Der 
sprintf() schreibt also 4 Zeichen + 0x00 (= 5 Bytes) in ein Array, das 
nur 4 Zeichen groß ist.
Wenn man nun noch wüsste, wie led_printNumber() und setNumber() 
aussehen... Siehe auch Vorposter...

von Hendrik M. (do1hrm)


Lesenswert?

@Bernd: In dem Modus lässt sich der vordefinierte Wert anpassen, wie 
gesagt nehme ich jedoch keine manuelle Änderung vor. Und wie gesagt 
stimmt der Wert ja noch bis einschließlich dem Ende der 
startCountdown-Funktion, erst direkt danach tritt die Veränderung 
offenbar in Kraft.


@g457: Ja ich bin leider auch noch neu in der Programmiersprache, habe 
gerade mal 1200 Seiten Theorie zu C/C++ durchgelesen und nun erst 
angefangen die Sprache praktisch zu verwenden. Ich dachte mir es müsste 
eine Funktion geben, mit der die Konvertierung von int in String ganz 
leicht möglich sein dürfte. Das war eine Lösung, die google mir erbracht 
hat und es funktioniert offenbar.
Da die Zahl ohnehin nur 4-stellig sein darf (4 LED-Anzeigen...) sollte 
lcd_output[4] eigentlich genug Speicher bereitstellen! :-D

von B e r n d W. (smiley46)


Lesenswert?

>"%04d"
Das sind 5 Bytes, die abschließende Null wurde nicht berücksichtigt.

Diese Null überschreibt wahrscheinlich die Variable Duration, denn wenn 
man bei 1234 = 0x04D2 das Low-Byte überschreibt, kommt 0x0400, also 1024 
raus.

Also auf lcd_output[5] ändern, dann könnte es funktionieren.

von Hendrik M. (do1hrm)


Lesenswert?

Perfekt, ich habe das Array nun auf 5 Chars erweitert und es 
funktioniert nun tatsächlich. Danke!

Ich dachte durch die Definition als Array würde sowas wie ein Nullbyte 
automatisch vorhanden sein, aber wie gewissermaßen erwiesen wurde, muss 
dies berücksichtigt werden! ;-)

von DirkB (Gast)


Lesenswert?

Hendrik Motza schrieb:
> Ich dachte durch die Definition als Array würde sowas wie ein Nullbyte
> automatisch vorhanden sein,

Dem Compiler ist es doch egal was du in dein Array packst. Wenn es 
Zahlen sind, brauchst du die '\0' ja nicht.

Aber bei Stringliteralen wie "Countdown:" wird die '\0' automatisch 
angehängt. Die brauchst du dann nicht per Hand vorgeben.

Was auch geht, ist die Bestimmung von der Länge des Arrays dem Compiler 
zu überlassen.
1
char lcd_output[] = "----";
Da reserviert der Compiler auch 5 chars.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Hendrik Motza schrieb:

>       sprintf(&lcd_output, "%04d", i);

Das "&" muss auch noch weg. lcd_output selbst ist bereits ein Array, 
verhält sich also hier wie ein Pointer. Entweder muss es heißen:

        sprintf(lcd_output, "%04d", i);

oder

        sprintf(&lcd_output[0], "%04d", i);

Aber das da ganz oben ist doppelt gemoppelt.

von Hendrik M. (do1hrm)


Lesenswert?

Klingt logisch, wird sofort verbessert Chef! =)

von Karl H. (kbuchegg)


Lesenswert?

Und mach dir bei solchen Sachen die Arrays ein wenig größer. Du hast das 
sowieso als lokales Array und du musst auch noch nicht Speicher sparen.

Das Problem: Derartige Dinge verändern sich gerne mal ein wenig. 
Irgendwann machst du den Text ein wenig größer, so dass das Array wieder 
zu klein ist, weil du natürlich vergessen hast es anzupassen und was 
dann alles passieren kann hast du ja jetzt gesehen.

von Hendrik M. (do1hrm)


Lesenswert?

Das sollte weniger das Problem sein. Dieses Board verfügt nur über 4 
LED-Anzeigen. Und Programmcodes, die möglicherweise mal auf anderen 
Boards verwendet werden sollen (wie led- und tastensteuerungen), habe 
ich ohnehin schon in ganz allgemeinen funktionssammlungen implementiert, 
die direkt auf andere systeme übertragbar sind. =)

von Hendrik M. (do1hrm)


Angehängte Dateien:

Lesenswert?

So, wieder ein kleines Problem, welches sich mir nicht erschließt... =(

Ich habe den Code wieder so weit wie möglich vereinfacht und mal 
angehängt. Im Grunde gibt es hier einfach die Funktionen home() und 
menu(), welche sich auf Tastendruck gegenseitig aufrufen sollen. Durch 
die LCD-Ausgabe lässt sich auch leicht erkennen, in welcher Funktion er 
gerade steckt.

Unglücklicherweise funktioniert die Umschaltung auf Knopfdruck nicht 
ganz wie geplant. Es gibt jeweils in den beiden Funktionen einen Delay 
von 1us (derzeit auskommentiert). Wenn ich einen der Delays wieder 
aktiviere, dann funktioniert es auf einmal zu der anderen Funktion auf 
Knopfdruck zu wechseln (zurück geht es nur, wenn auch in der zweiten 
Funktion das Delay aktiviert ist).

Alternativ ist in der Funktion button_pushed() auch noch ein Delay 
enthalten, der ebenfalls dafür sorgt, dass diese Sprünge auf Knopfdruck 
alle beide funktionieren. Allerdings läuft es an der Stelle nur mit 
einem Delay von mindestens 17ms.

Ich kann mir leider beim besten Willen nicht erklären, wo das Problem 
liegt und warum es durch diese Delays auf einmal behoben scheint. 
Vielleicht könnt ihr mich ja erleuchten! =)

von Uwe B. (boerge) Benutzerseite


Lesenswert?

Moin,

denke mal über das Wort "Tastenentprellung" nach.

Uwe

von Hendrik M. (do1hrm)


Lesenswert?

Ich dachte das hätte ich verwendet! ;-)
1
void button_update() {
2
   if(button_isPushed()) {
3
     button_count++;
4
   }
5
   else {
6
     if(button_count>5) pushed = true;
7
     button_count = 0;
8
   }    
9
}


Zumal die Tastenentprellung ja auch nur dafür sorgen soll, dass ein 
Tastendruck als einer und nicht viele Klicks wahrgenommen wird. Mein 
Problem besteht aber leider mehr darin, dass offenbar gar kein 
Tastendruck akzeptiert wird (ohne delays oder andere verzögerungen)...

von Karl H. (kbuchegg)


Lesenswert?

Hendrik Motza schrieb:
> Ich dachte das hätte ich verwendet! ;-)

Das geht alles viel zu schnell!
Tastenprellen spielt sich im Millisekunden Bereich ab. Für deinen µC ist 
eine Millisekunde eine viertel Ewigkeit.

Entprellung

Hol dir die letzte Lösung von dort, die mit dem Timer, und du bist das 
Problem Prellen ein für alle mal los.


AUsserdem geht das so nicht, dass du von einer Funktion die andere 
aufrufst und dann wieder zurück aufrufst. Du stapelst hier 
Funktionsaufrufe ineinander, ohne das ganze irgendwann aufzulösen.

von Karl H. (kbuchegg)


Lesenswert?

?
>    TIMSK |= (1<<0) | (1<<2);

0 ist der Overflow vom Timer 0. Aber was macht das Bit 2?

Schreib das doch so

  TIMSK |= (1 << TOIE0);

Dann sieht man auf einen Blick dass du den
1
   _T_imer _O_verflow _I_nterrupt _E_nable vom Timer _0_  (eben den TOIE0)
freigibst.

Und: man gibt niemals, unter keinen Umständen, einen Interrupt frei, für 
den man keinen Handler hat.

von Karl H. (kbuchegg)


Lesenswert?

Weiters dürfte da ein Problem entstehen:
Du fragst den Button indirekt per Polling ab (also innerhalb der 
Schleifen), fragst aber auch mit dem Timer die Tasten ab.
Entscheide dich für EINEN Mechanismus, der sich um die Tasten kümmert!

von Hendrik M. (do1hrm)


Lesenswert?

Über die Timer-Interrupts wird eigentlich regelmäßig geprüft, ob der 
Knopf gedrückt ist und hier ist ebenfalls die Entprellung eingebunden. 
Das scheint mit dem Wert auch ganz gut zu funktionieren, die Tasten habe 
ich durchaus schon erfolgreich verwenden können (nur hier kam es nun 
erstmalig zu den Problemen, die beispielsweise erst durch so einen Delay 
behoben werden).


>Du fragst den Button indirekt per Polling ab (also innerhalb der
>Schleifen), fragst aber auch mit dem Timer die Tasten ab.
>Entscheide dich für EINEN Mechanismus, der sich um die Tasten kümmert!

Wenn durch die Timer-Interrupts ein Tastendruck (entprellt) festgestellt 
wird, wird die eine status-Variable auf true gesetzt, dadurch wird erst 
ein entprellt getätigter Tastendruck anerkannt. Die Schleifen in meinen 
eigentlichen Funktionen fragen dann (über die button_pushed()-Funktion) 
nur noch diesen status ab.
Dieser Mechanismus erschien mir bisher als relativ günstig, da es 
insgesamt 4 Knöpfe gibt, die sowohl kurz als auch lang gedrückt werden 
können. Diese Nechanismen werden häufig in unterschiedlichen Funktionen 
benötigt, zudem ist die Funktionssammlung für Buttons auch so angelegt, 
dass es sich relativ leicht in andere Boards einbauen lässt. Ich hab 
halt alle Codes nur derart weit reduziert, dass man schneller den Code 
überblicken kann und gleich deutlich die Problemstellen erkennen kann.


>AUsserdem geht das so nicht, dass du von einer Funktion die andere
>aufrufst und dann wieder zurück aufrufst. Du stapelst hier
>Funktionsaufrufe ineinander, ohne das ganze irgendwann aufzulösen.

Dessen bin ich mir bewusst, hierbei handelt es sich auch nur um die 
stark vereinfachte Version meines Codes. Eigentlich gibt es sonst eine 
globale Variable (genauer gesagt einen Funktions-Zeiger). Die Funktion, 
die nun eine andere Funktion aufrufen möchte, setzt den Zeiger der 
globalen Variable also auf die Zielfunktion und springt dann via 
return-Anweisung in die main() zurück. Dort wird dann über eine Schleife 
alles wesentliche resetet (Display und Co) und es erfolgt der Aufruf der 
Zielfunktion.
Ich dachte dieser direkte gegenseitige Aufruf wäre an dieser Stelle 
schneller nachvollziehbar! =)

von Karl H. (kbuchegg)


Lesenswert?

Hendrik Motza schrieb:

>>Du fragst den Button indirekt per Polling ab (also innerhalb der
>>Schleifen), fragst aber auch mit dem Timer die Tasten ab.
>>Entscheide dich für EINEN Mechanismus, der sich um die Tasten kümmert!
>
> Wenn durch die Timer-Interrupts ein Tastendruck (entprellt) festgestellt
> wird, wird die eine status-Variable auf true gesetzt, dadurch wird erst
> ein entprellt getätigter Tastendruck anerkannt. Die Schleifen in meinen
> eigentlichen Funktionen fragen dann (über die button_pushed()-Funktion)
> nur noch diesen status ab.

Du hast recht.
Ich bin schon ganz wuselig, welche Funktion von wem an welcher Stelle 
aufgerufen wird. Ständig zwischen 2 Files wechseln und dann heißen alle 
Funktion ähnlich :-)

OK.
Dein button_update() müsste IMHO so aussehen
1
void button_update() {
2
   if(button_isPushed()) {
3
     if( button_count > 5 )
4
       pushed = true;
5
     else
6
       button_count++;
7
   }
8
   else {
9
     button_count = 0;
10
   }    
11
}

von Hendrik M. (do1hrm)


Lesenswert?

Karl Heinz Buchegger schrieb:
> OK.
> Dein button_update() müsste IMHO so aussehen
> void button_update() {
>    if(button_isPushed()) {
>      if( button_count > 5 )
>        pushed = true;
>      else
>        button_count++;
>    }
>    else {
>      button_count = 0;
>    }
> }

Ah okay, der Code leuchtet ein. Warum mein Code etwas anders aufgebaut 
ist:
Ein Knopf wird erst dann als gedrückt anerkannt, wenn er bereits wieder 
losgelassen wurde. Das hat derzeit den simplen Zweck, dass auch zwischen 
kurz und lang gedrückt unterschieden wird. Diese Festlegung wird bei mir 
also (derzeit) erst getroffen, wenn der Knopf wieder losgelassen wird. 
Bei Gelegenheit soll dies aber ohnehin noch etwas erweitert werden! ;-)

von Karl H. (kbuchegg)


Lesenswert?

Hendrik Motza schrieb:

> Ein Knopf wird erst dann als gedrückt anerkannt, wenn er bereits wieder
> losgelassen wurde. Das hat derzeit den simplen Zweck, dass auch zwischen
> kurz und lang gedrückt unterschieden wird. Diese Festlegung wird bei mir
> also (derzeit) erst getroffen, wenn der Knopf wieder losgelassen wird.
> Bei Gelegenheit soll dies aber ohnehin noch etwas erweitert werden! ;-)

Dann solltest du aber zumindest das hier

   if(button_isPushed()) {
     button_count++;

auf Überlauf absichern.


Ich hab das jetzt nicht mehr weiter durchdacht.
Ich benutz den Code aus Entprellung ganz unten. Der funktioniert 
zuverlässig für 8 Taster und spielt alle Stückeln, inklusive 
Tasten-Repeat bei längerem Drücken.

von Hendrik M. (do1hrm)


Lesenswert?

Das stimmt wohl, ne Absicherung gegen Überlauf wäre bestimmt sinnvoll. 
Aber ich hatte es bereits geprüft, ein Überlauf kam bei mir nicht zu 
Stande, als int dauert das ohnehin ein wenig länger (je nach 
Timer-Einstellung). Im schlimmsten Fall würde der Tastendruck wohl mal 
nicht korrekt erkannt werden, wenn er ewig gedrückt wird! :-D

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
Noch kein Account? Hier anmelden.