add brightness controll

This commit is contained in:
2026-04-17 09:45:32 +03:00
parent 8daff9c46a
commit d788358ac8
8 changed files with 426 additions and 70 deletions

View File

@ -207,7 +207,7 @@ HardwareTimer IR_Timer(TIM3);
void setup() void setup()
{ {
IR_Timer.setOverflow(carrierFrec * 2, HERTZ_FORMAT); IR_Timer.setOverflow((uint32_t)carrierFrec * (uint32_t)IR_Encoder::carrierMultiply(), HERTZ_FORMAT);
IR_Timer.attachInterrupt(1, EncoderISR); IR_Timer.attachInterrupt(1, EncoderISR);
NVIC_SetPriority(IRQn_Type::TIM3_IRQn, 0); NVIC_SetPriority(IRQn_Type::TIM3_IRQn, 0);
IR_Timer.resume(); IR_Timer.resume();

View File

@ -38,12 +38,120 @@ IR_Encoder::IR_Encoder(uint8_t pin, uint16_t addr, IR_DecoderRaw *decPair, bool
pinMode(pin, OUTPUT); pinMode(pin, OUTPUT);
} }
powerNumerator_ = 1;
}; };
HardwareTimer* IR_Encoder::IR_Timer = nullptr; HardwareTimer* IR_Encoder::IR_Timer = nullptr;
IR_Encoder::ExternalTxStartFn IR_Encoder::externalTxStartFn = nullptr; IR_Encoder::ExternalTxStartFn IR_Encoder::externalTxStartFn = nullptr;
IR_Encoder::ExternalTxBusyFn IR_Encoder::externalTxBusyFn = nullptr; IR_Encoder::ExternalTxBusyFn IR_Encoder::externalTxBusyFn = nullptr;
void *IR_Encoder::externalTxCtx = nullptr; void *IR_Encoder::externalTxCtx = nullptr;
bool IR_Encoder::txIsrLegacyMode_ = false;
uint16_t IR_Encoder::s_carrierMultiply = 2;
void IR_Encoder::setCarrierMultiply(uint16_t multiply)
{
if (multiply < 2)
{
multiply = 2;
}
s_carrierMultiply = multiply;
}
uint16_t IR_Encoder::carrierMultiply()
{
return s_carrierMultiply;
}
void IR_Encoder::retuneCarrierClock()
{
if (IR_Timer == nullptr)
{
return;
}
IR_Timer->pause();
IR_Timer->setOverflow((uint32_t)carrierFrec * (uint32_t)s_carrierMultiply, HERTZ_FORMAT);
IR_Timer->pause();
}
uint16_t IR_Encoder::maxPowerNumerator()
{
return static_cast<uint16_t>(s_carrierMultiply / 2U);
}
void IR_Encoder::setPowerNumerator(uint16_t n)
{
const uint16_t cap = maxPowerNumerator();
powerNumerator_ = (n > cap) ? cap : n;
}
void IR_Encoder::setPowerPercent(uint8_t p)
{
if (p > 100U)
{
p = 100U;
}
const uint16_t cap = maxPowerNumerator();
const uint32_t n = ((uint32_t)p * (uint32_t)cap + 50U) / 100U;
powerNumerator_ = static_cast<uint16_t>(n);
}
uint16_t IR_Encoder::powerNumerator() const
{
return powerNumerator_;
}
bool IR_Encoder::scaleGateRunsToPhysical(IR_TxGateRun* runs, size_t* ioCount, size_t maxRuns, uint16_t multiply)
{
if (runs == nullptr || ioCount == nullptr || maxRuns == 0)
{
return false;
}
if (multiply < 2)
{
multiply = 2;
}
const size_t nIn = *ioCount;
if (nIn > irproto::kIsrTxMaxGateRuns)
{
return false;
}
IrTxGateRun copy[irproto::kIsrTxMaxGateRuns];
memcpy(copy, runs, nIn * sizeof(IrTxGateRun));
size_t w = 0;
for (size_t r = 0; r < nIn; r++)
{
uint32_t phys = (uint32_t)copy[r].lenTicks * (uint32_t)multiply / 2U;
if (copy[r].lenTicks > 0 && phys == 0)
{
phys = 1;
}
const bool g = copy[r].gate;
while (phys > 0)
{
if (w >= maxRuns)
{
return false;
}
const uint32_t chunk = phys > 65535U ? 65535U : phys;
runs[w].lenTicks = static_cast<uint16_t>(chunk);
runs[w].gate = g;
w++;
phys -= chunk;
}
}
*ioCount = w;
return true;
}
void IR_Encoder::setTxIsrLegacyMode(bool legacy)
{
txIsrLegacyMode_ = legacy;
}
bool IR_Encoder::txIsrLegacyMode()
{
return txIsrLegacyMode_;
}
bool IR_Encoder::txAdvanceBoundary(TxFsmState &st, const uint8_t *sendBufferLocal) bool IR_Encoder::txAdvanceBoundary(TxFsmState &st, const uint8_t *sendBufferLocal)
{ {
@ -194,7 +302,7 @@ void IR_Encoder::begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, ui
IR_Timer = timer; IR_Timer = timer;
if(IR_Timer == nullptr) return; if(IR_Timer == nullptr) return;
IR_Timer->pause(); IR_Timer->pause();
IR_Timer->setOverflow(carrierFrec * 2, HERTZ_FORMAT); IR_Timer->setOverflow((uint32_t)carrierFrec * (uint32_t)s_carrierMultiply, HERTZ_FORMAT);
IR_Timer->attachInterrupt(channel, (isrCallback == nullptr ? IR_Encoder::isr : isrCallback)); IR_Timer->attachInterrupt(channel, (isrCallback == nullptr ? IR_Encoder::isr : isrCallback));
NVIC_SetPriority(IRQn, priority); NVIC_SetPriority(IRQn, priority);
IR_Timer->pause(); IR_Timer->pause();
@ -206,7 +314,7 @@ void IR_Encoder::beginClockOnly(HardwareTimer *timer)
if (IR_Timer == nullptr) if (IR_Timer == nullptr)
return; return;
IR_Timer->pause(); IR_Timer->pause();
IR_Timer->setOverflow(carrierFrec * 2, HERTZ_FORMAT); IR_Timer->setOverflow((uint32_t)carrierFrec * (uint32_t)s_carrierMultiply, HERTZ_FORMAT);
IR_Timer->pause(); IR_Timer->pause();
} }
@ -607,23 +715,86 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
} }
return; return;
} }
sendLen = len;
toggleCounter = preambToggle; // Первая генерация для первого signal
if (port == nullptr || mask == 0)
{
return;
}
if (ptr != sendBuffer)
{
memcpy(sendBuffer, ptr, len);
}
sendLen = len;
if (txIsrLegacyMode_)
{
toggleCounter = preambToggle;
dataBitCounter = bitPerByte - 1; dataBitCounter = bitPerByte - 1;
dataByteCounter = 0; dataByteCounter = 0;
preambFrontCounter = preambPulse * 2 - 1;
preambFrontCounter = preambPulse * 2 - 1; // -1 за счёт генерации уже на этапе сразу после инициализации
dataSequenceCounter = bitPerByte * 2; dataSequenceCounter = bitPerByte * 2;
syncSequenceCounter = syncBits * 2; syncSequenceCounter = syncBits * 2;
signal = preamb; signal = preamb;
isSending = true;
state = HIGH; state = HIGH;
currentBitSequence = bitHigh; currentBitSequence = bitHigh;
txMultiplySnap_ = carrierMultiply();
{
const uint16_t cap = maxPowerNumerator();
txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_;
}
legacyPhysPerLogical_ = static_cast<uint16_t>(txMultiplySnap_ / 2U);
if (legacyPhysPerLogical_ == 0)
{
legacyPhysPerLogical_ = 1;
}
legacyPhysCounter_ = 0;
legacySlotInPeriod_ = 0;
isSending = true;
refreshBlindDecoderMuteState(); refreshBlindDecoderMuteState();
IR_Encoder::carrierResume(); IR_Encoder::carrierResume();
// interrupts(); return;
}
size_t nRuns = buildGateRuns(sendBuffer, len, txGateRuns_, irproto::kIsrTxMaxGateRuns);
if (nRuns == 0U)
{
return;
}
if (!scaleGateRunsToPhysical(txGateRuns_, &nRuns, irproto::kIsrTxMaxGateRuns, carrierMultiply()))
{
return;
}
uint32_t total = 0;
for (size_t i = 0; i < nRuns; i++)
{
total += txGateRuns_[i].lenTicks;
}
txBsrrTotalTicks_ = total;
const uint32_t setW = (uint32_t)mask;
const uint32_t resetW = ((uint32_t)mask) << 16U;
txMultiplySnap_ = carrierMultiply();
{
const uint16_t cap = maxPowerNumerator();
txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_;
}
txBsrrWave_.configure(setW, resetW, txGateRuns_, nRuns, txMultiplySnap_, txPowerSnap_);
txBsrrHalfLen_ = (uint16_t)(irproto::kIsrTxBsrrWordCount / 2U);
txBsrrWave_.fill(txBsrrWords_, irproto::kIsrTxBsrrWordCount);
txBsrrReadIdx_ = 0;
txBsrrTicksSent_ = 0;
isSending = true;
refreshBlindDecoderMuteState();
if (port != nullptr)
{
port->BSRR = resetW;
}
IR_Encoder::carrierResume();
} }
void IR_Encoder::isr() void IR_Encoder::isr()
@ -641,10 +812,34 @@ void IR_Encoder::_isr()
if (!isSending) if (!isSending)
return; return;
ir_out_virtual = !ir_out_virtual && state; if (port == nullptr)
return;
port->ODR &= ~(mask); if (txIsrLegacyMode_)
port->ODR |= mask & (ir_out_virtual ? (uint16_t)0xFFFF : (uint16_t)0x0000); {
const uint32_t setW = (uint32_t)mask;
const uint32_t resetW = ((uint32_t)mask) << 16U;
if (!state)
{
port->BSRR = resetW;
legacySlotInPeriod_ = 0;
}
else
{
port->BSRR = (legacySlotInPeriod_ < txPowerSnap_) ? setW : resetW;
legacySlotInPeriod_++;
if (legacySlotInPeriod_ >= txMultiplySnap_)
{
legacySlotInPeriod_ = 0;
}
}
legacyPhysCounter_++;
if (legacyPhysCounter_ < legacyPhysPerLogical_)
{
return;
}
legacyPhysCounter_ = 0;
TxFsmState st{}; TxFsmState st{};
loadTxFsmFromMembers(st); loadTxFsmFromMembers(st);
@ -653,10 +848,36 @@ void IR_Encoder::_isr()
if (!active) if (!active)
{ {
port->BSRR = resetW;
isSending = false; isSending = false;
refreshBlindDecoderMuteState(); refreshBlindDecoderMuteState();
carrierStopPending = true; carrierStopPending = true;
} }
return;
}
port->BSRR = txBsrrWords_[txBsrrReadIdx_];
txBsrrReadIdx_++;
txBsrrTicksSent_++;
if (txBsrrTicksSent_ >= txBsrrTotalTicks_)
{
port->BSRR = ((uint32_t)mask) << 16U;
isSending = false;
refreshBlindDecoderMuteState();
carrierStopPending = true;
return;
}
if (txBsrrReadIdx_ == txBsrrHalfLen_)
{
txBsrrWave_.fill(&txBsrrWords_[0], txBsrrHalfLen_);
}
else if (txBsrrReadIdx_ >= irproto::kIsrTxBsrrWordCount)
{
txBsrrReadIdx_ = 0;
txBsrrWave_.fill(&txBsrrWords_[txBsrrHalfLen_], txBsrrHalfLen_);
}
} }
void IR_Encoder::sendByte(uint8_t byte, bool *prev, bool LOW_FIRST) void IR_Encoder::sendByte(uint8_t byte, bool *prev, bool LOW_FIRST)

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "IR_config.h" #include "IR_config.h"
#include "IrTxBsrrWave.h"
// TODO: Отложенная передача после завершения приема // TODO: Отложенная передача после завершения приема
@ -22,10 +23,7 @@ class IR_Encoder : public IR_FOX
public: public:
static HardwareTimer* IR_Timer; static HardwareTimer* IR_Timer;
struct IR_TxGateRun { using IR_TxGateRun = IrTxGateRun;
uint16_t lenTicks; // number of timer ticks at carrierFrec*2
bool gate; // true: carrier enabled (output toggles), false: silent (output forced low)
};
using ExternalTxBusyFn = bool (*)(void *ctx); using ExternalTxBusyFn = bool (*)(void *ctx);
using ExternalTxStartFn = bool (*)(void *ctx, IR_Encoder *enc, const uint8_t *packet, uint8_t len); using ExternalTxStartFn = bool (*)(void *ctx, IR_Encoder *enc, const uint8_t *packet, uint8_t len);
@ -40,12 +38,42 @@ public:
IR_Encoder(uint8_t pin, uint16_t addr = 0, IR_DecoderRaw *decPair = nullptr, bool autoHandle = true); IR_Encoder(uint8_t pin, uint16_t addr = 0, IR_DecoderRaw *decPair = nullptr, bool autoHandle = true);
static void isr(); static void isr();
static void begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)() = nullptr); static void begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)() = nullptr);
/** Configure timer frequency for TX clock (carrierFrec*2) without attaching ISR. */ /**
* Глобальный знаменатель: частота таймера TX = carrierFrec × multiply (слотов на период несущей).
* По умолчанию multiply=2 (как бывшие carrierFrec×2). Задавать до begin/beginClockOnly либо после
* изменения вызвать retuneCarrierClock() (не менять multiply во время активной передачи).
*/
static void setCarrierMultiply(uint16_t multiply);
static uint16_t carrierMultiply();
/** Повторно применить carrierFrec×multiply к IR_Timer (pause + setOverflow), ISR не перенавешивает. */
static void retuneCarrierClock();
/** Максимальный числитель мощности: ⌊multiply/2⌋ (100% в setPowerPercent). */
static uint16_t maxPowerNumerator();
/** Числитель N: при открытой огибающей N из multiply тиков HIGH за период несущей. Clamped к maxPowerNumerator(). */
void setPowerNumerator(uint16_t n);
uint16_t powerNumerator() const;
/** p∈[0,100] → ближайший допустимый числитель; 100% даёт N = maxPowerNumerator(). */
void setPowerPercent(uint8_t p);
/** После buildGateRuns: lenTicks в тактах 2×Fc → физические тики (carrierFrec×multiply). Может разбить сегменты. */
static bool scaleGateRunsToPhysical(IR_TxGateRun* runs, size_t* ioCount, size_t maxRuns, uint16_t multiply);
/** Configure timer frequency for TX clock (carrierFrec × multiply) without attaching ISR. */
static void beginClockOnly(HardwareTimer *timer); static void beginClockOnly(HardwareTimer *timer);
static HardwareTimer* get_IR_Timer(); static HardwareTimer* get_IR_Timer();
/** Call from main loop/tick: if ISR requested carrier stop, pause timer here (not in ISR). */ /** Call from main loop/tick: if ISR requested carrier stop, pause timer here (not in ISR). */
static void tick(); static void tick();
/**
* Режим внутреннего TX без DMA: false — BSRR + кольцо (buildGateRuns + scaleGateRunsToPhysical);
* true — FSM «налету» + скважность несущей как у буферного пути (подшаги multiply/2 на шаг FSM).
* Выставить до begin/rawSend (глобально на все IR_Encoder). Игнорируется при externalTxStartFn.
*/
static void setTxIsrLegacyMode(bool legacy);
static bool txIsrLegacyMode();
/** Optional: register external TX backend (e.g. DMA driver). */ /** Optional: register external TX backend (e.g. DMA driver). */
static void setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx); static void setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx);
@ -99,6 +127,8 @@ public:
void _isr(); void _isr();
private: private:
static volatile bool carrierStopPending; static volatile bool carrierStopPending;
static bool txIsrLegacyMode_;
static uint16_t s_carrierMultiply;
static void carrierResume(); static void carrierResume();
static void carrierPauseIfIdle(); static void carrierPauseIfIdle();
@ -146,6 +176,25 @@ private:
void loadTxFsmFromMembers(TxFsmState &st) const; void loadTxFsmFromMembers(TxFsmState &st) const;
void storeTxFsmToMembers(const TxFsmState &st); void storeTxFsmToMembers(const TxFsmState &st);
IrTxBsrrWave txBsrrWave_{};
IR_TxGateRun txGateRuns_[irproto::kIsrTxMaxGateRuns]{};
uint32_t txBsrrWords_[irproto::kIsrTxBsrrWordCount]{};
uint16_t txBsrrReadIdx_ = 0;
uint16_t txBsrrHalfLen_ = 0;
uint32_t txBsrrTotalTicks_ = 0;
uint32_t txBsrrTicksSent_ = 0;
/** Снимок на старт TX (буферный и legacy путь). */
uint16_t txPowerSnap_ = 1;
uint16_t txMultiplySnap_ = 2;
/** Legacy: физических тиков на один логический шаг FSM = multiply/2. */
uint16_t legacyPhysPerLogical_ = 1;
uint16_t legacyPhysCounter_ = 0;
uint16_t legacySlotInPeriod_ = 0;
volatile uint16_t powerNumerator_ = 1;
IR_DecoderRaw *decPair = nullptr; IR_DecoderRaw *decPair = nullptr;
IR_DecoderRaw *singleBlindDecoder = nullptr; IR_DecoderRaw *singleBlindDecoder = nullptr;
IR_DecoderRaw **blindDecoders = nullptr; IR_DecoderRaw **blindDecoders = nullptr;

View File

@ -6,6 +6,11 @@
/** Число потоков DMA-TX задаётся шаблоном: IrDmaTxStm32<2>, см. IrDmaTxStm32.h и irproto::kDefaultDmaTxMaxStreams. */ /** Число потоков DMA-TX задаётся шаблоном: IrDmaTxStm32<2>, см. IrDmaTxStm32.h и irproto::kDefaultDmaTxMaxStreams. */
namespace irproto { namespace irproto {
constexpr size_t kDefaultDmaTxMaxStreams = 4U; constexpr size_t kDefaultDmaTxMaxStreams = 4U;
/** Кольцевой буфер BSRR-слов для ISR-TX (как у DMA: два полублока). Чётное число. */
constexpr uint16_t kIsrTxBsrrWordCount = 256U;
/** Максимум RLE-сегментов для buildGateRuns при ISR-TX. */
constexpr size_t kIsrTxMaxGateRuns = 512U;
static_assert((kIsrTxBsrrWordCount & 1U) == 0U, "kIsrTxBsrrWordCount must be even");
} }
// Пошаговый разбор кадра на Serial (по умолчанию выключено). Пульсы IRDEBUG на пинах не меняют. // Пошаговый разбор кадра на Serial (по умолчанию выключено). Пульсы IRDEBUG на пинах не меняют.

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "IR_Encoder.h" #include "IR_Encoder.h"
#include "IrTxBsrrWave.h"
#if defined(ARDUINO_ARCH_STM32) && defined(STM32G4xx) #if defined(ARDUINO_ARCH_STM32) && defined(STM32G4xx)
@ -16,7 +17,7 @@
#include "stm32g4xx_hal.h" #include "stm32g4xx_hal.h"
/** /**
* STM32G4: ИК TX через DMA в GPIO BSRR, такт от TIM UPDATE (carrierFrec×2). * STM32G4: ИК TX через DMA в GPIO BSRR, такт от TIM UPDATE (carrierFrec × IR_Encoder::carrierMultiply()).
* *
* Число слотов потоков — параметр шаблона (без макросов), например IrDmaTxStm32<2>. * Число слотов потоков — параметр шаблона (без макросов), например IrDmaTxStm32<2>.
* По умолчанию: IrDmaTxStm32<> ≡ irproto::kDefaultDmaTxMaxStreams (см. IR_config.h). * По умолчанию: IrDmaTxStm32<> ≡ irproto::kDefaultDmaTxMaxStreams (см. IR_config.h).
@ -37,7 +38,7 @@ public:
uint32_t* dmaWords = nullptr; uint32_t* dmaWords = nullptr;
uint16_t dmaWordCount = 0; uint16_t dmaWordCount = 0;
IR_Encoder::IR_TxGateRun* gateRuns = nullptr; IrTxGateRun* gateRuns = nullptr;
size_t maxGateRuns = 0; size_t maxGateRuns = 0;
}; };
@ -134,13 +135,11 @@ private:
uint16_t bufLen = 0; uint16_t bufLen = 0;
uint16_t halfLen = 0; uint16_t halfLen = 0;
IR_Encoder::IR_TxGateRun* runs = nullptr; IrTxGateRun* runs = nullptr;
size_t maxRuns = 0; size_t maxRuns = 0;
size_t runCount = 0; size_t runCount = 0;
size_t runIndex = 0; IrTxBsrrWave wave{};
uint16_t ticksLeftInRun = 0;
bool carrierPhase = false;
uint32_t totalTicks = 0; uint32_t totalTicks = 0;
volatile uint32_t ticksOutput = 0; volatile uint32_t ticksOutput = 0;
@ -148,45 +147,14 @@ private:
bool active = false; bool active = false;
void resetWave() { void resetWave() {
runIndex = 0; wave.configure(setWord, resetWord, nullptr, 0, 2, 1);
carrierPhase = false;
ticksOutput = 0; ticksOutput = 0;
totalTicks = 0; totalTicks = 0;
runCount = 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) { IR_DMA_TX_HOT void fill(uint32_t* dst, uint16_t count) {
if (dst == nullptr || count == 0) { wave.fill(dst, count);
return;
}
do {
*dst++ = nextWord();
} while (--count != 0);
} }
void onHalf() { void onHalf() {
@ -325,13 +293,25 @@ private:
s.runCount = IR_Encoder::buildGateRuns(packet, len, s.runs, s.maxRuns); s.runCount = IR_Encoder::buildGateRuns(packet, len, s.runs, s.maxRuns);
if (s.runCount == 0) return false; if (s.runCount == 0) return false;
size_t rc = s.runCount;
if (!IR_Encoder::scaleGateRunsToPhysical(s.runs, &rc, s.maxRuns, IR_Encoder::carrierMultiply())) {
return false;
}
s.runCount = rc;
uint32_t total = 0; uint32_t total = 0;
for (size_t i = 0; i < s.runCount; i++) total += s.runs[i].lenTicks; for (size_t i = 0; i < s.runCount; i++) total += s.runs[i].lenTicks;
s.totalTicks = total; s.totalTicks = total;
s.runIndex = 0; const uint16_t mult = IR_Encoder::carrierMultiply();
s.ticksLeftInRun = s.runs[0].lenTicks; uint16_t pwr = mult / 2U;
s.carrierPhase = false; 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.fill(&s.dmaBuf[0], s.bufLen);

87
IrTxBsrrWave.h Normal file
View File

@ -0,0 +1,87 @@
#pragma once
#include "IrTxGateTypes.h"
#include <cstddef>
#include <cstdint>
#if defined(__GNUC__)
#define IR_TX_BSRR_WAVE_HOT __attribute__((always_inline)) inline
#else
#define IR_TX_BSRR_WAVE_HOT inline
#endif
/**
* Генерация потока 32-бит слов для GPIO BSRR из RLE-сегментов IrTxGateRun.
* За один период несущей — multiply физических тиков; при gate — powerN из них HIGH (N ≤ multiply/2).
*/
class IrTxBsrrWave {
public:
void configure(uint32_t setW, uint32_t resetW, IrTxGateRun* r, size_t n, uint16_t multiply, uint16_t powerN) {
setWord = setW;
resetWord = resetW;
runs = r;
runCount = n;
multiply_ = multiply < 2 ? 2 : multiply;
const uint16_t cap = static_cast<uint16_t>(multiply_ / 2U);
powerN_ = (powerN > cap) ? cap : powerN;
resetWave();
}
void resetWave() {
runIndex_ = 0;
slotInPeriod_ = 0;
ticksLeftInRun_ = 0;
if (runCount > 0U && runs != nullptr) {
ticksLeftInRun_ = runs[0].lenTicks;
}
}
IR_TX_BSRR_WAVE_HOT uint32_t nextWord() {
if (runIndex_ >= runCount) {
return resetWord;
}
const bool gate = runs[runIndex_].gate;
uint32_t out;
if (!gate) {
slotInPeriod_ = 0;
out = resetWord;
} else {
out = (slotInPeriod_ < powerN_) ? setWord : resetWord;
slotInPeriod_++;
if (slotInPeriod_ >= multiply_) {
slotInPeriod_ = 0;
}
}
if (ticksLeftInRun_ > 0) {
ticksLeftInRun_--;
}
if (ticksLeftInRun_ == 0) {
runIndex_++;
if (runIndex_ < runCount) {
ticksLeftInRun_ = runs[runIndex_].lenTicks;
}
}
return out;
}
IR_TX_BSRR_WAVE_HOT void fill(uint32_t* dst, uint16_t count) {
if (dst == nullptr || count == 0) {
return;
}
do {
*dst++ = nextWord();
} while (--count != 0);
}
private:
uint32_t setWord = 0;
uint32_t resetWord = 0;
IrTxGateRun* runs = nullptr;
size_t runCount = 0;
uint16_t multiply_ = 2;
uint16_t powerN_ = 1;
size_t runIndex_ = 0;
uint16_t ticksLeftInRun_ = 0;
uint16_t slotInPeriod_ = 0;
};

13
IrTxGateTypes.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
/**
* Один RLE-сегмент огибающей несущей.
* В buildGateRuns: lenTicks в тактах логической шкалы 2×carrierFrec (как раньше).
* После IR_Encoder::scaleGateRunsToPhysical — в физических тиках carrierFrec×multiply.
*/
struct IrTxGateRun {
uint16_t lenTicks;
bool gate;
};

View File

@ -91,6 +91,7 @@ void setup() {
rebuildIrPayload(); rebuildIrPayload();
#if LONGDATA_USE_DMA #if LONGDATA_USE_DMA
// IR_Encoder::setCarrierMultiply(N); // до beginClockOnly; после смены — retuneCarrierClock()
IR_Encoder::beginClockOnly(&irTimer); IR_Encoder::beginClockOnly(&irTimer);
IrDmaTxStm32<kIrDmaStreams>::Config cfg; IrDmaTxStm32<kIrDmaStreams>::Config cfg;