#pragma once #include "IR_Encoder.h" #include "IrTxBsrrWave.h" #if defined(ARDUINO_ARCH_STM32) && defined(STM32G4xx) #if defined(_MSC_VER) #define IRPROTO_DMA_PRAGMA_MESSAGE(text) __pragma(message(text)) #else #define IRPROTO_DMA_PRAGMA_MESSAGE(text) _Pragma(#text) #endif IRPROTO_DMA_PRAGMA_MESSAGE(message("[IR-protocol] TX path available: built-in DMA")) #include #include #if defined(__GNUC__) #define IR_DMA_TX_HOT __attribute__((always_inline)) inline #else #define IR_DMA_TX_HOT inline #endif #include "stm32g4xx_hal.h" /** * STM32G4: ИК TX через DMA в GPIO BSRR, такт от TIM UPDATE (carrierFrec × IR_Encoder::carrierMultiply()). * * Число слотов потоков — параметр шаблона (без макросов), например IrDmaTxStm32<2>. * По умолчанию: IrDmaTxStm32<> ≡ irproto::kDefaultDmaTxMaxStreams (см. IR_config.h). * * Контракт: ref/IR_DMA_TX_backend.md */ template class IrDmaTxStm32 { static_assert(MaxStreams >= 1U, "IrDmaTxStm32: MaxStreams >= 1"); public: struct StreamCfg { DMA_Channel_TypeDef* instance = nullptr; IRQn_Type irq = IRQn_Type(0); uint32_t dmamuxRequest = 0; IR_Encoder* enc = nullptr; uint32_t* dmaWords = nullptr; uint16_t dmaWordCount = 0; IrTxGateRun* gateRuns = nullptr; size_t maxGateRuns = 0; }; struct Config { HardwareTimer* timer = nullptr; uint8_t streamCount = 0; StreamCfg streams[MaxStreams]; }; bool begin(const Config& cfg) { cfg_ = cfg; streamCount_ = cfg.streamCount; if (cfg_.timer == nullptr || streamCount_ == 0) return false; if (streamCount_ > MaxStreams) return false; htim_ = cfg_.timer->getHandle(); if (htim_ == nullptr) return false; for (uint8_t i = 0; i < streamCount_; i++) { const StreamCfg& sc = cfg_.streams[i]; if (sc.enc == nullptr || sc.instance == nullptr) return false; if (sc.dmaWords == nullptr || sc.dmaWordCount < 2U) return false; if ((sc.dmaWordCount & 1U) != 0U) return false; if (sc.gateRuns == nullptr || sc.maxGateRuns == 0U) return false; } __HAL_RCC_DMA1_CLK_ENABLE(); __HAL_RCC_DMAMUX1_CLK_ENABLE(); for (uint8_t i = 0; i < streamCount_; i++) { const StreamCfg& sc = cfg_.streams[i]; if (!initStream(streams_[i], sc)) { return false; } } s_instance = this; activeCount_ = 0; for (uint8_t i = 0; i < streamCount_; i++) { HAL_NVIC_EnableIRQ(streams_[i].dmaIrq); } return true; } static IrDmaTxStm32* instance() { return s_instance; } bool busy() const { if (streamCount_ == 0) return false; for (uint8_t i = 0; i < streamCount_; i++) { if (!streams_[i].active) return false; } return true; } IR_SendStatus start(IR_Encoder* enc, const uint8_t* packet, uint8_t len) { if (enc == nullptr) return IR_SendStatus::ExternalNoStream; for (uint8_t i = 0; i < streamCount_; i++) { if (streams_[i].enc == enc) { return startStream(streams_[i], packet, len); } } return IR_SendStatus::ExternalNoStream; } void irqForStream(size_t streamIndex) { if (streamIndex >= streamCount_) return; HAL_DMA_IRQHandler(&streams_[streamIndex].hdma); } DMA_HandleTypeDef* dmaHandle(size_t streamIndex) { if (streamIndex >= streamCount_) return nullptr; return &streams_[streamIndex].hdma; } private: struct TxStream { DMA_HandleTypeDef hdma{}; DMA_Channel_TypeDef* dmaInstance = nullptr; IRQn_Type dmaIrq = IRQn_Type(0); uint32_t dmamuxRequest = 0; IR_Encoder* enc = nullptr; GPIO_TypeDef* port = nullptr; uint16_t mask = 0; uint32_t setWord = 0; uint32_t resetWord = 0; uint32_t* dmaBuf = nullptr; uint16_t bufLen = 0; uint16_t halfLen = 0; IrTxGateRun* runs = nullptr; size_t maxRuns = 0; size_t runCount = 0; IrTxBsrrWave wave{}; uint32_t totalTicks = 0; volatile uint32_t ticksOutput = 0; bool active = false; void resetWave() { wave.configure(setWord, resetWord, nullptr, 0, 2, 1); ticksOutput = 0; totalTicks = 0; runCount = 0; } IR_DMA_TX_HOT void fill(uint32_t* dst, uint16_t count) { wave.fill(dst, count); } void onHalf() { ticksOutput += halfLen; fill(&dmaBuf[0], halfLen); } void onComplete() { ticksOutput += halfLen; fill(&dmaBuf[halfLen], halfLen); } void onError() {} }; static IrDmaTxStm32* s_instance; Config cfg_{}; TIM_HandleTypeDef* htim_ = nullptr; TxStream streams_[MaxStreams]{}; uint8_t streamCount_ = 0; volatile uint8_t activeCount_ = 0; static uint32_t u32ptr(const volatile void* p) { return (uint32_t)(uintptr_t)p; } void startTimerIfNeeded() { if (htim_ == nullptr) return; if (activeCount_ != 1) return; __HAL_TIM_DISABLE_DMA(htim_, TIM_DMA_UPDATE); __HAL_TIM_CLEAR_FLAG(htim_, TIM_FLAG_UPDATE); __HAL_TIM_SET_COUNTER(htim_, 0); __HAL_TIM_ENABLE_DMA(htim_, TIM_DMA_UPDATE); HAL_TIM_Base_Start(htim_); } void stopTimerIfIdle() { if (htim_ == nullptr) return; if (activeCount_ != 0) return; __HAL_TIM_DISABLE_DMA(htim_, TIM_DMA_UPDATE); HAL_TIM_Base_Stop(htim_); } static TxStream* streamFromDma(DMA_HandleTypeDef* hdma) { if (s_instance == nullptr || hdma == nullptr) return nullptr; for (uint8_t i = 0; i < s_instance->streamCount_; i++) { if (hdma == &s_instance->streams_[i].hdma) { return &s_instance->streams_[i]; } } return nullptr; } static void dmaHalfCpltCb(DMA_HandleTypeDef* hdma) { auto* s = streamFromDma(hdma); if (s == nullptr || !s->active) return; s->onHalf(); if (s_instance != nullptr && s->ticksOutput >= s->totalTicks) { s_instance->stopStream(*s); } } static void dmaCpltCb(DMA_HandleTypeDef* hdma) { auto* s = streamFromDma(hdma); if (s == nullptr || !s->active) return; s->onComplete(); if (s_instance != nullptr && s->ticksOutput >= s->totalTicks) { s_instance->stopStream(*s); } } static void dmaErrorCb(DMA_HandleTypeDef* hdma) { auto* s = streamFromDma(hdma); if (s == nullptr) return; s->onError(); if (s_instance != nullptr) { s_instance->stopStream(*s); } } bool initStream(TxStream& s, const StreamCfg& chCfg) { s.enc = chCfg.enc; s.dmaInstance = chCfg.instance; s.dmaIrq = chCfg.irq; s.dmamuxRequest = chCfg.dmamuxRequest; s.dmaBuf = chCfg.dmaWords; s.bufLen = chCfg.dmaWordCount; s.halfLen = (uint16_t)(chCfg.dmaWordCount / 2U); s.runs = chCfg.gateRuns; s.maxRuns = chCfg.maxGateRuns; s.port = (s.enc != nullptr) ? s.enc->getPort() : nullptr; s.mask = (s.enc != nullptr) ? s.enc->getPinMask() : 0; s.setWord = (uint32_t)s.mask; s.resetWord = ((uint32_t)s.mask) << 16; s.resetWave(); s.hdma.Instance = s.dmaInstance; s.hdma.Init.Request = s.dmamuxRequest; s.hdma.Init.Direction = DMA_MEMORY_TO_PERIPH; s.hdma.Init.PeriphInc = DMA_PINC_DISABLE; s.hdma.Init.MemInc = DMA_MINC_ENABLE; s.hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; s.hdma.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; s.hdma.Init.Mode = DMA_CIRCULAR; s.hdma.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_DeInit(&s.hdma); if (HAL_DMA_Init(&s.hdma) != HAL_OK) { return false; } s.hdma.XferHalfCpltCallback = dmaHalfCpltCb; s.hdma.XferCpltCallback = dmaCpltCb; s.hdma.XferErrorCallback = dmaErrorCb; s.hdma.XferAbortCallback = nullptr; return true; } IR_SendStatus startStream(TxStream& s, const uint8_t* packet, uint8_t len) { if (s.enc == nullptr || s.port == nullptr || s.mask == 0) return IR_SendStatus::ExternalInvalidConfig; if (s.active) return IR_SendStatus::EncoderBusy; if (s.dmaBuf == nullptr || s.bufLen < 2 || s.halfLen == 0) return IR_SendStatus::ExternalInvalidConfig; if (s.runs == nullptr || s.maxRuns == 0) return IR_SendStatus::ExternalInvalidConfig; s.resetWave(); const uint16_t mult = IR_Encoder::carrierMultiply(); s.runCount = IR_Encoder::buildPhysicalGateRuns(packet, len, s.runs, s.maxRuns, mult); if (s.runCount == 0) return IR_SendStatus::BuildGateRunsFailed; uint32_t total = 0; for (size_t i = 0; i < s.runCount; i++) total += s.runs[i].lenTicks; s.totalTicks = total; uint16_t pwr = mult / 2U; if (s.enc != nullptr) { const uint16_t want = s.enc->powerNumerator(); const uint16_t cap = IR_Encoder::maxPowerNumerator(); pwr = (want > cap) ? cap : want; } s.wave.configure(s.setWord, s.resetWord, s.runs, s.runCount, mult, pwr); s.fill(&s.dmaBuf[0], s.bufLen); s.port->BSRR = s.resetWord; const uint32_t dst = u32ptr(&s.port->BSRR); if (HAL_DMA_Start_IT(&s.hdma, (uint32_t)(uintptr_t)s.dmaBuf, dst, s.bufLen) != HAL_OK) { return IR_SendStatus::DmaStartFailed; } s.active = true; activeCount_++; startTimerIfNeeded(); return IR_SendStatus::Success; } void stopStream(TxStream& s) { if (!s.active) return; s.active = false; HAL_DMA_Abort_IT(&s.hdma); if (s.port != nullptr) { s.port->BSRR = s.resetWord; } if (s.enc != nullptr) { s.enc->externalFinishSend(); } if (activeCount_ > 0) activeCount_--; stopTimerIfIdle(); } }; template IrDmaTxStm32* IrDmaTxStm32::s_instance = nullptr; inline void IrDmaTxStm32_onDmaHandle(DMA_HandleTypeDef* hdma) { HAL_DMA_IRQHandler(hdma); } #elif defined(ARDUINO_ARCH_STM32) #error "IrDmaTxStm32: добавьте ветку HAL для вашей серии STM32 (сейчас только STM32G4xx)." #else template class IrDmaTxStm32 {}; #endif