avr: initial commit
[z80.git] / avr / uart.c
diff --git a/avr/uart.c b/avr/uart.c
new file mode 100644 (file)
index 0000000..f4459f3
--- /dev/null
@@ -0,0 +1,113 @@
+/* interrupt-driven UART routines */
+
+#include <stdio.h>
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+
+#include "uart.h"
+
+static volatile uint8_t uart_txbuf[UART_TXBUFSIZE];
+static volatile uint8_t uart_tx_rp = 0;
+static volatile uint8_t uart_tx_wp = 0;
+static volatile uint8_t uart_rxbuf[UART_RXBUFSIZE];
+static volatile uint8_t uart_rx_rp = 0;
+static volatile uint8_t uart_rx_wp = 0;
+
+/* global, external accessable variables */
+volatile uint8_t uart_break = 0;       /* 0: no break detected */
+volatile uint8_t uart_echo  = 0;       /* 0: disabled, otherwise enabled */
+
+void uart_init(void)
+{
+       UBRR0  = ((F_CPU + BAUDRATE * 8) / (BAUDRATE * 16) - 1);
+       UCSR0B = (1<<RXCIE0) | (1<<RXEN0) | (1<<TXEN0);
+       UCSR0C = (1<<UCSZ01) | (1<<UCSZ00);
+}
+
+uint8_t uart_rx_state(void)
+{
+       /* number of bytes in Rx buffer */
+       return((uint8_t)(uart_rx_wp - uart_rx_rp) % UART_RXBUFSIZE);
+}
+
+uint8_t uart_tx_state(void)
+{
+       /* number of bytes in Tx buffer */
+       return((uint8_t)(uart_tx_wp - uart_tx_rp) & UART_TXBUFSIZE);
+}
+
+int uart_putc(char c, FILE *stream)
+{
+       if(c == '\n') uart_putc('\r', stream);
+
+       uint8_t tmp = (uart_tx_wp + 1) % UART_TXBUFSIZE;
+
+       /* buffer full -> block */
+       while(tmp == uart_tx_rp);
+
+       /* enqueue */
+       uart_txbuf[uart_tx_wp] = c;
+       uart_tx_wp = tmp;
+
+       /* enable UDRE interrupt */
+       UCSR0B |= (1<<UDRIE0);
+
+       return 0;
+}
+
+ISR(USART_UDRE_vect)
+{
+       uint8_t tmp;
+
+       if(uart_tx_rp != uart_tx_wp) {
+               /* dequeue */
+               tmp = (uart_tx_rp + 1) % UART_TXBUFSIZE;
+               UDR0 = uart_txbuf[uart_tx_rp];
+               uart_tx_rp = tmp;
+       } else {
+               /* buffer empty -> disable interrupt */
+               UCSR0B &= ~(1<<UDRIE0);
+       }
+}
+
+int uart_getc(FILE *stream)
+{
+       uint8_t tmp = (uart_rx_rp + 1) % UART_RXBUFSIZE;
+
+       /* empty buffer -> block */
+       while(uart_rx_rp == uart_rx_wp);
+
+       /* dequeue data */
+       uart_rx_rp = tmp;
+       unsigned char c = uart_rxbuf[uart_rx_rp];
+
+       if(uart_echo) {
+               /* echo */
+               if(c == '\r') uart_putc('\n', stream);
+               uart_putc(c, stream);
+       }
+
+       return c;
+}
+
+ISR(USART_RX_vect) {
+       uint8_t tmp  = (uart_rx_wp + 1) % UART_RXBUFSIZE;
+       uint8_t ferr = UCSR0A & (1<<FE0);
+       uint8_t data = UDR0;
+
+       /* check for BREAK */
+       if(ferr && (data == 0)) {
+               uart_break = 1;
+               return;
+       }
+
+       /* enqueue if space in buffer */
+       if(uart_rx_rp != tmp) {
+               uart_rx_wp = tmp;
+               uart_rxbuf[uart_rx_wp] = data;
+       }
+}
+
+FILE uart_out = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE);
+FILE uart_in  = FDEV_SETUP_STREAM(NULL, uart_getc, _FDEV_SETUP_READ);