diff --git a/serial-avr.c b/serial-avr.c new file mode 100644 index 0000000..a67a54a --- /dev/null +++ b/serial-avr.c @@ -0,0 +1,258 @@ + +/** \file + \brief Serial subsystem, AVR specific part. + + To be included from serial.c, for details see there. +*/ + +#if defined TEACUP_C_INCLUDE && defined __AVR__ + +#include +#include "memory_barrier.h" +#include "arduino.h" + +/** \def BUFSIZE + + Size of TX and RX buffers. MUST be a \f$2^n\f$ value. + + Unlike ARM MCUs, which come with a hardware buffer, AVRs require a read and + transmit buffer implemented in software. This buffer not only raises + reliability, it also allows transmitting characters from interrupt context. +*/ +#define BUFSIZE 64 + +/** \def ASCII_XOFF + + ASCII XOFF character. +*/ +#define ASCII_XOFF 19 + +/** \def ASCII_XON + + ASCII XON character. +*/ +#define ASCII_XON 17 + + +#ifndef USB_SERIAL + +/** RX buffer. + + rxhead is the head pointer and points to the next available space. + + rxtail is the tail pointer and points to last character in the buffer. +*/ +volatile uint8_t rxhead = 0; +volatile uint8_t rxtail = 0; +volatile uint8_t rxbuf[BUFSIZE]; + +/** TX buffer. + + Same mechanism as RX buffer. +*/ +volatile uint8_t txhead = 0; +volatile uint8_t txtail = 0; +volatile uint8_t txbuf[BUFSIZE]; + +/** Ringbuffer logic. + + head = written data pointer. + tail = read data pointer. + + When head == tail, buffer is empty. + When head + 1 == tail, buffer is full. + Thus, number of available spaces in buffer is (tail - head) & bufsize. + + Can write: + (tail - head - 1) & (BUFSIZE - 1) + + Write to buffer: + buf[head++] = data; head &= (BUFSIZE - 1); + + Can read: + (head - tail) & (BUFSIZE - 1) + + Read from buffer: + data = buf[tail++]; tail &= (BUFSIZE - 1); +*/ +/** \def buf_canread() + + Check if we can read from this buffer. +*/ +#define buf_canread(buffer) ((buffer ## head - buffer ## tail ) & \ + (BUFSIZE - 1)) + +/** \def buf_pop() + + Actually read from this buffer. +*/ +#define buf_pop(buffer, data) do { \ + data = buffer ## buf[buffer ## tail]; \ + buffer ## tail = (buffer ## tail + 1) & \ + (BUFSIZE - 1); \ + } while (0) + +/** \def buf_canwrite() + + Check if we can write to this buffer. +*/ +#define buf_canwrite(buffer) ((buffer ## tail - buffer ## head - 1) & \ + (BUFSIZE - 1)) + +/** \def buf_push() + + Actually write to this buffer. +*/ +#define buf_push(buffer, data) do { \ + buffer ## buf[buffer ## head] = data; \ + buffer ## head = (buffer ## head + 1) & \ + (BUFSIZE - 1); \ + } while (0) + +#ifdef XONXOFF +#define FLOWFLAG_STATE_XOFF 0 +#define FLOWFLAG_SEND_XON 1 +#define FLOWFLAG_SEND_XOFF 2 +#define FLOWFLAG_STATE_XON 4 +// initially, send an XON +volatile uint8_t flowflags = FLOWFLAG_SEND_XON; +#endif + + +/** Initialise serial subsystem. + + Set up baud generator and interrupts, clear buffers. +*/ +void serial_init() { + + #if BAUD > 38401 + UCSR0A = MASK(U2X0); + UBRR0 = (((F_CPU / 8) / BAUD) - 0.5); + #else + UCSR0A = 0; + UBRR0 = (((F_CPU / 16) / BAUD) - 0.5); + #endif + + UCSR0B = MASK(RXEN0) | MASK(TXEN0); + UCSR0C = MASK(UCSZ01) | MASK(UCSZ00); + + UCSR0B |= MASK(RXCIE0) | MASK(UDRIE0); +} + +/** Receive interrupt. + + We have received a character, stuff it in the RX buffer if we can, or drop + it if we can't. Using the pragma inside the function is incompatible with + Arduinos' gcc. +*/ +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#ifdef USART_RX_vect +ISR(USART_RX_vect) +#else +ISR(USART0_RX_vect) +#endif +{ + if (buf_canwrite(rx)) + buf_push(rx, UDR0); + else { + // Not reading the character makes the interrupt logic to swamp us with + // retries, so better read it and throw it away. + //#pragma GCC diagnostic ignored "-Wunused-but-set-variable" + uint8_t trash; + //#pragma GCC diagnostic pop + + trash = UDR0; + } + + #ifdef XONXOFF + if (flowflags & FLOWFLAG_STATE_XON && buf_canwrite(rx) <= 16) { + // The buffer has only 16 free characters left, so send an XOFF. + // More characters might come in until the XOFF takes effect. + flowflags = FLOWFLAG_SEND_XOFF | FLOWFLAG_STATE_XON; + // Enable TX interrupt so we can send this character. + UCSR0B |= MASK(UDRIE0); + } + #endif +} +#pragma GCC diagnostic pop + +/** Transmit buffer ready interrupt. + + Provide the next character to transmit if we can, otherwise disable this + interrupt. +*/ +#ifdef USART_UDRE_vect +ISR(USART_UDRE_vect) +#else +ISR(USART0_UDRE_vect) +#endif +{ + #ifdef XONXOFF + if (flowflags & FLOWFLAG_SEND_XON) { + UDR0 = ASCII_XON; + flowflags = FLOWFLAG_STATE_XON; + } + else if (flowflags & FLOWFLAG_SEND_XOFF) { + UDR0 = ASCII_XOFF; + flowflags = FLOWFLAG_STATE_XOFF; + } + else + #endif + + if (buf_canread(tx)) + buf_pop(tx, UDR0); + else + UCSR0B &= ~MASK(UDRIE0); +} + +/** Check how many characters can be read. +*/ +uint8_t serial_rxchars() { + return buf_canread(rx); +} + +/** Read one character. +*/ +uint8_t serial_popchar() { + uint8_t c = 0; + + // It's imperative that we check, because if the buffer is empty and we + // pop, we'll go through the whole buffer again. + if (buf_canread(rx)) + buf_pop(rx, c); + + #ifdef XONXOFF + if ((flowflags & FLOWFLAG_STATE_XON) == 0 && buf_canread(rx) <= 16) { + // The buffer has (BUFSIZE - 16) free characters again, so send an XON. + flowflags = FLOWFLAG_SEND_XON; + UCSR0B |= MASK(UDRIE0); + } + #endif + + return c; +} + +/** Send one character. +*/ +void serial_writechar(uint8_t data) { + + // Check if interrupts are enabled. + if (SREG & MASK(SREG_I)) { + // If they are, we should be ok to block since the tx buffer is emptied + // from an interrupt. + for ( ; buf_canwrite(tx) == 0; ) ; + buf_push(tx, data); + } + else { + // Interrupts are disabled -- maybe we're in one? + // Anyway, instead of blocking, only write if we have room. + if (buf_canwrite(tx)) + buf_push(tx, data); + } + + // Enable TX interrupt so we can send this character. + UCSR0B |= MASK(UDRIE0); +} +#endif /* USB_SERIAL */ + +#endif /* defined TEACUP_C_INCLUDE && defined __AVR__ */ diff --git a/serial.c b/serial.c index 46adcd3..8cae001 100644 --- a/serial.c +++ b/serial.c @@ -10,212 +10,11 @@ It also supports XON/XOFF flow control of the receive buffer, to help avoid overruns. */ -#include -#include "memory_barrier.h" +#define TEACUP_C_INCLUDE +#include "serial-avr.c" +//#include "serial-arm.c" +#undef TEACUP_C_INCLUDE -#include "arduino.h" - -/// size of TX and RX buffers. MUST be a \f$2^n\f$ value -#define BUFSIZE 64 - -/// ascii XOFF character -#define ASCII_XOFF 19 -/// ascii XON character -#define ASCII_XON 17 - -#ifndef USB_SERIAL -/// rx buffer head pointer. Points to next available space. -volatile uint8_t rxhead = 0; -/// rx buffer tail pointer. Points to last character in buffer -volatile uint8_t rxtail = 0; -/// rx buffer -volatile uint8_t rxbuf[BUFSIZE]; - -/// tx buffer head pointer. Points to next available space. -volatile uint8_t txhead = 0; -/// tx buffer tail pointer. Points to last character in buffer -volatile uint8_t txtail = 0; -/// tx buffer -volatile uint8_t txbuf[BUFSIZE]; - -/// check if we can read from this buffer -#define buf_canread(buffer) ((buffer ## head - buffer ## tail ) & (BUFSIZE - 1)) -/// read from buffer -#define buf_pop(buffer, data) do { data = buffer ## buf[buffer ## tail]; buffer ## tail = (buffer ## tail + 1) & (BUFSIZE - 1); } while (0) - -/// check if we can write to this buffer -#define buf_canwrite(buffer) ((buffer ## tail - buffer ## head - 1) & (BUFSIZE - 1)) -/// write to buffer -#define buf_push(buffer, data) do { buffer ## buf[buffer ## head] = data; buffer ## head = (buffer ## head + 1) & (BUFSIZE - 1); } while (0) - -/* - ringbuffer logic: - head = written data pointer - tail = read data pointer - - when head == tail, buffer is empty - when head + 1 == tail, buffer is full - thus, number of available spaces in buffer is (tail - head) & bufsize - - can write: - (tail - head - 1) & (BUFSIZE - 1) - - write to buffer: - buf[head++] = data; head &= (BUFSIZE - 1); - - can read: - (head - tail) & (BUFSIZE - 1) - - read from buffer: - data = buf[tail++]; tail &= (BUFSIZE - 1); -*/ - -#ifdef XONXOFF -#define FLOWFLAG_STATE_XOFF 0 -#define FLOWFLAG_SEND_XON 1 -#define FLOWFLAG_SEND_XOFF 2 -#define FLOWFLAG_STATE_XON 4 -// initially, send an XON -volatile uint8_t flowflags = FLOWFLAG_SEND_XON; -#endif - -/// initialise serial subsystem -/// -/// set up baud generator and interrupts, clear buffers -void serial_init() -{ -#if BAUD > 38401 - UCSR0A = MASK(U2X0); - UBRR0 = (((F_CPU / 8) / BAUD) - 0.5); -#else - UCSR0A = 0; - UBRR0 = (((F_CPU / 16) / BAUD) - 0.5); -#endif - - UCSR0B = MASK(RXEN0) | MASK(TXEN0); - UCSR0C = MASK(UCSZ01) | MASK(UCSZ00); - - UCSR0B |= MASK(RXCIE0) | MASK(UDRIE0); -} - -/* - Interrupts -*/ - -/// receive interrupt -/// -/// we have received a character, stuff it in the rx buffer if we can, or drop it if we can't -// Using the pragma inside the function is incompatible with Arduinos' gcc. -#pragma GCC diagnostic ignored "-Wunused-but-set-variable" -#ifdef USART_RX_vect -ISR(USART_RX_vect) -#else -ISR(USART0_RX_vect) -#endif -{ - if (buf_canwrite(rx)) - buf_push(rx, UDR0); - else { - // Not reading the character makes the interrupt logic to swamp us with - // retries, so better read it and throw it away. - // #pragma GCC diagnostic ignored "-Wunused-but-set-variable" - uint8_t trash; - // #pragma GCC diagnostic pop - - trash = UDR0; - } - - #ifdef XONXOFF - if (flowflags & FLOWFLAG_STATE_XON && buf_canwrite(rx) <= 16) { - // the buffer has only 16 free characters left, so send an XOFF - // more characters might come in until the XOFF takes effect - flowflags = FLOWFLAG_SEND_XOFF | FLOWFLAG_STATE_XON; - // enable TX interrupt so we can send this character - UCSR0B |= MASK(UDRIE0); - } - #endif -} -#pragma GCC diagnostic pop - -/// transmit buffer ready interrupt -/// -/// provide the next character to transmit if we can, otherwise disable this interrupt -#ifdef USART_UDRE_vect -ISR(USART_UDRE_vect) -#else -ISR(USART0_UDRE_vect) -#endif -{ - #ifdef XONXOFF - if (flowflags & FLOWFLAG_SEND_XON) { - UDR0 = ASCII_XON; - flowflags = FLOWFLAG_STATE_XON; - } - else if (flowflags & FLOWFLAG_SEND_XOFF) { - UDR0 = ASCII_XOFF; - flowflags = FLOWFLAG_STATE_XOFF; - } - else - #endif - if (buf_canread(tx)) - buf_pop(tx, UDR0); - else - UCSR0B &= ~MASK(UDRIE0); -} - -/* - Read -*/ - -/// check how many characters can be read -uint8_t serial_rxchars() -{ - return buf_canread(rx); -} - -/// read one character -uint8_t serial_popchar() -{ - uint8_t c = 0; - - // it's imperative that we check, because if the buffer is empty and we pop, we'll go through the whole buffer again - if (buf_canread(rx)) - buf_pop(rx, c); - - #ifdef XONXOFF - if ((flowflags & FLOWFLAG_STATE_XON) == 0 && buf_canread(rx) <= 16) { - // the buffer has (BUFSIZE - 16) free characters again, so send an XON - flowflags = FLOWFLAG_SEND_XON; - UCSR0B |= MASK(UDRIE0); - } - #endif - - return c; -} - -/* - Write -*/ - -/// send one character -void serial_writechar(uint8_t data) -{ - // check if interrupts are enabled - if (SREG & MASK(SREG_I)) { - // if they are, we should be ok to block since the tx buffer is emptied from an interrupt - for (;buf_canwrite(tx) == 0;); - buf_push(tx, data); - } - else { - // interrupts are disabled- maybe we're in one? - // anyway, instead of blocking, only write if we have room - if (buf_canwrite(tx)) - buf_push(tx, data); - } - // enable TX interrupt so we can send this character - UCSR0B |= MASK(UDRIE0); -} -#endif /* USB_SERIAL */ /// send a string- look for null byte instead of expecting a length void serial_writestr(uint8_t *data)