Merge pull request #11198 from SteveCEvans/sce_rc2
[betaflight.git] / src / main / drivers / serial_softserial.c
blob152d00269d89de8b0628dad12cbd245608d93f7a
1 /*
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)
8 * any later version.
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
26 #include <stdbool.h>
27 #include <stdint.h>
29 #include "platform.h"
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
52 #else
53 #define MAX_SOFTSERIAL_PORTS 1
54 #endif
56 typedef enum {
57 TIMER_MODE_SINGLE,
58 TIMER_MODE_DUAL,
59 } timerMode_e;
61 #define ICPOLARITY_RISING true
62 #define ICPOLARITY_FALLING false
64 typedef struct softSerial_s {
65 serialPort_t port;
67 IO_t rxIO;
68 IO_t txIO;
70 const timerHardware_t *timerHardware;
71 #ifdef USE_HAL_DRIVER
72 const TIM_HandleTypeDef *timerHandle;
73 #endif
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;
80 uint8_t rxBitIndex;
81 uint8_t rxLastLeadingEdgeAtBitIndex;
82 uint8_t rxEdge;
83 uint8_t rxActive;
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;
99 } softSerial_t;
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) {
111 state = !state;
114 if (state) {
115 IOHi(softSerial->txIO);
116 } else {
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);
125 #else
126 TIM_CCxCmd(softSerial->timerHardware->tim, softSerial->timerHardware->channel, TIM_CCx_Enable);
127 #endif
130 static void serialInputPortActivate(softSerial_t *softSerial)
132 if (softSerial->port.options & SERIAL_INVERTED) {
133 #ifdef STM32F1
134 IOConfigGPIO(softSerial->rxIO, IOCFG_IPD);
135 #else
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);
138 #endif
139 } else {
140 #ifdef STM32F1
141 IOConfigGPIO(softSerial->rxIO, IOCFG_IPU);
142 #else
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);
145 #endif
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);
163 #else
164 TIM_CCxCmd(softSerial->timerHardware->tim, softSerial->timerHardware->channel, TIM_CCx_Disable);
165 #endif
167 IOConfigGPIO(softSerial->rxIO, IOCFG_IN_FLOATING);
168 softSerial->rxActive = false;
171 static void serialOutputPortActivate(softSerial_t *softSerial)
173 #ifdef STM32F1
174 IOConfigGPIO(softSerial->txIO, IOCFG_OUT_PP);
175 #else
176 if (softSerial->exTimerHardware)
177 IOConfigGPIOAF(softSerial->txIO, IOCFG_OUT_PP, softSerial->exTimerHardware->alternateFunction);
178 else
179 IOConfigGPIO(softSerial->txIO, IOCFG_OUT_PP);
180 #endif
183 static void serialOutputPortDeActivate(softSerial_t *softSerial)
185 #ifdef STM32F1
186 IOConfigGPIO(softSerial->txIO, IOCFG_IN_FLOATING);
187 #else
188 if (softSerial->exTimerHardware)
189 IOConfigGPIOAF(softSerial->txIO, IOCFG_IN_FLOATING, softSerial->exTimerHardware->alternateFunction);
190 else
191 IOConfigGPIO(softSerial->txIO, IOCFG_IN_FLOATING);
192 #endif
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;
206 do {
207 timerPeriod = clock / baud;
208 if (isTimerPeriodTooLarge(timerPeriod)) {
209 if (clock > 1) {
210 clock = clock / 2; // this is wrong - mhz stays the same ... This will double baudrate until ok (but minimum baudrate is < 1200)
211 } else {
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)) {
254 return NULL;
257 softSerial->timerHardware = timerTx;
258 softSerial->txIO = txIO;
259 softSerial->rxIO = txIO;
260 IOInit(txIO, OWNER_SERIAL_TX, RESOURCE_INDEX(portIndex + RESOURCE_SOFT_OFFSET));
261 } else {
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)) {
265 return NULL;
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) {
276 // Need a pin on TX
277 if (!tagTx)
278 return NULL;
280 softSerial->txIO = txIO;
282 if (!(mode & MODE_RX)) {
283 // TX Simplex, must have a timer
284 if (!timerTx)
285 return NULL;
286 softSerial->timerHardware = timerTx;
287 } else {
288 // Duplex
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);
331 } else {
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);
338 #endif
340 if (!(options & SERIAL_BIDIR)) {
341 serialOutputPortActivate(softSerial);
342 setTxSignal(softSerial, ENABLE);
345 serialInputPortActivate(softSerial);
347 return &softSerial->port;
352 * Serial Engine
355 void processTxState(softSerial_t *softSerial)
357 uint8_t mask;
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);
367 return;
370 // data to send
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.)
391 return;
395 if (softSerial->bitsLeftToTransmit) {
396 mask = softSerial->internalTxBuffer & 1;
397 softSerial->internalTxBuffer >>= 1;
399 setTxSignal(softSerial, mask);
400 softSerial->bitsLeftToTransmit--;
401 return;
404 softSerial->isTransmittingData = false;
407 enum {
408 TRAILING,
409 LEADING
412 void applyChangedBits(softSerial_t *softSerial)
414 if (softSerial->rxEdge == TRAILING) {
415 uint8_t bitToSet;
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) {
440 return;
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++;
448 return;
451 uint8_t rxByte = (softSerial->internalRxBuffer >> 1) & 0xFF;
453 if (softSerial->port.rxCallback) {
454 softSerial->port.rxCallback(rxByte, softSerial->port.rxCallbackData);
455 } else {
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) {
464 return;
467 softSerial->rxBitIndex++;
469 if (softSerial->rxBitIndex == RX_TOTAL_BITS - 1) {
470 applyChangedBits(softSerial);
471 return;
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)
487 UNUSED(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)
499 UNUSED(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) {
505 return;
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);
514 #else
515 TIM_SetCounter(self->timerHardware->tim, self->timerHardware->tim->ARR / 2);
516 #endif
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);
530 #endif
531 self->rxEdge = LEADING;
533 self->rxBitIndex = 0;
534 self->rxLastLeadingEdgeAtBitIndex = 0;
535 self->internalRxBuffer = 0;
536 self->isSearchingForStartBit = false;
537 return;
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);
549 } else {
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);
555 #endif
560 * Standard serial driver API
563 uint32_t softSerialRxBytesWaiting(const serialPort_t *instance)
565 if ((instance->mode & MODE_RX) == 0) {
566 return 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) {
577 return 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)
589 uint8_t ch;
591 if ((instance->mode & MODE_RX) == 0) {
592 return 0;
595 if (softSerialRxBytesWaiting(instance) == 0) {
596 return 0;
599 ch = instance->rxBuffer[instance->rxBufferTail];
600 instance->rxBufferTail = (instance->rxBufferTail + 1) % instance->rxBufferSize;
601 return ch;
604 void softSerialWriteByte(serialPort_t *s, uint8_t ch)
606 if ((s->mode & MODE_TX) == 0) {
607 return;
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,
643 .writeBuf = NULL,
644 .beginWrite = NULL,
645 .endWrite = NULL
648 #endif