FIFO-bedingte Datenübertragungsprobleme zwischen Mikrocontroller und PC

Ich habe eine Situation, in der ein Mikrocontroller eine große Anzahl von ADC-Konvertierungen durchführen und die Ergebnisse in Befehle (oder Datenpakete) formatieren und diese mithilfe des UART an einen PC senden soll. Um während des Sendens von Daten kontinuierlich zu messen, habe ich einen Ringpuffer (Warteschlange / FIFO-Puffer) zum Speichern der anstehenden Pakete erstellt, und der Mikrocontroller wird diese Warteschlange dann (idealerweise) so schnell leeren, wie es der PC zulässt.

Der Mikrocontroller sendet automatisch den nächsten Befehl (falls vorhanden), nachdem ein ACK-Zeichen (in diesem Fall ein '!') empfangen wurde. Der UART-Interrupt-Handler sieht folgendermaßen aus:

if (ch == '!') // ch is the received character
{
    cmd_rx_ack = '!';
    cmd_tx_pop_cmd();
}

Die Funktion cmd_tx_pop_cmd() nimmt ein Paket aus dem Ringpuffer und legt die Bytes in ein cmd_tx[]-Array und die Länge des Pakets in cmd_tx_length.

In der Hauptschleife:

if (cmd_rx_ack == ‘!’)
{
    cmd_rx_ack = 0; // Reset ack status

    // Transmit the command
    if (cmd_tx_length > 0)
    {
        cmd_tx_transmit();
    }
}

Die Funktion cmd_tx_transmit() überträgt einfach die Bytes in cmd_tx[] einzeln (dies geschieht in der Hauptschleife, da dies im UART-Handler zu lange dauert).

Dies funktioniert gut, wenn der Verbraucher der Warteschlange (in diesem Fall der UART-Handler) eine höhere Priorität hat als der Erzeuger (ein Timer, der den ADC regelmäßig konvertiert und ein Datenpaket mit dem Ergebnis in die Warteschlange schiebt). Früher hatte ich Parallelitätsprobleme (siehe Parallelitätsprobleme mit Ringpuffer im eingebetteten Programm ), aber jetzt habe ich ein anderes Problem:

Ich möchte, dass der Mikrocontroller das nächste Paket in der Warteschlange nicht nur sendet, wenn ein ACK empfangen wurde, sondern auch in den unten aufgeführten Situationen:

  1. Das erste zu übertragende Paket
  2. Wenn die Messungen mit einer Geschwindigkeit durchgeführt werden, die langsamer ist als die Zeit, die zum Versenden eines Pakets benötigt wird.

In der ersten Situation wird das Paket nie übertragen, da kein ACK empfangen wird, nachdem das Paket in die Warteschlange geschoben wurde.

In der zweiten Situation werden die Pakete aus dem gleichen Grund wie in der ersten Situation nie aus der Warteschlange entfernt. (Angenommen, das erste Paket wird bestätigt und der UART-Handler möchte das nächste senden, aber die Warteschlange ist zu diesem Zeitpunkt leer). In diesem Fall wird die Warteschlange einfach immer größer und es werden keine Pakete übertragen.

Ich kann das erste lösen, indem ich das erste Paket manuell (ohne Verwendung der Warteschlange) übertrage und die restlichen in die Warteschlange stelle. Keine schöne Lösung, aber es funktioniert ... Ich muss noch das zweite Problem lösen.

Daher denke ich, dass der UART-Handler nicht der richtige Ort ist, um das Queue-Popping durchzuführen, da er die beiden Situationen, die ich gerade erwähnt habe, nicht berücksichtigen kann. Aber ich kann es nicht in die Hauptschleife stellen, da es die niedrigste Priorität hat und dann Parallelitätsprobleme zu einem Problem werden.

Was wäre ein guter Weg, um dies zu implementieren?

Vielen Dank im Voraus :-)

BTW 1: Ein ACK wird an den Mikrocontroller gesendet, wenn der PC das gesendete Paket angenommen hat. Ich habe eine spezielle Codierung der Pakete vorgenommen, damit ihre Länge bestimmt werden kann. Dies funktioniert einwandfrei und cmd_tx_pop_cmd() wird nur das erste verfügbare Paket in der Warteschlange öffnen. Ich habe das alles viele, viele Male getestet und es funktioniert perfekt, also konzentrieren Sie sich bitte nicht auf diesen Teil, da es nicht das Problem ist.

BTW 2: Der Mikrocontroller ist ein TM4C123GH6 der Tiva C-Serie, wie er auf dem Launchpad der Tiva C-Serie zu finden ist. Ich verwende gcc-arm-none-eabi.

Ein paar Fragen: (1) Was passiert, wenn der PC Ihr Gerät nicht abfragt und das Fifo überläuft? (2) Wenn der PC kein ACK sendet (möglicherweise geht die Übertragung verloren), was macht Ihr Gerät?
(1) Der FIFO läuft nicht über, da die Messungen angehalten werden, wenn im Puffer kein Platz für ein neues Paket vorhanden ist. Der Messtimer startet dann erneut, sobald die Warteschlange leer ist. Dies funktioniert gut, solange der FIFO noch Pakete enthält, wenn ein ACK empfangen wird. Wenn nicht, stoße ich auf das in meiner Frage beschriebene Problem. (2) Das Gerät muss wahrscheinlich zurückgesetzt werden, um wieder zu funktionieren. Ich denke, ich werde eine Funktion zum Zurücksetzen der Kommunikation implementieren, die alle TX- und RX-Puffer nach einer Sekunde ohne Aktivität auf dem UART löscht. Dies sollte einfach zu implementieren sein, da ich das zuvor getan habe.

Antworten (2)

Generell muss ein interruptgesteuertes Sendeprotokoll immer irgendwo außerhalb des Interrupts gestartet werden.

Aber braucht man wirklich Interrupts? Das klingt nach einer ganz einfachen Anwendung: Hauptschleife prüft auf einige mögliche Situationen:

  • Timer abgelaufen: A/D-Wandlung starten
  • A/D-Wandlung abgeschlossen: Ergebnis in Warteschlange stellen
  • ! empfangen: Senden erlaubt einstellen
  • Senden erlaubt und nicht senden: wenn Warteschlange nicht leer: Ergebnis ausgeben; klare Durchfahrt erlaubt; Senden einstellen
  • uart-Puffer leer: Nächstes Zeichen übertragen oder Übertragung löschen, wenn keines verfügbar ist

Der Sendeteil kann gelöscht werden, wenn er in einer STD ausgedrückt wird.

Danke für Ihre Antwort. Ich habe versucht, das erste Paket zu senden, indem ich eine boolesche Variable gesetzt habe, die meine Hauptschleife abfragt. Es wird dann die Warteschlange öffnen und übertragen. Es könnte die erste Situation lösen. In der zweiten Situation arbeiten der UART-Handler und die Hauptschleife nicht gut zusammen (der UART-Handler möchte möglicherweise Bytes aus der Warteschlange entnehmen, während die Hauptschleife dies ebenfalls tut). Außerdem fügt der Messzeitgeber währenddessen Pakete in die Warteschlange ein. Denken Sie daran, dass meine Frage vereinfacht ist - das echte Gerät ist komplexer. Trotzdem danke :-)
Eine Lösung könnte darin bestehen, einen Software-Interrupt zu erstellen, der vom UART-Handler ausgelöst wird (wenn ein ACK empfangen wird) und auch ausgelöst wird, nachdem jedes Paket in die Warteschlange geschoben wurde. Diese ISR hat dann eine höhere Priorität als der UART-Handler und der Messtimer (und natürlich die Hauptschleife) und ist der einzige Ort, an dem die Warteschlange geknallt wird. Die Hauptschleife kümmert sich immer noch um die eigentliche Übertragung von Bytes. Ich weiß nichts darüber (und weiß nicht viel über Software-Interrupts). Die Idee ist, dass die Warteschlange in jedem Fall geknallt wird, nicht nur nach einem ACK. Was halten Sie von dieser Idee?

Nach einigem Nachdenken bin ich selbst auf eine Lösung gekommen. Die Idee ist, dass nur der UART-Handler (der Verbraucher) die Warteschlange öffnen wird, da er eine höhere Priorität als die Hauptschleife und auch eine höhere Priorität als der Timer-Handler hat, der die Messungen durchführt. In den beiden Situationen, die ich in meiner obigen Frage aufgeführt habe, wird der UART-Handler die Warteschlange jedoch nicht öffnen, da die Warteschlange zu dem Zeitpunkt leer ist, zu dem der UART-Handler eine ACK erhalten hat.

Die Lösung, die ich mir ausgedacht habe, besteht darin, den Produzenten (den Messtimer) wissen zu lassen, dass die Warteschlange beim letzten ACK leer war und das nächste Paket manuell (ohne Verwendung der Warteschlange) übertragen werden muss. Dies löste die zweite Situation. Die erste Situation wird gelöst, indem das erste Paket immer manuell übertragen wird.

Ich habe den UART-Handler auf geändert

if (ch == '!')
{
    if (cmd_tx_queue.size == 0)
    {
        cmd_transmit_manually = 1;
        cmd_rx_ack = 0;
    }
    else
    {
        cmd_transmit_manually = 0;
        cmd_tx_pop_cmd();
        cmd_rx_ack = '!';
    }
}

Die Hauptschleife bleibt wie zuvor:

if (cmd_rx_ack == ‘!’)
{
   cmd_rx_ack = 0; // Reset ack status

   // Transmit the command
   if (cmd_tx_length > 0)
   {
       cmd_tx_transmit();
   }
}

Im Timer ISR überprüfe ich einfach, ob cmd_transmit_manually gesetzt ist. Wenn ja, lösche ich cmd_transmit_manually und füge das Paket direkt in das Array cmd_tx[] ein und setze cmd_rx_ack = '!'. Dann überträgt die Hauptschleife dieses Paket, wenn der Timer ISR zurückkehrt.

Wenn cmd_transmit_manually 0 ist, wird das Paket stattdessen in die Warteschlange verschoben.

Nach einer Weile erhält der UART-Handler die ACK. Wenn die Warteschlange nicht leer ist, wird der nächste Befehl in das Array cmd_tx[] eingefügt, damit die Hauptschleife übertragen wird. Wenn es leer ist, wird cmd_transmit_manually auf 1 gesetzt. Während dieser Prüfung kann kein Paket in die Warteschlange geschoben werden, da der UART-Handler eine höhere Priorität als der Timer-Handler hat.

Ich habe diesen Code jetzt viele Male getestet, mit unterschiedlichen Zeiträumen für die Messung. Ich habe noch nicht erlebt, dass es scheitert. Ich habe auch überprüft, dass es keine fehlenden Pakete in der Übertragung gibt.

Als nächstes könnte man eine cmd_tx_push_cmd()-Funktion erstellen, die automatisch manuell überträgt, wenn cmd_transmit_manually gesetzt ist, und wenn nicht, das Paket in die Warteschlange schiebt. Auf diese Weise kann die Timer-ISR vereinfacht und der Code etwas aufgeräumt werden.

Wenn Sie der Meinung sind, dass dies nicht die beste Lösung ist oder wenn ich etwas Wichtiges übersehen habe, lassen Sie es mich bitte wissen. Im Moment scheint es so zu funktionieren, wie es sollte - und auch eine ziemlich einfache Lösung, denke ich :-)