mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-04-28 03:08:08 +00:00
fix msgTypeReceive and isReceive
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ bin/*
|
|||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
log/*
|
log/*
|
||||||
/.vscode
|
/.vscode
|
||||||
|
*.zip
|
||||||
|
|||||||
125
IR_DMA_ISR_signal_analysis.md
Normal file
125
IR_DMA_ISR_signal_analysis.md
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# IR DMA vs ISR: анализ согласованности сигнала и ответа версии
|
||||||
|
|
||||||
|
Связка с остальным пультом (модули, настройки): **[`ARCHITECTURE.md`](ARCHITECTURE.md)**.
|
||||||
|
|
||||||
|
Документ фиксирует наблюдения по переходу машинки (проект Car) на **DMA-передачу** ИК через `IR_Encoder::setExternalTxBackend` и `IrDmaBackend`, сравнение со **старым путём** (таймер + **`_isr()`**), ручную проверку CRC по логу пульта и роль **`buildGateRuns`** в библиотеке **IR-protocol**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Контекст
|
||||||
|
|
||||||
|
- До введения DMA передача шла через **`IR_Encoder::begin(..., IR_Encoder::isr)`**: на каждый тик таймера (`carrierFrec * 2`) вызывается **`_isr()`**, формируются преамбула, данные, синхробиты.
|
||||||
|
- После коммита с **IR_DMA** (`Car`, `IR.cpp`): **`beginClockOnly`**, **`setExternalTxBackend`**, фактическая модуляция — **`IrDmaBackend::start`** → **`IR_Encoder::buildGateRuns`** + DMA в **BSRR**.
|
||||||
|
- Ответ версии — один из самых **длинных** кадров (до **31 байта** полного кадра по заголовку). Короткие пакеты (эхо `Version_Query`, 8 байт) в логе остаются **FrameOK**; длинный ответ версии даёт **CRC fail** / `Frame reject`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Два пути: ISR и DMA
|
||||||
|
|
||||||
|
| Этап | Старый ISR | DMA |
|
||||||
|
|------|------------|-----|
|
||||||
|
| Байты пакета + CRC | `sendDataFULL` → `sendBuffer` | То же; в `buildGateRuns` — `memcpy` в локальный буфер размером `dataByteSizeMax` |
|
||||||
|
| Развёртка в импульсы | **`_isr()`**: счётчик `toggleCounter`, ветки preamb / data / sync | **`buildGateRuns`**: RLE-сегменты `(gate, lenTicks)` → **`nextWord()`** по тикам таймера |
|
||||||
|
| Останов передачи | `signal == noSignal`, `isSending = false` | `ticksOutput >= totalTicks`, `sum(runs[i].lenTicks)` |
|
||||||
|
|
||||||
|
Идея `buildGateRuns`: **эмулировать** шаги FSM, которые в ISR выполняются при **`toggleCounter == 0`** (см. комментарий в `IR_Encoder.cpp` рядом с внутренним `while`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Ключевое наблюдение: `runLenTicks = toggleCounter + 1`
|
||||||
|
|
||||||
|
В **`IR_Encoder::buildGateRuns`** на каждой итерации внешнего цикла:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const uint16_t runLenTicks = (uint16_t)toggleCounterLocal + 1U;
|
||||||
|
```
|
||||||
|
|
||||||
|
В **`_isr()`** при стартовом **`toggleCounter == N`** выполняется **ровно N** раз ветка `if (toggleCounter) { toggleCounter--; }` подряд, пока счётчик не станет **0**; **следующий** тик попадает в `else` и делает один шаг `switch (signal)`.
|
||||||
|
|
||||||
|
Между двумя такими визитами в `else` проходит **N тиков таймера**, не **N+1**.
|
||||||
|
|
||||||
|
В `buildGateRuns` для того же начального `toggleCounterLocal` в run записывается **`N + 1` тик**. Это даёт **систематическое удлинение каждого сегмента на 1 тик** относительно модели «счётчик убывает N раз до нуля».
|
||||||
|
|
||||||
|
**Следствие:**
|
||||||
|
|
||||||
|
- `totalTicks = Σ lenTicks` в **`IrDmaBackend::startStream`** **больше**, чем число тиков, которое дал бы чистый ISR при том же пакете.
|
||||||
|
- Число внешних итераций `buildGateRuns` (шагов FSM) совпадает с числом таких сегментов; приближённо:
|
||||||
|
`totalTicks ≈ totalTicks_ISR + (число_внешних_шагов)`.
|
||||||
|
|
||||||
|
Короткий кадр: ошибка может «теряться» в допусках приёмника. Длинный (версия) — **накопление** ошибки по времени → сдвиг границ битов → **неверные байты**, в том числе **CRC**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Ручная проверка CRC по логу (пульт)
|
||||||
|
|
||||||
|
Алгоритм: **`IR_FOX::crc8`** (`IR_config.cpp`), два байта как в **`sendDataFULL`**:
|
||||||
|
|
||||||
|
- первый байт CRC = `crc8(data, 0, packSize - 2, poly1)`;
|
||||||
|
- второй = `crc8(data, 0, packSize - 1, poly2)` (в расчёт второго входит уже первый байт CRC).
|
||||||
|
|
||||||
|
Пример **31-байтного** кадра из лога `Frame reject`:
|
||||||
|
|
||||||
|
- Тело **0…28** (29 байт).
|
||||||
|
- Байты **29…30** — CRC на проводе.
|
||||||
|
|
||||||
|
Для фиксированного дампа байтов **0…28** корректная пара CRC по формуле библиотеки — **`6E 54`**, в логе на проводе — **`96 62`** → **не совпадает**; приёмник обоснованно отклоняет кадр.
|
||||||
|
|
||||||
|
Это **не** объясняется разницей AVR vs STM32: счёт идёт по массиву `uint8_t` побайтно.
|
||||||
|
|
||||||
|
Эхо **8 байт** `C8 FA 2A FD E8 5D AA B4`: пересчёт даёт **`AA B4`** — совпадает с последними байтами кадра → для этого пакета цепочка **байт → CRC** согласована.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Скрипт симуляции
|
||||||
|
|
||||||
|
В репозитории: **`docs/scripts/ir_protocol_gate_runs_sim.py`**.
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python docs/scripts/ir_protocol_gate_runs_sim.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт:
|
||||||
|
|
||||||
|
1. Считает **CRC** для примеров пакетов (8 байт эха и 31 байт из reject).
|
||||||
|
2. Воспроизводит логику **`buildGateRuns`** (с дополнением буфера до `dataByteSizeMax`, как в C++).
|
||||||
|
3. Печатает **`totalTicks`**, число **внешних шагов** FSM и связь **`totalTicks - outer_steps`** как оценку «тиков в модели ISR без +1 на каждый шаг».
|
||||||
|
|
||||||
|
Пример вывода (значения могут слегка отличаться при смене констант в `IR_config.h`):
|
||||||
|
|
||||||
|
- `preambToggle = 97`
|
||||||
|
- для 8-байт пакета: сотни шагов FSM, `totalTicks` порядка тысяч тиков
|
||||||
|
- для 31-байт: больше шагов и `totalTicks` (~25k+ тиков для текущих констант)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Связь с проектами
|
||||||
|
|
||||||
|
- **Car** (`Executer.cpp`): ответ версии через **`IR_Module::getENC().sendData(...)`** — тот же **`sendDataFULL`**, затем **`rawSend`** → DMA.
|
||||||
|
- **ControlPointUnion** (`CustomCmd.h`, слоты): запрос версии через **`sendResp`** с **`version_query`** — задержка **`IR_ResponseDelay`**, затем **`sendData`** на адрес машинки.
|
||||||
|
- **ControlPointUnion** (`Plan_B.ino`): разбор **`version_response`** из **`gotData` / `gotBackData`** только после **успешного CRC** в декодере.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Выводы
|
||||||
|
|
||||||
|
1. **Байты в RAM** на передаче формируются корректно библиотекой; проблема «после DMA» укладывается в **расхождение тайминговой развёртки** (`buildGateRuns` + DMA) со **старой** развёрткой ISR, а не в «другой CRC на машинке» при неизменённой библиотеке.
|
||||||
|
2. **Подозрение №1:** `runLenTicks = toggleCounter + 1` в **`buildGateRuns`** не совпадает с числом тиков ISR между шагами FSM (**`N`** vs **`N+1`**). Требуется сверка с эталонной трассой ISR или логическим анализатором.
|
||||||
|
3. **Проверка на будущее:** сравнить побитово выходы ISR и DMA на **одном** буфере (8 и 31 байт); при необходимости поправить формулу длины run в **`IR-protocol`** и пересобрать Car и пульт.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Ссылки на файлы
|
||||||
|
|
||||||
|
| Файл | Назначение |
|
||||||
|
|------|------------|
|
||||||
|
| `Documents/Arduino/libraries/IR-protocol/IR_Encoder.cpp` | `buildGateRuns`, `_isr`, `rawSend` |
|
||||||
|
| `Documents/Arduino/libraries/IR-protocol/IR_config.cpp` | `crc8` |
|
||||||
|
| `Car/src/IR/IR.cpp` | `setExternalTxBackend`, `txStart` |
|
||||||
|
| `Car/src/IR/IrDmaBackend.cpp` | `startStream`, `totalTicks`, `nextWord` |
|
||||||
|
| `ControlPointUnion/Plan_B/TestPoints/CustomCmd.h` | `sendResp` / `version_query` для тестовых слотов |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Документ составлен по обсуждению в чате; при смене версии IR-protocol числа констант и `totalTicks` пересчитывайте скриптом.*
|
||||||
@ -1,5 +1,6 @@
|
|||||||
#include "IR_DecoderRaw.h"
|
#include "IR_DecoderRaw.h"
|
||||||
#include "IR_Encoder.h"
|
#include "IR_Encoder.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
IR_DecoderRaw::IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair) : encoder(encPair)
|
IR_DecoderRaw::IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair) : encoder(encPair)
|
||||||
{
|
{
|
||||||
@ -587,6 +588,15 @@ void IR_DecoderRaw::writeToBuffer(bool bit)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Тип приёма (для isReceive): выставляем сразу после первого байта, ДО проверки «Конец».
|
||||||
|
// Иначе при packSize==1 один и тот же шаг i_dataBuffer==8 одновременно «закрывает» кадр (msgTypeReceive=0)
|
||||||
|
// и снова выставляет msgTypeReceive ниже — флаг залипает, пока не придёт ошибка/другой кадр.
|
||||||
|
if (packSize && (i_dataBuffer == 8))
|
||||||
|
{
|
||||||
|
msgTypeReceive = (dataBuffer[0] >> 5) | 0b11111000;
|
||||||
|
// SerialUSB.println(msgTypeReceive & IR_MASK_MSG_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
if (packSize && (i_dataBuffer == packSize * bitPerByte))
|
if (packSize && (i_dataBuffer == packSize * bitPerByte))
|
||||||
{ // Конец
|
{ // Конец
|
||||||
#ifdef IRDEBUG_INFO
|
#ifdef IRDEBUG_INFO
|
||||||
@ -631,12 +641,11 @@ void IR_DecoderRaw::writeToBuffer(bool bit)
|
|||||||
}
|
}
|
||||||
OUT_BRUTEFORCE:;
|
OUT_BRUTEFORCE:;
|
||||||
#endif
|
#endif
|
||||||
}
|
if (!isAvailable && packSize > 0 && packSize <= dataByteSizeMax) {
|
||||||
|
memcpy(rejectBuffer, dataBuffer, packSize);
|
||||||
if (packSize && (i_dataBuffer == 8)) {
|
rejectPackSize = static_cast<uint8_t>(packSize);
|
||||||
msgTypeReceive = (dataBuffer[0]>>5) | 0b11111000;
|
isRejectAvailable = true;
|
||||||
// SerialUSB.println(msgTypeReceive & IR_MASK_MSG_TYPE);
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,9 +660,7 @@ bool IR_DecoderRaw::crcCheck(uint8_t len, crc_t &crc)
|
|||||||
crc = (crc8(dataBuffer, 0, len, poly1) << 8) & ~((crc_t)0xFF);
|
crc = (crc8(dataBuffer, 0, len, poly1) << 8) & ~((crc_t)0xFF);
|
||||||
crc |= crc8(dataBuffer, 0, len + 1, poly2) & (crc_t)0xFF;
|
crc |= crc8(dataBuffer, 0, len + 1, poly2) & (crc_t)0xFF;
|
||||||
|
|
||||||
if (
|
if (dataBuffer[len] == (crc >> 8) & 0xFF &&
|
||||||
crc &&
|
|
||||||
dataBuffer[len] == (crc >> 8) & 0xFF &&
|
|
||||||
dataBuffer[len + 1] == (crc & 0xFF))
|
dataBuffer[len + 1] == (crc & 0xFF))
|
||||||
{
|
{
|
||||||
crcOK = true;
|
crcOK = true;
|
||||||
@ -666,6 +673,14 @@ bool IR_DecoderRaw::crcCheck(uint8_t len, crc_t &crc)
|
|||||||
return crcOK;
|
return crcOK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IR_DecoderRaw::availableReject()
|
||||||
|
{
|
||||||
|
if (!isRejectAvailable)
|
||||||
|
return false;
|
||||||
|
isRejectAvailable = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t IR_DecoderRaw::ceil_div(uint16_t val, uint16_t divider)
|
uint16_t IR_DecoderRaw::ceil_div(uint16_t val, uint16_t divider)
|
||||||
{
|
{
|
||||||
int ret = val / divider;
|
int ret = val / divider;
|
||||||
|
|||||||
@ -51,8 +51,17 @@ public:
|
|||||||
bool isSubOverflow();
|
bool isSubOverflow();
|
||||||
volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета
|
volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета
|
||||||
|
|
||||||
|
/// Кадр собран по длине из заголовка, но CRC не сошёлся — один раз можно прочитать копию сырых байтов.
|
||||||
|
bool availableReject();
|
||||||
|
uint8_t getRejectSize() const { return rejectPackSize; }
|
||||||
|
const uint8_t* getRejectBuffer() const { return rejectBuffer; }
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
private:
|
private:
|
||||||
|
bool isRejectAvailable = false;
|
||||||
|
uint8_t rejectPackSize = 0;
|
||||||
|
uint8_t rejectBuffer[dataByteSizeMax]{};
|
||||||
|
|
||||||
ErrorsStruct errors;
|
ErrorsStruct errors;
|
||||||
bool isAvailable = false;
|
bool isAvailable = false;
|
||||||
uint16_t packSize;
|
uint16_t packSize;
|
||||||
|
|||||||
@ -463,7 +463,7 @@ IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *d
|
|||||||
|
|
||||||
uint8_t packSize = msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + min(uint8_t(1), len) + crcBytes;
|
uint8_t packSize = msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + min(uint8_t(1), len) + crcBytes;
|
||||||
uint8_t msgType =
|
uint8_t msgType =
|
||||||
((isAdressed ? IR_MSG_BACK_TO : IR_MSG_BACK) << 5) | ((packSize) & (IR_MASK_MSG_INFO >> 1));
|
((isAdressed ? IR_MSG_BACK_TO : IR_MSG_BACK) << 5) | ((packSize) & IR_MASK_MSG_INFO);
|
||||||
|
|
||||||
// формирование массива
|
// формирование массива
|
||||||
// msg_type
|
// msg_type
|
||||||
|
|||||||
23
IR_config.h
23
IR_config.h
@ -48,12 +48,13 @@
|
|||||||
\____________________________________________________________________________________________________/
|
\____________________________________________________________________________________________________/
|
||||||
|
|
||||||
msg type:
|
msg type:
|
||||||
// __________
|
// __________
|
||||||
// | 01234567 |
|
// | 01234567 |
|
||||||
// ----------
|
// ----------
|
||||||
// | xxx..... | = тип сообщения
|
// | xxx..... | = тип сообщения (биты 7..5)
|
||||||
// | ...xxxxx | = длина (максимум 31 бита) - не больше 24 байт на тело пакета
|
// | ...xxxxx | = полная длина кадра в байтах (5 бит, 0..31, IR_MASK_MSG_INFO), не «31 бит» и не отдельный лимит «24 байта»
|
||||||
// ---------- */
|
// Полезная нагрузка в data pack: до bytePerPack байт (см. #define bytePerPack).
|
||||||
|
// ---------- */
|
||||||
#define IR_MSG_BACK 0U // | 000...... | = Задний сигнал машинки
|
#define IR_MSG_BACK 0U // | 000...... | = Задний сигнал машинки
|
||||||
#define IR_MSG_ACCEPT 1U // | 001..... | = подтверждение
|
#define IR_MSG_ACCEPT 1U // | 001..... | = подтверждение
|
||||||
#define IR_MSG_REQUEST 2U // | 010..... | = запрос
|
#define IR_MSG_REQUEST 2U // | 010..... | = запрос
|
||||||
@ -81,12 +82,13 @@ msg type:
|
|||||||
|
|
||||||
|
|
||||||
/`````````````````````` Задний сигнал машинки без адресации ``````````````````````\
|
/`````````````````````` Задний сигнал машинки без адресации ``````````````````````\
|
||||||
|
// Первый байт: (IR_MSG_BACK<<5) | (packSize & IR_MASK_MSG_INFO) — как у data pack (тип + длина 0..31).
|
||||||
|
|
||||||
{``````````} [````````````````````````] [````````````````````````] [``````````````]
|
{``````````} [````````````````````````] [````````````````````````] [``````````````]
|
||||||
{ msg type } [ addr_from uint16_t ] [====== data bytes ======] [ CRC Bytes ]
|
{ msg type } [ addr_from uint16_t ] [====== data bytes ======] [ CRC Bytes ]
|
||||||
{..........} [........................] [........................] [..............]
|
{..........} [........................] [........................] [..............]
|
||||||
|
|
||||||
{ 0000xxxx } [addr_from_H][addr_from_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]
|
{ xxx..|..xxxxx } [addr_from_H][addr_from_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]
|
||||||
| 0 1 2 3 | |
|
| 0 1 2 3 | |
|
||||||
\_____________________________________________________________________/ |
|
\_____________________________________________________________________/ |
|
||||||
| |
|
| |
|
||||||
@ -95,12 +97,13 @@ msg type:
|
|||||||
|
|
||||||
|
|
||||||
/```````````````````````````````````` Задний сигнал машинки с адресацией ````````````````````````````````````\
|
/```````````````````````````````````` Задний сигнал машинки с адресацией ````````````````````````````````````\
|
||||||
|
// Первый байт: (IR_MSG_BACK_TO<<5) | (packSize & IR_MASK_MSG_INFO) — IR_MSG_BACK_TO в битах 7..5, длина 0..31.
|
||||||
|
|
||||||
{``````````} [````````````````````````] [````````````````````````] [````````````````````````] [``````````````]
|
{``````````} [````````````````````````] [````````````````````````] [````````````````````````] [``````````````]
|
||||||
{ msg type } [ addr_from uint16_t ] [ addr_to uint16_t ] [====== data bytes ======] [ CRC Bytes ]
|
{ msg type } [ addr_from uint16_t ] [ addr_to uint16_t ] [====== data bytes ======] [ CRC Bytes ]
|
||||||
{..........} [........................] [........................] [........................] [..............]
|
{..........} [........................] [........................] [........................] [..............]
|
||||||
|
|
||||||
{ 0001xxxx } [addr_from_H][addr_from_L] [addr_from_H][addr_from_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]
|
{ xxx..|..xxxxx } [addr_from_H][addr_from_L] [addr_to_H][addr_to_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]
|
||||||
| 0 1 2 3 4 5 | |
|
| 0 1 2 3 4 5 | |
|
||||||
\________________________________________________________________________________________________/ |
|
\________________________________________________________________________________________________/ |
|
||||||
| |
|
| |
|
||||||
|
|||||||
174
ir_protocol_gate_runs_sim.py
Normal file
174
ir_protocol_gate_runs_sim.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Симуляция логики IR_Encoder::buildGateRuns (IR-protocol) и утилиты CRC8.
|
||||||
|
Запуск из корня репозитория: python docs/scripts/ir_protocol_gate_runs_sim.py
|
||||||
|
Или: python ir_protocol_gate_runs_sim.py из каталога scripts/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# --- IR_config.h (фрагмент) ---
|
||||||
|
bitPauseTakts = 12
|
||||||
|
bitActiveTakts = 25
|
||||||
|
preambPulse = 3
|
||||||
|
syncBits = 3
|
||||||
|
bitPerByte = 8
|
||||||
|
preambToggle = ((bitPauseTakts * 2 + bitActiveTakts) * 2 - 1)
|
||||||
|
bitHigh = [(bitPauseTakts) * 2 - 1, (bitActiveTakts) * 2 - 1]
|
||||||
|
bitLow = [(bitPauseTakts // 2 + bitActiveTakts) * 2 - 1, (bitPauseTakts) - 1]
|
||||||
|
|
||||||
|
preamb, data, sync, noSignal = 0, 1, 2, 3
|
||||||
|
HIGH = True
|
||||||
|
|
||||||
|
|
||||||
|
def crc8(data: bytes, start: int, end: int, poly: int) -> int:
|
||||||
|
"""Как IR_FOX::crc8 в IR_config.cpp: [start, end)."""
|
||||||
|
crc = 0xFF
|
||||||
|
for i in range(start, end):
|
||||||
|
crc ^= data[i]
|
||||||
|
for _ in range(8):
|
||||||
|
if (crc & 0x80) != 0:
|
||||||
|
crc = ((crc << 1) ^ poly) & 0xFF
|
||||||
|
else:
|
||||||
|
crc = (crc << 1) & 0xFF
|
||||||
|
return crc
|
||||||
|
|
||||||
|
|
||||||
|
def crc_pair_over_wire(packet: bytes) -> tuple[int, int]:
|
||||||
|
"""Два байта CRC как в IR_Encoder::sendDataFULL (poly1 старший, poly2 младший)."""
|
||||||
|
ps = len(packet)
|
||||||
|
if ps < 2:
|
||||||
|
return 0, 0
|
||||||
|
b1 = crc8(packet, 0, ps - 2, 0x31) & 0xFF
|
||||||
|
b2 = crc8(packet, 0, ps - 1, 0x8C) & 0xFF
|
||||||
|
return b1, b2
|
||||||
|
|
||||||
|
|
||||||
|
# Как dataByteSizeMax в IR_config.h (msg+addr+addr+bytePerPack+crc)
|
||||||
|
DATA_BYTE_SIZE_MAX = 1 + 2 + 2 + 31 + 2
|
||||||
|
|
||||||
|
|
||||||
|
def build_gate_runs(packet: bytes):
|
||||||
|
"""
|
||||||
|
Повторяет IR_Encoder::buildGateRuns: список (gate: bool, lenTicks: int), сумма lenTicks = totalTicks DMA.
|
||||||
|
Буфер дополняется нулями до dataByteSizeMax, как sendBufferLocal[dataByteSizeMax] в C++.
|
||||||
|
"""
|
||||||
|
send_len = len(packet)
|
||||||
|
send_buf = bytearray(packet) + bytes(max(0, DATA_BYTE_SIZE_MAX - len(packet)))
|
||||||
|
|
||||||
|
toggle = preambToggle
|
||||||
|
data_bit = bitPerByte - 1
|
||||||
|
data_byte = 0
|
||||||
|
preamb_front = preambPulse * 2 - 1
|
||||||
|
data_seq = bitPerByte * 2
|
||||||
|
sync_seq = syncBits * 2
|
||||||
|
sync_last = False
|
||||||
|
sig = preamb
|
||||||
|
state = HIGH
|
||||||
|
cur_seq = bitHigh
|
||||||
|
|
||||||
|
runs: list[tuple[bool, int]] = []
|
||||||
|
outer_steps = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
outer_steps += 1
|
||||||
|
gate = state
|
||||||
|
run_len = toggle + 1 # как в C++: (uint16_t)toggleCounterLocal + 1U
|
||||||
|
|
||||||
|
if runs and runs[-1][0] == gate:
|
||||||
|
g, ln = runs[-1]
|
||||||
|
runs[-1] = (g, ln + run_len)
|
||||||
|
else:
|
||||||
|
runs.append((gate, run_len))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if sig == noSignal:
|
||||||
|
return runs, outer_steps
|
||||||
|
|
||||||
|
if sig == preamb:
|
||||||
|
if preamb_front:
|
||||||
|
preamb_front -= 1
|
||||||
|
toggle = preambToggle
|
||||||
|
break
|
||||||
|
sig = data
|
||||||
|
state = not False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if sig == data:
|
||||||
|
if data_seq:
|
||||||
|
if not (data_seq & 1):
|
||||||
|
cur_seq = bitHigh if ((send_buf[data_byte] >> data_bit) & 1) else bitLow
|
||||||
|
data_bit -= 1
|
||||||
|
toggle = cur_seq[not state]
|
||||||
|
data_seq -= 1
|
||||||
|
break
|
||||||
|
sync_last = send_buf[data_byte] & 1
|
||||||
|
data_byte += 1
|
||||||
|
data_bit = bitPerByte - 1
|
||||||
|
data_seq = bitPerByte * 2
|
||||||
|
sig = sync
|
||||||
|
continue
|
||||||
|
|
||||||
|
if sig == sync:
|
||||||
|
if sync_seq:
|
||||||
|
if not (sync_seq & 1):
|
||||||
|
if sync_seq == 2:
|
||||||
|
cur_seq = bitLow if (send_buf[data_byte] & 0x80) else bitHigh
|
||||||
|
else:
|
||||||
|
cur_seq = bitLow if sync_last else bitHigh
|
||||||
|
sync_last = not sync_last
|
||||||
|
toggle = cur_seq[not state]
|
||||||
|
sync_seq -= 1
|
||||||
|
break
|
||||||
|
sig = data
|
||||||
|
sync_seq = syncBits * 2
|
||||||
|
if data_byte >= send_len:
|
||||||
|
sig = noSignal
|
||||||
|
continue
|
||||||
|
|
||||||
|
return [], 0
|
||||||
|
|
||||||
|
state = not state
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
print("IR-protocol: preambToggle =", preambToggle)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Пример из лога: 8-байтный эхо-пакет Version_Query (CRC OK на приёме)
|
||||||
|
echo = bytes.fromhex("C8 FA 2A FD E8 5D AA B4")
|
||||||
|
c1, c2 = crc_pair_over_wire(echo)
|
||||||
|
print("8 байт (эхо): CRC вычисленный:", f"{c1:02X}", f"{c2:02X}", "| на проводе:", f"{echo[6]:02X}", f"{echo[7]:02X}")
|
||||||
|
|
||||||
|
runs8, steps8 = build_gate_runs(echo)
|
||||||
|
total8 = sum(r[1] for r in runs8)
|
||||||
|
print(" buildGateRuns: внешних шагов FSM =", steps8, ", totalTicks =", total8, ", число run-сегментов =", len(runs8))
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 31 байт из лога Frame reject (пример)
|
||||||
|
reject = bytes.fromhex(
|
||||||
|
"DF 00 00 FA 2A 5E 43 61 72 5F 76 34 2E 33 2E 38 5F 5B 31 32 4D 68 7A 5D 6B ED 1D 9A 53 96 62"
|
||||||
|
)
|
||||||
|
if len(reject) == 31:
|
||||||
|
c1, c2 = crc_pair_over_wire(reject)
|
||||||
|
print("31 байт (reject): CRC по телу 0..28 должен быть:", f"{c1:02X}", f"{c2:02X}", "| байты [29:31]:", f"{reject[29]:02X}", f"{reject[30]:02X}")
|
||||||
|
print(" Совпадение с формулой:", c1 == reject[29] and c2 == reject[30])
|
||||||
|
|
||||||
|
runs31, steps31 = build_gate_runs(reject)
|
||||||
|
total31 = sum(r[1] for r in runs31)
|
||||||
|
print(" buildGateRuns: внешних шагов =", steps31, ", totalTicks =", total31, ", run-сегментов =", len(runs31))
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Связь totalTicks с моделью «N тиков на сегмент до шага FSM»
|
||||||
|
# total_build = sum(toggle_i + 1); если бы было sum(toggle_i), разница = steps
|
||||||
|
theoretical_isr_ticks = total31 - steps31
|
||||||
|
print("Для 31-байт пакета: totalTicks (buildGateRuns) =", total31)
|
||||||
|
print(" Если каждый внешний шаг даёт +1 к длине сегмента относительно ISR (runLen = toggle+1 vs toggle),")
|
||||||
|
print(" оценка «ISR-тиков» как totalTicks - outer_steps =", theoretical_isr_ticks)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user