Forum: Mikrocontroller und Digitale Elektronik ATmega 8 hängt sich auf


von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

Ich habe einen Atmega8 in Arbeit.
Leider hängt er sich (ausschließlich) innerhalb folgender Schleife auf:
1
while( !btn_enter && !btn_menue ){
2
3
    if( btn_up && *constant < i ) *constant = *constant + 1;
4
    if( btn_down && *constant > 0 ) *constant = *constant - 1;
5
6
    btn_down = btn_up = 0;
7
8
    set_cursor(x, 1);
9
10
    // Zeit oder Datum anzeigen?
11
    if( time ) show_time();
12
    else {
13
14
      show_wkday();
15
      show_date();
16
17
    }
18
19
20
21
    if( btn_menue ) return;
22
23
    _delay_ms(100);
24
25
  }

Das passiert rein zufällig.

btn_... wird über einen Interrupt von einem Portexpander gesetzt.
Ansonsten wird nur ein Wert erhöht, bzw. erniedrigt und dann wird er 
angezeigt.

Aktiviert sind zudem beide Externe-Interrupts, 3 Timer für SoftPWM etc.
Fällt jemanden ein Grund ein, warum der µC sich an dieser Stelle 
aufhängen kann oderliegt es an einem anderen Codeteil?

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Leider kann man bei diesem Codefetzen nur raten. Es fehlen die 
Definitionen aller Variablen und die Definitionen der aufgerufenen 
Funktionen.

> btn_... wird über einen Interrupt von einem Portexpander gesetzt.

Und sind deshalb volatile gekennzeichnet?

Hängt sich die nackische Variante, d,h. keine Funktionsaufrufe auch auf? 
Um das zu erkennen, kannst du z.B. vor dem while eine Debug-LED 
anschalten und dannach ausschalten. Wenn du ein Oszi oder ein LA hast, 
reicht das Schalten eines freien Portpins natürlich auch.
1
  while( !btn_enter && !btn_menue )
2
  {
3
    if( btn_up && *constant < i ) 
4
      *constant = *constant + 1;
5
6
    if( btn_down && *constant > 0 ) 
7
      *constant = *constant - 1;
8
9
    btn_down = 0;
10
    btn_up = 0;
11
12
#if 0
13
    set_cursor(x, 1);
14
    // Zeit oder Datum anzeigen?
15
    if( time ) 
16
    {
17
      show_time();
18
    } 
19
    else 
20
    {
21
      show_wkday();
22
      show_date();
23
    }
24
#endif
25
26
    if( !btn_menue ) 
27
      _delay_ms(100);
28
  }

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

> Und sind deshalb volatile gekennzeichnet?
Jup,ich brauche Sie später noch in der main();
Die Variablen sind im übrigen alle int.
*constant zeigt auf ein Element einer Struktur.

Durch das ständige auftreten von Interrupts (habe alle 3 Timer an) 
könnte der Programmablauf bei entsprechend langen Interruptroutinen ja 
blockiert werden.
Könnte das ein Grund sein. Dann müsste das aber jedes mal passieren und 
nicht rein zufällig.
Oft lässt sich der Wert auch ein paar ma ändern und dann hängt der AVR 
sich auf.

von Oliver (Gast)


Lesenswert?

>Leider hängt er sich (ausschließlich) innerhalb folgender Schleife auf:

Wie hast du das denn herausgefunden?

Oliver

von Christian H. (netzwanze) Benutzerseite


Lesenswert?

Könnte es sein, dass constant urplötzlich in den Stack zeigt?
Falls ja, ist Aufhängen vorprogrammiert.

Es wäre ansonsten viel besser, wenn man den kompletten Code sieht.
Du glaubst gar nicht, wie viele Leute schon behaupteten, dass die 
anderen Funktionen ihres Programms garantiert fehlerfrei seien. Meist 
steckte der Fehler dann in diesen Funktionen.

Btw:
Lange Interrupts sollte es nicht geben. In einem INT wird nur das 
nötigste gemacht (kein delay!); idealerweise nur Flags gesetzt, die dann 
in der Hauptschleife ausgewertet werden.

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

> Wie hast du das denn herausgefunden?
Die Funktion ändert einen Wert (z.B. eine Stelle der Uhrzeit). Die 
Gesamte Uhrzeit ist in einer Struktur gespeichert, welche dann mittels 
show_time() ausgegeben wird. Drück man jetzt also eine Taste, erhöht 
sich der Wert und der neue Wert wird angezeigt. Manchmalpassiert aber 
gar nix, obwohl der Interrupt ausgelöst wird.
Der rest funktioniert eigendlich so wie er soll, ich poste aber mal 
alles nötige:
1
// ----------------------------------------------------------
2
// Interrupt für Tastatur
3
ISR(INT_KEYBRD){
4
5
  uint8_t keybrd;
6
  keybrd = twi_read( IO_ADDR, 0x00 );
7
8
  if( keybrd == 0x10 ) btn_menue = 1;
9
  if( keybrd == 0x08 ) btn_enter = 1;
10
  if( keybrd == 0x04 ) btn_up = 1;
11
  if( keybrd == 0x02 ) btn_down = 1;
12
  if( keybrd == 0x01 ) btn_snooze = 1;
13
14
  if( keybrd ) btn_menue_key = 1;
15
16
}
17
// ----------------------------------------------------------
18
19
// ----------------------------------------------------------
20
// AUSSCHNITT AUS main()
21
22
// ggf.Menü aufrufen
23
    if( btn_menue_key == 1 ){
24
25
      menue();
26
27
      btn_menue_key = 0;
28
      edit_time = 0;
29
30
      lcd_clear();
31
32
    }
33
// -----------------------------------------------------------
34
35
// -----------------------------------------------------------
36
// Funktionen für die Menünavigation
37
void menue(){
38
39
  struct menueP *tmp;
40
  void (*function)(uint8_t);
41
42
  tmp = start;
43
44
  while( tmp ){
45
46
    // Innerhalb des Menüs
47
    function = tmp -> func;
48
    btn_menue = 0;
49
    btn_enter = 0;
50
    btn_up = 0;
51
    btn_down = 0;
52
    btn_menue_key = 0;
53
54
    
55
    // Anzeigen des Menüpunktes
56
    lcd_bg(1);            // Displaybeleuchtung an
57
    lcd_clear();          // Display löschen
58
    lcd_home();
59
60
    lcd_string(tmp -> name);
61
62
63
    while( !btn_menue_key ){
64
65
      set_cursor(7,2);
66
      lcd_string(">");
67
68
      // ggf.Funktion aufrufen
69
      if( btn_enter && *function  ){
70
        
71
        lcd_clear();
72
73
        (*function)(tmp -> fdata);
74
75
        btn_menue_key = 1;
76
        btn_menue = 0;
77
        btn_enter = 0;
78
        btn_up = 0;
79
        btn_down = 0;        
80
81
      }
82
83
      // Warten auf Benutzereingabe
84
      if( btn_menue ) tmp = tmp -> parent;
85
      if( btn_enter && !function ) tmp = tmp -> child;
86
      if( btn_up ) tmp = tmp -> prev;
87
      if( btn_down ) tmp = tmp -> next;
88
89
    }
90
91
  }
92
93
  btn_menue = 0;
94
  btn_enter = 0;
95
  btn_up = 0;
96
  btn_down = 0;
97
98
  return;
99
100
}
101
// -----------------------------------------------------------
102
103
// -----------------------------------------------------------
104
// Aufruf der funktion zum Setzen einer Stelle der Uhrzeit (Beispiel)
105
  m_set_time_constant(&new_timestamp.thours, 2, 4, 1);
106
// -----------------------------------------------------------
107
108
// -----------------------------------------------------------
109
void m_set_time_constant(int *constant, int i,int x, int time){
110
  
111
  //Einstellen einer Zeitkonstante
112
  while( !btn_enter && !btn_menue ){
113
114
    if( btn_up && *constant < i ) *constant = *constant + 1;
115
    if( btn_down && *constant > 0 ) *constant = *constant - 1;
116
117
    btn_down = btn_up = 0;
118
119
    set_cursor(x, 1);
120
121
    // Zeit oder Datum anzeigen?
122
    if( time ) show_time();
123
    else {
124
125
      show_wkday();
126
      show_date();
127
128
    }
129
130
    if( btn_menue ) return;
131
132
    _delay_ms(100);
133
134
  }
135
136
  btn_enter = 0;
137
138
  return;
139
140
}
141
// -----------------------------------------------------------
142
143
// -----------------------------------------------------------
144
// -- Ausgabe des Wochentags
145
void show_wkday(){
146
147
  get_time();        // aktuelle Zeit
148
  edit_timestamp = timestamp;
149
  if( edit_time ) edit_timestamp = new_timestamp;
150
151
  // Wochentag anzeigen
152
  switch (edit_timestamp.wkday){
153
154
    case 0:
155
      lcd_string("So.");
156
      break;
157
    
158
    case 1:
159
      lcd_string("Mo.");
160
      break;
161
      
162
      
163
    case 2:
164
      lcd_string("Di.");
165
      break;
166
      
167
    case 3:
168
      lcd_string("Mi.");
169
      break;
170
      
171
    case 4:
172
      lcd_string("Do.");;
173
      break;
174
      
175
    case 5:
176
      lcd_string("Fr.");
177
      break;  
178
      
179
    case 6:
180
      lcd_string("Sa.");
181
      break;  
182
183
  }
184
185
}
186
// -----------------------------------------------------------
187
188
// -----------------------------------------------------------
189
// -- Ausgabe des Datum
190
void show_date(){
191
192
  // Auslesen der Daten aus dem Uhrenbaustein
193
  char ASCII[ASCII_L];
194
195
  get_time();        // Aktuelle Zeit
196
  edit_timestamp = timestamp;
197
198
  if( edit_time ) edit_timestamp = new_timestamp; 
199
200
  lcd_string( itoa(edit_timestamp.tday, ASCII, 10) );
201
  lcd_string( itoa(edit_timestamp.nday, ASCII, 10) );
202
203
lcd_string( "." );  
204
205
  lcd_string( itoa(edit_timestamp.tmon, ASCII, 10) );
206
  lcd_string( itoa(edit_timestamp.nmon, ASCII, 10) );
207
  lcd_string(".");              
208
209
210
}
211
// -----------------------------------------------------------
212
213
// -----------------------------------------------------------
214
// -- Ausgeben der Uhrzeit
215
void show_time(){
216
217
  // Auslesen der Daten aus Uhrenbaustein
218
  char ASCII[ASCII_L];
219
220
  if( !flash_time ) get_time();    // Aktuelle Zeit   
221
  edit_timestamp = timestamp;
222
223
  if( edit_time ) edit_timestamp = new_timestamp;
224
225
  
226
  lcd_string( itoa(edit_timestamp.thours, ASCII, 10) );  
227
  lcd_string( itoa(edit_timestamp.nhours, ASCII, 10) );
228
  lcd_string(":");              
229
230
  lcd_string( itoa(edit_timestamp.tmin, ASCII, 10) );
231
  lcd_string( itoa(edit_timestamp.nmin, ASCII, 10) );
232
  lcd_string(":");              
233
234
  lcd_string( itoa(edit_timestamp.tsec, ASCII, 10) );  
235
  lcd_string( itoa(edit_timestamp.nsec, ASCII, 10) );
236
237
}
238
// -----------------------------------------------------------

Die Interrupts (bis auf Tastatur-Interrupt) habe ich jetzt so geändert, 
dass sie nur noch Flags setzen.
Wenn ich die Funktionsaufrufe zum Anzeigen auslasse, konnte den Fehler 
bisher nicht reproduzieren. Diese Funktionen werden aber auch regülärzum 
Anzeigen der Uhrzeit im Hauptprogramm genutzt.Dort gab es damit noch nie 
Probleme.

von Peter (Gast)


Lesenswert?

wo constant declariert ist wissen wir aber immer noch nicht.

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

*constant ist doch im Funktionskopf definiert
> [...] (int *constant, [...]
Reicht das nicht, oder habe ich deine Frage falsch verstanden?

Die Struktur für die Zeit ist folgende:
1
// Zeit-Struktur
2
struct time_struc{
3
  int thours;
4
  int nhours;
5
6
  int tmin;
7
  int nmin;
8
9
  int tsec;
10
  int nsec;
11
12
  int tday;
13
  int nday;
14
15
  int tmon;
16
  int nmon;
17
18
  int wkday;
19
20
  int year;
21
};
An *constant wird nur die Adresse eines Elements der Struktur übergeben.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Hannes Eilers schrieb:

> Die Variablen sind im übrigen alle int.

Und alle Zugriffe im gezeigten Code nicht nicht atomar... s. 
Interrupt

> char ASCII[ASCII_L];

Ich sehe keine Definition von ASCII_L. Reicht der Platz für das spätere 
itoa()?

Diese lokale Variable belastet den Stack. Wenn der Stack bereits eng ist 
und eine ISR angesprungen wird, kann die ISR der Todesstoss sein. Ich 
würde die versuchsweise global anlegen. Wieviel RAM braucht dein 
Programm ungefähr (=> AVR-GCC => avr-size)?

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

> Und alle Zugriffe im gezeigten Code nicht nicht atomar...
Daran habe ich gar nicht gedacht. Werde den Interrupt während des 
Zugriffs auf die Variabeln mal ausschalten.
1
#define ASCII_L 9
Ich brauche aber ind er Regel weniger Platz. Könnte das also nich 
optimieren. Ist noch so hoch, weil ich beim "anlegen" der Def. nicht 
wusste,wie viel Platz ich brauche.
Das Programm benötigt inzwischen 86,3% (7072 Byte) des 
Programmspeichers.

von Karl H. (kbuchegg)


Lesenswert?

Die Sache mit den Einzelvariablen für die Buttons finde ich keine so 
gute Idee. Die würde ich alle beisammen in einem uint8_t lassen (du bist 
überhaupt mit int ziemlich freigiebig!)

Wenn dir zb hier

       btn_menue_key = 1;
        btn_menue = 0;
        btn_enter = 0;
        btn_up = 0;
        btn_down = 0;

ein Interrupt dazwischenknallt, der einzelne btn_xxx Variablen setzt, 
dann können, je nachdem wo der Interrupt auftritt, Tastendrücke 
verlorengehen.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Hannes Eilers schrieb:

> Das Programm benötigt inzwischen 86,3% (7072 Byte) des
> Programmspeichers.

Das ist die Größe im Flash also .text plus .data (plus 
Spezialsektionen)? diese Zahl ist weniger interessant.

Wie groß ist der statische Bedarf an RAM, also .data plus .bss? 
RAM-Größe minus statischer Bedarf ist ca. max. Stackgröße. Wenn der 
statische Bedarf in Richtung >3/4 des RAM geht, fange ich an, die Sache 
kritisch zu beäugen.

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:

> Das Programm benötigt inzwischen 86,3% (7072 Byte) des
> Programmspeichers.

Das kriegt man schon noch runter.
Du bist da an einigen gezeigten Stellen ziemlich suboptimal unterwegs.
Du hast zb einige Zahlenausgaben und benutzt dafür jedesmal die Sequenz

  lcd_string( itoa(edit_timestamp.thours, ASCII, 10) );

Warum nicht für Zahlenausgabe eine eigene Funktion machen

void lcd_number( int num )
{
  char buf[10];

  itoa( num, buf, 10 );
  lcd_string( buf );
}

und dann an den verwendenden Stellen

  lcd_number( edit_timestamp.thours );

die Funktion braucht zwar auch etwas Platz, aber dafür sparst du an der 
Stelle der Verwendung Code ein. Und da du die Funktion oft verwendest 
spart das dann in Summe mehr Code ein als durch die zusätzliche Funktion 
aufgebraucht wird.

Aber im Grunde ist die Menge des verwendeten Flash uninteressat. Die 
Data Section ist das interessante.

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

> du bist überhaupt mit int ziemlich freigiebig!
Das stimmt,ich finde esso übersichtlicher.DerDatenspeicher ist 
gerademalbei 35% da geht also nich was. Aber man könnte das sicherlich 
ändern und optimieren.

>je nachdem wo der Interrupt auftritt, Tastendrücke verlorengehen
Wäre ja nicht so schlimm,man müsste halt nochmal auf die Taste drücken 
:-)

Was meinst du denn,könnte es Probleme mit dem Stack und der ASCII_L 
geben?

Ich werde mal versuchen den Code weiterb zu optimieren. Vielleicht leigt 
es daran. Ich werde aber trotzdem noch etwas Platz für dasProg brauchen, 
da noch einige Funktionen fehlen.

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:
>> du bist überhaupt mit int ziemlich freigiebig!
> Das stimmt,ich finde esso übersichtlicher.

Was ist da dran übersichtlicher? gar nichts!
1
struct time_struc{
2
  uint8_t thours;
3
  uint8_t nhours;
4
5
  uint8_t tmin;
6
  uint8_t nmin;
7
8
  uint8_t tsec;
9
  uint8_t nsec;
10
11
  uint8_t tday;
12
  uint8_t nday;
13
14
  uint8_t tmon;
15
  uint8_t nmon;
16
17
  uint8_t wkday;
18
19
  int year;
20
};

ist genauso übersichtlich wie deine Version. In der verwendenden 
Programmierung gibt es keinen Unterschied, ausser das du dir um atomaren 
Zugriff auf einzelne Member der Struktur keine Gedanken mehr zu machen 
brauchst.

Lass es dir noch einmal eindringlich gesagt sein:
Auf einem 8-Bit µC, wie einem Mega8, ist der Datentyp uint8_t oder 
int8_t immer deine erste Wahl!
* das ist der Datentyp mit dem der µC am besten umgehen kann
* alles andere erfordert mehr Aufwand als notwendig
* du hast bei allen anderen Typen immer das Problem, dass du
  Zugriff unter Umständen atomar absichern musst.
* du blockierst unnötigerweise Speicher, der dir an anderer Stelle
  manchmal schmerzlich fehlen kann


(Warum gibt es eigentlich in einem Datum jeweils einen t und einen n 
Eintrag? Das erscheint mir nicht wirklich logisch)


>>je nachdem wo der Interrupt auftritt, Tastendrücke verlorengehen
> Wäre ja nicht so schlimm,man müsste halt nochmal auf die Taste drücken
> :-)

Doch das ist schlimm.

>
> Was meinst du denn,könnte es Probleme mit dem Stack und der ASCII_L
> geben?

Bei 35% RAM Verbrauch: unwahrscheinlich.
Da wird wohl irgendwo ein Pointer im Wald stehen.

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:

> Ich werde mal versuchen den Code weiterb zu optimieren.

Schlechte Strategie.
Durch optimieren wird es selten besser, solange das Problem nicht 'Out 
of memory' lautet.

von Peter D. (peda)


Lesenswert?

Hannes Eilers schrieb:
> Die Variablen sind im übrigen alle int.

Damit verschenkst Du schonmal ein Großteil des Speicherplatzes, oder hat 
bei Dir die Stunde 65535 Minuten?

Nimm besser uint8_t, wos ausreicht.


Peter

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

Also ich habe jetzt alle Variabeln angepasst, was mir mächtig Platz 
spart.
Hierfür erstmal danke!

>(Warum gibt es eigentlich in einem Datum jeweils einen t und einen n Eintrag? Das 
erscheint mir nicht wirklich logisch)

Das t ist für die Zehnerstelle das n für die Einerstelle.Ich brauche die 
einzeln um die Dtaen hinterher in den Uhrenbaustein zu schieben, will 
die Stellen leider einzeln haben.

Meine Tastur habe ich jetzt wie folgt realisiert:
1
// ----------------------------------------------------------
2
// Interrupt für Tastatur
3
ISR(INT_KEYBRD){
4
5
  keybrd = twi_read( IO_ADDR, 0x00 );
6
7
}
8
// ----------------------------------------------------------

In den Funktionen wird jetzt der Hex-Wert von keybrd abgefragt:
1
void m_set_time_constant(uint8_t *constant,uint8_t i,uint8_t x,uint8_t time){
2
  
3
  //Einstellen einer Zeitkonstante
4
5
  keybrd = 0x00;
6
7
  while( keybrd != 0x10 && keybrd != 0x08 ){
8
9
    if( keybrd == 0x04 && *constant < i ){
10
      *constant = *constant + 1;
11
      keybrd = 0x00;
12
    }
13
14
    if( keybrd== 0x02 && *constant > 0 ){
15
      *constant = *constant - 1;
16
      keybrd = 0x00;
17
    }
18
19
    set_cursor(x, 1);
20
21
    // Zeit oder Datum anzeigen?
22
    if( time ) show_time();
23
    else {
24
25
      show_wkday();
26
      show_date();
27
28
    }
29
30
    if( keybrd == 0x10 ) return;
31
32
    _delay_ms(100);
33
34
  }
35
36
  keybrd = 0x00;
37
38
  return;
39
40
}

Bisher hebt das aber das Problem mit dem Aufhängen nicht auf.

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:

>>(Warum gibt es eigentlich in einem Datum jeweils einen t und einen n Eintrag? 
Das
> erscheint mir nicht wirklich logisch)
>
> Das t ist für die Zehnerstelle das n für die Einerstelle.Ich brauche die
> einzeln um die Dtaen hinterher in den Uhrenbaustein zu schieben, will
> die Stellen leider einzeln haben.

Das ist jetzt aber nicht dein Ernst, oder?
(Und dann noch für Zahlen die nur von 0 bis 9 laufen können einen 
eigenen int :-)

Du zwirbelst dir hier eine Menge 'Ungemach' auf, nur damit du die 
Zerlegung einer Zahl in Zehner und Einer direkt beim Verschicken an den 
Uhrenbaustein einsparst?

Hast du Zehner und Einer, dann errechnet sich die dadurch dargestellte 
Zahl durch:

   Zahl = 10 * Zehner + Einer

und die Umkehrung, die Zerlegung ist

   Zehner = Zahl / 10
   Einer = Zahl - 10 * Zehner;

Problem gelöst und du brauchst beim Erhöhen/Erniedrigen der Uhrezeit, 
sowie bei Vergleichen oder sonstigen Rechnereien keine 'Durch die Brust 
von hintern ins Auge' Aktionen mit Zehnern und Einern machen.

Einzig bei der Ausgabe auf LCD muss man ein wenig darauf achten, dass 
führende Nullen mit ausgegeben werden. Aber da die Ausgabe in einer 
einzigen Funktion gesammelt ist, ist auch das kein wirkliches Problem 
:-)

Jetzt versteh ich auch, warum da so viele lcd_string( itoa ... 
Konstruktionen sind

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:

> Bisher hebt das aber das Problem mit dem Aufhängen nicht auf.

Das Problem wird auch nicht in dieser Funktion sein.
Was du siehst, sind die Symptome. Die Ursache kann ganz woanders 
stecken.

von Peter D. (peda)


Lesenswert?

Hannes Eilers schrieb:
> Bisher hebt das aber das Problem mit dem Aufhängen nicht auf.

Nur Stochern im Nebel bringts eben nicht.
Poste endlich mal was compilierbares als Anhang!

Aber mach erstmal die tausende Leerzeilen weg, ich hab schon Brandblasen 
am Zeigefinger vom vielen Scrollen.


Peter

von Hannes E. (k1ngarthur) Benutzerseite


Angehängte Dateien:

Lesenswert?

Es sind mehrere Dateien.

Der AVR soll am Ende folgendes können(damit ihr versteht was ich mit dem 
Code eig. bezwekcne will):

- Anzeige von Uhrzeit und Datum (ohne Jahr) im Wechsel (4sec.)
- Alarmfunktion
- Ansteuerung von RGB-LEDs über RGB-Werte (inkl-. Helligkeit)
- Anzeige der Daten auf einem LCD
- programmierbarer Alarm (best.Wochentage, Tägl. oder aus)
- Steuerung eines MP3-Player (über Simulation eines Tastendrucks)

Dazu habe ich einen PCF8583 und einen PCA9555 am I2C.
Letzterer übernimmt die Ports für den MP3-Player und ide Tastatur. Beide 
sind über Interrupts angeschlossen.

Ich bin euch dankbar für die vielen Tipps, wie man etwas besser oder 
einfacher realisieren kann.
Aber jetzt alles zu überarbeiten ist nicht Zweck dieses Forum, das muss 
ich schon selber machen *freu, seufz* ;-)
Wenn jemand also einen Fehler findet, der dazu führen könnte, dass sich 
der AVR aufhängt wäre ich ihm sehr dankbar.

p.s.: Ja,Wecker wird ander geschrieben :-))

von Karl H. (kbuchegg)


Lesenswert?

Kurzer Zwischenbericht:

Du arbeitest mit malloc. Damit sind alle Aussagen bezüglich 
Speicherverbrauch und den 35% zu Makulatur verkommen. Die Schlacht um 
einen möglichen Stacküberlauf ist damit wieder eröffnet.

-> Versuch ohne malloc auszukommen.
Ein Menüsystem muss nicht die Menüs zur Laufzeit zusammenbauen. Das kann 
man auch statisch machen.
Und dann stimmen die vom Compiler ausgeworfenen Zahlen bezgl. 
Speicherverbrauch wieder und sind aussagekräftig.

von Karl H. (kbuchegg)


Lesenswert?

in menu.h

>      if( keybrd == 0x08 && *function  ){

       if( keybrd == 0x08 && function  ){

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

> Das kann man auch statisch machen.
Ich möchte jedoch gerne die Menüstruktur als Liste machen, dann bietet 
sich dieses Verfahrne doch an.
Wie würdest du es denn machen, dammit die Menüeinträge als Liste nicht 
erst zur Laufzeit angelegt werden? Hab schon drüber nachgedacht sie ins 
EEPROM zu legen, aber das ist auch nicht gerade so komfortabel.

Das Menü lässt sich auch prima aufrufen und durchblättern. Nur, wenn ich 
die Funktion zum Verändern von Werten aufrufe hackts manchmal. manchmal 
funktioniert es aber auch.

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:
>> Das kann man auch statisch machen.
> Ich möchte jedoch gerne die Menüstruktur als Liste machen, dann bietet
> sich dieses Verfahrne doch an.

Kannst du doch.
Leg meinetwegen die einzelnen Knoten statisch an und verkette sie zur 
Programmlaufzeit.

Aber verzichte auf malloc!

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

Also z.B. so?:
1
struct menueP m1, m2, m3, m4;
2
3
  // -----------------------------
4
  new_menueP("01 Zeit - Datum", &m1);
5
  new_menueP("02 Wecker", &m2);
6
  new_menueP("03 Farbe", &m3);
7
  new_menueP("04 Musik", &m4);
8
9
  m1.next = m3.prev = &m2;
10
  m2.next = m4.prev = &m3;
11
  m3.next = m1.prev = &m4;
12
  m4.next = m2.prev = &m1;
13
14
  start = &m1;
15
  // -----------------------------

Der Aufruf des ersten Elements funktioneirt auch. Das nächste in der 
Liste wird aber irgendwie nicht richtig gefunden, es tauchen dann als 
bezeichnung auf dem Display nur Zeichenfragmente auf (da scheint der 
Pointer irgendwie nicht richtig zu sein).

von Karl H. (kbuchegg)


Lesenswert?

Hier

    while( !keybrd ){

      set_cursor(7,2);
      lcd_string(">");

      // ggf.Funktion aufrufen
      if( keybrd == 0x08 && *function  ){

        lcd_clear();

        (*function)(tmp -> fdata);

        keybrd = 0x20;

      }

      // Warten auf Benutzereingabe
      if( keybrd == 0x10 ) tmp = tmp -> parent;
      if( keybrd == 0x08 && !function ) tmp = tmp -> child;
      if( keybrd == 0x04 ) tmp = tmp -> prev;
      if( keybrd == 0x02 ) tmp = tmp -> next;

solltest du unbedingt noch 'function' nachziehen. Sonst kann es dir 
passieren, dass du die falsche Funktion aufrufst. Am besten jedoch, 
eliminerst du die Variable function und greifst immer über tmp zu
(und bitte gewöhn dir an dieser Stelle die Leerzeichen wieder ab. Der 
Pointer und der Member auf den über den Pointer zugegriffen werden soll, 
gehören zusammen. Der -> ist Trenner genug.
Ich bin auch versucht zu sagen: Gewöhn dir auch das weiterschreiben in 
einer Zeile nach einem if wieder ab. Die abhängige Anweisung kommt 
eingerückt eine Zeile tiefer. Dann sieht man auch aus 3m Entfernung, das 
da irgendetwas besonderes passiert)

Auch wimmeln mir da ein bischen zuviele HEX-Konstanten rum. Mach dir 
dafür ein paar #define und lesbare Namen.
1
// Funktionen für die Menünavigation
2
void menue() {
3
4
 struct menueP *tmp = start;
5
6
  while( tmp ) {
7
8
    keybrd = 0x00;
9
10
    lcd_bg( 1 );      // Displaybeleuchtung an
11
    lcd_clear();
12
    lcd_home();
13
    lcd_string( tmp->name );
14
15
    while( !keybrd ) {
16
      set_cursor(7,2);
17
      lcd_string(">");
18
19
      if( ( keybrd & KEY_ENTER ) ) {
20
        if( tmp->function ) {
21
          lcd_clear();
22
          (*tmp->function)( tmp->fdata );
23
          keybrd = 0x20;   // was ist 0x20?
24
        }
25
        else
26
          tmp = tmp->child;
27
      }
28
29
      else if( keybrd & KEY_PARENT )
30
        tmp = tmp->parent;
31
32
      else if( keybrd & KEY_PREV )
33
        tmp = tmp->prev;
34
35
      else if( keybrd & KEY_NEXT )
36
        tmp = tmp->next;
37
    }
38
  }
39
}

Muss das wirklich sein, dass da bei jedem Durchlauf durch die äussere 
while-Schleife das Display gelöscht und neu beschrieben wird? Das wird 
ganz schön flackern :-)

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:
> Also z.B. so?:

Genau


> Der Aufruf des ersten Elements funktioneirt auch. Das nächste in der
> Liste wird aber irgendwie nicht richtig gefunden, es tauchen dann als
> bezeichnung auf dem Display nur Zeichenfragmente auf (da scheint der
> Pointer irgendwie nicht richtig zu sein).

Da hast du irgendeinen Fehler gemacht.
Allerdings ist der ganze 'start' Pointer suspekt. Den braucht kein 
Mensch.
Übergib der menue Funktion die Adresse des ersten Menüknotens und gut 
ists.
Du scheinst höllische Angst davor zu haben, Argumente an Funktionen zu 
übergeben. Statt dessen hast du dann eine Menge globale Variablen, die 
den Sacheverhalt auch nicht besser machen.

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

OK, habe jetzt alles geändert.
Navigation durch die Menüs funktioniert auch.

.data + .bss +.noinit = 79,6%
.text + .data +.bootloader = 61,1%


Ich habe zwar nciht so viel Ahnung wie viele Daten man maximal haben 
sollte, damit der Stack nicht voll ist aber ~80% sind wohl zu viel oder?

[edit]
Achso, lasse ich die Untermenüs weg, sind es statt ~80% nur ~15%.
Ev. könnte man die Hauptnavigation statisch machen und die Unterpunkte 
dynamisch generieren, dass kostet mehr Programmzeilen aber erheblich 
weniger Platz auf dem Stack oder?
[/edit]

von Peter D. (peda)


Lesenswert?

Hannes Eilers schrieb:
>
1
> struct menueP m1, m2, m3, m4;
2
> 
3
>   // -----------------------------
4
>   new_menueP("01 Zeit - Datum", &m1);
5
>   new_menueP("02 Wecker", &m2);
6
>   new_menueP("03 Farbe", &m3);
7
>   new_menueP("04 Musik", &m4);
8
> 
9
>   m1.next = m3.prev = &m2;
10
>   m2.next = m4.prev = &m3;
11
>   m3.next = m1.prev = &m4;
12
>   m4.next = m2.prev = &m1;
13
> 
14
>   start = &m1;
15
>   // -----------------------------
16
>

Wozu die Speicherverschwendung mit dem .next und .prev?
Du willst doch immer der Reihe nach zählen, dann zähl doch einfach nur 
den Index:
1
#define MENUES 4
2
struct menueP m[MENUES];
3
uint8_t menue_idx = 0;
4
// ...
5
  next = menue_idx >= (MENUES - 1) ? menue_idx + 1 : 0;
6
  prev = menue_idx ? menue_idx - 1 : MENUES - 1;


Peter

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:

> Ich habe zwar nciht so viel Ahnung wie viele Daten man maximal haben
> sollte, damit der Stack nicht voll ist aber ~80% sind wohl zu viel oder?

Welcher Prozessor?

80% ist schon eine Menge Holz.

> [edit]
> Achso, lasse ich die Untermenüs weg, sind es statt ~80% nur ~15%.
> Ev. könnte man die Hauptnavigation statisch machen und die Unterpunkte
> dynamisch generieren, dass kostet mehr Programmzeilen aber erheblich
> weniger Platz auf dem Stack oder?
> [/edit]

Den meisten Platz werden die Texte brauchen.
Jeweils ein 17-er char Array für einen Text ist schon ziemlich viel.
Mach da mal einen char* Pointer rein und setze den auf den in der 
Erzeugungsroutine auf den konstanten Text den du bekommst. Das sollte 
den Speicher schon mal drücken. Wenn das immer noch nicht reicht könnte 
man die Texte ins Flash auslagern. Das geht immer noch relativ billig 
ohen allzuviele Änderungen im Programm.

von Karl H. (kbuchegg)


Lesenswert?

Wie hoch bist du jetzt mit dem Speicherverbrauch?

von Karl H. (kbuchegg)


Lesenswert?

1
struct menueP {
2
  const char * name;
3
  
4
  void (*func)(uint8_t);  
5
  uint8_t fdata;
6
7
  uint8_t prevIndex;
8
  uint8_t nextIndex;
9
  struct menueP* child;
10
};
11
12
struct menuP soundMenu[] = {
13
   { "MP3-Player",       NULL, 0,   2, 1,  NULL },
14
   { "Lautstärke",       NULL, 0,   0, 2,  NULL },
15
   { "Weck-Song",        NULL, 0,   1, 0,  NULL },
16
};
17
18
struct menuP colorMenu[] = {
19
   { "Farbe setzen",     NULL, 0,   5, 1,  NULL },
20
   { "auto Farbwechsel", NULL, 0,   0, 2,  NULL },
21
   { "Weckfarbe",        NULL, 0,   1, 3,  NULL },
22
   { "auto. WeFarbWechs",NULL, 0,   2, 4,  NULL },
23
   { "norm. Helligkeit", NULL, 0,   3, 5,  NULL },
24
   { "Weck-Helligkeit",  NULL, 0,   4, 0,  NULL },
25
};
26
27
struct menuP alarmMenu[] = {
28
    ...
29
};
30
31
struct menuP timeMenu[] = {
32
   { "11 Zeit",          m_time, 0, 1, 1,  NULL },
33
   { "12 Datum",         m_date, 0, 0, 0,  NULL },
34
};
35
36
struct menuP mainMenu[] = {
37
   { "01 Zeit - Datum",  NULL, 0,   3, 1,  timeMenu  },
38
   { "02 Wecker",        NULL, 0,   0, 2,  alarmMenu },
39
   { "03 Farbe",         NULL, 0,   1, 3,  colorMenu },
40
   { "04 Musik",         NULL, 0,   2, 0,  soundMenu },
41
};

so würde ich das Menüsystem aufbauen. Wenn du den parent noch haben 
willst, dann mach in die Menüstruktur noch den Pointer dafür rein und 
füll den Parent Pointer dynamisch in der Menü Funktion aus. Wenn in ein 
Childmenü gewechselt wird, wird der parent Pointer in diesem Childmenü 
auf den Meneüpointer gesetzt, der für den Aufruf gesorgt hat.

Diese Darstellung oben hat auch den Vorteil gegenüber einer dynamischen 
Allokierung, dass man einen besseren Überblick über den Menüaufbau hat, 
als wie wenn sich der Aufbau in 40 Zeilen Code verbergen.

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

> Mach da mal einen char* Pointer rein und setze den auf den in der
> Erzeugungsroutine auf den konstanten Text den du bekommst.

Damit komme ich auf 53,2%
µC ist ein ATmega8-16

Fehler tritt aber immer noch auf.

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:
>> Mach da mal einen char* Pointer rein und setze den auf den in der
> Erzeugungsroutine auf den konstanten Text den du bekommst.
>
> Damit komme ich auf 53,2%
> µC ist ein ATmega8-16

OK.
Damit sollte man auf der sicheren Seite sein.

> Fehler tritt aber immer noch auf.
Es wäre einfacher, wenn ich hier das Zeug nachstellen könnte :-)

So bleibt nur: Alle Pointer Zugriffe absuchen. Sicherheitshalber immer 
auf NULL Pointer testen.

von Karl H. (kbuchegg)


Lesenswert?

Nachmal zum Fehlerbild.
Was machst du genau nach dem Einschalten, damit man zum Fehler kommt 
(und wie macht sich der Fehler bemerkbar?)

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

Na dann mal ran.(Das wird ein Spaß!) seufz

von Karl H. (kbuchegg)


Lesenswert?

Solche Abfragen

if( keybrd == 0x04 && *constant < i ){
    **************

sind nicht sehr schlau. Du hast deine Tasten bitcodiert. Jede Taste hat 
ihr eigenes Bit. Das kannst du benutzen um defensiv zu programmieren

 if( ( keybrd & 0x04 ) && *constant < i )

Der Unterschied:
Bei deiner Version MUSS keybrd exakt 0x04 sein. Ist irgendein anderes 
Bit aus irgendwelchem Grund gesetzt, dann geht deine Version nicht mehr.
Bei meiner Version wird getestet, ob das zur Taste gehörende Bit gesetzt 
ist. Diese Version funktioniert also auch dann noch, wenn in keybrd 
irgendein anderes Bit zusätzlich gesetzt wurde. Bei den erhöh und 
erniedrig-Tasten ist das wahrscheinlich egal, aber die Tasten für 
'Aussteigen' können davon profitieren, weil dein Programm dann auch aus 
einer 'kleinen' Fehlersituation heraus noch sinnvoll arbeiten kann.

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:
> Na dann mal ran.(Das wird ein Spaß!) *seufz*

Nochmal:
Was musst du exakt machen, damit es zum Fehler kommt?
Und wie sieht der Fehler aus?

(und poste nochmal deinen jetzigen Code)

von Karl H. (kbuchegg)


Lesenswert?

Hannes Eilers schrieb:
> Na dann mal ran.(Das wird ein Spaß!) *seufz*



Na ja.
So schlimm auch wieder nicht.
Du hast ein LCD.
Das kannst du benutzen um dir aus dem Programm heraus, Statusausgaben 
machen zu lassen. Wenn dein Programm nicht mehr auf Tastendrücke 
reagiert, aber in einer Schleife sein müsste, ist es zb eine gute Idee 
innerhalb dieser Schleife sich die keybrd Variable an einer Stelle im 
LCD ausgeben zu lassen.
usw. Wenn du nicht weißt was dein Programm tut, dann verpass ihm 
Ausgaben, so dass dir das Programm erzählt was es gerade tut :-)

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

Also der Fehler muss irgendwo in der Schleife liegen.
Außerhalb tritt nie ein Fehler auf.

Wenn ich aber innerhalb einfach mal wild auf die Tasten drücke, bleibt 
der AVR stehen und erhöht den Wert nicht mehr. Der Interrupt wird auch 
nicht mehr aufgerufen.
verwende ich jetzt aber Statusmeldungen um die Fehlerquelle zu 
lokalisieren, hängt er sich nicht mehr auf.

Ich habe daher einmal das delay am Ende auf 250ms gesetzt (ist ja eine 
kaum spürbare Zeit) und siehe da: Kein Fehler mehr.
Wenn ich gar kein delay rein mache hängt er sich im übrigen sofort auf.

Auf jeden Fall habe ich es heute geschafft, dass mein Code viel weniger 
Platz weg nimmt (jetzt ~53%).

von Peter D. (peda)


Lesenswert?

1
ISR(INT_KEYBRD){
2
3
  keybrd = twi_read( IO_ADDR, 0x00 );

Oh oh oh !

Wenn dann im Mainprogramm auch I2C gemacht wird, krachts.
I2C ist quasi ein Device, wie z.B. das LCD. Ein Transfer darf nicht 
durch eine anderen unterbrochen werden.

Wenn Du z.B. in Word eine Datei öffnest, kann da auch kein anderer 
schreiben. Es würde krachen, daher blockiert der PC Devices und Dateien, 
bis der anfordernde Prozess fertig ist.


Man sollte für I2C auch immer ein Timeout aufsetzen (1-10ms). Falls mal 
ne Störung erfolgt und der Bus sich verklemmt, würde sonst Dein Program 
für immer hängen.


Peter

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

> Oh oh oh !
Das wird nur vor der Aktivierung der Interrupts aufgerufen, da der 
PCA9555 nach dem Start bereits einen Interrupt erzeugt. Er geht nämlich 
davon aus, dass an den Pins ein High liegen soll, tut's aber nciht >> 
Interrupt.
Dann beschreibe ich ihn, dass Low normal ist udn nur bei high ein 
interrupt kommen soll.
Der erste Interrupt verschwindet aber erst, nachdem sich der Status des 
Pins ändert oder man ihn einmal ausliest.
Da sollte es also keine Probleme geben.



Habe jetzt einmalmit dem Eintritt in's Menü die Timer ausgeschaltet 
(bzw. die Interrupts dafür deaktiviert), dann lässt sich das delay noch 
ein wenig nach unten korrigieren.

von Peter D. (peda)


Lesenswert?

Hannes Eilers schrieb:
> Habe jetzt einmalmit dem Eintritt in's Menü die Timer ausgeschaltet
> (bzw. die Interrupts dafür deaktiviert), dann lässt sich das delay noch
> ein wenig nach unten korrigieren.

Dann scheine ich ja richtig zu liegen.

Ich weiß zwar nicht, welches Delay Du meinst, aber für ein sicheres 
Programmkonzept darf davon nicht die Funktion abhängen.

Ich würde dieses gefährliche Konzept in jedem Fall ändern.
Die Verwendung einer nicht reentranten Funktion in Main und Interrupt 
ist pfui-bäh und wird Dir auch immer wieder auf die Füße fallen.

Setze einfach im Interrupt ein Bit und im Main wird dieses getestet und 
dann an sicherer Stelle das I2C aufgerufen.
Tastendrücke sind nichts eiliges, da ist kein Interrupt nötig.


Peter

von Hannes E. (k1ngarthur) Benutzerseite


Lesenswert?

> Die Verwendung einer nicht reentranten Funktion in Main und Interrupt ist > 
pfui-bäh
Da muss ich zustimmen. Ich habe das jetzt geändert, was den Programmcode 
etwas größer macht, da ja jede Funktion die laufend auf Tastatureingaben 
wartet eine Zeile mehr braucht, aber dafür läuft jetzt alles stabil und 
ohne Fehler (auch ohne delays).

Manchmal vergisst man eben diese elementaren (wichtigen!) Dinge.
Danke für eure Hilfe.

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.