Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/config/config_eeprom.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "drivers/system.h"
#include "drivers/flash.h"
#include "drivers/pwm_output.h"

#include "fc/config.h"

Expand Down Expand Up @@ -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
Comment on lines +325 to +330
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Gate the DMA mode switch on the runtime motor protocol (e.g., isMotorProtocolDshot()), and skip the whole sequence if DShot isn’t active to avoid unintended DMA reconfiguration on other setups. [Learned best practice, importance: 6]

Suggested change
#if !defined(SITL_BUILD) && defined(USE_DSHOT)
// Prevent ESC spinup during settings save using circular DMA
pwmSetMotorDMACircular(true);
// Force motor updates to latch current (zero) throttle into circular DMA buffer
pwmCompleteMotorUpdate();
delayMicroseconds(200);
pwmCompleteMotorUpdate();
delayMicroseconds(200);
pwmCompleteMotorUpdate();
#endif
#if !defined(SITL_BUILD) && defined(USE_DSHOT)
if (isMotorProtocolDshot()) {
// Prevent ESC spinup during settings save using circular DMA
pwmSetMotorDMACircular(true);
// Force motor updates to latch current (zero) throttle into circular DMA buffer
pwmCompleteMotorUpdate();
delayMicroseconds(200);
pwmCompleteMotorUpdate();
delayMicroseconds(200);
pwmCompleteMotorUpdate();
}
#endif


bool success = false;
// write it
for (int attempt = 0; attempt < 3 && !success; attempt++) {
Expand All @@ -333,6 +341,10 @@ void writeConfigToEEPROM(void)
}
}

#if !defined(SITL_BUILD) && defined(USE_DSHOT)
pwmSetMotorDMACircular(false);
#endif

if (success && isEEPROMContentValid()) {
return;
}
Expand Down
65 changes: 62 additions & 3 deletions src/main/drivers/pwm_output.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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++) {
Expand Down
1 change: 1 addition & 0 deletions src/main/drivers/pwm_output.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/main/drivers/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/main/drivers/timer_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
95 changes: 95 additions & 0 deletions src/main/drivers/timer_impl_hal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]);
}
}
92 changes: 92 additions & 0 deletions src/main/drivers/timer_impl_stdperiph.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
Comment on lines +576 to +612
Copy link
Copy Markdown
Contributor

@qodo-code-review qodo-code-review Bot Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Match the HAL implementation by disabling the timer’s DMA request source before disabling/reconfiguring the DMA stream, then re-enable it afterward to prevent mid-transition DMA triggers. [Learned best practice, importance: 5]

Suggested change
ATOMIC_BLOCK(NVIC_PRIO_MAX) {
// Temporarily disable DMA while modifying configuration
DMA_Cmd(tch->dma->ref, DISABLE);
// Wait for DMA stream to actually be disabled
// The EN bit doesn't clear immediately, especially if transfer is in progress
uint32_t timeout = 10000;
while ((tch->dma->ref->CR & DMA_SxCR_EN) && timeout--) {
__NOP();
}
// If timeout occurred, DMA stream is still enabled - abort reconfiguration
if (timeout == 0 && (tch->dma->ref->CR & DMA_SxCR_EN)) {
DMA_Cmd(tch->dma->ref, ENABLE); // Re-enable and return
return;
}
// Modify the DMA mode
if (circular) {
tch->dma->ref->CR |= DMA_SxCR_CIRC; // Set circular bit
} else {
tch->dma->ref->CR &= ~DMA_SxCR_CIRC; // Clear circular bit
}
// Re-enable DMA
DMA_Cmd(tch->dma->ref, ENABLE);
}
ATOMIC_BLOCK(NVIC_PRIO_MAX) {
// Disable timer DMA request first to stop new transfer triggers
TIM_DMACmd(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], DISABLE);
// Temporarily disable DMA while modifying configuration
DMA_Cmd(tch->dma->ref, DISABLE);
uint32_t timeout = 10000;
while ((tch->dma->ref->CR & DMA_SxCR_EN) && timeout--) {
__NOP();
}
if (timeout == 0 && (tch->dma->ref->CR & DMA_SxCR_EN)) {
// Restore timer DMA requests and return to avoid unstable state
TIM_DMACmd(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], ENABLE);
DMA_Cmd(tch->dma->ref, ENABLE);
return;
}
if (circular) {
tch->dma->ref->CR |= DMA_SxCR_CIRC;
} else {
tch->dma->ref->CR &= ~DMA_SxCR_CIRC;
}
DMA_Cmd(tch->dma->ref, ENABLE);
TIM_DMACmd(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], ENABLE);
}

}
Loading
Loading