RS-485 auf inländischen Mikrocontrollern der Firma Milander


Vor ein paar Tagen hatte ich die Unklugheit, verschleiert zu versprechen, einen Beitrag über Milander zu kürzen ... Nun, versuchen wir es.

Wie Sie wahrscheinlich bereits wissen, gibt es eine russische Firma Milander, die unter anderem Mikrocontroller auf Basis des ARM Cortex-M-Kerns herstellt. Durch den Willen des Schicksals war ich gezwungen, sie ziemlich genau kennenzulernen, und ich kannte den Schmerz .

Ein kleiner Teil dieser Schmerzen, die durch die Arbeit mit RS-485 verursacht werden, wird unten beschrieben. Ich entschuldige mich im Voraus, wenn ich zu viel an grundlegenden Konzepten kaue, aber ich wollte diesen Artikel einem breiteren Publikum zugänglich machen.
Ich werde auch im Voraus reservieren, dass ich mich nur mit 1986-1991 und 1986-1 befasst habe. Ich kann nicht sicher über andere sprechen.

TL DR
Dem Milandrovsk UART fehlt der Interrupt "Transmit complete", die Krücke ist der "Loopback-Testmodus", d. H. Echo-Modus. Aber mit den Nuancen.

Eintrag


Die RS-485-Schnittstelle (auch als EIA-485 bekannt, obwohl ich sie im Alltag noch nie gehört habe) ist eine asynchrone Halbduplex-Schnittstelle mit einer Bustopologie. Dieser Standard schreibt nur Physik vor - d.h. Spannungspegel und Zeitdiagramme - spezifiziert jedoch nicht das Austauschprotokoll, den Schutz vor Übertragungsfehlern, die Schiedsgerichtsbarkeit und dergleichen.

Tatsächlich ist der RS-485 nur ein Halbduplex-UART mit höheren Differenzspannungspegeln. Es ist diese Einfachheit, die die Popularität des RS-485 sicherstellt.
Für die Konvertierung von UART in RS-485 werden spezielle Konverter-Mikroschaltungen verwendet, z. B. MAX485 oder 5559IN10AU (vom selben Milander). Sie arbeiten fast "transparent" für den Programmierer, der nur die richtige Betriebsart des Chips wählen kann - Empfang oder Übertragung. Dies erfolgt mit den Beinen nRE (nicht Receiver Output Enable) und DE (Driver Output Enable), die in der Regel von einem Bein des Mikrocontrollers kombiniert und gesteuert werden.

Durch Anheben dieses Beins wird der Chip auf Senden und auf Empfangen abgesenkt.
Dementsprechend muss der Programmierer lediglich diesen RE-DE-Abschnitt anheben, die gewünschte Anzahl von Bytes übertragen, den Abschnitt senken und auf eine Antwort warten. Klingt einfach genug, oder?
Hehe.

Das Problem


Dieser Zweig sollte zu einem Zeitpunkt abgesenkt werden, an dem alle übertragenen Bytes vollständig auf die Leitung übertragen werden. Wie kann man diesen Moment einfangen? Dazu müssen Sie das Ereignis "Übertragung abgeschlossen" (Übertragung abgeschlossen) abfangen, das einen UART-Block im Mikrocontroller generiert. Zum größten Teil setzen Ereignisse in einem Register oder einer Interrupt-Anforderung ein wenig. Um die Einstellung eines Bits im Register zu erfassen, muss das Register abgefragt werden, d.h. Verwenden Sie den folgenden Code:

while( MDR_UART1->FR & UART_FR_BUSY ) {;} 

Dies ist der Fall, wenn wir es uns leisten können, das Programm vollständig zu stoppen, bis alle Bytes übertragen sind. Das können wir uns in der Regel nicht leisten.

Eine diesbezügliche Unterbrechung ist viel bequemer, da sie von selbst asynchron eintrifft. In der Unterbrechung können wir RE-DE und das gesamte Geschäft schnell weglassen.

Wenn wir dies tun könnten, gäbe es natürlich keine Schmerzen, und dieser Beitrag würde auch nicht existieren.

Tatsache ist, dass im UART-Block, den Milander (soweit ich weiß) in alle seine Mikrocontroller auf dem Cortex-M einbaut, das Ereignis "Transfer Complete" nicht unterbrochen wird. Es gibt nur eine Flagge. Und es gibt eine Unterbrechung "Der Puffer des Senders ist leer." Und die Byte-Unterbrechung natürlich.

Es gibt noch
Eine Reihe anderer Interrupts und der FIFO-Modus sind meiner Meinung nach völlig nutzlos. Wenn jemand versteht, warum er gebraucht wird, sagen Sie es uns bitte!

Das Problem ist, dass "Sendepuffer ist leer" - dies ist überhaupt nicht dasselbe wie "Übertragung abgeschlossen". Soweit ich das interne UART-Gerät verstehe, bedeutet das Ereignis „Buffer Empty“, dass mindestens ein freier Speicherplatz im Senderpuffer vorhanden ist. Selbst wenn dieser Ort nur einer ist (d. H. Ein Puffer mit einer Bytegröße), bedeutet dies nur, dass das zuletzt übertragene Byte in das interne Schieberegister kopiert wurde, aus dem dieses Byte Stück für Stück auf die Leitung herauskriecht.

Kurz gesagt bedeutet das Ereignis "Senderpuffer ist leer" nicht, dass alle Bytes vollständig übertragen wurden. Wenn wir RE-DE in diesem Moment weglassen, werden wir unser Paket "schneiden".

Was tun?

Rebus



Dekodierung:
"Jäten von Bitfeldern" ist ein lokales Mem aus einem kurzen, aber schmerzhaften Thema im Milander-Forum - forum.milandr.ru/viewtopic.php?f=33&t=626 .
Die einfachste Lösung ist das "Unkraut" -Flag (aus der englischen "Umfrage" - kontinuierliche Umfrage) UART_FR_BUSY.

Natürlich ist diese Lösung nicht sehr schön. Wenn wir dieses Flag nicht blockieren können, müssen wir es regelmäßig überprüfen. Um dies regelmäßig zu überprüfen, müssen Sie einen ganzen Garten umzäunen (insbesondere, wenn Sie ein tragbares Modul schreiben und dieses Problem nicht nur einmal lösen möchten).

Wenn Sie eine Art RTOS verwenden, müssen Sie für dieses Unkraut eine ganz separate Aufgabe starten, sie in Unterbrechung aufwecken und nicht die niedrigste Priorität festlegen, kurz gesagt.

Aber es scheint, okay, einmal gequält, dann benutzen wir und freuen uns. Aber nein.
Leider reicht es nicht aus, RE-DE strikt wegzulassen, nachdem alle Bytes bis zum Ende übertragen wurden. Wir müssen es nicht zu spät senken. Weil wir nicht alleine im Bus sind. Höchstwahrscheinlich sollte eine Antwort von einem anderen Abonnenten auf unsere Nachricht kommen. Und wenn wir RE-DE zu spät weglassen, wechseln wir nicht in den Empfangsmodus und verlieren einige Antwortbits.

Die Zeit, die wir uns leisten können, um den RE-DE-Abschnitt zu „überbelichten“, hängt hauptsächlich von der Übertragungsgeschwindigkeit (Baudrate) und der Geschwindigkeit des Geräts ab, mit dem wir auf dem Bus kommunizieren.
In meinem Fall war die Geschwindigkeit relativ niedrig (57600 Baud) und das Gerät war ziemlich munter. Und manchmal kam es vor, dass die Antwort ein oder zwei Punkte verlor.

Insgesamt keine gute Lösung.

Timer


Die zweite Option, die Ihnen in den Sinn kommt, ist die Verwendung eines Hardware-Timers. Dann starten wir im Interrupt „Transmitter Buffer Empty“ einen Timer mit einer Zeitüberschreitung, die der Übertragungszeit von einem Byte entspricht (diese Zeit lässt sich leicht aus der Baudrate berechnen), und senken im Interrupt vom Timer unser Bein.

Guter, zuverlässiger Weg. Nur der Timer ist schade; Traditionell gibt es in der Milandra nicht viele davon - zwei oder drei Stücke.

Loop-Modus


Wenn Sie diese sorgfältig lesen. Beschreibung auf UART - zum Beispiel für 1986 BE91T - Sie können diesen sehr kurzen Absatz bemerken:



( ) 1 LBE UARTCR.


Wenn diese. Wenn Sie die Beschreibung nicht lesen, können Sie fast den gleichen Effekt erzielen, indem Sie die Beine der RX- und TX-Hardware kurzschließen.

Lautes Denken
Interessanterweise, wo ist diese Art von Schleife? Normalerweise wird dieser Modus "Echo" genannt, aber na ja.

Die Idee ist wie folgt: Bevor Sie das letzte Byte im Paket übertragen, müssen Sie den "Loopback" -Modus aktivieren. Dann können Sie eine Unterbrechung für den Empfang unseres letzten Bytes erhalten, wenn es vollständig in den Bus kriecht! Na ja, fast.

In der Praxis stellte sich heraus, dass der Interrupt beim Empfang etwas früher ausgelöst wird, als er sollte, etwa ein Drittel des Bitintervalls. Ich weiß nicht, womit das verbunden ist; Es ist möglich, dass im Schleifentestmodus keine echte Abtastung der Leitung auftritt, möglicherweise berücksichtigt der Schleifmodus das letzte Stoppbit nicht. Weiß nicht. Wie dem auch sei, wir können RE-DE nicht sofort nach Eingabe dieses Interrupts weglassen, da wir auf diese Weise das Stoppbit oder einen Teil des Stoppbits von unserem letzten Byte "abschneiden".

Genau genommen können wir uns auf das Verhältnis der Schnittstellengeschwindigkeit (d. H. Der Dauer eines Bitintervalls) und der Frequenz des Mikrocontrollers verlassen oder nicht, aber ich konnte die 80-MHz-Taktfrequenz nicht verwenden und die Baudrate betrug 57600.

Weitere Optionen sind möglich.

Wenn Sie es sich leisten können, das UART_FR_BUSY-Flag für ein Bitintervall abzufragen - sogar etwas weniger, da das Eingeben des Interrupts und der vorläufigen Überprüfungen ebenfalls Zeit in Anspruch nimmt - dann ist die Lösung gefunden. Bei einer Geschwindigkeit von 57600 beträgt die maximale Abrufzeit in der Praxis ~ 18 Mikrosekunden (ein Bitintervall) - ungefähr 5 Mikrosekunden.

Für diejenigen, die interessiert sind, zitiere ich den gesamten Interrupt-Handler-Code.
 void Handle :: irqHandler(void) { UMBA_ASSERT( m_isInited ); m_irqCounter++; // ---------------------------------------------  // do     break do { if ( UART_GetITStatusMasked( m_mdrUart, UART_IT_RX ) != SET ) break; // -,     ,     UART_ClearITPendingBit( m_mdrUart, UART_IT_RX ); uint8_t byte = UART_ReceiveData( m_mdrUart ); //  485   ,        if( m_rs485Port != nullptr && m_echoBytesCounter > 0 ) { //     m_echoBytesCounter--; if( m_echoBytesCounter == 0 ) { //     ____, //        ,  -      // -   . //   ,      -. //     ,   : //  |  , |  , | // |  |  | // | | | // 9600 | 105 | 32 | // 57600 | 18 | 4,5 | // 921600 | 1 | 0 | // | | | //      /  , //      . // ,     ,   . //    while( m_mdrUart->FR & UART_FR_BUSY ) {;} //          rs485TransmitDisable(); // ,    #ifdef UART_USE_FREERTOS osSemaphoreGiveFromISR( m_transmitCompleteSem, NULL ); #endif } break; } //      -      overrun #ifdef UART_USE_FREERTOS BaseType_t result = osQueueSendToBackFromISR( m_rxQueue, &byte, NULL ); if( result == errQUEUE_FULL ) { m_isRxOverrun = true; } #else if( m_rxBuffer.isFull() ) { m_isRxOverrun = true; } else { m_rxBuffer.writeHead(byte); } #endif } while( 0 ); // ---------------------------------------------  //    -   ! //  ,  SPL    m_error = m_mdrUart->RSR_ECR; if( m_error != error_none ) { //     m_mdrUart->RSR_ECR = 0; } // ---------------------------------------------  if( UART_GetITStatusMasked( m_mdrUart, UART_IT_TX ) != SET ) return; //    485 -    if( m_txCount == m_txMsgSize - 1 && m_rs485Port != nullptr ) { setEchoModeState( true ); m_echoBytesCounter = 2; } //   else if( m_txCount == m_txMsgSize ) { //    ( )      UART_ClearITPendingBit( m_mdrUart, UART_IT_TX ); m_pTxBuf = nullptr; return; } //  ,   UMBA_ASSERT( m_pTxBuf != nullptr ); UART_SendData( m_mdrUart, m_pTxBuf[ m_txCount ] ); m_txCount++; } 


Wenn Sie sich den Jumper (idealerweise gesteuert) zwischen den Beinen des RX und des TX leisten können, ist alles in Ordnung.

Leider kann ich heute keine anderen Optionen anbieten.

Das ist alles für mich. Wenn jemand andere Möglichkeiten zur Lösung dieses Problems kennt, teilen Sie diese bitte in den Kommentaren mit.

Wenn ich die Gelegenheit nutze und die Habr-Regeln ändere, möchte ich auch für die StartMilandr-Website werben, die eine Sammlung von Artikeln über die Milander-Mikrocontroller enthält. Aus einem unklaren Grund können Sie es nur aus Versehen googeln.

Und erinnern Sie sich natürlich an die Existenz einer Abzweigung der Standard-Peripheriebibliothek, in der im Gegensatz zur offiziellen Bibliothek Fehler behoben werden und es gcc-Unterstützung gibt.

Source: https://habr.com/ru/post/de439450/


All Articles