From e1a2d11b96f40b1d943a6a3676fb8a032b294f8c Mon Sep 17 00:00:00 2001 From: JKANetwork Date: Mon, 12 Sep 2016 18:33:24 +0000 Subject: [PATCH] Upload new file --- SoftwareSerialLite.cpp | 515 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 SoftwareSerialLite.cpp diff --git a/SoftwareSerialLite.cpp b/SoftwareSerialLite.cpp new file mode 100644 index 0000000..11c4b36 --- /dev/null +++ b/SoftwareSerialLite.cpp @@ -0,0 +1,515 @@ +/* +SoftwareSerialLite.h , derivated from SoftwareSerial.h (formerly NewSoftSerial.h) + +This is a lite version from SoftwareSerial, with only the basic, Serial +comunication between two arduinos by software. It consumes about 300bytes less of program code +than original. You can use the original one if you not define LITE in definitions part. +Some functions removed too in this lite version like listen() and stopListening() +because you can use only one serial comunication, end(), and peek(), and inverse logic. +But they are in code, you can tweak it to include or not. + +///Original text +Multi-instance software serial library for Arduino/Wiring +-- Interrupt-driven receive and other improvements by ladyada + (http://ladyada.net) +-- Tuning, circular buffer, derivation from class Print/Stream, + multi-instance support, porting to 8MHz processors, + various optimizations, PROGMEM delay tables, inverse logic and + direct port writing by Mikal Hart (http://www.arduiniana.org) +-- Pin change interrupt macros by Paul Stoffregen (http://www.pjrc.com) +-- 20MHz processor support by Garrett Mace (http://www.macetech.com) +-- ATmega1280/2560 support by Brett Hagman (http://www.roguerobotics.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 + +The latest version of this library can always be found at +http://arduiniana.org. +/// + +*/ +// +// Includes +// +#include +#include +#include +#include "SoftwareSerialLite.h" +#include + +// +// Statics +// +SoftwareSerialLite *SoftwareSerialLite::active_object = 0; + +uint8_t SoftwareSerialLite::_receive_buffer[_SS_MAX_RX_BUFF]; +volatile uint8_t SoftwareSerialLite::_receive_buffer_tail = 0; +volatile uint8_t SoftwareSerialLite::_receive_buffer_head = 0; + + +// +// Private methods +// + +/* static */ +inline void SoftwareSerialLite::tunedDelay(uint16_t delay) { + _delay_loop_2(delay); +} + +#ifndef LITE +// This function sets the current object as the "listening" +// one and returns true if it replaces another +bool SoftwareSerialLite::listen() +{ + if (!_rx_delay_stopbit) + return false; + + if (active_object != this) + { + if (active_object) + active_object->stopListening(); + + _buffer_overflow = false; + _receive_buffer_head = _receive_buffer_tail = 0; + active_object = this; + + setRxIntMsk(true); + return true; + } + + return false; +} + +// Stop listening. Returns true if we were actually listening. +bool SoftwareSerialLite::stopListening() +{ + if (active_object == this) + { + setRxIntMsk(false); + active_object = NULL; + return true; + } + return false; +} +#endif + + +// +// The receive routine called by the interrupt handler +// +void SoftwareSerialLite::recv() +{ + +#if GCC_VERSION < 40302 +// Work-around for avr-gcc 4.3.0 OSX version bug +// Preserve the registers that the compiler misses +// (courtesy of Arduino forum user *etracer*) + asm volatile( + "push r18 \n\t" + "push r19 \n\t" + "push r20 \n\t" + "push r21 \n\t" + "push r22 \n\t" + "push r23 \n\t" + "push r26 \n\t" + "push r27 \n\t" + ::); +#endif + + uint8_t d = 0; + + // If RX line is high, then we don't see any start bit + // so interrupt is probably not for us +#ifdef LITE + if (0 ? rx_pin_read() : !rx_pin_read()) +#else + if (_inverse_logic ? rx_pin_read() : !rx_pin_read()) +#endif + { + // Disable further interrupts during reception, this prevents + // triggering another interrupt directly after we return, which can + // cause problems at higher baudrates. + setRxIntMsk(false); + + // Wait approximately 1/2 of a bit width to "center" the sample + tunedDelay(_rx_delay_centering); + + // Read each of the 8 bits + for (uint8_t i=8; i > 0; --i) + { + tunedDelay(_rx_delay_intrabit); + d >>= 1; + if (rx_pin_read()) + d |= 0x80; + } + #ifndef LITE + if (_inverse_logic) + d = ~d; + #endif + // if buffer full, set the overflow flag and return + uint8_t next = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF; + if (next != _receive_buffer_head) + { + // save new data in buffer: tail points to where byte goes + _receive_buffer[_receive_buffer_tail] = d; // save new byte + _receive_buffer_tail = next; + } + else + { + _buffer_overflow = true; + } + + // skip the stop bit + tunedDelay(_rx_delay_stopbit); + + // Re-enable interrupts when we're sure to be inside the stop bit + setRxIntMsk(true); + + } + +#if GCC_VERSION < 40302 +// Work-around for avr-gcc 4.3.0 OSX version bug +// Restore the registers that the compiler misses + asm volatile( + "pop r27 \n\t" + "pop r26 \n\t" + "pop r23 \n\t" + "pop r22 \n\t" + "pop r21 \n\t" + "pop r20 \n\t" + "pop r19 \n\t" + "pop r18 \n\t" + ::); +#endif +} + +uint8_t SoftwareSerialLite::rx_pin_read() +{ + return *_receivePortRegister & _receiveBitMask; +} + +// +// Interrupt handling +// + +/* static */ +inline void SoftwareSerialLite::handle_interrupt() +{ + if (active_object) + { + active_object->recv(); + } +} + +#if defined(PCINT0_vect) +ISR(PCINT0_vect) +{ + SoftwareSerialLite::handle_interrupt(); +} +#endif + +#if defined(PCINT1_vect) +ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect)); +#endif + +#if defined(PCINT2_vect) +ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect)); +#endif + +#if defined(PCINT3_vect) +ISR(PCINT3_vect, ISR_ALIASOF(PCINT0_vect)); +#endif + +// +// Constructor +// +#ifdef LITE +SoftwareSerialLite::SoftwareSerialLite(uint8_t receivePin, uint8_t transmitPin): +#else +SoftwareSerialLite::SoftwareSerialLite(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic): +#endif + _rx_delay_centering(0), + _rx_delay_intrabit(0), + _rx_delay_stopbit(0), + _tx_delay(0), +#ifndef LITE + _inverse_logic(inverse_logic), +#endif + _buffer_overflow(false) + +{ + setTX(transmitPin); + setRX(receivePin); +} + +#ifndef LITE +// +// Destructor +// +SoftwareSerialLite::~SoftwareSerialLite() +{ + stopListening(); +} +#endif + +void SoftwareSerialLite::setTX(uint8_t tx) +{ + // First write, then set output. If we do this the other way around, + // the pin would be output low for a short while before switching to + // output high. Now, it is input with pullup for a short while, which + // is fine. With inverse logic, either order is fine. +#ifdef LITE + digitalWrite(tx, 0 ? LOW : HIGH); +#else + digitalWrite(tx, _inverse_logic ? LOW : HIGH); +#endif + pinMode(tx, OUTPUT); + _transmitBitMask = digitalPinToBitMask(tx); + uint8_t port = digitalPinToPort(tx); + _transmitPortRegister = portOutputRegister(port); +} + +void SoftwareSerialLite::setRX(uint8_t rx) +{ + pinMode(rx, INPUT); +#ifndef LITE + if (!_inverse_logic) + digitalWrite(rx, HIGH); // pullup for normal logic! +#endif + _receivePin = rx; + _receiveBitMask = digitalPinToBitMask(rx); + uint8_t port = digitalPinToPort(rx); + _receivePortRegister = portInputRegister(port); +} + +uint16_t SoftwareSerialLite::subtract_cap(uint16_t num, uint16_t sub) { + if (num > sub) + return num - sub; + else + return 1; +} + +// +// Public methods +// +#ifdef LITE +void SoftwareSerialLite::begin(unsigned int speed) +#else +void SoftwareSerialLite::begin(long speed) +#endif +{ + _rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0; + + // Precalculate the various delays, in number of 4-cycle delays + uint16_t bit_delay = (F_CPU / speed) / 4; + + // 12 (gcc 4.8.2) or 13 (gcc 4.3.2) cycles from start bit to first bit, + // 15 (gcc 4.8.2) or 16 (gcc 4.3.2) cycles between bits, + // 12 (gcc 4.8.2) or 14 (gcc 4.3.2) cycles from last bit to stop bit + // These are all close enough to just use 15 cycles, since the inter-bit + // timings are the most critical (deviations stack 8 times) + _tx_delay = subtract_cap(bit_delay, 15 / 4); + + // Only setup rx when we have a valid PCINT for this pin + if (digitalPinToPCICR(_receivePin)) { + #if GCC_VERSION > 40800 + // Timings counted from gcc 4.8.2 output. This works up to 115200 on + // 16Mhz and 57600 on 8Mhz. + // + // When the start bit occurs, there are 3 or 4 cycles before the + // interrupt flag is set, 4 cycles before the PC is set to the right + // interrupt vector address and the old PC is pushed on the stack, + // and then 75 cycles of instructions (including the RJMP in the + // ISR vector table) until the first delay. After the delay, there + // are 17 more cycles until the pin value is read (excluding the + // delay in the loop). + // We want to have a total delay of 1.5 bit time. Inside the loop, + // we already wait for 1 bit time - 23 cycles, so here we wait for + // 0.5 bit time - (71 + 18 - 22) cycles. + _rx_delay_centering = subtract_cap(bit_delay / 2, (4 + 4 + 75 + 17 - 23) / 4); + + // There are 23 cycles in each loop iteration (excluding the delay) + _rx_delay_intrabit = subtract_cap(bit_delay, 23 / 4); + + // There are 37 cycles from the last bit read to the start of + // stopbit delay and 11 cycles from the delay until the interrupt + // mask is enabled again (which _must_ happen during the stopbit). + // This delay aims at 3/4 of a bit time, meaning the end of the + // delay will be at 1/4th of the stopbit. This allows some extra + // time for ISR cleanup, which makes 115200 baud at 16Mhz work more + // reliably + _rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, (37 + 11) / 4); + #else // Timings counted from gcc 4.3.2 output + // Note that this code is a _lot_ slower, mostly due to bad register + // allocation choices of gcc. This works up to 57600 on 16Mhz and + // 38400 on 8Mhz. + _rx_delay_centering = subtract_cap(bit_delay / 2, (4 + 4 + 97 + 29 - 11) / 4); + _rx_delay_intrabit = subtract_cap(bit_delay, 11 / 4); + _rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, (44 + 17) / 4); + #endif + + + // Enable the PCINT for the entire port here, but never disable it + // (others might also need it, so we disable the interrupt by using + // the per-pin PCMSK register). + *digitalPinToPCICR(_receivePin) |= _BV(digitalPinToPCICRbit(_receivePin)); + // Precalculate the pcint mask register and value, so setRxIntMask + // can be used inside the ISR without costing too much time. + _pcint_maskreg = digitalPinToPCMSK(_receivePin); + _pcint_maskvalue = _BV(digitalPinToPCMSKbit(_receivePin)); + + tunedDelay(_tx_delay); // if we were low this establishes the end + } + + #ifdef LITE + _buffer_overflow = false; + _receive_buffer_head = _receive_buffer_tail = 0; + active_object = this; + setRxIntMsk(true); + #else + listen(); + #endif +} + +void SoftwareSerialLite::setRxIntMsk(bool enable) +{ + if (enable) + *_pcint_maskreg |= _pcint_maskvalue; + else + *_pcint_maskreg &= ~_pcint_maskvalue; +} + +#ifndef LITE +void SoftwareSerialLite::end() +{ + stopListening(); +} +#endif + + +// Read data from buffer +int SoftwareSerialLite::read() +{ + // Empty buffer? + if (_receive_buffer_head == _receive_buffer_tail) + return -1; + + // Read from "head" + uint8_t d = _receive_buffer[_receive_buffer_head]; // grab next byte + _receive_buffer_head = (_receive_buffer_head + 1) % _SS_MAX_RX_BUFF; + return d; +} + +int SoftwareSerialLite::available() +{ +#ifndef LITE + if (!isListening()) + return 0; +#endif + + return (_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head) % _SS_MAX_RX_BUFF; +} + +size_t SoftwareSerialLite::write(uint8_t b) +{ + if (_tx_delay == 0) { + setWriteError(); + return 0; + } + + // By declaring these as local variables, the compiler will put them + // in registers _before_ disabling interrupts and entering the + // critical timing sections below, which makes it a lot easier to + // verify the cycle timings + volatile uint8_t *reg = _transmitPortRegister; + uint8_t oldSREG = SREG; + uint16_t delay = _tx_delay; + #ifndef LITE + uint8_t reg_mask = _transmitBitMask; + uint8_t inv_mask = ~_transmitBitMask; + bool inv = _inverse_logic; + if (inv) + b = ~b; + #endif + cli(); // turn off interrupts for a clean txmit + #ifdef LITE + // Write the start bit + *reg &= ~_transmitBitMask; + + tunedDelay(delay); + + // Write each of the 8 bits + for (uint8_t i = 8; i > 0; --i) + { + if (b & 1) // choose bit + *reg |= _transmitBitMask; // send 1 + else + *reg &= ~_transmitBitMask; // send 0 + + tunedDelay(delay); + b >>= 1; + } + *reg |= _transmitBitMask; + SREG = oldSREG; // turn interrupts back on + tunedDelay(_tx_delay); + return 1; + + #else + + // Write the start bit + if (inv) + *reg |= reg_mask; + else + *reg &= inv_mask; + + tunedDelay(delay); + + // Write each of the 8 bits + for (uint8_t i = 8; i > 0; --i) + { + if (b & 1) // choose bit + *reg |= reg_mask; // send 1 + else + *reg &= inv_mask; // send 0 + + tunedDelay(delay); + b >>= 1; + } + + // restore pin to natural state + if (inv) + *reg &= inv_mask; + else + *reg |= reg_mask; + SREG = oldSREG; // turn interrupts back on + tunedDelay(_tx_delay); + + return 1; + #endif +} + +#ifndef LITE +int SoftwareSerialLite::peek() +{ + if (!isListening()) + return -1; + + // Empty buffer? + if (_receive_buffer_head == _receive_buffer_tail) + return -1; + + // Read from "head" + return _receive_buffer[_receive_buffer_head]; +} +#endif