2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
22 * Cleanflight (or Baseflight): original
23 * jflyper: Mono-timer and single-wire half-duplex
31 #if defined(USE_SOFTSERIAL)
33 #include "build/build_config.h"
34 #include "build/atomic.h"
36 #include "build/debug.h"
38 #include "common/utils.h"
40 #include "io/serial.h"
42 #include "drivers/nvic.h"
43 #include "drivers/io.h"
44 #include "drivers/serial.h"
45 #include "drivers/serial_impl.h"
46 #include "drivers/timer.h"
48 #include "serial_softserial.h"
50 #define RX_TOTAL_BITS 10
51 #define TX_TOTAL_BITS 10
58 #define ICPOLARITY_RISING true
59 #define ICPOLARITY_FALLING false
61 typedef struct softSerial_s
{
67 const timerHardware_t
*timerHardware
;
69 const TIM_HandleTypeDef
*timerHandle
;
71 const timerHardware_t
*exTimerHardware
;
73 volatile uint8_t rxBuffer
[SOFTSERIAL_BUFFER_SIZE
];
74 volatile uint8_t txBuffer
[SOFTSERIAL_BUFFER_SIZE
];
76 uint8_t isSearchingForStartBit
;
78 uint8_t rxLastLeadingEdgeAtBitIndex
;
82 uint8_t isTransmittingData
;
83 int8_t bitsLeftToTransmit
;
85 uint16_t internalTxBuffer
; // includes start and stop bits
86 uint16_t internalRxBuffer
; // includes start and stop bits
88 uint16_t transmissionErrors
;
89 uint16_t receiveErrors
;
91 timerMode_e timerMode
;
93 timerOvrHandlerRec_t overCb
;
94 timerCCHandlerRec_t edgeCb
;
97 static const struct serialPortVTable softSerialVTable
; // Forward
98 // SERIAL_SOFTSERIAL_COUNT is fine, softserial ports must start from 1 and be continuous
99 static softSerial_t softSerialPorts
[SERIAL_SOFTSERIAL_COUNT
];
101 void onSerialTimerOverflow(timerOvrHandlerRec_t
*cbRec
, captureCompare_t capture
);
102 void onSerialRxPinChange(timerCCHandlerRec_t
*cbRec
, captureCompare_t capture
);
104 typedef enum { IDLE
= ENABLE
, MARK
= DISABLE
} SerialTxState_e
;
105 static void setTxSignal(softSerial_t
*softSerial
, SerialTxState_e state
)
107 IOWrite(softSerial
->txIO
, (softSerial
->port
.options
& SERIAL_INVERTED
) ? !state
: state
);
110 static void serialEnableCC(softSerial_t
*softSerial
)
112 #ifdef USE_HAL_DRIVER
113 TIM_CCxChannelCmd(softSerial
->timerHardware
->tim
, softSerial
->timerHardware
->channel
, TIM_CCx_ENABLE
);
115 TIM_CCxCmd(softSerial
->timerHardware
->tim
, softSerial
->timerHardware
->channel
, TIM_CCx_Enable
);
119 // switch to receive mode
120 static void serialInputPortActivate(softSerial_t
*softSerial
)
122 const serialPullMode_t pull
= serialOptions_pull(softSerial
->port
.options
);
123 const uint8_t pinConfig
= ((const uint8_t[]){IOCFG_AF_PP
, IOCFG_AF_PP_PD
, IOCFG_AF_PP_UP
})[pull
];
124 // softserial can easily support opendrain mode, but it is not implemented
125 IOConfigGPIOAF(softSerial
->rxIO
, pinConfig
, softSerial
->timerHardware
->alternateFunction
);
127 softSerial
->rxActive
= true;
128 softSerial
->isSearchingForStartBit
= true;
129 softSerial
->rxBitIndex
= 0;
131 // Enable input capture
133 serialEnableCC(softSerial
);
136 static void serialInputPortDeActivate(softSerial_t
*softSerial
)
138 // Disable input capture
140 #ifdef USE_HAL_DRIVER
141 TIM_CCxChannelCmd(softSerial
->timerHardware
->tim
, softSerial
->timerHardware
->channel
, TIM_CCx_DISABLE
);
143 TIM_CCxCmd(softSerial
->timerHardware
->tim
, softSerial
->timerHardware
->channel
, TIM_CCx_Disable
);
145 IOConfigGPIO(softSerial
->rxIO
, IOCFG_IN_FLOATING
); // leave AF mode; serialOutputPortActivate will follow immediately
146 softSerial
->rxActive
= false;
149 static void serialOutputPortActivate(softSerial_t
*softSerial
)
151 if (softSerial
->exTimerHardware
) {
152 IOConfigGPIOAF(softSerial
->txIO
, IOCFG_OUT_PP
, softSerial
->exTimerHardware
->alternateFunction
);
154 IOConfigGPIO(softSerial
->txIO
, IOCFG_OUT_PP
);
158 static void serialOutputPortDeActivate(softSerial_t
*softSerial
)
160 if (softSerial
->exTimerHardware
) {
161 // TODO: there in no AF associated with input port
162 IOConfigGPIOAF(softSerial
->txIO
, IOCFG_IN_FLOATING
, softSerial
->exTimerHardware
->alternateFunction
);
164 IOConfigGPIO(softSerial
->txIO
, IOCFG_IN_FLOATING
);
168 static bool isTimerPeriodTooLarge(uint32_t timerPeriod
)
170 return timerPeriod
> 0xFFFF;
173 static void serialTimerConfigureTimebase(const timerHardware_t
*timerHardwarePtr
, uint32_t baud
)
175 uint32_t baseClock
= timerClock(timerHardwarePtr
->tim
);
176 uint32_t clock
= baseClock
;
177 uint32_t timerPeriod
;
179 while (timerPeriod
= clock
/ baud
, isTimerPeriodTooLarge(timerPeriod
) && clock
> 1) {
180 clock
= clock
/ 2; // minimum baudrate is < 1200
182 timerConfigure(timerHardwarePtr
, timerPeriod
, clock
);
185 static void resetBuffers(softSerial_t
*softSerial
)
187 softSerial
->port
.rxBufferSize
= SOFTSERIAL_BUFFER_SIZE
;
188 softSerial
->port
.rxBuffer
= softSerial
->rxBuffer
;
189 softSerial
->port
.rxBufferTail
= 0;
190 softSerial
->port
.rxBufferHead
= 0;
192 softSerial
->port
.txBuffer
= softSerial
->txBuffer
;
193 softSerial
->port
.txBufferSize
= SOFTSERIAL_BUFFER_SIZE
;
194 softSerial
->port
.txBufferTail
= 0;
195 softSerial
->port
.txBufferHead
= 0;
198 softSerial_t
* softSerialFromIdentifier(serialPortIdentifier_e identifier
)
200 if (identifier
>= SERIAL_PORT_SOFTSERIAL1
&& identifier
< SERIAL_PORT_SOFTSERIAL1
+ SERIAL_SOFTSERIAL_COUNT
) {
201 return &softSerialPorts
[identifier
- SERIAL_PORT_SOFTSERIAL1
];
206 serialPort_t
*softSerialOpen(serialPortIdentifier_e identifier
, serialReceiveCallbackPtr rxCallback
, void *rxCallbackData
, uint32_t baud
, portMode_e mode
, portOptions_e options
)
208 softSerial_t
*softSerial
= softSerialFromIdentifier(identifier
);
212 // fill identifier early, so initialization code can use it
213 softSerial
->port
.identifier
= identifier
;
215 const int resourceIndex
= serialResourceIndex(identifier
);
216 const resourceOwner_e ownerTxRx
= serialOwnerTxRx(identifier
); // rx is always +1
217 const int ownerIndex
= serialOwnerIndex(identifier
);
219 const ioTag_t tagRx
= serialPinConfig()->ioTagRx
[resourceIndex
];
220 const ioTag_t tagTx
= serialPinConfig()->ioTagTx
[resourceIndex
];
222 const timerHardware_t
*timerTx
= timerAllocate(tagTx
, ownerTxRx
, ownerIndex
);
223 const timerHardware_t
*timerRx
= (tagTx
== tagRx
) ? timerTx
: timerAllocate(tagRx
, ownerTxRx
+ 1, ownerIndex
);
225 IO_t rxIO
= IOGetByTag(tagRx
);
226 IO_t txIO
= IOGetByTag(tagTx
);
228 if (options
& SERIAL_BIDIR
) {
229 // If RX and TX pins are both assigned, we CAN use either with a timer.
230 // However, for consistency with hardware UARTs, we only use TX pin,
231 // and this pin must have a timer, and it must not be N-Channel.
232 if (!timerTx
|| (timerTx
->output
& TIMER_OUTPUT_N_CHANNEL
)) {
236 softSerial
->timerHardware
= timerTx
;
237 softSerial
->txIO
= txIO
;
238 softSerial
->rxIO
= txIO
;
239 IOInit(txIO
, ownerTxRx
, ownerIndex
);
241 if (mode
& MODE_RX
) {
242 // Need a pin & a timer on RX. Channel must not be N-Channel.
243 if (!timerRx
|| (timerRx
->output
& TIMER_OUTPUT_N_CHANNEL
)) {
247 softSerial
->rxIO
= rxIO
;
248 softSerial
->timerHardware
= timerRx
;
249 if (!((mode
& MODE_TX
) && rxIO
== txIO
)) {
251 IOInit(rxIO
, ownerTxRx
+ 1, ownerIndex
);
255 if (mode
& MODE_TX
) {
260 softSerial
->txIO
= txIO
;
262 if (!(mode
& MODE_RX
)) {
263 // TX Simplex, must have a timer
267 softSerial
->timerHardware
= timerTx
;
269 // Duplex, use timerTx if available
270 softSerial
->exTimerHardware
= timerTx
;
272 IOInit(txIO
, ownerTxRx
, ownerIndex
);
276 softSerial
->port
.vTable
= &softSerialVTable
;
277 softSerial
->port
.baudRate
= baud
;
278 softSerial
->port
.mode
= mode
;
279 softSerial
->port
.options
= options
;
280 softSerial
->port
.rxCallback
= rxCallback
;
281 softSerial
->port
.rxCallbackData
= rxCallbackData
;
283 resetBuffers(softSerial
);
285 softSerial
->transmissionErrors
= 0;
286 softSerial
->receiveErrors
= 0;
288 softSerial
->rxActive
= false;
289 softSerial
->isTransmittingData
= false;
291 // Configure master timer (on RX); time base and input capture
293 serialTimerConfigureTimebase(softSerial
->timerHardware
, baud
);
294 timerChConfigIC(softSerial
->timerHardware
, (options
& SERIAL_INVERTED
) ? ICPOLARITY_RISING
: ICPOLARITY_FALLING
, 0);
296 // Initialize callbacks
297 timerChCCHandlerInit(&softSerial
->edgeCb
, onSerialRxPinChange
);
298 timerChOvrHandlerInit(&softSerial
->overCb
, onSerialTimerOverflow
);
300 // Configure bit clock interrupt & handler.
301 // If we have an extra timer (on TX), it is initialized and configured
302 // for overflow interrupt.
303 // Receiver input capture is configured when input is activated.
305 if ((mode
& MODE_TX
) && softSerial
->exTimerHardware
&& softSerial
->exTimerHardware
->tim
!= softSerial
->timerHardware
->tim
) {
306 softSerial
->timerMode
= TIMER_MODE_DUAL
;
307 serialTimerConfigureTimebase(softSerial
->exTimerHardware
, baud
);
308 timerChConfigCallbacks(softSerial
->exTimerHardware
, NULL
, &softSerial
->overCb
);
309 timerChConfigCallbacks(softSerial
->timerHardware
, &softSerial
->edgeCb
, NULL
);
311 softSerial
->timerMode
= TIMER_MODE_SINGLE
;
312 timerChConfigCallbacks(softSerial
->timerHardware
, &softSerial
->edgeCb
, &softSerial
->overCb
);
315 #ifdef USE_HAL_DRIVER
316 softSerial
->timerHandle
= timerFindTimerHandle(softSerial
->timerHardware
->tim
);
319 if (!(options
& SERIAL_BIDIR
)) {
320 serialOutputPortActivate(softSerial
);
321 setTxSignal(softSerial
, IDLE
);
324 serialInputPortActivate(softSerial
);
326 return &softSerial
->port
;
333 void processTxState(softSerial_t
*softSerial
)
335 if (!softSerial
->isTransmittingData
) {
336 if (isSoftSerialTransmitBufferEmpty((serialPort_t
*)softSerial
)) {
337 // Transmit buffer empty.
338 // Switch to RX mode if not already listening and running in half-duplex mode
339 if (!softSerial
->rxActive
&& softSerial
->port
.options
& SERIAL_BIDIR
) {
340 serialOutputPortDeActivate(softSerial
); // TODO: not necessary
341 serialInputPortActivate(softSerial
);
347 uint8_t byteToSend
= softSerial
->port
.txBuffer
[softSerial
->port
.txBufferTail
++];
348 if (softSerial
->port
.txBufferTail
>= softSerial
->port
.txBufferSize
) {
349 softSerial
->port
.txBufferTail
= 0;
352 // build internal buffer, MSB = Stop Bit (1) + data bits (MSB to LSB) + start bit(0) LSB
353 softSerial
->internalTxBuffer
= (1 << (TX_TOTAL_BITS
- 1)) | (byteToSend
<< 1) | 0;
354 softSerial
->bitsLeftToTransmit
= TX_TOTAL_BITS
;
355 softSerial
->isTransmittingData
= true;
357 if (softSerial
->rxActive
&& (softSerial
->port
.options
& SERIAL_BIDIR
)) {
358 // Half-duplex: Deactivate receiver, activate transmitter
359 serialInputPortDeActivate(softSerial
);
360 serialOutputPortActivate(softSerial
);
362 // Start sending on next bit timing, as port manipulation takes time,
363 // and continuing here may cause bit period to decrease causing sampling errors
364 // at the receiver under high rates.
365 // Note that there will be (little less than) 1-bit delay; take it as "turn around time".
366 // This time is important in noninverted pulldown bidir mode (SmartAudio).
367 // During this period, TX pin is in IDLE state so next startbit (MARK) can be detected
368 // XXX Otherwise, we may be able to reload counter and continue. (Future work.)
373 if (softSerial
->bitsLeftToTransmit
) {
374 const bool bit
= softSerial
->internalTxBuffer
& 1;
375 softSerial
->internalTxBuffer
>>= 1;
377 setTxSignal(softSerial
, bit
);
378 softSerial
->bitsLeftToTransmit
--;
382 softSerial
->isTransmittingData
= false;
390 void applyChangedBits(softSerial_t
*softSerial
)
392 if (softSerial
->rxEdge
== TRAILING
) {
393 for (unsigned bitToSet
= softSerial
->rxLastLeadingEdgeAtBitIndex
; bitToSet
< softSerial
->rxBitIndex
; bitToSet
++) {
394 softSerial
->internalRxBuffer
|= 1 << bitToSet
;
399 void prepareForNextRxByte(softSerial_t
*softSerial
)
401 // prepare for next byte
402 softSerial
->rxBitIndex
= 0;
403 softSerial
->isSearchingForStartBit
= true;
404 if (softSerial
->rxEdge
== LEADING
) {
405 softSerial
->rxEdge
= TRAILING
;
406 timerChConfigIC(softSerial
->timerHardware
, (softSerial
->port
.options
& SERIAL_INVERTED
) ? ICPOLARITY_RISING
: ICPOLARITY_FALLING
, 0);
407 serialEnableCC(softSerial
);
411 #define STOP_BIT_MASK (1 << 0)
412 #define START_BIT_MASK (1 << (RX_TOTAL_BITS - 1))
414 void extractAndStoreRxByte(softSerial_t
*softSerial
)
416 if ((softSerial
->port
.mode
& MODE_RX
) == 0) {
420 uint8_t haveStartBit
= (softSerial
->internalRxBuffer
& START_BIT_MASK
) == 0;
421 uint8_t haveStopBit
= (softSerial
->internalRxBuffer
& STOP_BIT_MASK
) == 1;
423 if (!haveStartBit
|| !haveStopBit
) {
424 softSerial
->receiveErrors
++;
428 uint8_t rxByte
= (softSerial
->internalRxBuffer
>> 1) & 0xFF;
430 if (softSerial
->port
.rxCallback
) {
431 softSerial
->port
.rxCallback(rxByte
, softSerial
->port
.rxCallbackData
);
433 softSerial
->port
.rxBuffer
[softSerial
->port
.rxBufferHead
] = rxByte
;
434 softSerial
->port
.rxBufferHead
= (softSerial
->port
.rxBufferHead
+ 1) % softSerial
->port
.rxBufferSize
;
438 void processRxState(softSerial_t
*softSerial
)
440 if (softSerial
->isSearchingForStartBit
) {
444 softSerial
->rxBitIndex
++;
446 if (softSerial
->rxBitIndex
== RX_TOTAL_BITS
- 1) {
447 applyChangedBits(softSerial
);
451 if (softSerial
->rxBitIndex
== RX_TOTAL_BITS
) {
453 if (softSerial
->rxEdge
== TRAILING
) {
454 softSerial
->internalRxBuffer
|= STOP_BIT_MASK
;
457 extractAndStoreRxByte(softSerial
);
458 prepareForNextRxByte(softSerial
);
462 void onSerialTimerOverflow(timerOvrHandlerRec_t
*cbRec
, captureCompare_t capture
)
465 softSerial_t
*self
= container_of(cbRec
, softSerial_t
, overCb
);
467 if (self
->port
.mode
& MODE_TX
)
468 processTxState(self
);
470 if (self
->port
.mode
& MODE_RX
)
471 processRxState(self
);
474 void onSerialRxPinChange(timerCCHandlerRec_t
*cbRec
, captureCompare_t capture
)
478 softSerial_t
*self
= container_of(cbRec
, softSerial_t
, edgeCb
);
480 if ((self
->port
.mode
& MODE_RX
) == 0) {
484 const bool inverted
= self
->port
.options
& SERIAL_INVERTED
;
486 if (self
->isSearchingForStartBit
) {
487 // Synchronize the bit timing so that it will interrupt at the center
488 // of the bit period.
490 #ifdef USE_HAL_DRIVER
491 __HAL_TIM_SetCounter(self
->timerHandle
, __HAL_TIM_GetAutoreload(self
->timerHandle
) / 2);
493 TIM_SetCounter(self
->timerHardware
->tim
, self
->timerHardware
->tim
->ARR
/ 2);
496 // For a mono-timer full duplex configuration, this may clobber the
497 // transmission because the next callback to the onSerialTimerOverflow
498 // will happen too early causing transmission errors.
499 // For a dual-timer configuration, there is no problem.
501 if ((self
->timerMode
!= TIMER_MODE_DUAL
) && self
->isTransmittingData
) {
502 self
->transmissionErrors
++;
505 timerChConfigIC(self
->timerHardware
, inverted
? ICPOLARITY_FALLING
: ICPOLARITY_RISING
, 0);
506 #if defined(STM32F7) || defined(STM32H7) || defined(STM32G4)
507 serialEnableCC(self
);
509 self
->rxEdge
= LEADING
;
511 self
->rxBitIndex
= 0;
512 self
->rxLastLeadingEdgeAtBitIndex
= 0;
513 self
->internalRxBuffer
= 0;
514 self
->isSearchingForStartBit
= false;
518 if (self
->rxEdge
== LEADING
) {
519 self
->rxLastLeadingEdgeAtBitIndex
= self
->rxBitIndex
;
522 applyChangedBits(self
);
524 if (self
->rxEdge
== TRAILING
) {
525 self
->rxEdge
= LEADING
;
526 timerChConfigIC(self
->timerHardware
, inverted
? ICPOLARITY_FALLING
: ICPOLARITY_RISING
, 0);
528 self
->rxEdge
= TRAILING
;
529 timerChConfigIC(self
->timerHardware
, inverted
? ICPOLARITY_RISING
: ICPOLARITY_FALLING
, 0);
531 #if defined(STM32F7) || defined(STM32H7) || defined(STM32G4)
532 serialEnableCC(self
);
537 * Standard serial driver API
540 uint32_t softSerialRxBytesWaiting(const serialPort_t
*instance
)
542 if ((instance
->mode
& MODE_RX
) == 0) {
546 softSerial_t
*s
= (softSerial_t
*)instance
;
548 return (s
->port
.rxBufferHead
- s
->port
.rxBufferTail
) & (s
->port
.rxBufferSize
- 1);
551 uint32_t softSerialTxBytesFree(const serialPort_t
*instance
)
553 if ((instance
->mode
& MODE_TX
) == 0) {
557 softSerial_t
*s
= (softSerial_t
*)instance
;
559 uint8_t bytesUsed
= (s
->port
.txBufferHead
- s
->port
.txBufferTail
) & (s
->port
.txBufferSize
- 1);
561 return (s
->port
.txBufferSize
- 1) - bytesUsed
;
564 uint8_t softSerialReadByte(serialPort_t
*instance
)
568 if ((instance
->mode
& MODE_RX
) == 0) {
572 if (softSerialRxBytesWaiting(instance
) == 0) {
576 ch
= instance
->rxBuffer
[instance
->rxBufferTail
];
577 instance
->rxBufferTail
= (instance
->rxBufferTail
+ 1) % instance
->rxBufferSize
;
581 void softSerialWriteByte(serialPort_t
*s
, uint8_t ch
)
583 if ((s
->mode
& MODE_TX
) == 0) {
587 s
->txBuffer
[s
->txBufferHead
] = ch
;
588 s
->txBufferHead
= (s
->txBufferHead
+ 1) % s
->txBufferSize
;
591 void softSerialSetBaudRate(serialPort_t
*s
, uint32_t baudRate
)
593 softSerial_t
*softSerial
= (softSerial_t
*)s
;
595 softSerial
->port
.baudRate
= baudRate
;
597 serialTimerConfigureTimebase(softSerial
->timerHardware
, baudRate
);
600 void softSerialSetMode(serialPort_t
*instance
, portMode_e mode
)
602 instance
->mode
= mode
;
605 bool isSoftSerialTransmitBufferEmpty(const serialPort_t
*instance
)
607 return instance
->txBufferHead
== instance
->txBufferTail
;
610 static const struct serialPortVTable softSerialVTable
= {
611 .serialWrite
= softSerialWriteByte
,
612 .serialTotalRxWaiting
= softSerialRxBytesWaiting
,
613 .serialTotalTxFree
= softSerialTxBytesFree
,
614 .serialRead
= softSerialReadByte
,
615 .serialSetBaudRate
= softSerialSetBaudRate
,
616 .isSerialTransmitBufferEmpty
= isSoftSerialTransmitBufferEmpty
,
617 .setMode
= softSerialSetMode
,
618 .setCtrlLineStateCb
= NULL
,
619 .setBaudRateCb
= NULL
,