mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-04-28 03:08:08 +00:00
dma fix priority and debug
This commit is contained in:
386
IrDmaTxStm32.h
Normal file
386
IrDmaTxStm32.h
Normal file
@ -0,0 +1,386 @@
|
||||
#pragma once
|
||||
|
||||
#include "IR_Encoder.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_STM32) && defined(STM32G4xx)
|
||||
|
||||
#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×2).
|
||||
*
|
||||
* Число слотов потоков — параметр шаблона (без макросов), например 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;
|
||||
|
||||
IR_Encoder::IR_TxGateRun* 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;
|
||||
}
|
||||
|
||||
bool start(IR_Encoder* enc, const uint8_t* packet, uint8_t len) {
|
||||
if (enc == nullptr) return false;
|
||||
for (uint8_t i = 0; i < streamCount_; i++) {
|
||||
if (streams_[i].enc == enc) {
|
||||
return startStream(streams_[i], packet, len);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
IR_Encoder::IR_TxGateRun* runs = nullptr;
|
||||
size_t maxRuns = 0;
|
||||
|
||||
size_t runCount = 0;
|
||||
size_t runIndex = 0;
|
||||
uint16_t ticksLeftInRun = 0;
|
||||
bool carrierPhase = false;
|
||||
|
||||
uint32_t totalTicks = 0;
|
||||
volatile uint32_t ticksOutput = 0;
|
||||
|
||||
bool active = false;
|
||||
|
||||
void resetWave() {
|
||||
runIndex = 0;
|
||||
carrierPhase = false;
|
||||
ticksOutput = 0;
|
||||
totalTicks = 0;
|
||||
runCount = 0;
|
||||
ticksLeftInRun = 0;
|
||||
}
|
||||
|
||||
IR_DMA_TX_HOT uint32_t nextWord() {
|
||||
if (runIndex >= runCount) {
|
||||
return resetWord;
|
||||
}
|
||||
const bool gate = runs[runIndex].gate;
|
||||
if (!gate) {
|
||||
carrierPhase = false;
|
||||
} else {
|
||||
carrierPhase = !carrierPhase;
|
||||
}
|
||||
const uint32_t out = (gate && carrierPhase) ? setWord : resetWord;
|
||||
|
||||
if (ticksLeftInRun > 0) {
|
||||
ticksLeftInRun--;
|
||||
}
|
||||
if (ticksLeftInRun == 0) {
|
||||
runIndex++;
|
||||
if (runIndex < runCount) {
|
||||
ticksLeftInRun = runs[runIndex].lenTicks;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
IR_DMA_TX_HOT void fill(uint32_t* dst, uint16_t count) {
|
||||
if (dst == nullptr || count == 0) {
|
||||
return;
|
||||
}
|
||||
do {
|
||||
*dst++ = nextWord();
|
||||
} while (--count != 0);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool startStream(TxStream& s, const uint8_t* packet, uint8_t len) {
|
||||
if (s.enc == nullptr || s.port == nullptr || s.mask == 0) return false;
|
||||
if (s.active) return false;
|
||||
if (s.dmaBuf == nullptr || s.bufLen < 2 || s.halfLen == 0) return false;
|
||||
if (s.runs == nullptr || s.maxRuns == 0) return false;
|
||||
|
||||
s.resetWave();
|
||||
|
||||
s.runCount = IR_Encoder::buildGateRuns(packet, len, s.runs, s.maxRuns);
|
||||
if (s.runCount == 0) return false;
|
||||
|
||||
uint32_t total = 0;
|
||||
for (size_t i = 0; i < s.runCount; i++) total += s.runs[i].lenTicks;
|
||||
s.totalTicks = total;
|
||||
|
||||
s.runIndex = 0;
|
||||
s.ticksLeftInRun = s.runs[0].lenTicks;
|
||||
s.carrierPhase = false;
|
||||
|
||||
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 false;
|
||||
}
|
||||
|
||||
s.active = true;
|
||||
activeCount_++;
|
||||
startTimerIfNeeded();
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user