Files
ArduinoCore-AT32F4/cores/arduino/SoftwareSerial.cpp
2022-09-11 23:13:29 +08:00

424 lines
12 KiB
C++

/*
* 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
* -- AT32F4 support by WeactStudio
*
* 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
//
#include "SoftwareSerial.h"
#include "libcore/timer.h"
#define OVERSAMPLE 3 // in RX, Timer will generate interruption OVERSAMPLE time during a bit. Thus OVERSAMPLE ticks in a bit. (interrupt not synchronized 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 (TMR18_BASE)
#define TIMER_SERIAL TIM18
#elif defined (TMR7_BASE)
#define TIMER_SERIAL TIM7
#elif defined (TMR6_BASE)
#define TIMER_SERIAL TIM6
#elif defined (TMR22_BASE)
#define TIMER_SERIAL TIM22
#elif defined (TMR21_BASE)
#define TIMER_SERIAL TIM21
#elif defined (TMR17_BASE)
#define TIMER_SERIAL TIM17
#elif defined (TMR16_BASE)
#define TIMER_SERIAL TIM16
#elif defined (TMR15_BASE)
#define TIMER_SERIAL TIM15
#elif defined (TMR14_BASE)
#define TIMER_SERIAL TIM14
#elif defined (TMR13_BASE)
#define TIMER_SERIAL TIM13
#elif defined (TMR11_BASE)
#define TIMER_SERIAL TIM11
#elif defined (TMR10_BASE)
#define TIMER_SERIAL TIM10
#elif defined (TMR12_BASE)
#define TIMER_SERIAL TIM12
#elif defined (TMR19_BASE)
#define TIMER_SERIAL TIM19
#elif defined (TMR9_BASE)
#define TIMER_SERIAL TIM9
#elif defined (TMR5_BASE)
#define TIMER_SERIAL TIM5
#elif defined (TMR4_BASE)
#define TIMER_SERIAL TIM4
#elif defined (TMR3_BASE)
#define TIMER_SERIAL TIM3
#elif defined (TMR2_BASE)
#define TIMER_SERIAL TIM2
#elif defined (TMR20_BASE)
#define TIMER_SERIAL TIM20
#elif defined (TMR8_BASE)
#define TIMER_SERIAL TIM8
#elif defined (TMR1_BASE)
#define TIMER_SERIAL TIM1
#else
#error No suitable timer found for SoftwareSerial, define TIMER_SERIAL in variant.h
#endif
#endif
//
// Static
//
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;
//
// Private methods
//
void SoftwareSerial::setSpeed(uint32_t speed)
{
if (speed != cur_speed) {
// Timer_Pause(TIMER_SERIAL);
if (speed != 0) {
// Disable the timer
uint32_t clock_rate, cmp_value;
// Get timer clock
clock_rate = Timer_GetClockMax(TIMER_SERIAL);
int pre = 1;
// Calculate prescale an compare value
do {
cmp_value = clock_rate / (speed * OVERSAMPLE);
if (cmp_value >= UINT16_MAX) {
clock_rate = clock_rate / 2;
pre *= 2;
}
} while (cmp_value >= UINT16_MAX);
Timer_SetPrescaler(TIMER_SERIAL, pre - 1);
Timer_SetReload(TIMER_SERIAL, cmp_value - 1);
Timer_SetCounter(TIMER_SERIAL, 0);
// Timer_Resume(TIMER_SERIAL);
} else {
}
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);
if (active_listener != nullptr) {
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;
} else if (!active_out) {
setRXTX(true);
}
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) {
GPIO_LOW(_transmitPinPort, _transmitPinNumber);
} else {
GPIO_HIGH(_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 reach 0 we set TX pin.
if (tx_bit_cnt++ < 10) { // tx_bit_cnt < 10 transmission is not fiisehed (10 = 1 start +8 bits + 1 stop)
// send data (including start and stop bits)
if (tx_buffer & 1) {
GPIO_HIGH(_transmitPinPort, _transmitPinNumber);
} else {
GPIO_LOW(_transmitPinPort, _transmitPinNumber);
}
tx_buffer >>= 1;
tx_tick_cnt = OVERSAMPLE; // Wait OVERSAMPLE tick to send next bit
} else { // Transmission finished
tx_tick_cnt = 1;
if (_output_pending) {
active_out = nullptr;
// When in half-duplex mode, wait for 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 reach 0 RX pin is considered
bool inbit = GPIO_READ(_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 we don't get right level. Wait for next Interrupt to ckech 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 where byte goes
_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++; // Preprare for next bit
rx_tick_cnt = OVERSAMPLE; // Wait OVERSAMPLE ticks before sampling next bit
}
}
}
//
// Interrupt handling
//
/* static */
inline void SoftwareSerial::handleInterrupt()
{
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(digitalPinToBitMask(receivePin)),
_transmitPinPort(digitalPinToPort(transmitPin)),
_transmitPinNumber(digitalPinToBitMask(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)
{
}
//
// Destructor
//
SoftwareSerial::~SoftwareSerial()
{
end();
}
//
// Public methods
//
void SoftwareSerial::begin(long speed)
{
#ifdef FORCE_BAUD_RATE
speed = FORCE_BAUD_RATE;
#endif
_speed = speed;
Timer_SetInterrupt(TIMER_SERIAL, 1000, handleInterrupt);
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)
;
// 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];
}
void SoftwareSerial::setInterruptPriority(uint32_t preemptPriority, uint32_t subPriority)
{
}