diff options
Diffstat (limited to 'avr/serial.c')
-rw-r--r-- | avr/serial.c | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/avr/serial.c b/avr/serial.c new file mode 100644 index 0000000..4b3cd42 --- /dev/null +++ b/avr/serial.c @@ -0,0 +1,217 @@ +/* + */ + +#include <avr/io.h> +#include <avr/interrupt.h> +#include <util/atomic.h> +#include <errno.h> +#include <stdio.h> + +#include "serial.h" + + +static int _write(char c, FILE *stream); +static FILE mystdout = FDEV_SETUP_STREAM(_write, + NULL, _FDEV_SETUP_WRITE); + +#define CTL_S ('S'-'@') /* DC3 (Stop) */ +#define CTL_Q ('Q'-'@') /* DC1 (Start) */ + +typedef enum { + IDLE, + ACTIVE, + REQ_STOP, + REQ_CONT, + STOPPED +} xmit_stat_t; + + +static volatile uint8_t stat_tx; +static volatile uint8_t stat_rx; + + +struct ring { + uint8_t *data; + uint_fast8_t mask; + volatile uint_fast8_t begin; + volatile uint_fast8_t end; +}; + + +#define BUFFER_SIZE 256 + +#if ((BUFFER_SIZE-1) & BUFFER_SIZE) +# error: BUFFER_SIZE not power of 2 +#endif + +#if ((BUFFER_SIZE) > 256) +# error: BUFFER_SIZE +#endif + +struct ring rx_ring; +struct ring tx_ring; +uint8_t rx_ring_buffer[BUFFER_SIZE]; +uint8_t tx_ring_buffer[BUFFER_SIZE]; + + +static void ring_init(struct ring *ring, uint8_t *buf, int size) +{ + ring->data = buf; + ring->mask = (size-1) & 0xff; + ring->begin = 0; + ring->end = 0; +} + +static int ring_write_ch(struct ring *ring, uint8_t ch) +{ + uint_fast8_t ep = (ring->end + 1) & ring->mask; + + if ((ep) != ring->begin) { + ring->data[ring->end] = ch; + ring->end = ep; + return 1; + } + + return -1; +} + +#if 0 +static int ring_write(struct ring *ring, uint8_t *data, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (ring_write_ch(ring, data[i]) < 0) + return -i; + } + + return i; +} +#endif + +static int ring_read_ch(struct ring *ring) +{ + int ret = -1; + uint_fast8_t i = ring->begin; + + if (i != ring->end) { + ret = ring->data[i]; + ring->begin = (i +1) & ring->mask; + } + + return ret; +} + + +static int_fast8_t ring_is_empty(struct ring *ring) +{ + return ring->begin == ring->end; +} + + +/* Initialize UART */ + +void usart0_setup(void) { + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + + PRR0 &= ~_BV(PRUSART0); + UCSR0B = 0; + + /* Initialize ring buffers. */ + ring_init(&rx_ring, rx_ring_buffer, BUFFER_SIZE); + ring_init(&tx_ring, tx_ring_buffer, BUFFER_SIZE); + + stat_tx = ACTIVE; + stat_rx = ACTIVE; + + UCSR0A = _BV(U2X0); + UBRR0L = F_CPU / BAUD / 8 - 1; + UCSR0B = _BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0); + UCSR0C = 3 << UCSZ00; + }; +} + + + +/* UART RXC interrupt */ + +ISR(USART0_RX_vect) +{ + uint8_t d; + + d = UDR0; + + switch (d) { + case CTL_S: + stat_tx = REQ_STOP; + break; + case CTL_Q: + if ((stat_tx == STOPPED) || stat_tx == REQ_STOP) { + UCSR0B = _BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0) | _BV(UDRIE0); + stat_tx = ACTIVE; + } + break; + default: + ring_write_ch(&rx_ring, d); + break; + } +} + +/* UART UDRE interrupt */ + +ISR(USART0_UDRE_vect) +{ + uint8_t s; + + s = !ring_is_empty(&tx_ring); + if ((s == 0) || (stat_tx != ACTIVE)) { + UCSR0B = _BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0); + if (s) + stat_tx = STOPPED; + } else { + UDR0 = ring_read_ch(&tx_ring); + } +} + + + +/*--------------------------------------------------------------------------*/ + +void serial_setup(void) +{ + stdout = &mystdout; + usart0_setup(); +} + +/*--------------------------------------------------------------------------*/ + +int _write(char c, FILE *stream) +{ + (void) stream; + + if (c == '\n') + serial_putc('\r'); + serial_putc(c); + + return 0; +} + +int serial_getc(void) +{ + return ring_read_ch(&rx_ring); +} + +void serial_putc(uint8_t data) +{ + while (ring_write_ch(&tx_ring, data) < 0) + ; + switch (stat_tx) { + case ACTIVE: + default: + /* Enable the TXE interrupt. */ + UCSR0B = _BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0) | _BV(UDRIE0); + break; + } +} + |