UART - Empfangspuffer

Ich möchte Pakete über UART empfangen und senden. Das Problem ist, dass ich nicht sofort auf ein empfangenes Byte reagieren möchte, sondern auf eine bestimmte Sequenz. Diese Sequenz besteht aus 4 UART-Bytes und wird erst ausgewertet, wenn sie vollständig empfangen wurde.

Was ist der beste Weg, das zu tun? Erstellen Sie nur ein Array, das zu den 4 Bytes passt, und prüfen Sie nach Erhalt von 4 Bytes, ob es korrekt ist, oder wäre ein größerer Ringpuffer besser, in dem alle empfangenen Bytes nacheinander gespeichert werden? (keine Ahnung wie man das umsetzt)

Beispiel einer Übertragung:

-PC sendet Nullen, solange er keine Antwort erhält

-Ein kleines Mikrocontroller-Board ist angeschlossen und empfängt die Bytes

- Wenn 4 Bytes Null vom Mikro empfangen werden, sendet es seine 4 Bytes an den PC

- Der PC sieht die Daten auf seinem TXD und hört auf, die Nullen zu senden, und tut, was vom Mikro angefordert wurde. Wenn es fertig ist, sendet der PC 4 Bytes zurück an das Mikro, um seine Anfrage zu bestätigen und ihm mitzuteilen, dass alles bereit ist.

Wie setzt man so etwas am besten um?

Was (im Großen und Ganzen) versuchst du zu erreichen?
Ringpufferung und Ziehen nach Belieben zum Zusammenbauen zu Paketen, die verarbeitet werden, wenn sie vollständig sind, ist praktikabel und konzeptionell einfach. Das Puffern, das die Verarbeitung nach Abschluss eines Pakets auslöst, ist möglicherweise eher auf eine bestimmte Anwendung abgestimmt, aber nicht unbedingt die Mühe wert, es sei denn, es gibt ein Problem mit dem ersten Ansatz.
@Andy alias ich möchte ein Gerät (oder besser verschiedene Gerätetypen) kalibrieren. Der PC baut eine gewünschte Spannung auf. Jedes Gerät kennt seinen Typ und fordert die spezifische Spannung an, die es benötigt. Wenn der PC bereit ist, soll er dem Controller mitteilen, dass er die Messung mit dem ADC starten kann.
@canbus Das interne Design des mit einem UART verbundenen Hardwarepuffers variiert von einer Mikrocontrollerfamilie zur anderen. Haben Sie einen bestimmten Mikrocontroller im Sinn?
@Chris Stratton In einer Ringpufferanordnung müsste ich die eingehenden Bytes zählen und zwei Zeiger inkrementieren, einen für die Datenposition und einen für die zuletzt gelesenen Daten, habe ich Recht?
@ Nick Alexeev Der Controller stammt aus der STM32F030-Familie.

Antworten (2)

Zuerst empfehle ich ein Protokoll, eine Rahmenstruktur, idealerweise ein Startmuster, das nicht in den Daten gefunden wird oder wenn es gefunden wird, sich nicht wiederholt, oder ein Startmuster und eine Prüfsumme hat. In jedem Fall müssen Sie Ihr System-Engineering/-Design durchführen, um sicherzustellen, dass Sie nicht mit den falschen Bytes oder Bytes an der falschen Position arbeiten.

Zweitens führen Sie Ihre Systemtechnik durch, wie hoch ist Ihre Datenrate, wie hoch sind die Verarbeitungs- und Ausführungsraten? Wenn Sie beispielsweise die Daten im Wert von vier Bytes innerhalb eines Bytes Übertragungszeit mit genügend Zeit verarbeiten können, wäre dies eine einfachere Lösung. Sie müssen die 4 Bytes an Informationen natürlich irgendwo speichern, sei es im Uart, wenn der Uart ein Fifo hat, das tief genug ist, oder außerhalb des Uart, wenn nicht.

Nun, muss der Host tatsächlich ständig etwas übertragen, oder kann das Protokoll ein Burst eines Pakets für Sie sein, dann senden Sie eine Antwort zurück? Die Frage für Ihr Systemdesign lautet: Muss ich neue eingehende Bytes handhaben, während ich eine vorhandene Anfrage verarbeite?

Wenn Sie Daten übergeben müssen, während Sie andere verarbeiten, und die Verarbeitung eines Pakets mehr als ein Byte Zeit in Anspruch nimmt, ist es wahrscheinlich besser, einen Ringpuffer einzuplanen, dessen Größe Sie wahrscheinlich anpassen müssen, da Sie ihn wahrscheinlich nicht haben genug Ressourcen, um es groß genug zu machen, um sich nicht darum zu kümmern. Außerdem müssen Sie sicherstellen, dass Sie während der Verarbeitung der aktuellen Daten die neuen Daten erfassen (wenn das Protokoll erfordert, dass Sie mehr als eine gleichzeitig verarbeiten). Interrupts können hier ins Spiel kommen, sind aber schwierig zum Laufen zu bringen, Interrupts sind keine erforderliche Lösung, aber oft entscheiden sich die Leute dafür.

Wenn Sie sich für einen Paket/Frame-Ansatz entscheiden, brauchen Sie auf jeden Fall einen Fifo, der Daten im Wert von mindestens einem Paket sammelt. Jedes neu eingehende Byte verschiebt die Daten durch ein Fenster (oder verwenden Sie besser einen Ringpuffer und verschieben Sie Ihren Kopf und Endzeiger herum), sobald Sie ein Startmuster und mindestens die richtige Anzahl von Bytes für das Paket haben, dann prüfen Sie es, wenn Sie es so implementieren möchten, und wenn es bestanden wird, verarbeiten Sie es, wenn nicht, werfen Sie das Ganze einfach um der Head-Zeiger zum nächsten Startmuster, falls es eines in den akkumulierten Daten gibt, andernfalls vergleiche es mit dem Tail und warte auf mehr.

Wenn Sie Daten zurücksenden, dasselbe Abkommen, was ist das Protokoll dafür, Rahmen-/Datenformat? Es wird angenommen, dass es sich um die gleiche Baudrate handelt. Wenn Sie also, wie Sie erwähnt haben, vier akzeptieren und vier zurücksenden, dauert es in jeder Richtung gleich lang, selbst wenn es eine kleine Anzahl von MCU-Taktzyklen dauert, um die Daten zu verarbeiten, dauert es immer noch a viele Taktzyklen, um es mit dieser Baudrate zurückzusenden. Wenn die Hardware kein TX-FIFO hat und Ihr Protokoll nicht halbduplex ist, müssen Sie warten, bis sich ein TX-Slot öffnet, und das nächste Byte hineinschieben dort von irgendeinem Puffer/Ring. Das Warten kann Polling oder Interrupts sein, gleiche Antwort wie oben, MUSS keines von beiden sein, kann das sein, womit Sie sich wohlfühlen und was funktioniert, für TX ist es viel einfacher, es sei denn, das Protokoll hat eine Zeitüberschreitung, da Sie keine Bytes verlieren können auf dem Weg hinaus, Wenn Ihr Code nicht schnell genug ist, fügen Sie einfach Lücken hinzu. Auf der RX-Seite der Welt können Sie Bytes verlieren, wenn Sie nicht schnell genug sind.

All dies sind sehr grundlegende Dinge und hoffentlich keine Dinge, die Sie bereits kannten und herausgefunden hatten. Beginnen Sie einfach, senden Sie einen Datenblock vom PC, zeigen Sie Ihrer Debug-Lösung irgendwie von der MCU an (für mich ist es das Drucken eines UART oder das Blinken einer LED), dass Sie die Daten haben. Entwickeln Sie dann entweder die Antwort mit falschen Daten oder entwickeln Sie als nächstes den Verarbeitungsschritt. Führen Sie im Grunde Ihr System-Engineering durch, zerlegen Sie das Problem in mundgerechte Stücke, definieren Sie die Schnittstellen zwischen den Stücken und entwickeln Sie jedes Stück und kleben Sie es dann zusammen. Die Grenzen zwischen den Stücken sind Gelegenheiten, falls erforderlich, Testcode/Instrumentierung zu platzieren, um dies zu testen Stück. Einige dieser Chunks können möglicherweise auf einem Host-Computer mit einer gängigen Programmiersprache (C ist eine gute) mit einer Testvorrichtung um den Code herum entwickelt werden. Das wäre natürlich nur für die generische Verarbeitung, wenn Sie MCU/Board-Hardware-spezifische Ressourcen benötigen, müssten Sie diese entweder auf einem Host simulieren (mehr Code zum Entwickeln und Testen, um den zu testenden Code zu umschließen). Das bedeutet nicht unbedingt einen Simulator für diese Umgebung, aber es könnte nützlich sein, einen zu finden/zu entwickeln. Es hängt alles davon ab, wie schwierig das Problem wird.

Leistung, wenn ich sage, dass ein Byte Zeit wert ist, verstehen Sie, dass das einfache Hinzufügen oder Entfernen einer Codezeile die Leistung des Codes um einen spürbaren Betrag von 10 % bis 20 % oder mehr ändern kann. Sogar derselbe Code mit geänderter Ausrichtung (mehr oder weniger Anweisungen im Bootstrap) kann und wird manchmal die Leistung des Codes um bis zu 10 % oder 20 % oder mehr ändern. Das einfache Hochtakten des MCU um den Faktor N, um mehr Leistung zu erzielen, führt nicht dazu, dass derselbe Code N-mal schneller ausgeführt wird, insbesondere wenn der Flash langsamer als die maximale Geschwindigkeit des MCU-Takts ist und Sie beim Erhöhen zusätzliche Wartezustände setzen müssen die MCU-Geschwindigkeit, um zu versuchen, die Verarbeitungsanforderungen zu kompensieren. Erwägen Sie, von Sram aus zu laufen, wenn Sie genug haben, wenn Sie darauf stoßen, aber es gibt keine Garantie dafür, dass die RAM- oder Speicherschnittstelle auch für höhere Taktraten ohne Wartezustände ausgeführt wurde. Ebenso kann die CPU-Uhr eine Rate gehen, aber das bedeutet nicht, dass die peripheren Busse auf dieser Uhr sind. Der einzige Weg, dies zu erreichen, besteht darin, zu testen und zu messen (Michael Abrash Zen von Assembly Language, schauen Sie über die x86-Natur dieses Buches hinaus auf die Kernkonzepte zum Auffinden von Cycle Stealers, können kostenlos auf Github afaik gefunden werden)

Sehr kurze Antwort, führen Sie Ihr System-Engineering durch, hat die Hardware einen Puffer, welche anderen Ressourcen haben Sie? wie viel Speicherplatz benötigen Sie für die Kommunikation, um keine Daten zu verlieren oder ins Hintertreffen zu geraten. Offensichtlich müssen Sie eine gewisse Anzahl von Bytes speichern, bevor Sie die Verarbeitung durchführen können, sodass Sie mindestens so viel Speicherplatz benötigen. Wie viel darüber hinaus geht, hängt von Ihrem Protokoll und der Analyse ab, wie lange es dauern sollte, die Dinge zu erledigen. Oder gestalten Sie das Protokoll als Halbduplex-Handshake und nehmen Sie sich so viel Zeit, wie Sie möchten.

Wenn die Datenlesevorgänge generisch sein müssen, z. B. wenn Sie mehrere Textzeichenfolgen analysieren, müssen Sie tatsächlich einen FIFO-Ringpuffer implementieren (was ziemlich trivial ist, sollte im Internet reichlich Code dafür enthalten).

Wenn die Daten nicht generisch sind, sondern nur eine einzige konstante Folge, wie z. B. ein Synchronisationswort, dann benötigen Sie keinen Puffer. Einfach Zeichen für Zeichen lesen.

Konzeptioneller Pseudocode:

#define LENGTH 4

void read_uart (void)
{
  static const char sync [LENGTH] = "WORD";
  static uint8_t counter = 0;

  char ch;

  uart_err_t error = uart_receive(&ch);

  if(!error && ch == sync[counter])
  {
    counter++;
  }
  else
  {
    counter=0; // reset

    if(error)
    {
      // handle errors, frame/overrun error etc etc
    }
  }

  if(counter == LENGTH)
  {
    // whole sync word found, everything ok
  }
}