mikrocontroller.net

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


Autor: Bernhard N. (bernieserver)
Datum:

Bewertung
0 lesenswert
nicht 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.


#ifndef F_CPU
#warning "F_CPU wurde nicht extern in der Makefile definiert, es werden 16 Mhz angenommen."
#define F_CPU 16000000UL     /* Quarz mit 16 Mhz */
#endif
#define UBBRR_VAL 8 /* initialize USART for 115200 Baud */


///////////////////////////////////////
// Main mit Hauptschleife
///////////////////////////////////////


int main (void) {  

// Initialisierungen
InitTimer();
InitUsart();
 
// Globale Interrupts aktivieren
sei();   

while (1)
  {  
  Timeraufgaben_Abarbeiten();
  }
  return 0;
}



///////////////////////////////////////
// Timer
///////////////////////////////////////

 // Zählvariable für den Timer - ISR
 uint16_t gHeartbeat;
 uint8_t gfAufgabenLiegenVor = 0;


void InitTimer(){   

 // Schrittweite des Zählers in Millisekunden
 uint16_t Schrittweite_ms = 32;

 // Aktivierung des CTC Modes des Timer (Clear Timer on Compare Match)
 // Bewirkt ein Zurücksetzen des Timers nach Erreichen des Schwellwertes im OCR0 Register. 
 TCCR0 |= (0<<WGM00)|(1<<WGM01);      

 // Konfiguration des Timer - Prescalers: CPU - Takt / 1024. 
 // Somit wird der eigentliche Timer mit 15,625 kHz angesteuert.
 TCCR0 &= ~(1<<CS01);
 TCCR0 |= (1<<CS00)|(1<<CS02);   

 // Berechnung des Output Compare Registers
 uint16_t PrescalerFaktor =  1024; 
 OCR0 = (F_CPU * Schrittweite_ms / 1000 / (2 * PrescalerFaktor)) -1; 

 // Compare Interrupt aktivieren
 TIMSK |= (1<<OCIE0);

}

// ISR vom TimerCompare Int.

ISR(TIMER0_COMP_vect){ 

// Marke, ab der die globale Zählvariable zurückgesetzt wird.
// Zeitdauer entspricht somit der Periodendauer der gesamten Timerroutine.
// Zu errechnen ist sie mittels MaxMark * Schrittweite_ms.
uint8_t MaxMark = 64;  

uint8_t Mark1 = 32; // Marke für Timerereignis 1
uint8_t Mark2 = 16;  // Marke für Timerereignis 2
//uint8_t Mark3 = 1;  // Marke für Timerereignis 3

 
 if (gHeartbeat == MaxMark){
  gHeartbeat = 0;
 }
 if (gHeartbeat == Mark1){
  gfAufgabenLiegenVor = 1;
 }
 if (gHeartbeat == Mark2){
 
 //!!!!!!
 // HIER KLAPPT DIE ÜBERTRAGUNG EINWANDFREI
 //!!!!!!
  char text[] = "TEST \n";
  SendeTextUART(text,sizeof(text)); 
 }
  
 // Die globale Zählvariable zum Realisieren größerer Periodendauern wird hier hochgezählt
 gHeartbeat++;

}

void Timeraufgaben_Abarbeiten(){

 if(gfAufgabenLiegenVor == 1){
 
 //!!!!!!
 // HIER IST DAS EIGENTLICHE PROBLEM:
 // HIER GEHT ES NICHT SAUBER: DIE TEXTE WERDEN NUR TEILWEISE ÜBERTRAGENG
 //!!!!!!
 
  char text[] = "test \n";
   SendeTextUART(text,sizeof(text)); 
  gfAufgabenLiegenVor = 0;
 }

}




///////////////////////////////////////
// UART Teil
///////////////////////////////////////

// Globaler Eingangspuffer und Arrayindex
char gStrUebertrage[21];
uint8_t gI = 0;

void InitUsart(void)
{
  /* set the baudrate */
  UBRRH = (UBBRR_VAL >> 8) & 0x7F;  /* ensure URSEL to be zero when wrinting */
  UBRRL = UBBRR_VAL & 0xFF;

  // Aktivieren des Senden und Empfangsmodus
  UCSRB = (1<<RXEN)|(1<<TXEN);

  // Aktivierung des "Zeichen Empfangen" - Interrupts
  UCSRB |= (1<<RXCIE);

}


void SendeTextUART(char StrUebertrage[],uint8_t size){

    // Aktivierung des "Data Register Empty" - Interrupts
    UCSRB |= (1<<UDRIE);
  
  // Kopieren des lokalen Arrays auf einen globalen Puffer ohne Nutzung von malloc.
  strncpy(gStrUebertrage,StrUebertrage,size);  
  
  //Inkrement für das Arrayindex
  gI = 0;

  // Nach Übertragung des ersten Zeichen wird der "UDRE" Interrupt ausgelöst
  UDR = gStrUebertrage[0];
  
}

// ISR vom Data Register Empty Interrupt
ISR(USART_UDRE_vect){ 

   gI++; // Zuerst, da ansonsten der Interrupt "reinfunkt". Daher mit 0 initialisiert.    
  if (gStrUebertrage[gI] != 0){ // solange Nullterminierung noch nicht erreicht  
   UDR = gStrUebertrage[gI];
  }else{
   // Deaktivierung des "Data Register Empty" - Interrupts. Die Übertragung ist beendet.
     UCSRB &= ~(1<<UDRIE);
   gI = 0;
  }

}


Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich würde vorschlagen, dass du hier
void SendeTextUART(char StrUebertrage[],uint8_t size){

    // Aktivierung des "Data Register Empty" - Interrupts
    UCSRB |= (1<<UDRIE);
  
  // Kopieren des lokalen Arrays auf einen globalen Puffer ohne Nutzung von malloc.
  strncpy(gStrUebertrage,StrUebertrage,size);  
  
  //Inkrement für das Arrayindex
  gI = 0;

  // Nach Übertragung des ersten Zeichen wird der "UDRE" Interrupt ausgelöst
  UDR = gStrUebertrage[0];
  
}
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.

Autor: Bernhard N. (bernieserver)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Bernhard N. (bernieserver)
Datum:

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

Gruß

Bernhard

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
void SendeTextUART(char StrUebertrage[],uint8_t size){
  if(gI)
    return;

  ... Rest ...
}

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.)

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Bernhard N. (bernieserver)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke für die Hinweise. Ich versuche mich jetzt daran :)

Gruß

Bernhard N.

Autor: Bernhard N. (bernieserver)
Datum:

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


Gruß

Bernhard


void SendeTextUART(char StrUebertrage[],uint8_t size){   
  
  if (gI == 0){
  // Kopieren des lokalen Arrays auf einen globalen Puffer ohne Nutzung von malloc.
  strncpy(gStrUebertrage,StrUebertrage,size);  
  
  gI = 0;
  
   // Zulassen des "Data Register Empty" - Interrupts
    UCSRB |= (1<<UDRIE);

  // Nach Übertragung des ersten Zeichen wird der "UDRE" Interrupt ausgelöst
  UDR = gStrUebertrage[0];
  }
}


Autor: Bernhard N. (bernieserver)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Anders rum
  // Nach Übertragung des ersten Zeichen wird der "UDRE" Interrupt ausgelöst
  UDR = gStrUebertrage[0];

   // Zulassen des "Data Register Empty" - Interrupts
    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.

Autor: Bernhard N. (bernieserver)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Bernhard N. (bernieserver)
Datum:

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

Gruß

Bernhard N.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Bernhard N. (bernieserver)
Datum:

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

Gruß

Bernhard N.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.