Files
IR-protocol/IR_Encoder.h

263 lines
12 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include "IR_config.h"
#include "IrTxGateTypes.h"
// TODO: Отложенная передача после завершения приема
enum class IR_SendStatus : uint8_t {
Success = 0,
PayloadTooLarge,
EncoderBusy,
BufferTooLarge,
ExternalBackendBusy,
ExternalStartFailed,
ExternalNoStream,
ExternalInvalidConfig,
BuildGateRunsFailed,
ScaleGateRunsFailed,
DmaStartFailed,
EncoderPinUnavailable,
BufferedStorageInvalid,
};
const char* irSendStatusToString(IR_SendStatus status);
// Структура для возврата результата отправки
struct IR_SendResult {
bool success; // Флаг успешности отправки
uint32_t sendTimeMs; // Время отправки пакета в миллисекундах
IR_SendStatus status; // Детализированный статус старта передачи
IR_SendResult(bool success = false,
uint32_t sendTimeMs = 0,
IR_SendStatus status = IR_SendStatus::ExternalStartFailed)
: success(success), sendTimeMs(sendTimeMs), status(status) {}
};
class IR_DecoderRaw;
class IrTxIsrBufferedStorageBase;
class IR_Encoder : public IR_FOX
{
friend IR_DecoderRaw;
static IR_Encoder *head;
static IR_Encoder *last;
IR_Encoder *next;
public:
static HardwareTimer* IR_Timer;
using IR_TxGateRun = IrTxGateRun;
enum class TxIsrMode : uint8_t {
Legacy = 0,
Buffered = 1
};
using ExternalTxBusyFn = bool (*)(void *ctx);
using ExternalTxStartFn = IR_SendStatus (*)(void *ctx, IR_Encoder *enc, const uint8_t *packet, uint8_t len);
private:
// uint16_t id; /// @brief Адрес передатчика
public:
/// @brief Класс передатчика
/// @param addr Адрес передатчика
/// @param pin Вывод передатчика
/// @param decPair Если задан, конструктор регистрирует этот один приёмник как blind-decoder
/// (аналог setBlindDecoders() для одного RX).
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);
/**
* Глобальный знаменатель: частота таймера 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);
/** Legacy helper: 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 + кольцо (direct physical gate-runs builder);
* true — FSM «налету» + скважность несущей как у буферного пути (подшаги multiply/2 на шаг FSM).
* По умолчанию включён legacy=true для обратной совместимости. Вызов меняет default и обновляет
* все зарегистрированные encoder-объекты. Buffered ISR реально используется только если у encoder
* привязан storage через attachBufferedIsrStorage()/enableBufferedIsr().
* Выставить до begin/rawSend. Игнорируется при externalTxStartFn.
*/
static void setTxIsrLegacyMode(bool legacy);
static bool txIsrLegacyMode();
void attachBufferedIsrStorage(IrTxIsrBufferedStorageBase& storage);
void detachBufferedIsrStorage();
bool hasBufferedIsrStorage() const;
void enableBufferedIsr(IrTxIsrBufferedStorageBase& storage);
void disableBufferedIsr();
TxIsrMode txIsrMode() const;
/** Optional: register external TX backend (e.g. DMA driver). */
static void setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx);
/** Called by external TX backend on actual end of transmission. */
void externalFinishSend();
/** Build RLE runs of carrier gate for a packet in logical 2×Fc ticks (no HW access). */
static size_t buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns);
/** Build RLE runs directly in physical carrierFrec×multiply ticks (DMA/buffered ISR path). */
static size_t buildPhysicalGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns, uint16_t multiply);
void enable();
void disable();
void setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count);
template <size_t N>
void setBlindDecoders(IR_DecoderRaw *(&decoders)[N])
{
static_assert(N <= IR_PAIR_MUTE_MAX_ENCODERS,
"IR_Encoder::setBlindDecoders: array size exceeds IR_PAIR_MUTE_MAX_ENCODERS");
setBlindDecoders(decoders, static_cast<uint8_t>(N));
}
IR_SendStatus rawSend(uint8_t *ptr, uint8_t len);
IR_SendResult sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept = false);
IR_SendResult sendData(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);
IR_SendResult sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);
IR_SendResult sendAccept(uint16_t addrTo, uint8_t customByte = 0);
IR_SendResult sendRequest(uint16_t addrTo);
IR_SendResult sendBack(uint8_t data);
IR_SendResult sendBack(uint8_t *data = nullptr, uint8_t len = 0);
IR_SendResult sendBackTo(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0);
// Функция для тестирования времени отправки без фактической отправки
uint32_t testSendTime(uint16_t addrTo, uint8_t dataByte, bool needAccept = false) const;
uint32_t testSendTime(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false) const;
uint32_t testSendTimeFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false) const;
uint32_t testSendAccept(uint16_t addrTo, uint8_t customByte = 0) const;
uint32_t testSendRequest(uint16_t addrTo) const;
uint32_t testSendBack(uint8_t data) const;
uint32_t testSendBack(uint8_t *data = nullptr, uint8_t len = 0) const;
uint32_t testSendBackTo(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0) const;
inline bool isBusy() const { return isSending;}
~IR_Encoder();
volatile bool ir_out_virtual;
void _isr();
private:
static volatile bool carrierStopPending;
static bool txIsrLegacyMode_;
static uint16_t s_carrierMultiply;
static void carrierResume();
static void carrierPauseIfIdle();
static ExternalTxStartFn externalTxStartFn;
static ExternalTxBusyFn externalTxBusyFn;
static void *externalTxCtx;
IR_SendResult _sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len);
void refreshBlindDecoderMuteState();
void registerWithBlindDecoders();
void sendByte(uint8_t byte, bool *prev, bool LOW_FIRST);
void addSync(bool *prev, bool *next);
uint32_t calculateSendTime(uint8_t packSize) const;
uint32_t testSendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len) const;
void send_HIGH(bool = 1);
void send_LOW();
void send_EMPTY(uint8_t count);
enum SignalPart : uint8_t
{
noSignal = 0,
preamb = 1,
data = 2,
sync = 3
};
struct TxFsmState
{
uint8_t sendLen = 0;
uint8_t toggleCounter = 0;
uint8_t dataBitCounter = 0;
uint8_t dataByteCounter = 0;
uint8_t preambFrontCounter = 0;
uint8_t dataSequenceCounter = 0;
uint8_t syncSequenceCounter = 0;
bool syncLastBit = false;
bool state = LOW;
uint8_t *currentBitSequence = nullptr;
SignalPart signal = noSignal;
};
static bool txAdvanceBoundary(TxFsmState &st, const uint8_t *sendBufferLocal);
static bool txAdvanceAfterOutput(TxFsmState &st, const uint8_t *sendBufferLocal);
static bool txEmitTick(TxFsmState &st, const uint8_t *sendBufferLocal, bool &gateOut);
void loadTxFsmFromMembers(TxFsmState &st) const;
void storeTxFsmToMembers(const TxFsmState &st);
bool shouldUseBufferedIsr() const;
/** Снимок на старт 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;
IrTxIsrBufferedStorageBase* txBufferedCtx_ = nullptr;
IrTxIsrBufferedStorageBase* txActiveBufferedCtx_ = nullptr;
TxIsrMode txIsrMode_ = TxIsrMode::Legacy;
bool txUseBufferedIsr_ = false;
IR_DecoderRaw *decPair = nullptr;
IR_DecoderRaw *singleBlindDecoder = nullptr;
IR_DecoderRaw **blindDecoders = nullptr;
uint8_t decodersCount = 0;
uint8_t sendLen = 0;
uint8_t sendBuffer[dataByteSizeMax]{0}; /// @brief Буффер данных для отправки
volatile bool isSending = false;
volatile bool state = LOW; /// @brief Текущий уровень генерации
volatile uint8_t dataByteCounter = 0;
volatile uint8_t toggleCounter = 0; /// @brief Счётчик переключений
volatile uint8_t dataBitCounter = 0;
volatile uint8_t preambFrontCounter = 0;
volatile uint8_t dataSequenceCounter = 0;
volatile uint8_t syncSequenceCounter = 0;
volatile bool syncLastBit = false;
struct BitSequence
{
uint8_t low;
uint8_t high;
};
static uint8_t bitHigh[2];
static uint8_t bitLow[2];
uint8_t *currentBitSequence = bitLow;
volatile SignalPart signal = noSignal;
};