/* 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. */ #include #include #include "wiring_LL.h" #define MAX_SOFT_SERIAL_SPEED 115200 #define MIN_SOFT_SERIAL_SPEED 300 // Statics // static variable is common to all class instances SoftwareSerial *SoftwareSerial::active_object = 0; uint8_t SoftwareSerial::_receive_buffer[_SS_MAX_RX_BUFF]; volatile uint8_t SoftwareSerial::_receive_buffer_tail = 0; volatile uint8_t SoftwareSerial::_receive_buffer_head = 0; // Private methods // function placed in ram. Should not call other function placed in flash void SoftwareSerial::tunedDelay(uint32_t delayTicks) // .ram_text { if (delayTicks > 1) { uint64_t startTicks = SYSTICK_GET_TICKS(); while ((SYSTICK_GET_TICKS() - startTicks) < delayTicks) ; } } // 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; _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 SoftwareSerial::stopListening() { if (active_object == this) { setRxIntMsk(false); active_object = NULL; return true; } return false; } // // The receive routine called by the interrupt handler // void SoftwareSerial::recv() // .ram_text { // If RX line is high, then we don't see any start bit // so interrupt is probably not for us bool rxState = (bool)GPIO_READ_PIN((GPIO_TypeDef *)_receivePortRegister, (HAL_PinsTypeDef)_receiveBitMask); if (_inverse_logic ? rxState : !rxState) { // Disable further interrupts during reception, this prevents triggering another interrupt // directly after we return, which can cause problems at higher baudrates. setRxIntMsk(false); // __always_inline__ // Wait approximately 1/2 of a bit width to "center" the sample if (_rx_delay_centering > 0) tunedDelay(_rx_delay_centering); // .ram_text // Read each of the 8 bits uint8_t data = 0; for (uint8_t i=8; i > 0; --i) { tunedDelay(_rx_delay_intrabit); data >>= 1; if (GPIO_READ_PIN((GPIO_TypeDef *)_receivePortRegister, (HAL_PinsTypeDef)_receiveBitMask)) data |= 0x80; } if (_inverse_logic) data = ~data; // 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] = data; // 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); } GPIO_IRQ_CLEAR_ALL(); } uint8_t SoftwareSerial::rx_pin_read() { return (uint8_t)GPIO_READ_PIN((GPIO_TypeDef *)_receivePortRegister, (HAL_PinsTypeDef)_receiveBitMask); } // Interrupt handling /* static */ void SoftwareSerial::handle_interrupt() // .ram_text { if (active_object) { active_object->recv(); // .ram_text } } extern "C" void __attribute__((noinline, section(".ram_text"))) softSerial_interrupt_handler(void) { SoftwareSerial::handle_interrupt(); } // // 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) { _transmitPin = transmitPin; _receivePin = receivePin; } // // Destructor // SoftwareSerial::~SoftwareSerial() { end(); } void SoftwareSerial::setTX(uint8_t tx) { // save pin and port info _transmitBitMask = (uint16_t)digitalPinToBitMask(tx); _transmitPortRegister = (uint32_t)digitalPinToPort(tx); // set state before tx pin initialization to prevent false start bit if (_inverse_logic) GPIO_CLEAR_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask); else GPIO_SET_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask); // init pin as output pinMode(tx, OUTPUT); } void SoftwareSerial::setRX(uint8_t rx) { // save pin, port and gpio_line info _receiveBitMask = (uint16_t)digitalPinToBitMask(rx); _receivePortRegister = (uint32_t)digitalPinToPort(rx); _int_maskLine = digitalPinToGpioIntLine(_receivePin); // attach interrupt to rx pin if available attachInterrupt(digitalPinToInterrupt(_receivePin), softSerial_interrupt_handler, _inverse_logic ? RISING : FALLING); // turn on pull up for rx if logic is not inverse if (!_inverse_logic) { uint8_t pinNumber = PIN_MASK_TO_PIN_NUMBER(_receiveBitMask); if (((GPIO_TypeDef*)_receivePortRegister) == GPIO_0) PIN_SET_PAD_CONFIG(PORT_0_PUPD, pinNumber, HAL_GPIO_PULL_UP); else if (((GPIO_TypeDef*)_receivePortRegister) == GPIO_1) PIN_SET_PAD_CONFIG(PORT_1_PUPD, pinNumber, HAL_GPIO_PULL_UP); else if (((GPIO_TypeDef*)_receivePortRegister) == GPIO_2) PIN_SET_PAD_CONFIG(PORT_2_PUPD, pinNumber, HAL_GPIO_PULL_UP); } // turn off int line for while (it turning on in attachInterrupt()) GPIO_IRQ_LINE_DISABLE(_int_maskLine); } uint32_t SoftwareSerial::subtract_cap(uint32_t num, uint16_t sub) { if (num > sub) return num - sub; else return 1; } // // Public methods // void SoftwareSerial::begin(long speed) { // delays are empirical values here _rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0; // if pins exceeds the total number of pins, return with zero delays - it blocks further library work if ((_transmitPin >= pinCommonQty())) { ErrorMsgHandler("SoftwareSerial.begin(): Tx pin number exceeds the total number of pins"); return; } if ((_receivePin >= pinCommonQty())) { ErrorMsgHandler("SoftwareSerial.begin(): Rx pin number exceeds the total number of pins"); return; } // limit speed if (speed > MAX_SOFT_SERIAL_SPEED) speed = MAX_SOFT_SERIAL_SPEED; if (speed < MIN_SOFT_SERIAL_SPEED) speed = MIN_SOFT_SERIAL_SPEED; // Precalculate the various delays in number of ticks uint32_t bit_delay = F_CPU / speed; // init tx setTX(_transmitPin); _tx_delay = subtract_cap(bit_delay, 35); // 1 bit delay while transmitting data // init rx only when we have a valid INT for this pin if (digitalPinToInterrupt(_receivePin) != NOT_AN_INTERRUPT) { // set pin config setRX(_receivePin); // We want to have a total delay of 1.5 bit time from start bit. Inside the loop, we already // wait for 1 bit time, so here we wait for 0.5 bit time _rx_delay_centering = subtract_cap(bit_delay / 2, 315); // 1 bit time _rx_delay_intrabit = subtract_cap(bit_delay, 60); // 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, 142); } else ErrorMsgHandler("SoftwareSerial.begin(): Rx pin does not support interrupts, use different pin"); tunedDelay(_tx_delay); // if we were low this establishes the end listen(); } void SoftwareSerial::setRxIntMsk(bool enable) { if (enable) GPIO_IRQ_LINE_ENABLE(_int_maskLine); else GPIO_IRQ_LINE_DISABLE(_int_maskLine); } void SoftwareSerial::end() { stopListening(); } // function placed in ram. Should not call other function placed in flash size_t SoftwareSerial::write(uint8_t byte) { if (_tx_delay == 0) return 0; if (_inverse_logic) byte = ~byte; GLOBAL_IRQ_DISABLE(); // turn off interrupts for a clean txmit // Write the start bit if (_inverse_logic) GPIO_SET_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask); else GPIO_CLEAR_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask); tunedDelay(_tx_delay); // wait start bit // Write each of the 8 bits in LSB mode for (uint8_t i = 8; i > 0; --i) { if (byte & 1) // choose bit GPIO_SET_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask); else GPIO_CLEAR_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask); tunedDelay(_tx_delay); byte >>= 1; } // restore pin to natural state - stop bit if (_inverse_logic) GPIO_CLEAR_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask); else GPIO_SET_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask); // enable interrupts GLOBAL_IRQ_ENABLE(); tunedDelay(_tx_delay); // wait stop bit return 1; } // Read data from buffer int SoftwareSerial::read() { if (!isListening()) return -1; // Empty buffer? if (_receive_buffer_head == _receive_buffer_tail) return -1; // Read from "head" uint8_t data = _receive_buffer[_receive_buffer_head]; // grab next byte _receive_buffer_head = (_receive_buffer_head + 1) % _SS_MAX_RX_BUFF; return data; } int SoftwareSerial::available() { if (!isListening()) return 0; return ((unsigned int)(_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head)) % _SS_MAX_RX_BUFF; } void SoftwareSerial::flush() { // There is no tx buffering, simply return } 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]; }