Forum: Mikrocontroller und Digitale Elektronik Empfangsroutine und Protokollüberlegungen


von Tobias (Gast)


Lesenswert?

Hallo,

ich würde gerne von euch wissen, ob die nachfolgende Überlegung sinnvoll 
ist, oder ob ihr einen anderen Ansatz wählen würdet. Ich möchte mit 
einem Atmega8 vier Servos ansteuern, dafür möchte ich 250 Positionen 
festlegen die per USB übertragen werden sollen.

Dazu habe ich mir folgendes Protokoll ausgedacht: Es wird immer als 
erstes Zeichen ASCII/255 gesendet, dann ASCI/251-254 zur Auswahl des 
Servos und letztendlich der Wert als ASCII/0-250.

Erste Frage: Ist das sinnvoll, oder geht es einfacher?

Zweite Frage: Wird die unten angegebene Interruptroutine dem oben 
beschriebenen Protokoll gerecht? Wenn ich es richtig verstanden habe 
wird ja bei Eintritt in die Interruptroutine diese nicht wieder von 
einem Interrupt unterbrochen. Und jedesmal beim auslesen des UDR wird 
das RXC wieder gelöscht. Sollte im UDR nicht der Schlüsselwert gelesen 
werden beim Eintritt in den Interrupt, wird dieser verlassen und beim 
nächsten Zeichen wieder aufgerufen.

Danke fürs drüberschauen, bevor ich das so weiterverfolge.

Gruß,
Tobias
1
//Abbarbeitung bei Zeichenempfang
2
ISR (USART_RXC_vect)
3
{
4
  if(UDR==255);
5
    {
6
    while (!(UCSRA & (1<<RXC)));
7
    ;
8
    buffer = UDR;
9
    while (!(UCSRA & (1<<RXC)));
10
    ;
11
    value = UDR;
12
    switch(buffer)
13
      {
14
      case 251: servo_1=value;
15
      case 252: servo_2=value;
16
      case 253: servo_3=value;
17
      case 254: servo_4=value;
18
      }
19
    }
20
}

von Tobias (Gast)


Lesenswert?

Achso, jetzt habe ich doch tatsächlich die brakes bei der Caseanweisung 
vergessen, sorry. Und noch was ist mir eingefallen, sollte man das UDR 
vor der UDR==255-Abfrage vielleicht lieber auch in einen Puffer 
schreiben und erst dann die if-Abfrage durchführen, oder bleibt sich das 
gleich?

von Aha (Gast)


Lesenswert?

In einer ISR wartet man sicher nicht. Mach eine Zustandsmaschine 
aussenrum.

von Tobias (Gast)


Lesenswert?

>In einer ISR wartet man sicher nicht. Mach eine Zustandsmaschine
>aussenrum.

Also ungefähr so (evtl noch in case 1 und 2 die Abfrage des 
Wertebereichs, aber vom Prinzip her)?
1
//Abbarbeitung bei Zeichenempfang
2
ISR (USART_RXC_vect)
3
{
4
  buffer=UDR;
5
  switch(bytenummer)
6
    {
7
    case 0:
8
      if(buffer==255) bytenummer=1;
9
      break;
10
    case 1:
11
      servo=buffer;
12
      bytenummer=2;
13
      break;
14
    case 2:
15
      switch(servo)
16
      {
17
      case 251: servo_1=value; break;
18
      case 252: servo_2=value; break;
19
      case 253: servo_3=value; break;
20
      case 254: servo_4=value; break;
21
      }
22
      bytenummer=0;
23
      break;
24
    }
25
}

von Peter F. (piet)


Lesenswert?

Hier ein kleiner Auszug wie ich es gelöst habe, es funktioniert, wie 
elegant es ist weiss ich nicht ;-)
String Start/Stopbits benutze ich nicht, nur ein rudimentäres System 
gegen zu lange und zu Kurze Kommandos.

Ein Kommando hat immer 4 Bytes bei mir:
Sender ID
Kommando Typ
Wert 1
Wert 2

Der RX-Interrupt setzt das Flag das das Kommando pollt, inklusive 
Timeout:
1
// UART command receive interrupt
2
ISR (USART_RXC_vect) {
3
4
  // save first byte as sender ID
5
  cmd_bf[0] = UDR;
6
7
  // set uart active flag
8
  uart_active = 1;
9
10
  // disable interrupt
11
  UCSRB &= ~(1<<RXCIE);
12
}
13
14
// get command string from uart
15
void uart_getcmd(void) {
16
17
  if (uart_active == 1) {
18
19
    // save 3 incoming bytes in buffer, cmd_bf[0] reserved and fetched in interrupt
20
    uint8_t i = 1;
21
    while (i < 4) {
22
      cmd_bf[i] = uart_getc();  
23
      i++;
24
    }
25
    
26
    // when complete string received set new command flag
27
    if (inv_cmd == 0) {
28
      new_cmd = 1;
29
    }  
30
31
    // enable uart interrupt
32
    UCSRB |= (1<<RXCIE);
33
34
    // clear uart active flag
35
    uart_active = 0;
36
    inv_cmd = 0;
37
  }
38
}

Kommando Auswerten:
1
void parse_cmd(void) {
2
  if (new_cmd == 1) {
3
  // parse received commands by type
4
  switch(cmd_bf[TYPE]) {
5
6
  // execute servo value
7
  case 0x03:
8
9
  // execute only if within valid range
10
  if(servo_validate(cmd_bf[SERVOPOS])) {
11
12
    // set value
13
    switch (cmd_bf[SERVONUMBER]) {
14
    case 1: servo_pos[0] = cmd_bf[SERVOPOS]; break;
15
    case 2: servo_pos[1] = cmd_bf[SERVOPOS]; break;
16
    case 3: servo_pos[2] = cmd_bf[SERVOPOS]; break;
17
    case 4: servo_pos[3] = cmd_bf[SERVOPOS]; break;
18
    case 5: servo_pos[4] = cmd_bf[SERVOPOS]; break;
19
    case 6: servo_pos[5] = cmd_bf[SERVOPOS]; break;
20
    case 7: servo_pos[6] = cmd_bf[SERVOPOS]; break;
21
    case 8: servo_pos[7] = cmd_bf[SERVOPOS]; break;
22
    case 10: servo_all(cmd_bf[SERVOPOS]); break;
23
    case 11: servo_odd(cmd_bf[SERVOPOS]); break;
24
    case 12: servo_even(cmd_bf[SERVOPOS]); break;
25
    default: break;
26
    }
27
  }
28
  default: brak;
29
  }
30
}

von Tobias (Gast)


Lesenswert?

Ich wollte mich nur kurz für die Überlegungen bedanken, meine oben 
gepostete Variante habe ich noch etwas angepasst:
1
ISR (USART_RXC_vect) // Interruptroutine bei Zeichenempfang
2
{
3
  buffer=UDR;        // Zwischenspeichern
4
  switch(bytenummer) // Abfrage der Bytenummer
5
  {
6
    case 0: // Steuerbyte (muss 255 sein)
7
    if(buffer==255) bytenummer=1;
8
    break;
9
    
10
    case 1: // Auswahlbyte (25x, x=Servonummer)
11
    if( (buffer>=251) && (buffer<=254) )
12
    {
13
      auswahl=buffer-251;
14
      bytenummer=2;
15
    }
16
    else
17
    {
18
      bytenummer=0;
19
    }
20
    break;
21
    
22
    case 2: // Datenbyte (0 bis 250)
23
    if(buffer<=250)
24
    {      
25
      servo[auswahl]=buffer; break;
26
    }
27
    bytenummer=0;
28
    break;
29
  }
30
}

1.Ich nutze jetzt ein Array zum Ablegen der Werte, spart die zweite 
case-Abfrage
2. Der Wertebereich wird noch geprüft und evtl. wieder auf Byte 0 
resettet

Ich bin sehr glücklich mit meiner ersten Ansteuerung über USB, läuft 
besser als erwartet.

von Peter F. (Gast)


Lesenswert?

Genau das ist sehr suboptimal.
Da werden die Servos aus dem Takt kommen wenn du zuviel/zuoft Daten über 
den UART empfängst.

In den Interrupt gehört nur was unbedingt notwendig ist, nicht mehr.
Ich hab auch erst gedacht "das macht schon nix" und dann Probleme 
bekommen, seitdem wird der Code auf ein minimum begrenzt.
Wie in meinem Beispiel, nur ein Flag setzen und vielleicht ein Paket 
abholen, mehr nich.

Mfg,
Peter

von Tobias (Gast)


Lesenswert?

>Genau das ist sehr suboptimal.
>Da werden die Servos aus dem Takt kommen wenn du zuviel/zuoft Daten über
>den UART empfängst.
Werden sie deshalb nicht, weil vor dem Ansteuern eines Servos ein cli() 
und danach ein sei() steht, die Ansteuerung eines jeden Servos stimmt 
also weiterhin, wenn ich Glück habe geht noch nicht einmal ein Byte 
verloren.

Auch die maximal zu sendenden Daten halten sich in Grenzen, denn deren 
Menge bestimmt sich eigtl. einzig und allein von der 
Ausführgeschwindigkeit der Software die die Daten generiert und das ist 
die begrenzende Größe in meinem System (evtl. noch die 
Stellgeschwindigkeit der Servos selbst), im Moment sende ich Daten alle 
5ms und es klappt hervorragend, mehr braucht es nicht und dafür reicht 
es nach meinen bisherigen Tests, aber falls es Probleme gibt, weiß ich 
wo ich ansetzen muss und ich werde mich gleich auf die ISR stürzen, 
danke für den Hinweis.

von Peter F. (Gast)


Lesenswert?

Ich war jetzt davon ausgegangen das du soft-PWM benutzt, da würde es 
nichts bringen vorher die interrupts zu sperren.
Bei Hardware-PWM hast du allerdings recht.

Ich steuere 8 Servos mit Soft-PWM per UART da gibt es probleme wenn 
zulang im UART Interrupt getrödelt wird.
Trotzdem bleib ich dabei, state-machine im Interrupt ist böse. ;-P

von Peter D. (peda)


Lesenswert?

Peter F. wrote:
> Ich steuere 8 Servos mit Soft-PWM per UART da gibt es probleme wenn
> zulang im UART Interrupt getrödelt wird.
> Trotzdem bleib ich dabei, state-machine im Interrupt ist böse. ;-P

Das ist Quatsch mit Soße.
Statemachines sind keine Zeitfresser, wie Delays, Division, float, 
printf.

Größere Statemachines werden als Sprungtabelle ausgeführt, so daß selten 
>20 Zyklen benötigt werden.

Der Interrupthandler ist vollkommen o.k., dürfte <100 Zyklen sein.
Kürzer gehts nur unwesentlich.



Peter

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.