Forum: Mikrocontroller und Digitale Elektronik USART Senden unterbrechen ?


von Ben B. (benmk7)


Lesenswert?

Hi, ich bin neu hier im Forum und habe ein Problemchen.

Ich nutze einen ATMEGA32A und einen Profibus DP Chip von 
Deutschmann(UNIGATE IC-PROFIBUS DP)

Diese kommunizieren via USART - völlig problemlos.
Baud: 250k

Ich habe eine ISR, welche die USART-Kommunikation festlegt:
ISR(USART_RXC_vect)

Nun habe ich zusätzlich den PINC,0 an meinem ATMEGA32A als Eingang 
definiert und empfange dort von einem anderen Mircocontroller ein paar 
Bits.

Aufgerufen wird dieser Daten-Empfang in der ISR(TIMER2_COMP_vect).

Auch das funktioniert super - solange keine Profibus kommunikation 
stattfindet.
Werden Profibus-Werte übermittelt, bekomme ich in meinem Datenempfang 
regelmäßig falsche Werte.

Meine Vermutung ist, dass die ISR für USART zu lange dauert.
Im Moment übertrage ich 80 Bytes am Stück:
1
unsigned char i, empf_byte[80]; 
2
 
3
  if(UDR != 'A') return 0;//Anfang 
4
  for(i=1; i<=79; i++) {while(!(UCSRA & (1<<RXC))); empf_byte[i]=UDR;} //warten und abholen Zeichen 
5
  if(empf_byte[79] != 'E') return 0;//Ende
6
  for(i=1; i<=78; i++) Variable[i] = empf_byte[i];
7
  return 1;

Nun habe ich Zwei Ideen zur Lösung des Problemes:

1)nicht mehr alles auf einmalschicken, sondern "Byteweises senden"
Ein Byte senden -> irgendwas tun -> nächstes Byte senden etc

2)USART ISR durch eine weitere ISR unterbrechen.


Mein Problem ist jedoch, das ich nicht so recht weis, wie ich das 
geschickt realisieren kann.

Hat hierzu eventuell jmd einen Rat oder Tipps für mich ?

Danke schon einmal im Voraus :)

Ben

--
Wenn Du statt der "avrasm"-Tags die für C-Quelltext vorgesehenen 
"c"-Tags verwendest, sieht das Syntax-Highlighting doch gleich viel 
besser aus, oder?

-rufus

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

Ben B. schrieb:
> Auch das funktioniert super - solange keine Profibus kommunikation
> stattfindet.
> Werden Profibus-Werte übermittelt, bekomme ich in meinem Datenempfang
> regelmäßig falsche Werte.

Dann darfst Du offensichtlich nur sehr kurze Interrupts verwenden.

Ben B. schrieb:
> Meine Vermutung ist, dass die ISR für USART zu lange dauert.
> Im Moment übertrage ich 80 Bytes am Stück:

Haste mal ausgerechnet wie lange das bei 250kBaud dauert..? Hint: Ein 
Byte über UART versendet sind mindestens 10 Bits.

Byteweise senden könnte klappen, muss aber nicht. Wir wissen nämlich 
nicht wie empfindlich dein Timer Compare sein muss.

von Peter II (Gast)


Lesenswert?

Ben B. schrieb:
> Mein Problem ist jedoch, das ich nicht so recht weis, wie ich das
> geschickt realisieren kann.

dafür verwende man einen ringpuffer. Die Sendefunktion schieben es nur 
in den Puffer und die ISR holt sich wenn UART frei ist, eine Zeichen 
versendet das und das wars.

Fix und fertig ist z.b. die Fleury's UART library

von Karl M. (Gast)


Lesenswert?

Hallo Ben,

arbeiten die beiden Uart als Hardware Uart und ist jeweis ein RX- und 
TX- Fifo implementiert ?

von Stefan K. (stefan64)


Lesenswert?

Ist das Deine ISR-Routine?

unsigned char i, empf_byte[80];

  if(UDR != 'A') return 0;//Anfang
  for(i=1; i<=79; i++) {while(!(UCSRA & (1<<RXC))); empf_byte[i]=UDR;} 
//warten und abholen Zeichen
  if(empf_byte[79] != 'E') return 0;//Ende
  for(i=1; i<=78; i++) Variable[i] = empf_byte[i];
  return 1;

Das hiesse, Du löst mit dem ersten Zeichen einen Interrupt aus und 
wartest dann solange innerhalb der ISR-Routine, bis 80 Zeichen empfangen 
wurden. Das ist ein etwas unorthodoxes Vorgehen ...

Sinnvoller baust Du die ISR-Routine so um, dass Du sie nach dem Empfang 
eines Zeichens sofort wieder verlassen kannst. Dazu brauchst Du neben 
Deinem Buffer noch mindestens eine diesem Buffer zugeordnete globale 
Zählervariable. Mit dieser stellst Du beim jedem Start der ISR-Routine 
fest, an welcher Stelle Deines Empfangs-Streams Du Dich befindest.

Viele Grüße, Stefan

von Karl M. (Gast)


Lesenswert?

Ben,

noch eine Frage, wie hoch ist bei Dir der Baudratenfehler, 250kBit/s ist 
schon ein Hausnummer.

Wie ist der Hardware Uart konfiguriert ?

von Pandur S. (jetztnicht)


Lesenswert?

> for(i=1; i<=79; i++) {while(!(UCSRA & (1<<RXC))); empf_byte[i]=UDR;} //warten 
und abholen Zeichen


In einer Interruptroutine ... wow, sowas von dreist. In einer Interrupt 
Routine wird sicher gar nie gewartet. Schreib das 100 mal ab und dann 
mach's richtig.

von Ben B. (benmk7)


Lesenswert?

Danke schonmal für die ganzen Tipps schonmal, so langsam wird mir 
einiges klarer.


Jim M. schrieb:
> Haste mal ausgerechnet wie lange das bei 250kBaud dauert..? Hint: Ein
> Byte über UART versendet sind mindestens 10 Bits.
>
> Byteweise senden könnte klappen, muss aber nicht. Wir wissen nämlich
> nicht wie empfindlich dein Timer Compare sein muss.


1) die Dauer müsste dann quasi 3,2ms sein, wenn ich das richtig 
gerechnet habe (800 Bits gesamt bei 250k bits/s)

2) Meine Empfang Abfrage(nicht UART) ist sehr wichtig und muss 
eigentlich die höchste Priorität haben.

Peter II schrieb:
> dafür verwende man einen ringpuffer. Die Sendefunktion schieben es nur
> in den Puffer und die ISR holt sich wenn UART frei ist, eine Zeichen
> versendet das und das wars.
>
> Fix und fertig ist z.b. die Fleury's UART library

Bei dem Wort Ringpuffer stellen sich bei mir alle Nackenhaare auf, da 
habe ich ein regelrechtes Trauma aus dem Studium mitgenommen ;)

Karl M. schrieb:
> arbeiten die beiden Uart als Hardware Uart und ist jeweis ein RX- und
> TX- Fifo implementiert ?

Hi Karl, so vertraut bi nich leider noch nicht mit der Materie, könntest 
du mir erklären was du damit meinst ? :)

Stefan K. schrieb:
> Ist das Deine ISR-Routine?
>
> unsigned char i, empf_byte[80];
>
>   if(UDR != 'A') return 0;//Anfang
>   for(i=1; i<=79; i++) {while(!(UCSRA & (1<<RXC))); empf_byte[i]=UDR;}
> //warten und abholen Zeichen
>   if(empf_byte[79] != 'E') return 0;//Ende
>   for(i=1; i<=78; i++) Variable[i] = empf_byte[i];
>   return 1;
>
> Das hiesse, Du löst mit dem ersten Zeichen einen Interrupt aus und
> wartest dann solange innerhalb der ISR-Routine, bis 80 Zeichen empfangen
> wurden. Das ist ein etwas unorthodoxes Vorgehen ...
>
> Sinnvoller baust Du die ISR-Routine so um, dass Du sie nach dem Empfang
> eines Zeichens sofort wieder verlassen kannst. Dazu brauchst Du neben
> Deinem Buffer noch mindestens eine diesem Buffer zugeordnete globale
> Zählervariable. Mit dieser stellst Du beim jedem Start der ISR-Routine
> fest, an welcher Stelle Deines Empfangs-Streams Du Dich befindest.
>
> Viele Grüße, Stefan

Das ist quasi der Empfangsabschnitt des DP, welcher in der ISR abläuft.
hier mal die Komplette ISR:
1
ISR(USART_RXC_vect)
2
//ISR(USART_RXC_vect,ISR_NOBLOCK)
3
{
4
  UCSRB &= ~(1<<RXCIE);//Interrut beim Empfangenem Zeichen gespert
5
  if(profibus_empf()) 
6
  { 
7
8
    //profibus_send();
9
    ON(M_SENDEN);
10
    dp_tout=0;
11
  }
12
  else dp_fehler++;
13
  UCSRB |= (1<<RXCIE);//Interrut beim Empfangenem Zeichen frei
14
  
15
}

Der Sende-Teil wird mittels Flag(M_Senden) außerhalb der ISR gestartet.

Ich hatte mit das so überlegt:
In der ISR rufe ich zunächst den Empfangscode auf und zähle danach einen 
Zähler hoch bis maximal auf 79. Ich möchte ein Zeichen empfangen und es 
der entsprechenden Variablen (vom Zählerwert abhängig) zuordnen.

Quasi nach jedem Empfangen Zeichen, wird die ISR verlassen und findet 
wieder Statt, sobald ein neues Zeichen ankommt.


Muss ich das gleiche dann auch für den Sendeabschnitt machen, obwohl der 
außerhalb der ISR liegt?

Sapperlot W. schrieb:
>> for(i=1; i<=79; i++) {while(!(UCSRA & (1<<RXC))); empf_byte[i]=UDR;} //warten
> und abholen Zeichen
>
> In einer Interruptroutine ... wow, sowas von dreist. In einer Interrupt
> Routine wird sicher gar nie gewartet. Schreib das 100 mal ab und dann
> mach's richtig.

Das hat bis hierhin auch Problemlos funktioniert, da zuvor keine 
Zeitkritischen Operationen stattfanden.

Ich habe ein bestehendes Projekt um eine zeitkritische Kommunikation 
erweitert, dadurch muss ich diesen Abschnitt ja ändern. :)

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Ben B. schrieb:
> for(i=1; i<=78; i++) Variable[i] = empf_byte[i];

Das nennt sich memcpy:
1
#include <string.h>
2
  memcpy( Variable, empf_byte, 78 );

Ben B. schrieb:
> Bei dem Wort Ringpuffer stellen sich bei mir alle Nackenhaare auf, da
> habe ich ein regelrechtes Trauma aus dem Studium mitgenommen ;)

Warum?
Jeder nimmt für die UART ne FIFO. Das ist kein Hexenwerk, nimmt Dir aber 
zeitkritische Arbeit ab.

von A. S. (Gast)


Lesenswert?

Du hast also ein Interrupt-Routine, beispiel void int_rx(void), die 
Deinen geposteten Code (Beispiel int decode(void)) aufruft und nachher 
ein flag setzt, wenn der mit 1 zurückkommt?

also z.B.
1
int fertig = 0;
2
int decode(void);
3
unsigned char Variable[79];
4
5
INTERRUPT void int_rx(void)
6
{
7
   if(!fertig) fertig = decode();
8
}
9
10
int decode(void)
11
{
12
    ....
13
}
ja? Dann schreibe decode so um:
1
static unsigned char empf_byte[80]; 
2
static unsigned char i = 0;
3
static unsigned char run = 0;
4
unsigend char c=UDR;
5
 
6
7
  if(!run)
8
  { 
9
     if(c == 'A') {run=1;};
10
     i = 0;
11
     /* warum speicherst Du das 'A' nicht ab? */
12
     /* ah, verstehe... darum hier der Trick mit uchar c! */ 
13
     return 0;
14
  }   
15
  i++;
16
  if(i<79) {empf_byte[i]=c; return 0;}
17
  /* Fehler ? Alles auf Anfang ? warum plenke ich ? */
18
  if(c != 'E') 
19
  { 
20
      run=0; 
21
      return 0; 
22
  }
23
  /* alles OK! schnell noch kopieren, bevor sich einer beschwert!! */
24
  for(i=1; i<=78; i++) Variable[i] = empf_byte[i];
25
  run=0;
26
  return 1;
27
}

Das ist zwar immer noch unschön, da
 a) das Kopieren (grundlos) hier in der ISR ist,
 b) zuviele komische Verschiebungen (+1, -1, <= ) stattfinden
 c) würdest Du das erste Byte mit kopieren, wäre der Code viel sauberer 
und kleiner
aber es
- wartet nicht im Interrupt,
- hat einen sehr einfachen Fifo für Fifotraumatisierte
und man kann ja immer nur einen schritt nach dem anderen gehen.

von Amateur (Gast)


Lesenswert?

Den Du irgendwas Byteweise verschickst, musst Du doch sowieso nach jedem 
Byte fragen: "Seid Ihr denn auch alle da"?

Im Normalfall ist an dieser Stelle ist dann Feierabend angesagt oder 
weitermachen. Ist aber auch 'ne gute Stelle für 'ne Pinkelpause. Der 
Zähler und die Zeiger, rund um den Puffer, sollten aktuell sein und eine 
Unterbrechung somit kein Problem.

von Amateur (Gast)


Lesenswert?

Ich vergaß!
Wenn die Programmstruktur nicht total daneben ist, sollte das Programm 
folgendermaßen aussehen:

1. Eine Hintergrundroutine, die mit einem Ringpuffer zusammen arbeitet.
2. Eine Funktion zum "füttern" des Ringpuffers, die auch die Übertragung
   startet.

Nach der Übergabe des aktuellen Bytes werden die Zeiger im Ringpuffer 
aktualisiert. Dann wird überprüft ob der Puffer leer ist oder ein neu 
einzufügendes Flag, für Unterbrechung, gesetzt ist.
An dieser Stelle erfolgt sowohl wenn der Puffer leer ist, als auch bei 
einer Abbruchanforderung das gleiche.
Es gibt natürlich einen Sonderfall: Unterbrechung UND Puffer leer.

Da das Senden üblicherweise mit dem füttern des Puffers gestartet wird, 
gibt es natürlich eine kleine Änderung.
Entweder Du schreibst einen neuen Wert in den Puffer, wobei dann an der 
"letzten" Stelle weitergemacht wird oder Du baust, in die Hauptroutine, 
eine Kombination wie:
Unterbrochen UND ProblemGeloest UND NochWasImPuffer: Starte USART ein.

Ein interessantes Problem ergibt sich aber, wenn die Unterbrechung 
augenblicklich erfolgen muss. Die meisten USARTs schubsen das Byte, 
welches sich gerade im Sendepuffer befindet bedingungslos raus. 
Allerdings muss für diesen Sonderfall auch der Empfänger befummelt 
werden.

von Ben B. (benmk7)


Lesenswert?

Achim S. schrieb:

> Das ist zwar immer noch unschön, da
>  a) das Kopieren (grundlos) hier in der ISR ist,
>  b) zuviele komische Verschiebungen (+1, -1, <= ) stattfinden
>  c) würdest Du das erste Byte mit kopieren, wäre der Code viel sauberer
> und kleiner
> aber es
> - wartet nicht im Interrupt,
> - hat einen sehr einfachen Fifo für Fifotraumatisierte
> und man kann ja immer nur einen schritt nach dem anderen gehen.


Du bist meine Rettung, genau das hat mir schon geholfen !!!!!

Scheinbar läuft jetzt alles, werde mich am Montag nochmal intensiv damit 
auseinander setzen.

Besten Dank schon einmal - auch an alle anderen, die mir Tipps gegeben 
haben - und ein schönes Wochenende !

So sollte Unterstützung in einem Forum aussehen ! :)

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.