2019-11-13 02:23:02 +01:00
/*
* 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 8 MHz 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)
* - - 20 MHz 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
//
2019-12-20 17:14:27 -08:00
# if defined(PLATFORMIO) && defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC)
2019-11-14 12:21:23 +01:00
2019-11-13 02:23:02 +01:00
# include "SoftwareSerial.h"
2019-11-14 04:47:46 -06:00
# include "timers.h"
2019-11-13 02:23:02 +01:00
# 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 < IRQn_Type > ( 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 ] ;
}
2019-11-14 12:21:23 +01:00
# endif // ARDUINO_ARCH_STM32 && !STM32GENERIC