Fix DSHOT on G4
[betaflight.git] / src / main / drivers / dshot_bitbang.c
blob7c9c195a0ffe13b2131a15b5682c9a5ba24d3f85
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/>.
21 #include <stdint.h>
22 #include <math.h>
23 #include <string.h>
25 #include "platform.h"
27 #ifdef USE_DSHOT_BITBANG
29 #include "build/debug.h"
30 #include "build/debug_pin.h"
32 #include "drivers/io.h"
33 #include "drivers/io_impl.h"
34 #include "drivers/dma.h"
35 #include "drivers/dma_reqmap.h"
36 #include "drivers/dshot.h"
37 #include "drivers/dshot_bitbang.h"
38 #include "drivers/dshot_bitbang_impl.h"
39 #include "drivers/dshot_command.h"
40 #include "drivers/motor.h"
41 #include "drivers/nvic.h"
42 #include "drivers/pwm_output.h" // XXX for pwmOutputPort_t motors[]; should go away with refactoring
43 #include "drivers/dshot_dpwm.h" // XXX for motorDmaOutput_t *getMotorDmaOutput(uint8_t index); should go away with refactoring
44 #include "drivers/dshot_bitbang_decode.h"
45 #include "drivers/time.h"
46 #include "drivers/timer.h"
48 #include "pg/motor.h"
50 FAST_DATA_ZERO_INIT bbPacer_t bbPacers[MAX_MOTOR_PACERS]; // TIM1 or TIM8
51 FAST_DATA_ZERO_INIT int usedMotorPacers = 0;
53 FAST_DATA_ZERO_INIT bbPort_t bbPorts[MAX_SUPPORTED_MOTOR_PORTS];
54 FAST_DATA_ZERO_INIT int usedMotorPorts;
56 FAST_DATA_ZERO_INIT bbMotor_t bbMotors[MAX_SUPPORTED_MOTORS];
58 static FAST_DATA_ZERO_INIT int motorCount;
59 dshotBitbangStatus_e bbStatus;
61 // For MCUs that use MPU to control DMA coherency, there might be a performance hit
62 // on manipulating input buffer content especially if it is read multiple times,
63 // as the buffer region is attributed as not cachable.
64 // If this is not desirable, we should use manual cache invalidation.
65 #ifdef USE_DSHOT_CACHE_MGMT
66 #define BB_OUTPUT_BUFFER_ATTRIBUTE DMA_RW_AXI __attribute__((aligned(32)))
67 #define BB_INPUT_BUFFER_ATTRIBUTE DMA_RW_AXI __attribute__((aligned(32)))
68 #else
69 #if defined(STM32F4)
70 #define BB_OUTPUT_BUFFER_ATTRIBUTE
71 #define BB_INPUT_BUFFER_ATTRIBUTE
72 #elif defined(STM32F7)
73 #define BB_OUTPUT_BUFFER_ATTRIBUTE FAST_DATA_ZERO_INIT
74 #define BB_INPUT_BUFFER_ATTRIBUTE FAST_DATA_ZERO_INIT
75 #elif defined(STM32H7)
76 #define BB_OUTPUT_BUFFER_ATTRIBUTE DMA_RAM
77 #define BB_INPUT_BUFFER_ATTRIBUTE DMA_RAM
78 #elif defined(STM32G4)
79 #define BB_OUTPUT_BUFFER_ATTRIBUTE DMA_RAM_W
80 #define BB_INPUT_BUFFER_ATTRIBUTE DMA_RAM_R
81 #endif
82 #endif // USE_DSHOT_CACHE_MGMT
84 BB_OUTPUT_BUFFER_ATTRIBUTE uint32_t bbOutputBuffer[MOTOR_DSHOT_BUF_CACHE_ALIGN_LENGTH * MAX_SUPPORTED_MOTOR_PORTS];
85 BB_INPUT_BUFFER_ATTRIBUTE uint16_t bbInputBuffer[DSHOT_BB_PORT_IP_BUF_CACHE_ALIGN_LENGTH * MAX_SUPPORTED_MOTOR_PORTS];
87 uint8_t bbPuPdMode;
88 FAST_DATA_ZERO_INIT timeUs_t dshotFrameUs;
91 const timerHardware_t bbTimerHardware[] = {
92 #if defined(STM32F4) || defined(STM32F7)
93 #if !defined(STM32F411xE)
94 DEF_TIM(TIM8, CH1, NONE, TIM_USE_NONE, 0, 1),
95 DEF_TIM(TIM8, CH2, NONE, TIM_USE_NONE, 0, 1),
96 DEF_TIM(TIM8, CH3, NONE, TIM_USE_NONE, 0, 1),
97 DEF_TIM(TIM8, CH4, NONE, TIM_USE_NONE, 0, 0),
98 #endif
99 DEF_TIM(TIM1, CH1, NONE, TIM_USE_NONE, 0, 1),
100 DEF_TIM(TIM1, CH1, NONE, TIM_USE_NONE, 0, 2),
101 DEF_TIM(TIM1, CH2, NONE, TIM_USE_NONE, 0, 1),
102 DEF_TIM(TIM1, CH3, NONE, TIM_USE_NONE, 0, 1),
103 DEF_TIM(TIM1, CH4, NONE, TIM_USE_NONE, 0, 0),
105 #elif defined(STM32G4) || defined(STM32H7)
106 // XXX TODO: STM32G4 and STM32H7 can use any timer for pacing
108 // DMA request numbers are duplicated for TIM1 and TIM8:
109 // - Any pacer can serve a GPIO port.
110 // - For quads (or less), 4 pacers can cover the worst case scenario of
111 // 4 motors scattered across 4 different GPIO ports.
112 // - For hexas (and larger), more channels may become necessary,
113 // in which case the DMA request numbers should be modified.
114 DEF_TIM(TIM8, CH1, NONE, TIM_USE_NONE, 0, 0, 0),
115 DEF_TIM(TIM8, CH2, NONE, TIM_USE_NONE, 0, 1, 0),
116 DEF_TIM(TIM8, CH3, NONE, TIM_USE_NONE, 0, 2, 0),
117 DEF_TIM(TIM8, CH4, NONE, TIM_USE_NONE, 0, 3, 0),
118 DEF_TIM(TIM1, CH1, NONE, TIM_USE_NONE, 0, 0, 0),
119 DEF_TIM(TIM1, CH2, NONE, TIM_USE_NONE, 0, 1, 0),
120 DEF_TIM(TIM1, CH3, NONE, TIM_USE_NONE, 0, 2, 0),
121 DEF_TIM(TIM1, CH4, NONE, TIM_USE_NONE, 0, 3, 0),
123 #else
124 #error MCU dependent code required
125 #endif
128 static FAST_DATA_ZERO_INIT motorDevice_t bbDevice;
129 static FAST_DATA_ZERO_INIT timeUs_t lastSendUs;
131 static motorPwmProtocolTypes_e motorPwmProtocol;
133 // DMA GPIO output buffer formatting
135 static void bbOutputDataInit(uint32_t *buffer, uint16_t portMask, bool inverted)
137 uint32_t resetMask;
138 uint32_t setMask;
140 if (inverted) {
141 resetMask = portMask;
142 setMask = (portMask << 16);
143 } else {
144 resetMask = (portMask << 16);
145 setMask = portMask;
148 int bitpos;
150 for (bitpos = 0; bitpos < 16; bitpos++) {
151 buffer[bitpos * 3 + 0] |= setMask ; // Always set all ports
152 buffer[bitpos * 3 + 1] = 0; // Reset bits are port dependent
153 buffer[bitpos * 3 + 2] |= resetMask; // Always reset all ports
157 static void bbOutputDataSet(uint32_t *buffer, int pinNumber, uint16_t value, bool inverted)
159 uint32_t middleBit;
161 if (inverted) {
162 middleBit = (1 << (pinNumber + 0));
163 } else {
164 middleBit = (1 << (pinNumber + 16));
167 for (int pos = 0; pos < 16; pos++) {
168 if (!(value & 0x8000)) {
169 buffer[pos * 3 + 1] |= middleBit;
171 value <<= 1;
175 static void bbOutputDataClear(uint32_t *buffer)
177 // Middle position to no change
178 for (int bitpos = 0; bitpos < 16; bitpos++) {
179 buffer[bitpos * 3 + 1] = 0;
183 // bbPacer management
185 static bbPacer_t *bbFindMotorPacer(TIM_TypeDef *tim)
187 for (int i = 0; i < MAX_MOTOR_PACERS; i++) {
189 bbPacer_t *bbPacer = &bbPacers[i];
191 if (bbPacer->tim == NULL) {
192 bbPacer->tim = tim;
193 ++usedMotorPacers;
194 return bbPacer;
197 if (bbPacer->tim == tim) {
198 return bbPacer;
202 return NULL;
205 // bbPort management
207 static bbPort_t *bbFindMotorPort(int portIndex)
209 for (int i = 0; i < usedMotorPorts; i++) {
210 if (bbPorts[i].portIndex == portIndex) {
211 return &bbPorts[i];
214 return NULL;
217 static bbPort_t *bbAllocateMotorPort(int portIndex)
219 if (usedMotorPorts >= MAX_SUPPORTED_MOTOR_PORTS) {
220 bbStatus = DSHOT_BITBANG_STATUS_TOO_MANY_PORTS;
221 return NULL;
224 bbPort_t *bbPort = &bbPorts[usedMotorPorts];
226 if (!bbPort->timhw) {
227 // No more pacer channel available
228 bbStatus = DSHOT_BITBANG_STATUS_NO_PACER;
229 return NULL;
232 bbPort->portIndex = portIndex;
233 bbPort->owner.owner = OWNER_DSHOT_BITBANG;
234 bbPort->owner.resourceIndex = RESOURCE_INDEX(portIndex);
236 ++usedMotorPorts;
238 return bbPort;
241 const timerHardware_t *dshotBitbangTimerGetAllocatedByNumberAndChannel(int8_t timerNumber, uint16_t timerChannel)
243 for (int index = 0; index < usedMotorPorts; index++) {
244 const timerHardware_t *bitbangTimer = bbPorts[index].timhw;
245 if (bitbangTimer && timerGetTIMNumber(bitbangTimer->tim) == timerNumber && bitbangTimer->channel == timerChannel && bbPorts[index].owner.owner) {
246 return bitbangTimer;
250 return NULL;
253 const resourceOwner_t *dshotBitbangTimerGetOwner(const timerHardware_t *timer)
255 for (int index = 0; index < usedMotorPorts; index++) {
256 const timerHardware_t *bitbangTimer = bbPorts[index].timhw;
257 if (bitbangTimer && bitbangTimer == timer) {
258 return &bbPorts[index].owner;
262 return &freeOwner;
265 // Return frequency of smallest change [state/sec]
267 static uint32_t getDshotBaseFrequency(motorPwmProtocolTypes_e pwmProtocolType)
269 switch (pwmProtocolType) {
270 case(PWM_TYPE_DSHOT600):
271 return MOTOR_DSHOT600_SYMBOL_RATE * MOTOR_DSHOT_STATE_PER_SYMBOL;
272 case(PWM_TYPE_DSHOT300):
273 return MOTOR_DSHOT300_SYMBOL_RATE * MOTOR_DSHOT_STATE_PER_SYMBOL;
274 default:
275 case(PWM_TYPE_DSHOT150):
276 return MOTOR_DSHOT150_SYMBOL_RATE * MOTOR_DSHOT_STATE_PER_SYMBOL;
280 static void bbSetupDma(bbPort_t *bbPort)
282 const dmaIdentifier_e dmaIdentifier = dmaGetIdentifier(bbPort->dmaResource);
283 dmaEnable(dmaIdentifier);
284 bbPort->dmaSource = timerDmaSource(bbPort->timhw->channel);
286 bbPacer_t *bbPacer = bbFindMotorPacer(bbPort->timhw->tim);
287 bbPacer->dmaSources |= bbPort->dmaSource;
289 dmaSetHandler(dmaIdentifier, bbDMAIrqHandler, NVIC_BUILD_PRIORITY(2, 1), (uint32_t)bbPort);
291 bbDMA_ITConfig(bbPort);
294 FAST_IRQ_HANDLER void bbDMAIrqHandler(dmaChannelDescriptor_t *descriptor)
296 dbgPinHi(0);
298 bbPort_t *bbPort = (bbPort_t *)descriptor->userParam;
300 bbDMA_Cmd(bbPort, DISABLE);
302 bbTIM_DMACmd(bbPort->timhw->tim, bbPort->dmaSource, DISABLE);
304 if (DMA_GET_FLAG_STATUS(descriptor, DMA_IT_TEIF)) {
305 while (1) {};
308 DMA_CLEAR_FLAG(descriptor, DMA_IT_TCIF);
310 #ifdef USE_DSHOT_TELEMETRY
311 if (useDshotTelemetry) {
312 if (bbPort->direction == DSHOT_BITBANG_DIRECTION_INPUT) {
313 #ifdef DEBUG_COUNT_INTERRUPT
314 bbPort->inputIrq++;
315 #endif
316 } else {
317 #ifdef DEBUG_COUNT_INTERRUPT
318 bbPort->outputIrq++;
319 #endif
321 // Switch to input
323 bbSwitchToInput(bbPort);
325 bbTIM_DMACmd(bbPort->timhw->tim, bbPort->dmaSource, ENABLE);
328 #endif
329 dbgPinLo(0);
332 // Setup bbPorts array elements so that they each have a TIM1 or TIM8 channel
333 // in timerHardware array for BB-DShot.
335 static void bbFindPacerTimer(void)
337 for (int bbPortIndex = 0; bbPortIndex < MAX_SUPPORTED_MOTOR_PORTS; bbPortIndex++) {
338 for (unsigned timerIndex = 0; timerIndex < ARRAYLEN(bbTimerHardware); timerIndex++) {
339 const timerHardware_t *timer = &bbTimerHardware[timerIndex];
340 int timNumber = timerGetTIMNumber(timer->tim);
341 if ((motorConfig()->dev.useDshotBitbangedTimer == DSHOT_BITBANGED_TIMER_TIM1 && timNumber != 1)
342 || (motorConfig()->dev.useDshotBitbangedTimer == DSHOT_BITBANGED_TIMER_TIM8 && timNumber != 8)) {
343 continue;
345 bool timerConflict = false;
346 for (int channel = 0; channel < CC_CHANNELS_PER_TIMER; channel++) {
347 const timerHardware_t *timer = timerGetAllocatedByNumberAndChannel(timNumber, CC_CHANNEL_FROM_INDEX(channel));
348 const resourceOwner_e timerOwner = timerGetOwner(timer)->owner;
349 if (timerOwner != OWNER_FREE && timerOwner != OWNER_DSHOT_BITBANG) {
350 timerConflict = true;
351 break;
355 for (int index = 0; index < bbPortIndex; index++) {
356 const timerHardware_t* t = bbPorts[index].timhw;
357 if (timerGetTIMNumber(t->tim) == timNumber && timer->channel == t->channel) {
358 timerConflict = true;
359 break;
363 if (timerConflict) {
364 continue;
367 #ifdef USE_DMA_SPEC
368 dmaoptValue_t dmaopt = dmaGetOptionByTimer(timer);
369 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
370 dmaResource_t *dma = dmaChannelSpec->ref;
371 #else
372 dmaResource_t *dma = timer->dmaRef;
373 #endif
374 dmaIdentifier_e dmaIdentifier = dmaGetIdentifier(dma);
375 if (dmaGetOwner(dmaIdentifier)->owner == OWNER_FREE) {
376 bbPorts[bbPortIndex].timhw = timer;
378 break;
384 static void bbTimebaseSetup(bbPort_t *bbPort, motorPwmProtocolTypes_e dshotProtocolType)
386 uint32_t timerclock = timerClock(bbPort->timhw->tim);
388 uint32_t outputFreq = getDshotBaseFrequency(dshotProtocolType);
389 dshotFrameUs = 1000000 * 17 * 3 / outputFreq;
390 bbPort->outputARR = timerclock / outputFreq - 1;
392 // XXX Explain this formula
393 uint32_t inputFreq = outputFreq * 5 * 2 * DSHOT_BITBANG_TELEMETRY_OVER_SAMPLE / 24;
394 bbPort->inputARR = timerclock / inputFreq - 1;
398 // bb only use pin info associated with timerHardware entry designated as TIM_USE_MOTOR;
399 // it does not use the timer channel associated with the pin.
402 static bool bbMotorConfig(IO_t io, uint8_t motorIndex, motorPwmProtocolTypes_e pwmProtocolType, uint8_t output)
404 int pinIndex = IO_GPIOPinIdx(io);
405 int portIndex = IO_GPIOPortIdx(io);
407 bbPort_t *bbPort = bbFindMotorPort(portIndex);
409 if (!bbPort) {
411 // New port group
413 bbPort = bbAllocateMotorPort(portIndex);
415 if (bbPort) {
416 const timerHardware_t *timhw = bbPort->timhw;
418 #ifdef USE_DMA_SPEC
419 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timhw->tim, timhw->channel, dmaGetOptionByTimer(timhw));
420 bbPort->dmaResource = dmaChannelSpec->ref;
421 bbPort->dmaChannel = dmaChannelSpec->channel;
422 #else
423 bbPort->dmaResource = timhw->dmaRef;
424 bbPort->dmaChannel = timhw->dmaChannel;
425 #endif
428 if (!bbPort || !dmaAllocate(dmaGetIdentifier(bbPort->dmaResource), bbPort->owner.owner, bbPort->owner.resourceIndex)) {
429 bbDevice.vTable.write = motorWriteNull;
430 bbDevice.vTable.updateStart = motorUpdateStartNull;
431 bbDevice.vTable.updateComplete = motorUpdateCompleteNull;
433 return false;
436 bbPort->gpio = IO_GPIO(io);
438 bbPort->portOutputCount = MOTOR_DSHOT_BUF_LENGTH;
439 bbPort->portOutputBuffer = &bbOutputBuffer[(bbPort - bbPorts) * MOTOR_DSHOT_BUF_CACHE_ALIGN_LENGTH];
441 bbPort->portInputCount = DSHOT_BB_PORT_IP_BUF_LENGTH;
442 bbPort->portInputBuffer = &bbInputBuffer[(bbPort - bbPorts) * DSHOT_BB_PORT_IP_BUF_CACHE_ALIGN_LENGTH];
444 bbTimebaseSetup(bbPort, pwmProtocolType);
445 bbTIM_TimeBaseInit(bbPort, bbPort->outputARR);
446 bbTimerChannelInit(bbPort);
448 bbSetupDma(bbPort);
449 bbDMAPreconfigure(bbPort, DSHOT_BITBANG_DIRECTION_OUTPUT);
450 bbDMAPreconfigure(bbPort, DSHOT_BITBANG_DIRECTION_INPUT);
452 bbDMA_ITConfig(bbPort);
455 bbMotors[motorIndex].pinIndex = pinIndex;
456 bbMotors[motorIndex].io = io;
457 bbMotors[motorIndex].output = output;
458 bbMotors[motorIndex].bbPort = bbPort;
460 IOInit(io, OWNER_MOTOR, RESOURCE_INDEX(motorIndex));
462 // Setup GPIO_MODER and GPIO_ODR register manipulation values
464 bbGpioSetup(&bbMotors[motorIndex]);
466 #ifdef USE_DSHOT_TELEMETRY
467 if (useDshotTelemetry) {
468 bbOutputDataInit(bbPort->portOutputBuffer, (1 << pinIndex), DSHOT_BITBANG_INVERTED);
469 } else
470 #endif
472 bbOutputDataInit(bbPort->portOutputBuffer, (1 << pinIndex), DSHOT_BITBANG_NONINVERTED);
475 bbSwitchToOutput(bbPort);
477 bbMotors[motorIndex].configured = true;
479 return true;
482 static bool bbUpdateStart(void)
484 #ifdef USE_DSHOT_TELEMETRY
485 if (useDshotTelemetry) {
486 #ifdef USE_DSHOT_TELEMETRY_STATS
487 const timeMs_t currentTimeMs = millis();
488 #endif
489 timeUs_t currentUs = micros();
490 // don't send while telemetry frames might still be incoming
491 if (cmpTimeUs(currentUs, lastSendUs) < (timeDelta_t)(40 + 2 * dshotFrameUs)) {
492 return false;
495 for (int motorIndex = 0; motorIndex < MAX_SUPPORTED_MOTORS && motorIndex < motorCount; motorIndex++) {
496 #ifdef USE_DSHOT_CACHE_MGMT
497 // Only invalidate the buffer once. If all motors are on a common port they'll share a buffer.
498 bool invalidated = false;
499 for (int i = 0; i < motorIndex; i++) {
500 if (bbMotors[motorIndex].bbPort->portInputBuffer == bbMotors[i].bbPort->portInputBuffer) {
501 invalidated = true;
504 if (!invalidated) {
505 SCB_InvalidateDCache_by_Addr((uint32_t *)bbMotors[motorIndex].bbPort->portInputBuffer,
506 DSHOT_BB_PORT_IP_BUF_CACHE_ALIGN_BYTES);
508 #endif
510 #ifdef STM32F4
511 uint32_t value = decode_bb_bitband(
512 bbMotors[motorIndex].bbPort->portInputBuffer,
513 bbMotors[motorIndex].bbPort->portInputCount - bbDMA_Count(bbMotors[motorIndex].bbPort),
514 bbMotors[motorIndex].pinIndex);
515 #else
516 uint32_t value = decode_bb(
517 bbMotors[motorIndex].bbPort->portInputBuffer,
518 bbMotors[motorIndex].bbPort->portInputCount - bbDMA_Count(bbMotors[motorIndex].bbPort),
519 bbMotors[motorIndex].pinIndex);
520 #endif
521 if (value == BB_NOEDGE) {
522 continue;
524 dshotTelemetryState.readCount++;
526 if (value != BB_INVALID) {
527 dshotTelemetryState.motorState[motorIndex].telemetryValue = value;
528 dshotTelemetryState.motorState[motorIndex].telemetryActive = true;
529 if (motorIndex < 4) {
530 DEBUG_SET(DEBUG_DSHOT_RPM_TELEMETRY, motorIndex, value);
532 } else {
533 dshotTelemetryState.invalidPacketCount++;
535 #ifdef USE_DSHOT_TELEMETRY_STATS
536 updateDshotTelemetryQuality(&dshotTelemetryQuality[motorIndex], value != BB_INVALID, currentTimeMs);
537 #endif
540 #endif
541 for (int i = 0; i < usedMotorPorts; i++) {
542 bbDMA_Cmd(&bbPorts[i], DISABLE);
543 bbOutputDataClear(bbPorts[i].portOutputBuffer);
546 return true;
549 static void bbWriteInt(uint8_t motorIndex, uint16_t value)
551 bbMotor_t *const bbmotor = &bbMotors[motorIndex];
553 if (!bbmotor->configured) {
554 return;
557 // fetch requestTelemetry from motors. Needs to be refactored.
558 motorDmaOutput_t * const motor = getMotorDmaOutput(motorIndex);
559 bbmotor->protocolControl.requestTelemetry = motor->protocolControl.requestTelemetry;
560 motor->protocolControl.requestTelemetry = false;
562 // If there is a command ready to go overwrite the value and send that instead
563 if (dshotCommandIsProcessing()) {
564 value = dshotCommandGetCurrent(motorIndex);
565 if (value) {
566 bbmotor->protocolControl.requestTelemetry = true;
570 bbmotor->protocolControl.value = value;
572 uint16_t packet = prepareDshotPacket(&bbmotor->protocolControl);
574 bbPort_t *bbPort = bbmotor->bbPort;
576 #ifdef USE_DSHOT_TELEMETRY
577 if (useDshotTelemetry) {
578 bbOutputDataSet(bbPort->portOutputBuffer, bbmotor->pinIndex, packet, DSHOT_BITBANG_INVERTED);
579 } else
580 #endif
582 bbOutputDataSet(bbPort->portOutputBuffer, bbmotor->pinIndex, packet, DSHOT_BITBANG_NONINVERTED);
586 static void bbWrite(uint8_t motorIndex, float value)
588 bbWriteInt(motorIndex, value);
591 static void bbUpdateComplete(void)
593 // If there is a dshot command loaded up, time it correctly with motor update
595 if (!dshotCommandQueueEmpty()) {
596 if (!dshotCommandOutputIsEnabled(bbDevice.count)) {
597 return;
601 #ifdef USE_DSHOT_CACHE_MGMT
602 for (int motorIndex = 0; motorIndex < MAX_SUPPORTED_MOTORS && motorIndex < motorCount; motorIndex++) {
603 // Only clean each buffer once. If all motors are on a common port they'll share a buffer.
604 bool clean = false;
605 for (int i = 0; i < motorIndex; i++) {
606 if (bbMotors[motorIndex].bbPort->portOutputBuffer == bbMotors[i].bbPort->portOutputBuffer) {
607 clean = true;
610 if (!clean) {
611 SCB_CleanDCache_by_Addr(bbMotors[motorIndex].bbPort->portOutputBuffer, MOTOR_DSHOT_BUF_CACHE_ALIGN_BYTES);
614 #endif
616 for (int i = 0; i < usedMotorPorts; i++) {
617 bbPort_t *bbPort = &bbPorts[i];
619 #ifdef USE_DSHOT_TELEMETRY
620 if (useDshotTelemetry) {
621 if (bbPort->direction == DSHOT_BITBANG_DIRECTION_INPUT) {
622 bbPort->inputActive = false;
623 bbSwitchToOutput(bbPort);
625 } else
626 #endif
628 #if defined(STM32G4)
629 // Using circular mode resets the counter one short, so explicitly reload
630 bbSwitchToOutput(bbPort);
631 #endif
634 bbDMA_Cmd(bbPort, ENABLE);
637 lastSendUs = micros();
638 for (int i = 0; i < usedMotorPacers; i++) {
639 bbPacer_t *bbPacer = &bbPacers[i];
640 bbTIM_DMACmd(bbPacer->tim, bbPacer->dmaSources, ENABLE);
644 static bool bbEnableMotors(void)
646 for (int i = 0; i < motorCount; i++) {
647 if (bbMotors[i].configured) {
648 IOConfigGPIO(bbMotors[i].io, bbMotors[i].iocfg);
651 return true;
654 static void bbDisableMotors(void)
656 return;
659 static void bbShutdown(void)
661 return;
664 static bool bbIsMotorEnabled(uint8_t index)
666 return bbMotors[index].enabled;
669 static void bbPostInit()
671 bbFindPacerTimer();
673 for (int motorIndex = 0; motorIndex < MAX_SUPPORTED_MOTORS && motorIndex < motorCount; motorIndex++) {
675 if (!bbMotorConfig(bbMotors[motorIndex].io, motorIndex, motorPwmProtocol, bbMotors[motorIndex].output)) {
676 return NULL;
680 bbMotors[motorIndex].enabled = true;
682 // Fill in motors structure for 4way access (XXX Should be refactored)
684 motors[motorIndex].enabled = true;
688 static motorVTable_t bbVTable = {
689 .postInit = bbPostInit,
690 .enable = bbEnableMotors,
691 .disable = bbDisableMotors,
692 .isMotorEnabled = bbIsMotorEnabled,
693 .updateStart = bbUpdateStart,
694 .write = bbWrite,
695 .writeInt = bbWriteInt,
696 .updateComplete = bbUpdateComplete,
697 .convertExternalToMotor = dshotConvertFromExternal,
698 .convertMotorToExternal = dshotConvertToExternal,
699 .shutdown = bbShutdown,
702 dshotBitbangStatus_e dshotBitbangGetStatus()
704 return bbStatus;
707 motorDevice_t *dshotBitbangDevInit(const motorDevConfig_t *motorConfig, uint8_t count)
709 dbgPinLo(0);
710 dbgPinLo(1);
712 motorPwmProtocol = motorConfig->motorPwmProtocol;
713 bbDevice.vTable = bbVTable;
714 motorCount = count;
715 bbStatus = DSHOT_BITBANG_STATUS_OK;
717 #ifdef USE_DSHOT_TELEMETRY
718 useDshotTelemetry = motorConfig->useDshotTelemetry;
719 #endif
721 memset(bbOutputBuffer, 0, sizeof(bbOutputBuffer));
723 for (int motorIndex = 0; motorIndex < MAX_SUPPORTED_MOTORS && motorIndex < motorCount; motorIndex++) {
724 const unsigned reorderedMotorIndex = motorConfig->motorOutputReordering[motorIndex];
725 const timerHardware_t *timerHardware = timerGetConfiguredByTag(motorConfig->ioTags[reorderedMotorIndex]);
726 const IO_t io = IOGetByTag(motorConfig->ioTags[reorderedMotorIndex]);
728 uint8_t output = motorConfig->motorPwmInversion ? timerHardware->output ^ TIMER_OUTPUT_INVERTED : timerHardware->output;
729 bbPuPdMode = (output & TIMER_OUTPUT_INVERTED) ? BB_GPIO_PULLDOWN : BB_GPIO_PULLUP;
731 #ifdef USE_DSHOT_TELEMETRY
732 if (useDshotTelemetry) {
733 output ^= TIMER_OUTPUT_INVERTED;
735 #endif
737 if (!IOIsFreeOrPreinit(io)) {
738 /* not enough motors initialised for the mixer or a break in the motors */
739 bbDevice.vTable.write = motorWriteNull;
740 bbDevice.vTable.updateStart = motorUpdateStartNull;
741 bbDevice.vTable.updateComplete = motorUpdateCompleteNull;
742 bbStatus = DSHOT_BITBANG_STATUS_MOTOR_PIN_CONFLICT;
743 return NULL;
746 int pinIndex = IO_GPIOPinIdx(io);
748 bbMotors[motorIndex].pinIndex = pinIndex;
749 bbMotors[motorIndex].io = io;
750 bbMotors[motorIndex].output = output;
751 #if defined(STM32F4) || defined(STM32F3)
752 bbMotors[motorIndex].iocfg = IO_CONFIG(GPIO_Mode_OUT, GPIO_Speed_50MHz, GPIO_OType_PP, bbPuPdMode);
753 #elif defined(STM32F7) || defined(STM32G4) || defined(STM32H7)
754 bbMotors[motorIndex].iocfg = IO_CONFIG(GPIO_MODE_OUTPUT_PP, GPIO_SPEED_FREQ_VERY_HIGH, bbPuPdMode);
755 #endif
757 IOInit(io, OWNER_MOTOR, RESOURCE_INDEX(motorIndex));
758 IOConfigGPIO(io, bbMotors[motorIndex].iocfg);
759 if (output & TIMER_OUTPUT_INVERTED) {
760 IOLo(io);
761 } else {
762 IOHi(io);
765 // Fill in motors structure for 4way access (XXX Should be refactored)
766 motors[motorIndex].io = bbMotors[motorIndex].io;
769 return &bbDevice;
772 #endif // USE_DSHOT_BB