diff options
Diffstat (limited to 'libraries/SoftwareSerial')
| -rw-r--r-- | libraries/SoftwareSerial/SoftwareSerial.cpp | 334 | ||||
| -rw-r--r-- | libraries/SoftwareSerial/SoftwareSerial.h | 17 | 
2 files changed, 166 insertions, 185 deletions
| diff --git a/libraries/SoftwareSerial/SoftwareSerial.cpp b/libraries/SoftwareSerial/SoftwareSerial.cpp index d1f6c92..527f3f9 100644 --- a/libraries/SoftwareSerial/SoftwareSerial.cpp +++ b/libraries/SoftwareSerial/SoftwareSerial.cpp @@ -42,92 +42,7 @@ http://arduiniana.org.  #include <avr/pgmspace.h>
  #include <Arduino.h>
  #include <SoftwareSerial.h>
 -//
 -// Lookup table
 -//
 -typedef struct _DELAY_TABLE
 -{
 -  long baud;
 -  unsigned short rx_delay_centering;
 -  unsigned short rx_delay_intrabit;
 -  unsigned short rx_delay_stopbit;
 -  unsigned short tx_delay;
 -} DELAY_TABLE;
 -
 -#if F_CPU == 16000000
 -
 -static const DELAY_TABLE PROGMEM table[] = 
 -{
 -  //  baud    rxcenter   rxintra    rxstop    tx
 -  { 115200,   1,         17,        17,       12,    },
 -  { 57600,    10,        37,        37,       33,    },
 -  { 38400,    25,        57,        57,       54,    },
 -  { 31250,    31,        70,        70,       68,    },
 -  { 28800,    34,        77,        77,       74,    },
 -  { 19200,    54,        117,       117,      114,   },
 -  { 14400,    74,        156,       156,      153,   },
 -  { 9600,     114,       236,       236,      233,   },
 -  { 4800,     233,       474,       474,      471,   },
 -  { 2400,     471,       950,       950,      947,   },
 -  { 1200,     947,       1902,      1902,     1899,  },
 -  { 600,      1902,      3804,      3804,     3800,  },
 -  { 300,      3804,      7617,      7617,     7614,  },
 -};
 -
 -const int XMIT_START_ADJUSTMENT = 5;
 -
 -#elif F_CPU == 8000000
 -
 -static const DELAY_TABLE table[] PROGMEM = 
 -{
 -  //  baud    rxcenter    rxintra    rxstop  tx
 -  { 115200,   1,          5,         5,      3,      },
 -  { 57600,    1,          15,        15,     13,     },
 -  { 38400,    2,          25,        26,     23,     },
 -  { 31250,    7,          32,        33,     29,     },
 -  { 28800,    11,         35,        35,     32,     },
 -  { 19200,    20,         55,        55,     52,     },
 -  { 14400,    30,         75,        75,     72,     },
 -  { 9600,     50,         114,       114,    112,    },
 -  { 4800,     110,        233,       233,    230,    },
 -  { 2400,     229,        472,       472,    469,    },
 -  { 1200,     467,        948,       948,    945,    },
 -  { 600,      948,        1895,      1895,   1890,   },
 -  { 300,      1895,       3805,      3805,   3802,   },
 -};
 -
 -const int XMIT_START_ADJUSTMENT = 4;
 -
 -#elif F_CPU == 20000000
 -
 -// 20MHz support courtesy of the good people at macegr.com.
 -// Thanks, Garrett!
 -
 -static const DELAY_TABLE PROGMEM table[] =
 -{
 -  //  baud    rxcenter    rxintra    rxstop  tx
 -  { 115200,   3,          21,        21,     18,     },
 -  { 57600,    20,         43,        43,     41,     },
 -  { 38400,    37,         73,        73,     70,     },
 -  { 31250,    45,         89,        89,     88,     },
 -  { 28800,    46,         98,        98,     95,     },
 -  { 19200,    71,         148,       148,    145,    },
 -  { 14400,    96,         197,       197,    194,    },
 -  { 9600,     146,        297,       297,    294,    },
 -  { 4800,     296,        595,       595,    592,    },
 -  { 2400,     592,        1189,      1189,   1186,   },
 -  { 1200,     1187,       2379,      2379,   2376,   },
 -  { 600,      2379,       4759,      4759,   4755,   },
 -  { 300,      4759,       9523,      9523,   9520,   },
 -};
 -
 -const int XMIT_START_ADJUSTMENT = 6;
 -
 -#else
 -
 -#error This version of SoftwareSerial supports only 20, 16 and 8MHz processors
 -
 -#endif
 +#include <util/delay_basic.h>
  //
  // Statics
 @@ -162,36 +77,44 @@ inline void DebugPulse(uint8_t pin, uint8_t count)  /* static */ 
  inline void SoftwareSerial::tunedDelay(uint16_t delay) { 
 -  uint8_t tmp=0;
 -
 -  asm volatile("sbiw    %0, 0x01 \n\t"
 -    "ldi %1, 0xFF \n\t"
 -    "cpi %A0, 0xFF \n\t"
 -    "cpc %B0, %1 \n\t"
 -    "brne .-10 \n\t"
 -    : "+r" (delay), "+a" (tmp)
 -    : "0" (delay)
 -    );
 +  _delay_loop_2(delay);
  }
  // This function sets the current object as the "listening"
  // one and returns true if it replaces another 
  bool SoftwareSerial::listen()
  {
 +  if (!_rx_delay_stopbit)
 +    return false;
 +
    if (active_object != this)
    {
 +    if (active_object)
 +      active_object->stopListening();
 +
      _buffer_overflow = false;
 -    uint8_t oldSREG = SREG;
 -    cli();
      _receive_buffer_head = _receive_buffer_tail = 0;
      active_object = this;
 -    SREG = oldSREG;
 +
 +    setRxIntMsk(true);
      return true;
    }
    return false;
  }
 +// Stop listening. Returns true if we were actually listening.
 +bool SoftwareSerial::stopListening()
 +{
 +  if (active_object == this)
 +  {
 +    setRxIntMsk(false);
 +    active_object = NULL;
 +    return true;
 +  }
 +  return false;
 +}
 +
  //
  // The receive routine called by the interrupt handler
  //
 @@ -220,43 +143,49 @@ void SoftwareSerial::recv()    // 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=0x1; i; i <<= 1)
 +    for (uint8_t i=8; i > 0; --i)
      {
        tunedDelay(_rx_delay_intrabit);
 +      d >>= 1;
        DebugPulse(_DEBUG_PIN2, 1);
 -      uint8_t noti = ~i;
        if (rx_pin_read())
 -        d |= i;
 -      else // else clause added to ensure function timing is ~balanced
 -        d &= noti;
 +        d |= 0x80;
      }
 -    // skip the stop bit
 -    tunedDelay(_rx_delay_stopbit);
 -    DebugPulse(_DEBUG_PIN2, 1);
 -
      if (_inverse_logic)
        d = ~d;
      // if buffer full, set the overflow flag and return
 -    if ((_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF != _receive_buffer_head) 
 +    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 = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF;
 +      _receive_buffer_tail = next;
      } 
      else 
      {
 -#if _DEBUG // for scope: pulse pin as overflow indictator
        DebugPulse(_DEBUG_PIN1, 1);
 -#endif
        _buffer_overflow = true;
      }
 +
 +    // 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
 @@ -275,14 +204,6 @@ void SoftwareSerial::recv()  #endif
  }
 -void SoftwareSerial::tx_pin_write(uint8_t pin_state)
 -{
 -  if (pin_state == LOW)
 -    *_transmitPortRegister &= ~_transmitBitMask;
 -  else
 -    *_transmitPortRegister |= _transmitBitMask;
 -}
 -
  uint8_t SoftwareSerial::rx_pin_read()
  {
    return *_receivePortRegister & _receiveBitMask;
 @@ -309,24 +230,15 @@ ISR(PCINT0_vect)  #endif
  #if defined(PCINT1_vect)
 -ISR(PCINT1_vect)
 -{
 -  SoftwareSerial::handle_interrupt();
 -}
 +ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect));
  #endif
  #if defined(PCINT2_vect)
 -ISR(PCINT2_vect)
 -{
 -  SoftwareSerial::handle_interrupt();
 -}
 +ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect));
  #endif
  #if defined(PCINT3_vect)
 -ISR(PCINT3_vect)
 -{
 -  SoftwareSerial::handle_interrupt();
 -}
 +ISR(PCINT3_vect, ISR_ALIASOF(PCINT0_vect));
  #endif
  //
 @@ -354,8 +266,12 @@ SoftwareSerial::~SoftwareSerial()  void SoftwareSerial::setTX(uint8_t tx)
  {
 -  pinMode(tx, OUTPUT);
 +  // 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);
    uint8_t port = digitalPinToPort(tx);
    _transmitPortRegister = portOutputRegister(port);
 @@ -372,6 +288,13 @@ void SoftwareSerial::setRX(uint8_t rx)    _receivePortRegister = portInputRegister(port);
  }
 +uint16_t SoftwareSerial::subtract_cap(uint16_t num, uint16_t sub) {
 +  if (num > sub)
 +    return num - sub;
 +  else
 +    return 1;
 +}
 +
  //
  // Public methods
  //
 @@ -380,27 +303,64 @@ void SoftwareSerial::begin(long speed)  {
    _rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0;
 -  for (unsigned i=0; i<sizeof(table)/sizeof(table[0]); ++i)
 -  {
 -    long baud = pgm_read_dword(&table[i].baud);
 -    if (baud == speed)
 -    {
 -      _rx_delay_centering = pgm_read_word(&table[i].rx_delay_centering);
 -      _rx_delay_intrabit = pgm_read_word(&table[i].rx_delay_intrabit);
 -      _rx_delay_stopbit = pgm_read_word(&table[i].rx_delay_stopbit);
 -      _tx_delay = pgm_read_word(&table[i].tx_delay);
 -      break;
 -    }
 -  }
 +  // 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));
 -  // Set up RX interrupts, but only if we have a valid RX baud rate
 -  if (_rx_delay_stopbit)
 -  {
 -    if (digitalPinToPCICR(_receivePin))
 -    {
 -      *digitalPinToPCICR(_receivePin) |= _BV(digitalPinToPCICRbit(_receivePin));
 -      *digitalPinToPCMSK(_receivePin) |= _BV(digitalPinToPCMSKbit(_receivePin));
 -    }
      tunedDelay(_tx_delay); // if we were low this establishes the end
    }
 @@ -412,10 +372,17 @@ void SoftwareSerial::begin(long speed)    listen();
  }
 +void SoftwareSerial::setRxIntMsk(bool enable)
 +{
 +    if (enable)
 +      *_pcint_maskreg |= _pcint_maskvalue;
 +    else
 +      *_pcint_maskreg &= ~_pcint_maskvalue;
 +}
 +
  void SoftwareSerial::end()
  {
 -  if (digitalPinToPCMSK(_receivePin))
 -    *digitalPinToPCMSK(_receivePin) &= ~_BV(digitalPinToPCMSKbit(_receivePin));
 +  stopListening();
  }
 @@ -450,42 +417,47 @@ size_t SoftwareSerial::write(uint8_t b)      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 reg_mask = _transmitBitMask;
 +  uint8_t inv_mask = ~_transmitBitMask;
    uint8_t oldSREG = SREG;
 +  bool inv = _inverse_logic;
 +  uint16_t delay = _tx_delay;
 +
 +  if (inv)
 +    b = ~b;
 +
    cli();  // turn off interrupts for a clean txmit
    // Write the start bit
 -  tx_pin_write(_inverse_logic ? HIGH : LOW);
 -  tunedDelay(_tx_delay + XMIT_START_ADJUSTMENT);
 +  if (inv)
 +    *reg |= reg_mask;
 +  else
 +    *reg &= inv_mask;
 +
 +  tunedDelay(delay);
    // Write each of the 8 bits
 -  if (_inverse_logic)
 +  for (uint8_t i = 8; i > 0; --i)
    {
 -    for (byte mask = 0x01; mask; mask <<= 1)
 -    {
 -      if (b & mask) // choose bit
 -        tx_pin_write(LOW); // send 1
 -      else
 -        tx_pin_write(HIGH); // send 0
 -    
 -      tunedDelay(_tx_delay);
 -    }
 +    if (b & 1) // choose bit
 +      *reg |= reg_mask; // send 1
 +    else
 +      *reg &= inv_mask; // send 0
 -    tx_pin_write(LOW); // restore pin to natural state
 +    tunedDelay(delay);
 +    b >>= 1;
    }
 -  else
 -  {
 -    for (byte mask = 0x01; mask; mask <<= 1)
 -    {
 -      if (b & mask) // choose bit
 -        tx_pin_write(HIGH); // send 1
 -      else
 -        tx_pin_write(LOW); // send 0
 -    
 -      tunedDelay(_tx_delay);
 -    }
 -    tx_pin_write(HIGH); // restore pin to natural state
 -  }
 +  // restore pin to natural state
 +  if (inv)
 +    *reg &= inv_mask;
 +  else
 +    *reg |= reg_mask;
    SREG = oldSREG; // turn interrupts back on
    tunedDelay(_tx_delay);
 diff --git a/libraries/SoftwareSerial/SoftwareSerial.h b/libraries/SoftwareSerial/SoftwareSerial.h index a6a60b5..274f3df 100644 --- a/libraries/SoftwareSerial/SoftwareSerial.h +++ b/libraries/SoftwareSerial/SoftwareSerial.h @@ -53,7 +53,10 @@ private:    volatile uint8_t *_receivePortRegister;
    uint8_t _transmitBitMask;
    volatile uint8_t *_transmitPortRegister;
 +  volatile uint8_t *_pcint_maskreg;
 +  uint8_t _pcint_maskvalue;
 +  // Expressed as 4-cycle delays (must never be 0!)
    uint16_t _rx_delay_centering;
    uint16_t _rx_delay_intrabit;
    uint16_t _rx_delay_stopbit;
 @@ -69,11 +72,15 @@ private:    static SoftwareSerial *active_object;
    // private methods
 -  void recv();
 +  void recv() __attribute__((__always_inline__));
    uint8_t rx_pin_read();
 -  void tx_pin_write(uint8_t pin_state);
 +  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__));
 +
 +  // Return num - sub, or 1 if the result would be < 1
 +  static uint16_t subtract_cap(uint16_t num, uint16_t sub);
    // private static method for timing
    static inline void tunedDelay(uint16_t delay);
 @@ -86,18 +93,20 @@ public:    bool listen();
    void end();
    bool isListening() { return this == active_object; }
 -  bool overflow() { bool ret = _buffer_overflow; _buffer_overflow = false; return ret; }
 +  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();
 +  static inline void handle_interrupt() __attribute__((__always_inline__));
  };
  // Arduino 0012 workaround
 | 
