From e785ea23f2f08a1c36607170331f0c3fd1df25d9 Mon Sep 17 00:00:00 2001 From: Chiara Ruggeri Date: Wed, 24 Feb 2016 15:56:21 +0100 Subject: [PATCH] SoftwareSerial library for M0 --- SoftwareSerial.cpp | 328 ++++++++++++++++++ SoftwareSerial.h | 108 ++++++ .../SoftwareSerialExample.ino | 41 +++ examples/TwoPortReceive/TwoPortReceive.ino | 71 ++++ 4 files changed, 548 insertions(+) create mode 100644 SoftwareSerial.cpp create mode 100644 SoftwareSerial.h create mode 100644 examples/SoftwareSerialExample/SoftwareSerialExample.ino create mode 100644 examples/TwoPortReceive/TwoPortReceive.ino diff --git a/SoftwareSerial.cpp b/SoftwareSerial.cpp new file mode 100644 index 0000000..5315539 --- /dev/null +++ b/SoftwareSerial.cpp @@ -0,0 +1,328 @@ +/* + SoftwareSerial.cpp - library for Arduino M0/M0 pro + Copyright (c) 2016 Arduino Srl. All rights reserved. + Written by Chiara Ruggeri (chiara@arduino.org) + + 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 + + Enjoy! +*/ + + +#include +#include +#include +#include + +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; + + +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 + attachInterrupt(_receivePin, handle_interrupt, RISING); + else + //Start bit low + attachInterrupt(_receivePin, handle_interrupt, FALLING); + + uint32_t a; + uint32_t reg; + a = a | (1<INTENSET.reg; + reg= reg & a; + + return true; + } + return false; +} + +bool SoftwareSerial::stopListening() +{ + if (active_object == this) + { + EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT( 1 << digitalPinToInterrupt( _receivePin )) ; + active_object = NULL; + return true; + } + return false; +} + + +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()) + { + + EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT( 1 << digitalPinToInterrupt(_receivePin)); + + // 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); + 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); + + EIC->INTENSET.reg = EIC_INTENSET_EXTINT( 1 << digitalPinToInterrupt(_receivePin)); + } +} + + +uint32_t SoftwareSerial::rx_pin_read() +{ + return _receivePortRegister->reg & digitalPinToBitMask(_receivePin); +} + +/* static */ +inline void SoftwareSerial::handle_interrupt() +{ + if (active_object) + { + active_object->recv(); + } +} + + +// Constructor +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; +} + +// Destructor +SoftwareSerial::~SoftwareSerial() +{ + end(); +} + +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); + PortGroup * 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); + PortGroup * port = digitalPinToPort(rx); + _receivePortRegister = portInputRegister(port); + +} + + +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; + + // Only setup rx when we have a valid PCINT for this pin + if (digitalPinToInterrupt(_receivePin)!=NOT_AN_INTERRUPT) { + //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 - 2; + //Wait 1 bit (the stop one) + _rx_delay_stopbit = bit_delay; + + + delayMicroseconds(_tx_delay); + } + listen(); +} + +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 PORT_OUT_Type *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 + EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT( 1 << digitalPinToInterrupt( _receivePin )); + + // Write the start bit + if (inv) + reg->reg |= reg_mask; + else + reg->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 |= reg_mask; // send 1 + else + reg->reg &= inv_mask; // send 0 + + delayMicroseconds(delay); + b >>= 1; + } + + // restore pin to natural state + if (inv) + reg->reg &= inv_mask; + else + reg->reg |= reg_mask; + + + EIC->INTENSET.reg = EIC_INTENSET_EXTINT( 1 << digitalPinToInterrupt( _receivePin ) ) ; + + delayMicroseconds(delay); + + return 1; +} + +void SoftwareSerial::flush() +{ + if (!isListening()) + return; + + EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT( 1 << digitalPinToInterrupt( _receivePin ) ) ; + + _receive_buffer_head = _receive_buffer_tail = 0; + + EIC->INTENSET.reg = EIC_INTENSET_EXTINT( 1 << digitalPinToInterrupt( _receivePin ) ) ; + +} + +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]; +} diff --git a/SoftwareSerial.h b/SoftwareSerial.h new file mode 100644 index 0000000..69e5f46 --- /dev/null +++ b/SoftwareSerial.h @@ -0,0 +1,108 @@ +/* + SoftwareSerial.cpp - library for Arduino M0/M0 pro + Copyright (c) 2016 Arduino Srl. All rights reserved. + Written by Chiara Ruggeri (chiara@arduino.org) + + 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 + + Enjoy! +*/ + +#ifndef SoftwareSerial_h +#define SoftwareSerial_h + +#include +#include +#include + +/****************************************************************************** +* Definitions +******************************************************************************/ + +#define _SS_MAX_RX_BUFF 64 // RX buffer size +#ifndef GCC_VERSION +#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif + +class SoftwareSerial : public Stream +{ +private: + // per object data + uint8_t _transmitPin; + uint8_t _receivePin; + uint32_t _receiveBitMask; + volatile PORT_IN_Type *_receivePortRegister; + uint32_t _transmitBitMask; + volatile PORT_OUT_Type *_transmitPortRegister; + + // Expressed as 4-cycle delays (must never be 0!) + uint16_t _rx_delay_centering; + uint16_t _rx_delay_intrabit; + uint16_t _rx_delay_stopbit; + uint16_t _tx_delay; + + uint16_t _buffer_overflow:1; + uint16_t _inverse_logic:1; + + // static data + static char _receive_buffer[_SS_MAX_RX_BUFF]; + static volatile uint8_t _receive_buffer_tail; + static volatile uint8_t _receive_buffer_head; + static SoftwareSerial *active_object; + + // private methods + void recv() __attribute__((__always_inline__)); + uint32_t rx_pin_read(); + void tx_pin_write(uint8_t pin_state) __attribute__((__always_inline__)); + void setTX(uint8_t transmitPin); + void setRX(uint8_t receivePin); + void setRxIntMsk(bool enable) __attribute__((__always_inline__)); + + +public: + // public methods + SoftwareSerial(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic = false); + ~SoftwareSerial(); + void begin(long speed); + bool listen(); + void end(); + bool isListening() { return this == active_object; } + bool stopListening(); + bool overflow() { bool ret = _buffer_overflow; if (ret) _buffer_overflow = false; return ret; } + int peek(); + + virtual size_t write(uint8_t byte); + virtual int read(); + virtual int available(); + virtual void flush(); + operator bool() { return true; } + + using Print::write; + + // public only for easy access by interrupt handlers + static inline void handle_interrupt() __attribute__((__always_inline__)); +}; + +// Arduino 0012 workaround +#undef int +#undef char +#undef long +#undef byte +#undef float +#undef abs +#undef round + +#endif + diff --git a/examples/SoftwareSerialExample/SoftwareSerialExample.ino b/examples/SoftwareSerialExample/SoftwareSerialExample.ino new file mode 100644 index 0000000..b5d6927 --- /dev/null +++ b/examples/SoftwareSerialExample/SoftwareSerialExample.ino @@ -0,0 +1,41 @@ +/* + Software serial multiple serial test + + Receives from the hardware serial, sends to software serial. + Receives from software serial, sends to hardware serial. + + The circuit: + * RX is digital pin 9 (connect to TX of other device) + * TX is digital pin 10 (connect to RX of other device) + + Note: + You shouldn't use pin 2 + + This example code is in the public domain. + + */ + +#include + +SoftwareSerial mySerial(9, 10); // RX, TX + +void setup() +{ + // Open serial communications and wait for port to open: + SerialUSB.begin(115200); + SerialUSB.println("Goodnight moon!"); + + // set the data rate for the SoftwareSerial port + mySerial.begin(9600); + mySerial.println("Hello, world?"); +} + +void loop() // run over and over// +{ + if (mySerial.available()) + SerialUSB.write(mySerial.read()); + + if (SerialUSB.available()) + mySerial.write(SerialUSB.read()); +} + diff --git a/examples/TwoPortReceive/TwoPortReceive.ino b/examples/TwoPortReceive/TwoPortReceive.ino new file mode 100644 index 0000000..f872663 --- /dev/null +++ b/examples/TwoPortReceive/TwoPortReceive.ino @@ -0,0 +1,71 @@ +/* + Software serial multple serial test + + Receives from the two software serial ports, + sends to the hardware serial port. + + In order to listen on a software port, you call port.listen(). + When using two software serial ports, you have to switch ports + by listen()ing on each one in turn. Pick a logical time to switch + ports, like the end of an expected transmission, or when the + buffer is empty. This example switches ports when there is nothing + more to read from a port + + The circuit: + Two devices which communicate serially are needed. + * First serial device's TX attached to digital pin 9, RX to pin 11 + * Second serial device's TX attached to digital pin 8, RX to pin 10 + + This example code is in the public domain. + + */ + +#include +// software serial #1: TX = digital pin 11, RX = digital pin 9 +SoftwareSerial portOne(9, 11); + +// software serial #2: TX = digital pin 10, RX = digital pin 8 +SoftwareSerial portTwo(8, 10); + +void setup() +{ + // Open serial communications and wait for port to open: + SerialUSB.begin(115200); + + // Start each software serial port + portOne.begin(9600); + portTwo.begin(9600); +} + +void loop() +{ + // By default, the last intialized port is listening. + // when you want to listen on a port, explicitly select it: + portOne.listen(); + + SerialUSB.println("Data from port one:"); + // while there is data coming in, read it + // and send to the hardware serial port: + while (portOne.available() > 0) { + char inByte = portOne.read(); + SerialUSB.write(inByte); + } + + // blank line to separate data from the two ports: + SerialUSB.println(""); + + // Now listen on the second port + portTwo.listen(); + // while there is data coming in, read it + // and send to the hardware serial port: + + SerialUSB.println("Data from port two:"); + while (portTwo.available() > 0) { + char inByte = portTwo.read(); + SerialUSB.write(inByte); + } + + // blank line to separate data from the two ports: + SerialUSB.println(); +} +