fix(ir-dma): volatile active + errorCount() observability; live poll() in longData example

- IrDmaTxStm32.h: TxStream::active -> volatile (делится поток<->ISR: busy()/poll()/колбэки/спин applyCarrierMultiply)
- IrDmaTxStm32.h: + volatile errors_ и errorCount(), инкремент в dmaErrorCb (наблюдаемость Transfer-Error)
- test_examples/longData: dmaBackend.poll() в loop, иначе watchdog завершения DMA мёртв в эталонном примере

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 12:25:36 +03:00
parent 7176fe5321
commit 55935b8b92
2 changed files with 6 additions and 1 deletions

View File

@ -160,6 +160,8 @@ public:
void setStallTimeoutMs(uint32_t ms) { if (ms != 0U) stallTimeoutMs_ = ms; } void setStallTimeoutMs(uint32_t ms) { if (ms != 0U) stallTimeoutMs_ = ms; }
/** Сколько раз watchdog аварийно восстановил залипший поток (наблюдаемость/логи). */ /** Сколько раз watchdog аварийно восстановил залипший поток (наблюдаемость/логи). */
uint32_t recoveryCount() const { return recoveries_; } uint32_t recoveryCount() const { return recoveries_; }
/** Сколько раз DMA-передача завершилась по Transfer-Error (наблюдаемость/логи). */
uint32_t errorCount() const { return errors_; }
private: private:
struct TxStream { struct TxStream {
@ -191,7 +193,7 @@ private:
uint32_t lastTicks = 0; uint32_t lastTicks = 0;
uint32_t lastProgressMs = 0; uint32_t lastProgressMs = 0;
bool active = false; volatile bool active = false; // Fix: делится поток<->ISR (busy()/poll()/колбэки/спин applyCarrierMultiply) — запрет кэширования компилятором
void resetWave() { void resetWave() {
wave.configure(setWord, resetWord, nullptr, 0, 2, 1); wave.configure(setWord, resetWord, nullptr, 0, 2, 1);
@ -228,6 +230,7 @@ private:
// Fix C: разделяемый activeCount_ удалён — он и был источником гонки поток↔ISR. // Fix C: разделяемый activeCount_ удалён — он и был источником гонки поток↔ISR.
uint32_t stallTimeoutMs_ = 100; // Fix D uint32_t stallTimeoutMs_ = 100; // Fix D
uint32_t recoveries_ = 0; // Fix D (только контекст потока) uint32_t recoveries_ = 0; // Fix D (только контекст потока)
volatile uint32_t errors_ = 0; // Fix #5: счётчик Transfer-Error (пишется в ISR dmaErrorCb, читается из потока)
static uint32_t u32ptr(const volatile void* p) { static uint32_t u32ptr(const volatile void* p) {
return (uint32_t)(uintptr_t)p; return (uint32_t)(uintptr_t)p;
@ -292,6 +295,7 @@ private:
if (s == nullptr || !s->active) return; // симметрично HT/CPLT: одиночный stop, без двойного finish if (s == nullptr || !s->active) return; // симметрично HT/CPLT: одиночный stop, без двойного finish
s->onError(); s->onError();
if (s_instance != nullptr) { if (s_instance != nullptr) {
s_instance->errors_++; // Fix #5: наблюдаемость аварийных завершений по Transfer-Error
s_instance->stopStream(*s); s_instance->stopStream(*s);
} }
} }

View File

@ -132,6 +132,7 @@ void setup() {
void loop() { void loop() {
#if LONGDATA_USE_DMA #if LONGDATA_USE_DMA
IR_Encoder::tick(); IR_Encoder::tick();
dmaBackend.poll(); // Fix D: watchdog завершения DMA-передачи (как в Car/src/IR/IR.cpp)
#endif #endif
const uint32_t now = millis(); const uint32_t now = millis();