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_SOFTSERIAL1) || defined(USE_SOFTSERIAL2)
33 #include "build/build_config.h"
34 #include "build/atomic.h"
36 #include "build/debug.h"
38 #include "common/utils.h"
40 #include "drivers/nvic.h"
41 #include "drivers/io.h"
42 #include "drivers/serial.h"
43 #include "drivers/timer.h"
45 #include "serial_softserial.h"
47 #define RX_TOTAL_BITS 10
48 #define TX_TOTAL_BITS 10
50 #if defined(USE_SOFTSERIAL1) && defined(USE_SOFTSERIAL2)
51 #define MAX_SOFTSERIAL_PORTS 2
53 #define MAX_SOFTSERIAL_PORTS 1
61 #define ICPOLARITY_RISING true
62 #define ICPOLARITY_FALLING false
64 typedef struct softSerial_s
{
70 const timerHardware_t
*timerHardware
;
72 const TIM_HandleTypeDef
*timerHandle
;
74 const timerHardware_t
*exTimerHardware
;
76 volatile uint8_t rxBuffer
[SOFTSERIAL_BUFFER_SIZE
];
77 volatile uint8_t txBuffer
[SOFTSERIAL_BUFFER_SIZE
];
79 uint8_t isSearchingForStartBit
;
81 uint8_t rxLastLeadingEdgeAtBitIndex
;
85 uint8_t isTransmittingData
;
86 int8_t bitsLeftToTransmit
;
88 uint16_t internalTxBuffer
; // includes start and stop bits
89 uint16_t internalRxBuffer
; // includes start and stop bits
91 uint16_t transmissionErrors
;
92 uint16_t receiveErrors
;
94 uint8_t softSerialPortIndex
;
95 timerMode_e timerMode
;
97 timerOvrHandlerRec_t overCb
;
98 timerCCHandlerRec_t edgeCb
;
101 static const struct serialPortVTable softSerialVTable
; // Forward
103 static softSerial_t softSerialPorts
[MAX_SOFTSERIAL_PORTS
];
105 void onSerialTimerOverflow(timerOvrHandlerRec_t
*cbRec
, captureCompare_t capture
);
106 void onSerialRxPinChange(timerCCHandlerRec_t
*cbRec
, captureCompare_t capture
);
108 static void setTxSignal(softSerial_t
*softSerial
, uint8_t state
)
110 if (softSerial
->port
.options
& SERIAL_INVERTED
) {
115 IOHi(softSerial
->txIO
);
117 IOLo(softSerial
->txIO
);
121 static void serialEnableCC(softSerial_t
*softSerial
)
123 #ifdef USE_HAL_DRIVER
124 TIM_CCxChannelCmd(softSerial
->timerHardware
->tim
, softSerial
->timerHardware
->channel
, TIM_CCx_ENABLE
);
126 TIM_CCxCmd(softSerial
->timerHardware
->tim
, softSerial
->timerHardware
->channel
, TIM_CCx_Enable
);
130 static void serialInputPortActivate(softSerial_t
*softSerial
)
132 if (softSerial
->port
.options
& SERIAL_INVERTED
) {
133 const uint8_t pinConfig
= (softSerial
->port
.options
& SERIAL_BIDIR_NOPULL
) ? IOCFG_AF_PP
: IOCFG_AF_PP_PD
;
134 IOConfigGPIOAF(softSerial
->rxIO
, pinConfig
, softSerial
->timerHardware
->alternateFunction
);
136 const uint8_t pinConfig
= (softSerial
->port
.options
& SERIAL_BIDIR_NOPULL
) ? IOCFG_AF_PP
: IOCFG_AF_PP_UP
;
137 IOConfigGPIOAF(softSerial
->rxIO
, pinConfig
, softSerial
->timerHardware
->alternateFunction
);
140 softSerial
->rxActive
= true;
141 softSerial
->isSearchingForStartBit
= true;
142 softSerial
->rxBitIndex
= 0;
144 // Enable input capture
146 serialEnableCC(softSerial
);
149 static void serialInputPortDeActivate(softSerial_t
*softSerial
)
151 // Disable input capture
153 #ifdef USE_HAL_DRIVER
154 TIM_CCxChannelCmd(softSerial
->timerHardware
->tim
, softSerial
->timerHardware
->channel
, TIM_CCx_DISABLE
);
156 TIM_CCxCmd(softSerial
->timerHardware
->tim
, softSerial
->timerHardware
->channel
, TIM_CCx_Disable
);
159 IOConfigGPIO(softSerial
->rxIO
, IOCFG_IN_FLOATING
);
160 softSerial
->rxActive
= false;
163 static void serialOutputPortActivate(softSerial_t
*softSerial
)
165 if (softSerial
->exTimerHardware
)
166 IOConfigGPIOAF(softSerial
->txIO
, IOCFG_OUT_PP
, softSerial
->exTimerHardware
->alternateFunction
);
168 IOConfigGPIO(softSerial
->txIO
, IOCFG_OUT_PP
);
171 static void serialOutputPortDeActivate(softSerial_t
*softSerial
)
173 if (softSerial
->exTimerHardware
)
174 IOConfigGPIOAF(softSerial
->txIO
, IOCFG_IN_FLOATING
, softSerial
->exTimerHardware
->alternateFunction
);
176 IOConfigGPIO(softSerial
->txIO
, IOCFG_IN_FLOATING
);
179 static bool isTimerPeriodTooLarge(uint32_t timerPeriod
)
181 return timerPeriod
> 0xFFFF;
184 static void serialTimerConfigureTimebase(const timerHardware_t
*timerHardwarePtr
, uint32_t baud
)
186 uint32_t baseClock
= timerClock(timerHardwarePtr
->tim
);
187 uint32_t clock
= baseClock
;
188 uint32_t timerPeriod
;
191 timerPeriod
= clock
/ baud
;
192 if (isTimerPeriodTooLarge(timerPeriod
)) {
194 clock
= clock
/ 2; // this is wrong - mhz stays the same ... This will double baudrate until ok (but minimum baudrate is < 1200)
196 // TODO unable to continue, unable to determine clock and timerPeriods for the given baud
200 } while (isTimerPeriodTooLarge(timerPeriod
));
202 timerConfigure(timerHardwarePtr
, timerPeriod
, baseClock
);
205 static void resetBuffers(softSerial_t
*softSerial
)
207 softSerial
->port
.rxBufferSize
= SOFTSERIAL_BUFFER_SIZE
;
208 softSerial
->port
.rxBuffer
= softSerial
->rxBuffer
;
209 softSerial
->port
.rxBufferTail
= 0;
210 softSerial
->port
.rxBufferHead
= 0;
212 softSerial
->port
.txBuffer
= softSerial
->txBuffer
;
213 softSerial
->port
.txBufferSize
= SOFTSERIAL_BUFFER_SIZE
;
214 softSerial
->port
.txBufferTail
= 0;
215 softSerial
->port
.txBufferHead
= 0;
218 serialPort_t
*openSoftSerial(softSerialPortIndex_e portIndex
, serialReceiveCallbackPtr rxCallback
, void *rxCallbackData
, uint32_t baud
, portMode_e mode
, portOptions_e options
)
220 softSerial_t
*softSerial
= &(softSerialPorts
[portIndex
]);
222 int pinCfgIndex
= portIndex
+ RESOURCE_SOFT_OFFSET
;
224 ioTag_t tagRx
= serialPinConfig()->ioTagRx
[pinCfgIndex
];
225 ioTag_t tagTx
= serialPinConfig()->ioTagTx
[pinCfgIndex
];
227 const timerHardware_t
*timerTx
= timerAllocate(tagTx
, OWNER_SERIAL_TX
, RESOURCE_INDEX(portIndex
+ RESOURCE_SOFT_OFFSET
));
228 const timerHardware_t
*timerRx
= (tagTx
== tagRx
) ? timerTx
: timerAllocate(tagRx
, OWNER_SERIAL_RX
, RESOURCE_INDEX(portIndex
+ RESOURCE_SOFT_OFFSET
));
230 IO_t rxIO
= IOGetByTag(tagRx
);
231 IO_t txIO
= IOGetByTag(tagTx
);
233 if (options
& SERIAL_BIDIR
) {
234 // If RX and TX pins are both assigned, we CAN use either with a timer.
235 // However, for consistency with hardware UARTs, we only use TX pin,
236 // and this pin must have a timer, and it should not be N-Channel.
237 if (!timerTx
|| (timerTx
->output
& TIMER_OUTPUT_N_CHANNEL
)) {
241 softSerial
->timerHardware
= timerTx
;
242 softSerial
->txIO
= txIO
;
243 softSerial
->rxIO
= txIO
;
244 IOInit(txIO
, OWNER_SERIAL_TX
, RESOURCE_INDEX(portIndex
+ RESOURCE_SOFT_OFFSET
));
246 if (mode
& MODE_RX
) {
247 // Need a pin & a timer on RX. Channel should not be N-Channel.
248 if (!timerRx
|| (timerRx
->output
& TIMER_OUTPUT_N_CHANNEL
)) {
252 softSerial
->rxIO
= rxIO
;
253 softSerial
->timerHardware
= timerRx
;
254 if (!((mode
& MODE_TX
) && rxIO
== txIO
)) {
255 IOInit(rxIO
, OWNER_SERIAL_RX
, RESOURCE_INDEX(portIndex
+ RESOURCE_SOFT_OFFSET
));
259 if (mode
& MODE_TX
) {
264 softSerial
->txIO
= txIO
;
266 if (!(mode
& MODE_RX
)) {
267 // TX Simplex, must have a timer
270 softSerial
->timerHardware
= timerTx
;
273 softSerial
->exTimerHardware
= timerTx
;
275 IOInit(txIO
, OWNER_SERIAL_TX
, RESOURCE_INDEX(portIndex
+ RESOURCE_SOFT_OFFSET
));
279 softSerial
->port
.vTable
= &softSerialVTable
;
280 softSerial
->port
.baudRate
= baud
;
281 softSerial
->port
.mode
= mode
;
282 softSerial
->port
.options
= options
;
283 softSerial
->port
.rxCallback
= rxCallback
;
284 softSerial
->port
.rxCallbackData
= rxCallbackData
;
286 resetBuffers(softSerial
);
288 softSerial
->softSerialPortIndex
= portIndex
;
290 softSerial
->transmissionErrors
= 0;
291 softSerial
->receiveErrors
= 0;
293 softSerial
->rxActive
= false;
294 softSerial
->isTransmittingData
= false;
296 // Configure master timer (on RX); time base and input capture
298 serialTimerConfigureTimebase(softSerial
->timerHardware
, baud
);
299 timerChConfigIC(softSerial
->timerHardware
, (options
& SERIAL_INVERTED
) ? ICPOLARITY_RISING
: ICPOLARITY_FALLING
, 0);
301 // Initialize callbacks
302 timerChCCHandlerInit(&softSerial
->edgeCb
, onSerialRxPinChange
);
303 timerChOvrHandlerInit(&softSerial
->overCb
, onSerialTimerOverflow
);
305 // Configure bit clock interrupt & handler.
306 // If we have an extra timer (on TX), it is initialized and configured
307 // for overflow interrupt.
308 // Receiver input capture is configured when input is activated.
310 if ((mode
& MODE_TX
) && softSerial
->exTimerHardware
&& softSerial
->exTimerHardware
->tim
!= softSerial
->timerHardware
->tim
) {
311 softSerial
->timerMode
= TIMER_MODE_DUAL
;
312 serialTimerConfigureTimebase(softSerial
->exTimerHardware
, baud
);
313 timerChConfigCallbacks(softSerial
->exTimerHardware
, NULL
, &softSerial
->overCb
);
314 timerChConfigCallbacks(softSerial
->timerHardware
, &softSerial
->edgeCb
, NULL
);
316 softSerial
->timerMode
= TIMER_MODE_SINGLE
;
317 timerChConfigCallbacks(softSerial
->timerHardware
, &softSerial
->edgeCb
, &softSerial
->overCb
);
320 #ifdef USE_HAL_DRIVER
321 softSerial
->timerHandle
= timerFindTimerHandle(softSerial
->timerHardware
->tim
);
324 if (!(options
& SERIAL_BIDIR
)) {
325 serialOutputPortActivate(softSerial
);
326 setTxSignal(softSerial
, ENABLE
);
329 serialInputPortActivate(softSerial
);
331 return &softSerial
->port
;
339 void processTxState(softSerial_t
*softSerial
)
343 if (!softSerial
->isTransmittingData
) {
344 if (isSoftSerialTransmitBufferEmpty((serialPort_t
*)softSerial
)) {
345 // Transmit buffer empty.
346 // Start listening if not already in if half-duplex
347 if (!softSerial
->rxActive
&& softSerial
->port
.options
& SERIAL_BIDIR
) {
348 serialOutputPortDeActivate(softSerial
);
349 serialInputPortActivate(softSerial
);
355 uint8_t byteToSend
= softSerial
->port
.txBuffer
[softSerial
->port
.txBufferTail
++];
356 if (softSerial
->port
.txBufferTail
>= softSerial
->port
.txBufferSize
) {
357 softSerial
->port
.txBufferTail
= 0;
360 // build internal buffer, MSB = Stop Bit (1) + data bits (MSB to LSB) + start bit(0) LSB
361 softSerial
->internalTxBuffer
= (1 << (TX_TOTAL_BITS
- 1)) | (byteToSend
<< 1);
362 softSerial
->bitsLeftToTransmit
= TX_TOTAL_BITS
;
363 softSerial
->isTransmittingData
= true;
365 if (softSerial
->rxActive
&& (softSerial
->port
.options
& SERIAL_BIDIR
)) {
366 // Half-duplex: Deactivate receiver, activate transmitter
367 serialInputPortDeActivate(softSerial
);
368 serialOutputPortActivate(softSerial
);
370 // Start sending on next bit timing, as port manipulation takes time,
371 // and continuing here may cause bit period to decrease causing sampling errors
372 // at the receiver under high rates.
373 // Note that there will be (little less than) 1-bit delay; take it as "turn around time".
374 // XXX We may be able to reload counter and continue. (Future work.)
379 if (softSerial
->bitsLeftToTransmit
) {
380 mask
= softSerial
->internalTxBuffer
& 1;
381 softSerial
->internalTxBuffer
>>= 1;
383 setTxSignal(softSerial
, mask
);
384 softSerial
->bitsLeftToTransmit
--;
388 softSerial
->isTransmittingData
= false;
396 void applyChangedBits(softSerial_t
*softSerial
)
398 if (softSerial
->rxEdge
== TRAILING
) {
400 for (bitToSet
= softSerial
->rxLastLeadingEdgeAtBitIndex
; bitToSet
< softSerial
->rxBitIndex
; bitToSet
++) {
401 softSerial
->internalRxBuffer
|= 1 << bitToSet
;
406 void prepareForNextRxByte(softSerial_t
*softSerial
)
408 // prepare for next byte
409 softSerial
->rxBitIndex
= 0;
410 softSerial
->isSearchingForStartBit
= true;
411 if (softSerial
->rxEdge
== LEADING
) {
412 softSerial
->rxEdge
= TRAILING
;
413 timerChConfigIC(softSerial
->timerHardware
, (softSerial
->port
.options
& SERIAL_INVERTED
) ? ICPOLARITY_RISING
: ICPOLARITY_FALLING
, 0);
414 serialEnableCC(softSerial
);
418 #define STOP_BIT_MASK (1 << 0)
419 #define START_BIT_MASK (1 << (RX_TOTAL_BITS - 1))
421 void extractAndStoreRxByte(softSerial_t
*softSerial
)
423 if ((softSerial
->port
.mode
& MODE_RX
) == 0) {
427 uint8_t haveStartBit
= (softSerial
->internalRxBuffer
& START_BIT_MASK
) == 0;
428 uint8_t haveStopBit
= (softSerial
->internalRxBuffer
& STOP_BIT_MASK
) == 1;
430 if (!haveStartBit
|| !haveStopBit
) {
431 softSerial
->receiveErrors
++;
435 uint8_t rxByte
= (softSerial
->internalRxBuffer
>> 1) & 0xFF;
437 if (softSerial
->port
.rxCallback
) {
438 softSerial
->port
.rxCallback(rxByte
, softSerial
->port
.rxCallbackData
);
440 softSerial
->port
.rxBuffer
[softSerial
->port
.rxBufferHead
] = rxByte
;
441 softSerial
->port
.rxBufferHead
= (softSerial
->port
.rxBufferHead
+ 1) % softSerial
->port
.rxBufferSize
;
445 void processRxState(softSerial_t
*softSerial
)
447 if (softSerial
->isSearchingForStartBit
) {
451 softSerial
->rxBitIndex
++;
453 if (softSerial
->rxBitIndex
== RX_TOTAL_BITS
- 1) {
454 applyChangedBits(softSerial
);
458 if (softSerial
->rxBitIndex
== RX_TOTAL_BITS
) {
460 if (softSerial
->rxEdge
== TRAILING
) {
461 softSerial
->internalRxBuffer
|= STOP_BIT_MASK
;
464 extractAndStoreRxByte(softSerial
);
465 prepareForNextRxByte(softSerial
);
469 void onSerialTimerOverflow(timerOvrHandlerRec_t
*cbRec
, captureCompare_t capture
)
472 softSerial_t
*self
= container_of(cbRec
, softSerial_t
, overCb
);
474 if (self
->port
.mode
& MODE_TX
)
475 processTxState(self
);
477 if (self
->port
.mode
& MODE_RX
)
478 processRxState(self
);
481 void onSerialRxPinChange(timerCCHandlerRec_t
*cbRec
, captureCompare_t capture
)
485 softSerial_t
*self
= container_of(cbRec
, softSerial_t
, edgeCb
);
486 bool inverted
= self
->port
.options
& SERIAL_INVERTED
;
488 if ((self
->port
.mode
& MODE_RX
) == 0) {
492 if (self
->isSearchingForStartBit
) {
493 // Synchronize the bit timing so that it will interrupt at the center
494 // of the bit period.
496 #ifdef USE_HAL_DRIVER
497 __HAL_TIM_SetCounter(self
->timerHandle
, __HAL_TIM_GetAutoreload(self
->timerHandle
) / 2);
499 TIM_SetCounter(self
->timerHardware
->tim
, self
->timerHardware
->tim
->ARR
/ 2);
502 // For a mono-timer full duplex configuration, this may clobber the
503 // transmission because the next callback to the onSerialTimerOverflow
504 // will happen too early causing transmission errors.
505 // For a dual-timer configuration, there is no problem.
507 if ((self
->timerMode
!= TIMER_MODE_DUAL
) && self
->isTransmittingData
) {
508 self
->transmissionErrors
++;
511 timerChConfigIC(self
->timerHardware
, inverted
? ICPOLARITY_FALLING
: ICPOLARITY_RISING
, 0);
512 #if defined(STM32F7) || defined(STM32H7) || defined(STM32G4)
513 serialEnableCC(self
);
515 self
->rxEdge
= LEADING
;
517 self
->rxBitIndex
= 0;
518 self
->rxLastLeadingEdgeAtBitIndex
= 0;
519 self
->internalRxBuffer
= 0;
520 self
->isSearchingForStartBit
= false;
524 if (self
->rxEdge
== LEADING
) {
525 self
->rxLastLeadingEdgeAtBitIndex
= self
->rxBitIndex
;
528 applyChangedBits(self
);
530 if (self
->rxEdge
== TRAILING
) {
531 self
->rxEdge
= LEADING
;
532 timerChConfigIC(self
->timerHardware
, inverted
? ICPOLARITY_FALLING
: ICPOLARITY_RISING
, 0);
534 self
->rxEdge
= TRAILING
;
535 timerChConfigIC(self
->timerHardware
, inverted
? ICPOLARITY_RISING
: ICPOLARITY_FALLING
, 0);
537 #if defined(STM32F7) || defined(STM32H7) || defined(STM32G4)
538 serialEnableCC(self
);
544 * Standard serial driver API
547 uint32_t softSerialRxBytesWaiting(const serialPort_t
*instance
)
549 if ((instance
->mode
& MODE_RX
) == 0) {
553 softSerial_t
*s
= (softSerial_t
*)instance
;
555 return (s
->port
.rxBufferHead
- s
->port
.rxBufferTail
) & (s
->port
.rxBufferSize
- 1);
558 uint32_t softSerialTxBytesFree(const serialPort_t
*instance
)
560 if ((instance
->mode
& MODE_TX
) == 0) {
564 softSerial_t
*s
= (softSerial_t
*)instance
;
566 uint8_t bytesUsed
= (s
->port
.txBufferHead
- s
->port
.txBufferTail
) & (s
->port
.txBufferSize
- 1);
568 return (s
->port
.txBufferSize
- 1) - bytesUsed
;
571 uint8_t softSerialReadByte(serialPort_t
*instance
)
575 if ((instance
->mode
& MODE_RX
) == 0) {
579 if (softSerialRxBytesWaiting(instance
) == 0) {
583 ch
= instance
->rxBuffer
[instance
->rxBufferTail
];
584 instance
->rxBufferTail
= (instance
->rxBufferTail
+ 1) % instance
->rxBufferSize
;
588 void softSerialWriteByte(serialPort_t
*s
, uint8_t ch
)
590 if ((s
->mode
& MODE_TX
) == 0) {
594 s
->txBuffer
[s
->txBufferHead
] = ch
;
595 s
->txBufferHead
= (s
->txBufferHead
+ 1) % s
->txBufferSize
;
598 void softSerialSetBaudRate(serialPort_t
*s
, uint32_t baudRate
)
600 softSerial_t
*softSerial
= (softSerial_t
*)s
;
602 softSerial
->port
.baudRate
= baudRate
;
604 serialTimerConfigureTimebase(softSerial
->timerHardware
, baudRate
);
607 void softSerialSetMode(serialPort_t
*instance
, portMode_e mode
)
609 instance
->mode
= mode
;
612 bool isSoftSerialTransmitBufferEmpty(const serialPort_t
*instance
)
614 return instance
->txBufferHead
== instance
->txBufferTail
;
617 static const struct serialPortVTable softSerialVTable
= {
618 .serialWrite
= softSerialWriteByte
,
619 .serialTotalRxWaiting
= softSerialRxBytesWaiting
,
620 .serialTotalTxFree
= softSerialTxBytesFree
,
621 .serialRead
= softSerialReadByte
,
622 .serialSetBaudRate
= softSerialSetBaudRate
,
623 .isSerialTransmitBufferEmpty
= isSoftSerialTransmitBufferEmpty
,
624 .setMode
= softSerialSetMode
,
625 .setCtrlLineStateCb
= NULL
,
626 .setBaudRateCb
= NULL
,