www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Empfangsroutine und Protokollüberlegungen


Autor: Tobias (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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
//Abbarbeitung bei Zeichenempfang
ISR (USART_RXC_vect)
{
  if(UDR==255);
    {
    while (!(UCSRA & (1<<RXC)));
    ;
    buffer = UDR;
    while (!(UCSRA & (1<<RXC)));
    ;
    value = UDR;
    switch(buffer)
      {
      case 251: servo_1=value;
      case 252: servo_2=value;
      case 253: servo_3=value;
      case 254: servo_4=value;
      }
    }
}

Autor: Tobias (Gast)
Datum:

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

Autor: Aha (Gast)
Datum:

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

Autor: Tobias (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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)?
//Abbarbeitung bei Zeichenempfang
ISR (USART_RXC_vect)
{
  buffer=UDR;
  switch(bytenummer)
    {
    case 0:
      if(buffer==255) bytenummer=1;
      break;
    case 1:
      servo=buffer;
      bytenummer=2;
      break;
    case 2:
      switch(servo)
      {
      case 251: servo_1=value; break;
      case 252: servo_2=value; break;
      case 253: servo_3=value; break;
      case 254: servo_4=value; break;
      }
      bytenummer=0;
      break;
    }
}

Autor: Peter F. (piet)
Datum:

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

// UART command receive interrupt
ISR (USART_RXC_vect) {

  // save first byte as sender ID
  cmd_bf[0] = UDR;

  // set uart active flag
  uart_active = 1;

  // disable interrupt
  UCSRB &= ~(1<<RXCIE);
}

// get command string from uart
void uart_getcmd(void) {

  if (uart_active == 1) {

    // save 3 incoming bytes in buffer, cmd_bf[0] reserved and fetched in interrupt
    uint8_t i = 1;
    while (i < 4) {
      cmd_bf[i] = uart_getc();  
      i++;
    }
    
    // when complete string received set new command flag
    if (inv_cmd == 0) {
      new_cmd = 1;
    }  

    // enable uart interrupt
    UCSRB |= (1<<RXCIE);

    // clear uart active flag
    uart_active = 0;
    inv_cmd = 0;
  }
}


Kommando Auswerten:
void parse_cmd(void) {
  if (new_cmd == 1) {
  // parse received commands by type
  switch(cmd_bf[TYPE]) {

  // execute servo value
  case 0x03:

  // execute only if within valid range
  if(servo_validate(cmd_bf[SERVOPOS])) {

    // set value
    switch (cmd_bf[SERVONUMBER]) {
    case 1: servo_pos[0] = cmd_bf[SERVOPOS]; break;
    case 2: servo_pos[1] = cmd_bf[SERVOPOS]; break;
    case 3: servo_pos[2] = cmd_bf[SERVOPOS]; break;
    case 4: servo_pos[3] = cmd_bf[SERVOPOS]; break;
    case 5: servo_pos[4] = cmd_bf[SERVOPOS]; break;
    case 6: servo_pos[5] = cmd_bf[SERVOPOS]; break;
    case 7: servo_pos[6] = cmd_bf[SERVOPOS]; break;
    case 8: servo_pos[7] = cmd_bf[SERVOPOS]; break;
    case 10: servo_all(cmd_bf[SERVOPOS]); break;
    case 11: servo_odd(cmd_bf[SERVOPOS]); break;
    case 12: servo_even(cmd_bf[SERVOPOS]); break;
    default: break;
    }
  }
  default: brak;
  }
}

Autor: Tobias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich wollte mich nur kurz für die Überlegungen bedanken, meine oben 
gepostete Variante habe ich noch etwas angepasst:
ISR (USART_RXC_vect) // Interruptroutine bei Zeichenempfang
{
  buffer=UDR;        // Zwischenspeichern
  switch(bytenummer) // Abfrage der Bytenummer
  {
    case 0: // Steuerbyte (muss 255 sein)
    if(buffer==255) bytenummer=1;
    break;
    
    case 1: // Auswahlbyte (25x, x=Servonummer)
    if( (buffer>=251) && (buffer<=254) )
    {
      auswahl=buffer-251;
      bytenummer=2;
    }
    else
    {
      bytenummer=0;
    }
    break;
    
    case 2: // Datenbyte (0 bis 250)
    if(buffer<=250)
    {      
      servo[auswahl]=buffer; break;
    }
    bytenummer=0;
    break;
  }
}

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.

Autor: Peter F. (Gast)
Datum:

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

Autor: Tobias (Gast)
Datum:

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

Autor: Peter F. (Gast)
Datum:

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

Autor: Peter Dannegger (peda)
Datum:

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

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.