diff --git a/make/mcu/STM32F4.mk b/make/mcu/STM32F4.mk index ea7b0645c..6a756f7de 100644 --- a/make/mcu/STM32F4.mk +++ b/make/mcu/STM32F4.mk @@ -175,6 +175,9 @@ MCU_COMMON_SRC = \ drivers/bus_i2c_stm32f10x.c \ drivers/bus_spi_stdperiph.c \ drivers/dma_stm32f4xx.c \ + drivers/dshot_bitbang.c \ + drivers/dshot_bitbang_decode.c \ + drivers/dshot_bitbang_stdperiph.c \ drivers/inverter.c \ drivers/light_ws2811strip_stdperiph.c \ drivers/transponder_ir_io_stdperiph.c \ diff --git a/make/mcu/STM32F7.mk b/make/mcu/STM32F7.mk index c8f00135b..6d8f71568 100644 --- a/make/mcu/STM32F7.mk +++ b/make/mcu/STM32F7.mk @@ -174,6 +174,9 @@ MCU_COMMON_SRC = \ drivers/transponder_ir_io_hal.c \ drivers/bus_spi_ll.c \ drivers/persistent.c \ + drivers/dshot_bitbang.c \ + drivers/dshot_bitbang_decode.c \ + drivers/dshot_bitbang_ll.c \ drivers/pwm_output_dshot_hal.c \ drivers/pwm_output_dshot_shared.c \ drivers/timer_hal.c \ diff --git a/src/main/cli/cli.c b/src/main/cli/cli.c index 68ee37b89..b2dadc560 100644 --- a/src/main/cli/cli.c +++ b/src/main/cli/cli.c @@ -5616,12 +5616,28 @@ static void showTimers(void) cliRepeat('-', 23); #endif +#ifdef USE_DSHOT_BITBANG + resourceOwner_t bitbangOwner = { OWNER_DSHOT_BITBANG, 0 }; +#endif int8_t timerNumber; for (int i = 0; (timerNumber = timerGetNumberByIndex(i)); i++) { cliPrintf("TIM%d:", timerNumber); bool timerUsed = false; for (unsigned timerIndex = 0; timerIndex < CC_CHANNELS_PER_TIMER; timerIndex++) { const resourceOwner_t *timerOwner = timerGetOwner(timerNumber, CC_CHANNEL_FROM_INDEX(timerIndex)); +#ifdef USE_DSHOT_BITBANG + if (!timerOwner->owner) { + const timerHardware_t* timer; + int pacerIndex = 0; + while ((timer = dshotBitbangGetPacerTimer(pacerIndex++))) { + if (timerGetTIMNumber(timer->tim) == timerNumber && timer->channel == CC_CHANNEL_FROM_INDEX(timerIndex)) { + timerOwner = &bitbangOwner; + bitbangOwner.resourceIndex++; + break; + } + } + } +#endif if (timerOwner->owner) { if (!timerUsed) { timerUsed = true; @@ -5879,12 +5895,11 @@ static void cliDshotTelemetryInfo(char *cmdline) UNUSED(cmdline); if (useDshotTelemetry) { - cliPrintLinef("Dshot reads: %u", readDoneCount); - cliPrintLinef("Dshot invalid pkts: %u", dshotInvalidPacketCount); + cliPrintLinef("Dshot reads: %u", dshotTelemetryState.readCount); + cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState.invalidPacketCount); uint32_t directionChangeCycles = dshotDMAHandlerCycleCounters.changeDirectionCompletedAt - dshotDMAHandlerCycleCounters.irqAt; uint32_t directionChangeDurationUs = clockCyclesToMicros(directionChangeCycles); cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles, directionChangeDurationUs); - cliPrintLinef("Dshot packet decode micros: %u", decodePacketDurationUs); cliPrintLinefeed(); #ifdef USE_DSHOT_TELEMETRY_STATS @@ -5913,12 +5928,19 @@ static void cliDshotTelemetryInfo(char *cmdline) cliPrintLinefeed(); const int len = MAX_GCR_EDGES; +#ifdef DEBUG_BBDECODE + extern uint16_t bbBuffer[134]; + for (int i = 0; i < 134; i++) { + cliPrintf("%u ", (int)bbBuffer[i]); + } + cliPrintLinefeed(); +#endif for (int i = 0; i < len; i++) { - cliPrintf("%u ", (int)inputBuffer[i]); + cliPrintf("%u ", (int)dshotTelemetryState.inputBuffer[i]); } cliPrintLinefeed(); for (int i = 1; i < len; i++) { - cliPrintf("%u ", (int)(inputBuffer[i] - inputBuffer[i-1])); + cliPrintf("%u ", (int)(dshotTelemetryState.inputBuffer[i] - dshotTelemetryState.inputBuffer[i-1])); } cliPrintLinefeed(); } else { @@ -6566,4 +6588,5 @@ void cliEnter(serialPort_t *serialPort) resetCommandBatch(); #endif } + #endif // USE_CLI diff --git a/src/main/cli/settings.c b/src/main/cli/settings.c index 2b5d1c51a..71c89f223 100644 --- a/src/main/cli/settings.c +++ b/src/main/cli/settings.c @@ -469,6 +469,10 @@ static const char * const lookupTablePositionAltSource[] = { "DEFAULT", "BARO_ONLY", "GPS_ONLY" }; +static const char * const lookupTableDshotBitbang[] = { + "OFF", "ON", "AUTO" +}; + #define LOOKUP_TABLE_ENTRY(name) { name, ARRAYLEN(name) } const lookupTableEntry_t lookupTables[] = { @@ -583,6 +587,7 @@ const lookupTableEntry_t lookupTables[] = { LOOKUP_TABLE_ENTRY(lookupTableGyroFilterDebug), LOOKUP_TABLE_ENTRY(lookupTablePositionAltSource), + LOOKUP_TABLE_ENTRY(lookupTableDshotBitbang), }; #undef LOOKUP_TABLE_ENTRY @@ -755,6 +760,9 @@ const clivalue_t valueTable[] = { #ifdef USE_DSHOT_TELEMETRY { "dshot_bidir", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_MOTOR_CONFIG, offsetof(motorConfig_t, dev.useDshotTelemetry) }, #endif +#ifdef USE_DSHOT_BITBANG + { "dshot_bitbang", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_DSHOT_BITBANG }, PG_MOTOR_CONFIG, offsetof(motorConfig_t, dev.useDshotBitbang) }, +#endif #endif { "use_unsynced_pwm", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_MOTOR_CONFIG, offsetof(motorConfig_t, dev.useUnsyncedPwm) }, { "motor_pwm_protocol", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_MOTOR_PWM_PROTOCOL }, PG_MOTOR_CONFIG, offsetof(motorConfig_t, dev.motorPwmProtocol) }, diff --git a/src/main/cli/settings.h b/src/main/cli/settings.h index 57eb9bdaf..6952318d2 100644 --- a/src/main/cli/settings.h +++ b/src/main/cli/settings.h @@ -135,6 +135,7 @@ typedef enum { #endif TABLE_GYRO_FILTER_DEBUG, TABLE_POSITION_ALT_SOURCE, + TABLE_DSHOT_BITBANG, LOOKUP_TABLE_COUNT } lookupTableIndex_e; @@ -248,4 +249,4 @@ extern const char * const lookupTableRescueAltitudeMode[]; extern const char * const lookupTableItermRelax[]; -extern const char * const lookupTableItermRelaxType[]; \ No newline at end of file +extern const char * const lookupTableItermRelaxType[]; diff --git a/src/main/drivers/dshot.c b/src/main/drivers/dshot.c index dcf40f71a..af77bf144 100644 --- a/src/main/drivers/dshot.c +++ b/src/main/drivers/dshot.c @@ -50,6 +50,7 @@ #include "rx/rx.h" + void dshotInitEndpoints(float outputLimit, float *outputLow, float *outputHigh, float *disarm, float *deadbandMotor3dHigh, float *deadbandMotor3dLow) { float outputLimitOffset = (DSHOT_MAX_THROTTLE - DSHOT_MIN_THROTTLE) * (1 - outputLimit); *disarm = DSHOT_CMD_MOTOR_STOP; @@ -131,4 +132,37 @@ FAST_CODE uint16_t prepareDshotPacket(dshotProtocolControl_t *pcb) return packet; } + +#ifdef USE_DSHOT_TELEMETRY +FAST_RAM_ZERO_INIT dshotTelemetryState_t dshotTelemetryState; + +uint16_t getDshotTelemetry(uint8_t index) +{ + return dshotTelemetryState.motorState[index].telemetryValue; +} + +#endif + +#ifdef USE_DSHOT_TELEMETRY_STATS +FAST_RAM_ZERO_INIT dshotTelemetryQuality_t dshotTelemetryQuality[MAX_SUPPORTED_MOTORS]; + +void updateDshotTelemetryQuality(dshotTelemetryQuality_t *qualityStats, bool packetValid, timeMs_t currentTimeMs) +{ + uint8_t statsBucketIndex = (currentTimeMs / DSHOT_TELEMETRY_QUALITY_BUCKET_MS) % DSHOT_TELEMETRY_QUALITY_BUCKET_COUNT; + if (statsBucketIndex != qualityStats->lastBucketIndex) { + qualityStats->packetCountSum -= qualityStats->packetCountArray[statsBucketIndex]; + qualityStats->invalidCountSum -= qualityStats->invalidCountArray[statsBucketIndex]; + qualityStats->packetCountArray[statsBucketIndex] = 0; + qualityStats->invalidCountArray[statsBucketIndex] = 0; + qualityStats->lastBucketIndex = statsBucketIndex; + } + qualityStats->packetCountSum++; + qualityStats->packetCountArray[statsBucketIndex]++; + if (!packetValid) { + qualityStats->invalidCountSum++; + qualityStats->invalidCountArray[statsBucketIndex]++; + } +} +#endif // USE_DSHOT_TELEMETRY_STATS + #endif // USE_DSHOT diff --git a/src/main/drivers/dshot.h b/src/main/drivers/dshot.h index 57d156a03..0e2dc3584 100644 --- a/src/main/drivers/dshot.h +++ b/src/main/drivers/dshot.h @@ -20,10 +20,34 @@ #pragma once +#include "common/time.h" + #define DSHOT_MIN_THROTTLE 48 #define DSHOT_MAX_THROTTLE 2047 #define DSHOT_3D_FORWARD_MIN_THROTTLE 1048 +#define MIN_GCR_EDGES 7 +#define MAX_GCR_EDGES 22 + +// comment out to see frame dump of corrupted frames in dshot_telemetry_info +//#define DEBUG_BBDECODE + +#ifdef USE_DSHOT_TELEMETRY_STATS +#define DSHOT_TELEMETRY_QUALITY_WINDOW 1 // capture a rolling 1 second of packet stats +#define DSHOT_TELEMETRY_QUALITY_BUCKET_MS 100 // determines the granularity of the stats and the overall number of rolling buckets +#define DSHOT_TELEMETRY_QUALITY_BUCKET_COUNT (DSHOT_TELEMETRY_QUALITY_WINDOW * 1000 / DSHOT_TELEMETRY_QUALITY_BUCKET_MS) + +typedef struct dshotTelemetryQuality_s { + uint32_t packetCountSum; + uint32_t invalidCountSum; + uint32_t packetCountArray[DSHOT_TELEMETRY_QUALITY_BUCKET_COUNT]; + uint32_t invalidCountArray[DSHOT_TELEMETRY_QUALITY_BUCKET_COUNT]; + uint8_t lastBucketIndex; +} dshotTelemetryQuality_t; + +extern dshotTelemetryQuality_t dshotTelemetryQuality[MAX_SUPPORTED_MOTORS]; +#endif // USE_DSHOT_TELEMETRY_STATS + typedef struct dshotProtocolControl_s { uint16_t value; bool requestTelemetry; @@ -37,7 +61,26 @@ FAST_CODE uint16_t prepareDshotPacket(dshotProtocolControl_t *pcb); #ifdef USE_DSHOT_TELEMETRY extern bool useDshotTelemetry; -extern uint32_t dshotInvalidPacketCount; + +typedef struct dshotTelemetryMotorState_s { + uint16_t telemetryValue; + bool telemetryActive; +} dshotTelemetryMotorState_t; + + +typedef struct dshotTelemetryState_s { + bool useDshotTelemetry; + uint32_t invalidPacketCount; + uint32_t readCount; + dshotTelemetryMotorState_t motorState[MAX_SUPPORTED_MOTORS]; + uint32_t inputBuffer[MAX_GCR_EDGES]; +} dshotTelemetryState_t; + +extern dshotTelemetryState_t dshotTelemetryState; + +#ifdef USE_DSHOT_TELEMETRY_STATS +void updateDshotTelemetryQuality(dshotTelemetryQuality_t *qualityStats, bool packetValid, timeMs_t currentTimeMs); +#endif #endif uint16_t getDshotTelemetry(uint8_t index); diff --git a/src/main/drivers/dshot_bitbang.c b/src/main/drivers/dshot_bitbang.c new file mode 100644 index 000000000..6812f8687 --- /dev/null +++ b/src/main/drivers/dshot_bitbang.c @@ -0,0 +1,668 @@ +/* + * This file is part of Cleanflight and Betaflight. + * + * Cleanflight and Betaflight are free software. You can redistribute + * this software and/or modify this software under the terms of the + * GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Cleanflight and Betaflight are distributed in the hope that they + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. + * + * If not, see . + */ + +#include +#include +#include + +#include "platform.h" + +#ifdef USE_DSHOT_BITBANG + +#include "build/debug.h" + +#include "drivers/io.h" +#include "drivers/io_impl.h" +#include "drivers/dma.h" +#include "drivers/dma_reqmap.h" +#include "drivers/dshot.h" +#include "drivers/dshot_bitbang.h" +#include "drivers/dshot_bitbang_impl.h" +#include "drivers/dshot_command.h" +#include "drivers/motor.h" +#include "drivers/nvic.h" +#include "drivers/pwm_output.h" // XXX for pwmOutputPort_t motors[]; should go away with refactoring +#include "drivers/dshot_bitbang_decode.h" +#include "drivers/time.h" +#include "drivers/timer.h" + +#include "pg/motor.h" + +//TODO: Change these to be only used if USE_DEBUG_PIN is not defined once the debug_pin functionality has been merged +#define dbgPinInit() +#define dbgPinHi(x) +#define dbgPinLo(x) + +FAST_RAM_ZERO_INIT bbPacer_t bbPacers[MAX_MOTOR_PACERS]; // TIM1 or TIM8 +FAST_RAM_ZERO_INIT int usedMotorPacers = 0; + +FAST_RAM_ZERO_INIT bbPort_t bbPorts[MAX_SUPPORTED_MOTOR_PORTS]; +FAST_RAM_ZERO_INIT int usedMotorPorts; + +FAST_RAM_ZERO_INIT bbMotor_t bbMotors[MAX_SUPPORTED_MOTORS]; + +FAST_RAM_ZERO_INIT bbPort_t *gpioToMotorPort[10]; // GPIO group to bbPort mappin +static FAST_RAM_ZERO_INIT int motorCount; +dshotBitbangStatus_e bbStatus; + +// For MCUs that use MPU to control DMA coherency, there might be a performance hit +// on manipulating input buffer content especially if it is read multiple times, +// as the buffer region is attributed as not cachable. +// If this is not desirable, we should use manual cache invalidation. + +#if defined(STM32F4) +#define BB_OUTPUT_BUFFER_ATTRIBUTE +#define BB_INPUT_BUFFER_ATTRIBUTE +#elif defined(STM32F7) +#define BB_OUTPUT_BUFFER_ATTRIBUTE FAST_RAM_ZERO_INIT +#define BB_INPUT_BUFFER_ATTRIBUTE FAST_RAM_ZERO_INIT +#elif defined(STM32H7) +#define BB_OUTPUT_BUFFER_ATTRIBUTE DMA_RAM +#define BB_INPUT_BUFFER_ATTRIBUTE DMA_RAM +#endif + +BB_OUTPUT_BUFFER_ATTRIBUTE uint32_t bbOutputBuffer[MOTOR_DSHOT_BUFFER_SIZE * MAX_SUPPORTED_MOTOR_PORTS]; +BB_INPUT_BUFFER_ATTRIBUTE uint16_t bbInputBuffer[DSHOT_BITBANG_PORT_INPUT_BUFFER_LENGTH * MAX_SUPPORTED_MOTOR_PORTS]; + +uint8_t bbPuPdMode; +FAST_RAM_ZERO_INIT timeUs_t dshotFrameUs; + + +const timerHardware_t bbTimerHardware[] = { +#if defined(STM32F4) || defined(STM32F7) + DEF_TIM(TIM1, CH1, NONE, TIM_USE_NONE, 0, 1), + DEF_TIM(TIM1, CH1, NONE, TIM_USE_NONE, 0, 2), + DEF_TIM(TIM1, CH2, NONE, TIM_USE_NONE, 0, 1), + DEF_TIM(TIM1, CH3, NONE, TIM_USE_NONE, 0, 1), + DEF_TIM(TIM1, CH4, NONE, TIM_USE_NONE, 0, 0), +#if !defined(STM32F40_41xxx) + DEF_TIM(TIM8, CH1, NONE, TIM_USE_NONE, 0, 1), + DEF_TIM(TIM8, CH2, NONE, TIM_USE_NONE, 0, 1), + DEF_TIM(TIM8, CH3, NONE, TIM_USE_NONE, 0, 1), + DEF_TIM(TIM8, CH4, NONE, TIM_USE_NONE, 0, 0), +#endif +#endif +}; + +// DMA GPIO output buffer formatting + +static void bbOutputDataInit(uint32_t *buffer, uint16_t portMask, bool inverted) +{ + uint32_t resetMask; + uint32_t setMask; + + if (inverted) { + resetMask = portMask; + setMask = (portMask << 16); + } else { + resetMask = (portMask << 16); + setMask = portMask; + } + + int bitpos; + + for (bitpos = 0; bitpos < 16; bitpos++) { + buffer[bitpos * 3 + 0] |= setMask ; // Always set all ports + buffer[bitpos * 3 + 1] = 0; // Reset bits are port dependent + buffer[bitpos * 3 + 2] |= resetMask; // Always reset all ports + } +} + +static void bbOutputDataSet(uint32_t *buffer, int pinNumber, uint16_t value, bool inverted) +{ + uint32_t middleBit; + + if (inverted) { + middleBit = (1 << (pinNumber + 0)); + } else { + middleBit = (1 << (pinNumber + 16)); + } + + for (int pos = 0; pos < 16; pos++) { + if (!(value & 0x8000)) { + buffer[pos * 3 + 1] |= middleBit; + } + value <<= 1; + } +} + +static void bbOutputDataClear(uint32_t *buffer) +{ + // Middle position to no change + for (int bitpos = 0; bitpos < 16; bitpos++) { + buffer[bitpos * 3 + 1] = 0; + } +} + +// bbPacer management + +static bbPacer_t *bbFindMotorPacer(TIM_TypeDef *tim) +{ + for (int i = 0; i < MAX_MOTOR_PACERS; i++) { + + bbPacer_t *bbPacer = &bbPacers[i]; + + if (bbPacer->tim == NULL) { + bbPacer->tim = tim; + ++usedMotorPacers; + return bbPacer; + } + + if (bbPacer->tim == tim) { + return bbPacer; + } + } + + return NULL; +} + +// bbPort management + +static bbPort_t *bbFindMotorPort(int portIndex) +{ + for (int i = 0; i < usedMotorPorts; i++) { + if (bbPorts[i].portIndex == portIndex) { + return &bbPorts[i]; + } + } + return NULL; +} + +static bbPort_t *bbAllocMotorPort(int portIndex) +{ + if (usedMotorPorts == MAX_SUPPORTED_MOTOR_PORTS) { + return NULL; + } + + bbPort_t *bbPort = &bbPorts[usedMotorPorts]; + + if (!bbPort->timhw) { + // No more pacer channel available + bbStatus = DSHOT_BITBANG_NO_PACER; + return NULL; + } + + gpioToMotorPort[portIndex] = bbPort; + ++usedMotorPorts; + + return bbPort; +} + +const timerHardware_t* dshotBitbangGetPacerTimer(int index) +{ + return index < usedMotorPorts ? bbPorts[index].timhw : NULL; +} + +// Return frequency of smallest change [state/sec] + +static uint32_t getDshotBaseFrequency(motorPwmProtocolTypes_e pwmProtocolType) +{ + switch (pwmProtocolType) { + case(PWM_TYPE_DSHOT1200): + return MOTOR_DSHOT1200_SYMBOL_RATE * MOTOR_DSHOT_STATE_PER_SYMBOL; + case(PWM_TYPE_DSHOT600): + return MOTOR_DSHOT600_SYMBOL_RATE * MOTOR_DSHOT_STATE_PER_SYMBOL; + case(PWM_TYPE_DSHOT300): + return MOTOR_DSHOT300_SYMBOL_RATE * MOTOR_DSHOT_STATE_PER_SYMBOL; + default: + case(PWM_TYPE_DSHOT150): + return MOTOR_DSHOT150_SYMBOL_RATE * MOTOR_DSHOT_STATE_PER_SYMBOL; + } +} + +static void bbAllocDma(bbPort_t *bbPort) +{ + const timerHardware_t *timhw = bbPort->timhw; + +#ifdef USE_DMA_SPEC + dmaoptValue_t dmaopt = dmaGetOptionByTimer(timhw); + const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timhw->tim, timhw->channel, dmaopt); + bbPort->dmaResource = dmaChannelSpec->ref; + bbPort->dmaChannel = dmaChannelSpec->channel; +#else + bbPort->dmaResource = timhw->dmaRef; + bbPort->dmaChannel = timhw->dmaChannel; +#endif + + dmaIdentifier_e dmaIdentifier = dmaGetIdentifier(bbPort->dmaResource); + dmaInit(dmaIdentifier, OWNER_DSHOT_BITBANG, RESOURCE_INDEX(bbPort - bbPorts)); + bbPort->dmaSource = timerDmaSource(timhw->channel); + + bbPacer_t *bbPacer = bbFindMotorPacer(timhw->tim); + bbPacer->dmaSources |= bbPort->dmaSource; + + dmaSetHandler(dmaIdentifier, bbDMAIrqHandler, NVIC_BUILD_PRIORITY(2, 1), (uint32_t)bbPort); + + bbDMA_ITConfig(bbPort); +} + +void bbDMAIrqHandler(dmaChannelDescriptor_t *descriptor) +{ + dbgPinHi(0); + + bbPort_t *bbPort = (bbPort_t *)descriptor->userParam; + + bbDMA_Cmd(bbPort, DISABLE); + + bbTIM_DMACmd(bbPort->timhw->tim, bbPort->dmaSource, DISABLE); + + if (DMA_GET_FLAG_STATUS(descriptor, DMA_IT_TEIF)) { + while (1) {}; + } + + DMA_CLEAR_FLAG(descriptor, DMA_IT_TCIF); + +#ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + if (bbPort->direction == DSHOT_BITBANG_DIRECTION_INPUT) { +#ifdef DEBUG_COUNT_INTERRUPT + bbPort->inputIrq++; +#endif + } else { +#ifdef DEBUG_COUNT_INTERRUPT + bbPort->outputIrq++; +#endif + + // Switch to input + + bbSwitchToInput(bbPort); + + bbTIM_DMACmd(bbPort->timhw->tim, bbPort->dmaSource, ENABLE); + } + } +#endif + dbgPinLo(0); +} + +// Setup bbPorts array elements so that they each have a TIM1 or TIM8 channel +// in timerHardware array for BB-DShot. + +static void bbFindPacerTimer(void) +{ + const timerHardware_t *timer; + bool usedTimerChannels[32][CC_CHANNELS_PER_TIMER] = {0}; + + for (int bbPortIndex = 0; bbPortIndex < MAX_SUPPORTED_MOTOR_PORTS; bbPortIndex++) { + for (unsigned timerIndex = 0; timerIndex < ARRAYLEN(bbTimerHardware); timerIndex++) { + timer = &bbTimerHardware[timerIndex]; + int timNumber = timerGetTIMNumber(timer->tim); + bool timerConflict = false; + for (int channel = 0; channel < CC_CHANNELS_PER_TIMER; channel++) { + if(timerGetOwner(timNumber, CC_CHANNEL_FROM_INDEX(channel))->owner != OWNER_FREE) { + timerConflict = true; + break; + } + } + if (timerConflict) { + continue; + } + +#ifdef USE_DMA_SPEC + dmaoptValue_t dmaopt = dmaGetOptionByTimer(timer); + const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt); + dmaResource_t *dma = dmaChannelSpec->ref; +#else + dmaResource_t *dma = timer->dmaRef; +#endif + dmaIdentifier_e dmaIdentifier = dmaGetIdentifier(dma); + if (dmaGetOwner(dmaIdentifier)->owner == OWNER_FREE && + !usedTimerChannels[timNumber][timer->channel]) { + usedTimerChannels[timNumber][timer->channel] = true; + break; + } + } + bbPorts[bbPortIndex].timhw = timer; + } +} + +static void bbTimebaseSetup(bbPort_t *bbPort, motorPwmProtocolTypes_e dshotProtocolType) +{ + uint32_t timerclock = timerClock(bbPort->timhw->tim); + + uint32_t outputFreq = getDshotBaseFrequency(dshotProtocolType); + dshotFrameUs = 1000000 * 17 * 3 / outputFreq; + bbPort->outputARR = timerclock / outputFreq - 1; + + // XXX Explain this formula + uint32_t inputFreq = outputFreq * 5 * 2 * DSHOT_BITBANG_TELEMETRY_OVER_SAMPLE / 24; + bbPort->inputARR = timerclock / inputFreq - 1; +} + +// +// bb only use pin info associated with timerHardware entry designated as TIM_USE_MOTOR; +// it does not use the timer channel associated with the pin. +// + +static bool bbMotorConfig(IO_t io, uint8_t motorIndex, motorPwmProtocolTypes_e pwmProtocolType, uint8_t output) +{ + int pinIndex = IO_GPIOPinIdx(io); + int portIndex = IO_GPIOPortIdx(io); + + bbPort_t *bbPort = bbFindMotorPort(portIndex); + + if (!bbPort) { + + // New port group + + bbPort = bbAllocMotorPort(portIndex); + if (!bbPort) { + return false; + } + + bbPort->gpio = IO_GPIO(io); + bbPort->portIndex = portIndex; + + bbPort->portOutputCount = MOTOR_DSHOT_BUFFER_SIZE; + bbPort->portOutputBuffer = &bbOutputBuffer[(bbPort - bbPorts) * MOTOR_DSHOT_BUFFER_SIZE]; + + bbPort->portInputCount = DSHOT_BITBANG_PORT_INPUT_BUFFER_LENGTH; + bbPort->portInputBuffer = &bbInputBuffer[(bbPort - bbPorts) * DSHOT_BITBANG_PORT_INPUT_BUFFER_LENGTH]; + + bbTimebaseSetup(bbPort, pwmProtocolType); + bbTIM_TimeBaseInit(bbPort, bbPort->outputARR); + bbTimerChannelInit(bbPort); + + bbAllocDma(bbPort); + bbDMAPreconfigure(bbPort, DSHOT_BITBANG_DIRECTION_OUTPUT); + bbDMAPreconfigure(bbPort, DSHOT_BITBANG_DIRECTION_INPUT); + + bbDMA_ITConfig(bbPort); + } + + bbMotors[motorIndex].pinIndex = pinIndex; + bbMotors[motorIndex].io = io; + bbMotors[motorIndex].output = output; + bbMotors[motorIndex].bbPort = bbPort; + + IOInit(io, OWNER_MOTOR, RESOURCE_INDEX(motorIndex)); + + // Setup GPIO_MODER and GPIO_ODR register manipulation values + + bbGpioSetup(&bbMotors[motorIndex]); + +#ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + bbOutputDataInit(bbPort->portOutputBuffer, (1 << pinIndex), DSHOT_BITBANG_INVERTED); + } else +#endif + { + bbOutputDataInit(bbPort->portOutputBuffer, (1 << pinIndex), DSHOT_BITBANG_NONINVERTED); + } + + bbSwitchToOutput(bbPort); + + bbMotors[motorIndex].configured = true; + + return true; +} + +static FAST_RAM_ZERO_INIT motorDevice_t bbDevice; +static FAST_RAM_ZERO_INIT timeUs_t lastSendUs; + +static bool bbUpdateStart(void) +{ +#ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { +#ifdef USE_DSHOT_TELEMETRY_STATS + const timeMs_t currentTimeMs = millis(); +#endif + timeUs_t currentUs = micros(); + // don't send while telemetry frames might still be incoming + if (cmpTimeUs(currentUs, lastSendUs) < (timeDelta_t)(40 + 2 * dshotFrameUs)) { + return false; + } + + for (int motorIndex = 0; motorIndex < MAX_SUPPORTED_MOTORS && motorIndex < motorCount; motorIndex++) { +#ifdef STM32F4 + uint32_t value = decode_bb_bitband( + bbMotors[motorIndex].bbPort->portInputBuffer, + bbMotors[motorIndex].bbPort->portInputCount - bbDMA_Count(bbMotors[motorIndex].bbPort), + bbMotors[motorIndex].pinIndex); +#else + uint32_t value = decode_bb( + bbMotors[motorIndex].bbPort->portInputBuffer, + bbMotors[motorIndex].bbPort->portInputCount - bbDMA_Count(bbMotors[motorIndex].bbPort), + bbMotors[motorIndex].pinIndex); +#endif + if (value == BB_NOEDGE) { + continue; + } + dshotTelemetryState.readCount++; + + if (value != BB_INVALID) { + dshotTelemetryState.motorState[motorIndex].telemetryValue = value; + dshotTelemetryState.motorState[motorIndex].telemetryActive = true; + if (motorIndex < 4) { + DEBUG_SET(DEBUG_DSHOT_RPM_TELEMETRY, motorIndex, value); + } + } else { + dshotTelemetryState.invalidPacketCount++; + } +#ifdef USE_DSHOT_TELEMETRY_STATS + updateDshotTelemetryQuality(&dshotTelemetryQuality[motorIndex], value != BB_INVALID, currentTimeMs); +#endif + } + } +#endif + for (int i = 0; i < usedMotorPorts; i++) { + bbDMA_Cmd(&bbPorts[i], DISABLE); + bbOutputDataClear(bbPorts[i].portOutputBuffer); + } + + return true; +} + +static void bbWriteInt(uint8_t motorIndex, uint16_t value) +{ + bbMotor_t *const bbmotor = &bbMotors[motorIndex]; + + if (!bbmotor->configured) { + return; + } + + // If there is a command ready to go overwrite the value and send that instead + + if (dshotCommandIsProcessing()) { + value = dshotCommandGetCurrent(motorIndex); + if (value) { + bbmotor->protocolControl.requestTelemetry = true; + } + } + + bbmotor->protocolControl.value = value; + + uint16_t packet = prepareDshotPacket(&bbmotor->protocolControl); + + bbPort_t *bbPort = bbmotor->bbPort; + +#ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + bbOutputDataSet(bbPort->portOutputBuffer, bbmotor->pinIndex, packet, DSHOT_BITBANG_INVERTED); + } else +#endif + { + bbOutputDataSet(bbPort->portOutputBuffer, bbmotor->pinIndex, packet, DSHOT_BITBANG_NONINVERTED); + } +} + +static void bbWrite(uint8_t motorIndex, float value) +{ + bbWriteInt(motorIndex, value); +} + +static void bbUpdateComplete(void) +{ + // If there is a dshot command loaded up, time it correctly with motor update + + if (!dshotCommandQueueEmpty()) { + if (!dshotCommandOutputIsEnabled(bbDevice.count)) { + return; + } + } + +#ifdef USE_DSHOT_TELEMETRY + for (int i = 0; i < usedMotorPorts; i++) { + bbPort_t *bbPort = &bbPorts[i]; + + if (useDshotTelemetry) { + if (bbPort->direction == DSHOT_BITBANG_DIRECTION_INPUT) { + bbPort->inputActive = false; + bbSwitchToOutput(bbPort); + } + + + bbDMA_Cmd(bbPort, ENABLE); + } + } +#endif + + lastSendUs = micros(); + for (int i = 0; i < usedMotorPacers; i++) { + bbPacer_t *bbPacer = &bbPacers[i]; + bbTIM_DMACmd(bbPacer->tim, bbPacer->dmaSources, ENABLE); + } +} + +static bool bbEnableMotors(void) +{ + for (int i = 0; i < motorCount; i++) { + IOConfigGPIO(bbMotors[i].io, bbMotors[i].iocfg); + } + return true; +} + +static void bbDisableMotors(void) +{ + return; +} + +static void bbShutdown(void) +{ + return; +} + +static bool bbIsMotorEnabled(uint8_t index) +{ + return bbMotors[index].enabled; +} + +static const motorDevConfig_t *lastMotorConfig; + +static void bbPostInit() +{ + bbFindPacerTimer(); + + for (int motorIndex = 0; motorIndex < MAX_SUPPORTED_MOTORS && motorIndex < motorCount; motorIndex++) { + + if (!bbMotorConfig(bbMotors[motorIndex].io, motorIndex, lastMotorConfig->motorPwmProtocol, bbMotors[motorIndex].output)) { + return NULL; + } + + + bbMotors[motorIndex].enabled = true; + + // Fill in motors structure for 4way access (XXX Should be refactored) + + motors[motorIndex].enabled = true; + } +} + +static motorVTable_t bbVTable = { + .postInit = bbPostInit, + .enable = bbEnableMotors, + .disable = bbDisableMotors, + .isMotorEnabled = bbIsMotorEnabled, + .updateStart = bbUpdateStart, + .write = bbWrite, + .writeInt = bbWriteInt, + .updateComplete = bbUpdateComplete, + .convertExternalToMotor = dshotConvertFromExternal, + .convertMotorToExternal = dshotConvertToExternal, + .shutdown = bbShutdown, +}; + +dshotBitbangStatus_e dshotBitbangGetStatus() +{ + return bbStatus; +} + +motorDevice_t *dshotBitbangDevInit(const motorDevConfig_t *motorConfig, uint8_t count) +{ + dbgPinInit(); + dbgPinLo(0); + dbgPinLo(1); + + lastMotorConfig = motorConfig; + bbDevice.vTable = bbVTable; + motorCount = count; + bbStatus = DSHOT_BITBANG_OK; + +#ifdef USE_DSHOT_TELEMETRY + useDshotTelemetry = motorConfig->useDshotTelemetry; +#endif + + for (int motorIndex = 0; motorIndex < MAX_SUPPORTED_MOTORS && motorIndex < motorCount; motorIndex++) { + const timerHardware_t *timerHardware = timerGetByTag(motorConfig->ioTags[motorIndex]); + const IO_t io = IOGetByTag(motorConfig->ioTags[motorIndex]); + + uint8_t output = motorConfig->motorPwmInversion ? timerHardware->output ^ TIMER_OUTPUT_INVERTED : timerHardware->output; + bbPuPdMode = (output & TIMER_OUTPUT_INVERTED) ? BB_GPIO_PULLDOWN : BB_GPIO_PULLUP; + +#ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + output ^= TIMER_OUTPUT_INVERTED; + } +#endif + + if (!IOIsFreeOrPreinit(io)) { + /* not enough motors initialised for the mixer or a break in the motors */ + bbDevice.vTable.write = motorWriteNull; + bbDevice.vTable.updateComplete = motorUpdateCompleteNull; + bbStatus = DSHOT_BITBANG_MOTOR_PIN_CONFLICT; + return NULL; + } + + int pinIndex = IO_GPIOPinIdx(io); + + bbMotors[motorIndex].pinIndex = pinIndex; + bbMotors[motorIndex].io = io; + bbMotors[motorIndex].output = output; +#if defined(STM32F4) || defined(STM32F3) + bbMotors[motorIndex].iocfg = IO_CONFIG(GPIO_Mode_OUT, GPIO_Speed_50MHz, GPIO_OType_PP, bbPuPdMode); +#elif defined(STM32F7) + bbMotors[motorIndex].iocfg = IO_CONFIG(GPIO_MODE_OUTPUT_PP, GPIO_SPEED_FREQ_VERY_HIGH, bbPuPdMode); +#endif + + IOInit(io, OWNER_MOTOR, RESOURCE_INDEX(motorIndex)); + IOConfigGPIO(io, bbMotors[motorIndex].iocfg); + if (output & TIMER_OUTPUT_INVERTED) { + IOLo(io); + } else { + IOHi(io); + } + + // Fill in motors structure for 4way access (XXX Should be refactored) + motors[motorIndex].io = bbMotors[motorIndex].io; + } + + return &bbDevice; +} + +#endif // USE_DSHOT_BB diff --git a/src/main/drivers/dshot_bitbang.h b/src/main/drivers/dshot_bitbang.h new file mode 100644 index 000000000..0404ee263 --- /dev/null +++ b/src/main/drivers/dshot_bitbang.h @@ -0,0 +1,41 @@ +/* + * This file is part of Cleanflight and Betaflight. + * + * Cleanflight and Betaflight are free software. You can redistribute + * this software and/or modify this software under the terms of the + * GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Cleanflight and Betaflight are distributed in the hope that they + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. + * + * If not, see . + */ + +#pragma once + +#include "drivers/timer.h" + +typedef enum { + DSHOT_BITBANG_OFF, + DSHOT_BITBANG_ON, + DSHOT_BITBANG_AUTO, +} dshotBitbangMode_e; + +typedef enum { + DSHOT_BITBANG_OK, + DSHOT_BITBANG_MOTOR_PIN_CONFLICT, + DSHOT_BITBANG_NO_PACER +} dshotBitbangStatus_e; + +struct motorDevConfig_s; +struct motorDevice_s; +struct motorDevice_s *dshotBitbangDevInit(const struct motorDevConfig_s *motorConfig, uint8_t motorCount); +dshotBitbangStatus_e dshotBitbangGetStatus(); +const timerHardware_t* dshotBitbangGetPacerTimer(int index); diff --git a/src/main/drivers/dshot_bitbang_decode.c b/src/main/drivers/dshot_bitbang_decode.c new file mode 100644 index 000000000..4b52cf443 --- /dev/null +++ b/src/main/drivers/dshot_bitbang_decode.c @@ -0,0 +1,290 @@ +#include +#include +#include + +#include "platform.h" + +#if defined(USE_DSHOT_TELEMETRY) + +#include "common/maths.h" +#include "common/utils.h" +#include "drivers/dshot.h" +#include "drivers/dshot_bitbang_decode.h" + +#define MIN_VALID_BBSAMPLES ((21 - 2) * 3) +#define MAX_VALID_BBSAMPLES ((21 + 2) * 3) + +// setting this define in dshot.h allows the cli command dshot_telemetry_info to +// display the received telemetry data in raw form which helps identify +// the root cause of packet decoding issues. + +#ifdef DEBUG_BBDECODE +uint16_t bbBuffer[134]; +#endif + + +/* Bit band SRAM definitions */ +#define BITBAND_SRAM_REF 0x20000000 +#define BITBAND_SRAM_BASE 0x22000000 +#define BITBAND_SRAM(a,b) ((BITBAND_SRAM_BASE + (((a)-BITBAND_SRAM_REF)<<5) + ((b)<<2))) // Convert SRAM address + +typedef struct bitBandWord_s { + uint32_t value; + uint32_t junk[15]; +} bitBandWord_t; + +#ifdef DEBUG_BBDECODE +uint32_t sequence[MAX_GCR_EDGES]; +int sequenceIndex = 0; +#endif + + +static uint32_t decode_bb_value(uint32_t value, uint16_t buffer[], uint32_t count, uint32_t bit) +{ +#ifndef DEBUG_BBDECODE + UNUSED(buffer); + UNUSED(count); + UNUSED(bit); +#endif +#define iv 0xffffffff + // First bit is start bit so discard it. + value &= 0xfffff; + static const uint32_t decode[32] = { + iv, iv, iv, iv, iv, iv, iv, iv, iv, 9, 10, 11, iv, 13, 14, 15, + iv, iv, 2, 3, iv, 5, 6, 7, iv, 0, 8, 1, iv, 4, 12, iv }; + + uint32_t decodedValue = decode[value & 0x1f]; + decodedValue |= decode[(value >> 5) & 0x1f] << 4; + decodedValue |= decode[(value >> 10) & 0x1f] << 8; + decodedValue |= decode[(value >> 15) & 0x1f] << 12; + + uint32_t csum = decodedValue; + csum = csum ^ (csum >> 8); // xor bytes + csum = csum ^ (csum >> 4); // xor nibbles + + if ((csum & 0xf) != 0xf || decodedValue > 0xffff) { +#ifdef DEBUG_BBDECODE + memcpy(dshotTelemetryState.inputBuffer, sequence, sizeof(sequence)); + for (unsigned i = 0; i < count; i++) { + bbBuffer[i] = !!(buffer[i] & (1 << bit)); + } +#endif + value = BB_INVALID; + } else { + value = decodedValue >> 4; + + if (value == 0x0fff) { + return 0; + } + // Convert value to 16 bit from the GCR telemetry format (eeem mmmm mmmm) + value = (value & 0x000001ff) << ((value & 0xfffffe00) >> 9); + if (!value) { + return BB_INVALID; + } + // Convert period to erpm * 100 + value = (1000000 * 60 / 100 + value / 2) / value; + } + return value; +} + + +uint32_t decode_bb_bitband( uint16_t buffer[], uint32_t count, uint32_t bit) +{ +#ifdef DEBUG_BBDECODE + memset(sequence, 0, sizeof(sequence)); + sequenceIndex = 0; +#endif + uint32_t value = 0; + + bitBandWord_t* p = (bitBandWord_t*)BITBAND_SRAM((uint32_t)buffer, bit); + bitBandWord_t* b = p; + bitBandWord_t* endP = p + (count - MIN_VALID_BBSAMPLES); + + // Eliminate leading high signal level by looking for first zero bit in data stream. + // Manual loop unrolling and branch hinting to produce faster code. + while (p < endP) { + if (__builtin_expect((!(p++)->value), 0) || + __builtin_expect((!(p++)->value), 0) || + __builtin_expect((!(p++)->value), 0) || + __builtin_expect((!(p++)->value), 0)) { + break; + } + } + + if (p >= endP) { + // not returning telemetry is ok if the esc cpu is + // overburdened. in that case no edge will be found and + // BB_NOEDGE indicates the condition to caller + return BB_NOEDGE; + } + + int remaining = MIN(count - (p - b), (unsigned int)MAX_VALID_BBSAMPLES); + + bitBandWord_t* oldP = p; + uint32_t bits = 0; + endP = p + remaining; + +#ifdef DEBUG_BBDECODE + sequence[sequenceIndex++] = p - b; +#endif + + while (endP > p) { + while (endP > p) { + // Look for next positive edge. Manual loop unrolling and branch hinting to produce faster code. + if(__builtin_expect(!(p++)->value, 1) && + __builtin_expect(!(p++)->value, 1) && + __builtin_expect(!(p++)->value, 1) && + __builtin_expect(!(p++)->value, 1)) { + continue; + } + +#ifdef DEBUG_BBDECODE + sequence[sequenceIndex++] = p - b; +#endif + // A level of length n gets decoded to a sequence of bits of + // the form 1000 with a length of (n+1) / 3 to account for 3x + // oversampling. + const int len = MAX((p - oldP + 1) / 3, 1); + bits += len; + value <<= len; + value |= 1 << (len - 1); + oldP = p; + break; + } + + // Look for next zero edge. Manual loop unrolling and branch hinting to produce faster code. + while (endP > p) { + if (__builtin_expect((p++)->value, 1) && + __builtin_expect((p++)->value, 1) && + __builtin_expect((p++)->value, 1) && + __builtin_expect((p++)->value, 1)) { + continue; + } +#ifdef DEBUG_BBDECODE + sequence[sequenceIndex++] = p - b; +#endif + // A level of length n gets decoded to a sequence of bits of + // the form 1000 with a length of (n+1) / 3 to account for 3x + // oversampling. + const int len = MAX((p - oldP + 1) / 3, 1); + bits += len; + value <<= len; + value |= 1 << (len - 1); + oldP = p; + break; + } + } + + if (bits < 18) { + return BB_NOEDGE; + } + + // length of last sequence has to be inferred since the last bit with inverted dshot is high + const int nlen = 21 - bits; + if (nlen < 0) { + value = BB_INVALID; + } + +#ifdef DEBUG_BBDECODE + sequence[sequenceIndex] = sequence[sequenceIndex] + (nlen) * 3; + sequenceIndex++; +#endif + if (nlen > 0) { + value <<= nlen; + value |= 1 << (nlen - 1); + } + return decode_bb_value(value, buffer, count, bit); +} + +FAST_CODE uint32_t decode_bb( uint16_t buffer[], uint32_t count, uint32_t bit) +{ +#ifdef DEBUG_BBDECODE + memset(sequence, 0, sizeof(sequence)); + sequenceIndex = 0; +#endif + uint32_t mask = 1 << bit; + +#ifdef DEBUG_BBDECODE + uint32_t sequence[MAX_GCR_EDGES]; + memset(sequence, 0, sizeof(sequence)); + int sequenceIndex = 0; +#endif + + uint16_t lastValue = 0; + uint32_t value = 0; + + uint16_t* p = buffer; + uint16_t* endP = p + count - MIN_VALID_BBSAMPLES; + // Eliminate leading high signal level by looking for first zero bit in data stream. + // Manual loop unrolling and branch hinting to produce faster code. + while (p < endP) { + if (__builtin_expect(!(*p++ & mask), 0) || + __builtin_expect(!(*p++ & mask), 0) || + __builtin_expect(!(*p++ & mask), 0) || + __builtin_expect(!(*p++ & mask), 0)) { + break; + } + } + + if(*p & mask) { + // not returning telemetry is ok if the esc cpu is + // overburdened. in that case no edge will be found and + // BB_NOEDGE indicates the condition to caller + return BB_NOEDGE; + } + + int remaining = MIN(count - (p - buffer), (unsigned int)MAX_VALID_BBSAMPLES); + + uint16_t* oldP = p; + uint32_t bits = 0; + endP = p + remaining; + +#ifdef DEBUG_BBDECODE + sequence[sequenceIndex++] = p - buffer; +#endif + + // Look for next edge. Manual loop unrolling and branch hinting to produce faster code. + while (endP > p) { + if (__builtin_expect((*p++ & mask) == lastValue, 1) && + __builtin_expect((*p++ & mask) == lastValue, 1) && + __builtin_expect((*p++ & mask) == lastValue, 1) && + __builtin_expect((*p++ & mask) == lastValue, 1)) { + continue; + } + +#ifdef DEBUG_BBDECODE + sequence[sequenceIndex++] = p - buffer; +#endif + // A level of length n gets decoded to a sequence of bits of + // the form 1000 with a length of (n+1) / 3 to account for 3x + // oversampling. + const int len = MAX((p - oldP + 1) / 3,1); + bits += len; + value <<= len; + value |= 1 << (len - 1); + oldP = p; + lastValue = *(p-1) & mask; + } + + // length of last sequence has to be inferred since the last bit with inverted dshot is high + if (bits < 18) { + return BB_NOEDGE; + } + + const int nlen = 21 - bits; +#ifdef DEBUG_BBDECODE + sequence[sequenceIndex] = sequence[sequenceIndex] + (nlen) * 3; + sequenceIndex++; +#endif + + if (nlen < 0) { + value = BB_INVALID; + } + if (nlen > 0) { + value <<= nlen; + value |= 1 << (nlen - 1); + } + return decode_bb_value(value, buffer, count, bit); +} + +#endif diff --git a/src/main/drivers/dshot_bitbang_decode.h b/src/main/drivers/dshot_bitbang_decode.h new file mode 100644 index 000000000..da546c06d --- /dev/null +++ b/src/main/drivers/dshot_bitbang_decode.h @@ -0,0 +1,29 @@ +/* + * This file is part of Cleanflight and Betaflight. + * + * Cleanflight and Betaflight are free software. You can redistribute + * this software and/or modify this software under the terms of the + * GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Cleanflight and Betaflight are distributed in the hope that they + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. + * + * If not, see . + */ + +#if defined(USE_DSHOT) && defined(USE_DSHOT_TELEMETRY) + +#define BB_NOEDGE 0xfffe +#define BB_INVALID 0xffff + +uint32_t decode_bb(uint16_t buffer[], uint32_t count, uint32_t mask); +uint32_t decode_bb_bitband( uint16_t buffer[], uint32_t count, uint32_t bit); + +#endif diff --git a/src/main/drivers/dshot_bitbang_impl.h b/src/main/drivers/dshot_bitbang_impl.h new file mode 100644 index 000000000..927e6ce48 --- /dev/null +++ b/src/main/drivers/dshot_bitbang_impl.h @@ -0,0 +1,217 @@ +/* + * This file is part of Cleanflight and Betaflight. + * + * Cleanflight and Betaflight are free software. You can redistribute + * this software and/or modify this software under the terms of the + * GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Cleanflight and Betaflight are distributed in the hope that they + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. + * + * If not, see . + */ + +#pragma once + +#include "common/time.h" +#include "drivers/motor.h" + +#define USE_DMA_REGISTER_CACHE + +#define DEBUG_COUNT_INTERRUPT +#define DEBUG_MONITOR_PACER + +#define MAX_SUPPORTED_MOTOR_PORTS 4 // Max direct dshot port groups, limited by number of usable timer (TIM1 and TIM8) x number of channels per timer (4), 3 is enough to cover motor pins on GPIOA, GPIOB and GPIOC. + +#define DSHOT_BITBANG_TELEMETRY_OVER_SAMPLE 3 + +#define DSHOT_BITBANG_DIRECTION_OUTPUT 0 +#define DSHOT_BITBANG_DIRECTION_INPUT 1 + +#define DSHOT_BITBANG_INVERTED true +#define DSHOT_BITBANG_NONINVERTED false + +// XXX MOTOR_xSHOTyyyy_HZ is not usable as generic frequency for timers. +// XXX Trying to fiddle with constants here. + +// Symbol rate [symbol/sec] +#define MOTOR_DSHOT1200_SYMBOL_RATE (1200 * 1000) +#define MOTOR_DSHOT600_SYMBOL_RATE (600 * 1000) +#define MOTOR_DSHOT300_SYMBOL_RATE (300 * 1000) +#define MOTOR_DSHOT150_SYMBOL_RATE (150 * 1000) + +#define MOTOR_DSHOT_SYMBOL_TIME_NS(rate) (1000000000 / (rate)) + +#define MOTOR_DSHOT_BIT_PER_SYMBOL 1 + +#define MOTOR_DSHOT_STATE_PER_SYMBOL 3 // Initial high, 0/1, low + +#define MOTOR_DSHOT_FRAME_BITS 16 + +#define MOTOR_DSHOT_FRAME_TIME_NS(rate) ((MOTOR_DSHOT_FRAME_BITS / MOTOR_DSHOT_BIT_PER_SYMBOL) * MOTOR_DSHOT_SYMBOL_TIME_NS(rate)) + +#define MOTOR_DSHOT_TELEMETRY_WINDOW_US (30000 + MOTOR_DSHOT_FRAME_TIME_NS(rate) * (1.1)) / 1000 + +#define MOTOR_DSHOT_CHANGE_INTERVAL_NS(rate) (MOTOR_DSHOT_SYMBOL_TIME_NS(rate) / MOTOR_DSHOT_STATE_PER_SYMBOL) + +#define MOTOR_DSHOT_GCR_CHANGE_INTERVAL_NS(rate) (MOTOR_DSHOT_CHANGE_INTERVAL_NS(rate) * 5 / 4) + +#define MOTOR_DSHOT_BUFFER_SIZE ((MOTOR_DSHOT_FRAME_BITS / MOTOR_DSHOT_BIT_PER_SYMBOL) * MOTOR_DSHOT_STATE_PER_SYMBOL) + +#ifdef USE_HAL_DRIVER +#define BB_GPIO_PULLDOWN GPIO_PULLDOWN +#define BB_GPIO_PULLUP GPIO_PULLUP +#else +#define BB_GPIO_PULLDOWN GPIO_PuPd_DOWN +#define BB_GPIO_PULLUP GPIO_PuPd_UP +#endif + +#ifdef USE_DMA_REGISTER_CACHE +typedef struct dmaRegCache_s { +#if defined(STM32F4) || defined(STM32F7) + uint32_t CR; + uint32_t FCR; + uint32_t NDTR; + uint32_t PAR; + uint32_t M0AR; +#else +#error No MCU dependent code here +#endif +} dmaRegCache_t; +#endif + +// Per pacer timer + +typedef struct bbPacer_s { + TIM_TypeDef *tim; + uint16_t dmaSources; +} bbPacer_t; + +// Per GPIO port and timer channel + +typedef struct bbPort_s { + int portIndex; + GPIO_TypeDef *gpio; + const timerHardware_t *timhw; +#ifdef USE_HAL_DRIVER + uint32_t llChannel; +#endif + + uint16_t dmaSource; + + dmaResource_t *dmaResource; // DMA resource for this port & timer channel + uint32_t dmaChannel; // DMA channel or peripheral request + + uint8_t direction; + +#ifdef USE_DMA_REGISTER_CACHE + // DMA resource register cache + dmaRegCache_t dmaRegOutput; + dmaRegCache_t dmaRegInput; +#endif + + // For direct manipulation of GPIO_MODER register + uint32_t gpioModeMask; + uint32_t gpioModeInput; + uint32_t gpioModeOutput; + + // Idle value + uint32_t gpioIdleBSRR; + +#ifdef USE_HAL_DRIVER + LL_TIM_InitTypeDef timeBaseInit; +#else + TIM_TimeBaseInitTypeDef timeBaseInit; +#endif + + // Output + uint16_t outputARR; +#ifdef USE_HAL_DRIVER + LL_DMA_InitTypeDef outputDmaInit; +#else + DMA_InitTypeDef outputDmaInit; +#endif + uint32_t *portOutputBuffer; + uint32_t portOutputCount; + + // Input + uint16_t inputARR; +#ifdef USE_HAL_DRIVER + LL_DMA_InitTypeDef inputDmaInit; +#else + DMA_InitTypeDef inputDmaInit; +#endif + uint16_t *portInputBuffer; + uint32_t portInputCount; + bool inputActive; + + // Misc +#ifdef DEBUG_COUNT_INTERRUPT + uint32_t outputIrq; + uint32_t inputIrq; +#endif +} bbPort_t; + +// Per motor output + +typedef struct bbMotor_s { + dshotProtocolControl_t protocolControl; + int pinIndex; // pinIndex of this motor output within a group that bbPort points to + int portIndex; + IO_t io; // IO_t for this output + uint8_t output; + uint32_t iocfg; + bbPort_t *bbPort; + bool configured; + bool enabled; +} bbMotor_t; + +#define MAX_MOTOR_PACERS 4 +extern FAST_RAM_ZERO_INIT bbPacer_t bbPacers[MAX_MOTOR_PACERS]; // TIM1 or TIM8 +extern FAST_RAM_ZERO_INIT int usedMotorPacers; + +extern FAST_RAM_ZERO_INIT bbPort_t bbPorts[MAX_SUPPORTED_MOTOR_PORTS]; +extern FAST_RAM_ZERO_INIT int usedMotorPorts; + +extern FAST_RAM_ZERO_INIT bbMotor_t bbMotors[MAX_SUPPORTED_MOTORS]; + +extern uint8_t bbPuPdMode; + +// DMA buffers +// Note that we are not sharing input and output buffers, +// as output buffer is only modified for middle bits + +// DMA output buffer: +// DShot requires 3 [word/bit] * 16 [bit] = 48 [word] +extern uint32_t bbOutputBuffer[MOTOR_DSHOT_BUFFER_SIZE * MAX_SUPPORTED_MOTOR_PORTS]; + +// DMA input buffer +// (30us + + ) / +// = * 16 +// Temporary size for DS600 +// = 26us +// = 0.44us +// = 10% +// (30 + 26 + 3) / 0.44 = 134 +#define DSHOT_BITBANG_PORT_INPUT_BUFFER_LENGTH 134 +extern uint16_t bbInputBuffer[DSHOT_BITBANG_PORT_INPUT_BUFFER_LENGTH * MAX_SUPPORTED_MOTOR_PORTS]; + +void bbGpioSetup(bbMotor_t *bbMotor); +void bbTimerChannelInit(bbPort_t *bbPort); +void bbDMAPreconfigure(bbPort_t *bbPort, uint8_t direction); +void bbDMAIrqHandler(dmaChannelDescriptor_t *descriptor); +void bbSwitchToOutput(bbPort_t * bbPort); +void bbSwitchToInput(bbPort_t * bbPort); + +void bbTIM_TimeBaseInit(bbPort_t *bbPort, uint16_t period); +void bbTIM_DMACmd(TIM_TypeDef* TIMx, uint16_t TIM_DMASource, FunctionalState NewState); +void bbDMA_ITConfig(bbPort_t *bbPort); +void bbDMA_Cmd(bbPort_t *bbPort, FunctionalState NewState); +int bbDMA_Count(bbPort_t *bbPort); diff --git a/src/main/drivers/dshot_bitbang_ll.c b/src/main/drivers/dshot_bitbang_ll.c new file mode 100644 index 000000000..ac961cc14 --- /dev/null +++ b/src/main/drivers/dshot_bitbang_ll.c @@ -0,0 +1,314 @@ +/* + * This file is part of Cleanflight and Betaflight. + * + * Cleanflight and Betaflight are free software. You can redistribute + * this software and/or modify this software under the terms of the + * GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Cleanflight and Betaflight are distributed in the hope that they + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. + * + * If not, see . + */ + +#include +#include +#include + +#include "platform.h" + +#ifdef USE_DSHOT_BITBANG + +#include "build/atomic.h" +#include "build/debug.h" + +#include "drivers/io.h" +#include "drivers/io_impl.h" +#include "drivers/dma.h" +#include "drivers/dma_reqmap.h" +#include "drivers/dshot.h" +#include "drivers/dshot_bitbang_impl.h" +#include "drivers/dshot_command.h" +#include "drivers/motor.h" +#include "drivers/nvic.h" +#include "drivers/pwm_output.h" // XXX for pwmOutputPort_t motors[]; should go away with refactoring +#include "drivers/time.h" +#include "drivers/timer.h" + +#include "pg/motor.h" + +// Setup GPIO_MODER and GPIO_ODR register manipulation values + +void bbGpioSetup(bbMotor_t *bbMotor) +{ + bbPort_t *bbPort = bbMotor->bbPort; + int pinIndex = bbMotor->pinIndex; + + bbPort->gpioModeMask |= (GPIO_MODER_MODER0 << (pinIndex * 2)); + bbPort->gpioModeInput |= (LL_GPIO_MODE_INPUT << (pinIndex * 2)); + bbPort->gpioModeOutput |= (LL_GPIO_MODE_OUTPUT << (pinIndex * 2)); + +#ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + bbPort->gpioIdleBSRR |= (1 << pinIndex); // BS (lower half) + } else +#endif + { + bbPort->gpioIdleBSRR |= (1 << (pinIndex + 16)); // BR (higher half) + } + +#ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + IOWrite(bbMotor->io, 1); + } else +#endif + { + IOWrite(bbMotor->io, 0); + } + +#if defined(STM32F7) + IOConfigGPIO(bbMotor->io, IO_CONFIG(GPIO_MODE_OUTPUT_PP, GPIO_SPEED_FREQ_HIGH, bbPuPdMode)); +#else +#error MCU dependent code required +#endif +} + +void bbTimerChannelInit(bbPort_t *bbPort) +{ + const timerHardware_t *timhw = bbPort->timhw; + + switch (bbPort->timhw->channel) { + case TIM_CHANNEL_1: bbPort->llChannel = LL_TIM_CHANNEL_CH1; break; + case TIM_CHANNEL_2: bbPort->llChannel = LL_TIM_CHANNEL_CH2; break; + case TIM_CHANNEL_3: bbPort->llChannel = LL_TIM_CHANNEL_CH3; break; + case TIM_CHANNEL_4: bbPort->llChannel = LL_TIM_CHANNEL_CH4; break; + } + + LL_TIM_OC_InitTypeDef ocInit; + LL_TIM_OC_StructInit(&ocInit); + ocInit.OCMode = LL_TIM_OCMODE_PWM1; + ocInit.OCIdleState = LL_TIM_OCIDLESTATE_HIGH; + ocInit.OCState = LL_TIM_OCSTATE_ENABLE; + ocInit.OCPolarity = LL_TIM_OCPOLARITY_LOW; + ocInit.CompareValue = 10; // Duty doesn't matter, but too value small would make monitor output invalid + + //TIM_Cmd(bbPort->timhw->tim, DISABLE); + LL_TIM_DisableCounter(bbPort->timhw->tim); + + //timerOCInit(timhw->tim, timhw->channel, &TIM_OCStruct); + LL_TIM_OC_Init(timhw->tim, bbPort->llChannel, &ocInit); + + //timerOCPreloadConfig(timhw->tim, timhw->channel, TIM_OCPreload_Enable); + LL_TIM_OC_EnablePreload(timhw->tim, bbPort->llChannel); + +#ifdef DEBUG_MONITOR_PACER + if (timhw->tag) { + IO_t io = IOGetByTag(timhw->tag); + IOConfigGPIOAF(io, IOCFG_AF_PP, timhw->alternateFunction); + IOInit(io, OWNER_DSHOT_BITBANG, 0); + //TIM_CtrlPWMOutputs(timhw->tim, ENABLE); + LL_TIM_EnableAllOutputs(timhw->tim); + } +#endif + + // Enable and keep it running + + //TIM_Cmd(bbPort->timhw->tim, ENABLE); + LL_TIM_EnableCounter(bbPort->timhw->tim); +} + +#ifdef USE_DMA_REGISTER_CACHE +void bbLoadDMARegs(dmaResource_t *dmaResource, dmaRegCache_t *dmaRegCache) +{ + ((DMA_ARCH_TYPE *)dmaResource)->CR = dmaRegCache->CR; + ((DMA_ARCH_TYPE *)dmaResource)->FCR = dmaRegCache->FCR; + ((DMA_ARCH_TYPE *)dmaResource)->NDTR = dmaRegCache->NDTR; + ((DMA_ARCH_TYPE *)dmaResource)->PAR = dmaRegCache->PAR; + ((DMA_ARCH_TYPE *)dmaResource)->M0AR = dmaRegCache->M0AR; +} + +static void bbSaveDMARegs(dmaResource_t *dmaResource, dmaRegCache_t *dmaRegCache) +{ + dmaRegCache->CR = ((DMA_ARCH_TYPE *)dmaResource)->CR; + dmaRegCache->FCR = ((DMA_ARCH_TYPE *)dmaResource)->FCR; + dmaRegCache->NDTR = ((DMA_ARCH_TYPE *)dmaResource)->NDTR; + dmaRegCache->PAR = ((DMA_ARCH_TYPE *)dmaResource)->PAR; + dmaRegCache->M0AR = ((DMA_ARCH_TYPE *)dmaResource)->M0AR; +} +#endif + +void bbSwitchToOutput(bbPort_t * bbPort) +{ + // Output idle level before switching to output + // Use BSRR register for this + // Normal: Use BR (higher half) + // Inverted: Use BS (lower half) + + WRITE_REG(bbPort->gpio->BSRR, bbPort->gpioIdleBSRR); + + // Set GPIO to output + + ATOMIC_BLOCK(NVIC_PRIO_TIMER) { + MODIFY_REG(bbPort->gpio->MODER, bbPort->gpioModeMask, bbPort->gpioModeOutput); + } + + // Reinitialize port group DMA for output + + dmaResource_t *dmaResource = bbPort->dmaResource; +#ifdef USE_DMA_REGISTER_CACHE + bbDMA_Cmd(bbPort, DISABLE); + bbLoadDMARegs(dmaResource, &bbPort->dmaRegOutput); +#else + //xDMA_DeInit(dmaResource); + xLL_EX_DMA_Deinit(dmaResource); + //xDMA_Init(dmaResource, &bbPort->outputDmaInit); + xLL_EX_DMA_Init(dmaResource, &bbPort->outputDmaInit); + // Needs this, as it is DeInit'ed above... + //xDMA_ITConfig(dmaResource, DMA_IT_TC, ENABLE); + xLL_EX_DMA_EnableIT_TC(dmaResource); +#endif + + // Reinitialize pacer timer for output + + bbPort->timhw->tim->ARR = bbPort->outputARR; + + bbPort->direction = DSHOT_BITBANG_DIRECTION_OUTPUT; +} + +#ifdef USE_DSHOT_TELEMETRY +void bbSwitchToInput(bbPort_t *bbPort) +{ + // Set GPIO to input + + ATOMIC_BLOCK(NVIC_PRIO_TIMER) { + MODIFY_REG(bbPort->gpio->MODER, bbPort->gpioModeMask, bbPort->gpioModeInput); + } + + // Reinitialize port group DMA for input + + dmaResource_t *dmaResource = bbPort->dmaResource; +#ifdef USE_DMA_REGISTER_CACHE + bbLoadDMARegs(dmaResource, &bbPort->dmaRegInput); +#else + //xDMA_DeInit(dmaResource); + xLL_EX_DMA_Deinit(dmaResource); + //xDMA_Init(dmaResource, &bbPort->inputDmaInit); + xLL_EX_DMA_Init(dmaResource, &bbPort->inputDmaInit); + + // Needs this, as it is DeInit'ed above... + //xDMA_ITConfig(dmaResource, DMA_IT_TC, ENABLE); + xLL_EX_DMA_EnableIT_TC(dmaResource); +#endif + + // Reinitialize pacer timer for input + + bbPort->timhw->tim->ARR = bbPort->inputARR; + + bbDMA_Cmd(bbPort, ENABLE); + + bbPort->direction = DSHOT_BITBANG_DIRECTION_INPUT; +} +#endif + +void bbDMAPreconfigure(bbPort_t *bbPort, uint8_t direction) +{ + LL_DMA_InitTypeDef *dmainit = (direction == DSHOT_BITBANG_DIRECTION_OUTPUT) ? &bbPort->outputDmaInit : &bbPort->inputDmaInit; + + LL_DMA_StructInit(dmainit); + + dmainit->Mode = LL_DMA_MODE_NORMAL; + dmainit->Channel = bbPort->dmaChannel; + dmainit->PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + dmainit->MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + dmainit->FIFOMode = LL_DMA_FIFOMODE_ENABLE ; +#if 0 + dmainit->DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull ; + dmainit->DMA_MemoryBurst = DMA_MemoryBurst_Single ; + dmainit->DMA_PeripheralBurst = DMA_PeripheralBurst_Single; +#endif + + if (direction == DSHOT_BITBANG_DIRECTION_OUTPUT) { + dmainit->Priority = LL_DMA_PRIORITY_VERYHIGH; + dmainit->Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + dmainit->NbData = bbPort->portOutputCount; + dmainit->PeriphOrM2MSrcAddress = (uint32_t)&bbPort->gpio->BSRR; + dmainit->PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + dmainit->MemoryOrM2MDstAddress = (uint32_t)bbPort->portOutputBuffer; + dmainit->MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + +#ifdef USE_DMA_REGISTER_CACHE + xLL_EX_DMA_Init(bbPort->dmaResource, dmainit); + bbSaveDMARegs(bbPort->dmaResource, &bbPort->dmaRegOutput); +#endif + } else { + dmainit->Priority = LL_DMA_PRIORITY_HIGH; + dmainit->Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; + dmainit->NbData = bbPort->portInputCount; + + dmainit->PeriphOrM2MSrcAddress = (uint32_t)&bbPort->gpio->IDR; + dmainit->PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD; + dmainit->MemoryOrM2MDstAddress = (uint32_t)bbPort->portInputBuffer; + dmainit->MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + +#ifdef USE_DMA_REGISTER_CACHE + xLL_EX_DMA_Init(bbPort->dmaResource, dmainit); + bbSaveDMARegs(bbPort->dmaResource, &bbPort->dmaRegInput); +#endif + } +} + +void bbTIM_TimeBaseInit(bbPort_t *bbPort, uint16_t period) +{ + LL_TIM_InitTypeDef *init = &bbPort->timeBaseInit; + + init->Prescaler = 0; // Feed raw timerClock + init->ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + init->CounterMode = LL_TIM_COUNTERMODE_UP; + init->Autoreload = period; + //TIM_TimeBaseInit(bbPort->timhw->tim, &bbPort->timeBaseInit); + LL_TIM_Init(bbPort->timhw->tim, init); + MODIFY_REG(bbPort->timhw->tim->CR1, TIM_CR1_ARPE, TIM_AUTORELOAD_PRELOAD_ENABLE); +} + +void bbTIM_DMACmd(TIM_TypeDef* TIMx, uint16_t TIM_DMASource, FunctionalState NewState) +{ + //TIM_DMACmd(TIMx, TIM_DMASource, NewState); + if (NewState == ENABLE) { + SET_BIT(TIMx->DIER, TIM_DMASource); + } else { + CLEAR_BIT(TIMx->DIER, TIM_DMASource); + } +} + +void bbDMA_ITConfig(bbPort_t *bbPort) +{ + //xDMA_ITConfig(bbPort->dmaResource, DMA_IT_TC, ENABLE); + + xLL_EX_DMA_EnableIT_TC(bbPort->dmaResource); + + SET_BIT(((DMA_Stream_TypeDef *)(bbPort->dmaResource))->CR, DMA_SxCR_TCIE|DMA_SxCR_TEIE); +} + +void bbDMA_Cmd(bbPort_t *bbPort, FunctionalState NewState) +{ + //xDMA_Cmd(bbPort->dmaResource, NewState); + + if (NewState == ENABLE) { + xLL_EX_DMA_EnableResource(bbPort->dmaResource); + } else { + xLL_EX_DMA_DisableResource(bbPort->dmaResource); + } +} + +int bbDMA_Count(bbPort_t *bbPort) +{ + return xLL_EX_DMA_GetDataLength(bbPort->dmaResource); +} +#endif // USE_DSHOT_BITBANG diff --git a/src/main/drivers/dshot_bitbang_stdperiph.c b/src/main/drivers/dshot_bitbang_stdperiph.c new file mode 100644 index 000000000..c282a6752 --- /dev/null +++ b/src/main/drivers/dshot_bitbang_stdperiph.c @@ -0,0 +1,290 @@ +/* + * This file is part of Cleanflight and Betaflight. + * + * Cleanflight and Betaflight are free software. You can redistribute + * this software and/or modify this software under the terms of the + * GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Cleanflight and Betaflight are distributed in the hope that they + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. + * + * If not, see . + */ + +#include +#include +#include + +#include "platform.h" + +#ifdef USE_DSHOT_BITBANG + +#include "build/atomic.h" +#include "build/debug.h" + +#include "drivers/io.h" +#include "drivers/io_impl.h" +#include "drivers/dma.h" +#include "drivers/dma_reqmap.h" +#include "drivers/dshot.h" +#include "drivers/dshot_bitbang_impl.h" +#include "drivers/dshot_command.h" +#include "drivers/motor.h" +#include "drivers/nvic.h" +#include "drivers/pwm_output.h" // XXX for pwmOutputPort_t motors[]; should go away with refactoring +#include "drivers/time.h" +#include "drivers/timer.h" + +#include "pg/motor.h" + +//TODO: Change these to be only used if USE_DEBUG_PIN is not defined once the debug_pin functionality has been merged +#define dbgPinInit() +#define dbgPinHi(x) +#define dbgPinLo(x) + +void bbGpioSetup(bbMotor_t *bbMotor) +{ + bbPort_t *bbPort = bbMotor->bbPort; + int pinIndex = bbMotor->pinIndex; + + bbPort->gpioModeMask |= (GPIO_MODER_MODER0 << (pinIndex * 2)); + bbPort->gpioModeInput |= (GPIO_Mode_IN << (pinIndex * 2)); + bbPort->gpioModeOutput |= (GPIO_Mode_OUT << (pinIndex * 2)); + +#ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + bbPort->gpioIdleBSRR |= (1 << pinIndex); // BS (lower half) + } else +#endif + { + bbPort->gpioIdleBSRR |= (1 << (pinIndex + 16)); // BR (higher half) + } + +#ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + IOWrite(bbMotor->io, 1); + } else +#endif + { + IOWrite(bbMotor->io, 0); + } + +#if defined(STM32F4) + IOConfigGPIO(bbMotor->io, IO_CONFIG(GPIO_Mode_OUT, GPIO_Speed_50MHz, GPIO_OType_PP, bbPuPdMode)); +#else +#error MCU dependent code required +#endif +} + +void bbTimerChannelInit(bbPort_t *bbPort) +{ + const timerHardware_t *timhw = bbPort->timhw; + + TIM_OCInitTypeDef TIM_OCStruct; + + TIM_OCStructInit(&TIM_OCStruct); + TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM1; + TIM_OCStruct.TIM_OCIdleState = TIM_OCIdleState_Set; + TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable; + TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low; + + TIM_OCStruct.TIM_Pulse = 10; // Duty doesn't matter, but too value small would make monitor output invalid + + TIM_Cmd(bbPort->timhw->tim, DISABLE); + + timerOCInit(timhw->tim, timhw->channel, &TIM_OCStruct); + // timerOCPreloadConfig(timhw->tim, timhw->channel, TIM_OCPreload_Enable); + +#ifdef DEBUG_MONITOR_PACER + if (timhw->tag) { + IO_t io = IOGetByTag(timhw->tag); + IOConfigGPIOAF(io, IOCFG_AF_PP, timhw->alternateFunction); + IOInit(io, OWNER_DSHOT_BITBANG, 0); + TIM_CtrlPWMOutputs(timhw->tim, ENABLE); + } +#endif + + // Enable and keep it running + + TIM_Cmd(bbPort->timhw->tim, ENABLE); +} + +#ifdef USE_DMA_REGISTER_CACHE + +void bbLoadDMARegs(dmaResource_t *dmaResource, dmaRegCache_t *dmaRegCache) +{ + ((DMA_Stream_TypeDef *)dmaResource)->CR = dmaRegCache->CR; + ((DMA_Stream_TypeDef *)dmaResource)->FCR = dmaRegCache->FCR; + ((DMA_Stream_TypeDef *)dmaResource)->NDTR = dmaRegCache->NDTR; + ((DMA_Stream_TypeDef *)dmaResource)->PAR = dmaRegCache->PAR; + ((DMA_Stream_TypeDef *)dmaResource)->M0AR = dmaRegCache->M0AR; +} + +static void bbSaveDMARegs(dmaResource_t *dmaResource, dmaRegCache_t *dmaRegCache) +{ + dmaRegCache->CR = ((DMA_Stream_TypeDef *)dmaResource)->CR; + dmaRegCache->FCR = ((DMA_Stream_TypeDef *)dmaResource)->FCR; + dmaRegCache->NDTR = ((DMA_Stream_TypeDef *)dmaResource)->NDTR; + dmaRegCache->PAR = ((DMA_Stream_TypeDef *)dmaResource)->PAR; + dmaRegCache->M0AR = ((DMA_Stream_TypeDef *)dmaResource)->M0AR; +} +#endif + +void bbSwitchToOutput(bbPort_t * bbPort) +{ + dbgPinHi(1); + // Output idle level before switching to output + // Use BSRR register for this + // Normal: Use BR (higher half) + // Inverted: Use BS (lower half) + + WRITE_REG(bbPort->gpio->BSRRL, bbPort->gpioIdleBSRR); + + // Set GPIO to output + ATOMIC_BLOCK(NVIC_PRIO_TIMER) { + MODIFY_REG(bbPort->gpio->MODER, bbPort->gpioModeMask, bbPort->gpioModeOutput); + } + + // Reinitialize port group DMA for output + + dmaResource_t *dmaResource = bbPort->dmaResource; +#ifdef USE_DMA_REGISTER_CACHE + bbLoadDMARegs(dmaResource, &bbPort->dmaRegOutput); +#else + xDMA_DeInit(dmaResource); + xDMA_Init(dmaResource, &bbPort->outputDmaInit); + // Needs this, as it is DeInit'ed above... + xDMA_ITConfig(dmaResource, DMA_IT_TC, ENABLE); +#endif + + // Reinitialize pacer timer for output + + bbPort->timhw->tim->ARR = bbPort->outputARR; + + bbPort->direction = DSHOT_BITBANG_DIRECTION_OUTPUT; + + dbgPinLo(1); +} + +#ifdef USE_DSHOT_TELEMETRY +void bbSwitchToInput(bbPort_t *bbPort) +{ + dbgPinHi(1); + + // Set GPIO to input + + ATOMIC_BLOCK(NVIC_PRIO_TIMER) { + MODIFY_REG(bbPort->gpio->MODER, bbPort->gpioModeMask, bbPort->gpioModeInput); + } + + // Reinitialize port group DMA for input + + dmaResource_t *dmaResource = bbPort->dmaResource; +#ifdef USE_DMA_REGISTER_CACHE + bbLoadDMARegs(dmaResource, &bbPort->dmaRegInput); +#else + xDMA_DeInit(dmaResource); + xDMA_Init(dmaResource, &bbPort->inputDmaInit); + // Needs this, as it is DeInit'ed above... + xDMA_ITConfig(dmaResource, DMA_IT_TC, ENABLE); +#endif + + // Reinitialize pacer timer for input + + bbPort->timhw->tim->CNT = 0; + bbPort->timhw->tim->ARR = bbPort->inputARR; + + bbDMA_Cmd(bbPort, ENABLE); + + bbPort->direction = DSHOT_BITBANG_DIRECTION_INPUT; + + dbgPinLo(1); +} +#endif + +void bbDMAPreconfigure(bbPort_t *bbPort, uint8_t direction) +{ + DMA_InitTypeDef *dmainit = (direction == DSHOT_BITBANG_DIRECTION_OUTPUT) ? &bbPort->outputDmaInit : &bbPort->inputDmaInit; + + DMA_StructInit(dmainit); + + dmainit->DMA_Mode = DMA_Mode_Normal; + dmainit->DMA_Channel = bbPort->dmaChannel; + dmainit->DMA_PeripheralInc = DMA_PeripheralInc_Disable; + dmainit->DMA_MemoryInc = DMA_MemoryInc_Enable; + dmainit->DMA_FIFOMode = DMA_FIFOMode_Enable ; + dmainit->DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull ; + dmainit->DMA_MemoryBurst = DMA_MemoryBurst_Single ; + dmainit->DMA_PeripheralBurst = DMA_PeripheralBurst_Single; + + if (direction == DSHOT_BITBANG_DIRECTION_OUTPUT) { + dmainit->DMA_Priority = DMA_Priority_High; + dmainit->DMA_DIR = DMA_DIR_MemoryToPeripheral; + dmainit->DMA_BufferSize = bbPort->portOutputCount; + dmainit->DMA_PeripheralBaseAddr = (uint32_t)&bbPort->gpio->BSRRL; + dmainit->DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; + dmainit->DMA_Memory0BaseAddr = (uint32_t)bbPort->portOutputBuffer; + dmainit->DMA_MemoryDataSize = DMA_MemoryDataSize_Word; + +#ifdef USE_DMA_REGISTER_CACHE + xDMA_Init(bbPort->dmaResource, dmainit); + bbSaveDMARegs(bbPort->dmaResource, &bbPort->dmaRegOutput); +#endif + } else { + dmainit->DMA_Priority = DMA_Priority_VeryHigh; + dmainit->DMA_DIR = DMA_DIR_PeripheralToMemory; + dmainit->DMA_BufferSize = bbPort->portInputCount; + + dmainit->DMA_PeripheralBaseAddr = (uint32_t)&bbPort->gpio->IDR; + + dmainit->DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; + dmainit->DMA_Memory0BaseAddr = (uint32_t)bbPort->portInputBuffer; + dmainit->DMA_MemoryDataSize = DMA_MemoryDataSize_Word; + +#ifdef USE_DMA_REGISTER_CACHE + xDMA_Init(bbPort->dmaResource, dmainit); + bbSaveDMARegs(bbPort->dmaResource, &bbPort->dmaRegInput); +#endif + } +} + +void bbTIM_TimeBaseInit(bbPort_t *bbPort, uint16_t period) +{ + TIM_TimeBaseInitTypeDef *init = &bbPort->timeBaseInit; + + init->TIM_Prescaler = 0; // Feed raw timerClock + init->TIM_ClockDivision = TIM_CKD_DIV1; + init->TIM_CounterMode = TIM_CounterMode_Up; + init->TIM_Period = period; + TIM_TimeBaseInit(bbPort->timhw->tim, init); + TIM_ARRPreloadConfig(bbPort->timhw->tim, ENABLE); +} + +void bbTIM_DMACmd(TIM_TypeDef* TIMx, uint16_t TIM_DMASource, FunctionalState NewState) +{ + TIM_DMACmd(TIMx, TIM_DMASource, NewState); +} + +void bbDMA_ITConfig(bbPort_t *bbPort) +{ + xDMA_ITConfig(bbPort->dmaResource, DMA_IT_TC, ENABLE); +} + +void bbDMA_Cmd(bbPort_t *bbPort, FunctionalState NewState) +{ + xDMA_Cmd(bbPort->dmaResource, NewState); +} + +int bbDMA_Count(bbPort_t *bbPort) +{ + return xDMA_GetCurrDataCounter(bbPort->dmaResource); +} + +#endif // USE_DSHOT_BB diff --git a/src/main/drivers/dshot_dpwm.c b/src/main/drivers/dshot_dpwm.c index 290014975..9d9c72086 100644 --- a/src/main/drivers/dshot_dpwm.c +++ b/src/main/drivers/dshot_dpwm.c @@ -138,6 +138,7 @@ static FAST_CODE void dshotWrite(uint8_t index, float value) } static motorVTable_t dshotPwmVTable = { + .postInit = motorPostInitNull, .enable = dshotPwmEnableMotors, .disable = dshotPwmDisableMotors, .isMotorEnabled = dshotPwmIsMotorEnabled, diff --git a/src/main/drivers/dshot_dpwm.h b/src/main/drivers/dshot_dpwm.h index 482157b52..4961a225e 100644 --- a/src/main/drivers/dshot_dpwm.h +++ b/src/main/drivers/dshot_dpwm.h @@ -40,9 +40,6 @@ #define DSHOT_TELEMETRY_DEADTIME_US (30 + 5) // 30 to switch lines and 5 to switch lines back -#define MIN_GCR_EDGES 7 -#define MAX_GCR_EDGES 22 - typedef uint8_t loadDmaBufferFn(uint32_t *dmaBuffer, int stride, uint16_t packet); // function pointer used to encode a digital motor value into the DMA buffer representation extern FAST_RAM_ZERO_INIT loadDmaBufferFn *loadDmaBuffer; @@ -134,9 +131,7 @@ typedef struct motorDmaOutput_s { #ifdef USE_DSHOT_TELEMETRY volatile bool isInput; - uint16_t dshotTelemetryValue; timeDelta_t dshotTelemetryDeadtimeUs; - bool dshotTelemetryActive; uint8_t dmaInputLen; #ifdef USE_HAL_DRIVER diff --git a/src/main/drivers/motor.c b/src/main/drivers/motor.c index 12fd331b5..e39076994 100644 --- a/src/main/drivers/motor.c +++ b/src/main/drivers/motor.c @@ -37,6 +37,7 @@ #include "drivers/motor.h" #include "drivers/pwm_output.h" // for PWM_TYPE_* and others #include "drivers/time.h" +#include "drivers/dshot_bitbang.h" #include "drivers/dshot_dpwm.h" #include "fc/rc_controls.h" // for flight3DConfig_t @@ -122,6 +123,15 @@ uint16_t motorConvertToExternal(float motorValue) static bool isDshot = false; // XXX Should go somewhere else +void motorPostInit() +{ + motorDevice->vTable.postInit(); +} + +void motorPostInitNull(void) +{ +} + static bool motorEnableNull(void) { return false; @@ -169,6 +179,7 @@ static uint16_t motorConvertToExternalNull(float value) } static const motorVTable_t motorNullVTable = { + .postInit = motorPostInitNull, .enable = motorEnableNull, .disable = motorDisableNull, .updateStart = motorUpdateStartNull, @@ -206,7 +217,15 @@ void motorDevInit(const motorDevConfig_t *motorConfig, uint16_t idlePulse, uint8 case PWM_TYPE_DSHOT600: case PWM_TYPE_DSHOT1200: case PWM_TYPE_PROSHOT1000: - motorDevice = dshotPwmDevInit(motorConfig, idlePulse, motorCount, useUnsyncedPwm); +#ifdef USE_DSHOT_BITBANG + if (isDshotBitbangActive(motorConfig)) { + motorDevice = dshotBitbangDevInit(motorConfig, motorCount); + } else +#endif + { + motorDevice = dshotPwmDevInit(motorConfig, idlePulse, motorCount, useUnsyncedPwm); + } + isDshot = true; break; #endif @@ -255,4 +274,12 @@ bool isMotorProtocolDshot(void) { return isDshot; } + +#ifdef USE_DSHOT_BITBANG +bool isDshotBitbangActive(const motorDevConfig_t *motorConfig) { + return motorConfig->useDshotBitbang == DSHOT_BITBANG_ON || + (motorConfig->useDshotBitbang == DSHOT_BITBANG_AUTO && motorConfig->useDshotTelemetry && motorConfig->motorPwmProtocol != PWM_TYPE_PROSHOT1000); +} +#endif + #endif // USE_MOTOR diff --git a/src/main/drivers/motor.h b/src/main/drivers/motor.h index 5e2ba2682..ffee6ca8c 100644 --- a/src/main/drivers/motor.h +++ b/src/main/drivers/motor.h @@ -41,6 +41,7 @@ typedef enum { typedef struct motorVTable_s { // Common + void (*postInit)(void); float (*convertExternalToMotor)(uint16_t externalValue); uint16_t (*convertMotorToExternal)(float motorValue); bool (*enable)(void); @@ -63,10 +64,12 @@ typedef struct motorDevice_s { bool enabled; } motorDevice_t; +void motorPostInitNull(); void motorWriteNull(uint8_t index, float value); bool motorUpdateStartNull(void); void motorUpdateCompleteNull(void); +void motorPostInit(); void motorWriteAll(float *values); void motorInitEndpoints(float outputLimit, float *outputLow, float *outputHigh, float *disarm, float *deadbandMotor3DHigh, float *deadbandMotor3DLow); @@ -83,3 +86,9 @@ void motorEnable(void); bool motorIsEnabled(void); bool motorIsMotorEnabled(uint8_t index); void motorShutdown(void); // Replaces stopPwmAllMotors + +#ifdef USE_DSHOT_BITBANG +struct motorDevConfig_s; +typedef struct motorDevConfig_s motorDevConfig_t; +bool isDshotBitbangActive(const motorDevConfig_t *motorConfig); +#endif diff --git a/src/main/drivers/pwm_esc_detect.c b/src/main/drivers/pwm_esc_detect.c index 11cd951ee..961df06ec 100644 --- a/src/main/drivers/pwm_esc_detect.c +++ b/src/main/drivers/pwm_esc_detect.c @@ -50,6 +50,7 @@ void detectBrushedESC(ioTag_t motorIoTag) } else { hardwareMotorType = MOTOR_BRUSHED; } + IORelease(motorDetectPin); } uint8_t getDetectedMotorType(void) diff --git a/src/main/drivers/pwm_output.c b/src/main/drivers/pwm_output.c index 736a46a67..1dba14587 100644 --- a/src/main/drivers/pwm_output.c +++ b/src/main/drivers/pwm_output.c @@ -172,6 +172,7 @@ static uint16_t pwmConvertToExternal(float motorValue) } static motorVTable_t motorPwmVTable = { + .postInit = motorPostInitNull, .enable = pwmEnableMotors, .disable = pwmDisableMotors, .isMotorEnabled = pwmIsMotorEnabled, diff --git a/src/main/drivers/pwm_output_dshot_shared.c b/src/main/drivers/pwm_output_dshot_shared.c index e9e9b85b0..bcb3aad38 100644 --- a/src/main/drivers/pwm_output_dshot_shared.c +++ b/src/main/drivers/pwm_output_dshot_shared.c @@ -49,22 +49,6 @@ #include "pwm_output_dshot_shared.h" -#ifdef USE_DSHOT_TELEMETRY_STATS -#define DSHOT_TELEMETRY_QUALITY_WINDOW 1 // capture a rolling 1 second of packet stats -#define DSHOT_TELEMETRY_QUALITY_BUCKET_MS 100 // determines the granularity of the stats and the overall number of rolling buckets -#define DSHOT_TELEMETRY_QUALITY_BUCKET_COUNT (DSHOT_TELEMETRY_QUALITY_WINDOW * 1000 / DSHOT_TELEMETRY_QUALITY_BUCKET_MS) - -typedef struct dshotTelemetryQuality_s { - uint32_t packetCountSum; - uint32_t invalidCountSum; - uint32_t packetCountArray[DSHOT_TELEMETRY_QUALITY_BUCKET_COUNT]; - uint32_t invalidCountArray[DSHOT_TELEMETRY_QUALITY_BUCKET_COUNT]; - uint8_t lastBucketIndex; -} dshotTelemetryQuality_t; - -static FAST_RAM_ZERO_INIT dshotTelemetryQuality_t dshotTelemetryQuality[MAX_SUPPORTED_MOTORS]; -#endif // USE_DSHOT_TELEMETRY_STATS - FAST_RAM_ZERO_INIT uint8_t dmaMotorTimerCount = 0; #ifdef STM32F7 FAST_RAM_ZERO_INIT motorDmaTimer_t dmaMotorTimers[MAX_DMA_TIMERS]; @@ -75,12 +59,8 @@ motorDmaOutput_t dmaMotors[MAX_SUPPORTED_MOTORS]; #endif #ifdef USE_DSHOT_TELEMETRY -uint32_t readDoneCount; // TODO remove once debugging no longer needed -FAST_RAM_ZERO_INIT uint32_t dshotInvalidPacketCount; -FAST_RAM_ZERO_INIT uint32_t inputBuffer[GCR_TELEMETRY_INPUT_LEN]; -FAST_RAM_ZERO_INIT uint32_t decodePacketDurationUs; FAST_RAM_ZERO_INIT uint32_t inputStampUs; FAST_RAM_ZERO_INIT dshotDMAHandlerCycleCounters_t dshotDMAHandlerCycleCounters; @@ -117,8 +97,8 @@ FAST_CODE void pwmWriteDshotInt(uint8_t index, uint16_t value) #ifdef USE_DSHOT_TELEMETRY // reset telemetry debug statistics every time telemetry is enabled if (value == DSHOT_CMD_SIGNAL_LINE_CONTINUOUS_ERPM_TELEMETRY) { - dshotInvalidPacketCount = 0; - readDoneCount = 0; + dshotTelemetryState.invalidPacketCount = 0; + dshotTelemetryState.readCount = 0; } #endif if (value) { @@ -155,9 +135,9 @@ FAST_CODE void pwmWriteDshotInt(uint8_t index, uint16_t value) void dshotEnableChannels(uint8_t motorCount); + static uint32_t decodeTelemetryPacket(uint32_t buffer[], uint32_t count) { - uint32_t start = micros(); uint32_t value = 0; uint32_t oldValue = buffer[0]; int bits = 0; @@ -196,13 +176,11 @@ static uint32_t decodeTelemetryPacket(uint32_t buffer[], uint32_t count) csum = csum ^ (csum >> 4); // xor nibbles if ((csum & 0xf) != 0xf) { - decodePacketDurationUs = micros() - start; return 0xffff; } decodedValue >>= 4; if (decodedValue == 0x0fff) { - decodePacketDurationUs = micros() - start; return 0; } decodedValue = (decodedValue & 0x000001ff) << ((decodedValue & 0xfffffe00) >> 9); @@ -210,38 +188,12 @@ static uint32_t decodeTelemetryPacket(uint32_t buffer[], uint32_t count) return 0xffff; } uint32_t ret = (1000000 * 60 / 100 + decodedValue / 2) / decodedValue; - decodePacketDurationUs = micros() - start; return ret; } -uint16_t getDshotTelemetry(uint8_t index) -{ - return dmaMotors[index].dshotTelemetryValue; -} - #endif #ifdef USE_DSHOT_TELEMETRY -#ifdef USE_DSHOT_TELEMETRY_STATS -void updateDshotTelemetryQuality(dshotTelemetryQuality_t *qualityStats, bool packetValid, timeMs_t currentTimeMs) -{ - uint8_t statsBucketIndex = (currentTimeMs / DSHOT_TELEMETRY_QUALITY_BUCKET_MS) % DSHOT_TELEMETRY_QUALITY_BUCKET_COUNT; - if (statsBucketIndex != qualityStats->lastBucketIndex) { - qualityStats->packetCountSum -= qualityStats->packetCountArray[statsBucketIndex]; - qualityStats->invalidCountSum -= qualityStats->invalidCountArray[statsBucketIndex]; - qualityStats->packetCountArray[statsBucketIndex] = 0; - qualityStats->invalidCountArray[statsBucketIndex] = 0; - qualityStats->lastBucketIndex = statsBucketIndex; - } - qualityStats->packetCountSum++; - qualityStats->packetCountArray[statsBucketIndex]++; - if (!packetValid) { - qualityStats->invalidCountSum++; - qualityStats->invalidCountArray[statsBucketIndex]++; - } -} -#endif // USE_DSHOT_TELEMETRY_STATS - FAST_CODE_NOINLINE bool pwmStartDshotMotorUpdate(void) { if (!useDshotTelemetry) { @@ -272,15 +224,15 @@ FAST_CODE_NOINLINE bool pwmStartDshotMotorUpdate(void) uint16_t value = 0xffff; if (edges > MIN_GCR_EDGES) { - readDoneCount++; + dshotTelemetryState.readCount++; value = decodeTelemetryPacket(dmaMotors[i].dmaBuffer, edges); #ifdef USE_DSHOT_TELEMETRY_STATS bool validTelemetryPacket = false; #endif if (value != 0xffff) { - dmaMotors[i].dshotTelemetryValue = value; - dmaMotors[i].dshotTelemetryActive = true; + dshotTelemetryState.motorState[i].telemetryValue = value; + dshotTelemetryState.motorState[i].telemetryActive = true; if (i < 4) { DEBUG_SET(DEBUG_DSHOT_RPM_TELEMETRY, i, value); } @@ -288,9 +240,9 @@ FAST_CODE_NOINLINE bool pwmStartDshotMotorUpdate(void) validTelemetryPacket = true; #endif } else { - dshotInvalidPacketCount++; + dshotTelemetryState.invalidPacketCount++; if (i == 0) { - memcpy(inputBuffer,dmaMotors[i].dmaBuffer,sizeof(inputBuffer)); + memcpy(dshotTelemetryState.inputBuffer,dmaMotors[i].dmaBuffer,sizeof(dshotTelemetryState.inputBuffer)); } } #ifdef USE_DSHOT_TELEMETRY_STATS @@ -307,7 +259,7 @@ FAST_CODE_NOINLINE bool pwmStartDshotMotorUpdate(void) bool isDshotMotorTelemetryActive(uint8_t motorIndex) { - return dmaMotors[motorIndex].dshotTelemetryActive; + return dshotTelemetryState.motorState[motorIndex].telemetryActive; } bool isDshotTelemetryActive(void) @@ -325,7 +277,7 @@ int16_t getDshotTelemetryMotorInvalidPercent(uint8_t motorIndex) { int16_t invalidPercent = 0; - if (dmaMotors[motorIndex].dshotTelemetryActive) { + if (dshotTelemetryState.motorState[motorIndex].telemetryActive) { const uint32_t totalCount = dshotTelemetryQuality[motorIndex].packetCountSum; const uint32_t invalidCount = dshotTelemetryQuality[motorIndex].invalidCountSum; if (totalCount > 0) { diff --git a/src/main/drivers/pwm_output_dshot_shared.h b/src/main/drivers/pwm_output_dshot_shared.h index 9d9ec80d2..16e274297 100644 --- a/src/main/drivers/pwm_output_dshot_shared.h +++ b/src/main/drivers/pwm_output_dshot_shared.h @@ -44,9 +44,6 @@ extern motorDmaOutput_t dmaMotors[MAX_SUPPORTED_MOTORS]; extern uint32_t readDoneCount; // TODO remove once debugging no longer needed -FAST_RAM_ZERO_INIT extern uint32_t dshotInvalidPacketCount; -FAST_RAM_ZERO_INIT extern uint32_t inputBuffer[GCR_TELEMETRY_INPUT_LEN]; -FAST_RAM_ZERO_INIT extern uint32_t decodePacketDurationUs; FAST_RAM_ZERO_INIT extern uint32_t inputStampUs; typedef struct dshotDMAHandlerCycleCounters_s { diff --git a/src/main/drivers/resource.c b/src/main/drivers/resource.c index c4c9a460c..c542b3708 100644 --- a/src/main/drivers/resource.c +++ b/src/main/drivers/resource.c @@ -106,5 +106,6 @@ const char * const ownerNames[OWNER_TOTAL_COUNT] = { "QSPI_BK2CS", "BARO_XCLR", "PULLUP", - "PULLDOWN" + "PULLDOWN", + "DSHOT_BITBANG", }; diff --git a/src/main/drivers/resource.h b/src/main/drivers/resource.h index c8c9c31d8..a90da09db 100644 --- a/src/main/drivers/resource.h +++ b/src/main/drivers/resource.h @@ -105,6 +105,7 @@ typedef enum { OWNER_BARO_XCLR, OWNER_PULLUP, OWNER_PULLDOWN, + OWNER_DSHOT_BITBANG, OWNER_TOTAL_COUNT } resourceOwner_e; diff --git a/src/main/drivers/timer_common.c b/src/main/drivers/timer_common.c index 0101b982c..5c5e87330 100644 --- a/src/main/drivers/timer_common.c +++ b/src/main/drivers/timer_common.c @@ -158,5 +158,6 @@ ioTag_t timerioTagGetByUsage(timerUsageFlag_e usageFlag, uint8_t index) #endif return IO_TAG_NONE; } + #endif diff --git a/src/main/drivers/timer_def.h b/src/main/drivers/timer_def.h index ffa4697bb..c46eb5602 100644 --- a/src/main/drivers/timer_def.h +++ b/src/main/drivers/timer_def.h @@ -605,6 +605,16 @@ // AF table +// NONE +#define DEF_TIM_AF__NONE__TCH_TIM1_CH1 D(1, 1) +#define DEF_TIM_AF__NONE__TCH_TIM1_CH2 D(1, 1) +#define DEF_TIM_AF__NONE__TCH_TIM1_CH3 D(1, 1) +#define DEF_TIM_AF__NONE__TCH_TIM1_CH4 D(1, 1) +#define DEF_TIM_AF__NONE__TCH_TIM8_CH1 D(3, 8) +#define DEF_TIM_AF__NONE__TCH_TIM8_CH2 D(3, 8) +#define DEF_TIM_AF__NONE__TCH_TIM8_CH3 D(3, 8) +#define DEF_TIM_AF__NONE__TCH_TIM8_CH4 D(3, 8) + //PORTA #define DEF_TIM_AF__PA0__TCH_TIM2_CH1 D(1, 2) #define DEF_TIM_AF__PA1__TCH_TIM2_CH2 D(1, 2) @@ -1004,4 +1014,6 @@ #define DEF_TIM_AF__PI6__TCH_TIM8_CH2 D(3, 8) #define DEF_TIM_AF__PI7__TCH_TIM8_CH3 D(3, 8) + #endif + diff --git a/src/main/fc/config.c b/src/main/fc/config.c index c173d0311..b7005f26e 100644 --- a/src/main/fc/config.c +++ b/src/main/fc/config.c @@ -389,6 +389,13 @@ static void validateAndFixConfig(void) } } +#if defined(USE_DSHOT_TELEMETRY) && defined(USE_DSHOT_BITBANG) + if (motorConfig()->dev.motorPwmProtocol == PWM_TYPE_PROSHOT1000 && motorConfig()->dev.useDshotTelemetry && + motorConfig()->dev.useDshotBitbang == DSHOT_BITBANG_ON) { + motorConfigMutable()->dev.useDshotBitbang = DSHOT_BITBANG_AUTO; + } +#endif + // clear features that are not supported. // I have kept them all here in one place, some could be moved to sections of code above. @@ -504,7 +511,7 @@ static void validateAndFixConfig(void) } #if defined(USE_DSHOT_TELEMETRY) - if ((!usingDshotProtocol || motorConfig()->dev.useBurstDshot || !systemConfig()->schedulerOptimizeRate) + if ((!usingDshotProtocol || (motorConfig()->dev.useDshotBitbang == DSHOT_BITBANG_OFF && motorConfig()->dev.useBurstDshot) || !systemConfig()->schedulerOptimizeRate) && motorConfig()->dev.useDshotTelemetry) { motorConfigMutable()->dev.useDshotTelemetry = false; } diff --git a/src/main/fc/core.c b/src/main/fc/core.c index 591b10be5..468b8617e 100644 --- a/src/main/fc/core.c +++ b/src/main/fc/core.c @@ -85,6 +85,7 @@ #include "osd/osd.h" +#include "pg/motor.h" #include "pg/pg.h" #include "pg/pg_ids.h" #include "pg/rx.h" @@ -308,6 +309,14 @@ void updateArmingStatus(void) } #endif +#ifdef USE_DSHOT_BITBANG + if (isDshotBitbangActive(&motorConfig()->dev) && dshotBitbangGetStatus() != DSHOT_BITBANG_OK) { + setArmingDisabled(ARMING_DISABLED_DSHOT_BITBANG); + } else { + unsetArmingDisabled(ARMING_DISABLED_DSHOT_BITBANG); + } +#endif + if (IS_RC_MODE_ACTIVE(BOXPARALYZE)) { setArmingDisabled(ARMING_DISABLED_PARALYZE); } diff --git a/src/main/fc/init.c b/src/main/fc/init.c index 3a1fc1be5..42888336d 100644 --- a/src/main/fc/init.c +++ b/src/main/fc/init.c @@ -918,6 +918,7 @@ void init(void) #endif // USE_RCDEVICE #ifdef USE_MOTOR + motorPostInit(); motorEnable(); #endif diff --git a/src/main/fc/runtime_config.h b/src/main/fc/runtime_config.h index 365b35dbe..84dfd3822 100644 --- a/src/main/fc/runtime_config.h +++ b/src/main/fc/runtime_config.h @@ -62,7 +62,8 @@ typedef enum { ARMING_DISABLED_RESC = (1 << 19), ARMING_DISABLED_RPMFILTER = (1 << 20), ARMING_DISABLED_REBOOT_REQUIRED = (1 << 21), - ARMING_DISABLED_ARM_SWITCH = (1 << 22), // Needs to be the last element, since it's always activated if one of the others is active when arming + ARMING_DISABLED_DSHOT_BITBANG = (1 << 22), + ARMING_DISABLED_ARM_SWITCH = (1 << 23), // Needs to be the last element, since it's always activated if one of the others is active when arming } armingDisableFlags_e; #define ARMING_DISABLE_FLAGS_COUNT (LOG2(ARMING_DISABLED_ARM_SWITCH) + 1) diff --git a/src/main/pg/motor.c b/src/main/pg/motor.c index 04cc7a190..0e9690b72 100644 --- a/src/main/pg/motor.c +++ b/src/main/pg/motor.c @@ -73,6 +73,10 @@ void pgResetFn_motorConfig(motorConfig_t *motorConfig) #endif motorConfig->motorPoleCount = 14; // Most brushes motors that we use are 14 poles + +#ifdef USE_DSHOT_BITBANG + motorConfig->dev.useDshotBitbang = DSHOT_BITBANG_AUTO; +#endif } #endif // USE_MOTOR diff --git a/src/main/pg/motor.h b/src/main/pg/motor.h index ea110bea9..1b386a61d 100644 --- a/src/main/pg/motor.h +++ b/src/main/pg/motor.h @@ -23,6 +23,7 @@ #include "pg/pg.h" #include "drivers/io.h" +#include "drivers/dshot_bitbang.h" typedef struct motorDevConfig_s { uint16_t motorPwmRate; // The update rate of motor outputs (50-498Hz) @@ -33,6 +34,7 @@ typedef struct motorDevConfig_s { uint8_t useDshotTelemetry; ioTag_t ioTags[MAX_SUPPORTED_MOTORS]; uint8_t motorTransportProtocol; + uint8_t useDshotBitbang; } motorDevConfig_t; typedef struct motorConfig_s { diff --git a/src/main/target/common_pre.h b/src/main/target/common_pre.h index 5a662b0c8..0a898ab89 100644 --- a/src/main/target/common_pre.h +++ b/src/main/target/common_pre.h @@ -52,6 +52,7 @@ #define USE_FAST_RAM #endif #define USE_DSHOT +#define USE_DSHOT_BITBANG #define USE_DSHOT_TELEMETRY #define USE_DSHOT_TELEMETRY_STATS #define USE_RPM_FILTER @@ -82,6 +83,7 @@ #define USE_ITCM_RAM #define USE_FAST_RAM #define USE_DSHOT +#define USE_DSHOT_BITBANG #define USE_DSHOT_TELEMETRY #define USE_DSHOT_TELEMETRY_STATS #define USE_RPM_FILTER