(→Dateikopf) |
(→Dateikopf: infifo) |
||
Zeile 61: | Zeile 61: | ||
#define INBUF_SIZE 4 | #define INBUF_SIZE 4 | ||
static uint8_t inbuf[INBUF_SIZE]; | static uint8_t inbuf[INBUF_SIZE]; | ||
− | fifo_t infifo | + | fifo_t infifo; |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
# else // _FIFO_H_ | # else // _FIFO_H_ | ||
static volatile uint8_t indata; | static volatile uint8_t indata; |
Version vom 5. Juni 2006, 21:24 Uhr
Ein Software-UART ist eine Software, welche die Funktionalität eines UART (Universal Asynchronous Receiver and Transmitter) implementiert., oder kurz: eine serielle Schnittstelle in Software nachbildet.
Inhaltsverzeichnis
Einführung
Stehen keine oder nicht genügend Hardware-UARTs zur Verfügung, muss man selbst Hand anlegen. Die Herausforderung ist dabei, die festgelegten Zeitrahmen bzw. Übertragungsraten/Bitlängen möglichst genau einzuhalten, um die Kommunikation fehlerfrei zu halten. Dies gilt in besonderem Maße für den Empfänger (Receiver), der nicht entscheiden kann, wann Daten eintreffen.
Dafür benötigt man eine Zeitbasis. Am konfortabelsten geht das mit einem Timer. Ohne Timer muss man die Zeit durch Abzählen von Maschinenzyklen machen, während die restliche Software warten muss bzw. Interrupts deaktiviert sein müssen.
Jeder diese Herangehensweisen hat ihre Vor- und Nachteile. Welcher Weg der bessere ist, kann nur im Gesamtzusammenhang des Programmes entschieden werden.
Wie auch immer; Ziel der Software ist es, UART-Datenframes senden und empfangen zu können. Bei Inaktivität (idle) ist die Signalleitung auf 1 (High). Jeder Frame beginnt mit einem Startbit (S), das 0 ist. Danach folgen die 8 Datenbits und schliesslich ein Stop-Bit (P), das immer 1 ist. Danach ist die Leitung wieder inaktiv oder es folgen weitere Frames.
Läuft der AVR mit einer Taktrate von F_CPU und die Übertragung mit der Baudrate BAUDRATE, dann dauert ein Bit
- [math]Z_\mathrm{bit} = \frac\mathrm{F_{CPU}}\mathrm{BAUDRATE}[/math]
Zyklen. Bei 16MHz und 38400 Baud dauert ein Bit also rund 417 Zyklen, bei 1MHz und 9600 hat man nur rund 104 Zyklen Zeit.
Mit Verwendung von Timer1
Dateikopf
Im Dateikopf wird festgelegt, an welchen Ports die Ein- und Ausgabe stattfindet, mit welcher Baudrate übertragen wird und welche Teile des Soft-UARTs aktiv sind, also übersetzt werden und als Funktionen zur Verfügung stehen bzw. Flash verbrauchen. Damit eintreffende Daten nicht verloren gehen, können die Eingabedaten in einer FIFO zwischengespeichert werden.
Die verwendeten Ports sind aus Effizienzgründen hartcodiert. Der Receiver-Eingang liegt an einem ICP (InputCapture-Eingang). Ebenfalls hartcodiert ist das Sendeformat 8N1 (8 Datenbits, kein Parity, 1 Stopbit).
- F_CPU
- ist die Taktrate des AVR und muss irgendwo definiert worden sein, entweder über ein #define oder per Kommandozeile mit -DF_CPU=...
- #define BAUDRATE ...
- Die Baudrate, gleich für Receiver und Transmitter.
- #define SUART_TXD
- Wenn dieses Define gesetzt ist, wird der Transmitter aktiv bzw. die für ihn notwendigen Code-Sequenzen werden eingeblendet.
- #define SUART_RXD
- Wenn dieses Define gesetzt ist, wird der Receiver aktiv bzw. die für ihn notwendigen Code-Sequenzen werden eingeblendet.
- FIFO aktivieren
- Um die FIFO zu aktivieren, wird einfach der Header fifo.h includet und dadurch die entsprechende Codeerzeugung veranlasst.
- #define INBUF_SIZE ...
- Die Größe des Eingabe-Puffers für die Receiver-FIFO
#include <avr/io.h> #include <avr/signal.h> #include <avr/interrupt.h> #include "uart.h" // Einfach einkommentieren, falls FIFO verwendet werden soll // #include "fifo.h" #define BAUDRATE 38400 #define nop() __asm volatile ("nop") #ifdef SUART_TXD #define SUART_TXD_PORT PORTB #define SUART_TXD_DDR DDRB #define SUART_TXD_BIT PB1 static volatile uint16_t outframe; #endif // SUART_TXD #ifdef SUART_RXD #define SUART_RXD_PORT PORTB #define SUART_RXD_PIN PINB #define SUART_RXD_DDR DDRB #define SUART_RXD_BIT PB0 static volatile uint16_t inframe; static volatile uint8_t inbits, received; # ifdef _FIFO_H_ #define INBUF_SIZE 4 static uint8_t inbuf[INBUF_SIZE]; fifo_t infifo; # else // _FIFO_H_ static volatile uint8_t indata; # endif // _FIFO_H_ #endif // SUART_RXD
Initialisierung
Hier wird der Software-UART initialisiert. Die Initialisierung ist gegen Interrupts gesperrt. Das Modus- und OCR1A-Register von Timer1 werden so initialisiert, daß eine Zeitbasis für den UART entsteht.
void uart_init() { uint8_t sreg = SREG, tifr = 0; cli(); // Mode #4 für Timer1 // und volle MCU clock // IC Noise Cancel // IC on Falling Edge TCCR1A = 0; TCCR1B = (1 << WGM12) | (1 << CS10) | (0 << ICES1) | (1 << ICNC1); // PoutputCompare für gewünschte Timer1 Frequenz OCR1A = (uint16_t) ((uint32_t) F_CPU/BAUDRATE); #ifdef SUART_RXD SUART_RXD_DDR &= ~(1 << SUART_RXD_BIT); SUART_RXD_PORT |= (1 << SUART_RXD_BIT); TIMSK |= (1 << TICIE1); tifr |= (1 << ICF1) | (1 << OCF1B); #else TIMSK &= ~(1 << TICIE1); #endif // SUART_RXD #ifdef SUART_TXD tifr |= (1 << OCF1A); SUART_TXD_PORT |= (1 << SUART_TXD_BIT); SUART_TXD_DDR |= (1 << SUART_TXD_BIT); outframe = 0; #endif // SUART_TXD TIFR = tifr; SREG = sreg; }
Transmitter
Das Senden ist am einfachsten: Zuerst wird gewartet, bis eine evtl. laufende Sendung abgeschlossen ist. Dann wird der Ausgabe-Frame zusammengebaut (1 Startbit, 8 Datenbits, 1 Stopbit und 1 Bit als interne Endekennung). Dann wird OutputCompare1A-Interrupt aktiviert das entsprechende Interrupt-Flag zurückgesetzt, damit das erste Bit nicht zu kurz wird (wenn das Flag gesetzt wäre, würde direkt nach dem sei() ein Interrupt auftreten. Ein Transmit ist fertig, wenn outframe gleich 0 ist. Das Auslesen von outframe muss unter Interrupt-Deaktivierung geschehen. uart_putc geht davon aus, daß Interrupts global aktiviert sind (ansonsten hängt es sich auf bzw. es wird nicht ausgegeben).
#ifdef SUART_TXD void uart_putc (const char c) { do { sei(); nop(); cli(); // yield(); } while (outframe); // frame = *.P.7.6.5.4.3.2.1.0.S S=Start(0), P=Stop(1), *=Endemarke(1) outframe = (3 << 9) | (((uint8_t) c) << 1); TIMSK |= (1 << OCIE1A); TIFR = (1 << OCF1A); sei(); } #endif // SUART_TXD
Danach kommt es zu OC1A-Interrupts. Für jeden IRQ wird ein Bit ausgegeben. Im letzten Interrupt wird das Senden eingestellt und die ISR deaktiviert ihren eigenen IRQ.
#ifdef SUART_TXD SIGNAL (SIG_OUTPUT_COMPARE1A) { uint16_t data = outframe; if (data & 1) SUART_TXD_PORT |= (1 << SUART_TXD_BIT); else SUART_TXD_PORT &= ~(1 << SUART_TXD_BIT); if (1 == data) { TIMSK &= ~(1 << OCIE1A); } outframe = data >> 1; } #endif // SUART_TXD
Receiver
Eine fallende Flanke löst einen Input-Capture-IRQ aus. Der in ICR1 gesicherte Timer1-Wert wird gelesen, und die halbe Bit-Zeit OCR1A/2 wird draufaddiert, um den Empfangszeitpunkt (sample time) zu erhalten. Die ISR deaktiviert ihren IRQ und aktiviert stattdessen den OutputCompare1B-Interrupt, der die Bits einsammelt.
#ifdef SUART_RXD SIGNAL (SIG_INPUT_CAPTURE1) { uint16_t icr1 = ICR1; uint16_t ocr1a = OCR1A; // Eine halbe Bitzeit zu ICR1 addieren (modulo OCR1A) und nach OCR1B uint16_t ocr1b = icr1 + ocr1a/2; if (ocr1b >= ocr1a) ocr1b -= ocr1a; OCR1B = ocr1b; TIFR = (1 << OCF1B); TIMSK = (TIMSK & ~(1 << TICIE1)) | (1 << OCIE1B); inframe = 0; inbits = 0; } #endif // SUART_RXD
Die OutputCompare1B-ISR sammelt die Bits ein. Wurden 10 Bits übertragen, wird das empfangene Byte gespeichert, falls es nicht ungültig ist. Gespeichert wird in der Modul-globalen Variablen indata (oder bei aktiviertem FIFO-Mechanismus in infifo), und der Empfangsmerker received wird gesetzt.
#ifdef SUART_RXD SIGNAL (SIG_OUTPUT_COMPARE1B) { uint16_t data = inframe >> 1; if (SUART_RXD_PIN & (1 << SUART_RXD_BIT)) data |= (1 << 9); uint8_t bits = inbits+1; if (10 == bits) { if ((data & 1) == 0) if (data >= (1 << 9)) { #ifdef _FIFO_H_ _inline_fifo_put (&infifo, data >> 1); #else indata = data >> 1; #endif // _FIFO_H_ received = 1; } TIMSK = (TIMSK & ~(1 << OCIE1B)) | (1 << TICIE1); TIFR = (1 << ICF1); } else { inbits = bits; inframe = data; } } #endif // SUART_RXD
Zwei Funktionen, um empfangene Daten zu lesen. Je nachdem, ob die FIFO verwendet wird, sind diese Funktionen unterschiedlich implementiert. Das Benutzer-Interface bleibt jedoch gleich, und die Verwendung einer FIFO transparent.
#ifdef SUART_RXD #ifdef _FIFO_H_ int uart_getc_wait() { return (int) fifo_get_wait (&infifo); } int uart_getc_nowait() { return fifo_get_nowait (&infifo); } #else // _FIFO_H_ int uart_getc_wait() { while (!received) {} received = 0; return (int) indata; } int uart_getc_nowait() { if (received) { received = 0; return (int) indata; } return -1; } #endif // _FIFO_H_ #endif // SUART_RXD
Benutzer-Interface und Header
Im Header uart.h werden die Funktionen veröffentlicht und stehen in anderen Modulen zur Verfügung:
- extern void uart_init()
- Initialisiert den Soft-UART. Vor Verwendung des Soft-UARTs müssen Interrupts mit sei() global aktiviert worden sein (egal, ob vor oder nach dem Aufruf von uart_init).
- extern void uart_putc (const char c)
- Sendet das Zeichen c. Vorher wird gewartet, bis eine evtl. laufende Sendung beendet ist.
- extern int uart_getc_wait()
- Wartet bis zum nächsten Empfang bzw. liefert das empfangene Zeichen im Wertebereich 0...255.
- extern int uart_getc_nowait()
- Schaut nach, ob ein Zeichen empfangen wurde und liefert dieses gegebenenfalls als int zurück (Wertebereich ist 0...255). Wurde nichts empfangen, wird -1 geliefert.
Die Defines SUART_TXD und SUART_RXD zum Aktivieren der Code-Sequenzen für Transmitter resp. Receiver stehen hier im Header; das kann natürlich auch in die Kommandozeile/ins Makefile wandern und mit -DSUART_RXD etc. angeschaltet werden, Gleiches gilt für F_CPU und BAUDRATE.
#ifndef _UART_H_ #define _UART_H_ #define SUART_TXD #define SUART_RXD extern void uart_init(); #ifdef SUART_TXD extern void uart_putc (const char); #endif // SUART_RXD #ifdef SUART_RXD extern int uart_getc_wait (); extern int uart_getc_nowait(); #endif // SUART_RXD #endif /* _UART_H_ */