From 6074c410d98e1cca8c978ecef2193d4103897d62 Mon Sep 17 00:00:00 2001 From: "DrZiplok@gmail.com" Date: Mon, 23 Aug 2010 06:25:03 +0000 Subject: [PATCH] "Fast" serial driver that fully overrides the Arduino-provided HardwareSerial/Serial* driver. The receive side is basically a copy of the HardwareSerial driver, whilst the transmit side uses the same algorithm as the APM_FastSerial driver. See the example sketch for usage details. git-svn-id: https://arducopter.googlecode.com/svn/trunk@276 f9c3cf11-9bcb-44bc-f272-b75c42450872 --- libraries/FastSerial/FastSerial.cpp | 287 ++++++++++++++++++ libraries/FastSerial/FastSerial.h | 99 ++++++ .../examples/FastSerial/FastSerial.pde | 55 ++++ libraries/FastSerial/keywords.txt | 7 + 4 files changed, 448 insertions(+) create mode 100644 libraries/FastSerial/FastSerial.cpp create mode 100644 libraries/FastSerial/FastSerial.h create mode 100644 libraries/FastSerial/examples/FastSerial/FastSerial.pde create mode 100644 libraries/FastSerial/keywords.txt diff --git a/libraries/FastSerial/FastSerial.cpp b/libraries/FastSerial/FastSerial.cpp new file mode 100644 index 0000000000..abdb9bf672 --- /dev/null +++ b/libraries/FastSerial/FastSerial.cpp @@ -0,0 +1,287 @@ +// -*- 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 +#include +#include +#include "FastSerial.h" + +#if defined(__AVR_ATmega1280__) +# define FS_MAX_PORTS 4 +#else +# define FS_MAX_PORTS 1 +#endif + +static FastSerial *ports[FS_MAX_PORTS]; + +#define RX_BUFFER_SIZE sizeof(((FastSerial::RXBuffer *)1)->bytes) +#define TX_BUFFER_SIZE sizeof(((FastSerial::TXBuffer *)1)->bytes) + +// Interrupt handlers ////////////////////////////////////////////////////////// + +#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 + +// Constructor ///////////////////////////////////////////////////////////////// + +FastSerial::FastSerial(uint8_t portNumber) +{ + switch(portNumber) { +#if defined(__AVR_ATmega8__) + case 0: + _ubrrh = &UBRRH; + _ubrrl = &UBRRL; + _ucsra = &UCSRA; + _ucsrb = &UCSRB; + _udr = &UDR; + _u2x = U2X; + _portEnableBits = _BV(RXEN) | _BV(TXEN) | _BV(RXCIE); + _portTxBits = _BV(UDRIE); + break; +#else + case 0: + _ubrrh = &UBRR0H; + _ubrrl = &UBRR0L; + _ucsra = &UCSR0A; + _ucsrb = &UCSR0B; + _udr = &UDR0; + _u2x = U2X0; + _portEnableBits = _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0); + _portTxBits = _BV(UDRIE0); + break; +#if defined(__AVR_ATmega1280__) + case 1: + _ubrrh = &UBRR1H; + _ubrrl = &UBRR1L; + _ucsra = &UCSR1A; + _ucsrb = &UCSR1B; + _udr = &UDR1; + _u2x = U2X1; + _portEnableBits = _BV(RXEN1) | _BV(TXEN1) | _BV(RXCIE1); + _portTxBits = _BV(UDRIE1); + break; + case 2: + _ubrrh = &UBRR2H; + _ubrrl = &UBRR2L; + _ucsra = &UCSR2A; + _ucsrb = &UCSR2B; + _udr = &UDR2; + _u2x = U2X2; + _portEnableBits = _BV(RXEN2) | _BV(TXEN2) | _BV(RXCIE2); + _portTxBits = _BV(UDRIE2); + break; + case 3: + _ubrrh = &UBRR3H; + _ubrrl = &UBRR3L; + _ucsra = &UCSR3A; + _ucsrb = &UCSR3B; + _udr = &UDR3; + _u2x = U2X3; + _portEnableBits = _BV(RXEN3) | _BV(TXEN3) | _BV(RXCIE3); + _portTxBits = _BV(UDRIE3); + break; +#endif +#endif + default: + return; + }; + + _txBuffer.head = _txBuffer.tail = 0; + _rxBuffer.head = _rxBuffer.tail = 0; + + // claim the port + ports[portNumber] = this; +} + +// Public Methods ////////////////////////////////////////////////////////////// + +void FastSerial::begin(long baud) +{ + uint16_t baud_setting; + bool use_u2x; + + // 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); +} + + +uint8_t +FastSerial::available(void) +{ + return((RX_BUFFER_SIZE + _rxBuffer.head - _rxBuffer.tail) % RX_BUFFER_SIZE); +} + +int +FastSerial::read(void) +{ + uint8_t c; + + // if the head and tail are equal, the buffer is empty + if (_rxBuffer.head == _rxBuffer.tail) + return(-1); + + // pull character from tail + c = _rxBuffer.bytes[_rxBuffer.tail]; + _rxBuffer.tail = (_rxBuffer.tail + 1) % RX_BUFFER_SIZE; + + 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) +{ + uint8_t i; + + // wait for room in the tx buffer + do { + i = (_txBuffer.head + 1) % RX_BUFFER_SIZE; + } 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; +} + +void +FastSerial::write(const uint8_t *buffer, int count) +{ + while (count--) + write(*buffer++); +} + + +// 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) % RX_BUFFER_SIZE; + 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) % TX_BUFFER_SIZE; + } + + // if the buffer is (now) empty, disable the interrupt + if (_txBuffer.head == _txBuffer.tail) + *_ucsrb &= ~_portTxBits; +} diff --git a/libraries/FastSerial/FastSerial.h b/libraries/FastSerial/FastSerial.h new file mode 100644 index 0000000000..1550c80c54 --- /dev/null +++ b/libraries/FastSerial/FastSerial.h @@ -0,0 +1,99 @@ +// -*- 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 +// + +// +// Note that this library does not pre-declare drivers for serial +// ports; the user must explicitly create drivers for the ports they +// wish to use. This is less friendly than the stock Arduino driver, +// but it saves 256 bytes for every unused port. +// +// To adjust the transmit/receive buffer sizes, change the size of the +// 'bytes' member in the RXBuffer and TXBuffer structures. +// + +#ifndef FastSerial_h +#define FastSerial_h + +#include +#include + +// disable the stock Arduino serial driver +#define HardwareSerial_h + +class FastSerial : public Print { +public: + FastSerial(uint8_t portNumber = 0); + + // Serial API + void begin(long baud); + void end(void); + uint8_t available(void); + int read(void); + void flush(void); + void write(uint8_t c); + void write(const uint8_t *buffer, int count); + using Print::write; + + // Interrupt methods + void receive(uint8_t c); + void transmit(void); + +private: + // register accessors + volatile uint8_t *_ubrrh; + volatile uint8_t *_ubrrl; + volatile uint8_t *_ucsra; + volatile uint8_t *_ucsrb; + volatile uint8_t *_udr; + + // register magic numbers + uint8_t _portEnableBits; // rx, tx and rx interrupt enables + uint8_t _portTxBits; // tx data and completion interrupt enables + uint8_t _u2x; + + // ring buffers + struct RXBuffer { + volatile uint8_t head; + uint8_t tail; + uint8_t bytes[128]; // size must be power of 2 for best results + }; + struct TXBuffer { + uint8_t head; + volatile uint8_t tail; + uint8_t bytes[64]; // size must be power of 2 for best results + }; + RXBuffer _rxBuffer; + TXBuffer _txBuffer; +}; + +#endif // FastSerial_h diff --git a/libraries/FastSerial/examples/FastSerial/FastSerial.pde b/libraries/FastSerial/examples/FastSerial/FastSerial.pde new file mode 100644 index 0000000000..2923645ebb --- /dev/null +++ b/libraries/FastSerial/examples/FastSerial/FastSerial.pde @@ -0,0 +1,55 @@ +// -*- Mode: C++; c-basic-offset: 8; indent-tabs-mode: nil -*- + +// +// Example code for the FastSerial driver. +// +// This code is placed into the public domain. +// + +// +// Include the FastSerial library header. +// +// Note that this causes the standard Arduino Serial* driver to be +// disabled. +// +#include + +// +// Create a FastSerial driver that looks just like the stock Arduino +// driver. +// +FastSerial Serial; + +// +// To create a driver for a different serial port, on a board that +// supports more than one, pass an argument to the constructor: +// +//FastSerial Serial2(2); + + +void setup(void) +{ + // + // Set the speed for our replacement serial port. + // + Serial.begin(38400); + + // + // And send a message. + // + Serial.println("begin"); +} + +void +loop(void) +{ + int c; + + // + // Perform a simple loopback operation. + // + c = Serial.read(); + if (-1 != c) + Serial.write(c); +} + diff --git a/libraries/FastSerial/keywords.txt b/libraries/FastSerial/keywords.txt new file mode 100644 index 0000000000..5be403f4fa --- /dev/null +++ b/libraries/FastSerial/keywords.txt @@ -0,0 +1,7 @@ +FastSerial KEYWORD1 +begin KEYWORD2 +end KEYWORD2 +available KEYWORD2 +read KEYWORD2 +flush KEYWORD2 +write KEYWORD2