Files
IR-protocol/docs_analysis/Signal_physics_analysis.md
2026-07-01 15:33:49 +03:00

18 KiB
Raw Blame History

Анализ физики сигнала: TX-форма против RX-коррекций

1. Резюме

Система в основном не борется сама с собой: и TX, и RX выведены из одних констант IR_config.h, форма на передаче при multiply=2 (дефолт, чётный) — бит-точная, без округлений, а подавляющая часть RX-эвристик ловит деформации, которые реально вносит ИК-канал через демодулятор (TSOP-АРУ, отражения, фон, расхождение кварцев). Различение 0/1 идёт по скважности (highTime>lowTime, IR_DecoderRaw.cpp:620-633) внутри периода и не зависит от широкого окна периода — это архитектурно здоровое решение.

Но есть три очага самопорождённых ошибок, которые RX молча компенсирует:

  1. Главный (чистый баг, не канал): RX строит всю временную базу на усечённой константе carrierPeriod = 1e6/38000 = 26 (bitTime=962 µs), тогда как TX физически излучает на реальной частоте таймера — 26.316 µs, bitTime≈973.7 µs. Систематический односторонний сдвиг ~11.7 µs/бит (1.2%), который целиком съедается допуском tolerance=300. Это TX/модель кривит — RX терпит.
  2. Конфиг-зависимый: ISR-путь вносит программный джиттер фронтов (латентность/вытеснение, IR_Encoder.cpp:1037-1042), которого нет у DMA-пути. Часть glitch/reconstruction-логики RX страхует именно этот самопорождённый джиттер.
  3. Спровоцированный дизайном модуляции: протокол использует длинные непрерывные MARK (преамбула 49 периодов ≈1290 µs, бит '1' = 25 периодов ≈658 µs). Длинные непрерывные burst'ы — классический триггер АРУ TSOP-демодулятора (просадка огибающей в середине долгого mark) → «рваная единица», которую чинит реконструкция E/G. Деформацию вносит канал (TSOP), но провоцирует её выбор формы на TX.

Остальное — стартовая фаза (≤26 µs), дрейф периода, дребезг фронтов — это то, что обязан обрабатывать любой ИК-приёмник; здесь самолечения нет.

2. Карта механизмов RX

Механизм RX (file:line) Какую деформацию ловит/чинит Детект/Коррекция Источник деформации Вердикт
A. pulse-filter, минимум ширины (DecoderRaw 1493-1555) иголка/дребезг компаратора, пара «↑↓» короче минимума детект реальный канал (front-end, дребезг) оправданно (но дефолт MIN_PULSE_US=0 — выключен)
B. short-LOW glitch reject + phase nudge (484-499, 8-16) короткий ложный ↑ после микро-провала огибающей детект + коррекция фазы реальный канал (TSOP-АРУ, просадка) оправданно
C. micro-gap rise reject + nudge (500-516) распад фронта на два близких детект + коррекция фазы реальный канал; частично ISR-джиттер в основном оправданно
D. отсев короткого стартового периода / спада (517-524, 566-573) сжатый ↑→↑ без контекста, дребезг спада детект реальный канал (шум/дребезг) оправданно
E. сбор рваной единицы (526-557) длинный HIGH-бит, распавшийся из-за пропадания несущей подготовка к коррекции канал (TSOP-АРУ), спровоцировано длинными MARK на TX оправданно, но триггер — дизайн формы
F. окно валидности периода (h:24-27; 583-592) грубо растянутый/сжатый период детект/брак реальный канал (потеря кадра) оправданно
G. реконструкция ceil_div, invertErr, «лишний ноль/единица» (617-741, 980-986) пропущенные ↑-фронты, N слипшихся бит, дроблёный/короткий бит коррекция (ядро) канал (дропаут несущей); косвенно ISR-джиттер оправданно под канал; часть работы — следствие TX-дизайна
H. восстановление сетки по sync (804-853) сдвиг сетки data↔sync из-за лишнего/недостающего бита детект срыва + локальная коррекция на 1 бит канал (потеря бита); маркер {!L,L,!M} от TX детерминирован оправданно (детект реальной потери)
I. преамбула Idle→Candidate→Locked, EMA периода, якорь фазы, freeFrec (1557-1723) дрейф периода, разброс кварцев, старт-фаза, чужая частота коррекция базы/фазы смешанный: реальный (кварцы/дрейф) + самопорождённый (старт-фаза TIM ≤26 µs, сдвиг 1.2% из carrierPeriod=26) необходимо всегда; но частично адаптируется к TX-багу

3. Где Даша сама порождает ошибку формы и потом её чинит

(а) Усечённый carrierPeriod=26 — самое чистое «самолечение». Физическая генерация НЕ использует эту константу: setOverflow(carrierFrec*multiply, HERTZ_FORMAT) (IR_Encoder.cpp:411) даёт реальные ~26.316 µs. А вся сетка RX (riseSyncTime=962, riseTimeMin/Max, aroundRise, IR_timeout) выведена из carrierPeriod=26. Результат: TX-биты систематически на 1.2% длиннее, чем ждёт RX. Сдвиг односторонний (всегда в плюс), поэтому запас aroundRise со стороны riseTimeMax=1262 меньше, чем со стороны riseTimeMin=662. На бит это 11.7 µs, на преамбулу — ~15 µs (1274 vs 1289.5 µs). Это не канальная ошибка — это несогласованность двух представлений одной частоты внутри одного кода. RX-допуск tolerance=300 существует в т.ч. чтобы это не всплыло. Исправляется тривиально (общий реальный период), после чего часть запаса окна освобождается под настоящий канал.

(б) ISR-путь как источник джиттера, который чинит glitch-логика. DMA-путь детерминирован (аппаратная пересылка BSRR по TIM_UP, IrDmaTxStm32.h:246-253). ISR-путь плавает по времени из-за латентности прерывания и обхода списка энкодеров (IR_Encoder.cpp:1037-1042). Плавающие фронты — ровно то, что подхватывают B/C (nudge) и частично G. То есть при работе через ISR приёмник страхует собственный TX-джиттер, а не канал. Лечение: всегда использовать DMA-путь (или признать ISR как деградированный режим).

(в) Длинные непрерывные MARK провоцируют TSOP-АРУ → «рваная единица». Кодирование pulse-width с MARK до 25 периодов и преамбулой 49 периодов непрерывной несущей — это осознанный выбор формы на TX. TSOP-демодуляторы имеют АРУ и штатно «проседают» на длинных непрерывных burst'ах, порождая ложные провалы огибающей в середине mark. Реконструкция E + G (ceil_div, highCount==0 && highTime>riseTime/3 → highCount++, 641-648) существует во многом чтобы это дособрать. Деформацию физически вносит канал (TSOP), но её вероятность задаёт форма на TX. Это не баг, но это «дизайн, требующий костыля на приёме».

(г) Что НЕ является самолечением (важно не перепутать):

  • Стартовая фаза кадра ≤1 период несущей (free-running TIM, IrDmaTxStm32.h:241-253) — компенсируется якорем фазы преамбулы (I, prevRise=front.time+mean/2, 1709). Но приёмник в принципе не знает абсолютную фазу передатчика и обязан выводить её из преамбулы — это необходимая функция, а не борьба с собой.
  • Маркер sync {!L, L, !M} детерминирован (IR_Encoder.cpp:305-331), буфер зануляется — H ловит реальную потерю/вставку бита в канале, не TX-артефакт.
  • Квантование фронтов сеткой 13.16 µs (DMA/BSRR) — тонет в допуске TSOP (десятки µs) и погоды не делает.

4. Широкие ли допуски и что они маскируют

aroundRise = 662..1262 µs при бите 962 → ±31%. По логике подсчёта бит это не широко и путаницы 1-бит/2-бита не создаёт: 2 бита ≈1924 µs, порог ceil_div на 2 бита ≈1443 µs; между концом окна одного бита (1262) и порогом двух (1443) есть зазор ~180 µs — перекрытия нет. Значение бита определяется по duty, а не по длине периода, поэтому широкое окно значения не портит.

Куда реально уходит ±300 µs:

  • Легитимно: асимметрия leading/trailing фронтов TSOP-демодулятора (типично десятки–сотни µs), разброс кварцев RX/TX, дрожание огибающей, задержка АРУ. Для настоящего ИК-канала запас в этом порядке нужен.
  • Маскирует баг: самопорождённый односторонний сдвиг 1.2% (пункт 3а) полностью прячется в этом окне. Именно поэтому баг carrierPeriod=26 не проявляется как отказ — допуск его глотает. Это и есть «система не замечает, что борется сама с собой».

Слабое место (согласен с RX-разбором): при freeFrec=false база riseTime жёстко=962 и не подстраивается по ходу кадра (медиана — «TODO», IR_DecoderRaw.h:22). Односторонний TX-сдвиг + систематическое смещение периода передатчика съедают запас именно со стороны riseTimeMax; на краю окна шум легче перекидывает интервал в ветку реконструкции G, накручивая errors.*. Также джиттер-полки преамбулы (18%, но не менее 80 µs) жёстче рабочего окна декодера (31%) — залочиться труднее, чем потом удержать биты; это перекос порогов, а не форма.

5. Рекомендации

Чинить на TX (чтобы соответствующие RX-эвристики стали не нужны):

  1. Согласовать период несущей между TX и RX. Убрать усечение carrierPeriod=26 из временной модели RX: выводить bitTime/riseSyncTime из той же реальной частоты, что даёт setOverflow(carrierFrec*multiply,...) (дробно, напр. фиксированная точка). Устраняет односторонний сдвиг 1.2% и освобождает ~12 µs/бит запаса окна. Trade-off: дробная арифметика на MCU; но можно один раз посчитать реальный период при инициализации.
  2. Всегда использовать DMA-путь; ISR — только как явный деградированный fallback. Снимает программный джиттер фронтов и часть нагрузки с B/C/G. Trade-off: требует свободного канала DMA+таймера; на платах без ресурса — остаётся ISR с его джиттером.
  3. Пересмотреть длину непрерывных MARK/преамбулы под конкретный TSOP. Если приёмник — демодулирующий TSOP с АРУ, длинные burst'ы (658/1290 µs) провоцируют «рваную единицу». Сократить максимальный непрерывный mark или разбить преамбулу на более частые фронты в пределах спецификации TSOP по burst-length. Trade-off: меняет протокол/энергетику кадра, требует ревизии окон преамбулы RX; выигрыш — можно ослабить E/G.
  4. (Опционально) фазовое выравнивание старта кадра (сброс счётчика TIM при старте потока). Уберёт стартовый сдвиг ≤26 µs. Trade-off: минимальный выигрыш (преамбула всё равно якорит фазу) — низкий приоритет.

Что из «коррекций» оставить под настоящий канал (даже после идеального TX):

  • G (ceil_div реконструкция) и E (сбор рваной единицы) — оставить: дропаут несущей и просадка АРУ на реальной дистанции/отражениях никуда не денутся. Это ядро устойчивости к каналу.
  • B/C (glitch reject + phase nudge) и A (pulse-filter) — оставить: дребезг/иголки front-end реальны; A стоит включить (IR_INPUT_MIN_PULSE_US>0) на STM32, где сейчас фронты идут в декодер напрямую (tick 440-444).
  • H (sync-grid recovery) — оставить: детектирует реальную потерю/вставку бита; маркер детерминирован, ложных срабатываний от TX не будет.
  • I (преамбула: EMA периода, якорь фазы) — оставить: адаптация под кварцы/дрейф и вывод фазы обязательны для любого RX. Рекомендуется выровнять пороги: сделать джиттер-полку преамбулы не жёстче рабочего окна декодера, иначе лок труднее удержания.
  • Кандидаты на сужение после фикса TX: окно aroundRise можно сузить в сторону riseTimeMax (убрать заложенный под 1.2% односторонний запас) и реализовать бегущую медиану периода (IR_DecoderRaw.h:22 TODO) — тогда допуск начнёт отражать реальный канал, а не прятать несогласованность констант.

Итог-вердикт: коррекции RX в массе оправданны под реальный ИК-канал, но система содержит одно явное самолечение (усечённый carrierPeriod → 1.2% сдвиг, спрятанный в tolerance) и два условных (ISR-джиттер; длинные MARK, провоцирующие TSOP-АРУ). Широкий допуск ±31% сам по себе безопасен для подсчёта бит, но именно он маскирует баг с частотой — то есть система действительно частично борется сама с собой, просто настолько тихо, что это не видно в errors.*.