From 24b2cd1d02837fbe11690497e8c06a77bbe2258c Mon Sep 17 00:00:00 2001 From: Markus Hitter Date: Sat, 11 Jul 2015 17:23:17 +0200 Subject: [PATCH] ARM: start generic port by splitting out AVR specific serial code. This shows the new strategy to deal with architecture-specific code: - Keep common code as before. - Keep the header file unchanged as well, no architecture specific headers. - Move architecture specific code to an architecture specific file and wrap the whole contents into an architecture test. - Also wrap the whole contents with #ifdef TEACUP_C_INCLUDE. Without this wrapping, Arduino IDE as well as Configtool would compile the stuff twice, because they compile everything unconditionally. - Last not least, #define TEACUP_C_INCLUDE and #include all architecture specific files unconditionally. Build tests were successful with the Makefile, with Configtool and with Arduino 1.5.8, so this strategy is expected to work. Regarding the copy operation of this commit: code unchanged, other than rewriting of all the comments for the current idea of 'proper' formatting, getting rid of tabs and some other whitespace editing. --- serial-avr.c | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++ serial.c | 209 +---------------------------------------- 2 files changed, 262 insertions(+), 205 deletions(-) create mode 100644 serial-avr.c 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)