Verbinden von drei UART-Geräten mit dem Mikrocontroller ohne Datenverlust

Ich habe einen Mikrocontroller TM4C123GXL Launchpad mit 8 seriellen UART-Ports. Ich verwende Keil uVision5 mit Tiva Ware zum Programmieren.

Ich wollte 3 UART-Geräte ohne Byteverlust mit dem Mikrocontroller verbinden. Die Baudrate wurde auf 115200 Bit/s ohne Parität eingestellt, wie in der Abbildung unten gezeigt. Alle Geräte senden regelmäßig alle 5 ms einen Datenrahmen .

Kommunikationssystem

TX- und RX-Zeit werden mit No_0f_bytes*10/BaudRate berechnet

Ich habe die Geräte erfolgreich mit dem UART von µC verbunden. Das Problem bei der Kommunikation ist, dass ich die Bytes verliere. Wenn ich eine Kommunikation mit einem einzelnen Gerät (Gerät 2) mache, verliere ich immer noch die Bytes aus dem gesamten Frame (20 Bytes).

Liegt es an der 16-Byte-FIFO-Beschränkung von Tm4c123 oder an etwas anderem?

Ich habe auch die µDMA-Funktion von TM4C123 implementiert. Aber trotzdem gehen Bytes verloren. Was kann ich tun, um das System für die verlustfreie Übertragung und den Empfang von Datenrahmen zu verbessern?

Bearbeiten:

Hier ist die Softwarearchitektur:

Ich verwende Periodic Timer Interrupt von 5 ms, um den Frame zu empfangen und zu senden. Alle Frames haben zuerst 2 Byte Header und ein Zählerbyte als letztes Byte.

void Timer1IntHandler(void) //  Periodic Service Routine every 5ms
{

DisableIntrupts();

bool Correct=ReadJoystick(); //10 bytes  Device 1

if(Correct)
{
GenerateServoCardsFrame();

SendServo1Frame();   //20 bytes  Device 2
SendServo2Frame(); //17 bytes  Device 3
ReadServo1Frame();  //15 bytes Device 2
ReadServo2Frame();  //20 bytes Device 3

GenerateJoystickFrame();

SendJoystickFrame(); //10 bytes   Device 1

EnableIntrupts();
}

}

main()
{
SetupClock()  ;   //Setup 16 MHz Clock
SetupJoystick();  //Initalize uart1 port for Device1
SetupServoCard1(); //Initalize uart2 port for Device2
SetupServoCard2(); //Initalize uart3 port for Device3

InitalizePeriodicTimerHandler(5);   //Periodic Service Routine every 5ms  (Timer1IntHandler)

while(1)
{
}

}


bool ReadJoystick(void)
{
    int BytePos=0;
    int CountInvalid=0;
    int LoopoutTime=0;

    while(1)
    {
    if (ROM_UARTCharsAvail(UART1))                              
        { 
            ByteRX = ROM_UARTCharGetNonBlocking(UART1);            
            if (BytePos==0)
            {
                if (ByteRX== 0xA1)      //Header1 found                              
                {
                    KArray[0] = Bytebuf ;
                    BytePos ++;
                }
                else
                {
                    CountInvalid++;
                    if (CountInvalid>5) 
                        return 0;
                }           
            }            
            else if (BytePos ==1) 
            {           
                if (ByteRX == 0x66)      //Header2 found                                   
                {   
                    KArray[1] = ByteRX;
                    BytePos ++;
                }
                else                                                   
                    BytePos=0;                                                        
            }            
            else
            {
                KArray[BytePos++] = ByteRX;                
                if (BytePos==10)                                                      
                    return 1;      //Frame Recived                                                   
            }
        }
        else                                                          
        {
            SysCtlDelay(0.25*SysCtlClockGet()/3 / 1000);        //   0.25ms delay
            LoopoutTime++;            
            if (LoopoutTime > 10)                                      
                return 0;            
        }    
    }       

}

Nach meiner Berechnung benötigt 1 Byte 10/115200 = 0,08680 ms und das Lesen eines FIFO von 16 Bytes erfordert 1,38 ms. Die Tabelle in der Abbildung zeigt eine Gesamt-Tx-Zeit von 4,08 ms und eine Rx-Zeit von 3,91 ms, was insgesamt 8 ms ergibt. Dies ist größer als meine Periodic Interrupt Service Routine.

Muss ich das Periodic Interrupt Timing erhöhen?

hast du es mal mit einer niedrigeren baudrate probiert? Höhere Baudraten bedeuten mehr Fehleranfälligkeit.
Geräte können nicht für eine niedrigere Baudrate konfiguriert werden. Nur eine Baudrate von 115200 Bit/s ist kompatibel.
was ist das für ein gerät?
Einer ist ein Joy Stick und die anderen zwei sind Servocontrollerkarten. Ihre Firmware wird von jemand anderem eingestellt.
Ja, 16 Byte Fifo ist klein. Das wäre der Grund.
Über einen UART eingehende Bytes gehen verloren, wenn die UART-Treibersoftware den UART nicht schnell genug bedient. Sie haben nicht genügend Details zu Ihrer Software angegeben, damit jemand detaillierte Vorschläge machen kann. Im Allgemeinen würde ich vorschlagen, dass Sie ein reaktionsschnelleres Softwaredesign oder einen schnelleren CPU-Takt benötigen.
Sie müssten jedes Byte so schnell lesen (ungefähr alle 64 Mikrosekunden für Baudrate 115200)
@MasoodSalik Ich habe vor langer Zeit UART-Treiber für den 16C554-Chip geschrieben. Diese haben 16-Byte-FIFOs und sind Quad-Geräte, also gibt es vier Ports. Ich habe auch genau die Datenraten verwendet, die Sie erwähnt haben. Und das alles lief gut mit einem sehr alten und langsamen Mikro (dem 4,77 MHz 8088). Niemals ein einziges Byte verloren ... jemals. Es funktioniert einfach alles. Wenn Sie ein Problem haben, dann ist es entweder in Code vergraben, den Sie nicht geschrieben haben (eine Bibliotheksroutine, über die Sie falsche Annahmen treffen) oder ein Fehler in dem, was Sie schreiben. Wenn Ihre externen Geräte funktionieren, gibt es keine andere Möglichkeit, es zu sehen.
@MasoodSalik Die EINZIGE Art und Weise, wie ein 20-Byte-Frame Probleme bereitet, besteht darin, wenn Sie in Ihrer Codierung SEHR FAUL sind und Ihre Software dazu bringen, dort zu sitzen und nichts zu tun, während die Kommunikation stattfindet. Vielmehr muss Ihre Software beim Entfernen von Bytes aus dem FIFO aktiv sein (und es gibt eine Reihe von Möglichkeiten, dies anzugehen: Beginnen Sie mit dem Entleeren bei "halb voll" ist nur ein Beispiel), während die Kommunikation stattfindet. Ich weiß nichts über Ihren TM4C123GXL, aber FIFO-gestützte Ports haben fast immer unterschiedliche "Trigger-Levels", die Sie je nach Bedarf auswählen können (First-in, Half-Full oder Full, um drei zu nennen). Verwenden Sie einen.
@MasoodSalik Siehe Abschnitt "14.3.8 FIFO-Betrieb" des TM4C123GXL-Datenblatts . Hinweis: (1) „ Nach dem Zurücksetzen sind beide FIFOs deaktiviert “ und (2) „ FIFOs können individuell konfiguriert werden, um Interrupts auf verschiedenen Ebenen auszulösen. Zu den verfügbaren Konfigurationen gehören ⅛, ¼, ½, ¾ und ⅞. “ Zum Empfangen , ich würde wahrscheinlich entweder ½ oder ¾ nehmen, je nachdem. Sie müssen wirklich das Datenblatt lesen und die Kontrolle über Ihre Empfängercodierung erlangen. Ihre externen Geräte und die TI MCU sind in Ordnung. Verwenden Sie diese Website, um zu vermeiden, dass Sie selbst lesen und nachdenken müssen?
Ich habe die Frage mit Software Architecture aktualisiert. Ein periodischer Unterbrechungsdienst liest und überträgt den Rahmen. @jonk Ich habe das Datenblatt von TM4C123GXL durchgesehen. Wenn wir Interrupt zum Empfangen der Bytes verwenden, können wir den Interrupt-Fifo-Level definieren. Ich habe den uC auch mit Interrupt konfiguriert, aber das Problem darin war, dass Continuously Interrupt Service Routine aufgerufen wurde, obwohl keine Daten auf einem seriellen Bus waren.
@MasoodSalik Es muss funktionieren, wenn Sie den Code richtig geschrieben haben. Ich habe keine Frage dazu. Ich habe so etwas in der Vergangenheit schon so oft gemacht. Ich bin mir sicher, dass der Geräte-ASIC (MCU) richtig entworfen ist, damit Programmierer dies richtig tun können. Dies ist nicht die Art von Fehler, die IC-Designer machen. Sie denken wahrscheinlich, dass der Interrupt, den Sie erhalten, damit zusammenhängt, wenn er aus einem anderen Grund eintrifft, mit dem Sie nicht richtig umgehen. Die Boards kosten nur 13 US-Dollar und ich habe gestern eines bestellt. Es wird einfach zu testen sein. Aber Sie müssen ein paar Wochen warten.
Ich bin seit 4 Monaten dabei. Habe verschiedene Möglichkeiten ausprobiert, bin aber gescheitert. Laut Datenblatt ähnelt UART einem 16C550. Auf der Seite en.wikipedia.org/wiki/16550_UART heißt es: „Der ursprüngliche 16550 hatte einen Fehler, der die Verwendung dieses FIFO verhinderte. NS veröffentlichte später den 16550A, der dieses Problem korrigierte. Nicht alle Hersteller haben diese Nomenklatur übernommen, beziehen sich jedoch weiterhin darauf der feste Chip als 16550 Laut einer anderen Quelle wurde das FIFO-Problem nur im 16550AF-Modell behoben, wobei das A-Modell immer noch fehlerhaft war. (Die C- und CF-Modelle sind laut dieser Quelle auch in Ordnung.) "
@MasoodSalik Sicher, ich musste mich in der Vergangenheit mit einer Vielzahl von Siliziumfehlern auseinandersetzen, einschließlich derer auf einigen der 16550er. Aber nichts davon trifft hier zu. Dies ist ein modernes Gerät und basiert auf jahrzehntelanger Erfahrung. Ich bin ziemlich zuversichtlich, dass es nur so funktioniert, wie es dokumentiert ist. Aber ich werde es früh genug wissen. Sie können die TI Errata auch auf dem Gerät nachschlagen. Vergewissern Sie sich, dass (was ich bald tun muss) TI im Allgemeinen KEINE Siliziumfehler behebt, sondern sie stattdessen dokumentiert. (Microchip behebt sie tatsächlich oft.)
@MasoodSalik Beginnen Sie hier: Mikrocontroller der Tiva C-Serie TM4C123x, Silicon Revision 6 und 7, Silicon Errata . Suchen Sie nach "Uart" und sehen Sie, was Sie dort finden. Es schadet auch nicht, die ARM-Errata nachzuschlagen: ARM Cortex-M4F Errata (v3) . Aber es wird wahrscheinlich keine große Hilfe sein, da es stattdessen um den Prozessor geht.
@MasoodSalik Wenn es noch nicht klar ist, müssen Sie Ihre Recherchen auf jedem Gerät durchführen. In diesem Fall bedeutet dies, die MCU-Errata (von ARM), die ASIC-Errata (von TI) und die LaunchPad-Errata (ebenfalls von TI, aber separat im Dokument auf dem Board zu finden) zu studieren. Sie müssen auch alle sorgfältig studieren die Details zu Port-Setup, Timern und so ziemlich allem, was in dem fast 1.500-seitigen Datenblatt zu finden ist. Sie haben 4 Monate damit verbracht, sagen Sie. Ich hätte die erste (oder zwei) Woche, bevor ich EINE ZEILE Code geschrieben habe, damit verbracht, diese Dokumente sorgfältig zu studieren. Hast du das gemacht?
In diesem Fall scheint jedoch eine schlechte Programmierung schuld zu sein. So schreibt man ISRs nicht - es gibt viel zu viel Code, die Interrupt-Latenz wird schrecklich sein. Und dann offensichtliche Find-in-5-Sekunden-Bugs, wie zum Beispiel, dass der Interrupt dauerhaft abgeschaltet wird. Ich kann auch so ziemlich garantieren, dass Sie volatileüberall fehlende Fehler haben. Die verwendete Form mainstammt aus der Dinosaurierzeit, was darauf hindeutet, dass ein schlechter Compiler verwendet wird. Usw.
@jonk Ich habe die von Ihnen aufgelisteten Dokumente durchgesehen. Es gibt einen Siliziumfehler, der nichts mit mir zu tun hat. Ich habe das Datenblatt nicht viel durchgesehen, seit ich die Tiva Ware Driver Peripheral Library verwende, die automatisch die Programmierung auf Registerebene übernimmt. Und ich arbeite einmal die Woche. Lundin: Habe den Punkt verstanden, keine langen Subroutinen in ISR zu schreiben.
@MasoodSalik Freut mich, ein Update zu hören. Danke. Ich habe MCUs verwendet, lange bevor Sie geboren wurden, da bin ich mir sicher (bereits etwa 40 Jahre). In dieser Zeit habe ich oft Code geschrieben, um mehrere 16-Byte-FIFO-Kanäle zu handhaben – sowohl extern als auch intern für MCUs . Dazu gehörten zeitweise auch Silizium-Bugs. In jedem einzelnen Fall konnte ich sie einwandfrei zum Laufen bringen. Aber ich habe meinen eigenen Code geschrieben, meine eigenen Recherchen und Arbeiten durchgeführt und anhand dieser Informationen einen geeigneten Code entwickelt. Wenn Sie eine Bibliothek benutzen, dann sind Sie auf die Arbeit anderer angewiesen, und wenn es ein Problem gibt, sollten Sie sie kontaktieren, nehme ich an.
Ja. Sicher. TivaWare™ ist von Texas Instrument und wurde entwickelt, um den Entwicklungsprozess zu vereinfachen und zu beschleunigen, deshalb verwende ich es. Es gab Fehler in meiner Softwarearchitektur. Jetzt habe ich eine erfolgreiche Kommunikation zwischen den 2 Geräten hergestellt. Werde bald mit mehreren Geräten experimentieren.
@MasoodSalik Gut zu hören. Software, die entwickelt wurde, um Menschen beim "schnellen Einstieg" zu helfen, ist oft nicht die beste Software, sondern etwas, das entwickelt wurde, um ihren Außendienstmitarbeitern bei der Arbeit mit Kunden zu helfen, einem Kunden schnell zu zeigen, dass das Problem in der Software des Kunden und nicht in der Hardware des Herstellers liegt. Es löst oft nur die trivialsten Endbenutzeranforderungen. Ich benutze solche Software nie, außer um mich selbst zu überprüfen, da ich weiß, dass sie von sehr begrenztem wirklichem Wert sein wird. Und ja, es handelt sich normalerweise um Fehler in dem, was Sie schreiben. Das war meine Erfahrung.
@MasoodSalik Normalerweise zerlege ich meinen Treibercode in vier Teile mit zwei Puffern zur Vermittlung. Es gibt den Empfängertreiber, der aus einer High-Level-Schnittstelle für den Rest meines Codes und einer Low-Level-Schnittstelle für die Hardware besteht, mit einem Puffer dazwischen. Und das gleiche Konzept, wiederholt, für die Sendeseite. Die Puffer entkoppeln die Nutzung auf höherer Ebene von der Hardware-Unterstützungsseite auf niedrigerer Ebene. Sie können Pufferpoolverwaltung hinzufügen, wenn Sie knifflig werden möchten. Oder statisch allokierte Buffer dauerhaft bereitstellen.

Antworten (1)

Ihr Softwaredesign ist nicht gut und ist wahrscheinlich der Grund dafür, dass eingehende Bytes verloren gehen.

Erstens bin ich mir nicht sicher, ob dies ein Fehler oder ein Tippfehler ist. Sie deaktivieren Interrupts zu Beginn von, Timer1IntHandler()aber Sie aktivieren Interrupts nur dann wieder, wenn Correctwahr ist. Möchten Sie Interrupts nicht erneut aktivieren, bevor Sie unabhängig von der Bedingung zurückkehren? Es scheint seltsam, dass Interrupts deaktiviert bleiben könnten, wenn die Funktion zurückkehrt.

Es scheint, dass Ihr Code Zeichen von UART1 nur innerhalb der Funktion liest ReadJoystick(). Und ich vermute, dass UART1 nicht gelesen wird, während all diese Funktionen von GenerateServoCardsFrame()bis SendJoystickFrame()aufgerufen werden. Wie lange dauert es, bis diese Funktionen ausgeführt werden? Könnten diese Funktionen lange genug dauern, bis sich der UART1-FIFO füllt und überläuft? Dies kann daran liegen, dass eingehende Bytes verworfen werden.

Wenn ich diese Software entwerfen würde, würde ich sie ganz anders implementieren als Sie es getan haben. Ich würde die UART-Interrupt-Anforderung aktivieren und eine schnelle UART-Interrupt-Handler-Routine erstellen. Das einzige, was der UART ISR tun würde, ist, Bytes zu/von den UART TX/RX-Registern zu kopieren. Ich würde zwei kreisförmige (auch bekannt als Ring-) Puffer erstellen, um die Bytes zu halten. Der UART ISR würde ein empfangenes Byte aus dem UART-RX-Register in den RX-Ringpuffer kopieren. Und die UART ISR würde ein zu übertragendes Byte aus dem TX-Ringpuffer in das UART-TX-Register kopieren. Der UART ISR würde nicht versuchen, die Bedeutung irgendeines der Bytes zu interpretieren. Es verschiebt lediglich Bytes zwischen den RAM-Pufferspeichern und dem UART-Peripheriegerät. Dadurch bleibt die UART ISR kurz, wodurch das Gesamtprogramm besser auf andere Interrupts reagieren kann.

Dann würde ich eine Funktion mit einer Endlosschleife erstellen main()und innerhalb der Endlosschleife würde ich eine Funktion aufrufen, die aufgerufen wird, SerialReceive()um Nachrichten aus dem RX-Puffer zu lesen. SerialReceive()würde als Zustandsmaschine implementiert werden. Wenn irgendwelche Bytes im RX-Puffer verfügbar sind, verarbeitet er eine endliche Anzahl von Bytes durch die Zustandsmaschine. Die Zustandsmaschine hätte Zustände für den Frame-Header, -Body und -Trailer, ähnlich wie Sie es getan haben. SerialReceive()kehrt sofort zurück, wenn entweder eine Nachricht abgeschlossen ist oder keine Bytes mehr verfügbar sind. Wenn eine Nachricht unvollständig ist, weil keine weiteren Bytes sofort verfügbar sind, SerialReceive()wartet sie nicht auf sie, sondern merkt sich den aktuellen Zustand und die Nachricht, sodass sie mit derselben Nachricht fortfahren kann, wenn sie erneut von aufgerufen wird main().

Wenn Sie regelmäßig etwas tun müssen, richten Sie einen Timer ein, wie Sie es getan haben, aber anstatt die ganze Arbeit innerhalb der Timer-ISR zu erledigen, setzen Sie stattdessen einfach ein Flag. Die Haupt-Endlosschleife sollte das Flag wiederholt überprüfen und alles tun, was angemessen ist, wenn das Flag durch den Zeitgeber ISR gesetzt wurde. Das Erledigen der Arbeit aus dem Kontext von main()bedeutet, dass das System auf andere Interrupts reagieren kann, während es die Arbeit verrichtet.

Das Kurzhalten von ISRs ermöglicht es dem Gesamtsystem, besser auf andere Interrupt-Anforderungen zu reagieren. Wenn Sie zu viel Zeit in einer ISR verbringen, wie ich glaube, dass Sie es in Ihrer Timer-ISR tun, reagiert das System nicht.

Update: In Ihrem Kommentar sagen Sie, dass diese Funktionen eine Schleife durchlaufen, bis die Übertragung abgeschlossen ist, und dass sie über 7 Millisekunden dauern. Das ist genug Zeit, damit 80 Bytes am UART ankommen, und Ihr Code liest diese Bytes während dieser Zeit nicht, sodass natürlich Bytes verloren gehen.

Ihre Sendefunktionen sollten Bytes in den TX-Puffer kopieren und zurückkehren, ohne auf die Übertragung der gesamten Nachricht zu warten. Der UART ISR sollte bei jedem Aufruf ein Byte übertragen, während der TX-Puffer Bytes enthält.

Der RX- und TX-Puffer sollte größer sein als jede Nachricht. Typischerweise haben die Puffer eine Zweierpotenz in der Größe, da dies es einfach macht, die Pufferzeiger zurück zum Anfang zu kreisen. Machen Sie sie also zu 256 Bytes (oder 128 oder 64, aber warum nicht größer?).

Sie sollten für jeden UART einen unabhängigen Satz RX/TX-Puffer haben.

Das Ändern der Periode Ihres periodischen Timers ISR wird das Problem mit Ihrem ursprünglichen Code nicht beeinflussen. Innerhalb Ihrer periodischen ISR verbringt Ihr Code 7 Millisekunden damit, den UART NICHT zu lesen. Ihr Code wird unabhängig von der Timer-Periode Bytes verlieren.

Die Ending Brace war ein Tippfehler beim Schreiben des Pseudocodes. Meine UART-Übertragungsfunktionen sind so gestaltet, dass sie in der Schleife bleiben, bis alle Bytes gesendet wurden. Das Senden von 20 Bytes kostet also 1,74 ms. Die gesamte Funktion in einer "if"-Schleife dauert 7,11 ms. Muss ich das periodische ISR-Timing erhöhen? Ich habe erfolgreich einen Cicular-Puffer implementiert, um die von UART empfangenen Bytes mit einem Interrupt zu speichern. Und das Flag in der While-Schleife in main() überprüfen. Mit einem einzigen Gerät funktioniert es einwandfrei. Werde demnächst mit 2 Geräten experimentieren. Welche Ringpuffergröße sollte ich einstellen? 2x Datenbytes?
Sollte die Ringpuffergröße doppelt so groß sein wie der tatsächliche Frame?
@MasoodSalik, ich habe meine Antwort aktualisiert, um auf Ihre Kommentare einzugehen.
Gibt es ein Konzept, dass, sobald ich eine Funktion aufrufe, die Funktion sich nach einiger Zeit (berechnet) selbst aufruft, bis eine Bedingung erreicht ist; ohne das System in den Ruhezustand zu versetzen. Ich wollte es für die Sendefunktion implementieren. Sobald ich das Tx-Fifo fülle, ruft sich die Funktion nach einiger Zeit erneut auf und füllt das FIFO neu, bis der gesamte Rahmen gesendet ist.
@MasoodSalik, du denkst falsch darüber nach. Anstatt zu berechnen, wann Sie nachfüllen können, lassen Sie sich vom UART informieren, wann Sie nachfüllen können. Aktivieren Sie insbesondere den entsprechenden UART-IRQ, damit der UART-ISR aufgerufen wird, wenn Bytes zum FIFO hinzugefügt werden können. Ehrlich gesagt denke ich nicht, dass Sie versuchen sollten, das UART-FIFO zu verwenden, bis Sie es ohne das FIFO zum Laufen bringen. Und ich empfehle Ihnen dringend, online nach Beispielcode zu suchen. Meine Antwort ist ein Überblick und lässt wichtige Details aus, die Sie stolpern lassen.