123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- //
- // Created by robin on 2/24/17.
- //
-
- // When set, _DEBUG co-opts pins 11 and 13 for debugging with an
- // oscilloscope or logic analyzer. Beware: it also slightly modifies
- // the bit times, so don't rely on it too much at high baud rates
- #define _DEBUG 0
- #define _DEBUG_PIN1 11
- #define _DEBUG_PIN2 13
- //
- // Includes
- //
- #include <avr/interrupt.h>
- #include <avr/pgmspace.h>
- #include <Arduino.h>
- #include "SoftwareSerial8e1.h"
- #include <util/delay_basic.h>
-
- //
- // Statics
- //
- SoftwareSerial8e1 *SoftwareSerial8e1::active_object = 0;
- uint8_t SoftwareSerial8e1::_receive_buffer[_SS_MAX_RX_BUFF];
- volatile uint8_t SoftwareSerial8e1::_receive_buffer_tail = 0;
- volatile uint8_t SoftwareSerial8e1::_receive_buffer_head = 0;
-
- //
- // Debugging
- //
- // This function generates a brief pulse
- // for debugging or measuring on an oscilloscope.
- #if _DEBUG
- inline void DebugPulse(uint8_t pin, uint8_t count)
- {
- volatile uint8_t *pport = portOutputRegister(digitalPinToPort(pin));
-
- uint8_t val = *pport;
- while (count--)
- {
- *pport = val | digitalPinToBitMask(pin);
- *pport = val;
- }
- }
- #else
- inline void DebugPulse(uint8_t, uint8_t) {}
- #endif
-
- //
- // Private methods
- //
-
- /* static */
- inline void SoftwareSerial8e1::tunedDelay(uint16_t delay) {
- _delay_loop_2(delay);
- }
-
- // This function sets the current object as the "listening"
- // one and returns true if it replaces another
- bool SoftwareSerial8e1::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 SoftwareSerial8e1::stopListening()
- {
- if (active_object == this)
- {
- setRxIntMsk(false);
- active_object = NULL;
- return true;
- }
- return false;
- }
-
- //
- // The receive routine called by the interrupt handler
- //
- void SoftwareSerial8e1::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
- if (_inverse_logic ? rx_pin_read() : !rx_pin_read())
- {
- // 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);
- DebugPulse(_DEBUG_PIN2, 1);
-
- // Read each of the 8 bits
- for (uint8_t i=8; i > 0; --i)
- {
- tunedDelay(_rx_delay_intrabit);
- d >>= 1;
- DebugPulse(_DEBUG_PIN2, 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
- {
- DebugPulse(_DEBUG_PIN1, 1);
- _buffer_overflow = true;
- }
-
- // skip the parity bit
- tunedDelay(_rx_delay_stopbit);
- DebugPulse(_DEBUG_PIN1, 1);
-
- // skip the stop bit
- tunedDelay(_rx_delay_stopbit);
- DebugPulse(_DEBUG_PIN1, 1);
-
- // 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 SoftwareSerial8e1::rx_pin_read()
- {
- return *_receivePortRegister & _receiveBitMask;
- }
-
- //
- // Interrupt handling
- //
-
- /* static */
- inline void SoftwareSerial8e1::handle_interrupt()
- {
- if (active_object)
- {
- active_object->recv();
- }
- }
-
- #if defined(PCINT0_vect)
- ISR(PCINT0_vect)
- {
- SoftwareSerial8e1::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
- //
- SoftwareSerial8e1::SoftwareSerial8e1(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)
- {
- setTX(transmitPin);
- setRX(receivePin);
- }
-
- //
- // Destructor
- //
- SoftwareSerial8e1::~SoftwareSerial8e1()
- {
- end();
- }
-
- void SoftwareSerial8e1::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.
- digitalWrite(tx, _inverse_logic ? LOW : HIGH);
- pinMode(tx, OUTPUT);
- _transmitBitMask = digitalPinToBitMask(tx);
- uint8_t port = digitalPinToPort(tx);
- _transmitPortRegister = portOutputRegister(port);
- }
-
- void SoftwareSerial8e1::setRX(uint8_t rx)
- {
- pinMode(rx, INPUT);
- if (!_inverse_logic)
- digitalWrite(rx, HIGH); // pullup for normal logic!
- _receivePin = rx;
- _receiveBitMask = digitalPinToBitMask(rx);
- uint8_t port = digitalPinToPort(rx);
- _receivePortRegister = portInputRegister(port);
- }
-
- uint16_t SoftwareSerial8e1::subtract_cap(uint16_t num, uint16_t sub) {
- if (num > sub)
- return num - sub;
- else
- return 1;
- }
-
- //
- // Public methods
- //
-
- void SoftwareSerial8e1::begin(long speed)
- {
- _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
- }
-
- #if _DEBUG
- pinMode(_DEBUG_PIN1, OUTPUT);
- pinMode(_DEBUG_PIN2, OUTPUT);
- #endif
-
- listen();
- }
-
- void SoftwareSerial8e1::setRxIntMsk(bool enable)
- {
- if (enable)
- *_pcint_maskreg |= _pcint_maskvalue;
- else
- *_pcint_maskreg &= ~_pcint_maskvalue;
- }
-
- void SoftwareSerial8e1::end()
- {
- stopListening();
- }
-
-
- // Read data from buffer
- int SoftwareSerial8e1::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 SoftwareSerial8e1::available()
- {
- if (!isListening())
- return 0;
-
- return (_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head) % _SS_MAX_RX_BUFF;
- }
-
- size_t SoftwareSerial8e1::write(uint8_t b)
- {
- if (_tx_delay == 0) {
- setWriteError();
- return 0;
- }
- // setTX(_receivePin);
-
- // 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 reg_mask = _transmitBitMask;
- uint8_t inv_mask = ~_transmitBitMask;
- uint8_t oldSREG = SREG;
- bool inv = _inverse_logic;
- uint16_t delay = _tx_delay;
- uint8_t p = 0;
- for (uint8_t t = 0x80; t; t >>= 1)
- if (b & t) p++;
-
- if (inv)
- b = ~b;
-
- cli(); // turn off interrupts for a clean txmit
-
- // 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;
- }
-
- if (p & 0x01)
- *reg |= reg_mask; // send 1
- else
- *reg &= inv_mask; // send 0
- tunedDelay(_tx_delay);
-
- // restore pin to natural state
- if (inv)
- *reg &= inv_mask;
- else
- *reg |= reg_mask;
-
- SREG = oldSREG; // turn interrupts back on
- tunedDelay(_tx_delay);
-
- // setRX(_receivePin);
- // pinMode(_receivePin, INPUT);
-
- return 1;
- }
-
- void SoftwareSerial8e1::flush()
- {
- // There is no tx buffering, simply return
- }
-
- int SoftwareSerial8e1::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];
- }
|