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.
This commit is contained in:
Markus Hitter 2015-07-11 17:23:17 +02:00
parent 43626b2ba2
commit 24b2cd1d02
2 changed files with 262 additions and 205 deletions

258
serial-avr.c Normal file
View File

@ -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 <avr/interrupt.h>
#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__ */

209
serial.c
View File

@ -10,212 +10,11 @@
It also supports XON/XOFF flow control of the receive buffer, to help avoid overruns.
*/
#include <avr/interrupt.h>
#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)