Add Winbond W25Q512J support (#14036)
[betaflight.git] / src / main / drivers / serial_softserial.c
blob010c1ee1e75ab5517e92ddb368ebaf6e77345f27
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_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
53 typedef enum {
54 TIMER_MODE_SINGLE,
55 TIMER_MODE_DUAL,
56 } timerMode_e;
58 #define ICPOLARITY_RISING true
59 #define ICPOLARITY_FALLING false
61 typedef struct softSerial_s {
62 serialPort_t port;
64 IO_t rxIO;
65 IO_t txIO;
67 const timerHardware_t *timerHardware;
68 #ifdef USE_HAL_DRIVER
69 const TIM_HandleTypeDef *timerHandle;
70 #endif
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;
77 uint8_t rxBitIndex;
78 uint8_t rxLastLeadingEdgeAtBitIndex;
79 uint8_t rxEdge;
80 uint8_t rxActive;
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;
95 } softSerial_t;
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);
114 #else
115 TIM_CCxCmd(softSerial->timerHardware->tim, softSerial->timerHardware->channel, TIM_CCx_Enable);
116 #endif
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);
142 #else
143 TIM_CCxCmd(softSerial->timerHardware->tim, softSerial->timerHardware->channel, TIM_CCx_Disable);
144 #endif
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);
153 } else {
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);
163 } else {
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];
203 return NULL;
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);
209 if (!softSerial) {
210 return NULL;
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)) {
233 return NULL;
236 softSerial->timerHardware = timerTx;
237 softSerial->txIO = txIO;
238 softSerial->rxIO = txIO;
239 IOInit(txIO, ownerTxRx, ownerIndex);
240 } else {
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)) {
244 return NULL;
247 softSerial->rxIO = rxIO;
248 softSerial->timerHardware = timerRx;
249 if (!((mode & MODE_TX) && rxIO == txIO)) {
250 // RX only on pin
251 IOInit(rxIO, ownerTxRx + 1, ownerIndex);
255 if (mode & MODE_TX) {
256 // Need a pin on TX
257 if (!txIO)
258 return NULL;
260 softSerial->txIO = txIO;
262 if (!(mode & MODE_RX)) {
263 // TX Simplex, must have a timer
264 if (!timerTx) {
265 return NULL;
267 softSerial->timerHardware = timerTx;
268 } else {
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);
310 } else {
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);
317 #endif
319 if (!(options & SERIAL_BIDIR)) {
320 serialOutputPortActivate(softSerial);
321 setTxSignal(softSerial, IDLE);
324 serialInputPortActivate(softSerial);
326 return &softSerial->port;
330 * Serial Engine
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);
343 return;
346 // data to send
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.)
369 return;
373 if (softSerial->bitsLeftToTransmit) {
374 const bool bit = softSerial->internalTxBuffer & 1;
375 softSerial->internalTxBuffer >>= 1;
377 setTxSignal(softSerial, bit);
378 softSerial->bitsLeftToTransmit--;
379 return;
382 softSerial->isTransmittingData = false;
385 enum {
386 TRAILING,
387 LEADING
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) {
417 return;
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++;
425 return;
428 uint8_t rxByte = (softSerial->internalRxBuffer >> 1) & 0xFF;
430 if (softSerial->port.rxCallback) {
431 softSerial->port.rxCallback(rxByte, softSerial->port.rxCallbackData);
432 } else {
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) {
441 return;
444 softSerial->rxBitIndex++;
446 if (softSerial->rxBitIndex == RX_TOTAL_BITS - 1) {
447 applyChangedBits(softSerial);
448 return;
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)
464 UNUSED(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)
476 UNUSED(capture);
478 softSerial_t *self = container_of(cbRec, softSerial_t, edgeCb);
480 if ((self->port.mode & MODE_RX) == 0) {
481 return;
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);
492 #else
493 TIM_SetCounter(self->timerHardware->tim, self->timerHardware->tim->ARR / 2);
494 #endif
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);
508 #endif
509 self->rxEdge = LEADING;
511 self->rxBitIndex = 0;
512 self->rxLastLeadingEdgeAtBitIndex = 0;
513 self->internalRxBuffer = 0;
514 self->isSearchingForStartBit = false;
515 return;
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);
527 } else {
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);
533 #endif
537 * Standard serial driver API
540 uint32_t softSerialRxBytesWaiting(const serialPort_t *instance)
542 if ((instance->mode & MODE_RX) == 0) {
543 return 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) {
554 return 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)
566 uint8_t ch;
568 if ((instance->mode & MODE_RX) == 0) {
569 return 0;
572 if (softSerialRxBytesWaiting(instance) == 0) {
573 return 0;
576 ch = instance->rxBuffer[instance->rxBufferTail];
577 instance->rxBufferTail = (instance->rxBufferTail + 1) % instance->rxBufferSize;
578 return ch;
581 void softSerialWriteByte(serialPort_t *s, uint8_t ch)
583 if ((s->mode & MODE_TX) == 0) {
584 return;
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,
620 .writeBuf = NULL,
621 .beginWrite = NULL,
622 .endWrite = NULL
625 #endif