USI als UART nutzen
Ein UART mithilfe einer USI bereitstellen
Zuletzt aktualisiert am 16.03.2021- Vorwort
- Funktionen
- Globale Variablen
- uart_listen
- uart_stop
- uart_getBaudrate
- uart_TimerConfig
- uart_receive
- uart_transmit
- uart_transmitArray
- uart_available
- uart_readByte
- uart_getByte
- uart_delete
- rev_byte
- USI overflow interrupt
- Beispiel
- Downloads
Einige Mikrocontroller verfügen heutzutage über keine in Hardware ausgeführte UART-Schnittstelle mehr. Stattdessen wird eine sogenannte "USI" bereitgestellt. Das Kürzel steht für "Universal Serial Interface", also "Universelle serielle Schnittstelle". Mit einer USI können unterschiedliche serielle Schnittstellen aufgebaut werden. Hauptsächlich genannt sind SPI, I2C oder eben UART.
Auf dieser Seite präsentiere ich nun meine kleine Bibliothek, welche eine UART-Schnittelle auf USI-Basis bereitstellt.
Detailliertere Informationen stellt Microchip mit einer Application Note und einem Beispielcode selber bereit.
Meine geschriebene Bibliothek stellt folgende Funktionen bereit. Die einzelnen Funktionen werden im weiteren Verlauf dieser Seite genauer erklärt.
- uart_listen: Aktiviert den "Pin change interrupt" des DI-Pins. So können Bytes empfangen werden.
- uart_stop: Deaktiviert den "Pin change interrupt" des DI-Pins. Es werden also keine Bytes empfangen.
- uart_getBaudrate: Fragt den Logiklevel zweier IO-Pins ab und gibt so die eingestellte Baudrate zurück.
- uart_TimerConfig: Stellt den Sollwert und den Prescaler des Timers in Abhängigkeit von der Baudrate und der CPU-Frequenz ein.
- uart_receive: Startet den Timer und das USI zum einlesen eines eingehenden Bytes.
- uart_transmit: Sendet ein Byte.
- uart_transmitArray: Sendet ein Array von Bytes.
- uart_available: Gibt die Anzahl der empfangenden Bytes, welche noch im Puffer liegen, zurück.
- uart_readByte: Gibt das erste Byte aus dem Puffer zurück, löscht es aber nicht daraus.
- uart_getByte: Gibt das erste Byte aus dem Puffer zurück und löscht es daraus.
- uart_delete: Leert den Puffer.
- rev_byte: Dreht die Reihenfolge der Bits eines Bytes.
Zum Betrieb des UART werden ein paar globale Variablen deklariert/definiert:
Diese Funktion konfiguriert den DI und DO Pin als Eingänge und setzt sie auf einen H-Pegel. Der DO Pin wird ebenfalls als Eingang definiert, da er sonst mit dem Schieberegister der USI verbunden wäre und so den Zustand der letzten Stelle des Schieberegisters annehmen würde. So wäre der DO Pin beim empfangen eines Bytes nicht dauerhaft auf einem H-Pegel.
Diese Funktion deaktiviert den "pin change interrupt" des DI Pins, sodass keine Bytes mehr empfangen werden. Des Weiteren wird dafür gesorgt, dass der DI und DO Pin garantiert einen H-Pegel (Den Ruhepegel) haben.
Diese Funktion fragt den Logikpegel von zwei IO Pins (Hier: PA6 und PA7) ab und gibt die so eingestellte Baudrate zurück. Vorgesehen ist 9600, 19200 und 38400.
Die Dauer eines Bits ist der Kehrwert der Baudrate. Die Dauer bis der der Timer den Sollwert n erreicht, ergibt sich aus der CPU-Frequenz und dem Prescaler. So erhält man folgende Gleichung:
Der maximale Sollwert des Timers beträgt 255. Da der berechnete Sollwert n nachher immer aufgerundet werden soll, darf dieser maximal 254 betragen. Stellt man nun die Formel nach PRESCALER um, dann ergibt sich:
Diese Formel liefert den Wert, den der Prescaler mindestens haben muss. Die if-Bedingung wählt den nächst höheren Prescaler aus.
Zum Schluss wird der notwendige Sollwert berechnet, indem man die erste Formel nach n umstellt. Da der berechnete Sollwert auf jeden Fall aufgerundet werden soll, wird der berechnete Wert noch um 1 erhöht:
Diese Funktion wird von dem "Pin change interrupt" des DI Pins aufgerufen. Dadurch wird der Timer gestartet und die USI passend konfiguriert.
Wurde ein volles Byte empfangen, dann wird der "USI overflow interrupt" ausgelöst.
Diese Funktion setzt die richtige Konfiguration der Pins, parametriert die USI und schaltet den Timer ein.
Wurde der erste Teil übertragen, dann wird der "USI overflow interrupt" ausgelöst.
Hierbei handelt es um eine einfache for-Schleife, die für jedes Byte die "uart_transmit"-Funktion aufruft:
void uart_transmitArray(const uint8_t *byte, uint8_t length) { for(uint8_t i = 0; i < length; i++) uart_transmit(byte[i]); }
Diese Funktion gibt nur den Wert der globalen Variable "data_avail_rx" zurück:
uint8_t uart_available(void) { return data_avail_rx; }
Diese Funktion gibt das erste Element aus dem Puffer zurück. Allerdings bleibt das Byte im Puffer stehen. Sollten keine Bytes verfügbar sein, dann wird einfach eine Null zurück gegeben.
uint8_t uart_readByte(void) { return data_rx[0]; }
Diese Funktion gibt das erste Element aus dem Puffer zurück. Dabei wird das abgefragte Byte aus dem Puffer gelöscht, indem alle anderen Bytes um Eins nach vorne geschoben werden. Sollte kein Byte verfügbar sein, dann wird einfach eine Null zurückgegeben.
Diese Funktion setzt die Anzahl der im Puffer verfügbaren Bytes auf Null zurück. So sind alle gespeicherten Bytes nicht mehr über "uart_readByte", bzw. "uart_getByte" abrufbar.
Ein neu eingehendes Byte wird nun wieder vom Index 0 ausgehend im Puffer gespeichert.
void uart_delete(void) { data_avail_rx = 0; }
Da alle Bytes mit dem LSB zuerst empfangen und gesendet werden, muss die Reihenfolge der Bits gedreht werden. Diese Aufgabe übernimmt diese Funktion:
uint8_t rev_byte(uint8_t rbyte) { rbyte = ((rbyte >> 1) & 0x55) | ((rbyte << 1) & 0xaa); rbyte = ((rbyte >> 2) & 0x33) | ((rbyte << 2) & 0xcc); rbyte = ((rbyte >> 4) & 0x0f) | ((rbyte << 4) & 0xf0); return rbyte; }
Diese ISR (Interrupt Service Routine) wird aufgerufen, wenn
- Ein volles Byte (8 Bit) empfangen wurde.
- Der erste oder der zweite Teil eines Bytes (1x Startbit, 8x Datenbits, 8x Stoppbits) gesendet wurde.
Wurde ein Byte empfangen, dann wird der Timer und die USI abgeschaltet und das empfangende Byte in den Puffer geschrieben. Danach ist der Controller bereit, ein weiteres Byte zu empfangen.
Wurde der erste Teil eines Bytes (Startbit und die sieben ersten Datenbits) gesendet, dann wird das letzte Datenbit in das Schieberegister der USI geschrieben. Der Rest wird mit Einsen aufgefüllt.
Der Zählwert des 4-Bit-Zählers wird auf 13 gesetzt. So werden drei Zyklen durchlaufen, bis der Zähler überläuft. Dadurch wird das letzte Datenbit und zwei Stoppbits gesendet.
Wurde der zweite Teil eines Bytes (Letztes Datenbit und zwei Stoppbits) gesendet, dann wird der Timer und die USI abgeschaltet und der Rx-Modus wieder aktiviert, falls er vorher auch aktiviert war.
Dieses Werk von Niklas Menke ist lizenziert unter einer Creative Commons Namensnennung 4.0 International Lizenz.
Header-Datei und Programmcode (zip)