mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-09-21 20:39:35 +00:00
Compare commits
2 Commits
STM32
...
archive/ex
| Author | SHA1 | Date | |
|---|---|---|---|
| c7efd1cb65 | |||
| 36f234739a |
@ -25,7 +25,7 @@ class Print;
|
||||
#define riseTimeMin (riseTime - riseTolerance)
|
||||
#define aroundRise(t) (riseTimeMin < t && t < riseTimeMax)
|
||||
#define IR_timeout (riseTimeMax * (8 + syncBits + 1)) // us // таймаут в 8 data + 3 sync + 1
|
||||
constexpr uint16_t IR_ResponseDelay = ((uint16_t)(((bitTime+riseTolerance) * (8 + syncBits + 1))*2.7735))/1000;
|
||||
constexpr uint16_t IR_ResponseDelay = irproto::kMandatoryInterPacketQuietMs;
|
||||
|
||||
class IR_Encoder;
|
||||
class IR_DecoderRaw : virtual public IR_FOX
|
||||
|
||||
@ -1183,26 +1183,10 @@ uint8_t IR_Encoder::bitLow[2] = {
|
||||
|
||||
uint32_t IR_Encoder::calculateSendTime(uint8_t packSize) const
|
||||
{
|
||||
// Расчет времени отправки пакета в миллисекундах
|
||||
|
||||
// Время преамбулы: preambPulse * 2 фронта * bitTakts тактов
|
||||
uint32_t preambTime = preambPulse * 2 * bitTakts;
|
||||
|
||||
// Время данных: количество бит * bitTakts тактов
|
||||
uint32_t dataTime = packSize * 8 * bitTakts;
|
||||
|
||||
// Время синхронизации: syncBits * 2 фронта * bitTakts тактов
|
||||
uint32_t syncTime = syncBits * 2 * bitTakts;
|
||||
|
||||
// Общее время в тактах
|
||||
uint32_t totalTakts = preambTime + dataTime + syncTime;
|
||||
|
||||
// Конвертируем в миллисекунды
|
||||
// carrierPeriod - период несущей в микросекундах
|
||||
// totalTakts * carrierPeriod / 1000 = время в миллисекундах
|
||||
uint32_t sendTimeMs = (totalTakts * carrierPeriod) / 1000;
|
||||
|
||||
return sendTimeMs;
|
||||
// The TX FSM emits syncBits after every wire byte (including the last)
|
||||
// and its preamble runs are preambToggle+1 logical ticks long. The old
|
||||
// approximation omitted the per-byte sync and shortened the preamble.
|
||||
return irproto::wireAirtimeMsCeil(packSize);
|
||||
}
|
||||
|
||||
// Функции для тестирования времени отправки без фактической отправки
|
||||
|
||||
158
IR_config.h
158
IR_config.h
@ -264,6 +264,164 @@ typedef uint16_t crc_t;
|
||||
#define bitTime (bitTakts * carrierPeriod) // Общая длительность бита
|
||||
#define tolerance 300U
|
||||
|
||||
namespace irproto
|
||||
{
|
||||
/** Maximum complete frame length representable by the five header bits. */
|
||||
constexpr uint8_t kMaxWireFrameBytes = static_cast<uint8_t>(IR_MASK_MSG_INFO);
|
||||
|
||||
constexpr uint8_t kDataFrameOverheadBytes = msgBytes + addrBytes + addrBytes + crcBytes;
|
||||
constexpr uint8_t kBackFrameOverheadBytes = msgBytes + addrBytes + crcBytes;
|
||||
constexpr uint8_t kBackToFrameOverheadBytes = msgBytes + addrBytes + addrBytes + crcBytes;
|
||||
constexpr uint8_t kAcceptFrameBytes = msgBytes + addrBytes + 1U + crcBytes;
|
||||
constexpr uint8_t kRequestFrameBytes = msgBytes + addrBytes + addrBytes + crcBytes;
|
||||
|
||||
constexpr uint8_t kMaxDataPayloadBytes = kMaxWireFrameBytes - kDataFrameOverheadBytes;
|
||||
constexpr uint8_t kMaxBackPayloadBytes = kMaxWireFrameBytes - kBackFrameOverheadBytes;
|
||||
constexpr uint8_t kMaxBackToPayloadBytes = kMaxWireFrameBytes - kBackToFrameOverheadBytes;
|
||||
|
||||
/** Complete DATA frame size, or zero when payloadBytes cannot fit on wire. */
|
||||
constexpr uint8_t dataWireBytes(uint8_t payloadBytes)
|
||||
{
|
||||
return payloadBytes <= kMaxDataPayloadBytes
|
||||
? static_cast<uint8_t>(kDataFrameOverheadBytes + payloadBytes)
|
||||
: 0U;
|
||||
}
|
||||
|
||||
/** Complete non-addressed BACK frame size, or zero when it cannot fit. */
|
||||
constexpr uint8_t backWireBytes(uint8_t payloadBytes)
|
||||
{
|
||||
return payloadBytes <= kMaxBackPayloadBytes
|
||||
? static_cast<uint8_t>(kBackFrameOverheadBytes + payloadBytes)
|
||||
: 0U;
|
||||
}
|
||||
|
||||
/** Complete addressed BACK_TO frame size, or zero when it cannot fit. */
|
||||
constexpr uint8_t backToWireBytes(uint8_t payloadBytes)
|
||||
{
|
||||
return payloadBytes <= kMaxBackToPayloadBytes
|
||||
? static_cast<uint8_t>(kBackToFrameOverheadBytes + payloadBytes)
|
||||
: 0U;
|
||||
}
|
||||
|
||||
/** Minimum complete frame size for a known message type; zero means reserved/unknown. */
|
||||
constexpr uint8_t minimumWireBytes(uint8_t msgType)
|
||||
{
|
||||
return (msgType == IR_MSG_DATA_ACCEPT || msgType == IR_MSG_DATA_NOACCEPT)
|
||||
? kDataFrameOverheadBytes
|
||||
: msgType == IR_MSG_BACK
|
||||
? kBackFrameOverheadBytes
|
||||
: (msgType == IR_MSG_BACK_TO || msgType == IR_MSG_REQUEST)
|
||||
? kRequestFrameBytes
|
||||
: msgType == IR_MSG_ACCEPT
|
||||
? kAcceptFrameBytes
|
||||
: 0U;
|
||||
}
|
||||
|
||||
constexpr bool isTypedWireSizeValid(uint8_t msgType, uint8_t wireBytes)
|
||||
{
|
||||
return minimumWireBytes(msgType) != 0U &&
|
||||
wireBytes >= minimumWireBytes(msgType) &&
|
||||
wireBytes <= kMaxWireFrameBytes;
|
||||
}
|
||||
|
||||
/*
|
||||
* TX FSM timing contract.
|
||||
*
|
||||
* The FSM runs on 2*carrierFrec. The preamble contains preambPulse*2
|
||||
* constant runs; each run is preambToggle+1 ticks. Every data bit and every
|
||||
* per-byte sync bit occupies bitTakts*2 ticks, independently of its value.
|
||||
*/
|
||||
constexpr uint32_t kTxLogicalClockHz = static_cast<uint32_t>(carrierFrec) * 2U;
|
||||
constexpr uint32_t kPreambleLogicalTicks =
|
||||
static_cast<uint32_t>(preambPulse * 2U) * static_cast<uint32_t>(preambToggle + 1U);
|
||||
constexpr uint32_t kEncodedBitLogicalTicks = static_cast<uint32_t>(bitTakts * 2U);
|
||||
constexpr uint32_t kWireByteLogicalTicks =
|
||||
static_cast<uint32_t>(bitPerByte + syncBits) * kEncodedBitLogicalTicks;
|
||||
|
||||
constexpr uint32_t wireLogicalTicks(uint8_t wireBytes)
|
||||
{
|
||||
return wireBytes != 0U && wireBytes <= kMaxWireFrameBytes
|
||||
? kPreambleLogicalTicks + static_cast<uint32_t>(wireBytes) * kWireByteLogicalTicks
|
||||
: 0U;
|
||||
}
|
||||
|
||||
constexpr uint32_t logicalTicksToUsCeil(uint32_t logicalTicks)
|
||||
{
|
||||
return logicalTicks == 0U
|
||||
? 0U
|
||||
: static_cast<uint32_t>(
|
||||
(static_cast<uint64_t>(logicalTicks) * 1000000ULL +
|
||||
static_cast<uint64_t>(kTxLogicalClockHz) - 1ULL) /
|
||||
static_cast<uint64_t>(kTxLogicalClockHz));
|
||||
}
|
||||
|
||||
constexpr uint32_t preambleAirtimeUsCeil()
|
||||
{
|
||||
return logicalTicksToUsCeil(kPreambleLogicalTicks);
|
||||
}
|
||||
|
||||
/** Complete nominal on-air duration, rounded up to a whole microsecond. */
|
||||
constexpr uint32_t wireAirtimeUsCeil(uint8_t wireBytes)
|
||||
{
|
||||
return logicalTicksToUsCeil(wireLogicalTicks(wireBytes));
|
||||
}
|
||||
|
||||
constexpr uint32_t wireAirtimeMsCeil(uint8_t wireBytes)
|
||||
{
|
||||
return wireAirtimeUsCeil(wireBytes) == 0U
|
||||
? 0U
|
||||
: (wireAirtimeUsCeil(wireBytes) + 999U) / 1000U;
|
||||
}
|
||||
|
||||
/* Preserve the deployed library turn-around policy, but expose it by name. */
|
||||
constexpr uint16_t kMandatoryInterPacketQuietMs =
|
||||
static_cast<uint16_t>(
|
||||
static_cast<uint16_t>(
|
||||
(static_cast<uint32_t>(bitTime + tolerance) *
|
||||
static_cast<uint32_t>(bitPerByte + syncBits + 1U)) *
|
||||
2.7735) /
|
||||
1000U);
|
||||
constexpr uint32_t kMandatoryInterPacketQuietUs =
|
||||
static_cast<uint32_t>(kMandatoryInterPacketQuietMs) * 1000U;
|
||||
|
||||
constexpr uint16_t kDefaultTimingGuardPermille = 1150U;
|
||||
|
||||
constexpr uint32_t addTimingGuardUs(uint32_t durationUs,
|
||||
uint16_t marginPermille = kDefaultTimingGuardPermille)
|
||||
{
|
||||
return marginPermille == 0U
|
||||
? 0U
|
||||
: static_cast<uint32_t>(
|
||||
(static_cast<uint64_t>(durationUs) * marginPermille + 999ULL) / 1000ULL);
|
||||
}
|
||||
|
||||
/** Deadline for seeing enough preamble to know that a response has started. */
|
||||
constexpr uint32_t responseStartGuardUs(
|
||||
uint16_t marginPermille = kDefaultTimingGuardPermille)
|
||||
{
|
||||
return addTimingGuardUs(kMandatoryInterPacketQuietUs + preambleAirtimeUsCeil(),
|
||||
marginPermille);
|
||||
}
|
||||
|
||||
/** Conservative deadline for receiving a complete response of maxWireBytes. */
|
||||
constexpr uint32_t responseFrameGuardUs(
|
||||
uint8_t maxWireBytes,
|
||||
uint16_t marginPermille = kDefaultTimingGuardPermille)
|
||||
{
|
||||
return wireAirtimeUsCeil(maxWireBytes) == 0U
|
||||
? 0U
|
||||
: addTimingGuardUs(kMandatoryInterPacketQuietUs +
|
||||
wireAirtimeUsCeil(maxWireBytes),
|
||||
marginPermille);
|
||||
}
|
||||
|
||||
static_assert(kMaxDataPayloadBytes == 24U, "DATA payload contract changed");
|
||||
static_assert(kMaxBackPayloadBytes == 26U, "BACK payload contract changed");
|
||||
static_assert(kPreambleLogicalTicks == 588U, "preamble timing contract changed");
|
||||
static_assert(kWireByteLogicalTicks == 814U, "wire-byte timing contract changed");
|
||||
static_assert(kMandatoryInterPacketQuietMs == 42U, "inter-packet quiet policy changed");
|
||||
}
|
||||
|
||||
constexpr uint16_t test_all_Time = bitTime;
|
||||
constexpr uint16_t test_all_Takts = bitTakts * 2;
|
||||
constexpr uint16_t test_hi = ((bitPauseTakts) * 2 - 0) + ((bitActiveTakts) * 2 - 0);
|
||||
|
||||
100
PacketTypes.cpp
100
PacketTypes.cpp
@ -2,12 +2,30 @@
|
||||
|
||||
namespace PacketTypes
|
||||
{
|
||||
bool BasePack::checkPacketLayout() const
|
||||
{
|
||||
if (packInfo == nullptr || packInfo->buffer == nullptr ||
|
||||
packInfo->packSize < msgBytes + crcBytes ||
|
||||
packInfo->packSize > irproto::kMaxWireFrameBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return (packInfo->buffer[msgOffset] & IR_MASK_MSG_INFO) == packInfo->packSize;
|
||||
}
|
||||
|
||||
bool BasePack::checkAddress() { return true; };
|
||||
void BasePack::set(IR_FOX::PackInfo *packInfo, uint16_t id)
|
||||
{
|
||||
isAvailable = false;
|
||||
isRawAvailable = false;
|
||||
this->packInfo = packInfo;
|
||||
this->id = id;
|
||||
|
||||
if (!checkPacketLayout())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkAddress())
|
||||
{
|
||||
isAvailable = true;
|
||||
@ -27,24 +45,58 @@ namespace PacketTypes
|
||||
|
||||
uint16_t BasePack::_getAddrFrom(BasePack *obj)
|
||||
{
|
||||
if (obj == nullptr || !obj->checkPacketLayout() ||
|
||||
obj->packInfo == nullptr || obj->packInfo->buffer == nullptr ||
|
||||
obj->packInfo->packSize < crcBytes ||
|
||||
static_cast<uint16_t>(obj->addressFromOffset) + 1U >=
|
||||
static_cast<uint16_t>(obj->packInfo->packSize - crcBytes))
|
||||
{
|
||||
return 0U;
|
||||
}
|
||||
return (obj->packInfo->buffer[obj->addressFromOffset] << 8) | obj->packInfo->buffer[obj->addressFromOffset + 1];
|
||||
};
|
||||
uint16_t BasePack::_getAddrTo(BasePack *obj)
|
||||
{
|
||||
if (obj == nullptr || !obj->checkPacketLayout() ||
|
||||
obj->packInfo == nullptr || obj->packInfo->buffer == nullptr ||
|
||||
obj->packInfo->packSize < crcBytes ||
|
||||
static_cast<uint16_t>(obj->addressToOffset) + 1U >=
|
||||
static_cast<uint16_t>(obj->packInfo->packSize - crcBytes))
|
||||
{
|
||||
return 0U;
|
||||
}
|
||||
return (obj->packInfo->buffer[obj->addressToOffset] << 8) | obj->packInfo->buffer[obj->addressToOffset + 1];
|
||||
};
|
||||
|
||||
uint8_t BasePack::_getDataSize(BasePack *obj)
|
||||
{
|
||||
return obj->packInfo->packSize - crcBytes - obj->DataOffset;
|
||||
if (obj == nullptr || !obj->checkPacketLayout() ||
|
||||
obj->packInfo == nullptr || obj->packInfo->buffer == nullptr)
|
||||
{
|
||||
return 0U;
|
||||
}
|
||||
const uint16_t overhead = static_cast<uint16_t>(obj->DataOffset) + crcBytes;
|
||||
return static_cast<uint16_t>(obj->packInfo->packSize) > overhead
|
||||
? static_cast<uint8_t>(static_cast<uint16_t>(obj->packInfo->packSize) - overhead)
|
||||
: 0U;
|
||||
};
|
||||
uint8_t *BasePack::_getDataPrt(BasePack *obj)
|
||||
{
|
||||
if (obj == nullptr || !obj->checkPacketLayout() ||
|
||||
obj->packInfo == nullptr || obj->packInfo->buffer == nullptr ||
|
||||
obj->packInfo->packSize < crcBytes ||
|
||||
static_cast<uint16_t>(obj->DataOffset) >
|
||||
static_cast<uint16_t>(obj->packInfo->packSize - crcBytes))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return obj->packInfo->buffer + obj->DataOffset;
|
||||
};
|
||||
uint8_t BasePack::_getDataRawSize(BasePack *obj)
|
||||
{
|
||||
return obj->packInfo->packSize;
|
||||
return obj != nullptr && obj->checkPacketLayout() && obj->packInfo != nullptr
|
||||
? obj->packInfo->packSize
|
||||
: 0U;
|
||||
};
|
||||
|
||||
bool BasePack::available()
|
||||
@ -73,6 +125,17 @@ namespace PacketTypes
|
||||
}
|
||||
};
|
||||
|
||||
bool Data::checkPacketLayout() const
|
||||
{
|
||||
if (!BasePack::checkPacketLayout())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const uint8_t msgType = (packInfo->buffer[msgOffset] >> 5) & IR_MASK_MSG_TYPE;
|
||||
return (msgType == IR_MSG_DATA_ACCEPT || msgType == IR_MSG_DATA_NOACCEPT) &&
|
||||
irproto::isTypedWireSizeValid(msgType, packInfo->packSize);
|
||||
}
|
||||
|
||||
bool Data::checkAddress()
|
||||
{
|
||||
bool ret;
|
||||
@ -80,6 +143,17 @@ namespace PacketTypes
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool DataBack::checkPacketLayout() const
|
||||
{
|
||||
if (!BasePack::checkPacketLayout())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const uint8_t msgType = (packInfo->buffer[msgOffset] >> 5) & IR_MASK_MSG_TYPE;
|
||||
return (msgType == IR_MSG_BACK || msgType == IR_MSG_BACK_TO) &&
|
||||
irproto::isTypedWireSizeValid(msgType, packInfo->packSize);
|
||||
}
|
||||
|
||||
bool DataBack::checkAddress()
|
||||
{
|
||||
bool ret;
|
||||
@ -96,8 +170,30 @@ namespace PacketTypes
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Accept::checkPacketLayout() const
|
||||
{
|
||||
if (!BasePack::checkPacketLayout())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const uint8_t msgType = (packInfo->buffer[msgOffset] >> 5) & IR_MASK_MSG_TYPE;
|
||||
return msgType == IR_MSG_ACCEPT &&
|
||||
irproto::isTypedWireSizeValid(msgType, packInfo->packSize);
|
||||
}
|
||||
|
||||
bool Accept::checkAddress() { return true; }
|
||||
|
||||
bool Request::checkPacketLayout() const
|
||||
{
|
||||
if (!BasePack::checkPacketLayout())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const uint8_t msgType = (packInfo->buffer[msgOffset] >> 5) & IR_MASK_MSG_TYPE;
|
||||
return msgType == IR_MSG_REQUEST &&
|
||||
irproto::isTypedWireSizeValid(msgType, packInfo->packSize);
|
||||
}
|
||||
|
||||
bool Request::checkAddress()
|
||||
{
|
||||
bool ret;
|
||||
|
||||
@ -9,18 +9,19 @@ namespace PacketTypes
|
||||
friend IR_Decoder;
|
||||
|
||||
protected:
|
||||
bool isAvailable;
|
||||
bool isRawAvailable;
|
||||
bool isNeedAccept;
|
||||
bool isAvailable = false;
|
||||
bool isRawAvailable = false;
|
||||
bool isNeedAccept = false;
|
||||
|
||||
uint8_t msgOffset;
|
||||
uint8_t addressFromOffset;
|
||||
uint8_t addressToOffset;
|
||||
uint8_t DataOffset;
|
||||
uint8_t msgOffset = 0;
|
||||
uint8_t addressFromOffset = 0;
|
||||
uint8_t addressToOffset = 0;
|
||||
uint8_t DataOffset = 0;
|
||||
|
||||
IR_FOX::PackInfo *packInfo;
|
||||
uint16_t id;
|
||||
IR_FOX::PackInfo *packInfo = nullptr;
|
||||
uint16_t id = 0;
|
||||
|
||||
virtual bool checkPacketLayout() const;
|
||||
virtual bool checkAddress();
|
||||
void set(IR_FOX::PackInfo *packInfo, uint16_t id);
|
||||
|
||||
@ -34,9 +35,9 @@ namespace PacketTypes
|
||||
bool available();
|
||||
bool availableRaw();
|
||||
|
||||
inline uint8_t getMsgInfo() { return packInfo->buffer[0] & IR_MASK_MSG_INFO; };
|
||||
inline uint8_t getMsgType() { return (packInfo->buffer[0] >> 5) & IR_MASK_MSG_TYPE; };
|
||||
inline uint8_t getMsgRAW() { return packInfo->buffer[0]; };
|
||||
inline uint8_t getMsgInfo() { return packInfo != nullptr && packInfo->buffer != nullptr ? packInfo->buffer[0] & IR_MASK_MSG_INFO : 0U; };
|
||||
inline uint8_t getMsgType() { return packInfo != nullptr && packInfo->buffer != nullptr ? (packInfo->buffer[0] >> 5) & IR_MASK_MSG_TYPE : 0U; };
|
||||
inline uint8_t getMsgRAW() { return packInfo != nullptr && packInfo->buffer != nullptr ? packInfo->buffer[0] : 0U; };
|
||||
inline uint16_t getErrorCount() { return packInfo->err.all(); };
|
||||
inline uint8_t getErrorLowSignal() { return packInfo->err.lowSignal; };
|
||||
inline uint8_t getErrorHighSignal() { return packInfo->err.highSignal; };
|
||||
@ -65,6 +66,7 @@ namespace PacketTypes
|
||||
inline uint8_t *getDataPrt() { return _getDataPrt(this); };
|
||||
|
||||
private:
|
||||
bool checkPacketLayout() const override;
|
||||
bool checkAddress() override;
|
||||
};
|
||||
|
||||
@ -86,6 +88,7 @@ namespace PacketTypes
|
||||
inline uint8_t *getDataPrt() { return _getDataPrt(this); };
|
||||
|
||||
private:
|
||||
bool checkPacketLayout() const override;
|
||||
bool checkAddress() override;
|
||||
};
|
||||
|
||||
@ -103,6 +106,7 @@ namespace PacketTypes
|
||||
inline uint8_t getCustomByte() { return packInfo->buffer[DataOffset]; };
|
||||
|
||||
private:
|
||||
bool checkPacketLayout() const override;
|
||||
bool checkAddress() override;
|
||||
};
|
||||
|
||||
@ -121,6 +125,7 @@ namespace PacketTypes
|
||||
inline uint16_t getAddrTo() { return _getAddrTo(this); };
|
||||
|
||||
private:
|
||||
bool checkPacketLayout() const override;
|
||||
bool checkAddress() override;
|
||||
};
|
||||
|
||||
|
||||
50
tests/arduino_stubs/Arduino.h
Normal file
50
tests/arduino_stubs/Arduino.h
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
struct GPIO_TypeDef
|
||||
{
|
||||
uint32_t BSRR = 0U;
|
||||
uint32_t IDR = 0U;
|
||||
};
|
||||
|
||||
using IRQn_Type = int;
|
||||
enum TimerFormat_t : uint8_t { TICK_FORMAT = 0, MICROSEC_FORMAT, HERTZ_FORMAT };
|
||||
|
||||
constexpr uint8_t LOW = 0U;
|
||||
constexpr uint8_t HIGH = 1U;
|
||||
constexpr uint8_t INPUT = 0U;
|
||||
constexpr uint8_t OUTPUT = 1U;
|
||||
|
||||
class HardwareTimer
|
||||
{
|
||||
public:
|
||||
void pause() {}
|
||||
void resume() {}
|
||||
void setOverflow(uint32_t value, TimerFormat_t = TICK_FORMAT) { overflow_ = value; }
|
||||
uint32_t getOverflow(TimerFormat_t = TICK_FORMAT) { return overflow_; }
|
||||
uint32_t getPrescaleFactor() { return 1U; }
|
||||
uint32_t getTimerClkFreq() { return 12000000U; }
|
||||
void attachInterrupt(uint8_t, void (*)()) {}
|
||||
|
||||
private:
|
||||
uint32_t overflow_ = 1U;
|
||||
};
|
||||
|
||||
inline GPIO_TypeDef *digitalPinToPort(uint8_t) { return nullptr; }
|
||||
inline uint16_t digitalPinToBitMask(uint8_t) { return 0U; }
|
||||
inline void pinMode(uint8_t, uint8_t) {}
|
||||
inline void digitalWrite(uint8_t, uint8_t) {}
|
||||
inline void NVIC_SetPriority(IRQn_Type, uint8_t) {}
|
||||
inline void noInterrupts() {}
|
||||
inline void interrupts() {}
|
||||
|
||||
struct ArduinoSerialStub
|
||||
{
|
||||
template <typename T> void print(const T &) {}
|
||||
template <typename T> void println(const T &) {}
|
||||
void println() {}
|
||||
};
|
||||
|
||||
inline ArduinoSerialStub Serial;
|
||||
38
tests/run_host_tests.ps1
Normal file
38
tests/run_host_tests.ps1
Normal file
@ -0,0 +1,38 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$repo = Split-Path -Parent $PSScriptRoot
|
||||
$build = Join-Path $PSScriptRoot '.build'
|
||||
New-Item -ItemType Directory -Force -Path $build | Out-Null
|
||||
|
||||
$compiler = if (Test-Path -LiteralPath 'C:\MinGW\bin\g++.exe') {
|
||||
'C:\MinGW\bin\g++.exe'
|
||||
} else {
|
||||
(Get-Command g++ -ErrorAction Stop).Source
|
||||
}
|
||||
|
||||
$common = @(
|
||||
'-std=c++17', '-Wall', '-Wextra', '-Werror',
|
||||
'-Wno-unused-parameter', '-Wno-ignored-qualifiers', '-Wno-sign-compare',
|
||||
'-I', (Join-Path $PSScriptRoot 'arduino_stubs'),
|
||||
'-I', $repo
|
||||
)
|
||||
|
||||
& $compiler @common `
|
||||
(Join-Path $PSScriptRoot 'test_timing_contract.cpp') `
|
||||
(Join-Path $repo 'IR_Encoder.cpp') `
|
||||
(Join-Path $repo 'IR_config.cpp') `
|
||||
'-o' (Join-Path $build 'test_timing_contract.exe')
|
||||
if ($LASTEXITCODE -ne 0) { throw 'timing test build failed' }
|
||||
|
||||
& (Join-Path $build 'test_timing_contract.exe')
|
||||
if ($LASTEXITCODE -ne 0) { throw 'timing test failed' }
|
||||
|
||||
& $compiler @common `
|
||||
(Join-Path $PSScriptRoot 'test_packet_types.cpp') `
|
||||
(Join-Path $repo 'PacketTypes.cpp') `
|
||||
(Join-Path $repo 'IR_config.cpp') `
|
||||
'-o' (Join-Path $build 'test_packet_types.exe')
|
||||
if ($LASTEXITCODE -ne 0) { throw 'packet test build failed' }
|
||||
|
||||
& (Join-Path $build 'test_packet_types.exe')
|
||||
if ($LASTEXITCODE -ne 0) { throw 'packet test failed' }
|
||||
145
tests/test_packet_types.cpp
Normal file
145
tests/test_packet_types.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
#include "PacketTypes.h"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename Packet>
|
||||
class ExposedPacket : public Packet
|
||||
{
|
||||
public:
|
||||
void attach(IR_FOX::PackInfo *info, uint16_t id = 0U)
|
||||
{
|
||||
this->set(info, id);
|
||||
}
|
||||
};
|
||||
|
||||
IR_FOX::PackInfo makeFrame(uint8_t *buffer, uint8_t msgType, uint8_t wireBytes)
|
||||
{
|
||||
buffer[0] = static_cast<uint8_t>((msgType << 5) | (wireBytes & IR_MASK_MSG_INFO));
|
||||
IR_FOX::PackInfo result;
|
||||
result.buffer = buffer;
|
||||
result.packSize = wireBytes;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Packet>
|
||||
void verifyMinimum(uint8_t msgType, uint8_t minimum)
|
||||
{
|
||||
std::array<uint8_t, irproto::kMaxWireFrameBytes> buffer{};
|
||||
ExposedPacket<Packet> packet;
|
||||
|
||||
IR_FOX::PackInfo shortFrame = makeFrame(buffer.data(), msgType, minimum - 1U);
|
||||
packet.attach(&shortFrame);
|
||||
assert(!packet.available());
|
||||
assert(!packet.availableRaw());
|
||||
|
||||
IR_FOX::PackInfo minimumFrame = makeFrame(buffer.data(), msgType, minimum);
|
||||
packet.attach(&minimumFrame);
|
||||
assert(packet.available());
|
||||
}
|
||||
|
||||
void verifyTypedMinimums()
|
||||
{
|
||||
verifyMinimum<PacketTypes::Data>(IR_MSG_DATA_ACCEPT, 7U);
|
||||
verifyMinimum<PacketTypes::Data>(IR_MSG_DATA_NOACCEPT, 7U);
|
||||
verifyMinimum<PacketTypes::DataBack>(IR_MSG_BACK, 5U);
|
||||
verifyMinimum<PacketTypes::DataBack>(IR_MSG_BACK_TO, 7U);
|
||||
verifyMinimum<PacketTypes::Accept>(IR_MSG_ACCEPT, 6U);
|
||||
verifyMinimum<PacketTypes::Request>(IR_MSG_REQUEST, 7U);
|
||||
}
|
||||
|
||||
void verifyDataAccessCannotUnderflow()
|
||||
{
|
||||
std::array<uint8_t, irproto::kMaxWireFrameBytes> buffer{};
|
||||
ExposedPacket<PacketTypes::Data> data;
|
||||
|
||||
for (uint8_t wireBytes = 0U; wireBytes < irproto::kDataFrameOverheadBytes; ++wireBytes)
|
||||
{
|
||||
IR_FOX::PackInfo malformed = makeFrame(buffer.data(), IR_MSG_DATA_ACCEPT, wireBytes);
|
||||
data.attach(&malformed);
|
||||
assert(!data.available());
|
||||
assert(data.getDataSize() == 0U);
|
||||
assert(data.getDataPrt() == nullptr);
|
||||
assert(data.getAddrTo() == 0U);
|
||||
}
|
||||
|
||||
IR_FOX::PackInfo empty = makeFrame(buffer.data(), IR_MSG_DATA_ACCEPT, 7U);
|
||||
data.attach(&empty);
|
||||
assert(data.available());
|
||||
assert(data.getDataSize() == 0U);
|
||||
assert(data.getDataPrt() == buffer.data() + 5U);
|
||||
|
||||
IR_FOX::PackInfo oneByte = makeFrame(buffer.data(), IR_MSG_DATA_ACCEPT, 8U);
|
||||
data.attach(&oneByte);
|
||||
assert(data.available());
|
||||
assert(data.getDataSize() == 1U);
|
||||
assert(data.getDataPrt() == buffer.data() + 5U);
|
||||
}
|
||||
|
||||
void verifyBackLayouts()
|
||||
{
|
||||
std::array<uint8_t, irproto::kMaxWireFrameBytes> buffer{};
|
||||
ExposedPacket<PacketTypes::DataBack> back;
|
||||
|
||||
IR_FOX::PackInfo shortBroadcast = makeFrame(buffer.data(), IR_MSG_BACK, 4U);
|
||||
back.attach(&shortBroadcast);
|
||||
assert(!back.available());
|
||||
assert(back.getDataSize() == 0U);
|
||||
assert(back.getDataPrt() == nullptr);
|
||||
|
||||
IR_FOX::PackInfo broadcast = makeFrame(buffer.data(), IR_MSG_BACK, 5U);
|
||||
back.attach(&broadcast);
|
||||
assert(back.available());
|
||||
assert(back.getDataSize() == 0U);
|
||||
assert(back.getDataPrt() == buffer.data() + 3U);
|
||||
|
||||
IR_FOX::PackInfo shortAddressed = makeFrame(buffer.data(), IR_MSG_BACK_TO, 6U);
|
||||
back.attach(&shortAddressed);
|
||||
assert(!back.available());
|
||||
assert(back.getDataSize() == 0U);
|
||||
|
||||
IR_FOX::PackInfo addressed = makeFrame(buffer.data(), IR_MSG_BACK_TO, 7U);
|
||||
back.attach(&addressed);
|
||||
assert(back.available());
|
||||
assert(back.getDataSize() == 0U);
|
||||
assert(back.getDataPrt() == buffer.data() + 5U);
|
||||
}
|
||||
|
||||
void verifyRawAndHeaderContracts()
|
||||
{
|
||||
std::array<uint8_t, irproto::kMaxWireFrameBytes> buffer{};
|
||||
ExposedPacket<PacketTypes::BasePack> raw;
|
||||
|
||||
// Raw diagnostics remain able to observe a CRC-sized frame even when its
|
||||
// declared type-specific layout is too short.
|
||||
IR_FOX::PackInfo shortTyped = makeFrame(buffer.data(), IR_MSG_DATA_ACCEPT, 3U);
|
||||
raw.attach(&shortTyped);
|
||||
assert(raw.availableRaw());
|
||||
|
||||
IR_FOX::PackInfo inconsistent = makeFrame(buffer.data(), IR_MSG_BACK, 5U);
|
||||
inconsistent.packSize = 6U;
|
||||
raw.attach(&inconsistent);
|
||||
assert(!raw.available());
|
||||
assert(!raw.availableRaw());
|
||||
|
||||
IR_FOX::PackInfo nullFrame;
|
||||
nullFrame.packSize = 31U;
|
||||
raw.attach(&nullFrame);
|
||||
assert(!raw.available());
|
||||
assert(!raw.availableRaw());
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
verifyTypedMinimums();
|
||||
verifyDataAccessCannotUnderflow();
|
||||
verifyBackLayouts();
|
||||
verifyRawAndHeaderContracts();
|
||||
std::cout << "IR packet boundary tests: OK\n";
|
||||
return 0;
|
||||
}
|
||||
85
tests/test_timing_contract.cpp
Normal file
85
tests/test_timing_contract.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include "IR_Encoder.h"
|
||||
#include "IR_DecoderRaw.h"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
// Link seams: these paths are not exercised by the pure host timing test.
|
||||
bool IR_DecoderRaw::registerPairMuteEncoder(IR_Encoder *) { return true; }
|
||||
void IR_DecoderRaw::refreshPairMuteState() {}
|
||||
void IR_Encoder::send_HIGH(bool) {}
|
||||
void IR_Encoder::send_LOW() {}
|
||||
void IR_Encoder::send_EMPTY(uint8_t) {}
|
||||
|
||||
namespace
|
||||
{
|
||||
static_assert(irproto::dataWireBytes(0U) == 7U, "empty DATA wire size changed");
|
||||
static_assert(irproto::dataWireBytes(3U) == 10U, "DATA wire size changed");
|
||||
static_assert(irproto::dataWireBytes(24U) == 31U, "maximum DATA wire size changed");
|
||||
static_assert(irproto::dataWireBytes(25U) == 0U, "oversized DATA must be rejected");
|
||||
static_assert(irproto::backWireBytes(1U) == 6U, "BACK wire size changed");
|
||||
static_assert(irproto::backWireBytes(26U) == 31U, "maximum BACK wire size changed");
|
||||
static_assert(irproto::backToWireBytes(24U) == 31U, "maximum BACK_TO wire size changed");
|
||||
|
||||
static_assert(irproto::wireLogicalTicks(6U) == 5472U, "6-byte tick count changed");
|
||||
static_assert(irproto::wireLogicalTicks(10U) == 8728U, "10-byte tick count changed");
|
||||
static_assert(irproto::wireLogicalTicks(31U) == 25822U, "31-byte tick count changed");
|
||||
static_assert(irproto::preambleAirtimeUsCeil() == 7737U, "preamble airtime changed");
|
||||
static_assert(irproto::wireAirtimeUsCeil(6U) == 72000U, "6-byte airtime changed");
|
||||
static_assert(irproto::wireAirtimeUsCeil(10U) == 114843U, "10-byte airtime changed");
|
||||
static_assert(irproto::wireAirtimeUsCeil(31U) == 339764U, "31-byte airtime changed");
|
||||
static_assert(irproto::responseStartGuardUs() == 57198U, "response-start guard changed");
|
||||
static_assert(irproto::responseFrameGuardUs(6U) == 131100U, "response-frame guard changed");
|
||||
|
||||
uint32_t sumLogicalTicks(const IrTxGateRun *runs, size_t count)
|
||||
{
|
||||
uint32_t total = 0U;
|
||||
for (size_t i = 0U; i < count; ++i)
|
||||
total += runs[i].lenTicks;
|
||||
return total;
|
||||
}
|
||||
|
||||
void verifyFormulaAgainstTxFsm()
|
||||
{
|
||||
std::array<uint8_t, irproto::kMaxWireFrameBytes> frame{};
|
||||
std::array<IrTxGateRun, 1024U> runs{};
|
||||
|
||||
for (uint8_t wireBytes = 1U; wireBytes <= irproto::kMaxWireFrameBytes; ++wireBytes)
|
||||
{
|
||||
for (uint8_t pattern = 0U; pattern < 4U; ++pattern)
|
||||
{
|
||||
for (uint8_t i = 0U; i < wireBytes; ++i)
|
||||
{
|
||||
frame[i] = pattern == 0U ? 0x00U
|
||||
: pattern == 1U ? 0xFFU
|
||||
: pattern == 2U ? static_cast<uint8_t>((i & 1U) ? 0x55U : 0xAAU)
|
||||
: static_cast<uint8_t>(i * 73U + 19U);
|
||||
}
|
||||
const size_t count = IR_Encoder::buildGateRuns(
|
||||
frame.data(), wireBytes, runs.data(), runs.size());
|
||||
assert(count != 0U);
|
||||
assert(sumLogicalTicks(runs.data(), count) == irproto::wireLogicalTicks(wireBytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void verifyPublicSendTimeResults()
|
||||
{
|
||||
IR_Encoder encoder(1U, 42U, nullptr, false);
|
||||
uint8_t payload[26]{};
|
||||
|
||||
assert(encoder.testSendAccept(1U) == 72U); // six-byte wire frame
|
||||
assert(encoder.testSendTime(1U, payload, 3U) == 115U); // ten-byte wire frame
|
||||
assert(encoder.testSendBack(payload, 26U) == 340U); // 31-byte wire frame
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
verifyFormulaAgainstTxFsm();
|
||||
verifyPublicSendTimeResults();
|
||||
std::cout << "IR timing contract tests: OK\n";
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user