259 lines
6.0 KiB
C
259 lines
6.0 KiB
C
|
|
/** \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__ */
|