mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-04-28 03:08:08 +00:00
add brightness controll
This commit is contained in:
@ -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();
|
||||||
|
|||||||
245
IR_Encoder.cpp
245
IR_Encoder.cpp
@ -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)
|
||||||
|
|||||||
59
IR_Encoder.h
59
IR_Encoder.h
@ -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;
|
||||||
|
|||||||
@ -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 на пинах не меняют.
|
||||||
|
|||||||
@ -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
87
IrTxBsrrWave.h
Normal 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
13
IrTxGateTypes.h
Normal 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;
|
||||||
|
};
|
||||||
@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user