mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-09-21 20:39:35 +00:00
archive: freeze IR-protocol WIP before stepwise integration
This commit is contained in:
144
IR_Encoder.h
144
IR_Encoder.h
@ -18,20 +18,88 @@ enum class IR_SendStatus : uint8_t {
|
||||
DmaStartFailed,
|
||||
EncoderPinUnavailable,
|
||||
BufferedStorageInvalid,
|
||||
InvalidArgument,
|
||||
TimingOverflow,
|
||||
PlanMismatch,
|
||||
DmaTransferError,
|
||||
DmaStalled,
|
||||
};
|
||||
|
||||
const char* irSendStatusToString(IR_SendStatus status);
|
||||
|
||||
enum class IR_TxState : uint8_t {
|
||||
Idle = 0,
|
||||
Preparing,
|
||||
Transmitting,
|
||||
Completed,
|
||||
Failed,
|
||||
};
|
||||
|
||||
enum class IR_TxClockBasis : uint8_t {
|
||||
Nominal = 0,
|
||||
ConfiguredTimer,
|
||||
};
|
||||
|
||||
/**
|
||||
* Deterministic PHY plan produced by the same FSM that builds the actual
|
||||
* carrier-gate stream. airtimeUs is rounded up, so it is safe as a deadline
|
||||
* component; it does not include backend preparation or release latency.
|
||||
*/
|
||||
struct IR_TxPlan {
|
||||
IR_SendStatus status = IR_SendStatus::InvalidArgument;
|
||||
uint8_t wireBytes = 0;
|
||||
uint16_t carrierMultiply = 0;
|
||||
IR_TxClockBasis clockBasis = IR_TxClockBasis::Nominal;
|
||||
uint32_t tickClockHz = 0; // rational tick rate numerator
|
||||
uint32_t tickDivider = 1; // rational tick rate denominator
|
||||
uint32_t physicalTicks = 0;
|
||||
uint32_t gateRunCount = 0;
|
||||
uint32_t airtimeUs = 0;
|
||||
|
||||
bool valid() const { return status == IR_SendStatus::Success; }
|
||||
uint32_t tickHzFloor() const { return tickDivider == 0U ? 0U : tickClockHz / tickDivider; }
|
||||
uint32_t airtimeMsCeil() const { return (airtimeUs + 999U) / 1000U; }
|
||||
};
|
||||
|
||||
/** Coherent main-context snapshot of one encoder's latest accepted operation. */
|
||||
struct IR_TxSnapshot {
|
||||
uint32_t operationId = 0;
|
||||
IR_TxState state = IR_TxState::Idle;
|
||||
IR_SendStatus status = IR_SendStatus::Success;
|
||||
uint16_t carrierMultiply = 0;
|
||||
IR_TxClockBasis clockBasis = IR_TxClockBasis::Nominal;
|
||||
uint32_t plannedPhysicalTicks = 0;
|
||||
uint32_t plannedAirtimeUs = 0;
|
||||
uint32_t acceptedAtUs = 0;
|
||||
uint32_t armedAtUs = 0;
|
||||
uint32_t terminalAtUs = 0;
|
||||
|
||||
bool active() const {
|
||||
return state == IR_TxState::Preparing || state == IR_TxState::Transmitting;
|
||||
}
|
||||
bool terminal() const {
|
||||
return state == IR_TxState::Completed || state == IR_TxState::Failed;
|
||||
}
|
||||
};
|
||||
|
||||
// Структура для возврата результата отправки
|
||||
struct IR_SendResult {
|
||||
bool success; // Флаг успешности отправки
|
||||
uint32_t sendTimeMs; // Время отправки пакета в миллисекундах
|
||||
bool success; // true: backend принял и запустил эту операцию
|
||||
uint32_t sendTimeMs; // ceil(plannedAirtimeUs / 1000), compatibility field
|
||||
IR_SendStatus status; // Детализированный статус старта передачи
|
||||
uint32_t operationId; // 0, если новая операция не создавалась
|
||||
uint32_t plannedAirtimeUs; // PHY airtime; без подготовки/release backend-а
|
||||
IR_TxClockBasis clockBasis;
|
||||
|
||||
IR_SendResult(bool success = false,
|
||||
uint32_t sendTimeMs = 0,
|
||||
IR_SendStatus status = IR_SendStatus::ExternalStartFailed)
|
||||
: success(success), sendTimeMs(sendTimeMs), status(status) {}
|
||||
IR_SendStatus status = IR_SendStatus::ExternalStartFailed,
|
||||
uint32_t operationId = 0,
|
||||
uint32_t plannedAirtimeUs = 0,
|
||||
IR_TxClockBasis clockBasis = IR_TxClockBasis::Nominal)
|
||||
: success(success), sendTimeMs(sendTimeMs), status(status),
|
||||
operationId(operationId), plannedAirtimeUs(plannedAirtimeUs),
|
||||
clockBasis(clockBasis) {}
|
||||
};
|
||||
|
||||
class IR_DecoderRaw;
|
||||
@ -53,6 +121,12 @@ public:
|
||||
|
||||
using ExternalTxBusyFn = bool (*)(void *ctx);
|
||||
using ExternalTxStartFn = IR_SendStatus (*)(void *ctx, IR_Encoder *enc, const uint8_t *packet, uint8_t len);
|
||||
using ExternalTxStartFnV2 = IR_SendStatus (*)(void *ctx,
|
||||
IR_Encoder *enc,
|
||||
const uint8_t *packet,
|
||||
uint8_t len,
|
||||
const IR_TxPlan& plan,
|
||||
uint32_t operationId);
|
||||
private:
|
||||
// uint16_t id; /// @brief Адрес передатчика
|
||||
public:
|
||||
@ -111,14 +185,32 @@ public:
|
||||
|
||||
/** Optional: register external TX backend (e.g. DMA driver). */
|
||||
static void setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx);
|
||||
/** Token-aware backend contract. Prefer this overload for every new backend. */
|
||||
static void setExternalTxBackendV2(ExternalTxStartFnV2 startFn, ExternalTxBusyFn busyFn, void *ctx);
|
||||
|
||||
/** Called by external TX backend on actual end of transmission. */
|
||||
/** Legacy completion hook. It cannot reject a stale completion; retained for source compatibility. */
|
||||
void externalFinishSend();
|
||||
/** Complete exactly operationId; stale/duplicate completions are ignored. */
|
||||
void externalFinishSend(uint32_t operationId, IR_SendStatus terminalStatus);
|
||||
|
||||
/** 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);
|
||||
/** Preflight the exact physical stream without allocating or touching hardware. */
|
||||
static IR_TxPlan planPhysicalTransmission(const uint8_t *packet, uint8_t len, uint16_t multiply);
|
||||
/** Build into caller storage and report both required runs and exact timing. */
|
||||
static IR_TxPlan buildPhysicalTransmission(const uint8_t *packet,
|
||||
uint8_t len,
|
||||
IR_TxGateRun *outRuns,
|
||||
size_t maxRuns,
|
||||
uint16_t multiply);
|
||||
/** Replace nominal tick rate with an exact rational backend clock. */
|
||||
static bool applyTickClock(IR_TxPlan& plan,
|
||||
uint32_t clockNumeratorHz,
|
||||
uint32_t clockDivider,
|
||||
IR_TxClockBasis basis = IR_TxClockBasis::ConfiguredTimer);
|
||||
IR_TxPlan planTransmission(const uint8_t *packet, uint8_t len) const;
|
||||
|
||||
void enable();
|
||||
void disable();
|
||||
@ -132,6 +224,7 @@ public:
|
||||
setBlindDecoders(decoders, static_cast<uint8_t>(N));
|
||||
}
|
||||
IR_SendStatus rawSend(uint8_t *ptr, uint8_t len);
|
||||
IR_SendResult rawSendTracked(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);
|
||||
@ -155,7 +248,11 @@ public:
|
||||
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;}
|
||||
inline bool isBusy() const { return isSending; }
|
||||
/** Main-context coherent snapshot. Do not spin on this from an ISR. */
|
||||
IR_TxSnapshot txSnapshot() const;
|
||||
bool isOperationTerminal(uint32_t operationId) const;
|
||||
bool isOperationComplete(uint32_t operationId) const;
|
||||
|
||||
|
||||
~IR_Encoder();
|
||||
@ -170,6 +267,7 @@ private:
|
||||
static void carrierPauseIfIdle();
|
||||
|
||||
static ExternalTxStartFn externalTxStartFn;
|
||||
static ExternalTxStartFnV2 externalTxStartFnV2;
|
||||
static ExternalTxBusyFn externalTxBusyFn;
|
||||
static void *externalTxCtx;
|
||||
IR_SendResult _sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len);
|
||||
@ -210,6 +308,15 @@ private:
|
||||
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);
|
||||
static TxFsmState initialTxFsm(uint8_t len);
|
||||
static IR_TxPlan buildPhysicalPlan(const uint8_t *packet,
|
||||
uint8_t len,
|
||||
IR_TxGateRun *outRuns,
|
||||
size_t maxRuns,
|
||||
uint16_t multiply,
|
||||
bool emitRuns);
|
||||
static bool calculateAirtimeUs(IR_TxPlan& plan);
|
||||
static void applyConfiguredTimerClock(IR_TxPlan& plan);
|
||||
void loadTxFsmFromMembers(TxFsmState &st) const;
|
||||
void storeTxFsmToMembers(const TxFsmState &st);
|
||||
bool shouldUseBufferedIsr() const;
|
||||
@ -218,9 +325,8 @@ private:
|
||||
uint16_t txPowerSnap_ = 1;
|
||||
uint16_t txMultiplySnap_ = 2;
|
||||
|
||||
/** Legacy: физических тиков на один логический шаг FSM = multiply/2. */
|
||||
uint16_t legacyPhysPerLogical_ = 1;
|
||||
uint16_t legacyPhysCounter_ = 0;
|
||||
/** Fractional 2×Fc -> multiply×Fc phase accumulator (also exact for odd multiply). */
|
||||
uint32_t legacyScaleAccumulator_ = 0;
|
||||
uint16_t legacySlotInPeriod_ = 0;
|
||||
|
||||
volatile uint16_t powerNumerator_ = 1;
|
||||
@ -235,9 +341,27 @@ private:
|
||||
uint8_t decodersCount = 0;
|
||||
|
||||
uint8_t sendLen = 0;
|
||||
uint8_t sendBuffer[dataByteSizeMax]{0}; /// @brief Буффер данных для отправки
|
||||
uint8_t sendBuffer[irproto::kMaxWireFrameBytes]{0}; /// @brief Буффер полного wire-кадра
|
||||
|
||||
volatile bool isSending = false;
|
||||
|
||||
// Single-writer-at-a-time record (main starts, ISR/backend terminates).
|
||||
// The byte seqlock makes a coherent main-context snapshot without heap/locks.
|
||||
volatile uint8_t txRecordVersion_ = 0;
|
||||
volatile IR_TxState txState_ = IR_TxState::Idle;
|
||||
volatile IR_SendStatus txTerminalStatus_ = IR_SendStatus::Success;
|
||||
volatile uint32_t txOperationId_ = 0;
|
||||
volatile uint32_t txPlannedPhysicalTicks_ = 0;
|
||||
volatile uint32_t txPlannedAirtimeUs_ = 0;
|
||||
volatile IR_TxClockBasis txClockBasis_ = IR_TxClockBasis::Nominal;
|
||||
volatile uint32_t txAcceptedAtUs_ = 0;
|
||||
volatile uint32_t txArmedAtUs_ = 0;
|
||||
volatile uint32_t txTerminalAtUs_ = 0;
|
||||
uint32_t txNextOperationId_ = 0;
|
||||
|
||||
uint32_t beginTxOperation(const IR_TxPlan& plan);
|
||||
void markTxArmed(uint32_t operationId);
|
||||
bool finishTxOperation(uint32_t operationId, IR_SendStatus terminalStatus);
|
||||
volatile bool state = LOW; /// @brief Текущий уровень генерации
|
||||
|
||||
volatile uint8_t dataByteCounter = 0;
|
||||
|
||||
Reference in New Issue
Block a user