diff options
Diffstat (limited to 'arduino/libraries/SoftwareSerial/SoftwareSerial.cpp')
-rwxr-xr-x | arduino/libraries/SoftwareSerial/SoftwareSerial.cpp | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/arduino/libraries/SoftwareSerial/SoftwareSerial.cpp b/arduino/libraries/SoftwareSerial/SoftwareSerial.cpp new file mode 100755 index 0000000..cd32092 --- /dev/null +++ b/arduino/libraries/SoftwareSerial/SoftwareSerial.cpp @@ -0,0 +1,329 @@ +/* + SoftwareSerial.cpp - library for Arduino Primo + Copyright (c) 2016 Arduino. All rights reserved. + + 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 <Arduino.h> +#include <SoftwareSerial.h> +#include <variant.h> +#include <WInterrupts.h> + +SoftwareSerial *SoftwareSerial::active_object = 0; +char SoftwareSerial::_receive_buffer[_SS_MAX_RX_BUFF]; +volatile uint8_t SoftwareSerial::_receive_buffer_tail = 0; +volatile uint8_t SoftwareSerial::_receive_buffer_head = 0; + + +SoftwareSerial::SoftwareSerial(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic /* = false */) : + _rx_delay_centering(0), + _rx_delay_intrabit(0), + _rx_delay_stopbit(0), + _tx_delay(0), + _buffer_overflow(false), + _inverse_logic(inverse_logic) +{ + _receivePin = receivePin; + _transmitPin = transmitPin; +} + + +SoftwareSerial::~SoftwareSerial() +{ + end(); +} + +void SoftwareSerial::begin(long speed) + { + setTX(_transmitPin); + setRX(_receivePin); + // Precalculate the various delays + //Calculate the distance between bit in micro seconds + uint32_t bit_delay = (float(1)/speed)*1000000; + + _tx_delay = bit_delay; + + //Wait 1/2 bit - 2 micro seconds (time for interrupt to be served) + _rx_delay_centering = (bit_delay/2) - 2; + //Wait 1 bit - 2 micro seconds (time in each loop iteration) + _rx_delay_intrabit = bit_delay - 1;//2 + //Wait 1 bit (the stop one) + _rx_delay_stopbit = bit_delay; + + + delayMicroseconds(_tx_delay); + + listen(); +} + +bool SoftwareSerial::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; + + if(_inverse_logic) + //Start bit high + _intMask = attachInterrupt(_receivePin, handle_interrupt, RISING); + else + //Start bit low + _intMask = attachInterrupt(_receivePin, handle_interrupt, FALLING); + + return true; + } + return false; +} + +bool SoftwareSerial::stopListening() +{ + if (active_object == this) + { + detachInterrupt(_receivePin); + active_object = NULL; + return true; + } + return false; +} + +void SoftwareSerial::end() +{ + stopListening(); +} + +int SoftwareSerial::read() +{ + if (!isListening()){ + return -1;} + + + // 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 SoftwareSerial::available() +{ + if (!isListening()) + return 0; + + return (_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head) % _SS_MAX_RX_BUFF; +} + +size_t SoftwareSerial::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 uint32_t* reg = _transmitPortRegister; + uint32_t reg_mask = _transmitBitMask; + uint32_t inv_mask = ~_transmitBitMask; + bool inv = _inverse_logic; + uint16_t delay = _tx_delay; + + if (inv) + b = ~b; + // turn off interrupts for a clean txmit + NRF_GPIOTE->INTENCLR = _intMask; + // Write the start bit + if (inv) + *reg |= reg_mask; + else + *reg &= inv_mask; + + delayMicroseconds(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 + + delayMicroseconds(delay); + b >>= 1; + } + + // restore pin to natural state + if (inv) + *reg &= inv_mask; + else + *reg |= reg_mask; + + NRF_GPIOTE->INTENSET = _intMask; + + delayMicroseconds(delay); + + return 1; +} + +void SoftwareSerial::flush() +{ + if (!isListening()) + return; + + NRF_GPIOTE->INTENCLR = _intMask; + + _receive_buffer_head = _receive_buffer_tail = 0; + + NRF_GPIOTE->INTENSET = _intMask; +} + +int SoftwareSerial::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]; +} + + +//private methods + +void SoftwareSerial::recv() +{ + uint8_t d = 0; + + // If RX line is high, then we don't see any start bit + // so interrupt is probably not for us + if (_inverse_logic ? rx_pin_read() : !rx_pin_read()) + { + + NRF_GPIOTE->INTENCLR = _intMask; + + // Wait approximately 1/2 of a bit width to "center" the sample + delayMicroseconds(_rx_delay_centering); + + // Read each of the 8 bits + for (uint8_t i=8; i > 0; --i) + { + + delayMicroseconds(_rx_delay_intrabit); + // nRF52 needs another delay less than 1 uSec to be better synchronized + // with the highest baud rates + __ASM volatile ( + " NOP\n\t" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + " NOP\n" + ); + + d >>= 1; + + if (rx_pin_read()){ + d |= 0x80; + } + + } + if (_inverse_logic){ + d = ~d; + } + + // 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 + delayMicroseconds(_rx_delay_stopbit); + + NRF_GPIOTE->INTENSET = _intMask; + } +} + +uint32_t SoftwareSerial::rx_pin_read() +{ + return *_receivePortRegister & digitalPinToBitMask(_receivePin); +} + +/* static */ +inline void SoftwareSerial::handle_interrupt() +{ + if (active_object) + { + active_object->recv(); + } +} + +void SoftwareSerial::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 hihg. Now, it is input with pullup for a short while, which + // is fine. With inverse logic, either order is fine. + digitalWrite(tx, _inverse_logic ? LOW : HIGH); + pinMode(tx, OUTPUT); + _transmitBitMask = digitalPinToBitMask(tx); + NRF_GPIO_Type * port = digitalPinToPort(tx); + _transmitPortRegister = portOutputRegister(port); +} + +void SoftwareSerial::setRX(uint8_t rx) +{ + pinMode(rx, INPUT); + if (!_inverse_logic) + digitalWrite(rx, HIGH); // pullup for normal logic! + _receivePin = rx; + _receiveBitMask = digitalPinToBitMask(rx); + NRF_GPIO_Type * port = digitalPinToPort(rx); + _receivePortRegister = portInputRegister(port); +} |