6268189d31
This provides an opportunity for saving memory in the case of ports that do little or no work (e.g. the console) as well as increasing buffering for ports that receive large amounts of data in a short time (e.g. high-bitrate NMEA). git-svn-id: https://arducopter.googlecode.com/svn/trunk@425 f9c3cf11-9bcb-44bc-f272-b75c42450872
359 lines
11 KiB
C++
359 lines
11 KiB
C++
// -*- Mode: C++; c-basic-offset: 8; indent-tabs-mode: nil -*-
|
|
//
|
|
// Interrupt-driven serial transmit/receive library.
|
|
//
|
|
// Copyright (c) 2010 Michael Smith. All rights reserved.
|
|
//
|
|
// Receive and baudrate calculations derived from the Arduino
|
|
// HardwareSerial driver:
|
|
//
|
|
// Copyright (c) 2006 Nicholas Zambetti. All right reserved.
|
|
//
|
|
// Transmit algorithm inspired by work:
|
|
//
|
|
// Code Jose Julio and Jordi Munoz. DIYDrones.com
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2.1 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
//
|
|
|
|
|
|
#include "../AP_Common/AP_Common.h"
|
|
#include "FastSerial.h"
|
|
#include "WProgram.h"
|
|
|
|
#if defined(__AVR_ATmega1280__)
|
|
# define FS_MAX_PORTS 4
|
|
#else
|
|
# define FS_MAX_PORTS 1
|
|
#endif
|
|
|
|
FastSerial *__FastSerial__ports[FS_MAX_PORTS];
|
|
|
|
// Default buffer sizes
|
|
#define RX_BUFFER_SIZE 128
|
|
#define TX_BUFFER_SIZE 64
|
|
#define BUFFER_MAX 512
|
|
|
|
// Interrupt handlers //////////////////////////////////////////////////////////
|
|
|
|
#if 0
|
|
#define HANDLERS(_PORT, _RXVECTOR, _TXVECTOR, _UDR) \
|
|
SIGNAL(_RXVECTOR) \
|
|
{ \
|
|
unsigned char c = _UDR; \
|
|
ports[_PORT]->receive(c); \
|
|
} \
|
|
\
|
|
SIGNAL(_TXVECTOR) \
|
|
{ \
|
|
ports[_PORT]->transmit(); \
|
|
} \
|
|
struct hack
|
|
|
|
#if defined(__AVR_ATmega8__)
|
|
HANDLERS(0, SIG_UART_RECV, SIG_UART_DATA, UDR);
|
|
#else
|
|
HANDLERS(0, SIG_USART0_RECV, SIG_USART0_DATA, UDR0);
|
|
#if defined(__AVR_ATmega1280__)
|
|
HANDLERS(1, SIG_USART1_RECV, SIG_USART1_DATA, UDR1);
|
|
HANDLERS(2, SIG_USART2_RECV, SIG_USART2_DATA, UDR2);
|
|
HANDLERS(3, SIG_USART3_RECV, SIG_USART3_DATA, UDR3);
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
// Constructor /////////////////////////////////////////////////////////////////
|
|
|
|
FastSerial::FastSerial(const uint8_t portNumber,
|
|
volatile uint8_t *ubrrh,
|
|
volatile uint8_t *ubrrl,
|
|
volatile uint8_t *ucsra,
|
|
volatile uint8_t *ucsrb,
|
|
volatile uint8_t *udr,
|
|
const uint8_t u2x,
|
|
const uint8_t portEnableBits,
|
|
const uint8_t portTxBits)
|
|
{
|
|
_ubrrh = ubrrh;
|
|
_ubrrl = ubrrl;
|
|
_ucsra = ucsra;
|
|
_ucsrb = ucsrb;
|
|
_udr = udr;
|
|
_u2x = u2x;
|
|
_portEnableBits = portEnableBits;
|
|
_portTxBits = portTxBits;
|
|
|
|
// init buffers
|
|
_txBuffer.head = _txBuffer.tail = 0;
|
|
_rxBuffer.head = _rxBuffer.tail = 0;
|
|
|
|
// claim the port
|
|
__FastSerial__ports[portNumber] = this;
|
|
|
|
// init stdio
|
|
fdev_setup_stream(&_fd, &FastSerial::_putchar, NULL, _FDEV_SETUP_WRITE);
|
|
fdev_set_udata(&_fd, this);
|
|
if (0 == portNumber)
|
|
stdout = &_fd; // serial port 0 is always the default console
|
|
}
|
|
|
|
// Public Methods //////////////////////////////////////////////////////////////
|
|
|
|
void FastSerial::begin(long baud)
|
|
{
|
|
begin(baud, RX_BUFFER_SIZE, TX_BUFFER_SIZE);
|
|
}
|
|
|
|
void FastSerial::begin(long baud, unsigned int rxSpace, unsigned int txSpace)
|
|
{
|
|
uint16_t baud_setting;
|
|
bool use_u2x;
|
|
|
|
// if we are currently open, close and restart
|
|
if (_open)
|
|
end();
|
|
|
|
// allocate buffers
|
|
if (!_allocBuffer(&_rxBuffer, rxSpace ? : RX_BUFFER_SIZE) ||
|
|
!_allocBuffer(&_txBuffer, txSpace ? : TX_BUFFER_SIZE)) {
|
|
end();
|
|
return; // couldn't allocate buffers - fatal
|
|
}
|
|
_open = true;
|
|
|
|
// U2X mode is needed for baud rates higher than (CPU Hz / 16)
|
|
if (baud > F_CPU / 16) {
|
|
use_u2x = true;
|
|
} else {
|
|
// figure out if U2X mode would allow for a better connection
|
|
|
|
// calculate the percent difference between the baud-rate specified and
|
|
// the real baud rate for both U2X and non-U2X mode (0-255 error percent)
|
|
uint8_t nonu2x_baud_error = abs((int)(255-((F_CPU/(16*(((F_CPU/8/baud-1)/2)+1))*255)/baud)));
|
|
uint8_t u2x_baud_error = abs((int)(255-((F_CPU/(8*(((F_CPU/4/baud-1)/2)+1))*255)/baud)));
|
|
|
|
// prefer non-U2X mode because it handles clock skew better
|
|
use_u2x = (nonu2x_baud_error > u2x_baud_error);
|
|
}
|
|
|
|
if (use_u2x) {
|
|
*_ucsra = _BV(_u2x);
|
|
baud_setting = (F_CPU / 4 / baud - 1) / 2;
|
|
} else {
|
|
*_ucsra = 0;
|
|
baud_setting = (F_CPU / 8 / baud - 1) / 2;
|
|
}
|
|
|
|
// assign the baud_setting, a.k.a. ubbr (USART Baud Rate Register)
|
|
*_ubrrh = baud_setting >> 8;
|
|
*_ubrrl = baud_setting;
|
|
|
|
*_ucsrb |= _portEnableBits;
|
|
}
|
|
|
|
void FastSerial::end()
|
|
{
|
|
*_ucsrb &= ~(_portEnableBits | _portTxBits);
|
|
|
|
_freeBuffer(&_rxBuffer);
|
|
_freeBuffer(&_txBuffer);
|
|
_open = false;
|
|
}
|
|
|
|
int
|
|
FastSerial::available(void)
|
|
{
|
|
if (!_open)
|
|
return(-1);
|
|
return((_rxBuffer.head - _rxBuffer.tail) & _rxBuffer.mask);
|
|
}
|
|
|
|
int
|
|
FastSerial::read(void)
|
|
{
|
|
uint8_t c;
|
|
|
|
// if the head and tail are equal, the buffer is empty
|
|
if (!_open || (_rxBuffer.head == _rxBuffer.tail))
|
|
return(-1);
|
|
|
|
// pull character from tail
|
|
c = _rxBuffer.bytes[_rxBuffer.tail];
|
|
_rxBuffer.tail = (_rxBuffer.tail + 1) & _rxBuffer.mask;
|
|
|
|
return(c);
|
|
}
|
|
|
|
void
|
|
FastSerial::flush(void)
|
|
{
|
|
// don't reverse this or there may be problems if the RX interrupt
|
|
// occurs after reading the value of _rxBuffer.head but before writing
|
|
// the value to _rxBuffer.tail; the previous value of head
|
|
// may be written to tail, making it appear as if the buffer
|
|
// don't reverse this or there may be problems if the RX interrupt
|
|
// occurs after reading the value of head but before writing
|
|
// the value to tail; the previous value of rx_buffer_head
|
|
// may be written to tail, making it appear as if the buffer
|
|
// were full, not empty.
|
|
_rxBuffer.head = _rxBuffer.tail;
|
|
|
|
// don't reverse this or there may be problems if the TX interrupt
|
|
// occurs after reading the value of _txBuffer.tail but before writing
|
|
// the value to _txBuffer.head.
|
|
_txBuffer.tail = _rxBuffer.head;
|
|
}
|
|
|
|
void
|
|
FastSerial::write(uint8_t c)
|
|
{
|
|
int16_t i;
|
|
|
|
if (!_open) // drop bytes if not open
|
|
return;
|
|
|
|
// wait for room in the tx buffer
|
|
i = (_txBuffer.head + 1) & _txBuffer.mask;
|
|
while (i == _txBuffer.tail)
|
|
;
|
|
|
|
// add byte to the buffer
|
|
_txBuffer.bytes[_txBuffer.head] = c;
|
|
_txBuffer.head = i;
|
|
|
|
// enable the data-ready interrupt, as it may be off if the buffer is empty
|
|
*_ucsrb |= _portTxBits;
|
|
}
|
|
|
|
// STDIO emulation /////////////////////////////////////////////////////////////
|
|
|
|
int
|
|
FastSerial::_putchar(char c, FILE *stream)
|
|
{
|
|
FastSerial *fs;
|
|
|
|
fs = (FastSerial *)fdev_get_udata(stream);
|
|
fs->write(c);
|
|
return(0);
|
|
}
|
|
|
|
int
|
|
FastSerial::_getchar(FILE *stream)
|
|
{
|
|
FastSerial *fs;
|
|
|
|
fs = (FastSerial *)fdev_get_udata(stream);
|
|
|
|
// We return -1 if there is nothing to read, which the library interprets
|
|
// as an error, which our clients will need to deal with.
|
|
return(fs->read());
|
|
}
|
|
|
|
int
|
|
FastSerial::printf(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int i;
|
|
|
|
va_start(ap, fmt);
|
|
i = vfprintf(&_fd, fmt, ap);
|
|
va_end(ap);
|
|
|
|
return(i);
|
|
}
|
|
|
|
int
|
|
FastSerial::printf_P(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int i;
|
|
|
|
va_start(ap, fmt);
|
|
i = vfprintf_P(stdout, fmt, ap);
|
|
va_end(ap);
|
|
|
|
return(i);
|
|
}
|
|
|
|
// Interrupt methods ///////////////////////////////////////////////////////////
|
|
|
|
void
|
|
FastSerial::receive(uint8_t c)
|
|
{
|
|
uint8_t i;
|
|
|
|
// if we should be storing the received character into the location
|
|
// just before the tail (meaning that the head would advance to the
|
|
// current location of the tail), we're about to overflow the buffer
|
|
// and so we don't write the character or advance the head.
|
|
|
|
i = (_rxBuffer.head + 1) & _rxBuffer.mask;
|
|
if (i != _rxBuffer.tail) {
|
|
_rxBuffer.bytes[_rxBuffer.head] = c;
|
|
_rxBuffer.head = i;
|
|
}
|
|
}
|
|
|
|
void
|
|
FastSerial::transmit(void)
|
|
{
|
|
// if the buffer is not empty, send the next byte
|
|
if (_txBuffer.head != _txBuffer.tail) {
|
|
*_udr = _txBuffer.bytes[_txBuffer.tail];
|
|
_txBuffer.tail = (_txBuffer.tail + 1) & _txBuffer.mask;
|
|
}
|
|
|
|
// if the buffer is (now) empty, disable the interrupt
|
|
if (_txBuffer.head == _txBuffer.tail)
|
|
*_ucsrb &= ~_portTxBits;
|
|
}
|
|
|
|
// Buffer management ///////////////////////////////////////////////////////////
|
|
|
|
bool
|
|
FastSerial::_allocBuffer(Buffer *buffer, unsigned int size)
|
|
{
|
|
uint8_t shift;
|
|
|
|
// init buffer state
|
|
buffer->head = buffer->tail = 0;
|
|
|
|
// cap the buffer size
|
|
if (size > BUFFER_MAX)
|
|
size = BUFFER_MAX;
|
|
|
|
// compute the power of 2 greater or equal to the requested buffer size
|
|
// and then a mask to simplify wrapping operations
|
|
shift = 16 - __builtin_clz(size - 1);
|
|
buffer->mask = (1 << shift) - 1;
|
|
|
|
// allocate memory for the buffer - if this fails, we fail
|
|
buffer->bytes = (uint8_t *)malloc(buffer->mask + 1);
|
|
|
|
return(buffer->bytes != NULL);
|
|
}
|
|
|
|
void
|
|
FastSerial::_freeBuffer(Buffer *buffer)
|
|
{
|
|
buffer->head = buffer->tail = 0;
|
|
buffer->mask = 0;
|
|
if (NULL != buffer->bytes) {
|
|
free(buffer->bytes);
|
|
buffer->bytes = NULL;
|
|
}
|
|
}
|
|
|