summaryrefslogtreecommitdiff
path: root/avr/serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'avr/serial.c')
-rw-r--r--avr/serial.c217
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;
+ }
+}
+