From d788358ac8d1f065c2c2e2b3244948bb98bddeb0 Mon Sep 17 00:00:00 2001 From: DashyFox Date: Fri, 17 Apr 2026 09:45:32 +0300 Subject: [PATCH] add brightness controll --- IR-protocol.ino | 2 +- IR_Encoder.cpp | 265 +++++++++++++++++++++++++--- IR_Encoder.h | 59 ++++++- IR_config.h | 5 + IrDmaTxStm32.h | 64 +++---- IrTxBsrrWave.h | 87 +++++++++ IrTxGateTypes.h | 13 ++ test_examples/longData/longData.ino | 1 + 8 files changed, 426 insertions(+), 70 deletions(-) create mode 100644 IrTxBsrrWave.h create mode 100644 IrTxGateTypes.h diff --git a/IR-protocol.ino b/IR-protocol.ino index 631cf55..1d0e9b0 100644 --- a/IR-protocol.ino +++ b/IR-protocol.ino @@ -207,7 +207,7 @@ HardwareTimer IR_Timer(TIM3); 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); NVIC_SetPriority(IRQn_Type::TIM3_IRQn, 0); IR_Timer.resume(); diff --git a/IR_Encoder.cpp b/IR_Encoder.cpp index db08bad..ce9f163 100644 --- a/IR_Encoder.cpp +++ b/IR_Encoder.cpp @@ -38,12 +38,120 @@ IR_Encoder::IR_Encoder(uint8_t pin, uint16_t addr, IR_DecoderRaw *decPair, bool pinMode(pin, OUTPUT); } + powerNumerator_ = 1; }; HardwareTimer* IR_Encoder::IR_Timer = nullptr; IR_Encoder::ExternalTxStartFn IR_Encoder::externalTxStartFn = nullptr; IR_Encoder::ExternalTxBusyFn IR_Encoder::externalTxBusyFn = 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(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(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(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) { @@ -194,7 +302,7 @@ void IR_Encoder::begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, ui IR_Timer = timer; if(IR_Timer == nullptr) return; 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)); NVIC_SetPriority(IRQn, priority); IR_Timer->pause(); @@ -206,7 +314,7 @@ void IR_Encoder::beginClockOnly(HardwareTimer *timer) if (IR_Timer == nullptr) return; 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(); } @@ -607,23 +715,86 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) } return; } + + if (port == nullptr || mask == 0) + { + return; + } + + if (ptr != sendBuffer) + { + memcpy(sendBuffer, ptr, len); + } sendLen = len; - toggleCounter = preambToggle; // Первая генерация для первого signal - dataBitCounter = bitPerByte - 1; - dataByteCounter = 0; + if (txIsrLegacyMode_) + { + toggleCounter = preambToggle; + dataBitCounter = bitPerByte - 1; + dataByteCounter = 0; + preambFrontCounter = preambPulse * 2 - 1; + dataSequenceCounter = bitPerByte * 2; + syncSequenceCounter = syncBits * 2; + signal = preamb; + state = HIGH; + currentBitSequence = bitHigh; + txMultiplySnap_ = carrierMultiply(); + { + const uint16_t cap = maxPowerNumerator(); + txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_; + } + legacyPhysPerLogical_ = static_cast(txMultiplySnap_ / 2U); + if (legacyPhysPerLogical_ == 0) + { + legacyPhysPerLogical_ = 1; + } + legacyPhysCounter_ = 0; + legacySlotInPeriod_ = 0; + isSending = true; + refreshBlindDecoderMuteState(); + IR_Encoder::carrierResume(); + return; + } - preambFrontCounter = preambPulse * 2 - 1; // -1 за счёт генерации уже на этапе сразу после инициализации - dataSequenceCounter = bitPerByte * 2; - syncSequenceCounter = syncBits * 2; + 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; - signal = preamb; isSending = true; - state = HIGH; - currentBitSequence = bitHigh; refreshBlindDecoderMuteState(); + if (port != nullptr) + { + port->BSRR = resetW; + } IR_Encoder::carrierResume(); - // interrupts(); } void IR_Encoder::isr() @@ -641,21 +812,71 @@ void IR_Encoder::_isr() if (!isSending) return; - ir_out_virtual = !ir_out_virtual && state; + if (port == nullptr) + return; - port->ODR &= ~(mask); - port->ODR |= mask & (ir_out_virtual ? (uint16_t)0xFFFF : (uint16_t)0x0000); - - TxFsmState st{}; - loadTxFsmFromMembers(st); - const bool active = txAdvanceAfterOutput(st, sendBuffer); - storeTxFsmToMembers(st); - - if (!active) + if (txIsrLegacyMode_) { + 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{}; + loadTxFsmFromMembers(st); + const bool active = txAdvanceAfterOutput(st, sendBuffer); + storeTxFsmToMembers(st); + + if (!active) + { + port->BSRR = resetW; + isSending = false; + refreshBlindDecoderMuteState(); + 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_); } } diff --git a/IR_Encoder.h b/IR_Encoder.h index 3c6e5e1..ff5ced4 100644 --- a/IR_Encoder.h +++ b/IR_Encoder.h @@ -1,5 +1,6 @@ #pragma once #include "IR_config.h" +#include "IrTxBsrrWave.h" // TODO: Отложенная передача после завершения приема @@ -22,10 +23,7 @@ class IR_Encoder : public IR_FOX public: static HardwareTimer* IR_Timer; - struct IR_TxGateRun { - uint16_t lenTicks; // number of timer ticks at carrierFrec*2 - bool gate; // true: carrier enabled (output toggles), false: silent (output forced low) - }; + using IR_TxGateRun = IrTxGateRun; using ExternalTxBusyFn = bool (*)(void *ctx); 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); static void isr(); 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 HardwareTimer* get_IR_Timer(); /** Call from main loop/tick: if ISR requested carrier stop, pause timer here (not in ISR). */ 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). */ static void setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx); @@ -99,6 +127,8 @@ public: void _isr(); private: static volatile bool carrierStopPending; + static bool txIsrLegacyMode_; + static uint16_t s_carrierMultiply; static void carrierResume(); static void carrierPauseIfIdle(); @@ -146,6 +176,25 @@ private: void loadTxFsmFromMembers(TxFsmState &st) const; 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 *singleBlindDecoder = nullptr; IR_DecoderRaw **blindDecoders = nullptr; diff --git a/IR_config.h b/IR_config.h index aeb39ae..855d8d5 100644 --- a/IR_config.h +++ b/IR_config.h @@ -6,6 +6,11 @@ /** Число потоков DMA-TX задаётся шаблоном: IrDmaTxStm32<2>, см. IrDmaTxStm32.h и irproto::kDefaultDmaTxMaxStreams. */ namespace irproto { 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 на пинах не меняют. diff --git a/IrDmaTxStm32.h b/IrDmaTxStm32.h index f78edd7..f14435d 100644 --- a/IrDmaTxStm32.h +++ b/IrDmaTxStm32.h @@ -1,6 +1,7 @@ #pragma once #include "IR_Encoder.h" +#include "IrTxBsrrWave.h" #if defined(ARDUINO_ARCH_STM32) && defined(STM32G4xx) @@ -16,7 +17,7 @@ #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<> ≡ irproto::kDefaultDmaTxMaxStreams (см. IR_config.h). @@ -37,7 +38,7 @@ public: uint32_t* dmaWords = nullptr; uint16_t dmaWordCount = 0; - IR_Encoder::IR_TxGateRun* gateRuns = nullptr; + IrTxGateRun* gateRuns = nullptr; size_t maxGateRuns = 0; }; @@ -134,13 +135,11 @@ private: uint16_t bufLen = 0; uint16_t halfLen = 0; - IR_Encoder::IR_TxGateRun* runs = nullptr; + IrTxGateRun* runs = nullptr; size_t maxRuns = 0; size_t runCount = 0; - size_t runIndex = 0; - uint16_t ticksLeftInRun = 0; - bool carrierPhase = false; + IrTxBsrrWave wave{}; uint32_t totalTicks = 0; volatile uint32_t ticksOutput = 0; @@ -148,45 +147,14 @@ private: bool active = false; void resetWave() { - runIndex = 0; - carrierPhase = false; + wave.configure(setWord, resetWord, nullptr, 0, 2, 1); 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); + wave.fill(dst, count); } void onHalf() { @@ -325,13 +293,25 @@ private: s.runCount = IR_Encoder::buildGateRuns(packet, len, s.runs, s.maxRuns); 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; 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; + const uint16_t mult = IR_Encoder::carrierMultiply(); + 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); diff --git a/IrTxBsrrWave.h b/IrTxBsrrWave.h new file mode 100644 index 0000000..31115ff --- /dev/null +++ b/IrTxBsrrWave.h @@ -0,0 +1,87 @@ +#pragma once + +#include "IrTxGateTypes.h" +#include +#include + +#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(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; +}; diff --git a/IrTxGateTypes.h b/IrTxGateTypes.h new file mode 100644 index 0000000..78c3682 --- /dev/null +++ b/IrTxGateTypes.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +/** + * Один RLE-сегмент огибающей несущей. + * В buildGateRuns: lenTicks в тактах логической шкалы 2×carrierFrec (как раньше). + * После IR_Encoder::scaleGateRunsToPhysical — в физических тиках carrierFrec×multiply. + */ +struct IrTxGateRun { + uint16_t lenTicks; + bool gate; +}; diff --git a/test_examples/longData/longData.ino b/test_examples/longData/longData.ino index 370e3c3..d958800 100644 --- a/test_examples/longData/longData.ino +++ b/test_examples/longData/longData.ino @@ -91,6 +91,7 @@ void setup() { rebuildIrPayload(); #if LONGDATA_USE_DMA + // IR_Encoder::setCarrierMultiply(N); // до beginClockOnly; после смены — retuneCarrierClock() IR_Encoder::beginClockOnly(&irTimer); IrDmaTxStm32::Config cfg;