K (→WebLinks) |
|||
Zeile 1: | Zeile 1: | ||
+ | = Einleitung - | ||
+ | Hier werden zwei verschiedene Arten vorgestellt, wie die UART benutzt werden kann. Die erste Variante verzichtet auf Interrupts und pollt permanent nach eingehenden Daten (''live lock''). Diese Variante ist einfach, aber nicht effizient. Die zweite Variante arbeitet mit Interrupts. Das angegebene Beispiel benutzt FIFO-Methoden aus einer nicht-AVR-Sourcen. Am Ende der Seite ist ein Link zu den notwendigen Sourcen. | ||
+ | |||
= Ohne Interrupts = | = Ohne Interrupts = | ||
Zeile 5: | Zeile 8: | ||
== C-Datei == | == C-Datei == | ||
− | Der USART wird | + | Der USART wird initialisiert als UART mit dem Datenformat 8N1 (8 Datenbits, kein Parity, 1 Stopbit). |
Teilweise haben die Register andere Namen, etwa wenn mehr als ein USART vorhanden ist. Dann Gibt es Register UBRR0L, UBRR1L, etc. | Teilweise haben die Register andere Namen, etwa wenn mehr als ein USART vorhanden ist. Dann Gibt es Register UBRR0L, UBRR1L, etc. | ||
Zeile 123: | Zeile 126: | ||
#include <avr/interrupt.h> | #include <avr/interrupt.h> | ||
#include "uart.h" | #include "uart.h" | ||
− | #include "fifo.h" | + | #include "fifo.h" /* siehe Links weiter unten auf dieser Website */ |
#define BAUDRATE 38400 | #define BAUDRATE 38400 |
Version vom 29. Juli 2007, 18:19 Uhr
= Einleitung - Hier werden zwei verschiedene Arten vorgestellt, wie die UART benutzt werden kann. Die erste Variante verzichtet auf Interrupts und pollt permanent nach eingehenden Daten (live lock). Diese Variante ist einfach, aber nicht effizient. Die zweite Variante arbeitet mit Interrupts. Das angegebene Beispiel benutzt FIFO-Methoden aus einer nicht-AVR-Sourcen. Am Ende der Seite ist ein Link zu den notwendigen Sourcen.
Inhaltsverzeichnis
Ohne Interrupts
Hier eine der einfachsten Möglichkeiten, den Harware-UART von AVR zu nutzen. Die Empfangs- und Sendefunktionen sind so kurz, daß sie am besten als inline-Funktionen definiert werden. Auf Zeichen bzw. Ereignisse zu warten bezeichnet man auch als "polling".
C-Datei
Der USART wird initialisiert als UART mit dem Datenformat 8N1 (8 Datenbits, kein Parity, 1 Stopbit).
Teilweise haben die Register andere Namen, etwa wenn mehr als ein USART vorhanden ist. Dann Gibt es Register UBRR0L, UBRR1L, etc.
uart.c
#include "uart.h" #define BAUDRATE 38400 void uart_init() { uint16_t ubrr = (uint16_t) ((uint32_t) F_CPU/(16*BAUDRATE) - 1); UBRRH = (uint8_t) (ubrr>>8); UBRRL = (uint8_t) (ubrr); // UART Receiver und Transmitter anschalten // Data mode 8N1, asynchron UCSRB = (1 << RXEN) | (1 << TXEN); UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0); // Flush Receive-Buffer (entfernen evtl. vorhandener ungültiger Werte) do { UDR; } while (UCSRA & (1 << RXC)); }
Benutzer-Schnittstelle 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 UART und aktiviert Receiver und Transmitter.
- static inline int uart_putc (const uint8_t c)
- Sendet das Zeichen c über den UART. Der return-Wert ist immer 1.
- static inline uint8_t uart_getc_wait()
- Wartet bis zum nächsten Empfang bzw. liefert das empfangene Zeichen.
- static inline 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 F_CPU und BAUDRATE geben die Taktrate des AVR sowie die Baudrate an. Dabei hat F_CPU nur rein informativen Character, es ändert die CPU-Frequenz nicht! Der Define für F_CPU kann man in die Quelle dazu schreiben, oder man gibt einen Wert per Kommandozeile/Makefile an mit -DF_CPU=...
uart.h
#ifndef _UART_H_ #define _UART_H_ #include <avr/io.h> extern void uart_init(); static inline int uart_putc (const uint8_t c) { // Warten, bis UDR bereit ist für einen neuen Wert while (!(UCSRA & (1 << UDRE))) ; // UDR Schreiben startet die Übertragung UDR = c; return 1; } static inline uint8_t uart_getc_wait() { // Warten, bis etwas empfangen wird while (!(UCSRA & (1 << RXC))) ; // Das empfangene Zeichen zurückliefern return UDR; } static inline int uart_getc_nowait() { // Liefer das empfangene Zeichen, falls etwas empfangen wurde; -1 sonst return (UCSRA & (1 << RXC)) ? (int) UDR : -1; } #endif /* _UART_H_ */
Mit Interrupts
Benutzer-Schnittstelle und Header
Im Header uart.h werden die Funktionen veröffentlicht und stehen in anderen Modulen zur Verfügung. Das Interface ist das gleiche wie beim Polling.
- extern void uart_init()
- Initialisiert den UART und aktiviert Receiver und Transmitter sowie den Receive-Interrupt. Die Ein- und Ausgebe-FIFO werden initialisiert. Das globale Interrupt-Enable-Flag (I-Bit in SREG) wird nicht verändert.
- extern int uart_putc (const uint8_t c)
- Speichert das Zeichen c in der Ausgabe-Warteschlange. Der return-Wert ist 1, falls das Zeichen in die FIFO eingetragen wurde, und 0, falls die Ausgabe-FIFO voll ist. Der UART DATA-Interupt wird aktiviert, der die Zeichen aus der Ausgabe-FIFO über den UART verschickt.
- extern uint8_t uart_getc_wait()
- Liefert das nächste empfangene Zeichen aus der Eingabe-FIFO bzw. wartet darauf, falls die FIFO leer ist.
- 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.
- static inline void uart_flush()
- Wartet, bis die Übertragung fertig ist.
- uart.h
#ifndef _UART_H_ #define _UART_H_ #include <avr/io.h> extern void uart_init(); extern int uart_putc (const uint8_t); extern uint8_t uart_getc_wait(); extern int uart_getc_nowait(); static inline void uart_flush() { while (UCSRB & (1 << UDRIE)); } #endif /* _UART_H_ */
C-Datei
Dateikopf und Initialisierung
#include <avr/io.h> #include <avr/interrupt.h> #include "uart.h" #include "fifo.h" /* siehe Links weiter unten auf dieser Website */ #define BAUDRATE 38400 // FIFO-Objekte und Puffer für die Ein- und Ausgabe #define BUFSIZE_IN 0x40 uint8_t inbuf[BUFSIZE_IN]; fifo_t infifo; #define BUFSIZE_OUT 0x40 uint8_t outbuf[BUFSIZE_OUT]; fifo_t outfifo; void uart_init() { uint8_t sreg = SREG; uint16_t ubrr = (uint16_t) ((uint32_t) F_CPU/(16*BAUDRATE) - 1); UBRRH = (uint8_t) (ubrr>>8); UBRRL = (uint8_t) (ubrr); // Interrupts kurz deaktivieren cli(); // UART Receiver und Transmitter anschalten, Receive-Interrupt aktivieren // Data mode 8N1, asynchron UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE); UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0); // Flush Receive-Buffer (entfernen evtl. vorhandener ungültiger Werte) do { // UDR auslesen (Wert wird nicht verwendet) UDR; } while (UCSRA & (1 << RXC)); // Rücksetzen von Receive und Transmit Complete-Flags UCSRA = (1 << RXC) | (1 << TXC); // Global Interrupt-Flag wieder herstellen SREG = sreg; // FIFOs für Ein- und Ausgabe initialisieren fifo_init (&infifo, inbuf, BUFSIZE_IN); fifo_init (&outfifo, outbuf, BUFSIZE_OUT); }
Interrupt-Routinen
// Empfangene Zeichen werden in die Eingabgs-FIFO gespeichert und warten dort SIGNAL (SIG_UART_RECV) { _inline_fifo_put (&infifo, UDR); } // Ein Zeichen aus der Ausgabe-FIFO lesen und ausgeben // Ist das Zeichen fertig ausgegeben, wird ein neuer SIG_UART_DATA-IRQ getriggert // Ist die FIFO leer, deaktiviert die ISR ihren eigenen IRQ. SIGNAL (SIG_UART_DATA) { if (outfifo.count > 0) UDR = _inline_fifo_get (&outfifo); else UCSRB &= ~(1 << UDRIE); }
Ein- und Ausgabe
Die Ein- und Ausgebefunktionen machen nichts weiter, als die empfangenen/zusendenden Zeichen aus der FIFO zu lesen/in die FIFO zu schreiben.
int uart_putc (const uint8_t c) { int ret = fifo_put (&outfifo, c); UCSRB |= (1 << UDRIE); return ret; } int uart_getc_nowait () { return fifo_get_nowait (&infifo); } uint8_t uart_getc_wait () { return fifo_get_wait (&infifo); }
Weitere Routinen
Einen String senden
Übergeben wird die Start-Adresse des Strings. Die Zeichenkette wird solange durchlaufen und die Zeichen ausgegeben, bis eine '\0' (Stringende-Marke) gelesen wird.
#include <avr/interrupt.h> // Wird nur gebraucht bei der Interrupt-Version #include "uart.h" // Einen 0-terminierten String übertragen. void uart_puts (const char *s) { do { uart_putc (*s); } while (*s++); } // Ein Zeilenumbruch, abhängig davon, was die Gegenstelle haben will // Windows: "rn" // Linux : "n" // MacOS : "r" #define CR "\r\n" char text[] = "Hallo Welt." CR; int main() { uart_init(); sei(); // Wird nur gebraucht bei der Interrupt-Version uart_puts (text); uart_puts ("Hallo Welt!" CR); return 0; }
Einen konstanten String senden
Unveränderliche Strings brauchen kein Platz im SRAM zu verschwenden, man lässt sie im Flash (wo sie sonst ebenfalls stehen für die SRAM-Initialisierung).
#include <avr/interrupt.h> // Wird nur gebraucht bei der Interrupt-Version #include <avr/pgmspace.h> #include "uart.h" // Einen 0-terminierten String senden, der im Flash steht. void uart_puts_P (PGM_P s) { while (1) { unsigned char c = pgm_read_byte (s); s++; if ('\0' == c) break; uart_putc (c); } } // Ein Zeilenumbruch, abhängig davon, was die Gegenstelle haben will // Windows: "rn" // Linux : "n" // MacOS : "r" #define CR "\r\n" const prog_char text_p[] = "Hallo Welt." CR; int main() { uart_init(); sei(); // Wird nur gebraucht bei der Interrupt-Version uart_puts_P (text_p); uart_puts_P (PSTR("Hallo Welt!" CR)); return 0; }