/* * SoftwareSerial.cpp (formerly NewSoftSerial.cpp) * * Multi-instance software serial library for Arduino/Wiring * -- Interrupt-driven receive and other improvements by ladyada * (http://ladyada.net) * -- Tuning, circular buffer, derivation from class Print/Stream, * multi-instance support, porting to 8MHz processors, * various optimizations, PROGMEM delay tables, inverse logic and * direct port writing by Mikal Hart (http://www.arduiniana.org) * -- Pin change interrupt macros by Paul Stoffregen (http://www.pjrc.com) * -- 20MHz processor support by Garrett Mace (http://www.macetech.com) * -- ATmega1280/2560 support by Brett Hagman (http://www.roguerobotics.com/) * -- STM32 support by Armin van der Togt * * 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. */ // // Includes // #if defined(PLATFORMIO) && defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC) #include "SoftwareSerial.h" #include "timers.h" #define OVERSAMPLE 3 // in RX, Timer will generate interruption OVERSAMPLE time during a bit. Thus OVERSAMPLE ticks in a bit. (interrupt not synchonized with edge). // defined in bit-periods #define HALFDUPLEX_SWITCH_DELAY 5 // It's best to define TIMER_SERIAL in variant.h. If not defined, we choose one here // The order is based on (lack of) features and compare channels, we choose the simplest available // because we only need an update interrupt #if !defined(TIMER_SERIAL) #if defined(TIM18_BASE) #define TIMER_SERIAL TIM18 #elif defined(TIM7_BASE) #define TIMER_SERIAL TIM7 #elif defined(TIM6_BASE) #define TIMER_SERIAL TIM6 #elif defined(TIM22_BASE) #define TIMER_SERIAL TIM22 #elif defined(TIM21_BASE) #define TIMER_SERIAL TIM21 #elif defined(TIM17_BASE) #define TIMER_SERIAL TIM17 #elif defined(TIM16_BASE) #define TIMER_SERIAL TIM16 #elif defined(TIM15_BASE) #define TIMER_SERIAL TIM15 #elif defined(TIM14_BASE) #define TIMER_SERIAL TIM14 #elif defined(TIM13_BASE) #define TIMER_SERIAL TIM13 #elif defined(TIM11_BASE) #define TIMER_SERIAL TIM11 #elif defined(TIM10_BASE) #define TIMER_SERIAL TIM10 #elif defined(TIM12_BASE) #define TIMER_SERIAL TIM12 #elif defined(TIM19_BASE) #define TIMER_SERIAL TIM19 #elif defined(TIM9_BASE) #define TIMER_SERIAL TIM9 #elif defined(TIM5_BASE) #define TIMER_SERIAL TIM5 #elif defined(TIM4_BASE) #define TIMER_SERIAL TIM4 #elif defined(TIM3_BASE) #define TIMER_SERIAL TIM3 #elif defined(TIM2_BASE) #define TIMER_SERIAL TIM2 #elif defined(TIM20_BASE) #define TIMER_SERIAL TIM20 #elif defined(TIM8_BASE) #define TIMER_SERIAL TIM8 #elif defined(TIM1_BASE) #define TIMER_SERIAL TIM1 #else #error No suitable timer found for SoftwareSerial, define TIMER_SERIAL in variant.h #endif #endif // // Statics // HardwareTimer SoftwareSerial::timer(TIMER_SERIAL); const IRQn_Type SoftwareSerial::timer_interrupt_number = static_cast(getTimerUpIrq(TIMER_SERIAL)); uint32_t SoftwareSerial::timer_interrupt_priority = NVIC_EncodePriority(NVIC_GetPriorityGrouping(), TIM_IRQ_PRIO, TIM_IRQ_SUBPRIO); SoftwareSerial *SoftwareSerial::active_listener = nullptr; SoftwareSerial *volatile SoftwareSerial::active_out = nullptr; SoftwareSerial *volatile SoftwareSerial::active_in = nullptr; int32_t SoftwareSerial::tx_tick_cnt = 0; // OVERSAMPLE ticks needed for a bit int32_t volatile SoftwareSerial::rx_tick_cnt = 0; // OVERSAMPLE ticks needed for a bit uint32_t SoftwareSerial::tx_buffer = 0; int32_t SoftwareSerial::tx_bit_cnt = 0; uint32_t SoftwareSerial::rx_buffer = 0; int32_t SoftwareSerial::rx_bit_cnt = -1; // rx_bit_cnt = -1 : waiting for start bit uint32_t SoftwareSerial::cur_speed = 0; void SoftwareSerial::setInterruptPriority(uint32_t preemptPriority, uint32_t subPriority) { timer_interrupt_priority = NVIC_EncodePriority(NVIC_GetPriorityGrouping(), preemptPriority, subPriority); } // // Private methods // void SoftwareSerial::setSpeed(uint32_t speed) { if (speed != cur_speed) { timer.pause(); if (speed != 0) { // Disable the timer uint32_t clock_rate, cmp_value; // Get timer clock clock_rate = timer.getTimerClkFreq(); int pre = 1; // Calculate prescale an compare value do { cmp_value = clock_rate / (speed * OVERSAMPLE); if (cmp_value >= UINT16_MAX) { clock_rate /= 2; pre *= 2; } } while (cmp_value >= UINT16_MAX); timer.setPrescaleFactor(pre); timer.setOverflow(cmp_value); timer.setCount(0); timer.attachInterrupt(&handleInterrupt); timer.resume(); NVIC_SetPriority(timer_interrupt_number, timer_interrupt_priority); } else timer.detachInterrupt(); cur_speed = speed; } } // This function sets the current object as the "listening" // one and returns true if it replaces another bool SoftwareSerial::listen() { if (active_listener != this) { // wait for any transmit to complete as we may change speed while (active_out); active_listener->stopListening(); rx_tick_cnt = 1; // 1 : next interrupt will decrease rx_tick_cnt to 0 which means RX pin level will be considered. rx_bit_cnt = -1; // rx_bit_cnt = -1 : waiting for start bit setSpeed(_speed); active_listener = this; if (!_half_duplex) active_in = this; return true; } return false; } // Stop listening. Returns true if we were actually listening. bool SoftwareSerial::stopListening() { if (active_listener == this) { // wait for any output to complete while (active_out); if (_half_duplex) setRXTX(false); active_listener = nullptr; active_in = nullptr; // turn off ints setSpeed(0); return true; } return false; } inline void SoftwareSerial::setTX() { if (_inverse_logic) LL_GPIO_ResetOutputPin(_transmitPinPort, _transmitPinNumber); else LL_GPIO_SetOutputPin(_transmitPinPort, _transmitPinNumber); pinMode(_transmitPin, OUTPUT); } inline void SoftwareSerial::setRX() { pinMode(_receivePin, _inverse_logic ? INPUT_PULLDOWN : INPUT_PULLUP); // pullup for normal logic! } inline void SoftwareSerial::setRXTX(bool input) { if (_half_duplex) { if (input) { if (active_in != this) { setRX(); rx_bit_cnt = -1; // rx_bit_cnt = -1 : waiting for start bit rx_tick_cnt = 2; // 2 : next interrupt will be discarded. 2 interrupts required to consider RX pin level active_in = this; } } else { if (active_in == this) { setTX(); active_in = nullptr; } } } } inline void SoftwareSerial::send() { if (--tx_tick_cnt <= 0) { // if tx_tick_cnt > 0 interrupt is discarded. Only when tx_tick_cnt reaches 0 is TX pin set. if (tx_bit_cnt++ < 10) { // tx_bit_cnt < 10 transmission is not finished (10 = 1 start +8 bits + 1 stop) // Send data (including start and stop bits) if (tx_buffer & 1) LL_GPIO_SetOutputPin(_transmitPinPort, _transmitPinNumber); else LL_GPIO_ResetOutputPin(_transmitPinPort, _transmitPinNumber); tx_buffer >>= 1; tx_tick_cnt = OVERSAMPLE; // Wait OVERSAMPLE ticks to send next bit } else { // Transmission finished tx_tick_cnt = 1; if (_output_pending) { active_out = nullptr; // In half-duplex mode wait HALFDUPLEX_SWITCH_DELAY bit-periods after the byte has // been transmitted before allowing the switch to RX mode } else if (tx_bit_cnt > 10 + OVERSAMPLE * HALFDUPLEX_SWITCH_DELAY) { if (_half_duplex && active_listener == this) setRXTX(true); active_out = nullptr; } } } } // // The receive routine called by the interrupt handler // inline void SoftwareSerial::recv() { if (--rx_tick_cnt <= 0) { // if rx_tick_cnt > 0 interrupt is discarded. Only when rx_tick_cnt reaches 0 is RX pin considered bool inbit = LL_GPIO_IsInputPinSet(_receivePinPort, _receivePinNumber) ^ _inverse_logic; if (rx_bit_cnt == -1) { // rx_bit_cnt = -1 : waiting for start bit if (!inbit) { // got start bit rx_bit_cnt = 0; // rx_bit_cnt == 0 : start bit received rx_tick_cnt = OVERSAMPLE + 1; // Wait 1 bit (OVERSAMPLE ticks) + 1 tick in order to sample RX pin in the middle of the edge (and not too close to the edge) rx_buffer = 0; } else rx_tick_cnt = 1; // Waiting for start bit, but wrong level. Wait for next Interrupt to check RX pin level } else if (rx_bit_cnt >= 8) { // rx_bit_cnt >= 8 : waiting for stop bit if (inbit) { // Stop-bit read complete. Add to buffer. uint8_t next = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF; if (next != _receive_buffer_head) { // save new data in buffer: tail points to byte destination _receive_buffer[_receive_buffer_tail] = rx_buffer; // save new byte _receive_buffer_tail = next; } else // rx_bit_cnt = x with x = [0..7] correspond to new bit x received _buffer_overflow = true; } // Full trame received. Restart waiting for start bit at next interrupt rx_tick_cnt = 1; rx_bit_cnt = -1; } else { // data bits rx_buffer >>= 1; if (inbit) rx_buffer |= 0x80; rx_bit_cnt++; // Prepare for next bit rx_tick_cnt = OVERSAMPLE; // Wait OVERSAMPLE ticks before sampling next bit } } } // // Interrupt handling // /* static */ inline void SoftwareSerial::handleInterrupt(HardwareTimer*) { if (active_in) active_in->recv(); if (active_out) active_out->send(); } // // Constructor // SoftwareSerial::SoftwareSerial(uint16_t receivePin, uint16_t transmitPin, bool inverse_logic /* = false */) : _receivePin(receivePin), _transmitPin(transmitPin), _receivePinPort(digitalPinToPort(receivePin)), _receivePinNumber(STM_LL_GPIO_PIN(digitalPinToPinName(receivePin))), _transmitPinPort(digitalPinToPort(transmitPin)), _transmitPinNumber(STM_LL_GPIO_PIN(digitalPinToPinName(transmitPin))), _speed(0), _buffer_overflow(false), _inverse_logic(inverse_logic), _half_duplex(receivePin == transmitPin), _output_pending(0), _receive_buffer_tail(0), _receive_buffer_head(0) { if ((receivePin < NUM_DIGITAL_PINS) || (transmitPin < NUM_DIGITAL_PINS)) { /* Enable GPIO clock for tx and rx pin*/ set_GPIO_Port_Clock(STM_PORT(digitalPinToPinName(transmitPin))); set_GPIO_Port_Clock(STM_PORT(digitalPinToPinName(receivePin))); } else _Error_Handler("ERROR: invalid pin number\n", -1); } // // Destructor // SoftwareSerial::~SoftwareSerial() { end(); } // // Public methods // void SoftwareSerial::begin(long speed) { #ifdef FORCE_BAUD_RATE speed = FORCE_BAUD_RATE; #endif _speed = speed; if (!_half_duplex) { setTX(); setRX(); listen(); } else setTX(); } void SoftwareSerial::end() { stopListening(); } // Read data from buffer int SoftwareSerial::read() { // 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() { return (_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head) % _SS_MAX_RX_BUFF; } size_t SoftwareSerial::write(uint8_t b) { // wait for previous transmit to complete _output_pending = 1; while (active_out) { /* nada */ } // add start and stop bits. tx_buffer = b << 1 | 0x200; if (_inverse_logic) tx_buffer = ~tx_buffer; tx_bit_cnt = 0; tx_tick_cnt = OVERSAMPLE; setSpeed(_speed); if (_half_duplex) setRXTX(false); _output_pending = 0; // make us active active_out = this; return 1; } void SoftwareSerial::flush() { noInterrupts(); _receive_buffer_head = _receive_buffer_tail = 0; interrupts(); } int SoftwareSerial::peek() { // Empty buffer? if (_receive_buffer_head == _receive_buffer_tail) return -1; // Read from "head" return _receive_buffer[_receive_buffer_head]; } #endif // ARDUINO_ARCH_STM32 && !STM32GENERIC