diff --git a/src/main/config/config_eeprom.c b/src/main/config/config_eeprom.c index 6c1dfc3dc7e..608a08a0a84 100755 --- a/src/main/config/config_eeprom.c +++ b/src/main/config/config_eeprom.c @@ -33,6 +33,7 @@ #include "drivers/system.h" #include "drivers/flash.h" +#include "drivers/pwm_output.h" #include "fc/config.h" @@ -321,6 +322,13 @@ static bool writeSettingsToEEPROM(void) void writeConfigToEEPROM(void) { +#if !defined(SITL_BUILD) && defined(USE_DSHOT) + // Enable circular DMA so hardware keeps repeating zero-throttle DShot + // packets during flash writes (which block the CPU for 20-200ms). + // Without this, ESCs lose signal and may spin up or reboot. + pwmSetMotorDMACircular(true); +#endif + bool success = false; // write it for (int attempt = 0; attempt < 3 && !success; attempt++) { @@ -333,6 +341,10 @@ void writeConfigToEEPROM(void) } } +#if !defined(SITL_BUILD) && defined(USE_DSHOT) + pwmSetMotorDMACircular(false); +#endif + if (success && isEEPROMContentValid()) { return; } diff --git a/src/main/drivers/pwm_output.c b/src/main/drivers/pwm_output.c index 619f4b95db5..a4efb9e3008 100644 --- a/src/main/drivers/pwm_output.c +++ b/src/main/drivers/pwm_output.c @@ -124,6 +124,18 @@ static timeUs_t commandPostDelay = 0; static circularBuffer_t commandsCircularBuffer; static uint8_t commandsBuff[DHSOT_COMMAND_QUEUE_SIZE]; static currentExecutingCommand_t currentExecutingCommand; + +static uint16_t prepareDshotPacket(const uint16_t value, bool requestTelemetry); +#ifndef USE_DSHOT_DMAR +static void loadDmaBufferDshot(timerDMASafeType_t *dmaBuffer, uint16_t packet); +#else +static void loadDmaBufferDshotStride(timerDMASafeType_t *dmaBuffer, int stride, uint16_t packet); +#endif + +#ifdef USE_DSHOT_DMAR +burstDmaTimer_t burstDmaTimers[MAX_DMA_TIMERS]; +uint8_t burstDmaTimersCount = 0; +#endif #endif static void pwmOutConfigTimer(pwmOutputPort_t * p, TCH_t * tch, uint32_t hz, uint16_t period, uint16_t value) @@ -226,6 +238,56 @@ void pwmEnableMotors(void) pwmMotorsEnabled = true; } +void pwmSetMotorDMACircular(bool circular) +{ +#ifdef USE_DSHOT + if (!isMotorProtocolDshot()) { + return; + } + + int motorCount = getMotorCount(); + + if (circular) { + // Load zero-throttle packets directly into DMA buffers, + // bypassing the rate limiter in pwmCompleteMotorUpdate() + uint16_t packet = prepareDshotPacket(0, false); + for (int i = 0; i < motorCount; i++) { + if (motors[i].pwmPort && motors[i].pwmPort->configured) { +#ifdef USE_DSHOT_DMAR + loadDmaBufferDshotStride(&motors[i].pwmPort->dmaBurstBuffer[motors[i].pwmPort->tch->timHw->channelIndex], 4, packet); +#else + loadDmaBufferDshot(motors[i].pwmPort->dmaBuffer, packet); +#endif + } + } + } + +#ifdef USE_DSHOT_DMAR + // Burst DMA: one DMA stream per timer, shared across channels + for (int i = 0; i < burstDmaTimersCount; i++) { + burstDmaTimer_t *burstDmaTimer = &burstDmaTimers[i]; + // Find the first motor using this timer to get the TCH for DMA state + for (int m = 0; m < motorCount; m++) { + if (motors[m].pwmPort && motors[m].pwmPort->configured && motors[m].pwmPort->tch + && motors[m].pwmPort->tch->timHw->tim == burstDmaTimer->timer) { + impl_pwmBurstDMASetCircular(burstDmaTimer, motors[m].pwmPort->tch, circular, DSHOT_DMA_BUFFER_SIZE * 4); + break; + } + } + } +#else + // Per-channel DMA: one DMA stream per motor + for (int i = 0; i < motorCount; i++) { + if (motors[i].pwmPort && motors[i].pwmPort->configured && motors[i].pwmPort->tch) { + impl_timerPWMSetDMACircular(motors[i].pwmPort->tch, circular, DSHOT_DMA_BUFFER_SIZE); + } + } +#endif +#else + UNUSED(circular); +#endif +} + bool isMotorBrushed(uint16_t motorPwmRateHz) { return (motorPwmRateHz > 500); @@ -264,9 +326,6 @@ uint32_t getDshotHz(motorPwmProtocolTypes_e pwmProtocolType) } #ifdef USE_DSHOT_DMAR -burstDmaTimer_t burstDmaTimers[MAX_DMA_TIMERS]; -uint8_t burstDmaTimersCount = 0; - static uint8_t getBurstDmaTimerIndex(TIM_TypeDef *timer) { for (int i = 0; i < burstDmaTimersCount; i++) { diff --git a/src/main/drivers/pwm_output.h b/src/main/drivers/pwm_output.h index 1041ace04fa..1dc644f7f4e 100644 --- a/src/main/drivers/pwm_output.h +++ b/src/main/drivers/pwm_output.h @@ -50,6 +50,7 @@ void pwmWriteServo(uint8_t index, uint16_t value); void pwmDisableMotors(void); void pwmEnableMotors(void); +void pwmSetMotorDMACircular(bool circular); struct timerHardware_s; void pwmMotorPreconfigure(void); diff --git a/src/main/drivers/timer.h b/src/main/drivers/timer.h index d87e0400d52..512b0d7c106 100644 --- a/src/main/drivers/timer.h +++ b/src/main/drivers/timer.h @@ -138,6 +138,7 @@ typedef enum { TCH_DMA_IDLE = 0, TCH_DMA_READY, TCH_DMA_ACTIVE, + TCH_DMA_CIRCULAR, } tchDmaState_e; // Some forward declarations for types diff --git a/src/main/drivers/timer_impl.h b/src/main/drivers/timer_impl.h index 4d0a87f9aa5..6a302f57cb4 100644 --- a/src/main/drivers/timer_impl.h +++ b/src/main/drivers/timer_impl.h @@ -84,8 +84,10 @@ bool impl_timerPWMConfigChannelDMA(TCH_t * tch, void * dmaBuffer, uint8_t dmaBuf void impl_timerPWMPrepareDMA(TCH_t * tch, uint32_t dmaBufferElementCount); void impl_timerPWMStartDMA(TCH_t * tch); void impl_timerPWMStopDMA(TCH_t * tch); +void impl_timerPWMSetDMACircular(TCH_t * tch, bool circular, uint32_t dmaBufferSize); #ifdef USE_DSHOT_DMAR bool impl_timerPWMConfigDMABurst(burstDmaTimer_t *burstDmaTimer, TCH_t * tch, void * dmaBuffer, uint8_t dmaBufferElementSize, uint32_t dmaBufferElementCount); void impl_pwmBurstDMAStart(burstDmaTimer_t * burstDmaTimer, uint32_t BurstLength); +void impl_pwmBurstDMASetCircular(burstDmaTimer_t * burstDmaTimer, TCH_t * tch, bool circular, uint32_t dmaBufferSize); #endif \ No newline at end of file diff --git a/src/main/drivers/timer_impl_hal.c b/src/main/drivers/timer_impl_hal.c index 8df0f7024d3..8cfe3642021 100644 --- a/src/main/drivers/timer_impl_hal.c +++ b/src/main/drivers/timer_impl_hal.c @@ -319,6 +319,12 @@ static void impl_timerDMA_IRQHandler(DMA_t descriptor) if (DMA_GET_FLAG_STATUS(descriptor, DMA_IT_TCIF)) { TCH_t * tch = (TCH_t *)descriptor->userParam; + // In circular mode, let DMA keep running - don't disable the stream + if (tch->dmaState == TCH_DMA_CIRCULAR) { + DMA_CLEAR_FLAG(descriptor, DMA_IT_TCIF); + return; + } + // If it was ACTIVE - switch to IDLE if (tch->dmaState == TCH_DMA_ACTIVE) { tch->dmaState = TCH_DMA_IDLE; @@ -512,6 +518,46 @@ void impl_pwmBurstDMAStart(burstDmaTimer_t * burstDmaTimer, uint32_t BurstLength //LL_TIM_EnableDMAReq_UPDATE(burstDmaTimer->timer); LL_TIM_EnableDMAReq_CCx(burstDmaTimer->timer, burstDmaTimer->burstRequestSource); } + +void impl_pwmBurstDMASetCircular(burstDmaTimer_t * burstDmaTimer, TCH_t * tch, bool circular, uint32_t dmaBufferSize) +{ + if (!tch->dma || !tch->dma->dma) { + return; + } + + ATOMIC_BLOCK(NVIC_PRIO_MAX) { + LL_TIM_DisableDMAReq_CCx(burstDmaTimer->timer, burstDmaTimer->burstRequestSource); + LL_DMA_DisableStream(burstDmaTimer->dma, burstDmaTimer->streamLL); + + uint32_t timeout = 10000; + while (LL_DMA_IsEnabledStream(burstDmaTimer->dma, burstDmaTimer->streamLL) && timeout--) { + __NOP(); + } + + if (LL_DMA_IsEnabledStream(burstDmaTimer->dma, burstDmaTimer->streamLL)) { + LL_TIM_EnableDMAReq_CCx(burstDmaTimer->timer, burstDmaTimer->burstRequestSource); + return; + } + + DMA_CLEAR_FLAG(tch->dma, DMA_IT_TCIF); + + if (circular) { + LL_DMA_SetMode(burstDmaTimer->dma, burstDmaTimer->streamLL, LL_DMA_MODE_CIRCULAR); + LL_DMA_SetDataLength(burstDmaTimer->dma, burstDmaTimer->streamLL, dmaBufferSize); + LL_DMA_DisableIT_TC(burstDmaTimer->dma, burstDmaTimer->streamLL); + tch->dmaState = TCH_DMA_CIRCULAR; + } else { + LL_DMA_SetMode(burstDmaTimer->dma, burstDmaTimer->streamLL, LL_DMA_MODE_NORMAL); + LL_DMA_EnableIT_TC(burstDmaTimer->dma, burstDmaTimer->streamLL); + tch->dmaState = TCH_DMA_IDLE; + } + + __DSB(); + + LL_DMA_EnableStream(burstDmaTimer->dma, burstDmaTimer->streamLL); + LL_TIM_EnableDMAReq_CCx(burstDmaTimer->timer, burstDmaTimer->burstRequestSource); + } +} #endif void impl_timerPWMPrepareDMA(TCH_t * tch, uint32_t dmaBufferElementCount) @@ -580,3 +626,52 @@ void impl_timerPWMStopDMA(TCH_t * tch) HAL_TIM_Base_Start(tch->timCtx->timHandle); } + +void impl_timerPWMSetDMACircular(TCH_t * tch, bool circular, uint32_t dmaBufferSize) +{ + if (!tch->dma || !tch->dma->dma) { + return; + } + + const uint32_t streamLL = lookupDMALLStreamTable[DMATAG_GET_STREAM(tch->timHw->dmaTag)]; + DMA_TypeDef *dmaBase = tch->dma->dma; + + ATOMIC_BLOCK(NVIC_PRIO_MAX) { + // Stop new transfer triggers before reconfiguring + LL_TIM_DisableDMAReq_CCx(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex]); + LL_DMA_DisableStream(dmaBase, streamLL); + + // STM32H7 RM: poll EN bit until stream is actually disabled + uint32_t timeout = 10000; // ~20us at 480MHz, well above worst-case disable latency + while (LL_DMA_IsEnabledStream(dmaBase, streamLL) && timeout--) { + __NOP(); + } + + if (LL_DMA_IsEnabledStream(dmaBase, streamLL)) { + LL_TIM_EnableDMAReq_CCx(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex]); + return; + } + + DMA_CLEAR_FLAG(tch->dma, DMA_IT_TCIF); + + if (circular) { + LL_DMA_SetMode(dmaBase, streamLL, LL_DMA_MODE_CIRCULAR); + // Circular mode requires non-zero NDTR (STM32H7 RM constraint) + LL_DMA_SetDataLength(dmaBase, streamLL, dmaBufferSize); + // Disable TC interrupt — in circular mode, TC fires every cycle + // and the IRQ handler would otherwise disable the stream + LL_DMA_DisableIT_TC(dmaBase, streamLL); + tch->dmaState = TCH_DMA_CIRCULAR; + } else { + LL_DMA_SetMode(dmaBase, streamLL, LL_DMA_MODE_NORMAL); + LL_DMA_EnableIT_TC(dmaBase, streamLL); + tch->dmaState = TCH_DMA_IDLE; + } + + // Ensure register writes are visible to DMA before re-enabling + __DSB(); + + LL_DMA_EnableStream(dmaBase, streamLL); + LL_TIM_EnableDMAReq_CCx(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex]); + } +} diff --git a/src/main/drivers/timer_impl_stdperiph.c b/src/main/drivers/timer_impl_stdperiph.c index d2bd35dd521..5b52cb862a4 100644 --- a/src/main/drivers/timer_impl_stdperiph.c +++ b/src/main/drivers/timer_impl_stdperiph.c @@ -270,6 +270,13 @@ static void impl_timerDMA_IRQHandler(DMA_t descriptor) { if (DMA_GET_FLAG_STATUS(descriptor, DMA_IT_TCIF)) { TCH_t * tch = (TCH_t *)descriptor->userParam; + + // In circular mode, let DMA keep running - don't disable the stream + if (tch->dmaState == TCH_DMA_CIRCULAR) { + DMA_CLEAR_FLAG(descriptor, DMA_IT_TCIF); + return; + } + tch->dmaState = TCH_DMA_IDLE; DMA_Cmd(tch->dma->ref, DISABLE); @@ -463,6 +470,46 @@ void impl_pwmBurstDMAStart(burstDmaTimer_t * burstDmaTimer, uint32_t BurstLength TIM_DMAConfig(burstDmaTimer->timer, TIM_DMABase_CCR1, TIM_DMABurstLength_4Transfers); TIM_DMACmd(burstDmaTimer->timer, burstDmaTimer->burstRequestSource, ENABLE); } + +void impl_pwmBurstDMASetCircular(burstDmaTimer_t * burstDmaTimer, TCH_t * tch, bool circular, uint32_t dmaBufferSize) +{ + if (!tch->dma || !tch->dma->ref) { + return; + } + + ATOMIC_BLOCK(NVIC_PRIO_MAX) { + TIM_DMACmd(burstDmaTimer->timer, burstDmaTimer->burstRequestSource, DISABLE); + DMA_Cmd(burstDmaTimer->dmaBurstStream, DISABLE); + + uint32_t timeout = 10000; + while ((burstDmaTimer->dmaBurstStream->CR & DMA_SxCR_EN) && timeout--) { + __NOP(); + } + + if (burstDmaTimer->dmaBurstStream->CR & DMA_SxCR_EN) { + TIM_DMACmd(burstDmaTimer->timer, burstDmaTimer->burstRequestSource, ENABLE); + return; + } + + DMA_CLEAR_FLAG(tch->dma, DMA_IT_TCIF); + + if (circular) { + burstDmaTimer->dmaBurstStream->CR |= DMA_SxCR_CIRC; + DMA_SetCurrDataCounter(burstDmaTimer->dmaBurstStream, dmaBufferSize); + DMA_ITConfig(burstDmaTimer->dmaBurstStream, DMA_IT_TC, DISABLE); + tch->dmaState = TCH_DMA_CIRCULAR; + } else { + burstDmaTimer->dmaBurstStream->CR &= ~DMA_SxCR_CIRC; + DMA_ITConfig(burstDmaTimer->dmaBurstStream, DMA_IT_TC, ENABLE); + tch->dmaState = TCH_DMA_IDLE; + } + + __DSB(); + + DMA_Cmd(burstDmaTimer->dmaBurstStream, ENABLE); + TIM_DMACmd(burstDmaTimer->timer, burstDmaTimer->burstRequestSource, ENABLE); + } +} #endif void impl_timerPWMPrepareDMA(TCH_t * tch, uint32_t dmaBufferElementCount) @@ -519,3 +566,48 @@ void impl_timerPWMStopDMA(TCH_t * tch) tch->dmaState = TCH_DMA_IDLE; TIM_Cmd(tch->timHw->tim, ENABLE); } + +void impl_timerPWMSetDMACircular(TCH_t * tch, bool circular, uint32_t dmaBufferSize) +{ + if (!tch->dma || !tch->dma->ref) { + return; + } + + ATOMIC_BLOCK(NVIC_PRIO_MAX) { + // Stop new transfer triggers before reconfiguring + TIM_DMACmd(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], DISABLE); + DMA_Cmd(tch->dma->ref, DISABLE); + + // STM32F4/F7 RM: poll EN bit until stream is actually disabled + uint32_t timeout = 10000; // ~60us at 168MHz, well above worst-case disable latency + while ((tch->dma->ref->CR & DMA_SxCR_EN) && timeout--) { + __NOP(); + } + + if (tch->dma->ref->CR & DMA_SxCR_EN) { + TIM_DMACmd(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], ENABLE); + return; + } + + DMA_CLEAR_FLAG(tch->dma, DMA_IT_TCIF); + + if (circular) { + tch->dma->ref->CR |= DMA_SxCR_CIRC; + DMA_SetCurrDataCounter(tch->dma->ref, dmaBufferSize); + // Disable TC interrupt — in circular mode, TC fires every cycle + // and the IRQ handler would otherwise disable the stream + DMA_ITConfig(tch->dma->ref, DMA_IT_TC, DISABLE); + tch->dmaState = TCH_DMA_CIRCULAR; + } else { + tch->dma->ref->CR &= ~DMA_SxCR_CIRC; + DMA_ITConfig(tch->dma->ref, DMA_IT_TC, ENABLE); + tch->dmaState = TCH_DMA_IDLE; + } + + // Ensure register writes are visible to DMA before re-enabling + __DSB(); + + DMA_Cmd(tch->dma->ref, ENABLE); + TIM_DMACmd(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], ENABLE); + } +} diff --git a/src/main/drivers/timer_impl_stdperiph_at32.c b/src/main/drivers/timer_impl_stdperiph_at32.c index 0cc194897d9..782943ab25d 100644 --- a/src/main/drivers/timer_impl_stdperiph_at32.c +++ b/src/main/drivers/timer_impl_stdperiph_at32.c @@ -269,6 +269,13 @@ static void impl_timerDMA_IRQHandler(DMA_t descriptor) { if (DMA_GET_FLAG_STATUS(descriptor, DMA_IT_TCIF)) { TCH_t * tch = (TCH_t *)descriptor->userParam; + + // In circular mode, let DMA keep running - don't disable the channel + if (tch->dmaState == TCH_DMA_CIRCULAR) { + DMA_CLEAR_FLAG(descriptor, DMA_IT_TCIF); + return; + } + tch->dmaState = TCH_DMA_IDLE; dma_channel_enable(tch->dma->ref,FALSE); tmr_dma_request_enable(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], FALSE); @@ -407,3 +414,48 @@ void impl_timerPWMStopDMA(TCH_t * tch) tch->dmaState = TCH_DMA_IDLE; tmr_counter_enable(tch->timHw->tim, TRUE); } + +void impl_timerPWMSetDMACircular(TCH_t * tch, bool circular, uint32_t dmaBufferSize) +{ + if (!tch->dma || !tch->dma->ref) { + return; + } + + ATOMIC_BLOCK(NVIC_PRIO_MAX) { + // Stop new transfer triggers before reconfiguring + tmr_dma_request_enable(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], FALSE); + dma_channel_enable(tch->dma->ref, FALSE); + + // AT32: poll enable bit until channel is actually disabled + uint32_t timeout = 10000; // ~40us at 288MHz, well above worst-case disable latency + while (tch->dma->ref->ctrl_bit.chen && timeout--) { + __NOP(); + } + + if (tch->dma->ref->ctrl_bit.chen) { + tmr_dma_request_enable(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], TRUE); + return; + } + + DMA_CLEAR_FLAG(tch->dma, DMA_IT_TCIF); + + if (circular) { + tch->dma->ref->ctrl_bit.lm = TRUE; + dma_data_number_set(tch->dma->ref, dmaBufferSize); + // Disable TC interrupt — in circular mode, TC fires every cycle + // and the IRQ handler would otherwise disable the channel + dma_interrupt_enable(tch->dma->ref, DMA_IT_TCIF, FALSE); + tch->dmaState = TCH_DMA_CIRCULAR; + } else { + tch->dma->ref->ctrl_bit.lm = FALSE; + dma_interrupt_enable(tch->dma->ref, DMA_IT_TCIF, TRUE); + tch->dmaState = TCH_DMA_IDLE; + } + + // Ensure register writes are visible to DMA before re-enabling + __DSB(); + + dma_channel_enable(tch->dma->ref, TRUE); + tmr_dma_request_enable(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], TRUE); + } +}