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
) {
134 IOConfigGPIO(softSerial
->rxIO
, IOCFG_IPD
);
136 const uint8_t pinConfig
= (softSerial
->port
.options
& SERIAL_BIDIR_NOPULL
) ? IOCFG_AF_PP
: IOCFG_AF_PP_PD
;
137 IOConfigGPIOAF(softSerial
->rxIO
, pinConfig
, softSerial
->timerHardware
->alternateFunction
);
141 IOConfigGPIO(softSerial
->rxIO
, IOCFG_IPU
);
143 const uint8_t pinConfig
= (softSerial
->port
.options
& SERIAL_BIDIR_NOPULL
) ? IOCFG_AF_PP
: IOCFG_AF_PP_UP
;
144 IOConfigGPIOAF(softSerial
->rxIO
, pinConfig
, softSerial
->timerHardware
->alternateFunction
);
148 softSerial
->rxActive
= true;
149 softSerial
->isSearchingForStartBit
= true;
150 softSerial
->rxBitIndex
= 0;
152 // Enable input capture
154 serialEnableCC(softSerial
);
157 static void serialInputPortDeActivate(softSerial_t
*softSerial
)
159 // Disable input capture
161 #ifdef USE_HAL_DRIVER
162 TIM_CCxChannelCmd(softSerial
->timerHardware
->tim
, softSerial
->timerHardware
->channel
, TIM_CCx_DISABLE
);
164 TIM_CCxCmd(softSerial
->timerHardware
->tim
, softSerial
->timerHardware
->channel
, TIM_CCx_Disable
);
167 IOConfigGPIO(softSerial
->rxIO
, IOCFG_IN_FLOATING
);
168 softSerial
->rxActive
= false;
171 static void serialOutputPortActivate(softSerial_t
*softSerial
)
174 IOConfigGPIO(softSerial
->txIO
, IOCFG_OUT_PP
);
176 if (softSerial
->exTimerHardware
)
177 IOConfigGPIOAF(softSerial
->txIO
, IOCFG_OUT_PP
, softSerial
->exTimerHardware
->alternateFunction
);
179 IOConfigGPIO(softSerial
->txIO
, IOCFG_OUT_PP
);
183 static void serialOutputPortDeActivate(softSerial_t
*softSerial
)
186 IOConfigGPIO(softSerial
->txIO
, IOCFG_IN_FLOATING
);
188 if (softSerial
->exTimerHardware
)
189 IOConfigGPIOAF(softSerial
->txIO
, IOCFG_IN_FLOATING
, softSerial
->exTimerHardware
->alternateFunction
);
191 IOConfigGPIO(softSerial
->txIO
, IOCFG_IN_FLOATING
);
195 static bool isTimerPeriodTooLarge(uint32_t timerPeriod
)
197 return timerPeriod
> 0xFFFF;
200 static void serialTimerConfigureTimebase(const timerHardware_t
*timerHardwarePtr
, uint32_t baud
)
202 uint32_t baseClock
= timerClock(timerHardwarePtr
->tim
);
203 uint32_t clock
= baseClock
;
204 uint32_t timerPeriod
;
207 timerPeriod
= clock
/ baud
;
208 if (isTimerPeriodTooLarge(timerPeriod
)) {
210 clock
= clock
/ 2; // this is wrong - mhz stays the same ... This will double baudrate until ok (but minimum baudrate is < 1200)
212 // TODO unable to continue, unable to determine clock and timerPeriods for the given baud
216 } while (isTimerPeriodTooLarge(timerPeriod
));
218 timerConfigure(timerHardwarePtr
, timerPeriod
, baseClock
);
221 static void resetBuffers(softSerial_t
*softSerial
)
223 softSerial
->port
.rxBufferSize
= SOFTSERIAL_BUFFER_SIZE
;
224 softSerial
->port
.rxBuffer
= softSerial
->rxBuffer
;
225 softSerial
->port
.rxBufferTail
= 0;
226 softSerial
->port
.rxBufferHead
= 0;
228 softSerial
->port
.txBuffer
= softSerial
->txBuffer
;
229 softSerial
->port
.txBufferSize
= SOFTSERIAL_BUFFER_SIZE
;
230 softSerial
->port
.txBufferTail
= 0;
231 softSerial
->port
.txBufferHead
= 0;
234 serialPort_t
*openSoftSerial(softSerialPortIndex_e portIndex
, serialReceiveCallbackPtr rxCallback
, void *rxCallbackData
, uint32_t baud
, portMode_e mode
, portOptions_e options
)
236 softSerial_t
*softSerial
= &(softSerialPorts
[portIndex
]);
238 int pinCfgIndex
= portIndex
+ RESOURCE_SOFT_OFFSET
;
240 ioTag_t tagRx
= serialPinConfig()->ioTagRx
[pinCfgIndex
];
241 ioTag_t tagTx
= serialPinConfig()->ioTagTx
[pinCfgIndex
];
243 const timerHardware_t
*timerTx
= timerAllocate(tagTx
, OWNER_SERIAL_TX
, RESOURCE_INDEX(portIndex
+ RESOURCE_SOFT_OFFSET
));
244 const timerHardware_t
*timerRx
= (tagTx
== tagRx
) ? timerTx
: timerAllocate(tagRx
, OWNER_SERIAL_RX
, RESOURCE_INDEX(portIndex
+ RESOURCE_SOFT_OFFSET
));
246 IO_t rxIO
= IOGetByTag(tagRx
);
247 IO_t txIO
= IOGetByTag(tagTx
);
249 if (options
& SERIAL_BIDIR
) {
250 // If RX and TX pins are both assigned, we CAN use either with a timer.
251 // However, for consistency with hardware UARTs, we only use TX pin,
252 // and this pin must have a timer, and it should not be N-Channel.
253 if (!timerTx
|| (timerTx
->output
& TIMER_OUTPUT_N_CHANNEL
)) {
257 softSerial
->timerHardware
= timerTx
;
258 softSerial
->txIO
= txIO
;
259 softSerial
->rxIO
= txIO
;
260 IOInit(txIO
, OWNER_SERIAL_TX
, RESOURCE_INDEX(portIndex
+ RESOURCE_SOFT_OFFSET
));
262 if (mode
& MODE_RX
) {
263 // Need a pin & a timer on RX. Channel should not be N-Channel.
264 if (!timerRx
|| (timerRx
->output
& TIMER_OUTPUT_N_CHANNEL
)) {
268 softSerial
->rxIO
= rxIO
;
269 softSerial
->timerHardware
= timerRx
;
270 if (!((mode
& MODE_TX
) && rxIO
== txIO
)) {
271 IOInit(rxIO
, OWNER_SERIAL_RX
, RESOURCE_INDEX(portIndex
+ RESOURCE_SOFT_OFFSET
));
275 if (mode
& MODE_TX
) {
280 softSerial
->txIO
= txIO
;
282 if (!(mode
& MODE_RX
)) {
283 // TX Simplex, must have a timer
286 softSerial
->timerHardware
= timerTx
;
289 softSerial
->exTimerHardware
= timerTx
;
291 IOInit(txIO
, OWNER_SERIAL_TX
, RESOURCE_INDEX(portIndex
+ RESOURCE_SOFT_OFFSET
));
295 softSerial
->port
.vTable
= &softSerialVTable
;
296 softSerial
->port
.baudRate
= baud
;
297 softSerial
->port
.mode
= mode
;
298 softSerial
->port
.options
= options
;
299 softSerial
->port
.rxCallback
= rxCallback
;
300 softSerial
->port
.rxCallbackData
= rxCallbackData
;
302 resetBuffers(softSerial
);
304 softSerial
->softSerialPortIndex
= portIndex
;
306 softSerial
->transmissionErrors
= 0;
307 softSerial
->receiveErrors
= 0;
309 softSerial
->rxActive
= false;
310 softSerial
->isTransmittingData
= false;
312 // Configure master timer (on RX); time base and input capture
314 serialTimerConfigureTimebase(softSerial
->timerHardware
, baud
);
315 timerChConfigIC(softSerial
->timerHardware
, (options
& SERIAL_INVERTED
) ? ICPOLARITY_RISING
: ICPOLARITY_FALLING
, 0);
317 // Initialize callbacks
318 timerChCCHandlerInit(&softSerial
->edgeCb
, onSerialRxPinChange
);
319 timerChOvrHandlerInit(&softSerial
->overCb
, onSerialTimerOverflow
);
321 // Configure bit clock interrupt & handler.
322 // If we have an extra timer (on TX), it is initialized and configured
323 // for overflow interrupt.
324 // Receiver input capture is configured when input is activated.
326 if ((mode
& MODE_TX
) && softSerial
->exTimerHardware
&& softSerial
->exTimerHardware
->tim
!= softSerial
->timerHardware
->tim
) {
327 softSerial
->timerMode
= TIMER_MODE_DUAL
;
328 serialTimerConfigureTimebase(softSerial
->exTimerHardware
, baud
);
329 timerChConfigCallbacks(softSerial
->exTimerHardware
, NULL
, &softSerial
->overCb
);
330 timerChConfigCallbacks(softSerial
->timerHardware
, &softSerial
->edgeCb
, NULL
);
332 softSerial
->timerMode
= TIMER_MODE_SINGLE
;
333 timerChConfigCallbacks(softSerial
->timerHardware
, &softSerial
->edgeCb
, &softSerial
->overCb
);
336 #ifdef USE_HAL_DRIVER
337 softSerial
->timerHandle
= timerFindTimerHandle(softSerial
->timerHardware
->tim
);
340 if (!(options
& SERIAL_BIDIR
)) {
341 serialOutputPortActivate(softSerial
);
342 setTxSignal(softSerial
, ENABLE
);
345 serialInputPortActivate(softSerial
);
347 return &softSerial
->port
;
355 void processTxState(softSerial_t
*softSerial
)
359 if (!softSerial
->isTransmittingData
) {
360 if (isSoftSerialTransmitBufferEmpty((serialPort_t
*)softSerial
)) {
361 // Transmit buffer empty.
362 // Start listening if not already in if half-duplex
363 if (!softSerial
->rxActive
&& softSerial
->port
.options
& SERIAL_BIDIR
) {
364 serialOutputPortDeActivate(softSerial
);
365 serialInputPortActivate(softSerial
);
371 uint8_t byteToSend
= softSerial
->port
.txBuffer
[softSerial
->port
.txBufferTail
++];
372 if (softSerial
->port
.txBufferTail
>= softSerial
->port
.txBufferSize
) {
373 softSerial
->port
.txBufferTail
= 0;
376 // build internal buffer, MSB = Stop Bit (1) + data bits (MSB to LSB) + start bit(0) LSB
377 softSerial
->internalTxBuffer
= (1 << (TX_TOTAL_BITS
- 1)) | (byteToSend
<< 1);
378 softSerial
->bitsLeftToTransmit
= TX_TOTAL_BITS
;
379 softSerial
->isTransmittingData
= true;
381 if (softSerial
->rxActive
&& (softSerial
->port
.options
& SERIAL_BIDIR
)) {
382 // Half-duplex: Deactivate receiver, activate transmitter
383 serialInputPortDeActivate(softSerial
);
384 serialOutputPortActivate(softSerial
);
386 // Start sending on next bit timing, as port manipulation takes time,
387 // and continuing here may cause bit period to decrease causing sampling errors
388 // at the receiver under high rates.
389 // Note that there will be (little less than) 1-bit delay; take it as "turn around time".
390 // XXX We may be able to reload counter and continue. (Future work.)
395 if (softSerial
->bitsLeftToTransmit
) {
396 mask
= softSerial
->internalTxBuffer
& 1;
397 softSerial
->internalTxBuffer
>>= 1;
399 setTxSignal(softSerial
, mask
);
400 softSerial
->bitsLeftToTransmit
--;
404 softSerial
->isTransmittingData
= false;
412 void applyChangedBits(softSerial_t
*softSerial
)
414 if (softSerial
->rxEdge
== TRAILING
) {
416 for (bitToSet
= softSerial
->rxLastLeadingEdgeAtBitIndex
; bitToSet
< softSerial
->rxBitIndex
; bitToSet
++) {
417 softSerial
->internalRxBuffer
|= 1 << bitToSet
;
422 void prepareForNextRxByte(softSerial_t
*softSerial
)
424 // prepare for next byte
425 softSerial
->rxBitIndex
= 0;
426 softSerial
->isSearchingForStartBit
= true;
427 if (softSerial
->rxEdge
== LEADING
) {
428 softSerial
->rxEdge
= TRAILING
;
429 timerChConfigIC(softSerial
->timerHardware
, (softSerial
->port
.options
& SERIAL_INVERTED
) ? ICPOLARITY_RISING
: ICPOLARITY_FALLING
, 0);
430 serialEnableCC(softSerial
);
434 #define STOP_BIT_MASK (1 << 0)
435 #define START_BIT_MASK (1 << (RX_TOTAL_BITS - 1))
437 void extractAndStoreRxByte(softSerial_t
*softSerial
)
439 if ((softSerial
->port
.mode
& MODE_RX
) == 0) {
443 uint8_t haveStartBit
= (softSerial
->internalRxBuffer
& START_BIT_MASK
) == 0;
444 uint8_t haveStopBit
= (softSerial
->internalRxBuffer
& STOP_BIT_MASK
) == 1;
446 if (!haveStartBit
|| !haveStopBit
) {
447 softSerial
->receiveErrors
++;
451 uint8_t rxByte
= (softSerial
->internalRxBuffer
>> 1) & 0xFF;
453 if (softSerial
->port
.rxCallback
) {
454 softSerial
->port
.rxCallback(rxByte
, softSerial
->port
.rxCallbackData
);
456 softSerial
->port
.rxBuffer
[softSerial
->port
.rxBufferHead
] = rxByte
;
457 softSerial
->port
.rxBufferHead
= (softSerial
->port
.rxBufferHead
+ 1) % softSerial
->port
.rxBufferSize
;
461 void processRxState(softSerial_t
*softSerial
)
463 if (softSerial
->isSearchingForStartBit
) {
467 softSerial
->rxBitIndex
++;
469 if (softSerial
->rxBitIndex
== RX_TOTAL_BITS
- 1) {
470 applyChangedBits(softSerial
);
474 if (softSerial
->rxBitIndex
== RX_TOTAL_BITS
) {
476 if (softSerial
->rxEdge
== TRAILING
) {
477 softSerial
->internalRxBuffer
|= STOP_BIT_MASK
;
480 extractAndStoreRxByte(softSerial
);
481 prepareForNextRxByte(softSerial
);
485 void onSerialTimerOverflow(timerOvrHandlerRec_t
*cbRec
, captureCompare_t capture
)
488 softSerial_t
*self
= container_of(cbRec
, softSerial_t
, overCb
);
490 if (self
->port
.mode
& MODE_TX
)
491 processTxState(self
);
493 if (self
->port
.mode
& MODE_RX
)
494 processRxState(self
);
497 void onSerialRxPinChange(timerCCHandlerRec_t
*cbRec
, captureCompare_t capture
)
501 softSerial_t
*self
= container_of(cbRec
, softSerial_t
, edgeCb
);
502 bool inverted
= self
->port
.options
& SERIAL_INVERTED
;
504 if ((self
->port
.mode
& MODE_RX
) == 0) {
508 if (self
->isSearchingForStartBit
) {
509 // Synchronize the bit timing so that it will interrupt at the center
510 // of the bit period.
512 #ifdef USE_HAL_DRIVER
513 __HAL_TIM_SetCounter(self
->timerHandle
, __HAL_TIM_GetAutoreload(self
->timerHandle
) / 2);
515 TIM_SetCounter(self
->timerHardware
->tim
, self
->timerHardware
->tim
->ARR
/ 2);
518 // For a mono-timer full duplex configuration, this may clobber the
519 // transmission because the next callback to the onSerialTimerOverflow
520 // will happen too early causing transmission errors.
521 // For a dual-timer configuration, there is no problem.
523 if ((self
->timerMode
!= TIMER_MODE_DUAL
) && self
->isTransmittingData
) {
524 self
->transmissionErrors
++;
527 timerChConfigIC(self
->timerHardware
, inverted
? ICPOLARITY_FALLING
: ICPOLARITY_RISING
, 0);
528 #if defined(STM32F7) || defined(STM32H7) || defined(STM32G4)
529 serialEnableCC(self
);
531 self
->rxEdge
= LEADING
;
533 self
->rxBitIndex
= 0;
534 self
->rxLastLeadingEdgeAtBitIndex
= 0;
535 self
->internalRxBuffer
= 0;
536 self
->isSearchingForStartBit
= false;
540 if (self
->rxEdge
== LEADING
) {
541 self
->rxLastLeadingEdgeAtBitIndex
= self
->rxBitIndex
;
544 applyChangedBits(self
);
546 if (self
->rxEdge
== TRAILING
) {
547 self
->rxEdge
= LEADING
;
548 timerChConfigIC(self
->timerHardware
, inverted
? ICPOLARITY_FALLING
: ICPOLARITY_RISING
, 0);
550 self
->rxEdge
= TRAILING
;
551 timerChConfigIC(self
->timerHardware
, inverted
? ICPOLARITY_RISING
: ICPOLARITY_FALLING
, 0);
553 #if defined(STM32F7) || defined(STM32H7) || defined(STM32G4)
554 serialEnableCC(self
);
560 * Standard serial driver API
563 uint32_t softSerialRxBytesWaiting(const serialPort_t
*instance
)
565 if ((instance
->mode
& MODE_RX
) == 0) {
569 softSerial_t
*s
= (softSerial_t
*)instance
;
571 return (s
->port
.rxBufferHead
- s
->port
.rxBufferTail
) & (s
->port
.rxBufferSize
- 1);
574 uint32_t softSerialTxBytesFree(const serialPort_t
*instance
)
576 if ((instance
->mode
& MODE_TX
) == 0) {
580 softSerial_t
*s
= (softSerial_t
*)instance
;
582 uint8_t bytesUsed
= (s
->port
.txBufferHead
- s
->port
.txBufferTail
) & (s
->port
.txBufferSize
- 1);
584 return (s
->port
.txBufferSize
- 1) - bytesUsed
;
587 uint8_t softSerialReadByte(serialPort_t
*instance
)
591 if ((instance
->mode
& MODE_RX
) == 0) {
595 if (softSerialRxBytesWaiting(instance
) == 0) {
599 ch
= instance
->rxBuffer
[instance
->rxBufferTail
];
600 instance
->rxBufferTail
= (instance
->rxBufferTail
+ 1) % instance
->rxBufferSize
;
604 void softSerialWriteByte(serialPort_t
*s
, uint8_t ch
)
606 if ((s
->mode
& MODE_TX
) == 0) {
610 s
->txBuffer
[s
->txBufferHead
] = ch
;
611 s
->txBufferHead
= (s
->txBufferHead
+ 1) % s
->txBufferSize
;
614 void softSerialSetBaudRate(serialPort_t
*s
, uint32_t baudRate
)
616 softSerial_t
*softSerial
= (softSerial_t
*)s
;
618 softSerial
->port
.baudRate
= baudRate
;
620 serialTimerConfigureTimebase(softSerial
->timerHardware
, baudRate
);
623 void softSerialSetMode(serialPort_t
*instance
, portMode_e mode
)
625 instance
->mode
= mode
;
628 bool isSoftSerialTransmitBufferEmpty(const serialPort_t
*instance
)
630 return instance
->txBufferHead
== instance
->txBufferTail
;
633 static const struct serialPortVTable softSerialVTable
= {
634 .serialWrite
= softSerialWriteByte
,
635 .serialTotalRxWaiting
= softSerialRxBytesWaiting
,
636 .serialTotalTxFree
= softSerialTxBytesFree
,
637 .serialRead
= softSerialReadByte
,
638 .serialSetBaudRate
= softSerialSetBaudRate
,
639 .isSerialTransmitBufferEmpty
= isSoftSerialTransmitBufferEmpty
,
640 .setMode
= softSerialSetMode
,
641 .setCtrlLineStateCb
= NULL
,
642 .setBaudRateCb
= NULL
,