mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-04-28 03:08:08 +00:00
369 lines
11 KiB
C++
369 lines
11 KiB
C++
#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 <Arduino.h>
|
||
#include <HardwareTimer.h>
|
||
|
||
#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<size_t MaxStreams = irproto::kDefaultDmaTxMaxStreams>
|
||
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<MaxStreams>* 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<MaxStreams>* 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<size_t MaxStreams>
|
||
IrDmaTxStm32<MaxStreams>* IrDmaTxStm32<MaxStreams>::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<size_t MaxStreams = irproto::kDefaultDmaTxMaxStreams>
|
||
class IrDmaTxStm32 {};
|
||
|
||
#endif
|