UART und Timer: Verrücktes Verhalten

OP #1494095
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
}
#1494100
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.
#1494144
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.)
OP #1494281
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
}
OP #1494325
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
#1494339
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.
OP #1494371
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.
#1494377
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.
#1494385
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren