Forum: Mikrocontroller und Digitale Elektronik UART und Timer: Verrücktes Verhalten


von Bernhard N. (bernieserver)


Lesenswert?

Hallo,

Ich habe hier eine einfache Steuerung von Ausgängen, das über mittes 
UART gesendete Befehle geschehen soll. Alles ist hierbei 
interruptgesteuert. Es existiert ein Timer und eine Sende / 
Empfangsroutine für den UART.

Mein Problem ist folgendes:

Meine Senderoutine (siehe unten beim Timer) klappt nur, wenn sie in der 
Timerinterrupt - ISR aufgerufen wird. Außerhalb davon in der Mainloop 
funktioniert sie leider nicht.

Denn als Ausgabe kommt statt

test
TEST
test
TEST
...

folgendes:

TEST
 est
TEST
 est
TEST
 est
TEST
 est
...

Hier ein Teil meines Codes, das alleine laufen sollte:
Es beinhaltet die Main Loop, den Timer mit allen Routinen, dem Compare 
ISR und den interruptgesteuerten UART Sendealgorithmus incl. dessen 
benötigtem ISR.

Bitte schaut mal drüber. Ich bin für alle Hinweise dankbar.

Gruß

Bernhard N.

1
#ifndef F_CPU
2
#warning "F_CPU wurde nicht extern in der Makefile definiert, es werden 16 Mhz angenommen."
3
#define F_CPU 16000000UL     /* Quarz mit 16 Mhz */
4
#endif
5
#define UBBRR_VAL 8 /* initialize USART for 115200 Baud */
6
7
8
///////////////////////////////////////
9
// Main mit Hauptschleife
10
///////////////////////////////////////
11
12
13
int main (void) {  
14
15
// Initialisierungen
16
InitTimer();
17
InitUsart();
18
 
19
// Globale Interrupts aktivieren
20
sei();   
21
22
while (1)
23
  {  
24
  Timeraufgaben_Abarbeiten();
25
  }
26
  return 0;
27
}
28
29
30
31
///////////////////////////////////////
32
// Timer
33
///////////////////////////////////////
34
35
 // Zählvariable für den Timer - ISR
36
 uint16_t gHeartbeat;
37
 uint8_t gfAufgabenLiegenVor = 0;
38
39
40
void InitTimer(){   
41
42
 // Schrittweite des Zählers in Millisekunden
43
 uint16_t Schrittweite_ms = 32;
44
45
 // Aktivierung des CTC Modes des Timer (Clear Timer on Compare Match)
46
 // Bewirkt ein Zurücksetzen des Timers nach Erreichen des Schwellwertes im OCR0 Register. 
47
 TCCR0 |= (0<<WGM00)|(1<<WGM01);      
48
49
 // Konfiguration des Timer - Prescalers: CPU - Takt / 1024. 
50
 // Somit wird der eigentliche Timer mit 15,625 kHz angesteuert.
51
 TCCR0 &= ~(1<<CS01);
52
 TCCR0 |= (1<<CS00)|(1<<CS02);   
53
54
 // Berechnung des Output Compare Registers
55
 uint16_t PrescalerFaktor =  1024; 
56
 OCR0 = (F_CPU * Schrittweite_ms / 1000 / (2 * PrescalerFaktor)) -1; 
57
58
 // Compare Interrupt aktivieren
59
 TIMSK |= (1<<OCIE0);
60
61
}
62
63
// ISR vom TimerCompare Int.
64
65
ISR(TIMER0_COMP_vect){ 
66
67
// Marke, ab der die globale Zählvariable zurückgesetzt wird.
68
// Zeitdauer entspricht somit der Periodendauer der gesamten Timerroutine.
69
// Zu errechnen ist sie mittels MaxMark * Schrittweite_ms.
70
uint8_t MaxMark = 64;  
71
72
uint8_t Mark1 = 32; // Marke für Timerereignis 1
73
uint8_t Mark2 = 16;  // Marke für Timerereignis 2
74
//uint8_t Mark3 = 1;  // Marke für Timerereignis 3
75
76
 
77
 if (gHeartbeat == MaxMark){
78
  gHeartbeat = 0;
79
 }
80
 if (gHeartbeat == Mark1){
81
  gfAufgabenLiegenVor = 1;
82
 }
83
 if (gHeartbeat == Mark2){
84
 
85
 //!!!!!!
86
 // HIER KLAPPT DIE ÜBERTRAGUNG EINWANDFREI
87
 //!!!!!!
88
  char text[] = "TEST \n";
89
  SendeTextUART(text,sizeof(text)); 
90
 }
91
  
92
 // Die globale Zählvariable zum Realisieren größerer Periodendauern wird hier hochgezählt
93
 gHeartbeat++;
94
95
}
96
97
void Timeraufgaben_Abarbeiten(){
98
99
 if(gfAufgabenLiegenVor == 1){
100
 
101
 //!!!!!!
102
 // HIER IST DAS EIGENTLICHE PROBLEM:
103
 // HIER GEHT ES NICHT SAUBER: DIE TEXTE WERDEN NUR TEILWEISE ÜBERTRAGENG
104
 //!!!!!!
105
 
106
  char text[] = "test \n";
107
   SendeTextUART(text,sizeof(text)); 
108
  gfAufgabenLiegenVor = 0;
109
 }
110
111
}
112
113
114
115
116
///////////////////////////////////////
117
// UART Teil
118
///////////////////////////////////////
119
120
// Globaler Eingangspuffer und Arrayindex
121
char gStrUebertrage[21];
122
uint8_t gI = 0;
123
124
void InitUsart(void)
125
{
126
  /* set the baudrate */
127
  UBRRH = (UBBRR_VAL >> 8) & 0x7F;  /* ensure URSEL to be zero when wrinting */
128
  UBRRL = UBBRR_VAL & 0xFF;
129
130
  // Aktivieren des Senden und Empfangsmodus
131
  UCSRB = (1<<RXEN)|(1<<TXEN);
132
133
  // Aktivierung des "Zeichen Empfangen" - Interrupts
134
  UCSRB |= (1<<RXCIE);
135
136
}
137
138
139
void SendeTextUART(char StrUebertrage[],uint8_t size){
140
141
    // Aktivierung des "Data Register Empty" - Interrupts
142
    UCSRB |= (1<<UDRIE);
143
  
144
  // Kopieren des lokalen Arrays auf einen globalen Puffer ohne Nutzung von malloc.
145
  strncpy(gStrUebertrage,StrUebertrage,size);  
146
  
147
  //Inkrement für das Arrayindex
148
  gI = 0;
149
150
  // Nach Übertragung des ersten Zeichen wird der "UDRE" Interrupt ausgelöst
151
  UDR = gStrUebertrage[0];
152
  
153
}
154
155
// ISR vom Data Register Empty Interrupt
156
ISR(USART_UDRE_vect){ 
157
158
   gI++; // Zuerst, da ansonsten der Interrupt "reinfunkt". Daher mit 0 initialisiert.    
159
  if (gStrUebertrage[gI] != 0){ // solange Nullterminierung noch nicht erreicht  
160
   UDR = gStrUebertrage[gI];
161
  }else{
162
   // Deaktivierung des "Data Register Empty" - Interrupts. Die Übertragung ist beendet.
163
     UCSRB &= ~(1<<UDRIE);
164
   gI = 0;
165
  }
166
167
}

von Karl H. (kbuchegg)


Lesenswert?

Ich würde vorschlagen, dass du hier
1
void SendeTextUART(char StrUebertrage[],uint8_t size){
2
3
    // Aktivierung des "Data Register Empty" - Interrupts
4
    UCSRB |= (1<<UDRIE);
5
  
6
  // Kopieren des lokalen Arrays auf einen globalen Puffer ohne Nutzung von malloc.
7
  strncpy(gStrUebertrage,StrUebertrage,size);  
8
  
9
  //Inkrement für das Arrayindex
10
  gI = 0;
11
12
  // Nach Übertragung des ersten Zeichen wird der "UDRE" Interrupt ausgelöst
13
  UDR = gStrUebertrage[0];
14
  
15
}
als allererstes einfach einmal überprüfst, ob nicht noch eine UART 
Übertragung im Gange ist.
Du weist hier dem UDR das erste Zeichen einfach so zu, ohne dich darum 
zu kümmern, ob die UART überhaupt im Leerlauf ist.
Genauso wie du ohne Rücksicht auf Verluste den globalen Buffer 
überschreibst.

Das alles ist mir persönlich ein wenig zu optimistisch programmiert. Geh 
die Funktionalität nochmal durch und schliesse in deine Überlegungen die 
Fragestellung mit ein: Was passiert eigentlich, wenn SendeTextUART zu 
schnell hintereinander aufgerufen wird, sodass der erste Text noch nicht 
draussen ist, während der nächste schon fürs Ausgeben aufbereitet wird.

von Bernhard N. (bernieserver)


Lesenswert?

Hallo Karl heinz Buchegger,

ich denke das Problem was Du meinst ist jetzt noch nicht relevant. Denn 
selbst wenn ich nur alle Sekunde mal ein Text außerhalb der Timer ISR 
versende kommt der Fehler, also ohne weitere Übertragungen von anderer 
Stelle.


Gruß

Bernhard

von Karl H. (kbuchegg)


Lesenswert?

Mach es einfach.
Wenns das nicht war, muss man weitersuchen. Aber erst einmal solltest du 
diese potentiell Fehlerquelle ausschalten.
Solche Race-Conditions können ganz schön tricky sein.

von Bernhard N. (bernieserver)


Lesenswert?

Ok, ich versuche es mal nachmittags. Ich muss mal davon abstand nehmen. 
sonst wird man noch ganz "baller-baller" ;)

Gruß

Bernhard

von Karl H. (kbuchegg)


Lesenswert?

1
void SendeTextUART(char StrUebertrage[],uint8_t size){
2
  if(gI)
3
    return;
4
5
  ... Rest ...
6
}

das sollte erst mal reichen.
Wenn du Texte siehst, die fehlen, dann weißt du das der beschriebene 
Fall vorliegt.

(Warten geht nicht, weil die Ausgabefunktion aus einer ISR aufgerufen 
wird. Interrups sind also gesperrt, du bist aber auf Interrupts 
angewiesen, damit der Buffer geleert wird.)

von (prx) A. K. (prx)


Lesenswert?

Wenn du den Tx-Interrupt bereits ohne Daten einschaltest, was soll der 
dann schon machen? Sich sofort wieder abschalten und lustig zusehen wie 
der Programmierer verzweifelt.

Also: Erst Puffer füllen, dann Tx-Interrupt einschalten, damit er auch 
eine Chance hat.

von Bernhard N. (bernieserver)


Lesenswert?

Danke für die Hinweise. Ich versuche mich jetzt daran :)

Gruß

Bernhard N.

von Bernhard N. (bernieserver)


Lesenswert?

So,
die Senderoutine sieht jetzt so aus, aber es ändert leider gar nichts am 
Verhalten:


Gruß

Bernhard

1
void SendeTextUART(char StrUebertrage[],uint8_t size){   
2
  
3
  if (gI == 0){
4
  // Kopieren des lokalen Arrays auf einen globalen Puffer ohne Nutzung von malloc.
5
  strncpy(gStrUebertrage,StrUebertrage,size);  
6
  
7
  gI = 0;
8
  
9
   // Zulassen des "Data Register Empty" - Interrupts
10
    UCSRB |= (1<<UDRIE);
11
12
  // Nach Übertragung des ersten Zeichen wird der "UDRE" Interrupt ausgelöst
13
  UDR = gStrUebertrage[0];
14
  }
15
}

von Bernhard N. (bernieserver)


Lesenswert?

Das ist wirklich frustrierend. Der einzigste Unterschied ist wie gesagt, 
dass der Aufruf, der funktioniert, in der Timer ISR erfolgt. Der der 
nicht funktioniert wird in der Hauptschleife aufgerufen, die aber ein 
von der Timerroutine gesetztes Flag abfragt.

Mir ist absolut unverständlich warum da so ein großer Unterschied sein 
soll.

Wer weiß weiter?


Gruß
Bernhard

von Karl H. (kbuchegg)


Lesenswert?

Anders rum
1
  // Nach Übertragung des ersten Zeichen wird der "UDRE" Interrupt ausgelöst
2
  UDR = gStrUebertrage[0];
3
4
   // Zulassen des "Data Register Empty" - Interrupts
5
    UCSRB |= (1<<UDRIE);

Zuerst befüllen, dann Interrupts freigeben.
Der Data Register Empty Interrupt wird aufgerufen, wenn das Data 
Register empty ist. Das ist auch dann der Fall, wenn du noch gar nichts 
übertragen hast.

   Empty != laufende Übertragung abgeschlossen
   Empty == zur Zeit ist keine Übertragung im Gange

Rufst du SendeTextUART aus der ISR heraus auf, sind die Interrupts 
global gesperrt. D.h. da spielt die falsche Reihenfolge keine Rolle. Die 
Empty-ISR wird erst aufgerufen, nachdem die Timer-ISR beendet ist. Zu 
diesem Zeitpunkt ist aber bereits das erste Zeichen in UDR.
Rufst du aber die Funktion aus main heraus auf, dann wird die Empty-ISR 
nach Freigabe schon aufgerufen noch ehe du das erste Zeichen in UDR 
unterbringen kannst.

von Bernhard N. (bernieserver)


Lesenswert?

Wow vielen Dank, Karl heinz Buchegger!

Genau das wars.

Jetzt kommt im HyperHyper - Terminal

test
Test
test
Test

und der Bernhard ist nun wieder zufrieden mit sich und der Welt.

Sicherlich: seltsam ist das immer noch, dass innerhalb des Interrupts 
das funktioniert hat. Aber vielleicht deshalb, da ja beim bearbeiten 
eines Int. halt alle anderen erstmal unterdrückt werden..

Gruß

Bernhard N.

von Karl H. (kbuchegg)


Lesenswert?

Bernhard N. schrieb:

> Sicherlich: seltsam ist das immer noch, dass innerhalb des Interrupts
> das funktioniert hat. Aber vielleicht deshalb, da ja beim bearbeiten
> eines Int. halt alle anderen erstmal unterdrückt werden..

Ich habe noch einen Nachsatz im vorhergehenden Post angebracht, der 
erklären soll, wie die zeitlichen Abläufe sind.

von Bernhard N. (bernieserver)


Lesenswert?

Gute Idee für welche die auch das Problem haben. Wird sicherlich 
jemandem helfen.

Gruß

Bernhard N.

von Karl H. (kbuchegg)


Lesenswert?

PS:

Gewöhn dir das generell an.
Man sieht hier im Forum ab und an immer wieder Programme, die zb einen 
Timer Interrupt freigeben, noch ehe die Timer überhaupt konfiguriert 
sind. Oder Programme die mit einem sei() anfangen.

IMHO ist das praktisch immer die falsche Reihenfolge. Zuerst alles 
einstellen und erst dann Interrupt freigeben.

von Bernhard N. (bernieserver)


Lesenswert?

Das habe ich bereits berücksichtigt. Die Initialisierungsfunktion machen 
die gesamte Interruptregisterrei. Die werden zuerst aufgerufen bevor 
sei() aufgerufen wird.

Gruß

Bernhard N.

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.