# test_buttons: finished, all documentation refactored

This commit is contained in:
Dmitry Akimov 2026-06-23 15:02:36 +03:00
parent 8d85ffe877
commit 293b61fff3
34 changed files with 2917 additions and 3945 deletions

1
.gitignore vendored
View file

@ -75,3 +75,4 @@ __pycache__/
tools/host/.venv-host/ tools/host/.venv-host/
tools/host/.venv-host-win/ tools/host/.venv-host-win/
.zed/ .zed/
project_tree.txt

View file

@ -1,6 +1,6 @@
# Журнал изменений: `tft_manufacture_test` # Журнал изменений: `tft_manufacture_test`
Репозиторий: https://github.com/OSabuser/tft_manufacture_test.git Репозиторий: <https://github.com/OSabuser/tft_manufacture_test.git>
Отслеживаемая ветка: `dev` Отслеживаемая ветка: `dev`
Базовый диапазон: 2026-03-05 .. 2026-05-08, 52 коммита Базовый диапазон: 2026-03-05 .. 2026-05-08, 52 коммита
Базовый HEAD: `22f98c311a4564d8f18ed72d11635bf908046b1e` Базовый HEAD: `22f98c311a4564d8f18ed72d11635bf908046b1e`
@ -50,27 +50,67 @@
## [Не выпущено] ## [Не выпущено]
Диапазон: после `22f98c311a4564d8f18ed72d11635bf908046b1e`
### Кратко
- После базового среза новые изменения ещё не обрабатывались.
### Что отслеживать ### Что отслеживать
- Проверять, остаётся ли `.github/workflows/ci.yml` только сборочным workflow через `just ci::build` или начинает запускать host-тесты. - Проверять, остаётся ли `.github/workflows/ci.yml` только сборочным workflow через `just ci::build` или начинает запускать host-тесты.
- Отслеживать появление self-hosted runner, регулярных аппаратных прогонов или отчётов по HIL. - Отслеживать появление self-hosted runner, регулярных аппаратных прогонов или отчётов по HIL.
- Проверять новые firmware-test модули в `firmware/test/src/tests/` и синхронные обновления протокола/документации. - Проверять новые firmware-test модули в `firmware/test/src/tests/` и синхронные обновления протокола/документации.
- Следить за развитием BSP: MQS, RGB, bootloader или `tft_app`. - Следить за развитием BSP: MQS, RGB, bootloader или `tft_app`.
- Отслеживать локальные патчи поверх vendor SDK, которые нужно вести отдельным patch log. - Отслеживать локальные патчи поверх vendor SDK, которые нужно вести отдельным patch log.
## [2026-06-23] — Buttons/display test modules и полный рефакторинг документации
Диапазон: `22f98c311a4564d8f18ed72d11635bf908046b1e..<NEW_SHA>`
Сравнение: <https://github.com/OSabuser/tft_manufacture_test/compare/22f98c311a4564d8f18ed72d11635bf908046b1e>...<NEW_SHA>
### Кратко
- Manufacturing-test firmware получил два новых test module (`test_buttons`, `test_display`), оба hardware-verified на таргете.
- Вся проектная документация прошла полный рефакторинг: единый шаблон для BSP README, Mermaid-диаграммы вместо ASCII, структурные README для `utils/` и `port/`.
### Добавлено
- `firmware/test/src/tests/test_buttons.c` — интерактивный тест двух тактовых кнопок (GPIO_B1_14/GPIO2[30], GPIO_B1_15/GPIO2[31]); non-blocking polling 5 мс, таймаут даёт `TEST_STATUS_SKIP`; `bsp_button_init()` вызывается внутри init-фазы модуля.
- `firmware/test/src/tests/test_display.c` — интерактивный тест дисплея (два этапа: заливка цветом R/G/B/W с confirm-запросами, проверка ротации LR/UD через `bsp_display_set_rotation()`).
- `port/fatfs/README.md` — новый документ, описывает INTERFACE-архитектуру `port_fatfs_sd` и причину per-binary компиляции `diskio_sd.c`.
- `utils/prio_queue/README.md` — новый документ (модуль существовал, документация отсутствовала).
### Изменено
- Все 11 BSP README (`led`, `tick`, `uart_host`, `opto`, `can`, `button`, `usb_cdc`, `sdram`, `qspi_flash`, `sd`, `display`) приведены к единому шаблону: Аппаратура (с номерами корпуса из pin_mux) → Архитектура → API → Быстрый старт → Тестирование → Интеграция → CMake.
- `bsp/README.md` — исправлена таблица компонентов (ранее отсутствовали 6 из 11 модулей), ASCII-диаграмма концепции заменена Mermaid.
- `docs/DEV_ARCH.md` — шесть ASCII-диаграмм заменены Mermaid (`graph TB`, `flowchart`, `sequenceDiagram`); §2 разбит на две отдельные диаграммы (физические связи + состав инструментов).
- `docs/HOW_TO_FLASH.md` — ASCII bash-поток USB SDP заменён Mermaid `flowchart TD`.
- `docs/HOW_TO_DEBUG.md` — ASCII топология хост/devcontainer заменена Mermaid `flowchart LR`.
- `docs/testing/PROTOCOL.md` — четыре ASCII-блока (стенд, жизненный цикл, три интерактивных теста, state machine) заменены Mermaid (`flowchart`, `sequenceDiagram`, `stateDiagram-v2`).
- `docs/testing/host/HOST_CREATE_TEST.md` — ASCII стек заменён Mermaid.
- `docs/testing/hil/HIL_CREATE_TEST.md` — ASCII стек и цепочка фикстур заменены Mermaid.
- `README.md` (корневой) — убрана устаревшая таблица BSP с 5 из 11 модулями, заменена ссылкой на `bsp/README.md`; исправлено описание `tft_app`.
- `utils/README.md` — добавлен пропущенный модуль `prio_queue`, добавлен раздел CMake.
- `port/README.md` — переработан с Mermaid-диаграммой архитектуры слоёв.
- `port/log/README.md` — добавлена Mermaid-диаграмма потока, раздел FreeRTOS с реальным кодом.
### Тесты
- `test_buttons` hardware-verified: обнаружен и исправлен баг `cli.c``g_s_line_len` должен сбрасываться в `0` до вызова `process_line()`, иначе входящие байты корруптят буфер команды в blocking confirm-wait цикле.
- `test_display` hardware-verified: уточнена семантика ротации — `SHLR` управляет горизонтальным направлением (LR пин), `UPDN` — вертикальным (UD пин); enum использует семантические имена (`BSP_DISPLAY_ROTATE_0`, `BSP_DISPLAY_FLIP_VERTICAL` и др.).
### Отфильтрованный шум
- Нормализация форматирования внутри уже существующих документов без изменения содержания.
- Перестановки в порядке разделов там, где смысл не менялся.
## Базовый срез — 2026-05-13 ## Базовый срез — 2026-05-13
Диапазон: вся история репозитория от initial commit до `22f98c311a4564d8f18ed72d11635bf908046b1e` Диапазон: вся история репозитория от initial commit до `22f98c311a4564d8f18ed72d11635bf908046b1e`
### Кратко ### Кратко
- Репозиторий представляет собой firmware-монорепозиторий для платы на базе NXP MIMXRT1052CVJ5B. Он охватывает manufacturing-test firmware, планируемые bootloader/application firmware, BSP-модули, host tooling, HIL tooling и документацию. - Репозиторий представляет собой firmware-монорепозиторий для платы на базе NXP MIMXRT1052CVJ5B. Он охватывает manufacturing-test firmware, планируемые bootloader/application firmware, BSP-модули, host tooling, HIL tooling и документацию.
- К базовому срезу в проекте уже есть полноценный BSP-слой, двухуровневая стратегия тестирования, USB-CDC архитектура тестовой прошивки, GitHub Actions CI и обширная инженерная документация. - К базовому срезу в проекте уже есть полноценный BSP-слой, двухуровневая стратегия тестирования, USB-CDC архитектура тестовой прошивки, GitHub Actions CI и обширная инженерная документация.
### Текущая архитектура ### Текущая архитектура
- BSP-модули на момент базового среза: `led`, `tick`, `uart_host`, `opto`, `can`, `button`, `usb_cdc`, `sdram`, `qspi_flash`, `sd`, `display`. - BSP-модули на момент базового среза: `led`, `tick`, `uart_host`, `opto`, `can`, `button`, `usb_cdc`, `sdram`, `qspi_flash`, `sd`, `display`.
- Host-тесты используют Unity/fff-подобные моки и инструменты, рассчитанные на devcontainer. - Host-тесты используют Unity/fff-подобные моки и инструменты, рассчитанные на devcontainer.
- HIL-тесты используют pyOCD, pyserial, pytest и поддержку M5StampPLC. - HIL-тесты используют pyOCD, pyserial, pytest и поддержку M5StampPLC.
@ -78,98 +118,121 @@
- Документация покрывает архитектуру разработки, прошивку, отладку, HAB, HIL, host-тесты и поведение протокола. - Документация покрывает архитектуру разработки, прошивку, отладку, HAB, HIL, host-тесты и поведение протокола.
### Состояние CI ### Состояние CI
- GitHub Actions уже есть и собирает проект внутри devcontainer. - GitHub Actions уже есть и собирает проект внутри devcontainer.
- Видимый пробел: host unit tests и HIL tests пока не входят в GitHub Actions workflow. - Видимый пробел: host unit tests и HIL tests пока не входят в GitHub Actions workflow.
## [2026-05-08] — Display test module и выравнивание документации ## [2026-05-08] — Display test module и выравнивание документации
### Кратко ### Кратко
- Базовый срез заканчивается тем, что поддержка display становится частью и BSP, и manufacturing-test firmware. - Базовый срез заканчивается тем, что поддержка display становится частью и BSP, и manufacturing-test firmware.
- README и инженерные документы были синхронизированы между несколькими модулями, поэтому это documentation-heavy, но смысловое изменение состояния проекта. - README и инженерные документы были синхронизированы между несколькими модулями, поэтому это documentation-heavy, но смысловое изменение состояния проекта.
### Добавлено ### Добавлено
- Реализован `bsp/display` с API, исходниками и интеграцией в сборку. - Реализован `bsp/display` с API, исходниками и интеграцией в сборку.
- Добавлен `firmware/test/src/tests/test_display.c` как firmware-side test module для display. - Добавлен `firmware/test/src/tests/test_display.c` как firmware-side test module для display.
### Изменено ### Изменено
- README нескольких BSP-модулей и manufacturing-test firmware приведены к более единому стилю. - README нескольких BSP-модулей и manufacturing-test firmware приведены к более единому стилю.
- `docs/DEV_ARCH.md`, документы по прошивке/отладке и HIL-гайды обновлены под актуальную структуру проекта. - `docs/DEV_ARCH.md`, документы по прошивке/отладке и HIL-гайды обновлены под актуальную структуру проекта.
- `just/ci_workflow.md` расширен дополнительными деталями CI workflow. - `just/ci_workflow.md` расширен дополнительными деталями CI workflow.
### Документация ### Документация
- Документация по display, SD, CAN, button, USB-CDC и firmware-test получила содержательные обновления, а не только форматирование. - Документация по display, SD, CAN, button, USB-CDC и firmware-test получила содержательные обновления, а не только форматирование.
### Удалено ### Удалено
- Удалён `bsp/qspi_flash/REFACTORING.md`, так как временный план потерял актуальность. - Удалён `bsp/qspi_flash/REFACTORING.md`, так как временный план потерял актуальность.
### Отфильтрованный шум ### Отфильтрованный шум
- Чистая нормализация стиля в документации не учитывалась как отдельная feature, если она не меняла содержание или проектные инструкции. - Чистая нормализация стиля в документации не учитывалась как отдельная feature, если она не меняла содержание или проектные инструкции.
## [2026-05-07] — USD/SD test flow ## [2026-05-07] — USD/SD test flow
### Кратко ### Кратко
- SD/MMC testing перешёл от BSP/middleware-работ к firmware-test module с обновлением протокола и тестовой документации. - SD/MMC testing перешёл от BSP/middleware-работ к firmware-test module с обновлением протокола и тестовой документации.
### Добавлено ### Добавлено
- Добавлен `firmware/test/src/tests/test_usd.c`, затем доведён до usable SD/MMC test module. - Добавлен `firmware/test/src/tests/test_usd.c`, затем доведён до usable SD/MMC test module.
### Изменено ### Изменено
- `firmware/test/src/main.c` упрощён по мере модульного оформления test modules. - `firmware/test/src/main.c` упрощён по мере модульного оформления test modules.
- `docs/testing/PROTOCOL.md`, `firmware/test/src/tests/README.md` и `firmware/test/PLAN.md` обновлены под USD/SD test coverage. - `docs/testing/PROTOCOL.md`, `firmware/test/src/tests/README.md` и `firmware/test/PLAN.md` обновлены под USD/SD test coverage.
### Тесты ### Тесты
- Firmware-side SD/MMC testing стал видимым отдельным модулем, хотя базовый срез всё ещё требует отслеживать стабильное HIL-покрытие SD. - Firmware-side SD/MMC testing стал видимым отдельным модулем, хотя базовый срез всё ещё требует отслеживать стабильное HIL-покрытие SD.
## [2026-05-06] — SD card и FatFS integration ## [2026-05-06] — SD card и FatFS integration
### Кратко ### Кратко
- Поддержка SD card и FatFS вошла в BSP и firmware-test stack. - Поддержка SD card и FatFS вошла в BSP и firmware-test stack.
- NXP SD middleware был пропатчен, что создаёт будущую точку контроля для vendor SDK drift. - NXP SD middleware был пропатчен, что создаёт будущую точку контроля для vendor SDK drift.
### Добавлено ### Добавлено
- Добавлен `bsp/sd` с API, реализацией, README и CMake-интеграцией. - Добавлен `bsp/sd` с API, реализацией, README и CMake-интеграцией.
- Добавлены `firmware/test/fatfs` и `port/fatfs/sd` для подключения FatFS к SD BSP. - Добавлены `firmware/test/fatfs` и `port/fatfs/sd` для подключения FatFS к SD BSP.
- Добавлены generated SDMMC configuration files. - Добавлены generated SDMMC configuration files.
### Изменено ### Изменено
- SDK SD middleware и SDMMC host code изменены для поддержки smoke-test path. - SDK SD middleware и SDMMC host code изменены для поддержки smoke-test path.
- Clock и pin configuration скорректированы под SD/MMC. - Clock и pin configuration скорректированы под SD/MMC.
### Тесты ### Тесты
- Появился SD smoke-test, но стабильное HIL regression coverage ещё не закреплено в базовом срезе. - Появился SD smoke-test, но стабильное HIL regression coverage ещё не закреплено в базовом срезе.
### Отфильтрованный шум ### Отфильтрованный шум
- `project_tree.txt` и `sdk/sdk_tree.txt` рассматривались как artifacts состояния репозитория, а не как функциональные изменения. - `project_tree.txt` и `sdk/sdk_tree.txt` рассматривались как artifacts состояния репозитория, а не как функциональные изменения.
## [2026-04-23] — Добавлен GitHub Actions CI ## [2026-04-23] — Добавлен GitHub Actions CI
### Кратко ### Кратко
- CI стал реальным: проект получил GitHub Actions workflow, который собирает проект внутри devcontainer. - CI стал реальным: проект получил GitHub Actions workflow, который собирает проект внутри devcontainer.
- Workflow ориентирован на сборку; host tests и HIL tests остаются будущей работой. - Workflow ориентирован на сборку; host tests и HIL tests остаются будущей работой.
### Добавлено ### Добавлено
- Добавлен `.github/workflows/ci.yml` с triggers на push, PR и manual dispatch. - Добавлен `.github/workflows/ci.yml` с triggers на push, PR и manual dispatch.
- Workflow собирает devcontainer image, запускает `just ci::build` и загружает build tree как artifact. - Workflow собирает devcontainer image, запускает `just ci::build` и загружает build tree как artifact.
- Добавлен `just/ci_workflow.md` с описанием CI flow. - Добавлен `just/ci_workflow.md` с описанием CI flow.
### Изменено ### Изменено
- `just/ci.just` скорректирован под CI build path. - `just/ci.just` скорректирован под CI build path.
### CI ### CI
- CI покрывает воспроизводимость containerized build. - CI покрывает воспроизводимость containerized build.
- CI пока не запускает host unit tests. - CI пока не запускает host unit tests.
- CI пока не запускает HIL tests, которым требуется подключённое железо или self-hosted runner. - CI пока не запускает HIL tests, которым требуется подключённое железо или self-hosted runner.
### Удалено ### Удалено
- Удалён `.clang-tidy` override для generated code из `bsp/generated`. - Удалён `.clang-tidy` override для generated code из `bsp/generated`.
## [2026-04-20 .. 2026-04-22] — SDRAM и QSPI firmware-test modules ## [2026-04-20 .. 2026-04-22] — SDRAM и QSPI firmware-test modules
### Кратко ### Кратко
- Manufacturing-test firmware получил memory-oriented test modules для SDRAM и QSPI Flash. - Manufacturing-test firmware получил memory-oriented test modules для SDRAM и QSPI Flash.
- QSPI support появился и как BSP module, и как firmware-side test. - QSPI support появился и как BSP module, и как firmware-side test.
### Добавлено ### Добавлено
- Добавлены API и реализация `bsp/sdram`. - Добавлены API и реализация `bsp/sdram`.
- Добавлен `firmware/test/src/tests/test_sdram.c`. - Добавлен `firmware/test/src/tests/test_sdram.c`.
- Добавлен `bsp/qspi_flash` с API, реализацией и README. - Добавлен `bsp/qspi_flash` с API, реализацией и README.
@ -177,21 +240,26 @@
- Добавлен `firmware/test/src/tests/README.md` с описанием firmware-side test modules. - Добавлен `firmware/test/src/tests/README.md` с описанием firmware-side test modules.
### Изменено ### Изменено
- `firmware/test/src/main.c` обновлён для интеграции новых test modules. - `firmware/test/src/main.c` обновлён для интеграции новых test modules.
### Тесты ### Тесты
- SDRAM и QSPI вошли в firmware-test command model. - SDRAM и QSPI вошли в firmware-test command model.
### Документация ### Документация
- Документация по test modules начала описывать растущий firmware-test suite. - Документация по test modules начала описывать растущий firmware-test suite.
## [2026-04-17] — Binary protocol, test runner и hardware documentation ## [2026-04-17] — Binary protocol, test runner и hardware documentation
### Кратко ### Кратко
- Manufacturing-test firmware перешёл от простого CLI к protocol-driven test runner architecture. - Manufacturing-test firmware перешёл от простого CLI к protocol-driven test runner architecture.
- Hardware reference documentation существенно расширилась. - Hardware reference documentation существенно расширилась.
### Добавлено ### Добавлено
- `protocol.c/.h` ввели binary protocol для управления тестами. - `protocol.c/.h` ввели binary protocol для управления тестами.
- `test_module.h` и `test_runner.c/.h` ввели modular firmware-test runner. - `test_module.h` и `test_runner.c/.h` ввели modular firmware-test runner.
- Добавлены host tests для protocol и runner behavior. - Добавлены host tests для protocol и runner behavior.
@ -199,69 +267,85 @@
- Добавлены hardware PDFs и board/display reference materials в документацию. - Добавлены hardware PDFs и board/display reference materials в документацию.
### Изменено ### Изменено
- `firmware/test/README.md` сильно переписан под новую архитектуру. - `firmware/test/README.md` сильно переписан под новую архитектуру.
- Existing CLI host tests были расширены. - Existing CLI host tests были расширены.
### Тесты ### Тесты
- Host coverage расширился на protocol, runner и CLI behavior. - Host coverage расширился на protocol, runner и CLI behavior.
### Удалено ### Удалено
- Старые planning/architecture artifacts в `firmware/test` и HIL refactor notes удалены после замены новой структурой. - Старые planning/architecture artifacts в `firmware/test` и HIL refactor notes удалены после замены новой структурой.
### Отфильтрованный шум ### Отфильтрованный шум
- Большие добавления hardware PDF сведены по назначению, без перечисления каждого файла. - Большие добавления hardware PDF сведены по назначению, без перечисления каждого файла.
## [2026-04-07 .. 2026-04-09] — USB-CDC CLI и priority queue ## [2026-04-07 .. 2026-04-09] — USB-CDC CLI и priority queue
### Кратко ### Кратко
- Test firmware получил modular USB-CDC CLI. - Test firmware получил modular USB-CDC CLI.
- Добавлен priority queue utility и host coverage для него. - Добавлен priority queue utility и host coverage для него.
### Добавлено ### Добавлено
- `firmware/test/src/cli.c` и `cli.h` ввели отдельный CLI module. - `firmware/test/src/cli.c` и `cli.h` ввели отдельный CLI module.
- `tests/host/cli/test_cli.c` добавил host coverage для CLI behavior. - `tests/host/cli/test_cli.c` добавил host coverage для CLI behavior.
- Добавлен `utils/prio_queue` с README и большим host test. - Добавлен `utils/prio_queue` с README и большим host test.
- Появилась placeholder structure для `bsp/display`. - Появилась placeholder structure для `bsp/display`.
### Изменено ### Изменено
- Старый monolithic `firmware/test/main.c` path заменён на modular source layout. - Старый monolithic `firmware/test/main.c` path заменён на modular source layout.
- Добавлены firmware-test planning docs вокруг CLI/protocol roadmap. - Добавлены firmware-test planning docs вокруг CLI/protocol roadmap.
### Тесты ### Тесты
- Host tests покрыли CLI и priority queue. - Host tests покрыли CLI и priority queue.
## [2026-04-03 .. 2026-04-06] — USB-CDC stack и HIL coverage ## [2026-04-03 .. 2026-04-06] — USB-CDC stack и HIL coverage
### Кратко ### Кратко
- USB-CDC ACM стал реальной BSP capability и был подключён к HIL validation. - USB-CDC ACM стал реальной BSP capability и был подключён к HIL validation.
- HIL configuration и documentation были уточнены вокруг нового USB path. - HIL configuration и documentation были уточнены вокруг нового USB path.
### Добавлено ### Добавлено
- Реализован `bsp/usb_cdc` с API, descriptors, Chapter 9 handling и hardware wrappers. - Реализован `bsp/usb_cdc` с API, descriptors, Chapter 9 handling и hardware wrappers.
- SDK USB middleware integration добавлена в сборку. - SDK USB middleware integration добавлена в сборку.
- `tests/target/hil_usb_cdc` и `tools/hil/05_test_usb_cdc.py` добавили USB-CDC HIL coverage. - `tests/target/hil_usb_cdc` и `tools/hil/05_test_usb_cdc.py` добавили USB-CDC HIL coverage.
### Изменено ### Изменено
- Обработка HIL configuration была отрефакторена, `.env.example` получил новые переменные. - Обработка HIL configuration была отрефакторена, `.env.example` получил новые переменные.
- `bsp/usb_cdc/README.md` переписан с фокусом на API documentation. - `bsp/usb_cdc/README.md` переписан с фокусом на API documentation.
### Тесты ### Тесты
- USB-CDC вошёл в numbered HIL suite после UART, opto, CAN и button. - USB-CDC вошёл в numbered HIL suite после UART, opto, CAN и button.
### Документация ### Документация
- HIL docs, включая bench, creation guide, fixtures и how-to, были обновлены. - HIL docs, включая bench, creation guide, fixtures и how-to, были обновлены.
## [2026-04-01 .. 2026-04-02] — Clock, pin и MPU setup ## [2026-04-01 .. 2026-04-02] — Clock, pin и MPU setup
### Кратко ### Кратко
- Board generated files стали полнее: в firmware base вошли clock, pin и MPU configuration. - Board generated files стали полнее: в firmware base вошли clock, pin и MPU configuration.
### Добавлено ### Добавлено
- Добавлены generated clock и pin-mux configuration files. - Добавлены generated clock и pin-mux configuration files.
- Добавлен `TFT_Board.mex` как project state NXP Config Tools. - Добавлен `TFT_Board.mex` как project state NXP Config Tools.
- Появились initial empty `bsp/sdram` placeholders. - Появились initial empty `bsp/sdram` placeholders.
### Изменено ### Изменено
- Board startup/configuration получил MPU initialization. - Board startup/configuration получил MPU initialization.
- Linker scripts скорректированы для FlexSPI NOR и RAM layout. - Linker scripts скорректированы для FlexSPI NOR и RAM layout.
- `firmware/test/main.c` упрощён вокруг нового initialization path. - `firmware/test/main.c` упрощён вокруг нового initialization path.
@ -269,94 +353,116 @@
## [2026-03-30 .. 2026-03-31] — CAN и button BSP с host/HIL tests ## [2026-03-30 .. 2026-03-31] — CAN и button BSP с host/HIL tests
### Кратко ### Кратко
- CAN и button support стали полноценными BSP modules с host и HIL validation. - CAN и button support стали полноценными BSP modules с host и HIL validation.
- HIL tests были пронумерованы в ordered suite. - HIL tests были пронумерованы в ordered suite.
### Добавлено ### Добавлено
- Добавлен `bsp/can` с API, реализацией, README и mocks. - Добавлен `bsp/can` с API, реализацией, README и mocks.
- Добавлены host tests и HIL target/test code для CAN. - Добавлены host tests и HIL target/test code для CAN.
- Добавлен `bsp/button` с API, реализацией и README. - Добавлен `bsp/button` с API, реализацией и README.
- Добавлены host tests и HIL target/test code для button. - Добавлены host tests и HIL target/test code для button.
### Изменено ### Изменено
- HIL pytest files переименованы в ordered sequence: UART, opto, CAN, button. - HIL pytest files переименованы в ordered sequence: UART, opto, CAN, button.
- HIL documentation обновлена по мере конкретизации test suite. - HIL documentation обновлена по мере конкретизации test suite.
### Тесты ### Тесты
- CAN и button получили host test coverage. - CAN и button получили host test coverage.
- CAN и button получили HIL coverage. - CAN и button получили HIL coverage.
### Удалено ### Удалено
- Удалён `bsp/can/PLAN.md` после переноса полезного содержания в README. - Удалён `bsp/can/PLAN.md` после переноса полезного содержания в README.
## [2026-03-26 .. 2026-03-28] — M5StampPLC HIL bench и fixture documentation ## [2026-03-26 .. 2026-03-28] — M5StampPLC HIL bench и fixture documentation
### Кратко ### Кратко
- HIL стал больше чем pyOCD prototype: появились M5StampPLC support, power/control helpers и fixture documentation. - HIL стал больше чем pyOCD prototype: появились M5StampPLC support, power/control helpers и fixture documentation.
### Добавлено ### Добавлено
- Добавлена поддержка M5StampPLC в `tools/hil`, включая agent/CLI logic и MicroPython firmware assets. - Добавлена поддержка M5StampPLC в `tools/hil`, включая agent/CLI logic и MicroPython firmware assets.
- HIL support code реорганизован в M5-specific helpers. - HIL support code реорганизован в M5-specific helpers.
- `tests/target/hil_opto` и `tools/hil/test_opto.py` добавили opto HIL coverage. - `tests/target/hil_opto` и `tools/hil/test_opto.py` добавили opto HIL coverage.
- `docs/testing/hil/HIL_FIXTURES.md` описал pytest fixtures для HIL. - `docs/testing/hil/HIL_FIXTURES.md` описал pytest fixtures для HIL.
### Изменено ### Изменено
- `just/host.just` и `tools/hil/conftest.py` существенно расширены под HIL workflows. - `just/host.just` и `tools/hil/conftest.py` существенно расширены под HIL workflows.
- Документация реорганизована в более понятные иерархии hardware, MIMXRT1052 и testing. - Документация реорганизована в более понятные иерархии hardware, MIMXRT1052 и testing.
### Тесты ### Тесты
- Opto inputs получили HIL-level validation. - Opto inputs получили HIL-level validation.
### Удалено ### Удалено
- Старые locations CMake/test guides заменены новой иерархией `docs/testing`. - Старые locations CMake/test guides заменены новой иерархией `docs/testing`.
### Отфильтрованный шум ### Отфильтрованный шум
- Перемещения PDF и документов учтены как изменение структуры документации, а не как отдельное content change для каждого файла. - Перемещения PDF и документов учтены как изменение структуры документации, а не как отдельное content change для каждого файла.
## [2026-03-23] — Logging infrastructure и BSP opto ## [2026-03-23] — Logging infrastructure и BSP opto
### Кратко ### Кратко
- Появились logging infrastructure и opto input BSP вместе с host coverage. - Появились logging infrastructure и opto input BSP вместе с host coverage.
### Добавлено ### Добавлено
- `port/log` и `utils/log` ввели logging abstractions и UART-oriented logging support. - `port/log` и `utils/log` ввели logging abstractions и UART-oriented logging support.
- Добавлены host tests для logging behavior. - Добавлены host tests для logging behavior.
- Добавлен `bsp/opto` с API, реализацией, README, GPIO mocks и host tests. - Добавлен `bsp/opto` с API, реализацией, README, GPIO mocks и host tests.
### Тесты ### Тесты
- Host coverage расширился на logging и opto behavior. - Host coverage расширился на logging и opto behavior.
### Удалено ### Удалено
- Корневой `TODO.md` удалён после переноса планирования в другие места. - Корневой `TODO.md` удалён после переноса планирования в другие места.
## [2026-03-18 .. 2026-03-20] — Первый HIL skeleton и flashing/debug docs ## [2026-03-18 .. 2026-03-20] — Первый HIL skeleton и flashing/debug docs
### Кратко ### Кратко
- Проект получил первый HIL skeleton и target-side UART validation path. - Проект получил первый HIL skeleton и target-side UART validation path.
- Flashing и debugging tooling стали документированными и scriptable. - Flashing и debugging tooling стали документированными и scriptable.
### Добавлено ### Добавлено
- Добавлен `tools/hil` с pytest/pyOCD/pyserial-oriented utilities. - Добавлен `tools/hil` с pytest/pyOCD/pyserial-oriented utilities.
- `tests/target/host_uart` предоставил target firmware для UART HIL validation. - `tests/target/host_uart` предоставил target firmware для UART HIL validation.
- `tools/host/flash_swd.py` добавил SWD flashing support. - `tools/host/flash_swd.py` добавил SWD flashing support.
- Добавлены `docs/HOW_TO_DEBUG.md`, расширенные flash docs и tool README. - Добавлены `docs/HOW_TO_DEBUG.md`, расширенные flash docs и tool README.
### Изменено ### Изменено
- Main README и development architecture docs расширены вокруг host/container workflow и testing. - Main README и development architecture docs расширены вокруг host/container workflow и testing.
- Добавлены host и HIL test creation guides. - Добавлены host и HIL test creation guides.
### Тесты ### Тесты
- Появился первый UART HIL path. - Появился первый UART HIL path.
### Удалено ### Удалено
- Temporary flash logs удалены после окончания диагностической пользы. - Temporary flash logs удалены после окончания диагностической пользы.
## [2026-03-16 .. 2026-03-17] — Первые BSP modules и host test infrastructure ## [2026-03-16 .. 2026-03-17] — Первые BSP modules и host test infrastructure
### Кратко ### Кратко
- Репозиторий получил первые concrete BSP modules и host-test layout. - Репозиторий получил первые concrete BSP modules и host-test layout.
### Добавлено ### Добавлено
- Добавлены `bsp_led` и `bsp_tick` с API, реализацией и README files. - Добавлены `bsp_led` и `bsp_tick` с API, реализацией и README files.
- `bsp/uart_host` добавил LPUART1/MCU-Link VCOM support с API, реализацией, README, mocks и host tests. - `bsp/uart_host` добавил LPUART1/MCU-Link VCOM support с API, реализацией, README, mocks и host tests.
- Введены shared BSP status codes. - Введены shared BSP status codes.
@ -365,29 +471,36 @@
- `TODO-HIL.md` зафиксировал initial HIL plan. - `TODO-HIL.md` зафиксировал initial HIL plan.
### Тесты ### Тесты
- Host testing начался с Unity/fff-style mocks и isolated test directories. - Host testing начался с Unity/fff-style mocks и isolated test directories.
## [2026-03-13 .. 2026-03-15] — Draft архитектуры manufacturing-test firmware ## [2026-03-13 .. 2026-03-15] — Draft архитектуры manufacturing-test firmware
### Кратко ### Кратко
- Архитектура manufacturing-test firmware была задокументирована до последующей реализации protocol/runner. - Архитектура manufacturing-test firmware была задокументирована до последующей реализации protocol/runner.
### Добавлено ### Добавлено
- `firmware/test/README.md` и `firmware/test/arch.svg` описали первый architecture concept. - `firmware/test/README.md` и `firmware/test/arch.svg` описали первый architecture concept.
- BSP и USB-CDC README зафиксировали early design intent. - BSP и USB-CDC README зафиксировали early design intent.
### Изменено ### Изменено
- `bootstrap.sh` был упрощён. - `bootstrap.sh` был упрощён.
### Удалено ### Удалено
- Ранняя VS Code launch configuration удалена при cleanup bootstrap. - Ранняя VS Code launch configuration удалена при cleanup bootstrap.
## [2026-03-10 .. 2026-03-12] — Project environment, BSP base и HAB flow ## [2026-03-10 .. 2026-03-12] — Project environment, BSP base и HAB flow
### Кратко ### Кратко
- Проект перешёл от пустого scaffold к buildable embedded workspace с generated board support, HAB assets и containerized tooling. - Проект перешёл от пустого scaffold к buildable embedded workspace с generated board support, HAB assets и containerized tooling.
### Добавлено ### Добавлено
- Добавлен board support для MIMXRT1052: startup code, generated config, linker scripts и FlexSPI NOR-related assets. - Добавлен board support для MIMXRT1052: startup code, generated config, linker scripts и FlexSPI NOR-related assets.
- Добавлен HAB signing/configuration flow для app, bootloader и firmware-test images. - Добавлен HAB signing/configuration flow для app, bootloader и firmware-test images.
- Добавлены formatting/lint/editor configuration. - Добавлены formatting/lint/editor configuration.
@ -396,19 +509,23 @@
- Добавлена development architecture и CMake hints documentation. - Добавлена development architecture и CMake hints documentation.
### Изменено ### Изменено
- Generated NXP Config Tools content перенесён из `bsp/board` в `bsp/generated`. - Generated NXP Config Tools content перенесён из `bsp/board` в `bsp/generated`.
- Логика `Justfile` разделена на modules под `just/`. - Логика `Justfile` разделена на modules под `just/`.
- Добавлена flashing documentation, bootstrap logic переработана. - Добавлена flashing documentation, bootstrap logic переработана.
### Документация ### Документация
- Early docs явно отмечали, что CI и tests ещё не покрыты. - Early docs явно отмечали, что CI и tests ещё не покрыты.
## [2026-03-05] — Initial repository scaffold ## [2026-03-05] — Initial repository scaffold
### Кратко ### Кратко
- Репозиторий инициализирован с базовой metadata и README placeholder. - Репозиторий инициализирован с базовой metadata и README placeholder.
### Добавлено ### Добавлено
- Добавлены `.gitattributes`, `.gitignore` и начальный `README.md`. - Добавлены `.gitattributes`, `.gitignore` и начальный `README.md`.
## Отфильтрованный шум базового среза ## Отфильтрованный шум базового среза

View file

@ -1,59 +1,34 @@
# tft_manufacture_test # tft_manufacture_test
Монорепозиторий для **MIMXRT1052CVJ5B**. Содержит три независимых firmware-проекта с общей инфраструктурой сборки, тестирования и инструментарием. Монорепозиторий для **MIMXRT1052CVJ5B**. Содержит три независимых firmware-проекта
с общей инфраструктурой сборки, тестирования и инструментарием.
> Архитектура рабочего окружения — [docs/DEV_ARCH.md](docs/DEV_ARCH.md) > Архитектура рабочего окружения — [docs/DEV_ARCH.md](docs/DEV_ARCH.md)
--- ---
## Три firmware-проекта ## Firmware-проекты
| Проект | Путь | Описание | | Проект | Путь | Описание |
| ------------------- | ---------------------- | -------------------------------------------------------------------------------------- | | ------------------- | ---------------------- | -------------------------------------------------------------------------------------- |
| Тестовая прошивка | `firmware/test/` | Входной контроль платы: CAN, UART, SDRAM, QSPI, SDIO, RGB, оптовходы, LED, кнопки, MQS | | Тестовая прошивка | `firmware/test/` | Входной контроль платы: CAN, UART, SDRAM, QSPI, SDIO, RGB, оптовходы, LED, кнопки, MQS |
| Загрузчик | `firmware/bootloader/` | A/B обновление через uSD. Сам обновляется только через USB ROM + blhost / SWD | | Загрузчик | `firmware/bootloader/` | A/B обновление через uSD. Обновляется только через USB ROM + blhost / SWD |
| Production прошивка | `firmware/tft_app/` | FreeRTOS + FatFS + бизнес-логика. Обновляется загрузчиком | | Production прошивка | `firmware/tft_app/` | Приложение с реализацией логики лифтового индикатора. Обновляется загрузчиком |
--- ---
## BSP ## BSP
| Модуль | Путь | Описание | Описание модулей, правила написания компонентов и CMake-шаблоны — [bsp/README.md](bsp/README.md).
| --------------- | ---------------- | --------------------------------------------------------- |
| `bsp_led` | `bsp/led/` | Два UserLed (GPIO3[3], GPIO3[4]) |
| `bsp_tick` | `bsp/tick/` | SysTick / FreeRTOS-совместимый таймер |
| `bsp_uart_host` | `bsp/uart_host/` | LPUART1 — MCU-Link VCOM (J2) |
| `bsp_opto` | `bsp/opto/` | Оптоизолированные входы PS2801-4: EXT_IN1, EXT_IN2, RS_RX |
| `bsp_usb_cdc` | `bsp/usb_cdc/` | USB CDC ACM |
| generated | `bsp/generated/` | NXP Config Tools: pin_mux, clock_config, board, startup |
--- ---
## Тестирование ## Тестирование
| Уровень | Где | Инструменты | Запуск | | Уровень | Инструменты | Запуск |
| ---------------- | ------------------------------ | -------------------------------------- | -------------------------------------- | | ---------------- | -------------------------------------- | -------------------------------------- |
| Host unit-тесты | `tests/host/` | Unity + fff, clang | `just build::test-host` (devcontainer) | | Host unit-тесты | Unity + fff, clang | `just build::test-host` (devcontainer) |
| HIL target-тесты | `tests/target/` + `tools/hil/` | pyOCD + pyserial + pytest + M5StampPLC | `just host::hil-run` (хост) | | HIL target-тесты | pyOCD + pyserial + pytest + M5StampPLC | `just host::hil-run` (хост) |
**Host-тесты** запускаются в devcontainer без железа. BSP-модули тестируются через fff-фейки и stub-хедеры.
**HIL-тесты** — каждый тест это пара: C-прошивка с UART CLI (`tests/target/<n>/`) и pytest-файл (`tools/hil/NN_test_<n>.py`). pyOCD загружает ELF в RAM через MCU-Link. Тесты с внешними сигналами управляются через M5StampPLC (реле → оптовходы таргета).
Фактический набор HIL-тестов (`tools/hil/`):
| Файл | Назначение |
| -------------------- | ----------------------------------------- |
| `01_test_uart.py` | UART CLI / MCU-Link VCOM |
| `02_test_opto.py` | Оптовходы EXT_IN1, EXT_IN2, RS_RX |
| `03_test_can.py` | CAN-интерфейс |
| `04_test_button.py` | Пользовательские кнопки |
| `05_test_usb_cdc.py` | USB CDC ACM |
```bash
pytest → uart_cmd() → MCU-Link VCOM → RT1052
pytest → m5.opto_set() → M5StampPLC RLY → EXT_IN1/IN2/RS_RX → RT1052
```
- Как добавить host-тест — [docs/testing/host/HOST_CREATE_TEST.md](docs/testing/host/HOST_CREATE_TEST.md) - Как добавить host-тест — [docs/testing/host/HOST_CREATE_TEST.md](docs/testing/host/HOST_CREATE_TEST.md)
- Как добавить HIL-тест — [docs/testing/hil/HIL_CREATE_TEST.md](docs/testing/hil/HIL_CREATE_TEST.md) - Как добавить HIL-тест — [docs/testing/hil/HIL_CREATE_TEST.md](docs/testing/hil/HIL_CREATE_TEST.md)
@ -92,7 +67,8 @@ just host::debug-server # GDB-сервер для отладки
| pyOCD, pyserial, pytest, mpremote | `tools/hil/uv.lock` | | pyOCD, pyserial, pytest, mpremote | `tools/hil/uv.lock` |
| spsdk (nxpimage, blhost, sdphost) | `tools/host/uv.lock` | | spsdk (nxpimage, blhost, sdphost) | `tools/host/uv.lock` |
Всё что не меняется — vendored. Сборка работает после `git clone` без интернета (кроме Python-зависимостей). Всё что не меняется — vendored. Сборка работает после `git clone` без интернета
(кроме Python-зависимостей).
--- ---

View file

@ -7,20 +7,17 @@
## Концепция ## Концепция
BSP — единственное место в монорепо где есть знание о конкретном железе. Все три прошивки работают с периферией только через BSP. Прямых вызовов NXP SDK (`fsl_*.h`) за пределами `bsp/` быть не должно. BSP — единственное место в монорепо где есть знание о конкретном железе. Все три
прошивки работают с периферией только через BSP. Прямых вызовов NXP SDK (`fsl_*.h`)
за пределами `bsp/` быть не должно.
```bash ```mermaid
firmware/test firmware/bootloader firmware/tft_app graph TB
↓ ↓ ↓ FW["firmware/test · firmware/bootloader · firmware/tft_app"]
┌─────────────────────────────────────────────────────┐ BSP["BSP"]
│ BSP │ SDK["NXP SDK / middleware\nfsl_lpuart · fsl_gpio · fsl_iomuxc · …"]
│ bsp_led bsp_opto bsp_tick bsp_uart_host ... │
└─────────────────────────────────────────────────────┘ FW --> BSP --> SDK
↓ ↓ ↓
┌─────────────────────────────────────────────────────┐
│ NXP SDK / middleware │
│ fsl_lpuart fsl_gpio fsl_iomuxc ... │
└─────────────────────────────────────────────────────┘
``` ```
--- ---
@ -74,9 +71,9 @@ target_link_libraries(bsp_<любой_компонент> PUBLIC bsp_board)
### Boot-стратегии — INTERFACE-библиотеки ### Boot-стратегии — INTERFACE-библиотеки
| Таргет CMake | Сценарий | Кто использует | | Таргет CMake | Сценарий | Кто использует |
|---|---|---| | -------------- | ------------------------- | ------------------------------------- |
| `bsp_boot_xip` | XIP — исполнение из Flash | `firmware/test`, `firmware/tft_app` | | `bsp_boot_xip` | XIP — исполнение из Flash | `firmware/test`, `firmware/tft_app` |
| `bsp_boot_ram` | исполнение из ITCM/DTCM | HIL target-прошивки (`tests/target/`) | | `bsp_boot_ram` | Исполнение из ITCM/DTCM | HIL target-прошивки (`tests/target/`) |
Подключается явно в каждом проекте: Подключается явно в каждом проекте:
@ -88,7 +85,7 @@ target_link_libraries(test_hil_opto PRIVATE bsp_board bsp_boot_ram ...)
### Компоненты периферии ### Компоненты периферии
| Библиотека | Модуль | README | | Библиотека | Модуль | README |
|---|---|---| | ---------------- | ------------- | -------------------------------------------- |
| `bsp_led` | `led/` | [led/README.md](led/README.md) | | `bsp_led` | `led/` | [led/README.md](led/README.md) |
| `bsp_tick` | `tick/` | [tick/README.md](tick/README.md) | | `bsp_tick` | `tick/` | [tick/README.md](tick/README.md) |
| `bsp_uart_host` | `uart_host/` | [uart_host/README.md](uart_host/README.md) | | `bsp_uart_host` | `uart_host/` | [uart_host/README.md](uart_host/README.md) |
@ -107,7 +104,8 @@ target_link_libraries(test_hil_opto PRIVATE bsp_board bsp_boot_ram ...)
### Граница изоляции ### Граница изоляции
Публичные заголовки (`include/bsp/*.h`) не должны содержать ни одного `#include` из NXP SDK. Снаружи BSP — только стандартные типы C и собственные типы проекта. Публичные заголовки (`include/bsp/*.h`) не должны содержать ни одного `#include`
из NXP SDK. Снаружи BSP — только стандартные типы C и собственные типы проекта.
```c ```c
/* ПРАВИЛЬНО — bsp/opto/include/bsp/opto.h */ /* ПРАВИЛЬНО — bsp/opto/include/bsp/opto.h */

View file

@ -1,49 +1,52 @@
# bsp_button — тактовые кнопки # bsp_button — тактовые кнопки (SWT6x6)
Чтение двух тактовых кнопок SWT6x6 с программным debounce. Чтение двух тактовых кнопок с программным дебаунсом. Предоставляет мгновенное
Предоставляет мгновенное сырое чтение, стабильное состояние и одноразовые сырое чтение, стабильное состояние и одноразовые события нажатия/отпускания.
события нажатия/отпускания.
--- ---
## Аппаратура ## Аппаратура
| Кнопка | Пин MCU | GPIO | Схема | Нажатие | | Идентификатор | Сигнал | Пин MCU | Корпус | GPIO | Нажатие |
| -------------- | ---------- | --------- | ----------------------------- | ------- | | -------------- | -------- | ---------- | ------ | --------- | ------- |
| `BSP_BUTTON_1` | GPIO_B1_14 | GPIO2[30] | SWT6x6, pull-up к 3V3 внешний | LOW | | `BSP_BUTTON_1` | TactBut1 | GPIO_B1_14 | C14 | GPIO2[30] | LOW |
| `BSP_BUTTON_2` | GPIO_B1_15 | GPIO2[31] | SWT6x6, pull-up к 3V3 внешний | LOW | | `BSP_BUTTON_2` | TactBut2 | GPIO_B1_15 | B14 | GPIO2[31] | LOW |
Пины настроены в `BOARD_InitPins()` (`generated/pin_mux.c`) как INPUT с включённым Пины настроены в `BOARD_InitPins()` как INPUT с гистерезисом, без внутренней
гистерезисом, без внутренней подтяжки (`0x0100B0`). `bsp_button_init()` не трогает подтяжки (`0x0100B0`) — внешний pull-up к 3V3. `bsp_button_init()` не трогает
GPIO — только сбрасывает внутреннее состояние модуля. GPIO — только сбрасывает внутреннее состояние модуля.
--- ---
## Принцип работы ## Архитектура
```bash ```mermaid
GPIO2[30] / GPIO2[31] flowchart LR
A["GPIO2[30] / GPIO2[31]"] --> B["GPIO_ReadPinInput()\nв bsp_button_poll()"]
GPIO_ReadPinInput() ← вызывается в bsp_button_poll() B --> C["счётчик дебаунса\n4 одинаковых сэмпла = 20 мс"]
C --> D["stable state\nevt_pressed / evt_released"]
debounce (счётчик) ← 4 одинаковых сэмпла подряд = 20 мс D --> E["bsp_button_is_pressed()\nbsp_button_get_event_pressed()\nbsp_button_get_event_released()"]
stable state + события ← evt_pressed / evt_released
bsp_button_is_pressed()
bsp_button_get_event_pressed()
bsp_button_get_event_released()
``` ```
- **Polling**`bsp_button_poll()` вызывается снаружи каждые 5 мс. Без прерываний. - **Polling**`bsp_button_poll()` вызывается каждые 5 мс. Без прерываний.
- **Debounce** — счётчик подтверждений: 4 одинаковых сэмпла подряд фиксируют - **Дебаунс** — 4 одинаковых сэмпла подряд (`BUTTON_DEBOUNCE_SAMPLES = 4`,
переход. Глитч (смена уровня до набора порога) сбрасывает счётчик. 4 × 5 мс = 20 мс). Глитч сбрасывает счётчик.
- **События** — одноразовые флаги `evt_pressed` / `evt_released`, сбрасываются - **События** — одноразовые флаги `evt_pressed`/`evt_released`, сбрасываются
при первом обращении через `get_event_*()`. при первом обращении через `get_event_*()`.
Максимальная задержка реакции = период поллинга = 5 мс. Для навигации по меню ---
и производственного теста этого достаточно — порог восприятия задержки UI
около 50100 мс. ## API
```c
void bsp_button_init(void);
void bsp_button_poll(void); /* вызывать каждые 5 мс */
bool bsp_button_read(bsp_button_t btn); /* сырое чтение без дебаунса */
bool bsp_button_is_pressed(bsp_button_t btn); /* стабильное состояние */
bool bsp_button_get_event_pressed(bsp_button_t btn); /* одноразовое, сбрасывается при чтении */
bool bsp_button_get_event_released(bsp_button_t btn);
```
--- ---
@ -58,167 +61,98 @@ bsp_button_init();
/* bare-metal — вызывать каждые 5 мс из tick-коллбэка: */ /* bare-metal — вызывать каждые 5 мс из tick-коллбэка: */
bsp_button_poll(); bsp_button_poll();
/* В основном цикле: */ /* В main loop: */
if (**bsp_button_get_event_pressed(BSP_BUTTON_1)**) { if (bsp_button_get_event_pressed(BSP_BUTTON_1)) {
/* однократное срабатывание по нажатию */ /* однократное срабатывание по нажатию */
} }
if (bsp_button_is_pressed(BSP_BUTTON_2)) { if (bsp_button_is_pressed(BSP_BUTTON_2)) {
/* кнопка удерживается */ /* кнопка удерживается */
} }
/* FreeRTOS — в таске: */
vTaskDelay(pdMS_TO_TICKS(5));
bsp_button_poll();
if (bsp_button_get_event_pressed(BSP_BUTTON_1)) {
xQueueSend(btn_queue, &btn_event, 0);
}
``` ```
### Startup check (bootloader) **Startup check (bootloader) — сырое чтение до инициализации:**
```c ```c
/* Сырое чтение без debounce — сразу после board_hw_init(), /* До bsp_button_init(), сразу после board_hw_init(): */
до вызова bsp_button_init(). */
if (bsp_button_read(BSP_BUTTON_1)) { if (bsp_button_read(BSP_BUTTON_1)) {
/* кнопка удерживается при старте → войти в режим обновления */ /* удерживается при старте → режим обновления */
} }
``` ```
--- ---
## API
### `bsp_button_init()`
Сбрасывает внутреннее состояние (счётчики debounce, флаги событий).
GPIO уже настроен в `BOARD_InitPins()` — вызывать после `board_hw_init()`.
### `bsp_button_read(btn)`
Мгновенное сырое чтение пина без debounce. Возвращает `true` если кнопка
нажата прямо сейчас. Предназначено для проверки при старте (bootloader hold-check).
### `bsp_button_poll()`
Один шаг debounce. Вызывать строго каждые 5 мс — из tick-коллбэка
(bare-metal) или таска (FreeRTOS). Обновляет стабильное состояние
и выставляет одноразовые события для обеих кнопок за один вызов.
### `bsp_button_is_pressed(btn)`
Стабильное состояние после debounce. `true` = кнопка удерживается нажатой.
Не сбрасывается при чтении.
### `bsp_button_get_event_pressed(btn)`
Одноразовое событие нажатия. Возвращает `true` один раз после того как
debounce зафиксировал переход в нажатое состояние. **Флаг сбрасывается при вызове.**
### `bsp_button_get_event_released(btn)`
Одноразовое событие отпускания. Возвращает `true` один раз после того как
debounce зафиксировал переход в отпущенное состояние. **Флаг сбрасывается при вызове.**
---
## Логика длинного/короткого нажатия
`bsp_button` намеренно не реализует логику длинного/короткого нажатия —
это интерпретация событий, зависящая от контекста приложения.
В `tft_app` реализуется отдельным модулем `button_handler` поверх событий BSP:
```bash
bsp_button → button_handler → app (меню, навигация)
```
`button_handler` хранит `press_start_ms`, использует `bsp_tick_get_ms()`
и вызывает коллбэк с типом действия (`SHORT_PRESS`, `LONG_PRESS`, `REPEAT`).
Не зависит от железа — тестируется на хосте как категория A (без fff).
---
## Конфигурация debounce
```c
/* bsp/button/src/button.c */
#define BUTTON_DEBOUNCE_SAMPLES 4U /* 4 × 5 мс = 20 мс */
```
При изменении периода поллинга нужно пересчитать `BUTTON_DEBOUNCE_SAMPLES`
чтобы сохранить целевое время debounce (рекомендуется 1530 мс).
---
## Тестирование ## Тестирование
### Host unit-тесты ### Host unit-тесты
Категория **B**`button.c` вызывает `GPIO_ReadPinInput()` из `fsl_gpio.h`. Категория **B**`button.c` вызывает `GPIO_ReadPinInput()` из `fsl_gpio.h`.
SDK-функция мокируется через fff в тестовом файле. SDK-функция мокируется через fff. Stub `fsl_gpio.h` в `tests/host/mocks/`.
Stub `fsl_gpio.h` уже существует в `tests/host/mocks/`.
```cmake ```bash
add_host_test( just build::test-host # покрытие: init, дебаунс нажатия/отпускания,
NAME test_bsp_button # потребление событий, сброс при глитче, независимость кнопок
SOURCES button/test_bsp_button.c
${CMAKE_SOURCE_DIR}/bsp/button/src/button.c
INCLUDES
${CMAKE_SOURCE_DIR}/bsp/button/include
${CMAKE_SOURCE_DIR}/bsp/common/include
MOCKS
${BSP_MOCKS_DIR}
)
``` ```
Покрытие: инициализация, сырое чтение, debounce нажатия/отпускания,
потребление событий, сброс счётчика при глитче, независимость кнопок,
граничные значения индекса.
### HIL-тест (интерактивный) ### HIL-тест (интерактивный)
Кнопки расположены на плате таргета — оператор нажимает вручную по подсказкам. Оператор нажимает кнопки вручную по подсказкам. Не входит в `hil-run`.
```bash ```bash
just host::hil-button # запускать отдельно, не входит в hil-run just host::hil-button
``` ```
C-прошивка: `tests/target/hil_button/` — CLI через `bsp_uart_host`. C-прошивка: `tests/target/hil_button/` — CLI через `bsp_uart_host`.
pytest: `tools/hil/04_test_button.py` — помечен `@pytest.mark.interactive`. pytest: `tools/hil/04_test_button.py` — помечен `@pytest.mark.interactive`.
Команды CLI прошивки:
| Команда | Ответ | Описание | | Команда | Ответ | Описание |
| --------------- | --------- | ------------------------------------- | | --------------- | --------- | ------------------------------------- |
| `PING` | `PONG` | Проверка канала | | `PING` | `PONG` | Проверка канала |
| `READ <idx>` | `1` / `0` | Сырое состояние (без debounce) | | `READ <idx>` | `1` / `0` | Сырое состояние без дебаунса |
| `STATE <idx>` | `1` / `0` | Стабильное состояние после debounce | | `STATE <idx>` | `1` / `0` | Стабильное состояние после дебаунса |
| `EVENT_P <idx>` | `1` / `0` | `get_event_pressed`, сбрасывает флаг | | `EVENT_P <idx>` | `1` / `0` | `get_event_pressed`, сбрасывает флаг |
| `EVENT_R <idx>` | `1` / `0` | `get_event_released`, сбрасывает флаг | | `EVENT_R <idx>` | `1` / `0` | `get_event_released`, сбрасывает флаг |
--- ---
## Зависимости ## Интеграция
| Зависимость | Тип | Описание | ```c
| ------------ | ------- | -------------------------------------------- | /* FreeRTOS — из задачи: */
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API | vTaskDelay(pdMS_TO_TICKS(5));
| `bsp_board` | PRIVATE | Транзитивно: `pin_mux.h`, clock, SDK headers | bsp_button_poll();
| `sdk_gpio` | PRIVATE | `fsl_gpio.h``GPIO_ReadPinInput()` | if (bsp_button_get_event_pressed(BSP_BUTTON_1)) {
xQueueSend(btn_queue, &btn_event, 0);
}
```
Логика длинного/короткого нажатия намеренно не реализована в BSP —
это зона ответственности `button_handler` в `tft_app`:
```bash
bsp_button → button_handler → app (меню, навигация)
```
`button_handler` хранит `press_start_ms`, вызывает коллбэк с типом
`SHORT_PRESS` / `LONG_PRESS` / `REPEAT`. Не зависит от железа — тестируется
на хосте как категория A.
--- ---
## Подключение ## CMake
```cmake ```cmake
# bsp/CMakeLists.txt
add_subdirectory(button)
# firmware/test/CMakeLists.txt или firmware/tft_app/CMakeLists.txt
target_link_libraries(firmware_test PRIVATE target_link_libraries(firmware_test PRIVATE
bsp_board bsp_board
bsp_tick bsp_tick
bsp_button bsp_button
) )
``` ```
**Зависимости модуля:**
| Зависимость | Тип | Описание |
| ------------ | ------- | -------------------------------------------- |
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
| `bsp_board` | PRIVATE | Транзитивно: `pin_mux.h`, clock, SDK headers |
| `sdk_gpio` | PRIVATE | `fsl_gpio.h``GPIO_ReadPinInput()` |

View file

@ -1,47 +1,93 @@
# bsp_can # bsp_can — FlexCAN2 (CAN 2.0)
Приём и передача CAN 2.0 фреймов через FlexCAN2 (трансивер SN65HVD230D, разъём P1 контакты 34). Приём и передача CAN 2.0 фреймов через FlexCAN2 (трансивер SN65HVD230D,
разъём P1 контакты 34). Применяется для обмена с управляющими модулями
по CAN-шине: приём команд, отправка откликов.
Применяется для обмена с управляющими модулями по CAN-шине: приём команд, ---
отправка откликов. Протоколы на стороне приложения — разный DLC, STD/EXT ID.
## Аппаратура
| Сигнал | Пин MCU | Корпус | Интерфейс | Примечание |
| ------- | ------------- | ------ | ----------- | --------------------------- |
| CAN2_TX | GPIO_AD_B0_14 | H14 | FlexCAN2 TX | Трансивер SN65HVD230D, P1/3 |
| CAN2_RX | GPIO_AD_B0_15 | L10 | FlexCAN2 RX | Трансивер SN65HVD230D, P1/4 |
Пины настроены в `BOARD_InitPins()` (`generated/pin_mux.c`).
**Распределение Message Buffers:**
| MB | Назначение |
| ---- | -------------------------------------------------- |
| 0 | Зарезервирован (ERR005829 workaround: inactive TX) |
| 1 | TX — отправка фреймов |
| 217 | RX — до 16 индивидуальных фильтров |
ERR005829 — errata FlexCAN на i.MX RT1050/1052: при гонке TX/RX арбитража
MB 0 может зависнуть. Workaround: MB 0 всегда `kFLEXCAN_TxMbInactive`.
--- ---
## Архитектура ## Архитектура
```bash ```mermaid
bsp_can_send() ← blocking polling + таймаут flowchart TD
subgraph TX
[FlexCAN2 TX MB1] → SN65HVD230D → CAN bus A["bsp_can_send()"] --> B["FLEXCAN_WriteTxMb()\nMB1"]
B --> C["polling FLEXCAN_GetMbStatusFlags()\nтаймаут"]
C --> D["SN65HVD230D → CAN bus"]
end
[CAN bus] → SN65HVD230D → [FlexCAN2 RX MB2..17] subgraph RX
E["CAN bus → SN65HVD230D"] --> F["FlexCAN2 RX MB217"]
poll_rx_mailboxes() ← опрос флагов MB F --> G["poll_rx_mailboxes()"]
G --> H["ring_buffer (внутренний FIFO)"]
ring_buffer ← внутренний FIFO H --> I["bsp_can_receive()"]
end
bsp_can_receive() ← polling + таймаут
``` ```
- **TX** — blocking polling с таймаутом. `bsp_can_send()` записывает фрейм - **TX** — blocking polling с таймаутом. Worst case при 500 kbit/s — ~260 мкс на фрейм.
в TX MB и ждёт флага завершения. Worst case при 500 kbit/s — ~260 мкс на фрейм. - **RX** — polling без прерываний. `bsp_can_receive()` опрашивает все активные
- **RX** — polling. `bsp_can_receive()` опрашивает все активные RX MB, RX MB, складывает фреймы в ring buffer, возвращает первый доступный.
складывает найденные фреймы во внутренний ring buffer, возвращает первый
доступный. Без прерываний.
- **Singleton** — один экземпляр, одна CAN-шина. - **Singleton** — один экземпляр, одна CAN-шина.
--- ---
## Распределение Message Buffers ## API
| MB | Назначение | ```c
| ----- | -------------------------------------------------- | bsp_status_t bsp_can_init(const bsp_can_config_t *p_cfg);
| 0 | Зарезервирован (ERR005829 workaround: inactive TX) |
| 1 | TX — отправка фреймов |
| 2..17 | RX — до 16 индивидуальных фильтров |
ERR005829 — errata FlexCAN на i.MX RT1050/1052: при гонке TX/RX арбитража bsp_status_t bsp_can_send(const bsp_can_frame_t *p_frame, uint32_t timeout_ms);
MB 0 может зависнуть. Workaround: MB 0 всегда `kFLEXCAN_TxMbInactive`. bsp_status_t bsp_can_receive(bsp_can_frame_t *p_frame, uint32_t timeout_ms);
bsp_status_t bsp_can_set_filter(uint8_t idx, uint32_t id,
uint32_t mask, bool is_extended);
bsp_status_t bsp_can_accept_all(void);
bsp_status_t bsp_can_register_rx_callback(bsp_can_rx_cb_t cb, void *p_ctx);
```
**Коды возврата `bsp_can_send()`:**
| Код | Условие |
| ----------------- | -------------------------------- |
| `BSP_OK` | Фрейм успешно отправлен |
| `BSP_ERR_BUSY` | TX MB занят предыдущей передачей |
| `BSP_ERR_TIMEOUT` | Истёк `timeout_ms` |
| `BSP_ERR_PARAM` | Невалидные параметры |
**Коды возврата `bsp_can_receive()`:**
| Код | Условие |
| ----------------- | -------------------- |
| `BSP_OK` | Фрейм получен |
| `BSP_ERR_TIMEOUT` | Истёк `timeout_ms` |
| `BSP_ERR_PARAM` | Невалидные параметры |
`bsp_can_register_rx_callback()` в текущей версии возвращает
`BSP_ERR_NOT_SUPPORTED` — API заложен для будущей интеграции с FreeRTOS
(ISR → `xQueueSendFromISR`).
--- ---
@ -54,7 +100,7 @@ MB 0 может зависнуть. Workaround: MB 0 всегда `kFLEXCAN_TxMb
bsp_can_config_t cfg = { .bitrate = 500000U }; bsp_can_config_t cfg = { .bitrate = 500000U };
bsp_can_init(&cfg); bsp_can_init(&cfg);
/* Принимать всё */ /* Принимать все фреймы */
bsp_can_accept_all(); bsp_can_accept_all();
/* TX — blocking, таймаут 500 мс */ /* TX — blocking, таймаут 500 мс */
@ -71,129 +117,31 @@ if (bsp_can_receive(&rx, 100) == BSP_OK) {
} }
``` ```
--- **Фильтрация:**
## Фильтрация
Каждый фильтр занимает один RX MB. Максимум 16 фильтров (`BSP_CAN_FILTER_MAX`).
```c ```c
/* Принимать только STD ID 0x200 с точным совпадением */ /* STD ID 0x200 — точное совпадение */
bsp_can_set_filter(0, 0x200, 0x7FF, false); bsp_can_set_filter(0, 0x200, 0x7FF, false);
/* Принимать STD ID 0x300..0x30F (маска 0x7F0, младшие 4 бита игнорируются) */ /* STD ID 0x3000x30F */
bsp_can_set_filter(1, 0x300, 0x7F0, false); bsp_can_set_filter(1, 0x300, 0x7F0, false);
/* Принимать EXT ID 0x1ABCDEF0 с точным совпадением */ /* EXT ID 0x1ABCDEF0 */
bsp_can_set_filter(2, 0x1ABCDEF0, 0x1FFFFFFF, true); bsp_can_set_filter(2, 0x1ABCDEF0, 0x1FFFFFFF, true);
/* Сбросить фильтры — принимать всё (STD + EXT) */
bsp_can_accept_all();
``` ```
`bsp_can_accept_all()` настраивает два MB: один для STD (маска 0), один Каждый фильтр занимает один RX MB. Максимум 16 фильтров (`BSP_CAN_FILTER_MAX`).
для EXT (маска 0). Все остальные MB деактивируются. `bsp_can_accept_all()` настраивает два MB (STD + EXT с маской 0),
деактивирует остальные.
---
## Блокирующее поведение
### TX: `bsp_can_send()`
Блокирующий вызов. Записывает фрейм в TX MB через `FLEXCAN_WriteTxMb()`,
затем ждёт флага завершения с polling `FLEXCAN_GetMbStatusFlags()`.
Возвращается по одному из условий:
| Условие | Возврат |
| -------------------------------- | ----------------- |
| Фрейм успешно отправлен | `BSP_OK` |
| TX MB занят предыдущей передачей | `BSP_ERR_BUSY` |
| Истёк `timeout_ms` | `BSP_ERR_TIMEOUT` |
| Невалидные параметры | `BSP_ERR_PARAM` |
При 500 kbit/s максимальное время отправки одного фрейма — ~260 мкс.
Для bare-metal и FreeRTOS-задачи это приемлемо.
### RX: `bsp_can_receive()`
Polling с таймаутом. Обходит все активные RX MB, читает готовые фреймы
во внутренний ring buffer, пытается извлечь один фрейм:
| Условие | Возврат |
| ------------------------------- | ----------------- |
| Фрейм найден (из буфера или MB) | `BSP_OK` |
| Истёк `timeout_ms` | `BSP_ERR_TIMEOUT` |
| Невалидные параметры | `BSP_ERR_PARAM` |
```c
/* Неблокирующий опрос — timeout_ms = 0 */
if (bsp_can_receive(&rx, 0) == BSP_OK) { /* есть фрейм */ }
/* Ожидание с таймаутом */
bsp_can_receive(&rx, 1000); /* ждать до 1 секунды */
```
---
## Callback-механизм (заглушка)
В первой итерации `bsp_can_register_rx_callback()` возвращает
`BSP_ERR_NOT_SUPPORTED`. API заложен для будущей интеграции с FreeRTOS:
```c
/* Будущее использование в firmware/tft_app: */
static void can_isr_to_queue(const bsp_can_frame_t *p_frame, void *p_ctx)
{
/* xQueueSendFromISR(...) */
}
bsp_can_register_rx_callback(can_isr_to_queue, NULL);
```
При реализации callback включит прерывания на RX MB. ISR читает фрейм
и вызывает callback напрямую. Callback **не должен блокироваться** — только
атомарные операции (флаг, очередь). Polling через `bsp_can_receive()`
отключается при активном callback.
---
## FreeRTOS
Модуль не зависит от FreeRTOS и работает в обоих контекстах:
| Контекст | TX | RX |
| --------------------------------- | ----------------------------------- | ---------------------------------------------- |
| bare-metal (`firmware/test`, HIL) | `bsp_can_send()` — blocking polling | `bsp_can_receive()` — polling |
| FreeRTOS (`firmware/tft_app`) | `bsp_can_send()` — из задачи | `bsp_can_receive()` — из задачи с `timeout_ms` |
Для FreeRTOS с минимальной латентностью — будущий callback + `xQueueSendFromISR()`.
Polling с `timeout_ms = 10` из задачи подходит для протоколов с интервалом > 10 мс.
---
## Подключение
```cmake
# bsp/CMakeLists.txt
add_subdirectory(can)
# firmware/test/CMakeLists.txt
target_link_libraries(firmware_test PRIVATE
bsp_board
bsp_tick
bsp_can
)
```
--- ---
## Тестирование ## Тестирование
### Host unit-тесты (тестирование логики bsp_can) ### Host unit-тесты
Категория **B** — модуль вызывает NXP SDK (`fsl_flexcan.h`). Категория **B**`bsp_can.c` вызывает `fsl_flexcan.h`. SDK-функции мокируются
SDK-функции мокаются через fff в тестовом файле. через fff. Stub `fsl_flexcan.h` в `tests/host/mocks/`.
Stubs: `fsl_flexcan.h`, `fsl_common.h`, `clock_config.h` в `tests/host/mocks/`.
```cmake ```cmake
add_host_test( add_host_test(
@ -205,39 +153,60 @@ add_host_test(
${PROJECT_SOURCE_DIR}/bsp/can/include ${PROJECT_SOURCE_DIR}/bsp/can/include
${PROJECT_SOURCE_DIR}/bsp/common/include ${PROJECT_SOURCE_DIR}/bsp/common/include
${PROJECT_SOURCE_DIR}/utils/ring_buffer ${PROJECT_SOURCE_DIR}/utils/ring_buffer
MOCKS MOCKS ${BSP_MOCKS_DIR}
${BSP_MOCKS_DIR}
) )
``` ```
### Humble Object (тестирование потребителей bsp_can) **Humble Object** — fff-заглушки для потребителей в `bsp/can/mocks/`:
Модуль предоставляет fff-заглушки в `bsp/can/mocks/`:
```c ```c
#include "fff.h"
DEFINE_FFF_GLOBALS;
#include "bsp/can.h"
#include "can_mock.h" #include "can_mock.h"
void setUp(void) { CAN_MOCK_RESET_ALL(); } void setUp(void) { CAN_MOCK_RESET_ALL(); }
void test_protocol_sends_response(void) { void test_protocol_sends_response(void) {
bsp_can_send_fake.return_val = BSP_OK; bsp_can_send_fake.return_val = BSP_OK;
/* ... вызываем protocol_handle_frame() ... */ /* ... */
TEST_ASSERT_EQUAL(1, bsp_can_send_fake.call_count); TEST_ASSERT_EQUAL(1, bsp_can_send_fake.call_count);
} }
``` ```
### HIL-тесты ### HIL-тесты
C-прошивка `tests/target/can/` с CLI через UART + pytest `tools/hil/test_can.py`. C-прошивка: `tests/target/hil_can/` — CLI через `bsp_uart_host`.
CAN-адаптер на стороне хоста — M5Stack с CAN-модулем. pytest: `tools/hil/03_test_can.py` — CAN-адаптер на M5Stack с CAN-модулем.
```bash
just host::hil-can
```
--- ---
## Зависимости ## Интеграция
Модуль работает в обоих контекстах без изменений:
| Контекст | TX | RX |
| ----------------------------- | ---------------------------- | ------------------------------------- |
| bare-metal (`firmware/test`) | `bsp_can_send()` — blocking | `bsp_can_receive()` — polling |
| FreeRTOS (`firmware/tft_app`) | `bsp_can_send()` — из задачи | `bsp_can_receive()` из задачи с yield |
Для FreeRTOS с минимальной латентностью — будущий callback + `xQueueSendFromISR()`.
---
## CMake
```cmake
# firmware/test/CMakeLists.txt
target_link_libraries(firmware_test PRIVATE
bsp_board
bsp_tick
bsp_can
)
```
**Зависимости модуля:**
| Зависимость | Тип | Описание | | Зависимость | Тип | Описание |
| -------------- | ------- | -------------------------------------- | | -------------- | ------- | -------------------------------------- |

View file

@ -1,96 +1,51 @@
# bsp_display — ELCDIF RGB-дисплей (TFT4 / TFT7 / TFT8 / TFT10) # bsp_display — ELCDIF RGB-дисплей (TFT7 / TFT8 / TFT4 / TFT10)
> Расположение: `bsp/display/` Инициализация ELCDIF в RGB-режиме, настройка пиксельного клока, управление
> Публичный заголовок: `bsp/display/include/bsp/display.h` GPIO подсветки и пинами ориентации/режима. Предоставляет API для смены
> Реализация: `bsp/display/src/display.c` фреймбуфера, поворота и нотификации о завершении кадра через ISR-safe callback.
Bare-metal совместим, без FreeRTOS.
Модуль инициализирует ELCDIF в RGB-режиме, настраивает пиксельный клок,
управляет GPIO подсветки и пинами ориентации/режима LR, UD, MODE, DITHB.
Предоставляет минимальное API для смены фреймбуфера, поворота и оповещения
о завершении кадра через callback. Bare-metal совместим (без FreeRTOS).
--- ---
## Аппаратный контекст ## Аппаратура
| Сигнал / параметр | Аппаратное назначение | **Управляющие GPIO:**
| ----------------- | ------------------------------------------------------------------------------------------------ |
| ELCDIF | NXP ELCDIF, RGB-режим, формат пикселя `kELCDIF_PixelFormatXRGB8888`, шина `kELCDIF_DataBus24Bit` |
| Подсветка | `GPIO1[20]` — active-high |
| LR (горизонт.) | `GPIO1[28]` (`Lcdlr_value`) |
| MODE | `GPIO1[29]` — HIGH = DE mode (обязательно для ELCDIF) |
| UD (вертикаль) | `GPIO1[30]` |
| DITHB | `GPIO1[31]` — HIGH = dithering disable (IC default) |
| Пиксельный клок | PLL2 (`mux=0`) для TFT7/TFT8; Video PLL (`mux=2`) для TFT4 |
| IRQ | `LCDIF_IRQHandler` в ITCM, приоритет `DISPLAY_IRQ_PRIORITY = 2` |
`IOMUXC` конфигурируется в `BOARD_InitPins()` за пределами модуля — здесь | Сигнал | Пин MCU | Корпус | GPIO | Назначение |
выполняется только `GPIO_PinWrite`. | ------------ | ------------- | ------ | --------- | ---------------------------- |
| LcdLed | GPIO_AD_B1_04 | L12 | GPIO1[20] | Подсветка, active-high |
| LcdLR (SHLR) | GPIO_AD_B1_12 | H12 | GPIO1[28] | Горизонтальное направление |
| LcdMode | GPIO_AD_B1_13 | H11 | GPIO1[29] | HIGH = DE mode (обязательно) |
| LcdUD (UPDN) | GPIO_AD_B1_14 | G12 | GPIO1[30] | Вертикальное направление |
| LcdDithb | GPIO_AD_B1_15 | J14 | GPIO1[31] | HIGH = dithering disable |
Делители пиксельного клока (исходный код, PLL2 = 528 МГц): **RGB-интерфейс ELCDIF:**
| Дисплей | clk_mux | pre_div | div | Эффективная частота | | Сигнал | Пины MCU | Примечание |
| ------- | --------- | -------- | --- | ------------------------------ | | ------------ | ---------------- | ------------------------------- |
| TFT7 | PLL2 | 2 | 4 | `528/3/5 = 35.2 МГц` | | LCDIF_CLK | GPIO_B0_00 (D7) | |
| TFT8 | PLL2 | 2 | 3 | `528/3/4 = 44.0 МГц` | | LCDIF_ENABLE | GPIO_B0_01 (E7) | |
| TFT4 | Video PLL | — (TODO) | — | требует `CLOCK_InitVideoPll` | | LCDIF_HSYNC | GPIO_B0_02 (E8) | |
| TFT10 | — | — | — | таблица не заполнена (`{ 0 }`) | | LCDIF_VSYNC | GPIO_B0_03 (D8) | |
| DATA[07] | GPIO_B0_04B0_11 | C8, B8, A8, A9, B9, C9, D9, A10 |
| DATA[815] | GPIO_B0_12B1_03 | |
| DATA[1623] | GPIO_B1_04B1_11 | |
Тайминги HSW/HFP/HBP/VSW/VFP/VBP заданы константами в `display.c` Пины настроены в `BOARD_InitPins()`. `LCDIF_IRQHandler` размещён в ITCM
(`DISPLAY_TFT7_*`, `DISPLAY_TFT8_*`, `DISPLAY_TFT4_*`). (`AT_QUICKACCESS_SECTION_CODE`), приоритет `DISPLAY_IRQ_PRIORITY = 2`.
**Пиксельный клок (PLL2 = 528 МГц):**
| Дисплей | clk_mux | pre_div | div | Частота |
| ------- | --------- | ------- | --- | ----------------------------------- |
| TFT7 | PLL2 | 2 | 4 | 528 / 3 / 5 = **35.2 МГц** |
| TFT8 | PLL2 | 2 | 3 | 528 / 3 / 4 = **44.0 МГц** |
| TFT4 | Video PLL | — | — | требует `CLOCK_InitVideoPll` (TODO) |
| TFT10 | — | — | — | зарезервировано |
--- ---
## Состав модуля ## API
```bash
bsp/display/
├── include/bsp/display.h # публичный заголовок
├── src/display.c # реализация API + LCDIF_IRQHandler
└── CMakeLists.txt # цель bsp_display
```
`LCDIF_IRQHandler` размещён в ITCM (`AT_QUICKACCESS_SECTION_CODE`) и только
вызывает зарегистрированный callback; состояние модуля он не модифицирует.
---
## Публичные типы
```c
typedef enum bsp_display_type_e {
BSP_DISPLAY_TFT4 = 0U, /* 480 × 272, нет ножек ориентации/MODE/DITHB */
BSP_DISPLAY_TFT7, /* 1024 × 600, LR + UD + MODE + DITHB */
BSP_DISPLAY_TFT8, /* 800 × 600, LR + UD + MODE + DITHB */
BSP_DISPLAY_TFT10, /* зарезервировано, спецификации уточняются */
BSP_DISPLAY_COUNT,
} bsp_display_type_t;
typedef enum bsp_display_rotation_e {
BSP_DISPLAY_ROTATE_0 = 0U, /* LR=1 UD=0 */
BSP_DISPLAY_ROTATE_90, /* LR=1 UD=1 */
BSP_DISPLAY_ROTATE_180, /* LR=0 UD=1 */
BSP_DISPLAY_ROTATE_270, /* LR=0 UD=0 */
} bsp_display_rotation_t;
typedef struct bsp_display_size_s {
uint16_t width;
uint16_t height;
} bsp_display_size_t;
typedef void (*bsp_display_frame_cb_t)(void); /* ISR-safe */
```
Размеры максимального дисплея (для статического выделения буферов):
```c
#define BSP_DISPLAY_MAX_WIDTH 1024U
#define BSP_DISPLAY_MAX_HEIGHT 600U
```
---
## Публичный API
```c ```c
bsp_status_t bsp_display_init(bsp_display_type_t type, bsp_status_t bsp_display_init(bsp_display_type_t type,
@ -98,231 +53,93 @@ bsp_status_t bsp_display_init(bsp_display_type_t type,
bsp_display_frame_cb_t p_on_frame_done); bsp_display_frame_cb_t p_on_frame_done);
bsp_status_t bsp_display_deinit(void); bsp_status_t bsp_display_deinit(void);
bsp_status_t bsp_display_set_rotation(bsp_display_rotation_t rotation); bsp_status_t bsp_display_set_rotation(bsp_display_rotation_t rotation);
void bsp_display_set_next_buffer(uint32_t framebuffer_addr); void bsp_display_set_next_buffer(uint32_t framebuffer_addr);
const bsp_display_size_t *bsp_display_get_size(void); const bsp_display_size_t *bsp_display_get_size(void);
bsp_display_type_t bsp_display_get_type(void); bsp_display_type_t bsp_display_get_type(void);
``` ```
### `bsp_display_init` **Типы дисплея:**
Настраивает пиксельный клок (через `CLOCK_SetMux` / `CLOCK_SetDiv`), включает ```c
`kCLOCK_LcdPixel`, поднимает подсветку, для TFT7/TFT8 инициализирует ножки typedef enum {
ориентации (`ROTATE_0`: LR=1, UD=0) и режима (MODE=1: DE-mode, DITHB=1), BSP_DISPLAY_TFT4 = 0U, /* 480 × 272 */
конфигурирует ELCDIF через `ELCDIF_RgbModeInit` и запускает его через BSP_DISPLAY_TFT7, /* 1024 × 600 */
`ELCDIF_RgbModeStart`. Включает прерывание `kELCDIF_CurFrameDoneInterruptEnable` BSP_DISPLAY_TFT8, /* 800 × 600 */
с приоритетом `DISPLAY_IRQ_PRIORITY = 2`. BSP_DISPLAY_TFT10, /* зарезервировано */
BSP_DISPLAY_COUNT,
} bsp_display_type_t;
```
Требования к аргументам и поведение: **Ротация → LR/UD (TFT7/TFT8):**
- `framebuffer_addr` — физический адрес первого фреймбуфера. Согласно ```c
заголовку, ожидается выравнивание по 64 байтам и размещение в NonCacheable typedef enum {
SDRAM. (Само значение модуль не проверяет — это контракт потребителя.) BSP_DISPLAY_ROTATE_0 = 0U, /* LR=1, UD=0 */
- `p_on_frame_done` — ISR-safe callback; `NULL` означает «без callback». BSP_DISPLAY_FLIP_VERTICAL, /* LR=1, UD=1 */
Сохраняется до включения IRQ, чтобы избежать гонки. BSP_DISPLAY_FLIP_BOTH, /* LR=0, UD=1 */
- Повторный вызов без `bsp_display_deinit()` — идемпотентен, возвращает BSP_DISPLAY_FLIP_HORIZONTAL, /* LR=0, UD=0 */
`BSP_OK` без побочных эффектов. } bsp_display_rotation_t;
```
Коды возврата: **Коды возврата `bsp_display_init()`:**
| Код | Когда | | Код | Условие |
| ----------------------- | --------------------------------------------------------------------------------------------------------- | | ----------------------- | ------------------------------ |
| `BSP_OK` | Дисплей инициализирован (или уже был инициализирован). | | `BSP_OK` | Инициализирован (или уже был) |
| `BSP_ERR_PARAM` | `type >= BSP_DISPLAY_COUNT`. | | `BSP_ERR_PARAM` | `type >= BSP_DISPLAY_COUNT` |
| `BSP_ERR_NOT_SUPPORTED` | `type` требует Video PLL (TFT4) — `init_pixelclock` возвращает ошибку до реализации `CLOCK_InitVideoPll`. | | `BSP_ERR_NOT_SUPPORTED` | TFT4 — Video PLL не реализован |
> Поведение для `BSP_DISPLAY_TFT10` целостно не описано в коде: запись в `bsp_display_init()` идемпотентен: повторный вызов без `deinit` возвращает
> `K_HW_CFG[BSP_DISPLAY_TFT10]` сделана как `{ 0 }`. Использовать TFT10 как `BSP_OK` без побочных эффектов. Callback `p_on_frame_done` должен быть
> рабочий параметр сейчас не гарантируется — типу зарезервировано место в ISR-safe (`NULL` допускается).
> enum.
### `bsp_display_deinit` `bsp_display_set_next_buffer()` безопасна из ISR и из задачи; переключение
происходит аппаратно по окончании текущего кадра.
Останавливает ELCDIF (`ELCDIF_RgbModeStop`), выключает IRQ, вызывает
`ELCDIF_Deinit`, отключает `kCLOCK_LcdPixel` и гасит подсветку. Безопасен
при вызове до `init` и повторно. Всегда возвращает `BSP_OK`.
### `bsp_display_set_rotation`
Переключает ориентацию через ножки LR/UD для дисплеев с
`has_orientation_pins = true` (TFT7/TFT8). Для TFT4 поддерживается только
`BSP_DISPLAY_ROTATE_0`; другие значения возвращают `BSP_ERR_NOT_SUPPORTED`.
До `bsp_display_init()` возвращает `BSP_ERR_INIT`. Неизвестная rotation —
`BSP_ERR_PARAM`.
Соответствие rotation → LR/UD (из `display.c`):
| Rotation | LR | UD |
| ----------------------------- | --- | --- |
| `BSP_DISPLAY_ROTATE_0` | 1 | 0 |
| `BSP_DISPLAY_FLIP_VERTICAL` | 1 | 1 |
| `BSP_DISPLAY_FLIP_BOTH` | 0 | 1 |
| `BSP_DISPLAY_FLIP_HORIZONTAL` | 0 | 0 |
### `bsp_display_set_next_buffer`
Тонкая обёртка над `ELCDIF_SetNextBufferAddr`. Согласно заголовку, безопасна
из ISR и из задачи; переключение произойдёт аппаратно по окончании текущего
кадра. Возврата нет.
### `bsp_display_get_size` / `bsp_display_get_type`
Возвращают зафиксированные при `init` параметры активного дисплея.
`bsp_display_get_size()` возвращает `NULL` до `init`; `bsp_display_get_type()`
возвращает `BSP_DISPLAY_COUNT`, если дисплей не инициализирован.
### Callback `bsp_display_frame_cb_t`
Вызывается из `LCDIF_IRQHandler` при флаге `kELCDIF_CurFrameDone`. Должен
быть ISR-safe: запись в `volatile`, `xSemaphoreGiveFromISR()` и т.п.; любые
блокирующие операции запрещены (требование из заголовка).
--- ---
## Порядок использования ## Быстрый старт
```c ```c
#include "bsp/display.h" #include "bsp/display.h"
/* Фреймбуфер — статический, в NonCacheable SDRAM, выровнен по 64 байтам. */ /* Фреймбуфер — в NonCacheable SDRAM, выровнен по 64 байтам */
static AT_NONCACHEABLE_SECTION_ALIGN( static AT_NONCACHEABLE_SECTION_ALIGN(
uint32_t fb[BSP_DISPLAY_MAX_HEIGHT * BSP_DISPLAY_MAX_WIDTH], 64U); uint32_t fb[BSP_DISPLAY_MAX_HEIGHT * BSP_DISPLAY_MAX_WIDTH], 64U);
static volatile bool g_frame_done; static volatile bool g_frame_done;
static void on_frame_done(void) { g_frame_done = true; } /* ISR-safe */ static void on_frame_done(void) { g_frame_done = true; }
void app_init(void) /* После board_hw_init(): */
{ bsp_display_init(BSP_DISPLAY_TFT8, (uint32_t)fb, on_frame_done);
/* board_hw_init() / CLOCK_*/ bsp_display_set_rotation(BSP_DISPLAY_ROTATE_0);
bsp_status_t s = bsp_display_init(BSP_DISPLAY_TFT8, /* Показать кадр */
(uint32_t) fb, g_frame_done = false;
on_frame_done); bsp_display_set_next_buffer((uint32_t)fb);
if (s != BSP_OK) { /* обработать */ } while (!g_frame_done) {}
(void) bsp_display_set_rotation(BSP_DISPLAY_ROTATE_0);
/* Залить буфер и показать кадр */
g_frame_done = false;
bsp_display_set_next_buffer((uint32_t) fb);
while (!g_frame_done) { /* ждать */ }
}
``` ```
--- ---
## Зависимости и CMake ## CMake
```cmake ```cmake
# bsp/display/CMakeLists.txt target_link_libraries(firmware_test PRIVATE bsp_display)
add_library(bsp_display STATIC src/display.c)
target_include_directories(bsp_display
PUBLIC include/
PRIVATE src/)
target_link_libraries(bsp_display
PUBLIC bsp_status
PRIVATE bsp_board sdk_elcdif)
```
- `bsp_status` (PUBLIC) — `bsp_status_t` в публичном API.
- `bsp_board` (PRIVATE) — общие board-уровневые символы (`BOARD_*`, IOMUXC).
- `sdk_elcdif` (PRIVATE) — `fsl_elcdif.h`, тип `elcdif_rgb_mode_config_t`,
функции `ELCDIF_*`, флаги полярности.
Дополнительно `display.c` подключает `fsl_clock.h` (`CLOCK_SetMux`,
`CLOCK_SetDiv`, `CLOCK_EnableClock`, `kCLOCK_LcdifPreMux/PreDiv/Div`,
`kCLOCK_LcdPixel`) и `fsl_gpio.h` (`GPIO_PinWrite`) — символы предоставляются
SDK через транзитивные зависимости.
Цель не собирается при `BUILD_TESTS_HOST=ON` (host-сборка) — ранний
`return()` в `CMakeLists.txt`.
Потребитель (`firmware/test/CMakeLists.txt`):
```cmake
target_link_libraries(firmware_test PRIVATE
...
bsp_display
...
)
target_compile_definitions(firmware_test PRIVATE target_compile_definitions(firmware_test PRIVATE
DISPLAY_TEST_TYPE=BSP_DISPLAY_TFT8 DISPLAY_TEST_TYPE=BSP_DISPLAY_TFT8
...
) )
``` ```
`DISPLAY_TEST_TYPE` — параметр **тест-модуля** (`test_display.c`), а не самого `DISPLAY_TEST_TYPE` — параметр тест-модуля `test_display.c`, не самого BSP.
`bsp_display`; см. ниже.
--- **Зависимости модуля:**
## Связь с firmware_test (`test_display.c`) | Зависимость | Тип | Описание |
| ------------ | ------- | ------------------------------------------- |
Тест-модуль `firmware/test/src/tests/test_display.c` использует это BSP так: | `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
| `bsp_board` | PRIVATE | Транзитивно: `BOARD_*`, IOMUXC |
- Тип дисплея определяется макросом `DISPLAY_TEST_TYPE` (по умолчанию | `sdk_elcdif` | PRIVATE | `fsl_elcdif.h`, `fsl_clock.h`, `fsl_gpio.h` |
`BSP_DISPLAY_TFT8`), задаётся через `target_compile_definitions` в
`firmware/test/CMakeLists.txt`.
- В `init` тест-модуль вызывает
`bsp_display_init((bsp_display_type_t) DISPLAY_TEST_TYPE, (uint32_t) g_s_framebuf, display_frame_cb)`,
затем `bsp_display_set_rotation(BSP_DISPLAY_ROTATE_0)`.
- Фреймбуфер — `g_s_framebuf[BSP_DISPLAY_MAX_HEIGHT * BSP_DISPLAY_MAX_WIDTH]`
типа `uint32_t`, выровнен по 64 байтам через `AT_NONCACHEABLE_SECTION_ALIGN`.
- Frame sync — `volatile bool g_s_frame_done`, выставляется в callback
`display_frame_cb` (записывает `true`); сбрасывается перед каждым
`bsp_display_set_next_buffer((uint32_t) g_s_framebuf)`.
- Два этапа теста (фактическое поведение из кода):
- **Этап 1 — Цвет**: четыре шага `step_color(RED, GREEN, BLUE, WHITE)`
с XRGB8888 цветами (`0x00FF0000`, `0x0000FF00`, `0x000000FF`, `0x00FFFFFF`).
Каждый шаг — заливка → ожидание кадра → confirm оператора
с таймаутом `DISPLAY_CONFIRM_TIMEOUT_MS = 15000 мс`.
- **Этап 2 — Ротация**: выполняется только если
`bsp_display_get_type() != BSP_DISPLAY_TFT4`. Заливка «левая половина RED,
правая BLUE», затем confirm для `ROTATE_0`, затем `ROTATE_90` через
`bsp_display_set_rotation()`, после чего восстанавливается `ROTATE_0`.
- В `deinit` вызывается `bsp_display_deinit()`.
Дескриптор тест-модуля:
```c
const test_module_t K_TEST_DISPLAY = {
.id = "display",
.name = "TFT Display RGB888",
.critical = false,
.requires_hil = false,
...
};
```
---
## Ограничения и замечания
- **TFT4 не поддерживается** до реализации Video PLL: `bsp_display_init`
возвращает `BSP_ERR_NOT_SUPPORTED`, в исходнике это явно отмечено как TODO
(`CLOCK_InitVideoPll`). Соответственно, и тест-модуль `display` пропускает
этап ротации для TFT4 и поддерживает только `ROTATE_0` через API.
- **TFT10** присутствует только как зарезервированное значение `enum`
(`K_HW_CFG[BSP_DISPLAY_TFT10] = { 0 }`). Реальные тайминги/делители не
заполнены; конкретное поведение `bsp_display_init(BSP_DISPLAY_TFT10, ...)`
не описано документацией модуля и не гарантируется.
- `bsp_display_init` идемпотентен: повторный вызов без `deinit` возвращает
`BSP_OK` и не перенастраивает аппаратуру. Чтобы переинициализировать с
другим типом или новым адресом фреймбуфера, нужно сначала вызвать
`bsp_display_deinit()`.
- Адрес фреймбуфера должен указывать на NonCacheable память (согласно
заголовку); модуль не делает cache maintenance над буфером.
- `bsp_display_set_next_buffer` возвращает `void` — отсутствие ошибки от
ELCDIF предполагается; верификация переключения буфера — задача
потребителя (например, через FRAME_DONE callback).
- IOMUXC ножек LR/UD/MODE/DITHB и пина подсветки конфигурируется в
`BOARD_InitPins()` за пределами модуля; неправильная конфигурация
IOMUXC проявится отсутствием реакции дисплея, а не возвратом ошибки из
API.

View file

@ -1,25 +1,26 @@
# bsp_led # bsp_led — пользовательские светодиоды
Драйвер двух пользовательских светодиодов на плате. Управление двумя пользовательскими светодиодами на плате.
Используется для индикации heartbeat и состояния приложения.
--- ---
## Аппаратная часть ## Аппаратура
| `led_id_t` | Сигнал | GPIO | Pin | Координата | Активный уровень | | Идентификатор | Сигнал | Пин MCU | Корпус | GPIO | Активный уровень |
| --------------- | ---------- | ----- | --- | ---------- | ---------------- | | --------------- | -------- | ------------- | ------ | -------- | ---------------- |
| `LED_HEARTBEAT` | `UserLed1` | GPIO3 | 3 | M4 | LOW (0 = горит) | | `LED_HEARTBEAT` | UserLed1 | GPIO_SD_B1_03 | M4 | GPIO3[3] | LOW (0 = горит) |
| `LED_APP` | `UserLed2` | GPIO3 | 4 | P2 | LOW (0 = горит) | | `LED_APP` | UserLed2 | GPIO_SD_B1_04 | P2 | GPIO3[4] | LOW (0 = горит) |
Пины сконфигурированы в `generated/pin_mux.h` (MCUXpresso Config Tools). Пины настроены в `BOARD_InitPins()` (`generated/pin_mux.c`) как OUTPUT,
`INIT_GPIO_VALUE = 1U`оба LED выключены сразу после `led_init()`. `INIT_GPIO_VALUE = 1U`оба LED выключены сразу после `bsp_led_init()`.
--- ---
## API ## API
```c ```c
void bsp_led_init(void); // вызвать один раз после board_hw_init() void bsp_led_init(void);
void bsp_led_on(led_id_t id); void bsp_led_on(led_id_t id);
void bsp_led_off(led_id_t id); void bsp_led_off(led_id_t id);
@ -28,22 +29,24 @@ void bsp_led_set(led_id_t id, bool on);
bool bsp_led_get(led_id_t id); bool bsp_led_get(led_id_t id);
``` ```
Вызвать `bsp_led_init()` один раз после `board_hw_init()`.
--- ---
## Использование ## Быстрый старт
```c ```c
#include "bsp/led.h" #include "bsp/led.h"
// инициализация
bsp_led_init(); bsp_led_init();
// heartbeat /* heartbeat в main loop */
bsp_led_toggle(LED_HEARTBEAT); bsp_led_toggle(LED_HEARTBEAT);
// прикладная индикация /* индикация события */
bsp_led_on(LED_APP); // пакет принят / тест запущен bsp_led_on(LED_APP);
bsp_led_off(LED_APP); // сброс /* ... */
bsp_led_off(LED_APP);
``` ```
--- ---
@ -51,20 +54,13 @@ bsp_led_off(LED_APP); // сброс
## CMake ## CMake
```cmake ```cmake
target_link_libraries(<target> PRIVATE bsp_led) target_link_libraries(firmware_test PRIVATE bsp_led)
``` ```
Зависимости: `bsp_board` (PUBLIC, транзитивно), `sdk_gpio` (PRIVATE). **Зависимости модуля:**
При `BUILD_TESTS_HOST=ON` компонент не собирается — мокается через `fff` на уровне теста.
--- | Зависимость | Тип | Описание |
| ------------ | ------- | -------------------------------------------- |
## Файлы | `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
| `bsp_board` | PRIVATE | Транзитивно: `pin_mux.h`, clock, SDK headers |
```bash | `sdk_gpio` | PRIVATE | `fsl_gpio.h``GPIO_PinWrite/Read()` |
led/
├── CMakeLists.txt
├── include/led.h # публичный API — без NXP хедеров
├── src/led.c # реализация, fsl_gpio.h только здесь
└── README.md # этот файл
```

View file

@ -1,75 +1,85 @@
# bsp_opto — оптоизолированные входы # bsp_opto — оптоизолированные входы (PS2801-4)
Три оптоизолированных входа на базе PS2801-4. Два канала (`IN1`, `IN2`)
предназначены для детектирования уровня с дебаунсом; третий (`RS`) —
для захвата старт-бита бинарного протокола с минимальной задержкой.
---
## Аппаратура ## Аппаратура
| Канал | Пин MCU | GPIO | Схема | | Канал | Сигнал | Пин MCU | Корпус | GPIO | Логика |
|--------------------|------------------|------------|--------------------------| | ----------------- | ------ | ------------- | ------ | --------- | ----------- |
| `BSP_OPTO_CH_IN1` | GPIO_AD_B1_06 | GPIO1[22] | PS2801-4 + 1K pull-up | | `BSP_OPTO_CH_IN1` | ExtIn1 | GPIO_AD_B1_06 | J12 | GPIO1[22] | active-HIGH |
| `BSP_OPTO_CH_IN2` | GPIO_AD_B1_05 | GPIO1[21] | PS2801-4 + 1K pull-up | | `BSP_OPTO_CH_IN2` | ExtIn2 | GPIO_AD_B1_05 | K12 | GPIO1[21] | active-HIGH |
| `BSP_OPTO_CH_RS` | GPIO_AD_B1_07 | GPIO1[23] | PS2801-4 + 1K pull-up | | `BSP_OPTO_CH_RS` | RsRx | GPIO_AD_B1_07 | K10 | GPIO1[23] | active-HIGH |
**Логика:** active-HIGH. Оптопары неинвертирующие (PS2801-4): PS2801-4 неинвертирующие: пин HIGH (ток есть) → `BSP_OPTO_STATE_ACTIVE`.
- Пин HIGH (ток есть) → `BSP_OPTO_STATE_ACTIVE`
- Пин LOW (тока нет) → `BSP_OPTO_STATE_INACTIVE`
`BSP_OPTO_CH_RS` — опциональный. Активен только при `rs_as_gpio = true` в конфигурации.
При `rs_as_gpio = false` пин остаётся под управлением `bsp_uart_rs` (LPUART3_RX).
Все три пина принадлежат GPIO1[16..31] → один IRQ: `GPIO1_Combined_16_31_IRQn`. Все три пина принадлежат GPIO1[16..31] → один IRQ: `GPIO1_Combined_16_31_IRQn`.
--- `BSP_OPTO_CH_RS` — двойное назначение пина K10:
## Режимы работы каналов | Режим | Функция | pin_mux |
| ---------- | ------------- | --------------------- |
| GPIO input | `bsp_opto` | `BOARD_InitRS_GPIO()` |
| LPUART3 RX | `bsp_uart_rs` | `BOARD_InitRS_UART()` |
Каждый канал настраивается независимо через поле `modes[]` конфигурации. Одновременное использование невозможно. `rs_as_gpio = true` в конфигурации
активирует канал RS; `false` — пин остаётся под LPUART3.
### `BSP_OPTO_MODE_LEVEL` — детектирование уровня (IN1, IN2)
Предназначен для детектирования наличия/отсутствия сигнала с программным дебаунсом.
1. ISR фиксирует timestamp (`bsp_tick_get_ms()`) и raw состояние пина, взводит `pending`.
2. ISR **автоматически переключает направление прерывания** (RISING↔FALLING) после каждого
фронта — оба края сигнала ловятся без дополнительной настройки.
3. `bsp_opto_process()` вызывается из main loop. Если с момента последнего фронта прошло
>= `debounce_ms` — перечитывает пин, сравнивает с `confirmed_state`, вызывает коллбэк.
Начальный фронт выбирается **автоматически** при инициализации по текущему состоянию пина
(LOW → ждём RISING, HIGH → ждём FALLING). Поле `edges[]` для этого режима игнорируется.
Рекомендуемое значение `debounce_ms`: **10 мс** (PS2801-4 response ~50 мкс,
основной источник шума — механические контакты на стенде).
Коллбэк вызывается из контекста **main loop** (не из ISR).
### `BSP_OPTO_MODE_PROTO` — детектирование старт-бита протокола (RS)
Предназначен для приёма бинарных протоколов, где требуется минимальная задержка реакции
на первый фронт (старт-бит).
1. ISR фиксирует фронт и **немедленно вызывает коллбэк** — без дебаунса.
2. После срабатывания прерывание канала **отключается** автоматически.
3. Принимающий модуль (декодер протокола) после обработки пакета вызывает
`bsp_opto_proto_arm()` чтобы взвести прерывание для следующего старт-бита.
Направление фронта задаётся полем `edges[]` и не меняется автоматически
(обычно `BSP_OPTO_EDGE_RISING` для старт-бита).
Коллбэк вызывается **прямо из ISR** — он должен быть ISR-safe:
только взводить флаг или писать в `volatile`-переменную, никакой бизнес-логики.
--- ---
## Использование ## Архитектура
```mermaid
flowchart TD
subgraph MODE_LEVEL["MODE_LEVEL — IN1, IN2"]
A["Фронт на пине"] --> B["GPIO1_Combined_16_31_IRQn\nфиксирует timestamp + raw state\nпереключает направление RISING↔FALLING"]
B --> C["bsp_opto_process()\nв main loop"]
C --> D{"debounce_ms прошло?"}
D -- да --> E["перечитать пин\nсравнить с confirmed_state\nвызвать callback"]
D -- нет --> C
end
subgraph MODE_PROTO["MODE_PROTO — RS"]
F["Старт-бит (фронт)"] --> G["GPIO1_Combined_16_31_IRQn\nнемедленный вызов callback\nотключить прерывание канала"]
G --> H["декодер протокола"]
H --> I["bsp_opto_proto_arm()\nвзвести прерывание снова"]
end
```
- **MODE_LEVEL**: callback вызывается из контекста **main loop** — после подтверждения дебаунсом.
- **MODE_PROTO**: callback вызывается **прямо из ISR** — только атомарные операции.
- После срабатывания канал RS автоматически отключается; повторный вызов
`bsp_opto_proto_arm()` обязателен, иначе канал остаётся неактивным.
---
## API
```c
bsp_status_t bsp_opto_init(const bsp_opto_config_t *p_cfg);
void bsp_opto_process(void); /* вызывать из main loop */
bsp_opto_state_t bsp_opto_read(bsp_opto_ch_t ch);
bsp_status_t bsp_opto_proto_arm(bsp_opto_ch_t ch);
```
`bsp_opto_read()` всегда возвращает `BSP_OPTO_STATE_INACTIVE` для каналов
в `MODE_PROTO` — используй `GPIO_PinRead` напрямую при побитовом сэмплировании.
---
## Быстрый старт
### MODE_LEVEL (IN1, IN2) ### MODE_LEVEL (IN1, IN2)
```c ```c
#include "bsp/opto.h"
static void on_level_change(bsp_opto_ch_t ch, bsp_opto_state_t state) static void on_level_change(bsp_opto_ch_t ch, bsp_opto_state_t state)
{ {
if (ch == BSP_OPTO_CH_IN1 && state == BSP_OPTO_STATE_ACTIVE) { if (ch == BSP_OPTO_CH_IN1 && state == BSP_OPTO_STATE_ACTIVE) { /* ... */ }
/* IN1 активирован */
}
} }
bsp_opto_config_t cfg = { bsp_opto_config_t cfg = {
@ -81,31 +91,22 @@ bsp_opto_config_t cfg = {
}; };
bsp_opto_init(&cfg); bsp_opto_init(&cfg);
/* в main loop: */
for (;;) { for (;;) {
bsp_opto_process(); bsp_opto_process();
} }
``` ```
Полинг без коллбэков:
```c
bsp_opto_state_t state = bsp_opto_read(BSP_OPTO_CH_IN1);
```
### MODE_PROTO (RS) совместно с MODE_LEVEL (IN1, IN2) ### MODE_PROTO (RS) совместно с MODE_LEVEL (IN1, IN2)
```c ```c
/* Коллбэк вызывается из ISR — только атомарные операции */ static volatile bool s_start_bit = false;
static volatile bool s_start_bit_detected = false;
/* Вызывается из ISR — только volatile-запись */
static void on_rs_start_bit(bsp_opto_ch_t ch, bsp_opto_state_t state) static void on_rs_start_bit(bsp_opto_ch_t ch, bsp_opto_state_t state)
{ {
s_start_bit_detected = true; s_start_bit = true;
} }
static void on_level_change(bsp_opto_ch_t ch, bsp_opto_state_t state) { /* ... */ }
bsp_opto_config_t cfg = { bsp_opto_config_t cfg = {
.callbacks = { on_level_change, on_level_change, on_rs_start_bit }, .callbacks = { on_level_change, on_level_change, on_rs_start_bit },
.modes = { BSP_OPTO_MODE_LEVEL, BSP_OPTO_MODE_LEVEL, BSP_OPTO_MODE_PROTO }, .modes = { BSP_OPTO_MODE_LEVEL, BSP_OPTO_MODE_LEVEL, BSP_OPTO_MODE_PROTO },
@ -115,30 +116,56 @@ bsp_opto_config_t cfg = {
}; };
bsp_opto_init(&cfg); bsp_opto_init(&cfg);
/* в main loop: */
for (;;) { for (;;) {
bsp_opto_process(); /* обслуживает IN1, IN2 */ bsp_opto_process();
if (s_start_bit_detected) { if (s_start_bit) {
s_start_bit_detected = false; s_start_bit = false;
/* запустить декодер протокола по таймеру... */ /* запустить декодер... */
bsp_opto_proto_arm(BSP_OPTO_CH_RS); /* взвести для следующего старт-бита */
/* по завершении приёма пакета — взвести для следующего старт-бита */
bsp_opto_proto_arm(BSP_OPTO_CH_RS);
} }
} }
``` ```
--- ---
## Совместное использование RS_RX ## Тестирование
Пин GPIO_AD_B1_07 может работать в двух режимах: ### Host unit-тесты
| Режим | BSP-модуль | pin_mux функция | Категория **B**`bsp_opto.c` вызывает `fsl_gpio.h`. SDK-функции мокируются
|----------------|-----------------|-------------------------| через fff. Stub `fsl_gpio.h` в `tests/host/mocks/`.
| LPUART3 RX | `bsp_uart_rs` | `BOARD_InitRS_UART()` |
| GPIO input | `bsp_opto` | `BOARD_InitRS_GPIO()` |
Одновременно использовать оба нельзя. В `firmware_test` режим выбирается ```bash
при инициализации в зависимости от конфигурации теста. just build::test-host # покрытие: init, MODE_LEVEL debounce, MODE_PROTO arm/disarm
```
### HIL-тесты
C-прошивка: `tests/target/hil_opto/` — CLI через `bsp_uart_host`.
pytest: `tools/hil/02_test_opto.py` — управление входами через M5StampPLC RLY24.
```bash
just host::hil-opto
```
---
## CMake
```cmake
target_link_libraries(firmware_test PRIVATE
bsp_board
bsp_tick
bsp_opto
)
```
**Зависимости модуля:**
| Зависимость | Тип | Описание |
| ------------ | ------- | -------------------------------------------- |
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для дебаунс-таймаута |
| `bsp_board` | PRIVATE | Транзитивно: `pin_mux.h`, clock, SDK headers |
| `sdk_gpio` | PRIVATE | `fsl_gpio.h` — GPIO IRQ, `GPIO_PinRead()` |

View file

@ -1,215 +1,153 @@
# bsp_qspi_flash — QSPI Flash W25Q64/128/256/512 # bsp_qspi_flash — QSPI Flash W25Q64/128/256/512
> Расположение: `bsp/qspi_flash/` Драйвер QSPI Flash на FlexSPI1 с поддержкой четырёх чипов Winbond.
> Публичный заголовок: `bsp/qspi_flash/include/bsp/qspi_flash.h` XIP-безопасен: все функции, трогающие FlexSPI IP-регистры, размещены в ITCM
> Реализация: `bsp/qspi_flash/src/qspi_flash.c` и выполняются под IRQ lock.
--- ---
## Поддерживаемое железо ## Аппаратура
| Чип | JEDEC mfr | JEDEC cap | Размер | LUT-таблица | | Параметр | Значение |
|---------|-----------|-----------|--------|-------------| | ------------- | ----------------- |
| W25Q64 | 0xEF | 0x17 | 8 MB | `K_LUT_3B` | | Интерфейс MCU | FlexSPI1, порт A1 |
| W25Q128 | 0xEF | 0x18 | 16 MB | `K_LUT_3B` |
| W25Q256 | 0xEF | 0x19 | 32 MB | `K_LUT_4B` |
| W25Q512 | 0xEF | 0x20 | 64 MB | `K_LUT_4B` |
Интерфейс MCU: **FlexSPI1, порт A1** (`FLEXSPI_PortA1`). **Поддерживаемые чипы:**
| Чип | JEDEC mfr | JEDEC cap | Размер | Адресация |
| ------- | --------- | --------- | ------ | --------- |
| W25Q64 | `0xEF` | `0x17` | 8 MB | 3-byte |
| W25Q128 | `0xEF` | `0x18` | 16 MB | 3-byte |
| W25Q256 | `0xEF` | `0x19` | 32 MB | 4-byte |
| W25Q512 | `0xEF` | `0x20` | 64 MB | 4-byte |
--- ---
## XIP-безопасность ## Архитектура
### Проблема ### XIP-безопасность
Прошивка исполняется XIP из Flash через FlexSPI AHB-интерфейс. Прошивка исполняется XIP из Flash по AHB. Любая IP-команда FlexSPI блокирует
CPU непрерывно фетчит инструкции из Flash по AHB — через LUT-слот 0. AHB-путь — если в этот момент CPU фетчит инструкцию из Flash, происходит
Любая IP-команда FlexSPI блокирует AHB-путь на время выполнения. **HardFault**.
Если в этот момент CPU попытается фетчить инструкцию из Flash — **HardFault**.
### Решение: ITCM + IRQ lock Решение — два уровня защиты:
Все функции, обращающиеся к регистрам FlexSPI, размещены в **ITCM** ```mermaid
(`0x00000000`) через `AT_QUICKACCESS_SECTION_CODE`. ITCM подключён к CPU flowchart LR
по выделенной шине (не AHB), поэтому фетч инструкций из ITCM не конкурирует A["публичная функция\nbsp_qspi_*(...)"] --> B["IRQ lock\n__get_PRIMASK + DSB + ISB"]
с IP-командами FlexSPI. B --> C["AHBCR.PREFETCHEN = 0"]
C --> D["IP-команда FlexSPI\nиз ITCM\n(AT_QUICKACCESS_SECTION_CODE)"]
D --> E["AHBCR.PREFETCHEN = 1"]
E --> F["IRQ unlock\nвосстановить PRIMASK"]
```
Дополнительно, каждая публичная операция выполняется под **IRQ lock** **ITCM** (`0x00000000`) подключён к CPU по выделенной шине — фетч инструкций
(`qspi_irq_lock` / `qspi_irq_unlock` с `__get_PRIMASK()` + `DSB` + `ISB`): не конкурирует с AHB. **IRQ lock** гарантирует, что прерывание не застанет
это гарантирует, что прерывание не застанет FlexSPI в середине IP-транзакции. FlexSPI в середине IP-транзакции; `PRIMASK` восстанавливается, не сбрасывается
IRQ unlock восстанавливает предыдущий `PRIMASK`, не включает IRQ безусловно — безусловно — вызов из уже заблокированного контекста корректен.
вызов из уже заблокированного контекста корректен.
AHB prefetch отключается (`AHBCR.PREFETCHEN = 0`) перед серией IP-транзакций **Обязательный дефайн в CMakeLists потребителя:**
и восстанавливается после.
### Обязательный дефайн в CMakeLists потребителя
```cmake ```cmake
target_compile_definitions(firmware_test PRIVATE target_compile_definitions(firmware_test PRIVATE
__STARTUP_INITIALIZE_RAMFUNCTION # ← обязательно __STARTUP_INITIALIZE_RAMFUNCTION # ← без этого ITCM содержит нули → HardFault
__STARTUP_CLEAR_BSS) __STARTUP_CLEAR_BSS
)
``` ```
Без `__STARTUP_INITIALIZE_RAMFUNCTION` startup-файл NXP SDK не копирует ### Адресация W25Q256/512
`CodeQuickAccess` секцию из Flash в ITCM. В ITCM остаются нули. Первый же
вызов любой ITCM-функции вызывает **HardFault**.
--- Вместо `Enter 4-Byte Mode (0xB7)` используются dedicated 4-byte opcodes — XIP-слот 0
(24-bit адресация) не изменяется:
## Стратегия адресации W25Q256/512 | Операция | W25Q64/128 | W25Q256/512 |
| ---------------- | ---------- | ----------- |
### Почему не Enter 4-Byte Mode (0xB7)
FDCB фиксирует XIP-слот 0 в режиме 24-bit адресации на всех чипах.
Переключение чипа командой 0xB7 сломало бы XIP — AHB продолжал бы
посылать 24-bit адреса, чип ждал бы 32-bit → **HardFault**.
### Dedicated 4-byte address opcodes
W25Q256/512 принимают 32-bit адрес через отдельный набор opcodes — без
изменения режима адресации чипа:
| Операция | W25Q64/128 (3-byte) | W25Q256/512 (4-byte) |
|-------------------|---------------------|----------------------|
| Sector Erase 4KB | `0x20` | `0x21` | | Sector Erase 4KB | `0x20` | `0x21` |
| Block Erase 32KB | `0x52` | `0x5C` | | Block Erase 32KB | `0x52` | `0x5C` |
| Block Erase 64KB | `0xD8` | `0xDC` | | Block Erase 64KB | `0xD8` | `0xDC` |
| Quad Page Program | `0x32` | `0x34` | | Quad Page Prog | `0x32` | `0x34` |
| IP Quad Out Read | `0x6B` | `0x6C` | | IP Quad Out Read | `0x6B` | `0x6C` |
Слот 0 (XIP) **не изменяется**. XIP работает непрерывно на всех чипах.
--- ---
## LUT-слоты ## API
| Слот | Константа | Команда | Зависит от чипа |
|------|----------------|-----------------------------|-----------------|
| 0 | (XIP, FDCB) | Quad Read | Нет (не трогаем)|
| 1 | LSEQ_READ_SR1 | Read SR1 (0x05) | Нет |
| 2 | LSEQ_WR_EN | Write Enable (0x06) | Нет |
| 3 | LSEQ_ERASE_4K | Sector Erase (0x20/0x21) | Да |
| 4 | LSEQ_PP_QUAD | Quad Page Prog (0x32/0x34) | Да |
| 5 | LSEQ_JEDEC | Read JEDEC (0x9F) | Нет |
| 6 | LSEQ_READ_SR2 | Read SR2 (0x35) | Нет |
| 7 | LSEQ_WR_SR2 | Write SR2 (0x31) | Нет |
| 8 | LSEQ_READ_SR3 | Read SR3 (0x15) | Нет |
| 9 | LSEQ_ERASE_32K | Block Erase 32KB (0x52/0x5C)| Да |
| 10 | LSEQ_ERASE_64K | Block Erase 64KB (0xD8/0xDC)| Да |
| 11 | LSEQ_IP_READ | Quad Out Read (0x6B/0x6C) | Да |
Слоты 111 обновляются в `bsp_qspi_init()` под конкретный чип.
Слот 0 никогда не изменяется BSP-кодом.
---
## Watermark FIFO
Размер watermark-юнита читается из регистров `IPRXFCR.RXWMRK` и
`IPTXFCR.TXWMRK` в рантайме — не зашит константой. Это гарантирует
корректную работу если FDCB или SDK изменили настройки watermark по
умолчанию.
---
## Публичный API
Все публичные функции размещены в ITCM (`AT_QUICKACCESS_SECTION_CODE`) и
выполняются под IRQ lock.
```c ```c
/* Инициализация — вызвать до bsp_tick_init() и любой другой функции модуля */ /* Инициализация — вызвать до bsp_tick_init() */
bsp_status_t bsp_qspi_init(void); bsp_status_t bsp_qspi_init(void);
/* Чтение JEDEC ID (0x9F) */ /* Идентификация */
bsp_status_t bsp_qspi_read_jedec_id(bsp_qspi_jedec_t *p_jedec); bsp_status_t bsp_qspi_read_jedec_id(bsp_qspi_jedec_t *p_jedec);
uint32_t bsp_qspi_flash_size(void); /* доступно после init() */
/* Стирание */ /* Стирание */
bsp_status_t bsp_qspi_erase_sector(uint32_t addr); /* 4KB, ~45 мс */ bsp_status_t bsp_qspi_erase_sector(uint32_t addr); /* 4 KB, ~45 мс */
bsp_status_t bsp_qspi_erase_block_32k(uint32_t addr); /* 32KB, ~120 мс */ bsp_status_t bsp_qspi_erase_block_32k(uint32_t addr); /* 32 KB, ~120 мс */
bsp_status_t bsp_qspi_erase_block_64k(uint32_t addr); /* 64KB, ~150 мс */ bsp_status_t bsp_qspi_erase_block_64k(uint32_t addr); /* 64 KB, ~150 мс */
/* Запись одной страницы (256 байт) */ /* Запись одной страницы (256 байт, адрес выровнен на BSP_QSPI_PAGE_SIZE) */
bsp_status_t bsp_qspi_write_page(uint32_t addr, const uint8_t *p_data); /* ~3 мс */ bsp_status_t bsp_qspi_write_page(uint32_t addr, const uint8_t *p_data); /* ~3 мс */
/* Чтение через IP-команду (не AHB/XIP) */ /* Чтение через IP-команду (не AHB/XIP) */
bsp_status_t bsp_qspi_read(uint32_t addr, uint8_t *p_buf, size_t size); bsp_status_t bsp_qspi_read(uint32_t addr, uint8_t *p_buf, size_t size);
/* Размер Flash — доступен после bsp_qspi_init() */
uint32_t bsp_qspi_flash_size(void);
``` ```
Все функции возвращают `BSP_OK` при успехе или `BSP_ERR_HW` при ошибке Все функции возвращают `BSP_OK` или `BSP_ERR_HW`.
FlexSPI / неверном аргументе. `BSP_ERR_HW` должен присутствовать в
`bsp/common/include/bsp/status.h`.
--- **Выбор операции стирания:**
## Порядок инициализации | Объём | Рекомендация | Время |
| ------------ | ------------------ | -------------- |
`bsp_qspi_init()` должна вызываться **до** `bsp_tick_init()`: | < 32 KB | `erase_sector` 4KB | ~45 мс × N |
| 32 KB 1 MB | `erase_block_32k` | ~120 мс / 32KB |
```c
/* main.c */
board_hw_init();
bsp_qspi_init(); /* ← сначала QSPI, до SysTick */
bsp_tick_init(); /* ← потом SysTick */
bsp_usb_cdc_init();
```
Причина: `bsp_qspi_init()` и все ITCM-функции выполняются под полным
IRQ lock. Если SysTick уже запущен и прерывание сработает в момент
IP-команды — возможен AHB stall. Порядок инициализации устраняет эту
гонку при первом запуске.
---
## Выбор операции стирания
| Объём очистки | Рекомендация | Время |
|---------------|------------------------|-----------------|
| < 32 KB | `erase_sector` (4KB) | пропорционально |
| 32 KB — 1 MB | `erase_block_32k` | ~120 мс / 32KB |
| > 1 MB | `erase_block_64k` | ~150 мс / 64KB | | > 1 MB | `erase_block_64k` | ~150 мс / 64KB |
Пример: 4 MB через 64KB = 64 × 150 мс ≈ **9.6 с** Пример: 4 MB через 64KB = 64 × 150 мс ≈ **9.6 с** против 46 с через 4KB.
против 1024 × 45 мс ≈ **46 с** через 4KB.
---
## Быстрый старт
```c
#include "bsp/qspi_flash.h"
/* main.c — порядок инициализации: */
board_hw_init();
bsp_qspi_init(); /* ← до bsp_tick_init() */
bsp_tick_init();
/* Идентификация чипа */
bsp_qspi_jedec_t jedec;
bsp_qspi_read_jedec_id(&jedec);
/* Стереть сектор и записать страницу */
bsp_qspi_erase_sector(0x00010000);
uint8_t page[256] = { /* ... */ };
bsp_qspi_write_page(0x00010000, page);
/* Прочитать обратно */
uint8_t buf[256];
bsp_qspi_read(0x00010000, buf, sizeof(buf));
```
--- ---
## CMake ## CMake
```cmake ```cmake
# bsp/qspi_flash/CMakeLists.txt target_link_libraries(firmware_test PRIVATE bsp_qspi_flash)
target_link_libraries(bsp_qspi_flash
PUBLIC bsp_status
PRIVATE bsp_board sdk_flexspi
)
```
Потребитель (`firmware_test`):
```cmake
target_link_libraries(firmware_test PRIVATE
bsp_qspi_flash
...
)
target_compile_definitions(firmware_test PRIVATE target_compile_definitions(firmware_test PRIVATE
__STARTUP_INITIALIZE_RAMFUNCTION # обязательно для ITCM-функций __STARTUP_INITIALIZE_RAMFUNCTION
__STARTUP_CLEAR_BSS __STARTUP_CLEAR_BSS
) )
``` ```
--- **Зависимости модуля:**
## Известные ограничения | Зависимость | Тип | Описание |
| ------------- | ------- | ------------------------------------ |
- `bsp_qspi_write_page()` — строго одна страница (256 байт). Адрес обязан | `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
быть выровнен на `BSP_QSPI_PAGE_SIZE`. Запись через границу страницы не | `bsp_board` | PRIVATE | Транзитивно: clock, SDK headers |
поддерживается. | `sdk_flexspi` | PRIVATE | `fsl_flexspi.h` — FlexSPI IP-команды |
- Нет timeout в `qspi_wait_not_busy()`. Зависание из-за дефектного чипа
потребует watchdog reset. Для диагностической прошивки это приемлемо.
- Chip Erase (0xC7) не реализован — слишком деструктивно при XIP-исполнении.

View file

@ -1,130 +1,114 @@
# bsp_sd — SD host-контроллер (USDHC1) # bsp_sd — SD host-контроллер (USDHC1)
Модуль инициализирует SD host-контроллер и проверяет наличие карты. Инициализация SD host-контроллера и детект карты. Файловой системой не
Файловой системой не занимается — это ответственность `bsp_usd` (поверх) или занимается — это ответственность слоя `bsp_usd` / `port_fatfs_sd` поверх.
приложения напрямую.
## Место в архитектуре
Каждый слой знает только о слое ниже — зависимости не пересекают границы.
```bash
firmware_test / tft_app
bsp_usd ← монтирование FatFS, тест R/W
├──► firmware_test_fatfs ← ff.c + fsl_sd_disk + diskio (bare-metal ffconf)
│ tft_app_fatfs ← ff.c + fsl_sd_disk + diskio (FreeRTOS ffconf)
│ │
│ ▼
│ port/fatfs/sd ← diskio_sd.c: microsd_disk_* → fsl_sd_disk
│ │
▼ ▼
bsp_sd ← этот модуль: host init / deinit / card detect
bsp/generated/sdmmc_config ← board-level: BOARD_SD_Config, GPIO питания, pad config
sdk_sdmmc_sd ← NXP: fsl_sd, fsl_sdmmc_common, fsl_sdmmc_host (non-blocking)
sdk_usdhc ← NXP HAL: fsl_usdhc
```
--- ---
## Аппаратный контекст ## Аппаратура
| Сигнал | Пин MCU | Конфигурация | | Сигнал | Пин MCU | Корпус | Конфигурация |
| ------ | ---------------- | ------------------------------------------------ | | ------ | ------------- | ------ | --------------------------------------------- |
| CLK | GPIO_SD_B0_01 | USDHC1_CLK, периферийный режим | | CLK | GPIO_SD_B0_01 | J3 | USDHC1_CLK, периферийный режим |
| CMD | GPIO_SD_B0_00 | USDHC1_CMD, периферийный режим | | CMD | GPIO_SD_B0_00 | J4 | USDHC1_CMD, периферийный режим |
| D0D3 | GPIO_SD_B0_0205 | USDHC1_DATA03, периферийный режим | | D0 | GPIO_SD_B0_02 | J1 | USDHC1_DATA0, периферийный режим |
| CD_B | GPIO_B1_12 | USDHC1_CD_B — детект через GPIO2[28] | | D1 | GPIO_SD_B0_03 | K1 | USDHC1_DATA1, периферийный режим |
| SdPwr | GPIO_AD_B1_03 | GPIO1[19], active-low, управляется SDK через BSP | | D2 | GPIO_SD_B0_04 | H2 | USDHC1_DATA2, периферийный режим |
| D3 | GPIO_SD_B0_05 | J2 | USDHC1_DATA3, периферийный режим |
| CD_B | GPIO_B1_12 | D13 | USDHC1_CD_B — детект через периферийный режим |
| SdPwr | GPIO_AD_B1_03 | M12 | GPIO1[19], active-low |
**CD_B** подключён как периферийный сигнал USDHC1, а не как GPIO. Детект карты **CD_B** подключён как периферийный сигнал USDHC1 — детект читается через
читается через `USDHC_GetPresentStatusFlags``kUSDHC_CardInsertedFlag`. `USDHC_GetPresentStatusFlags``kUSDHC_CardInsertedFlag`. GPIO-прерывание
GPIO-прерывание на CD не используется (`kSD_DetectCardByHostCD`). не используется (`kSD_DetectCardByHostCD`).
**SdPwr** инициализируется в `BOARD_SD_Config()` как GPIO-выход, выключен при старте. **SdPwr** инициализируется в `BOARD_SD_Config()` как GPIO-выход, выключен
SDK включает питание автоматически в процессе `SD_HostInit()` через callback. при старте. SDK включает питание автоматически в `SD_HostInit()` через callback.
---
## Архитектура
```mermaid
graph TB
FW["firmware_test / tft_app"]
USD["bsp_usd\nмонтирование FatFS, тест R/W"]
FATFS["firmware_test_fatfs / tft_app_fatfs\nff.c + fsl_sd_disk + diskio"]
PORT["port/fatfs/sd\ndiskio_sd.c → fsl_sd_disk"]
BSP["bsp_sd\nhost init / deinit / card detect"]
SDMMC_CFG["bsp/generated/sdmmc_config\nBOARD_SD_Config, GPIO питания"]
SDK_SD["sdk_sdmmc_sd\nfsl_sd, fsl_sdmmc_common"]
SDK_USDHC["sdk_usdhc\nfsl_usdhc"]
FW --> USD --> FATFS --> PORT --> BSP
BSP --> SDMMC_CFG --> SDK_SD --> SDK_USDHC
```
**Почему `ff.c` и `fsl_sd_disk.c` не собираются как общая библиотека:**
оба включают `ffconf.h`, который разный для `firmware_test` (`FF_FS_REENTRANT=0`)
и `tft_app` (`FF_FS_REENTRANT=1`). Общий только `port_fatfs_sd` — он `ff.h`
напрямую не включает.
--- ---
## API ## API
### `bsp_sd_init(void)` ```c
bsp_status_t bsp_sd_init(void);
Конфигурирует SDMMC host однократно (`BOARD_SD_Config`) и запускает bsp_status_t bsp_sd_deinit(void);
host-контроллер (`SD_HostInit`). bool bsp_sd_is_inserted(void);
Повторный вызов без `bsp_sd_deinit` — no-op, возвращает `BSP_OK`.
Возвращает:
- `BSP_OK` — host готов к работе;
- `BSP_ERR_INIT``SD_HostInit` вернул ошибку.
### `bsp_sd_deinit(void)`
Останавливает host-контроллер и отключает питание карты.
Безопасен при вызове до `init` или повторно после `deinit`.
Возвращает:
- `BSP_OK` — всегда.
### `bsp_sd_is_inserted(void)`
Читает регистр `USDHC1 PRSSTAT`. Не требует предварительного `bsp_sd_init()`
включает тактирование USDHC1 самостоятельно через `CLOCK_EnableClock`.
Возвращает:
- `true` — карта вставлена;
- `false` — карта отсутствует.
---
## Разделение ответственности: bsp_sd vs sdmmc_config vs port_fatfs_sd
| Слой | Что делает | Где живёт |
| --------------------- | ------------------------------------------------------- | ----------------------------------- |
| `sdmmc_config` | Константы платы, `BOARD_SD_Config`, GPIO питания, pads | `bsp/generated/` |
| `bsp_sd` | `SD_HostInit/Deinit`, идемпотентность, card detect | `bsp/sd/` |
| `port_fatfs_sd` | `microsd_disk_*``fsl_sd_disk` (FatFS diskio glue) | `port/fatfs/sd/` |
| `firmware_test_fatfs` | `ff.c` + `fsl_sd_disk` + `diskio.c` (bare-metal ffconf) | `firmware/test/fatfs/` |
| `tft_app_fatfs` | `ff.c` + `fsl_sd_disk` + `diskio.c` (FreeRTOS ffconf) | `firmware/tft_app/fatfs/` (будущее) |
**Почему `ff.c` и `fsl_sd_disk.c` не компилируются один раз как общая библиотека:**
оба включают `ff.h``ffconf.h`, который разный для `firmware_test` (bare-metal,
`FF_FS_REENTRANT=0`) и `tft_app` (FreeRTOS, `FF_FS_REENTRANT=1`, `FF_VOLUMES=3`).
Общий только `port_fatfs_sd` — он не включает `ff.h` напрямую.
---
## Зависимости
```cmake
target_link_libraries(bsp_sd
PUBLIC bsp_status # bsp_status_t
PRIVATE bsp_sdmmc_config # BOARD_SD_Config, sdmmc_config.h, sdk_sdmmc_sd
)
``` ```
`sdk_sdmmc_sd` — транзитивно через `bsp_sdmmc_config`. **`bsp_sd_init()`** — конфигурирует SDMMC host однократно (`BOARD_SD_Config`)
`sdk_usdhc` — транзитивно через `sdk_sdmmc_sd`. и запускает host-контроллер (`SD_HostInit`). Повторный вызов без `deinit` — no-op,
возвращает `BSP_OK`.
**`bsp_sd_is_inserted()`** — читает регистр `USDHC1 PRSSTAT`. Не требует
предварительного `bsp_sd_init()` — включает тактирование через `CLOCK_EnableClock`.
Читает аппаратный регистр без дебаунса — добавляй дебаунс в вызывающем коде
при механическом детекте.
**Коды возврата:**
| Функция | Код | Условие |
| --------------- | -------------- | --------------------------- |
| `bsp_sd_init` | `BSP_OK` | Host готов к работе |
| `bsp_sd_init` | `BSP_ERR_INIT` | `SD_HostInit` вернул ошибку |
| `bsp_sd_deinit` | `BSP_OK` | Всегда |
--- ---
## Ограничения ## Быстрый старт
- Модуль рассчитан на одну карту (USDHC1, `g_sd` — единственный дескриптор). ```c
- `bsp_sd_is_inserted()` читает аппаратный регистр без дебаунса. При #include "bsp/sd.h"
механическом детекте возможны ложные срабатывания в момент вставки/извлечения —
добавляй дебаунс в вызывающем коде если нужно. if (!bsp_sd_is_inserted()) {
- Hot-swap не поддерживается: `bsp_sd_deinit()` + `bsp_sd_init()` между сессиями. /* карта отсутствует */
}
if (bsp_sd_init() != BSP_OK) {
/* host не инициализирован */
}
/* работа с картой через FatFS... */
bsp_sd_deinit();
```
---
## CMake
```cmake
target_link_libraries(firmware_test PRIVATE bsp_sd)
```
**Зависимости модуля:**
| Зависимость | Тип | Описание |
| ------------------ | ------- | ----------------------------------------------- |
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
| `bsp_sdmmc_config` | PRIVATE | `BOARD_SD_Config`, sdmmc_config.h, sdk_sdmmc_sd |
`sdk_sdmmc_sd` и `sdk_usdhc` — транзитивно через `bsp_sdmmc_config`.

View file

@ -1,228 +1,105 @@
# bsp_sdram — внешняя SDRAM MT48LC16M16A2 (32 МБ) # bsp_sdram — внешняя SDRAM MT48LC16M16A2 (32 МБ)
> Расположение: `bsp/sdram/` Минимальная верификация доступности внешней SDRAM, подключённой к SEMC.
> Публичный заголовок: `bsp/sdram/include/bsp/sdram.h` Подробное тестирование (паттерны, шина адреса/данных, retention) выполняется
> Реализация: `bsp/sdram/src/sdram.c` в тест-модуле `firmware_test/test_sdram.c`, который использует константы
и API этого модуля.
Модуль обеспечивает минимальную верификацию доступности внешней SDRAM,
подключённой к SEMC. Подробное тестирование (паттерны, шина адреса/данных,
retention) выполняется не здесь, а в тест-модуле `firmware_test/test_sdram.c`,
который опирается на константы и API этого модуля.
--- ---
## Аппаратный контекст ## Аппаратура
| Параметр | Значение | | Параметр | Значение |
| --------------------- | ------------------------------------------------ | | ------------------ | ------------------------------------ |
| Чип | MT48LC16M16A2 | | Чип | MT48LC16M16A2 |
| Объём | 32 МБ | | Объём | 32 МБ |
| Ширина шины данных | 16 бит | | Ширина шины данных | 16 бит |
| Интерфейс MCU | SEMC, регион BR0 | | Интерфейс MCU | SEMC, регион BR0 |
| Базовый адрес | `0x80000000` (`BSP_SDRAM_BASE_ADDR`) | | Базовый адрес | `0x80000000` (`BSP_SDRAM_BASE_ADDR`) |
| Конец региона | `0x81FFFFFF` (`+ BSP_SDRAM_SIZE_BYTES = 32 МБ`) |
Карта тестового региона (из `sdram.h`): **Карта тестового региона:**
| Адрес | Назначение | | Адрес | Назначение |
| ------------ | ---------------------------------------------------------- | | ------------ | --------------------------------------------------- |
| `0x80000000` | Начало SDRAM (SEMC BR0) | | `0x80000000` | Начало SDRAM (SEMC BR0) |
| `0x80200000` | `BSP_SDRAM_TEST_BASE_ADDR` — база тестового региона | | `0x80200000` | `BSP_SDRAM_TEST_BASE_ADDR` — база тестового региона |
| `0x81E00000` | Начало non-cacheable региона (USB DMA, 2 MB) | | `0x81E00000` | Начало non-cacheable региона (USB DMA, 2 MB) |
| `0x81FFFFFF` | Конец SDRAM | | `0x81FFFFFF` | Конец SDRAM |
Тестовая база смещена на 2 МБ от начала SDRAM, что согласно комментариям в Тестовая база смещена на 2 МБ от начала — гарантированно выше `.data`/`.bss`
заголовке гарантированно выше `.data`/`.bss` прошивки и ниже non-cacheable прошивки и ниже non-cacheable региона.
региона.
**Важно:** SEMC инициализируется через DCD **до вызова `main()`**. Этот модуль
не настраивает SEMC и не трогает его регистры. Если DCD не отработал —
`bsp_sdram_init()` вернёт ошибку, но исправить ситуацию из модуля нельзя.
--- ---
## Архитектурное ограничение: SEMC инициализируется DCD до `main()` ## API
Модуль **не настраивает** контроллер SEMC и не модифицирует его регистры.
Согласно комментарию в `bsp/sdram/CMakeLists.txt` и `sdram.h`, инициализация
SEMC выполнена через **DCD до вызова `main()`**. Регион SDRAM также описан в
MPU как Normal Write-Back cacheable — соответствующая настройка делается за
пределами этого модуля (исходники модуля её не выполняют).
Следствие для верификации: чтобы проверить именно физическую SDRAM, а не
кэш, при readback в реализации используется явный
`SCB_CleanDCache_by_Addr` + `SCB_InvalidateDCache_by_Addr` + `__DSB()`.
---
## Состав модуля
```
bsp/sdram/
├── include/bsp/sdram.h # публичный заголовок
├── src/sdram.c # реализация bsp_sdram_init()
└── CMakeLists.txt # цель bsp_sdram
```
В `sdram.c` определены только статические вспомогательные функции
(`wait_semc_idle`, `flush_cache_at_test_base`, `verify_word`) и одна публичная
функция `bsp_sdram_init()`. Других публичных операций (например, расширенных
тестов памяти) модуль не предоставляет.
---
## Публичные константы
```c
#define BSP_SDRAM_BASE_ADDR 0x80000000UL /* SEMC BR0 */
#define BSP_SDRAM_SIZE_BYTES 0x02000000UL /* 32 MB */
#define BSP_SDRAM_TEST_BASE_ADDR 0x80200000UL /* +2 MB от базы */
#define BSP_SDRAM_TEST_FAST_SIZE 0x00010000UL /* 64 KB */
#define BSP_SDRAM_TEST_FULL_SIZE 0x00100000UL /* 1 MB */
#define BSP_SDRAM_TEST_EXTENDED_SIZE 0x01B00000UL /* 27 MB */
```
Размеры тестов — это **константы для потребителей**; сам `bsp_sdram` не
запускает по ним внутренние проходы. Например, `BSP_SDRAM_TEST_FAST_SIZE`
используется в `firmware_test/test_sdram.c` (фаза «data bus»). Константы
`BSP_SDRAM_TEST_FULL_SIZE` и `BSP_SDRAM_TEST_EXTENDED_SIZE` определены в
заголовке, но их использование текущими потребителями в дереве не описано —
рассматривайте их как ориентиры из описания карты памяти.
---
## Публичный API
```c ```c
bsp_status_t bsp_sdram_init(void); bsp_status_t bsp_sdram_init(void);
``` ```
Назначение: верифицировать, что SEMC завершил инициализацию (выполненную DCD) **Публичные константы:**
и что SDRAM отвечает по тестовому адресу.
Поведение (из `sdram.c`): ```c
#define BSP_SDRAM_BASE_ADDR 0x80000000UL /* SEMC BR0 */
#define BSP_SDRAM_SIZE_BYTES 0x02000000UL /* 32 MB */
#define BSP_SDRAM_TEST_BASE_ADDR 0x80200000UL /* +2 MB */
#define BSP_SDRAM_TEST_FAST_SIZE 0x00010000UL /* 64 KB */
#define BSP_SDRAM_TEST_FULL_SIZE 0x00100000UL /* 1 MB */
#define BSP_SDRAM_TEST_EXTENDED_SIZE 0x01B00000UL /* 27 MB */
```
1. Ждёт перехода SEMC в состояние IDLE по флагу `SEMC->STS0 & SEMC_STS0_IDLE_MASK`, Константы размеров — для потребителей; `bsp_sdram` не запускает по ним
таймаут — `SDRAM_SEMC_IDLE_TIMEOUT_MS = 10 мс` (через `bsp_tick_get_ms()`). внутренних проходов.
2. Записывает по `BSP_SDRAM_TEST_BASE_ADDR` паттерн `0xA5A5A5A5`,
**Поведение `bsp_sdram_init()`:**
1. Ждёт перехода SEMC в IDLE (`SEMC->STS0 & SEMC_STS0_IDLE_MASK`),
таймаут 10 мс.
2. Записывает `0xA5A5A5A5` по `BSP_SDRAM_TEST_BASE_ADDR`,
делает `SCB_CleanDCache_by_Addr` + `SCB_InvalidateDCache_by_Addr` + `__DSB()`, делает `SCB_CleanDCache_by_Addr` + `SCB_InvalidateDCache_by_Addr` + `__DSB()`,
читает обратно и сверяет. читает обратно и сверяет.
3. Повторяет то же для инверсного паттерна `0x5A5A5A5A`. 3. Повторяет для инверсного паттерна `0x5A5A5A5A`.
4. При успехе устанавливает внутренний флаг готовности и возвращает `BSP_OK`.
Коды возврата: **Коды возврата:**
| Код | Когда | | Код | Условие |
| ----------------- | --------------------------------------------------------------------- | | ----------------- | ------------------------------------------- |
| `BSP_OK` | SDRAM доступна, оба паттерна успешно прочитаны обратно. | | `BSP_OK` | SDRAM доступна, оба паттерна совпали |
| `BSP_ERR_TIMEOUT` | SEMC не перешёл в IDLE за `SDRAM_SEMC_IDLE_TIMEOUT_MS` (10 мс). | | `BSP_ERR_TIMEOUT` | SEMC не перешёл в IDLE за 10 мс |
| `BSP_ERR_INIT` | Хотя бы один паттерн не совпал при readback (DCD/SDRAM не готовы). | | `BSP_ERR_INIT` | Хотя бы один паттерн не совпал при readback |
`bsp_sdram_init()` затрагивает только 4 байта по адресу
`BSP_SDRAM_TEST_BASE_ADDR` (две записи 32-битных слов) и не пересекается с
`.data`/`.bss` прошивки благодаря смещению на 2 МБ от базы.
--- ---
## Порядок использования ## Быстрый старт
```c ```c
#include "bsp/sdram.h" #include "bsp/sdram.h"
if (bsp_sdram_init() != BSP_OK) { if (bsp_sdram_init() != BSP_OK) {
/* SEMC/SDRAM недоступны — это критическая ошибка для прошивки,
которая использует SDRAM под фреймбуферы и тестовые регионы. */
handle_critical_error(); handle_critical_error();
} }
/* Дальше — обычная работа с памятью по адресам внутри /* Работа с памятью по адресам внутри
[BSP_SDRAM_BASE_ADDR, BSP_SDRAM_BASE_ADDR + BSP_SDRAM_SIZE_BYTES). */ [BSP_SDRAM_BASE_ADDR, BSP_SDRAM_BASE_ADDR + BSP_SDRAM_SIZE_BYTES) */
``` ```
Полноценные тесты памяти (шина адреса, шина данных, sequential, retention)
запускаются отдельным тест-модулем — см. раздел «Связь с firmware_test».
--- ---
## Зависимости и CMake ## CMake
```cmake ```cmake
# bsp/sdram/CMakeLists.txt target_link_libraries(firmware_test PRIVATE bsp_sdram)
add_library(bsp_sdram STATIC src/sdram.c)
target_include_directories(bsp_sdram
PUBLIC include/
PRIVATE src/)
target_link_libraries(bsp_sdram
PUBLIC bsp_status
PRIVATE bsp_board bsp_tick sdk_semc)
``` ```
- `bsp_status` (PUBLIC) — `bsp_status_t` в публичном API. **Зависимости модуля:**
- `bsp_tick` (PRIVATE) — `bsp_tick_get_ms()` для таймаута SEMC IDLE.
- `sdk_semc` (PRIVATE) — `fsl_semc.h`, нужен для `SEMC->STS0` и
`SEMC_STS0_IDLE_MASK` при проверке готовности контроллера.
- `bsp_board` (PRIVATE) — общие board-уровневые символы.
Цель не собирается при `BUILD_TESTS_HOST=ON` (host-сборка), `CMakeLists.txt` | Зависимость | Тип | Описание |
содержит ранний `return()`. | ------------ | ------- | -------------------------------------------------- |
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
Потребитель (пример из `firmware/test/CMakeLists.txt`): | `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаута SEMC IDLE |
| `sdk_semc` | PRIVATE | `fsl_semc.h``SEMC->STS0`, `SEMC_STS0_IDLE_MASK` |
```cmake | `bsp_board` | PRIVATE | Общие board-уровневые символы |
target_link_libraries(firmware_test PRIVATE
...
bsp_sdram
...
)
```
---
## Связь с firmware_test (`test_sdram.c`)
Тест-модуль `firmware/test/src/tests/test_sdram.c` использует этот BSP как
основу:
- В `init`-фазе модуль вызывает `bsp_sdram_init()` и сохраняет результат
в `g_s_ready`. При неуспехе `run` сразу возвращает FAIL с
`detail = "SEMC not ready — DCD failed?"`.
- Базовый адрес тестового региона берётся из `BSP_SDRAM_TEST_BASE_ADDR`.
- Размер фазы «data bus» — `BSP_SDRAM_TEST_FAST_SIZE` (64 KB).
- Остальные фазы (`address bus`, `sequential`, `retention`) используют
свои локальные константы (`SDRAM_ADDR_BUS_BITS`, `SDRAM_SEQUENTIAL_SIZE`,
`SDRAM_RETENTION_SIZE`), определённые в `test_sdram.c`, не в BSP.
- Cache maintenance в тестовом модуле повторяет ту же схему, что в BSP:
`SCB_CleanDCache_by_Addr` + `SCB_InvalidateDCache_by_Addr` + `__DSB()`.
Дескриптор тест-модуля:
```c
const test_module_t K_TEST_SDRAM = {
.id = "sdram",
.name = "SDRAM 32 MB",
.critical = true,
.requires_hil = false,
...
};
```
---
## Ограничения и замечания
- Модуль **не выполняет** инициализацию SEMC, DCD или MPU. Если соответствующие
механизмы не отработали до `main()`, `bsp_sdram_init()` вернёт ошибку, но
починить ситуацию из этого модуля нельзя — корень проблемы в DCD / startup
/ clock-config.
- Реентрантность `bsp_sdram_init()` не описана и не гарантируется: в прошивке
вызов выполняется однократно на этапе инициализации.
- Тайм-аут IDLE (`10 мс`) рассчитан на здоровый контроллер; в случае реального
отказа SEMC именно эта величина определяет, через сколько `BSP_ERR_TIMEOUT`
будет возвращён.
- Cache maintenance в `verify_word()` работает только по одной кэш-линии
(32 байта), и `BSP_SDRAM_TEST_BASE_ADDR = 0x80200000` подобран кратным
размеру кэш-линии Cortex-M7 — иначе вызовы `SCB_*_by_Addr` потребовали бы
выравнивания.
- Связь между константами размеров (`BSP_SDRAM_TEST_FULL_SIZE`,
`BSP_SDRAM_TEST_EXTENDED_SIZE`) и конкретными сценариями тестирования не
гарантируется этим BSP — это значения из карты памяти, которые потребитель
может использовать или игнорировать. Точная стратегия тестов SDRAM
определена в `firmware_test/test_sdram.c`.

View file

@ -1,82 +1,104 @@
# bsp_tick — интеграция в firmware-проекты # bsp_tick — системный таймер
`SysTick_Handler` определён внутри `tick.c` и принадлежит модулю `bsp_tick`. SysTick-based таймер с миллисекундным счётчиком. Используется всеми BSP-модулями
Для добавления внешней логики в обработчик используй слабый хук (см. ниже). для таймаутов и периодических операций. Совместим с FreeRTOS.
--- ---
## firmware/test и firmware/bootloader (bare-metal) ## API
**CMakeLists.txt**: ```c
void bsp_tick_init(void);
uint32_t bsp_tick_get_ms(void);
void bsp_delay(uint32_t ms);
void bsp_tick_inc(void); /* вызывать из SysTick_Handler / vApplicationTickHook */
```
`bsp_tick_get_ms()` корректно обрабатывает wraparound (`uint32_t` переполняется
через ~49 суток) — используй паттерн с вычитанием:
```c
uint32_t start = bsp_tick_get_ms();
while ((bsp_tick_get_ms() - start) < TIMEOUT_MS) { /* ждать */ }
```
---
## Быстрый старт
```c
#include "bsp/tick.h"
/* Таймаут: */
uint32_t start = bsp_tick_get_ms();
while (!done) {
if ((bsp_tick_get_ms() - start) >= TIMEOUT_MS) { break; }
}
/* Блокирующая пауза: */
bsp_delay(10);
/* Периодическое действие без блокировки: */
static uint32_t s_last_ms = 0U;
if ((bsp_tick_get_ms() - s_last_ms) >= 500U) {
s_last_ms = bsp_tick_get_ms();
/* действие */
}
```
---
## Интеграция
### bare-metal (`firmware/test`, `firmware/bootloader`)
`SysTick_Handler` определён в `tick.c` и принадлежит модулю целиком.
```cmake ```cmake
target_link_libraries(firmware_test PRIVATE bsp_tick) target_link_libraries(firmware_test PRIVATE bsp_tick)
``` ```
**Порядок инициализации в main()**:
```c ```c
board_hw_init(); // тактирование и пины (BOARD_BootClockRUN внутри) /* main.c — порядок инициализации: */
bsp_tick_init(); // SysTick — после того как SystemCoreClock актуален board_hw_init(); /* устанавливает SystemCoreClock */
bsp_uart_init(115200); // и далее всё что зависит от времени bsp_tick_init(); /* после board_hw_init() */
``` ```
--- ### FreeRTOS (`firmware/tft_app`)
## firmware/tft_app (FreeRTOS) В FreeRTOS-режиме SysTick захватывается планировщиком. `bsp_tick_init()`
no-op, `bsp_tick_inc()` вызывается из `vApplicationTickHook`.
**FreeRTOSConfig.h** — убедиться:
```c ```c
#define configTICK_RATE_HZ 1000 // 1 тик = 1 мс /* FreeRTOSConfig.h */
#define configUSE_TICK_HOOK 1 // включить vApplicationTickHook #define configTICK_RATE_HZ 1000
#define configUSE_TICK_HOOK 1
``` ```
**board.c** — добавить hook (см. раздел про хуки ниже):
```c ```c
/* board.c */
#include "bsp/tick.h" #include "bsp/tick.h"
void vApplicationTickHook(void) void vApplicationTickHook(void)
{ {
bsp_tick_inc(); // no-op в FreeRTOS-режиме, но оставляем для единообразия bsp_tick_inc();
} }
``` ```
**CMakeLists.txt**:
```cmake ```cmake
target_link_libraries(tft_app PRIVATE bsp_tick freertos_kernel) target_link_libraries(tft_app PRIVATE bsp_tick freertos_kernel)
target_compile_definitions(tft_app PRIVATE BSP_TICK_FREERTOS_MODE) target_compile_definitions(tft_app PRIVATE BSP_TICK_FREERTOS_MODE)
``` ```
**Инициализация**: ### Добавление логики в SysTick — weak hook
`tick.c` объявляет `bsp_systick_hook()` с атрибутом `weak`. Определи
эту функцию в любом `.c` файле проекта — линкер подхватит автоматически.
Никаких изменений в `tick.c` не требуется.
```c ```c
board_hw_init(); /* tick.c (уже реализовано): */
bsp_tick_init(); // no-op, но вызываем для симметрии с bare-metal __attribute__((weak)) void bsp_systick_hook(void) {}
vTaskStartScheduler(); // FreeRTOS берёт SysTick себе здесь
```
---
## Добавление логики в SysTick — weak hook
Каждый BSP-модуль владеет своим прерыванием целиком. `SysTick_Handler`
живёт в `tick.c` и принадлежит `bsp_tick`. Если другому модулю нужно
выполнять работу каждый тик (watchdog, программный таймер и т.п.) —
используй слабый хук, не трогая `tick.c`.
**Как это работает:**
`tick.c` объявляет и вызывает `bsp_systick_hook()` с атрибутом `weak`.
Если никто не определил эту функцию — линкер подставляет пустую
заглушку, накладные расходы нулевые. Как только в любом `.c` файле
проекта появляется сильное определение — оно автоматически подхватывается.
```c
// tick.c (уже реализовано):
__attribute__((weak)) void bsp_systick_hook(void) { /* no-op по умолчанию */ }
void SysTick_Handler(void) void SysTick_Handler(void)
{ {
@ -85,54 +107,27 @@ void SysTick_Handler(void)
} }
``` ```
### **Пример: watchdog из board.c**
```c ```c
// board.c /* board.c — пример: watchdog из хука */
#include "bsp/tick.h"
#include "bsp/wdog.h"
// Переопределяем слабый хук — линкер возьмёт эту версию
void bsp_systick_hook(void) void bsp_systick_hook(void)
{ {
bsp_wdog_feed(); bsp_wdog_feed();
} }
``` ```
### **Пример: два действия в хуке** **Важно:** хук вызывается из ISR-контекста. Блокирующие операции, мьютексы
и `bsp_delay()` внутри запрещены.
```c
// board.c
void bsp_systick_hook(void)
{
bsp_wdog_feed();
bsp_some_other_periodic_task();
}
```
**Важно:** хук вызывается из ISR-контекста. Никаких блокирующих
операций, мьютексов или `bsp_delay()` внутри.
--- ---
## Использование в BSP-модулях и приложении ## CMake
```c ```cmake
#include "bsp/tick.h" target_link_libraries(firmware_test PRIVATE bsp_tick)
// Таймаут (wraparound-safe):
uint32_t start = bsp_tick_get_ms();
while (!done) {
if ((bsp_tick_get_ms() - start) >= TIMEOUT_MS) { break; }
}
// Блокирующая пауза (инициализация, datasheet-задержки):
bsp_delay(10);
// Периодическое действие без блокировки основного цикла:
static uint32_t s_last_ms = 0U;
if ((bsp_tick_get_ms() - s_last_ms) >= 500U) {
s_last_ms = bsp_tick_get_ms();
// ... действие ...
}
``` ```
**Зависимости модуля:**
| Зависимость | Тип | Описание |
| ----------- | ------ | ------------------------------ |
| `bsp_board` | PUBLIC | Транзитивно: `SystemCoreClock` |

View file

@ -1,109 +1,153 @@
# bsp_uart_host # bsp_uart_host — LPUART1 (MCU-Link VCOM)
Коммуникационный канал с хост-машиной через LPUART1 (разъём J2, MCU-Link VCOM). Коммуникационный канал с хост-машиной через LPUART1 (разъём J2, MCU-Link VCOM).
Используется для HIL-тестов (pytest + pyserial), отладочного вывода и резервного
Применяется для HIL-тестов (pytest + pyserial), отладочного вывода и резервного
канала связи. канала связи.
--- ---
## Аппаратура
| Сигнал | Пин MCU | Корпус | Интерфейс | Назначение |
| ---------- | ------------- | ------ | ---------- | -------------- |
| LPUART1_TX | GPIO_AD_B0_12 | K14 | LPUART1 TX | MCU → MCU-Link |
| LPUART1_RX | GPIO_AD_B0_13 | L14 | LPUART1 RX | MCU-Link → MCU |
Пины настроены в `BOARD_InitPins()` (`generated/pin_mux.c`).
---
## Архитектура ## Архитектура
```bash ```mermaid
[LPUART1 RX] → LPUART1_IRQHandler → ring_buffer_put() flowchart TD
subgraph TX
bsp_uart_host_read() ← polling + таймаут A["bsp_uart_host_write()"] --> B["LPUART_WriteBlocking()\nблокирующий polling"]
bsp_uart_host_read_byte() B --> C["LPUART1 TX → MCU-Link VCOM"]
end
[LPUART1 TX] ← bsp_uart_host_write() ← LPUART_WriteBlocking() subgraph RX
D["MCU-Link VCOM → LPUART1 RX"] --> E["LPUART1_IRQHandler"]
E --> F["ring_buffer_put()"]
F --> G["bsp_uart_host_read()\nbsp_uart_host_read_byte()\npolling + таймаут"]
end
``` ```
- **TX** — blocking polling (`LPUART_WriteBlocking`). Пакеты короткие, задержка 12 мс приемлема. - **TX**`LPUART_WriteBlocking`. Пакеты короткие, задержка 12 мс приемлема.
- **RX** — ISR пишет в ring buffer, задача/main читает с таймаутом. - **RX** — ISR пишет в ring buffer; main loop читает с таймаутом.
- **ISR**`LPUART1_IRQHandler` определён в модуле, модуль владеет прерыванием целиком. - **ISR**`LPUART1_IRQHandler` определён в модуле, владеет прерыванием целиком.
- **Singleton** — один экземпляр, один физический UART. - **Singleton** — один экземпляр, один физический UART.
--- ---
## API
```c
bsp_status_t bsp_uart_host_init(uint32_t baud);
bsp_status_t bsp_uart_host_write(const uint8_t *p_data, size_t len);
bsp_status_t bsp_uart_host_write_str(const char *p_str);
size_t bsp_uart_host_read(uint8_t *p_buf, size_t len, uint32_t timeout_ms);
int32_t bsp_uart_host_read_byte(uint32_t timeout_ms);
```
`bsp_uart_host_read()` возвращает фактически прочитанное количество байт —
частичное чтение при таймауте не является ошибкой.
```c
/* Неблокирующий опрос */
bsp_uart_host_read(buf, len, 0);
/* Ждать с таймаутом */
bsp_uart_host_read(buf, len, 100);
/* Ждать вечно */
bsp_uart_host_read(buf, len, BSP_UART_HOST_WAIT_FOREVER);
```
---
## Быстрый старт ## Быстрый старт
```c ```c
#include "bsp/uart_host.h" #include "bsp/uart_host.h"
// В main(), после board_hw_init(): /* После board_hw_init(): */
bsp_uart_host_init(115200); bsp_uart_host_init(115200);
// TX /* TX */
bsp_uart_host_write_str("hello\r\n"); bsp_uart_host_write_str("hello\r\n");
// RX — ждать байт до 100 мс /* RX — ждать байт до 100 мс */
int32_t byte = bsp_uart_host_read_byte(100); int32_t byte = bsp_uart_host_read_byte(100);
if (byte < 0) { /* таймаут */ } if (byte < 0) { /* таймаут */ }
// RX — прочитать пакет целиком /* RX — прочитать пакет */
uint8_t buf[64]; uint8_t buf[64];
size_t n = bsp_uart_host_read(buf, sizeof(buf), 500); size_t n = bsp_uart_host_read(buf, sizeof(buf), 500);
``` ```
--- ---
## Конфигурация ## Тестирование
Задаётся в CMakeLists.txt **firmware-таргета**, не модуля: ### Host unit-тесты (Humble Object)
Модуль предоставляет fff-заглушки в `bsp/uart_host/mocks/`. В тестовой сборке
вместо `uart_host.c` линкуется `mocks/uart_host_mock.c`.
```cmake ```cmake
target_compile_definitions(firmware_test PRIVATE add_host_test(
BSP_UART_HOST_RX_BUFFER_SIZE=256 # степень двойки, дефолт 256 NAME uart_host_mock_example
BSP_UART_HOST_SRC_CLOCK_HZ=24000000 SOURCES uart_host/test_uart_host.c
BSP_UART_HOST_IRQ_PRIORITY=5 ${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks/uart_host_mock.c
INCLUDES
${CMAKE_SOURCE_DIR}/bsp/uart_host/include
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks
${CMAKE_SOURCE_DIR}/bsp/common/include
) )
``` ```
| Define | Дефолт | Описание |
| ------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------- |
| `BSP_UART_HOST_RX_BUFFER_SIZE` | `256` | Размер RX ring buffer. **Должен быть степенью двойки.** |
| `BSP_UART_HOST_SRC_CLOCK_HZ` | `24000000` | Частота источника тактирования LPUART1. |
| `BSP_UART_HOST_IRQ_PRIORITY` | `5` | Приоритет `LPUART1_IRQn`. Должен быть ≥ `configMAX_SYSCALL_INTERRUPT_PRIORITY` при использовании FreeRTOS. |
---
## Таймауты
```c ```c
// Без ожидания — вернёт только то, что уже есть в буфере #include "bsp/uart_host_mock.h"
bsp_uart_host_read(buf, len, 0);
// Ждать с таймаутом (межбайтовый: сбрасывается после каждого принятого байта) void setUp(void) { UART_HOST_MOCK_RESET_ALL(); }
bsp_uart_host_read(buf, len, 100);
// Ждать вечно void test_something(void) {
bsp_uart_host_read(buf, len, BSP_UART_HOST_WAIT_FOREVER); bsp_uart_host_write_fake.return_val = BSP_OK;
/* ... */
TEST_ASSERT_EQUAL(1, bsp_uart_host_write_fake.call_count);
}
``` ```
`bsp_uart_host_read()` возвращает `size_t` — частичное чтение при таймауте ### HIL-тесты
не является ошибкой, caller сам решает что делать с полученным количеством байт.
C-прошивка: `tests/target/host_uart/` — CLI через LPUART1.
pytest: `tools/hil/01_test_uart.py` — PING/ECHO/BUF_SIZE через pyserial.
```bash
just host::hil-uart
```
--- ---
## FreeRTOS ## Интеграция
Модуль работает в FreeRTOS без отдельной реализации. При сборке с | Контекст | TX | RX |
`BSP_TICK_FREERTOS_MODE` в цикле ожидания добавляется `vTaskDelay(1)` | ---------- | --------------------- | ------------------------------ |
задача отдаёт управление планировщику вместо busy-wait. | bare-metal | `write()` — blocking | `read()` — polling с таймаутом |
| FreeRTOS | `write()` — из задачи | `read()` — из задачи с yield |
`BSP_UART_HOST_IRQ_PRIORITY` должен быть установлен ниже В FreeRTOS-режиме (`BSP_TICK_FREERTOS_MODE`) цикл ожидания добавляет
`configMAX_SYSCALL_INTERRUPT_PRIORITY` (числовое значение выше). `vTaskDelay(1)` вместо busy-wait. `BSP_UART_HOST_IRQ_PRIORITY` должен быть
выше `configMAX_SYSCALL_INTERRUPT_PRIORITY` (числовое значение ниже).
--- ---
## Подключение ## CMake
```cmake ```cmake
# bsp/CMakeLists.txt
add_subdirectory(common)
add_subdirectory(uart_host)
# firmware/test/CMakeLists.txt
target_link_libraries(firmware_test PRIVATE target_link_libraries(firmware_test PRIVATE
bsp_board bsp_board
bsp_tick bsp_tick
@ -111,50 +155,17 @@ target_link_libraries(firmware_test PRIVATE
) )
``` ```
--- Конфигурация задаётся в CMakeLists.txt **потребителя**, не модуля:
## Тестирование
Для host unit-тестов модуль предоставляет fff-заглушки через **Humble Object**:
в тестовой сборке вместо `uart_host.c` линкуется `mocks/uart_host_mock.c`.
```cmake ```cmake
# tests/host/CMakeLists.txt target_compile_definitions(firmware_test PRIVATE
add_host_test( BSP_UART_HOST_RX_BUFFER_SIZE=256 # степень двойки
NAME BSP_UART_HOST_SRC_CLOCK_HZ=24000000
uart_host_mock_example BSP_UART_HOST_IRQ_PRIORITY=5
SOURCES )
${CMAKE_CURRENT_SOURCE_DIR}/uart_host/test_uart_host.c
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks/uart_host_mock.c
# Если тестируете "protocol.c" который использует uart_host:
# ${CMAKE_SOURCE_DIR}/bsp/protocol/src/protocol.c
INCLUDES
${CMAKE_SOURCE_DIR}/bsp/uart_host/include # bsp/uart_host.h
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks # uart_host_mock.h
${CMAKE_SOURCE_DIR}/bsp/common/include # bsp/status.h
# ${CMAKE_SOURCE_DIR}/bsp/protocol/include # protocol.h
MOCKS
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks/uart_host_mock.c)
``` ```
```c **Зависимости модуля:**
#include "fff.h"
DEFINE_FFF_GLOBALS;
#include "bsp/uart_host_mock.h"
void setUp(void) { UART_HOST_MOCK_RESET_ALL(); }
void test_something(void) {
bsp_uart_host_write_fake.return_val = BSP_OK;
// ... вызываем тестируемый код ...
TEST_ASSERT_EQUAL(1, bsp_uart_host_write_fake.call_count);
}
```
---
## Зависимости
| Зависимость | Тип | Описание | | Зависимость | Тип | Описание |
| --------------------- | ------- | --------------------------------- | | --------------------- | ------- | --------------------------------- |

View file

@ -1,123 +1,85 @@
# bsp_usb_cdc — USB CDC ACM (Virtual COM Port) # bsp_usb_cdc — USB CDC ACM (Virtual COM Port)
USB CDC ACM device на USB1 (EHCI0). Хост видит устройство как виртуальный COM-порт USB CDC ACM device на USB1 (EHCI0). Хост видит устройство как виртуальный
(`/dev/ttyACM*` на Linux/macOS, `COMx` на Windows). COM-порт (`/dev/ttyACM*` на Linux/macOS, `COMx` на Windows). Используется
для передачи данных между платой и ПК: CLI команды, отладочные лог-каналы.
Используется для передачи данных между платой и ПК: отладочные лог-каналы, Работает параллельно с `bsp_uart_host` (LPUART1) — два независимых канала.
CLI команды, обновление конфигурации. Работает параллельно с `bsp_uart_host`
(LPUART1) — два независимых канала.
--- ---
## Аппаратура ## Аппаратура
| Сигнал | Пин MCU | Назначение | | Сигнал | Пин MCU | Назначение |
| ------------- | ------------- | -------------------------- | | ------------- | ------------- | ----------- |
| USB_OTG1_DN | USB_OTG1_DN | USB1 Data | | USB_OTG1_DN | USB_OTG1_DN | USB1 Data |
| USB_OTG1_DP | USB_OTG1_DP | USB1 Data+ | | USB_OTG1_DP | USB_OTG1_DP | USB1 Data+ |
| USB_OTG1_VBUS | USB_OTG1_VBUS | VBUS detect (self-powered) | | USB_OTG1_VBUS | USB_OTG1_VBUS | VBUS detect |
Встроенный HS PHY (480 MHz PLL). Контроллер: EHCI0 (`kUSB_ControllerEhci0`). Встроенный HS PHY (480 MHz PLL). Контроллер: EHCI0 (`kUSB_ControllerEhci0`).
Скорость: High-Speed (480 Mbit/s) при поддержке хоста, fallback Full-Speed (12 Mbit/s). Скорость: High-Speed (480 Mbit/s), fallback Full-Speed (12 Mbit/s).
PHY калибровка: `D_CAL=0x0C`, `TXCAL45DP=0x06`, `TXCAL45DM=0x06`.
USB PHY калибровка: `D_CAL=0x0C`, `TXCAL45DP=0x06`, `TXCAL45DM=0x06` — стандартные **VID/PID**: `0x1234` / `0x0001` — placeholder, заменить на производственные.
значения для EVKB, подходят для кабелей до 3 м.
**VID/PID**: `0x1234` / `0x0001` (placeholder, заменить на производственные).
--- ---
## Архитектура ## Архитектура
```bash ```mermaid
bsp_usb_cdc_write() flowchart TD
subgraph TX
memcpy → s_sendBuf (NonCacheable OCRAM) A["bsp_usb_cdc_write()"] --> B["memcpy → s_sendBuf\n(NonCacheable OCRAM)"]
B --> C["USB_DeviceSendRequest()"]
USB_DeviceSendRequest() C --> D["EHCI0 DMA → USB1_DP/DN → Host"]
D --> E["BulkIn callback\ns_txIdle = 1 (volatile)"]
[EHCI0 DMA] → USB1_DP/DN → Host end
Host → USB1_DP/DN → [EHCI0 DMA] subgraph RX
F["Host → USB1_DP/DN → EHCI0 DMA"] --> G["USB_OTG1_IRQHandler\nBulkOut callback"]
USB_OTG1_IRQHandler → BulkOut callback G --> H["s_recvBuf (NonCacheable OCRAM)\ns_recvSize = len (volatile)"]
H --> I["bsp_usb_cdc_read()\nmain loop polling"]
s_recvBuf (NonCacheable OCRAM) end
s_recvSize = len (volatile)
bsp_usb_cdc_read() ← main loop polling
``` ```
Все DMA-буферы (`s_sendBuf`, `s_recvBuf`, дескрипторы) размещены в секции Все DMA-буферы размещены в секции `NonCacheable` (OCRAM `0x20200000`).
`NonCacheable` (OCRAM `0x20200000`). MPU region 9 настраивает эту область MPU region 9 настраивает эту область как Normal non-cacheable — записи CPU
как Normal non-cacheable — записи CPU видны DMA без `SCB_CleanDCache()`. видны DMA без `SCB_CleanDCache()`.
--- **Lite stack** — сознательное решение вместо full NXP class framework:
## USB стек — lite архитектура
Модуль использует **lite** вариант NXP USB стека (не full class framework).
Это сознательное решение:
| Аспект | Full stack | Lite stack (наш выбор) | | Аспект | Full stack | Lite stack (наш выбор) |
| ------------------ | -------------------------------------- | ----------------------------- | | --------------- | --------------------- | ---------------------- |
| Class framework | `usb_device_class.h`, `class_handle_t` | Отсутствует | | Class framework | `usb_device_class.h` | Отсутствует |
| `usb_device_ch9.c` | SDK middleware, тянет class driver | Приватная копия в `src/` |
| CDC ACM хедер | Полный: struct + API функции | Только define-ы request codes |
| Callbacks | Через class driver dispatch | Напрямую в `usb_cdc.c` |
| Размер кода | ~12 KB | ~6 KB | | Размер кода | ~12 KB | ~6 KB |
| Гибкость | Multi-class composite | Один CDC ACM | | Гибкость | Multi-class composite | Один CDC ACM |
Lite stack достаточен для одного CDC ACM интерфейса. Переход на full stack Переход на full stack понадобится только при добавлении composite device (CDC + MSC).
понадобится только при добавлении composite device (CDC + MSC).
### Стек зависимостей ---
```bash ## API
bsp_usb_cdc
├── src/usb_cdc.c ← BSP API + USB device callbacks ```c
├── src/usb_cdc_descriptors.c ← дескрипторы + descriptor callbacks bsp_status_t bsp_usb_cdc_init(void);
├── src/usb_cdc_hw.c ← clock, PHY, IRQ handler bool bsp_usb_cdc_is_ready(void);
├── src/usb_device_ch9.c ← lite Chapter 9 (приватная копия) bool bsp_usb_cdc_write_ready(void);
├── SDK (PRIVATE): bsp_status_t bsp_usb_cdc_write(const uint8_t *p_data, size_t len);
│ ├── sdk_usb_device_ehci ← EHCI контроллер + DCI абстракция size_t bsp_usb_cdc_read(uint8_t *p_buf, size_t max_len);
│ │ ├── usb_device_ehci.c void bsp_usb_cdc_poll(void); /* зарезервировано */
│ │ └── usb_device_dci.c
│ ├── sdk_usb_phy ← USB PHY инициализация
│ │ └── usb_phy.c
│ └── sdk_osa_bm ← OS Abstraction (bare-metal)
│ ├── fsl_os_abstraction_bm.c
│ └── fsl_component_generic_list.c
└── Приватные конфиги в src/:
├── usb_device_config.h ← EHCI=1, CDC_ACM=1, endpoints=4
├── fsl_os_abstraction_config.h ← bare-metal OSA конфиг
├── usb_device_descriptor.h ← VID/PID, endpoint numbers
├── usb_device_ch9.h ← lite ch9 API (1 arg)
└── usb_device_cdc_acm.h ← lite: только CDC request codes
``` ```
### Проброс конфиг-хедеров (sdk_usb_config) **`bsp_usb_cdc_is_ready()`** — `true` когда enumeration завершён **и** хост
открыл COM-порт (DTR установлен через `SET_CONTROL_LINE_STATE`).
NXP USB middleware при компиляции ищет `usb_device_config.h` и **Коды возврата `bsp_usb_cdc_write()`:**
`fsl_os_abstraction_config.h` через include path. Эти файлы —
application-specific, живут в `bsp/usb_cdc/src/`.
Проблема: SDK таргеты (`sdk_usb_device_ehci`, `sdk_usb_phy`, `sdk_osa_bm`) | Код | Условие |
компилируются независимо от `bsp_usb_cdc` и не видят его include paths. | ------------------- | ------------------------------------------ |
| `BSP_OK` | Transfer поставлен в очередь |
Решение: INTERFACE библиотека `sdk_usb_config` в `sdk/CMakeLists.txt`: | `BSP_ERR_BUSY` | Предыдущий transfer не завершён |
| `BSP_ERR_NOT_READY` | Хост не подключён |
```cmake | `BSP_ERR_INVALID` | `data == NULL`, `len == 0` или `len > 512` |
add_library(sdk_usb_config INTERFACE)
target_include_directories(sdk_usb_config SYSTEM
INTERFACE ${CMAKE_SOURCE_DIR}/bsp/usb_cdc/src)
```
Все SDK USB таргеты линкуют `sdk_usb_config` и находят конфиг-хедеры при
компиляции. Циклических зависимостей нет — `sdk_usb_config` не содержит кода.
--- ---
@ -129,152 +91,16 @@ target_include_directories(sdk_usb_config SYSTEM
/* После board_hw_init() + bsp_tick_init(): */ /* После board_hw_init() + bsp_tick_init(): */
bsp_usb_cdc_init(); bsp_usb_cdc_init();
/* Ждём подключения хоста */ while (!bsp_usb_cdc_is_ready()) { /* ждём enumeration */ }
while (!bsp_usb_cdc_is_ready()) {
/* USB enumeration в процессе */
}
/* TX — неблокирующая отправка */ /* TX — неблокирующая отправка */
const char *msg = "Hello from TFT Board\r\n"; const char *msg = "Hello\r\n";
bsp_usb_cdc_write((const uint8_t *)msg, strlen(msg)); bsp_usb_cdc_write((const uint8_t *)msg, strlen(msg));
/* RX — polling в main loop */ /* RX — polling в main loop */
uint8_t buf[64]; uint8_t buf[64];
size_t n = bsp_usb_cdc_read(buf, sizeof(buf)); size_t n = bsp_usb_cdc_read(buf, sizeof(buf));
if (n > 0) { if (n > 0) { /* обработать buf[0..n-1] */ }
/* обработать buf[0..n-1] */
}
```
---
## API
### `bsp_usb_cdc_init()`
Полная инициализация: USB PHY clock 480 MHz → EHCI0 init → endpoint registration →
NVIC enable → USB_DeviceRun. Включает задержку 5 мс для стабилизации DP pull-down.
**Предусловие**: `board_hw_init()` вызван (MPU настроен, NonCacheable регион активен).
Возвращает `BSP_OK` или `BSP_ERR_HW`.
### `bsp_usb_cdc_is_ready()`
`true` когда USB enumeration завершён **и** хост открыл COM-порт (DTR установлен
через `SET_CONTROL_LINE_STATE`). До этого момента `write()` вернёт `BSP_ERR_NOT_READY`.
### `bsp_usb_cdc_write(data, len)`
Неблокирующая отправка. Копирует данные в NonCacheable TX буфер и ставит в очередь
USB IN transfer. Максимум `BSP_USB_CDC_MAX_PACKET_SIZE` (512) байт за вызов.
| Возврат | Условие |
| ------------------- | ------------------------------------------ |
| `BSP_OK` | Transfer поставлен в очередь |
| `BSP_ERR_BUSY` | Предыдущий transfer не завершён |
| `BSP_ERR_NOT_READY` | Хост не подключён |
| `BSP_ERR_INVALID` | `data == NULL`, `len == 0` или `len > 512` |
Проверить готовность TX канала перед отправкой: `bsp_usb_cdc_write_ready()`.
### `bsp_usb_cdc_write_ready()`
`true` если предыдущий TX transfer завершён и хост подключён.
Удобно для non-blocking write loop:
```c
if (bsp_usb_cdc_write_ready()) {
bsp_usb_cdc_write(data, len);
}
```
### `bsp_usb_cdc_read(buf, max_len)`
Неблокирующее чтение. Забирает данные из RX буфера, заполненного USB OUT ISR callback.
Автоматически перепланирует следующий OUT transfer. Возвращает количество прочитанных
байт (0 если данных нет).
```c
/* Polling в main loop: */
uint8_t buf[64];
size_t n = bsp_usb_cdc_read(buf, sizeof(buf));
```
### `bsp_usb_cdc_poll()`
Зарезервировано. Для bare-metal на EHCI NXP стек обрабатывает всё в ISR.
Для будущего использования с `USB_DEVICE_CONFIG_USE_TASK`.
---
## NonCacheable память
USB EHCI DMA требует некэшируемые буферы. Модуль размещает буферы через макросы
`USB_DMA_INIT_DATA_ALIGN()` и `USB_DMA_NONINIT_DATA_ALIGN()`, которые помещают
данные в секции `NonCacheable.init` и `NonCacheable`.
Линкер-скрипт размещает эти секции в OCRAM (`m_data2`, `0x20200000`).
`board_mpu_init()` настраивает MPU region 9 для этой области.
Проверка: `firmware_test/main.c` содержит `ncache_test_run()` — верификация
что NonCacheable буфер физически попадает в ожидаемый регион.
**Объём**: ~2.5 KB (два bulk буфера по 512 байт + дескрипторы + ACM info +
setup buffer). При NonCacheable регионе 8 KB запас достаточный.
---
## ISR и синхронизация
```bash
USB_OTG1_IRQHandler (usb_cdc_hw.c)
└── USB_DeviceEhciIsrFunction() (SDK)
├── BulkOut callback → s_recvSize = len (volatile)
├── BulkIn callback → s_txIdle = 1 (volatile)
└── DeviceCallback → s_cdcState.attach (volatile)
```
Синхронизация между ISR и main loop:
- **RX**: `bsp_usb_cdc_read()` входит в critical section (`DisableGlobalIRQ`),
копирует `s_recvSize`, сбрасывает в 0, выходит. Копирование из `s_recvBuf`
происходит после выхода из critical section.
- **TX**: `s_txIdle` — volatile flag, устанавливается в BulkIn callback (ISR),
проверяется в `bsp_usb_cdc_write()` (main loop). Гонка исключена: write
сбрасывает flag перед `USB_DeviceSendRequest`.
---
## FreeRTOS
Модуль работает без изменений в контексте FreeRTOS-задачи:
| Контекст | TX | RX |
| ---------- | ------------------------------------------- | ------------------------------ |
| bare-metal | `bsp_usb_cdc_write()` — non-blocking | `bsp_usb_cdc_read()` — polling |
| FreeRTOS | Из задачи, `write_ready()` + `vTaskDelay()` | Из задачи с yield |
Для минимальной латентности в FreeRTOS — будущий `USB_DEVICE_CONFIG_USE_TASK=1`
с `bsp_usb_cdc_poll()` из выделенной задачи.
`USB_DEVICE_INTERRUPT_PRIORITY` (3) должен быть ниже
`configMAX_SYSCALL_INTERRUPT_PRIORITY` при использовании FreeRTOS API из ISR.
---
## Подключение
```cmake
# bsp/CMakeLists.txt — уже добавлено
add_subdirectory(usb_cdc)
# firmware/test/CMakeLists.txt
target_link_libraries(firmware_test PRIVATE
bsp_board
bsp_tick
bsp_usb_cdc
)
``` ```
--- ---
@ -283,70 +109,46 @@ target_link_libraries(firmware_test PRIVATE
### HIL-тест ### HIL-тест
USB CDC появляется как второй COM-порт на хосте (помимо MCU-Link VCOM). USB CDC появляется как второй COM-порт (помимо MCU-Link VCOM).
C-прошивка `tests/target/hil_usb_cdc/` — CLI через USB CDC. C-прошивка: `tests/target/hil_usb_cdc/` — CLI через USB CDC.
pytest: `tools/hil/test_usb_cdc.py` — отправка/приём через pyserial. pytest: `tools/hil/05_test_usb_cdc.py` — через pyserial (`HIL_USB_CDC_PORT`).
Переменная окружения `HIL_USB_CDC_PORT` — порт USB CDC устройства таргета.
```bash ```bash
just host::hil-usb-cdc just host::hil-usb-cdc
``` ```
Команды CLI прошивки:
| Команда | Ответ | Описание | | Команда | Ответ | Описание |
| ------------- | -------- | ---------------- | | ------------- | -------- | --------------- |
| `PING` | `PONG` | Проверка канала | | `PING` | `PONG` | Проверка канала |
| `ECHO <data>` | `<data>` | Echo-back данных | | `ECHO <data>` | `<data>` | Echo-back |
### Host unit-тесты Host unit-тесты не применяются — модуль полностью завязан на USB hardware.
Не применяются — модуль полностью завязан на USB hardware и NXP middleware.
Тестирование только через HIL.
--- ---
## Конфигурация ## Интеграция
Все настройки находятся в приватных хедерах `src/`: | Контекст | TX | RX |
| ---------- | -------------------------------------------- | ------------------------------ |
| bare-metal | `write()` — non-blocking | `read()` — polling в main loop |
| FreeRTOS | `write_ready()` + `write()` + `vTaskDelay()` | `read()` из задачи с yield |
| Файл | Настройка | Значение | Описание | `USB_DEVICE_INTERRUPT_PRIORITY = 3` должен быть ниже
| ------------------------- | ------------------------------- | -------- | -------------------------------- | `configMAX_SYSCALL_INTERRUPT_PRIORITY` при использовании FreeRTOS API из ISR.
| `usb_device_config.h` | `USB_DEVICE_CONFIG_EHCI` | `1` | Контроллер EHCI0 |
| `usb_device_config.h` | `USB_DEVICE_CONFIG_ENDPOINTS` | `4` | EP0 + interrupt IN + bulk IN/OUT |
| `usb_device_config.h` | `USB_DEVICE_CONFIG_SELF_POWER` | `1` | Self-powered device |
| `usb_device_descriptor.h` | `USB_DEVICE_VID` | `0x1234` | Vendor ID (placeholder) |
| `usb_device_descriptor.h` | `USB_DEVICE_PID` | `0x0001` | Product ID (placeholder) |
| `usb_cdc_hw.c` | `USB_DEVICE_INTERRUPT_PRIORITY` | `3` | NVIC приоритет |
| `usb_cdc_hw.c` | `BOARD_USB_PHY_D_CAL` | `0x0C` | PHY калибровка |
--- ---
## Файловая структура ## CMake
```bash ```cmake
bsp/usb_cdc/ target_link_libraries(firmware_test PRIVATE
├── CMakeLists.txt bsp_board
├── README.md bsp_tick
├── include/ bsp_usb_cdc
│ └── bsp/ )
│ └── usb_cdc.h # публичный API — без NXP хедеров
└── src/
├── usb_cdc.c # BSP API + USB device callbacks
├── usb_cdc_descriptors.c # дескрипторы + descriptor callbacks
├── usb_cdc_hw.c # clock, PHY init, IRQ handler
├── usb_device_ch9.c # lite Chapter 9 (копия из NXP примера)
├── usb_device_ch9.h # lite ch9 API
├── usb_device_cdc_acm.h # lite: только CDC request codes
├── usb_device_config.h # конфигурация USB стека
├── usb_device_descriptor.h # VID/PID, endpoints, packet sizes
└── fsl_os_abstraction_config.h # OSA bare-metal конфиг
``` ```
--- **Зависимости модуля:**
## Зависимости
| Зависимость | Тип | Описание | | Зависимость | Тип | Описание |
| --------------------- | --------------------- | ------------------------------------------------------- | | --------------------- | --------------------- | ------------------------------------------------------- |
@ -354,19 +156,5 @@ bsp/usb_cdc/
| `bsp_board` | PRIVATE | Транзитивно: `clock_config.h`, `pin_mux.h`, SDK headers | | `bsp_board` | PRIVATE | Транзитивно: `clock_config.h`, `pin_mux.h`, SDK headers |
| `sdk_usb_device_ehci` | PRIVATE | EHCI контроллер + DCI абстракция | | `sdk_usb_device_ehci` | PRIVATE | EHCI контроллер + DCI абстракция |
| `sdk_usb_phy` | PRIVATE | USB PHY инициализация (480 MHz PLL) | | `sdk_usb_phy` | PRIVATE | USB PHY инициализация (480 MHz PLL) |
| `sdk_osa_bm` | PRIVATE | OS Abstraction Layer (bare-metal, generic list) | | `sdk_osa_bm` | PRIVATE | OS Abstraction Layer (bare-metal) |
| `sdk_usb_common` | PRIVATE (транзитивно) | USB common headers (`usb.h`, `usb_misc.h`) |
| `sdk_usb_config` | PRIVATE (транзитивно) | INTERFACE: проброс конфиг-хедеров в SDK | | `sdk_usb_config` | PRIVATE (транзитивно) | INTERFACE: проброс конфиг-хедеров в SDK |
### Зависимости на уровне SDK CMake
```bash
sdk_usb_device_ehci ─┬─ sdk_usb_common ── sdk_osa_bm ── sdk_usb_config
└─ sdk_usb_config │ │
components/osa bsp/usb_cdc/src/
sdk_usb_phy ── sdk_usb_common components/lists (конфиг-хедеры)
```
`sdk_usb_config` — INTERFACE библиотека без кода. Единственная роль —
прокинуть include path к `bsp/usb_cdc/src/` для SDK таргетов,
которым нужны `usb_device_config.h` и `fsl_os_abstraction_config.h`.

View file

@ -22,43 +22,50 @@ HIL-тесты через pyOCD + pytest, GDB-сервер для отладки
## 2. Компоненты окружения ## 2. Компоненты окружения
```bash ### Физические связи
ПК разработчика
```mermaid
├── Хост (Linux / macOS / Windows + Git Bash) graph LR
│ ├── just ← запуск задач хостового уровня (just host::*) Host["Хост"]
│ ├── docker ← управление devcontainer
│ ├── uv + spsdk ← прошивка через USB ROM (flash_usb.py, sdphost, blhost) subgraph Board["Плата TFT (MIMXRT1052)"]
│ │ venv: tools/host/ USB_SDP["USB"]
│ ├── uv + pyocd ← GDB-сервер отладки + HIL-тесты MCULink["MCU-Link"]
│ │ + pyserial venv: tools/hil/ end
│ │ + pytest
│ │ + mpremote ← деплой агента на M5StampPLC subgraph M5["HIL стенд (M5StampPLC)"]
│ └── VSCode ← IDE (Dev Containers extension) M5_USB["USB"]
RLY["RLY14"]
├── Devcontainer (Docker) end
│ ├── arm-none-eabi-gcc ← кросс-компилятор (firmware + HIL target-прошивки)
│ ├── cmake + ninja ← система сборки USB_SDP -->|"SDP — прошивка через ROM"| Host
│ ├── clang-17 ← компилятор для host-тестов MCULink -->|"SWD — GDB-сервер, прошивка Flash, загрузка HIL ELF"| Host
│ ├── clangd-17 ← LSP (автодополнение, диагностика) MCULink -->|"VCOM — UART CLI (pytest ↔ HIL firmware)"| Host
│ ├── clang-tidy-17 ← статический анализ M5_USB -->|"JSON-lines CLI"| Host
│ ├── clang-format-17 ← форматирование кода RLY -->|"VIN · RS_RX · EXT_IN1 · EXT_IN2"| Board
│ ├── just ← запуск задач внутри контейнера (just build::*) ```
│ ├── uv + spsdk ← сборка HAB-образов (только nxpimage)
│ └── Unity + fff ← фреймворки host-тестов ### Состав инструментов
├── Плата TFT (MIMXRT1052) ```mermaid
│ ├── USB ──────────────────────▶ хост (SDP-режим, прошивка через ROM) graph TB
│ └── MCU-Link (USB) ───────────▶ хост (CMSIS-DAP) subgraph Host["Хост (Linux / macOS / Windows + Git Bash)"]
│ ├── SWD ← pyOCD: GDB-сервер отладки + прошивка Flash + загрузка HIL ELF в RAM H1["just host::*\nзапуск задач хостового уровня"]
│ └── VCOM ← pytest общается с HIL прошивкой через UART CLI H2["docker\nуправление devcontainer"]
H3["uv + spsdk — tools/host/\nsdphost · blhost · nxpimage"]
└── HIL стенд (M5Stack StamPLC) H4["uv + pyocd + pyserial + pytest — tools/hil/\nGDB-сервер · HIL-тесты"]
├── USB ──────────────────────▶ хост (M5 агент, JSON-lines CLI) H5["mpremote\nдеплой агента на M5StampPLC"]
├── RLY1 ─────────────────────▶ VIN таргета (управление питанием) H6["VSCode (Dev Containers extension)"]
├── RLY2 ─────────────────────▶ RS_RX таргета (BSP_OPTO_CH_RS) end
├── RLY3 ─────────────────────▶ EXT_IN1 таргета (BSP_OPTO_CH_IN1)
└── RLY4 ─────────────────────▶ EXT_IN2 таргета (BSP_OPTO_CH_IN2) subgraph DC["Devcontainer (Docker)"]
D1["arm-none-eabi-gcc\nкросс-компилятор firmware + HIL"]
D2["cmake + ninja\nсистема сборки"]
D3["clang-17 · clangd-17\nclang-tidy · clang-format"]
D4["uv + spsdk — tools/host/\nтолько nxpimage (HAB-образы)"]
D5["Unity + fff\nфреймворки host-тестов"]
D6["just build::*\nзапуск задач внутри контейнера"]
end
``` ```
--- ---
@ -130,17 +137,29 @@ HIL_USB_CDC_TIMEOUT=5.0
**Как значения попадают в инструменты:** **Как значения попадают в инструменты:**
```bash ```mermaid
.env flowchart LR
ENV[".env"]
├─▶ just (set dotenv-load + set export)
│ ├─▶ just-рецепты: {{BOOTROM_VID}}, {{HIL_VCOM_PORT}}, {{GDB_PORT}} subgraph Just["just (dotenv-load + export)"]
│ └─▶ uv run python ← наследует os.environ автоматически JR["just-рецепты\n{{BOOTROM_VID}}\n{{HIL_VCOM_PORT}}\n{{GDB_PORT}}"]
│ ├─▶ flash_usb.py: os.environ["BOOTROM_VID"] UV["uv run python\n(наследует os.environ)"]
│ ├─▶ flash_swd.py: os.environ["PYOCD_TARGET"] end
│ └─▶ env_config.py: os.environ["HIL_VCOM_PORT"]
subgraph Python["Python-скрипты"]
└─▶ .vscode/launch.json ← через ${env:GDB_PORT} FU["flash_usb.py\nos.environ[BOOTROM_VID]"]
FS["flash_swd.py\nos.environ[PYOCD_TARGET]"]
EC["env_config.py\nos.environ[HIL_VCOM_PORT]"]
end
VS[".vscode/launch.json\n${env:GDB_PORT}"]
ENV --> Just
JR --> Python
UV --> FU
UV --> FS
UV --> EC
ENV --> VS
``` ```
--- ---
@ -254,21 +273,23 @@ git clone <repo-url> && cd <repo>
### 5.3 Что делает bootstrap ### 5.3 Что делает bootstrap
```bash ```mermaid
bootstrap.sh (уровень 0) flowchart TD
A["bootstrap.sh\n(уровень 0)"]
├── определить платформу (Linux / macOS / Windows Git Bash) A --> B["определить платформу\nLinux / macOS / Windows Git Bash"]
├── проверить/установить uv >= 0.4.0 B --> C["проверить/установить\nuv >= 0.4.0"]
├── проверить/установить just >= 1.36.0 (через uv tool) C --> D["проверить/установить\njust >= 1.36.0\n(через uv tool)"]
D --> E["exec just host::bootstrap"]
└── exec just host::bootstrap
├── [1/3] check-deps — just · uv · docker E --> F["[1/3] check-deps\njust · uv · docker"]
├── [2/3] setup-udev — udev-правила NXP USB (только Linux) E --> G["[2/3] setup-udev\n(только Linux)"]
│ 1FC9:0130 ← BootROM SDP E --> H["[3/3] setup-tools\nuv sync → tools/host/"]
│ 15A2:0073 ← Flashloader
│ dialout ← группа для /dev/ttyACM* (M5StampPLC) G --> G1["1FC9:0130 — BootROM SDP"]
└── [3/3] setup-tools — uv sync в tools/host/ G --> G2["15A2:0073 — Flashloader"]
SHA-256 uv.lock кешируется → повторный вызов мгновенный G --> G3["dialout — /dev/ttyACM*"]
H --> H1["SHA-256 uv.lock кешируется\nповторный вызов мгновенный"]
``` ```
### 5.4 После bootstrap ### 5.4 После bootstrap
@ -323,7 +344,7 @@ buildPresets (HIL):
target-debug-build: test_host_uart, test_hil_button, test_hil_can, target-debug-build: test_host_uart, test_hil_button, test_hil_can,
test_hil_usb_cdc, test_hil_opto test_hil_usb_cdc, test_hil_opto
Источник истины по списку целей — [CMakePresets.json](../CMakePresets.json). Источник истины по списку целей — CMakePresets.json.
``` ```
### 6.3 Boot-стратегии ### 6.3 Boot-стратегии
@ -431,22 +452,36 @@ HIL-тесты проверяют периферию на реальном же
Только MCU-Link: SWD загружает ELF в RAM, VCOM обеспечивает UART CLI. Только MCU-Link: SWD загружает ELF в RAM, VCOM обеспечивает UART CLI.
```bash ```mermaid
pytest → uart_cmd("PING") sequenceDiagram
↓ pyserial / VCOM participant PT as pytest
MCU-Link participant ML as MCU-Link VCOM
↓ LPUART1 participant RT as RT1052
RT1052 → "PONG"
PT->>ML: uart_cmd("PING")\n(pyserial)
ML->>RT: LPUART1
RT-->>ML: "PONG"
ML-->>PT: "PONG"
``` ```
### С M5StampPLC — `02_test_opto.py` и другие ### С M5StampPLC — `02_test_opto.py` и другие
`M5StampPLC` управляет входными сигналами таргета через реле. pytest оркестрирует оба канала одновременно. `M5StampPLC` управляет входными сигналами таргета через реле. pytest оркестрирует оба канала одновременно.
```bash ```mermaid
pytest sequenceDiagram
├─▶ m5.opto_set(1, True) → M5 (JSON) → RLY3 → EXT_IN1 таргета participant PT as pytest
└─▶ uart_cmd("OPTO_READ 1") → MCU-Link VCOM → RT1052 → "ACTIVE" participant M5 as M5StampPLC
participant ML as MCU-Link VCOM
participant RT as RT1052
PT->>M5: m5.opto_set(1, True)\n(JSON-lines)
M5->>RT: RLY3 → EXT_IN1
PT->>ML: uart_cmd("OPTO_READ 1")\n(pyserial)
ML->>RT: LPUART1
RT-->>ML: "ACTIVE"
ML-->>PT: "ACTIVE"
``` ```
Перед каждой тест-сессией фикстура `m5` автоматически включает питание таргета (RLY1), ждёт стабилизации, затем `loaded_<n>` загружает ELF через pyOCD. Перед каждой тест-сессией фикстура `m5` автоматически включает питание таргета (RLY1), ждёт стабилизации, затем `loaded_<n>` загружает ELF через pyOCD.
@ -503,17 +538,26 @@ timeout-паттерн.
Подробно — [docs/HOW_TO_DEBUG.md](HOW_TO_DEBUG.md). Краткая схема: Подробно — [docs/HOW_TO_DEBUG.md](HOW_TO_DEBUG.md). Краткая схема:
```bash ```mermaid
Хост flowchart LR
├── just host::debug-server subgraph DC["Devcontainer"]
│ └── pyocd gdbserver :3333 CD["cortex-debug\n(VSCode F5)"]
│ USB/SWD → MCU-Link → плата GDB["arm-none-eabi-gdb"]
└── host.docker.internal:3333 ← доступен из devcontainer CD --> GDB
end
Devcontainer subgraph Host["Хост"]
└── cortex-debug (VSCode) DS["just host::debug-server"]
↔ arm-none-eabi-gdb PO["pyocd gdbserver :3333"]
target remote host.docker.internal:3333 DS --> PO
end
ML["MCU-Link\n(USB/SWD)"]
Board["Плата TFT"]
GDB -->|"TCP host.docker.internal:3333"| PO
PO --> ML
ML -->|"SWD"| Board
``` ```
Три конфигурации в `.vscode/launch.json`: Три конфигурации в `.vscode/launch.json`:
@ -529,28 +573,41 @@ RTT-логи доступны в Debug-сборках (`SEGGER_RTT_ENABLED=ON`);
## 12. Жизненный цикл изменений ## 12. Жизненный цикл изменений
```bash ```mermaid
feature-ветка flowchart TD
FB["feature-ветка"]
├── devcontainer
│ just build::test-host ← зелёные host-тесты? subgraph DC["Devcontainer"]
│ just build::build-firmware-test-debug T1["just build::test-host\nhost-тесты зелёные?"]
│ just build::build-hil ← HIL-прошивки собираются? T2["just build::build-firmware-test-debug"]
T3["just build::build-hil\nHIL-прошивки собираются?"]
├── хост end
│ just host::flash-test-debug ← прошить, проверить на железе
│ just host::hil-run ← HIL зелёные? subgraph HostW["Хост"]
T4["just host::flash-test-debug\nпрошить, проверить на железе"]
├── подготовка к MR T5["just host::hil-run\nHIL зелёные?"]
│ just build::hab-all-release end
│ just host::flash firmware_test release
subgraph MR["Подготовка к MR"]
└── Merge Request → CI T6["just build::hab-all-release"]
host-тесты · сборка · HIL · публикация артефактов T7["just host::flash firmware_test release"]
end
Производственный сервер
just host::incoming → firmware_test release → HIL CI["Merge Request → CI\nhost-тесты · сборка · HIL\nпубликация артефактов"]
just host::production → bootloader + tft_app release
subgraph Prod["Производственный сервер"]
P1["just host::incoming\nfirmware_test release → HIL"]
P2["just host::production\nbootloader + tft_app release"]
end
FB --> DC
T1 --> T2 --> T3
DC --> HostW
T4 --> T5
HostW --> MR
T6 --> T7
MR --> CI
CI --> Prod
``` ```
--- ---

View file

@ -2,23 +2,33 @@
## Обзор архитектуры ## Обзор архитектуры
Отладка построена на проброске GDB-сервера с хоста в devcontainer по TCP. Это позволяет держать весь инструментарий сборки и языковой сервер внутри контейнера, не проводя USB-пробник внутрь Docker. Отладка построена на пробросе GDB-сервера с хоста в devcontainer по TCP. Это
позволяет держать весь инструментарий сборки и языковой сервер внутри контейнера,
не проводя USB-пробник внутрь Docker.
```bash ```mermaid
┌─────────────────────────────────────┐ ┌──────────────────────────────────┐ flowchart LR
│ Хост (macOS/Linux) │ │ DevContainer │ subgraph Host["Хост (macOS / Linux)"]
│ │ │ │ DS["just host::debug-server\npyocd gdbserver :3333"]
│ just host::debug-server │ │ VSCode + cortex-debug │ ML["MCU-Link (CMSIS-DAP)"]
│ └─ pyocd gdbserver :3333 ──────────┼─────┼──► arm-none-eabi-gdb │ DS --> ML
│ │TCP │ └─ символы из .elf │ end
│ MCU-Link (CMSIS-DAP) │3333 │ │
│ └─ SWD ──► MIMXRT1052 │ │ RTT Console (SEGGER RTT логи) │ subgraph DC["Devcontainer"]
│ Flash / SDRAM │ │ Peripherals (SVD регистры) │ CD["cortex-debug\n(VSCode F5)"]
│ SEGGER RTT буфер │ │ RTOS view (FreeRTOS задачи) │ GDB["arm-none-eabi-gdb\nсимволы из .elf"]
└─────────────────────────────────────┘ └──────────────────────────────────┘ CD --> GDB
end
Board["MIMXRT1052\nFlash / SDRAM\nSEGGER RTT буфер"]
GDB -->|"TCP host.docker.internal:3333"| DS
ML -->|"SWD"| Board
``` ```
**Ключевой принцип:** `pyocd gdbserver` запускается на хосте и слушает на `0.0.0.0:3333`. Из контейнера GDB подключается через `host.docker.internal:3333` — специальный DNS-алиас Docker, который резолвится в IP хост-машины. **Ключевой принцип:** `pyocd gdbserver` слушает на `0.0.0.0:3333`. Из контейнера
GDB подключается через `host.docker.internal:3333` — специальный DNS-алиас Docker,
резолвится в IP хост-машины.
--- ---
@ -48,10 +58,9 @@
### Конфигурация ### Конфигурация
Параметры отладки задаются в `.env` и автоматически экспортируются через `just` (`set export`), откуда наследуются скриптами: Параметры отладки задаются в `.env`:
```bash ```bash
# .env — секция Debug / SWD
GDB_PORT=3333 GDB_PORT=3333
PYOCD_TARGET=mimxrt1050_quadspi PYOCD_TARGET=mimxrt1050_quadspi
PYOCD_FREQUENCY=4000000 PYOCD_FREQUENCY=4000000
@ -60,7 +69,7 @@ FCB_PATH=tools/host/dcd/w25q128_fdcb.bin
--- ---
## Прошивки, поддерживаемые отладкой ## Поддерживаемые прошивки
| Конфигурация VSCode | ELF | Особенности | | Конфигурация VSCode | ELF | Особенности |
| ----------------------------- | ------------------------------- | ---------------------------- | | ----------------------------- | ------------------------------- | ---------------------------- |
@ -68,7 +77,7 @@ FCB_PATH=tools/host/dcd/w25q128_fdcb.bin
| `🐛 Debug: bootloader` | `build/Debug/bootloader.elf` | Bare-metal, A/B обновление | | `🐛 Debug: bootloader` | `build/Debug/bootloader.elf` | Bare-metal, A/B обновление |
| `🐛 Debug: tft_app (FreeRTOS)` | `build/Debug/app.elf` | FreeRTOS, task view | | `🐛 Debug: tft_app (FreeRTOS)` | `build/Debug/app.elf` | FreeRTOS, task view |
Все три — XIP-прошивки, исполняются напрямую из QuadSPI NOR Flash (`0x60000000`). Все три — XIP-прошивки, исполняются из QuadSPI NOR Flash (`0x60000000`).
--- ---
@ -76,32 +85,29 @@ FCB_PATH=tools/host/dcd/w25q128_fdcb.bin
### Режим А — прошивка уже в Flash ### Режим А — прошивка уже в Flash
Стандартный ежедневный сценарий. Прошивка была залита ранее любым способом и исполняется на плате.
```bash ```bash
# 1. Хост — запустить GDB-сервер (оставить работать в отдельном терминале) # 1. Хост — запустить GDB-сервер (оставить в отдельном терминале)
just host::debug-server just host::debug-server
# 2. DevContainer — VSCode # 2. DevContainer — VSCode
# Run & Debug (Ctrl+Shift+D) → выбрать конфигурацию → F5 # Run & Debug (Ctrl+Shift+D) → выбрать конфигурацию → F5
``` ```
GDB сбрасывает MCU, загружает символы из ELF и останавливается на входе в `main`. Flash не перезаписывается. GDB сбрасывает MCU, загружает символы из ELF и останавливается на входе
в `main`. Flash не перезаписывается.
### Режим Б — прошить через SWD, затем отладить ### Режим Б — прошить через SWD, затем отладить
Когда нужно обновить прошивку без перевода платы в режим Serial Downloader. Удобно при итеративной разработке когда плата закреплена в стенде.
```bash ```bash
# 1. DevContainer — собрать HAB-образ # 1. DevContainer
just build::hab-firmware-test-debug just build::hab-firmware-test-debug
# 2. Хост — прошить через SWD (MCU-Link, без смены BOOT_MODE) # 2. Хост
just host::flash-swd-test-debug just host::flash-swd-test-debug
# 3. ⚡ Power cycle платы (обязательно — VECTRESET не реинициализирует FlexSPI) # 3. ⚡ Power cycle платы (обязательно)
# 4. Хост — запустить GDB-сервер # 4. Хост
just host::debug-server just host::debug-server
# 5. DevContainer — VSCode → 🐛 Debug: firmware_test → F5 # 5. DevContainer — VSCode → 🐛 Debug: firmware_test → F5
@ -109,16 +115,14 @@ just host::debug-server
### Режим В — прошить через USB SDP, затем отладить ### Режим В — прошить через USB SDP, затем отладить
Классический способ. Требует перевода платы в режим Serial Downloader (BOOT_MODE = 01).
```bash ```bash
# 1. DevContainer — собрать # 1. DevContainer
just build::build-firmware-test-debug just build::build-firmware-test-debug
# 2. Хост — перевести плату в Serial Downloader mode, затем: # 2. Хост — перевести плату в SDP-режим, затем:
just host::flash-test-debug just host::flash-test-debug
# 3. Хост — запустить GDB-сервер # 3. Хост
just host::debug-server just host::debug-server
# 4. DevContainer — VSCode → 🐛 Debug: firmware_test → F5 # 4. DevContainer — VSCode → 🐛 Debug: firmware_test → F5
@ -128,33 +132,32 @@ just host::debug-server
## Почему flash через SWD требует FCB ## Почему flash через SWD требует FCB
При прошивке через USB SDP (режимы А и В) ROM-загрузчик сам инициализирует FlexSPI контроллер по DCD из HAB-образа — Flash Configuration Block ему не нужен. При USB SDP ROM-загрузчик инициализирует FlexSPI по DCD из HAB-образа — FCB
не нужен. При SWD flash-алгоритм pyOCD пишет в NOR Flash напрямую. При
cold-start Boot ROM сначала читает FCB по адресу `0x60000000`, конфигурирует
FlexSPI, и только потом ищет IVT. Без FCB бутлоадер не стартует.
При прошивке через SWD flash-алгоритм pyOCD записывает данные напрямую в NOR Flash. При cold-start Boot ROM первым делом читает FCB по адресу `0x60000000`, конфигурирует по нему FlexSPI, и только потом ищет IVT. Без FCB бутлоадер не может обратиться к Flash. `flash_swd.py` решает это, собирая образ перед записью:
`flash_swd.py` решает это, собирая итоговый образ перед записью:
```bash ```bash
0x60000000 w25q128_fdcb.bin (512 байт) — FCB: параметры W25Q128, Quad SPI 0x60000000 w25q128_fdcb.bin (512 байт) — FCB
0x60000200 0xFF × 3584 байт — padding (значение стёртой ячейки) 0x60000200 0xFF × 3584 байт — padding
0x60001000 firmware_test_hab.bin — IVT + DCD + код (ivtOffset = 0x1000) 0x60001000 firmware_test_hab.bin — IVT + DCD + код
``` ```
Весь диапазон `0x600000000x6000FFFF` умещается в один 64KB-сектор Flash, поэтому стирается и записывается за одну транзакцию — FCB и HAB не перезаписывают друг друга. Весь диапазон `0x600000000x6000FFFF` — один 64KB сектор: стирается и
записывается за одну транзакцию.
--- ---
## RTT-логи ## RTT-логи
SEGGER RTT включён только в Debug-сборках (`SEGGER_RTT_ENABLED=ON` в `CMakePresets.json`). В Release-сборках RTT отключён и символ `_SEGGER_RTT` в ELF отсутствует. SEGGER RTT включён только в Debug-сборках (`SEGGER_RTT_ENABLED=ON`).
После старта отладки вкладка `TERMINAL → RTT` принимает вывод канала 0.
После старта отладки вкладка `TERMINAL → RTT` в VSCode принимает вывод из RTT-буфера канала 0. `cortex-debug` находит адрес буфера автоматически по символу `_SEGGER_RTT` из ELF (`address: auto` в `launch.json`). `cortex-debug` находит адрес буфера по символу `_SEGGER_RTT` из ELF.
Использование в коде:
```c ```c
#include "SEGGER_RTT.h" #include "SEGGER_RTT.h"
SEGGER_RTT_printf(0, "value = %d\n", value); SEGGER_RTT_printf(0, "value = %d\n", value);
``` ```
@ -162,46 +165,47 @@ SEGGER_RTT_printf(0, "value = %d\n", value);
## FreeRTOS task view ## FreeRTOS task view
Конфигурация `🐛 Debug: tft_app (FreeRTOS)` включает `"rtos": "FreeRTOS"` — cortex-debug разбирает внутренние структуры планировщика и показывает вкладку `RTOS` с таблицей задач: имя, состояние (`Running` / `Ready` / `Blocked` / `Suspended`), использование стека, приоритет. При паузе можно переключиться в контекст любой задачи и просмотреть её стек вызовов. Конфигурация `🐛 Debug: tft_app (FreeRTOS)` включает `"rtos": "FreeRTOS"`
cortex-debug разбирает структуры планировщика и показывает вкладку `RTOS`
с таблицей задач: имя, состояние, использование стека, приоритет.
--- ---
## Просмотр регистров периферии ## Просмотр регистров периферии
Вкладка `Peripherals` в панели отладки показывает все периферийные блоки MIMXRT1052 по SVD-файлу `bsp/generated/startup/MIMXRT1052.xml`. Значения регистров обновляются при каждой паузе. Можно раскрыть любой блок (GPIO, LPUART, USB, FlexSPI и т.д.) и просматривать поля побитово. Вкладка `Peripherals` показывает все блоки MIMXRT1052 по SVD-файлу
`bsp/generated/startup/MIMXRT1052.xml`. Значения обновляются при каждой паузе.
--- ---
## Ограничения и важные замечания ## Ограничения
**MCU-Link монопольный ресурс.** `debug-server` и `flash-swd` не могут работать одновременно — оба занимают пробник. Перед `flash-swd` остановите сервер (Ctrl+C), и наоборот. **MCU-Link монопольный ресурс.** `debug-server` и `flash-swd` не могут
работать одновременно. Перед `flash-swd` остановите сервер (Ctrl+C).
**HIL-тесты vs отладка.** pyOCD также используется для HIL (загрузка ELF в RAM через `pyocd.yaml`). Перед запуском HIL-тестов (`just host::hil-run`) остановите GDB-сервер. **HIL-тесты vs отладка.** pyOCD также используется для HIL. Перед
`just host::hil-run` остановите GDB-сервер.
**Power cycle после flash-swd обязателен.** pyOCD завершает запись командой VECTRESET, которая не реинициализирует FlexSPI контроллер. Boot ROM при таком сбросе не может прочитать FCB и не стартует из Flash. Только полное отключение питания гарантирует корректный cold-start. **Power cycle после flash-swd обязателен.** VECTRESET не реинициализирует
FlexSPI — только полное отключение питания гарантирует корректный cold-start.
**Только Debug-сборки.** Отладка с символами возможна только для `Debug` CMake-пресета. Release-сборки компилируются с `-O2` без DWARF-символов. **Только Debug-сборки.** Release компилируется с `-O2` без DWARF-символов.
--- ---
## Быстрый старт (первый запуск) ## Быстрый старт (первый запуск)
```bash ```bash
# 1. Убедиться что cortex-debug установлен в devcontainer # 1. Убедиться что в .devcontainer/devcontainer.json есть (для Linux):
# .devcontainer/devcontainer.json → extensions: ["marus25.cortex-debug"]
# 2. Убедиться что в .devcontainer/devcontainer.json есть (для Linux-хостов):
# "runArgs": ["--add-host=host.docker.internal:host-gateway"] # "runArgs": ["--add-host=host.docker.internal:host-gateway"]
# 3. Залить прошивку любым способом (один раз) # 2. Залить прошивку
just host::flash-test-debug # USB SDP just host::flash-test-debug
# или
just host::flash-swd-test-debug # SWD (после just build::hab-firmware-test-debug)
# 4. Запустить GDB-сервер на хосте # 3. Хост — запустить GDB-сервер
just host::debug-server just host::debug-server
# 5. В VSCode (devcontainer) # 4. DevContainer — VSCode
# Ctrl+Shift+D → 🐛 Debug: firmware_test → F5 # Ctrl+Shift+D → 🐛 Debug: firmware_test → F5
``` ```
@ -213,7 +217,7 @@ just host::debug-server
. .
├── .env # GDB_PORT, PYOCD_TARGET, PYOCD_FREQUENCY, FCB_PATH ├── .env # GDB_PORT, PYOCD_TARGET, PYOCD_FREQUENCY, FCB_PATH
├── .vscode/ ├── .vscode/
│ ├── launch.json # Конфигурации cortex-debug (3 проекта) │ ├── launch.json # cortex-debug конфигурации (3 проекта)
│ └── tasks.json # preLaunchTask: build:*-debug │ └── tasks.json # preLaunchTask: build:*-debug
├── bsp/generated/startup/ ├── bsp/generated/startup/
│ └── MIMXRT1052.xml # SVD — регистры периферии │ └── MIMXRT1052.xml # SVD — регистры периферии

View file

@ -11,7 +11,9 @@
## Способ 1 — USB SDP (Serial Download Protocol) ## Способ 1 — USB SDP (Serial Download Protocol)
Стандартный производственный способ. ROM-загрузчик принимает образ по USB и записывает его во Flash через Flashloader. Требует физического переключения пина `BOOT_MOD_1`. Стандартный производственный способ. ROM-загрузчик принимает образ по USB и
записывает его во Flash через Flashloader. Требует физического переключения
пина `BOOT_MOD_1`.
### 1.1 Перевести плату в SDP-режим ### 1.1 Перевести плату в SDP-режим
@ -58,17 +60,15 @@ just host::flash-production # bootloader release + app release (с подт
### 1.4 Что происходит при прошивке через USB SDP ### 1.4 Что происходит при прошивке через USB SDP
```bash ```mermaid
Плата в SDP-режиме (1FC9:0130) flowchart TD
A["Плата в SDP-режиме\n1FC9:0130"] --> B["sdphost\nзагрузить ivt_flashloader.bin\nв RAM 0x20001C00"]
├── sdphost: загрузить ivt_flashloader.bin в RAM (0x20001C00) B --> C["sdphost jump-address\nFlashloader поднимается\nкак 15A2:0073"]
└── sdphost: jump-address → Flashloader поднимается как 15A2:0073 C --> D["configure-memory 0xC0000007\nинициализация FlexSPI NOR"]
D --> E["flash-erase-region 0x60000000"]
├── configure-memory (0xC0000007) — инициализация FlexSPI NOR E --> F["configure-memory 0xF000000F\nзапись FCB в 0x60000000"]
├── flash-erase-region 0x60000000 F --> G["write-memory 0x60001000\nHAB-образ"]
├── configure-memory (0xF000000F) — запись FCB в 0x60000000 G --> H["reset"]
├── write-memory 0x60001000 ← HAB-образ
└── reset
``` ```
ROM-загрузчик сам конфигурирует FlexSPI через DCD из HAB-образа, поэтому FCB ROM-загрузчик сам конфигурирует FlexSPI через DCD из HAB-образа, поэтому FCB
@ -80,13 +80,14 @@ ROM-загрузчик сам конфигурирует FlexSPI через DCD
Прошивка через отладочный пробник (MCU-Link, CMSIS-DAP). Плата остаётся Прошивка через отладочный пробник (MCU-Link, CMSIS-DAP). Плата остаётся
в нормальном режиме загрузки — переключать `BOOT_MOD_1` не нужно. Удобно в нормальном режиме загрузки — переключать `BOOT_MOD_1` не нужно. Удобно
при итеративной разработке когда плата закреплена в стенде, а также как при итеративной разработке когда плата закреплена в стенде.
часть отладочного цикла.
**Ограничения:** **Ограничения:**
- После записи обязателен **power cycle** (не reset) — VECTRESET не реинициализирует FlexSPI, Boot ROM не стартует - После записи обязателен **power cycle** (не reset) — VECTRESET не
- MCU-Link используется монопольно: нельзя запускать одновременно с `debug-server` или HIL-тестами реинициализирует FlexSPI, Boot ROM не стартует
- MCU-Link используется монопольно: нельзя запускать одновременно с
`debug-server` или HIL-тестами
### 2.1 Подготовить HAB-образ (внутри devcontainer) ### 2.1 Подготовить HAB-образ (внутри devcontainer)
@ -99,8 +100,8 @@ just build::hab-app-debug
### 2.2 Прошить (хостовый терминал) ### 2.2 Прошить (хостовый терминал)
```bash ```bash
just host::flash-swd-test-debug # firmware_test Debug just host::flash-swd-test-debug
just host::flash-swd-test-release # firmware_test Release just host::flash-swd-test-release
just host::flash-swd-bootloader-debug just host::flash-swd-bootloader-debug
just host::flash-swd-bootloader-release just host::flash-swd-bootloader-release
just host::flash-swd-app-debug just host::flash-swd-app-debug
@ -134,7 +135,7 @@ just host::flash-swd-app-release
| `tools/hil/` (uv-проект) | pyocd, вызывается через `uv run` | | `tools/hil/` (uv-проект) | pyocd, вызывается через `uv run` |
FCB-бинарник (`w25q128_fdcb.bin`) генерируется в NXP SecureProvisioningTool FCB-бинарник (`w25q128_fdcb.bin`) генерируется в NXP SecureProvisioningTool
для W25Q128 в режиме Quad SPI и хранится в репозитории — пересоздавать не нужно. и хранится в репозитории — пересоздавать не нужно.
--- ---

View file

@ -15,29 +15,22 @@
инженером через USB CDC ACM (разъём J2). Загружается через BootROM (USB SDP) инженером через USB CDC ACM (разъём J2). Загружается через BootROM (USB SDP)
без предварительной прошивки загрузчика. без предварительной прошивки загрузчика.
**Стенд:** ```mermaid
flowchart TB
Host["Хост-ПК\nсервисного инженера"]
FW["Плата MIMXRT1052\nfirmware_test"]
Periph["Периферия\nSDRAM · QSPI Flash · uSD\nDisplay · CAN · UART · Opto"]
M5["M5StampPLC\nвнешние сигналы для HIL"]
```bash Host -->|"USB CDC ACM J2\nJSON-lines, 1 строка = 1 сообщение"| FW
[Хост-ПК сервисного инженера] FW -->|"GPIO / LPUART / SEMC\nFlexSPI / USDHC"| Periph
│ USB CDC ACM (J2) M5 -->|"реле → оптовходы / CAN / UART"| FW
│ JSON-lines, 1 строка = 1 сообщение
[Плата MIMXRT1052 с firmware_test]
│ GPIO / LPUART / SEMC / FlexSPI / USDHC
[Периферия: SDRAM, QSPI Flash, uSD, Display, CAN, UART, Opto]
[M5StampPLC — управление внешними сигналами для HIL тестов]
``` ```
**Принцип работы:** **Принцип работы:** вся тест-логика живёт на таргете (`test_runner.c`,
`tests/*.c`). Хост — тонкий клиент: отправляет команды, отображает события,
- Вся тест-логика живёт **на таргете** (`test_runner.c`, `tests/*.c`). управляет интерактивными шагами. Инженер запускает тесты атомарно или все
- Хост — тонкий клиент: отправляет команды, отображает события, управляет подряд (`run_all`).
интерактивными шагами.
- Инженер запускает тесты **атомарно** (один тест за раз) или все подряд
(`run_all`). Последовательность не фиксирована — инженер сам решает,
что проверять.
--- ---
@ -53,26 +46,23 @@
| CR+LF | Принимается (таргет отбрасывает `\r` перед `\n`) | | CR+LF | Принимается (таргет отбрасывает `\r` перед `\n`) |
| Направление | Двунаправленный, half-duplex по логике | | Направление | Двунаправленный, half-duplex по логике |
Нет хэндшейка, нет sequence number, нет подтверждений доставки. При потере Нет хэндшейка, нет sequence number, нет подтверждений доставки.
строки хост повторяет команду — таргет идемпотентен для `ping` и `run`.
--- ---
## Формат сообщений ## Формат сообщений
Все сообщения — JSON-объекты в одну строку (`\n` в конце). Все сообщения — JSON-объекты в одну строку (`\n` в конце). Поле `"type"`
определяет смысл сообщения:
### Ключевые поля ```bash
Каждое сообщение содержит поле `"type"`, определяющее его смысл:
```
Хост → Таргет: "type": "cmd" — команда Хост → Таргет: "type": "cmd" — команда
"type": "confirm" — ответ оператора на интерактивный шаг "type": "confirm" — ответ оператора на интерактивный шаг
Таргет → Хост: "type": "session_start" — таргет готов Таргет → Хост: "type": "session_start" — таргет готов
"type": "pong" — ответ на ping "type": "pong" — ответ на ping
"type": "test_begin" — тест стартовал "type": "test_begin" — тест стартовал
"type": "progress" — промежуточный шаг теста
"type": "test_result" — тест завершён "type": "test_result" — тест завершён
"type": "confirm_request" — ожидание действия оператора "type": "confirm_request" — ожидание действия оператора
"type": "summary" — итог run_all "type": "summary" — итог run_all
@ -83,83 +73,63 @@
## Жизненный цикл сессии ## Жизненный цикл сессии
```bash ```mermaid
Хост Таргет sequenceDiagram
│ │ participant H as Хост
│ [прошивка загружена через USB SDP] │ participant T as Таргет
│ [USB CDC установлен] │
│◄─── {"type":"session_start","fw":"0.1.0", │ Note over T: прошивка загружена через USB SDP
│ "target":"IMXRT1052","uptime_ms":0} │ T-->>H: {"type":"session_start","fw":"0.1.0","target":"IMXRT1052","uptime_ms":0}
│ │
│──── {"type":"cmd","cmd":"ping"} ──────────►│ H->>T: {"type":"cmd","cmd":"ping"}
│◄─── {"type":"pong"} ────────────────────── │ T-->>H: {"type":"pong"}
│ │
│ [инженер выбирает тест] │ Note over H: инженер выбирает тест
│ │ H->>T: {"type":"cmd","cmd":"run","id":"sdram"}
│──── {"type":"cmd","cmd":"run","id":"sdram"} ►│ T-->>H: {"type":"test_begin","id":"sdram","name":"SDRAM 32 MB","critical":true}
│◄─── {"type":"test_begin","id":"sdram",...} │ T-->>H: {"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":""}
│◄─── {"type":"test_result","id":"sdram",...} │
│ │ H->>T: {"type":"cmd","cmd":"run_all"}
│──── {"type":"cmd","cmd":"run_all"} ────────►│ T-->>H: {"type":"test_begin","id":"sdram",...}
│◄─── {"type":"test_begin","id":"sdram",...} │ T-->>H: {"type":"test_result","id":"sdram","status":"pass",...}
│◄─── {"type":"test_result","id":"sdram",...} │ Note over T: ...каждый тест в реестре...
│ … (каждый тест в реестре) … │ T-->>H: {"type":"summary","overall":"pass","passed":7,"failed":0,"skipped":1}
│◄─── {"type":"summary","overall":"pass",...} │
│ │
``` ```
`session_start` отправляется **автоматически** при каждом старте таргета, `session_start` отправляется автоматически при каждом старте таргета, до
до получения первой команды. Хост должен быть готов принять его сразу после получения первой команды.
открытия CDC порта.
--- ---
## Команды хоста → таргет (`"type":"cmd"`) ## Команды хоста → таргет
### `ping` ### `ping`
Проверка связи. Таргет отвечает немедленно.
```json ```json
→ {"type":"cmd","cmd":"ping"} → {"type":"cmd","cmd":"ping"}
← {"type":"pong"} ← {"type":"pong"}
``` ```
---
### `run` — запуск одного теста ### `run` — запуск одного теста
Запустить тест по идентификатору. Если тест требует предварительного
подтверждения оператора (`pre_confirm_prompt` задан), таргет сначала пошлёт
`confirm_request`.
```json ```json
→ {"type":"cmd","cmd":"run","id":"sdram"} → {"type":"cmd","cmd":"run","id":"sdram"}
← {"type":"test_begin","id":"sdram","name":"SDRAM 32 MB","critical":true} ← {"type":"test_begin","id":"sdram","name":"SDRAM 32 MB","critical":true}
← {"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":""} ← {"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":""}
``` ```
Если `id` не найден в реестре: Если `id` не найден: `← {"ok":false,"error":"UNKNOWN_TEST"}`
```json ### `run_all` — запуск всех тестов
← {"ok":false,"error":"UNKNOWN_TEST"}
```
--- Если тест помечен `"critical":true` и вернул `"fail"` — выполнение
прерывается, остальные получают `"skip"`.
### `run_all` — запуск всех тестов по реестру
Запускает все тест-модули в порядке реестра. Если тест помечен `"critical":true`
и вернул `"status":"fail"` — выполнение прерывается, остальные тесты
получают `"status":"skip"` в итоге (но `summary` всё равно отправляется).
```json ```json
→ {"type":"cmd","cmd":"run_all"} → {"type":"cmd","cmd":"run_all"}
← {"type":"test_begin","id":"sdram","name":"SDRAM 32 MB","critical":true} ← {"type":"test_begin","id":"sdram",...}
← {"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":""} ← {"type":"test_result","id":"sdram","status":"pass",...}
← {"type":"test_begin","id":"qspi","name":"QSPI Flash 8 MB","critical":true} ← ... (каждый тест в реестре) ...
← {"type":"test_result","id":"qspi","status":"pass","ms":88,"detail":""}
← ... (остальные тесты) ...
← {"type":"summary","passed":7,"failed":0,"skipped":1,"overall":"pass"} ← {"type":"summary","passed":7,"failed":0,"skipped":1,"overall":"pass"}
``` ```
@ -169,70 +139,40 @@
### `session_start` ### `session_start`
Таргет готов к работе. Отправляется автоматически при старте.
```json ```json
{ {"type":"session_start","fw":"0.1.0","target":"IMXRT1052","uptime_ms":0}
"type": "session_start",
"fw": "0.1.0",
"target": "IMXRT1052",
"uptime_ms": 0
}
``` ```
| Поле | Тип | Описание |
| ----------- | ------ | -------------------------- |
| `fw` | string | Версия firmware_test |
| `target` | string | Идентификатор платформы |
| `uptime_ms` | number | Время с момента старта, мс |
---
### `test_begin` ### `test_begin`
Тест начат. Отправляется непосредственно перед вызовом `run()`.
```json ```json
{ {"type":"test_begin","id":"sdram","name":"SDRAM 32 MB","critical":true}
"type": "test_begin",
"id": "sdram",
"name": "SDRAM 32 MB",
"critical": true
}
``` ```
---
### `test_result` ### `test_result`
Тест завершён (pass / fail / skip).
```json ```json
{ {"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":""}
"type": "test_result",
"id": "sdram",
"status": "pass",
"ms": 312,
"detail": ""
}
``` ```
| `status` | Смысл | | `status` | Смысл |
| -------- | ---------------------------------------------------------------------------- | | -------- | ----------------------------------------------------------------------- |
| `"pass"` | Тест пройден | | `"pass"` | Тест пройден |
| `"fail"` | Тест провален; поле `detail` содержит описание | | `"fail"` | Тест провален; `detail` содержит описание |
| `"skip"` | Тест пропущен (нет оборудования, таймаут оператора, прерван `critical` fail) | | `"skip"` | Пропущен (нет оборудования, таймаут оператора, прерван `critical` fail) |
Поле `detail` — произвольная ASCII-строка до 95 символов. При `pass` — пустая. `detail` — ASCII-строка до 95 символов. При `pass` — пустая.
Примеры: `"addr=0x80001000 expected=0xA5 got=0x00"`, `"JEDEC ID mismatch"`.
--- ### `progress`
```json
{"type":"progress","test":"usd","step":"mount","status":"ok"}
```
Промежуточные шаги внутри теста. Используется в `usd`.
### `confirm_request` ### `confirm_request`
Таргет ожидает действия оператора. Используется интерактивными тестами:
display (подтвердить цвет), кнопки (нажать кнопку), uSD (вставить карту).
```json ```json
{ {
"type": "confirm_request", "type": "confirm_request",
@ -242,58 +182,28 @@ display (подтвердить цвет), кнопки (нажать кнопк
} }
``` ```
Хост должен отобразить `prompt` оператору и ждать его реакции. Если оператор Хост отображает `prompt` оператору. Авторитетный таймаут — на таргете;
не ответил за `timeout_ms` — таргет переходит в `SKIP` для этого шага если оператор не ответил за `timeout_ms` — таргет переходит в `SKIP`.
автоматически. Хост может дублировать таймаут на своей стороне для UX, но
авторитетный таймаут — на таргете.
---
### `summary` ### `summary`
Итог `run_all`. Отправляется после завершения последнего теста в реестре или
после прерывания по critical fail.
```json ```json
{ {"type":"summary","passed":6,"failed":1,"skipped":0,"overall":"fail"}
"type": "summary",
"passed": 6,
"failed": 1,
"skipped": 0,
"overall": "fail"
}
``` ```
`"overall": "fail"` если хотя бы один `critical` тест провален. `"overall": "fail"` если хотя бы один `critical` тест провален.
`"overall": "pass"` если все `critical` тесты прошли (non-critical могут fail).
---
### `confirm` (хост → таргет) ### `confirm` (хост → таргет)
Ответ оператора на `confirm_request`. Поле `"id"` должно совпадать с `id`
из `confirm_request`.
```json ```json
→ {"type":"confirm","id":"display_red","confirmed":true} → {"type":"confirm","id":"display_red","confirmed":true}
``` ```
Если `"confirmed": false` — таргет записывает `TEST_STATUS_FAIL` для этого шага. `"id"` должен совпадать с `id` из `confirm_request`. Ответ после `timeout_ms`
Если ответ пришёл после истечения `timeout_ms` — таргет игнорирует его игнорируется.
(уже перешёл в SKIP).
---
### Ошибки протокола ### Ошибки протокола
```json
← {"ok":false,"error":"PARSE_ERR"}
← {"ok":false,"error":"UNKNOWN_CMD"}
← {"ok":false,"error":"UNKNOWN_TEST"}
← {"ok":false,"error":"LINE_TOO_LONG"}
← {"ok":false,"error":"BUSY"}
```
| Код | Причина | | Код | Причина |
| --------------- | ----------------------------------------------- | | --------------- | ----------------------------------------------- |
| `PARSE_ERR` | Строка не является валидным JSON-lines запросом | | `PARSE_ERR` | Строка не является валидным JSON-lines запросом |
@ -318,12 +228,8 @@ display (подтвердить цвет), кнопки (нажать кнопк
| `uart_iso` | UART ISO / RS_RX Opto | HIL | ❌ | ✅ | ❌ | | `uart_iso` | UART ISO / RS_RX Opto | HIL | ❌ | ✅ | ❌ |
| `opto` | Opto-in EXT_IN1/IN2 | HIL | ❌ | ✅ | ❌ | | `opto` | Opto-in EXT_IN1/IN2 | HIL | ❌ | ✅ | ❌ |
**Типы тестов:** **Типы:** **self** — таргет тестирует периферию самостоятельно; **interactive**
требует `confirm_request`; **HIL** — требует M5StampPLC.
- **self** — таргет тестирует периферию самостоятельно, без внешних сигналов.
- **interactive** — требует действия оператора через механизм `confirm_request`.
- **HIL** — требует M5StampPLC для генерации внешних сигналов
(реле, CAN фреймы, UART echo).
--- ---
@ -331,131 +237,96 @@ display (подтвердить цвет), кнопки (нажать кнопк
### uSD ### uSD
Карта вставляется оператором по запросу. Тест не входит в критический путь. ```mermaid
Pre-confirm обрабатывается `test_runner` до вызова `run()`. sequenceDiagram
participant H as Хост
participant T as Таргет
```bash T-->>H: {"type":"confirm_request","id":"usd","prompt":"Insert microSD card","timeout_ms":30000}
← {"type":"confirm_request","id":"usd","prompt":"Insert microSD card and press OK","timeout_ms":30000} H->>T: {"type":"confirm","id":"usd","confirmed":true}
→ {"type":"confirm","id":"usd","confirmed":true} T-->>H: {"type":"test_begin","id":"usd","name":"microSD (SDIO)","critical":false}
← {"type":"test_begin","id":"usd","name":"microSD (SDIO)","critical":false} T-->>H: {"type":"progress","test":"usd","step":"card_detect","status":"ok"}
← {"type":"progress","test":"usd","step":"card_detect","status":"ok"} T-->>H: {"type":"progress","test":"usd","step":"mount","status":"ok"}
← {"type":"progress","test":"usd","step":"mount","status":"ok"} T-->>H: {"type":"progress","test":"usd","step":"write","status":"ok"}
← {"type":"progress","test":"usd","step":"write","status":"ok"} T-->>H: {"type":"progress","test":"usd","step":"read_compare","status":"ok"}
← {"type":"progress","test":"usd","step":"read_compare","status":"ok"} T-->>H: {"type":"test_result","id":"usd","status":"pass","ms":741,"detail":""}
← {"type":"test_result","id":"usd","status":"pass","ms":741,"detail":""}
``` ```
Если оператор отказался (`"confirmed":false`): При отказе (`"confirmed":false`) или таймауте:
```bash ```json
← {"type":"test_begin","id":"usd","name":"microSD (SDIO)","critical":false} ← {"type":"test_begin","id":"usd","name":"microSD (SDIO)","critical":false}
← {"type":"test_result","id":"usd","status":"skip","ms":0,"detail":"operator declined"} ← {"type":"test_result","id":"usd","status":"skip","ms":0,"detail":"operator declined"}
``` ```
Если истёк таймаут (30 с без ответа):
```bash
← {"type":"test_begin","id":"usd","name":"microSD (SDIO)","critical":false}
← {"type":"test_result","id":"usd","status":"skip","ms":0,"detail":"confirm timeout"}
```
---
### Display (RGB888) ### Display (RGB888)
Четыре шага: красный, зелёный, синий, белый. Итог — AND всех подтверждений. ```mermaid
Одновременно верифицируется подсветка (PWM включён). sequenceDiagram
participant H as Хост
participant T as Таргет
```bash T-->>H: {"type":"test_begin","id":"display",...}
← {"type":"test_begin","id":"display",...} T-->>H: {"type":"confirm_request","id":"display_red","prompt":"Экран залит красным?","timeout_ms":15000}
← {"type":"confirm_request","id":"display_red","prompt":"Экран залит красным?","timeout_ms":15000} H->>T: {"type":"confirm","id":"display_red","confirmed":true}
→ {"type":"confirm","id":"display_red","confirmed":true} T-->>H: {"type":"confirm_request","id":"display_green","prompt":"Экран залит зелёным?","timeout_ms":15000}
← {"type":"confirm_request","id":"display_green","prompt":"Экран залит зелёным?","timeout_ms":15000} H->>T: {"type":"confirm","id":"display_green","confirmed":true}
→ {"type":"confirm","id":"display_green","confirmed":true} T-->>H: {"type":"confirm_request","id":"display_blue","prompt":"Экран залит синим?","timeout_ms":15000}
← {"type":"confirm_request","id":"display_blue","prompt":"Экран залит синим?","timeout_ms":15000} H->>T: {"type":"confirm","id":"display_blue","confirmed":true}
→ {"type":"confirm","id":"display_blue","confirmed":true} T-->>H: {"type":"confirm_request","id":"display_white","prompt":"Экран залит белым?","timeout_ms":15000}
← {"type":"confirm_request","id":"display_white","prompt":"Экран залит белым?","timeout_ms":15000} H->>T: {"type":"confirm","id":"display_white","confirmed":false}
→ {"type":"confirm","id":"display_white","confirmed":false} T-->>H: {"type":"test_result","id":"display","status":"fail","ms":22103,"detail":"display_white not confirmed"}
← {"type":"test_result","id":"display","status":"fail","ms":22103,"detail":"display_white not confirmed"}
``` ```
### Кнопки
```mermaid
sequenceDiagram
participant H as Хост
participant T as Таргет
T-->>H: {"type":"test_begin","id":"buttons",...}
T-->>H: {"type":"confirm_request","id":"btn1_press","prompt":"Нажмите кнопку Test_But_1","timeout_ms":10000}
Note over T: ждёт bsp_button_get(BTN_TEST_1) == PRESSED<br/>таймаут 10 с
T-->>H: {"type":"confirm_request","id":"btn2_press","prompt":"Нажмите кнопку Test_But_2","timeout_ms":10000}
Note over T: ждёт bsp_button_get(BTN_TEST_2) == PRESSED
T-->>H: {"type":"test_result","id":"buttons","status":"pass","ms":3821,"detail":""}
```
> **Важно:** для теста кнопок таргет **не ждёт** `{"type":"confirm",…}` от
> хоста. Нажатие детектируется прошивкой через `bsp_button`. Хост отображает
> `prompt` и ждёт следующего события от таргета.
--- ---
### Кнопки (Test_But_1 / Test_But_2) ## State machine test_runner
Два шага. Таргет ждёт физического нажатия через `bsp_button`, не через confirm. ```mermaid
`confirm_request` здесь используется как инструкция оператору — ответом является stateDiagram-v2
не JSON, а сам факт нажатия кнопки, который таргет детектирует самостоятельно. [*] --> IDLE
```bash IDLE --> PRE_CONFIRM : cmd run / run_all
← {"type":"test_begin","id":"buttons",...} note right of PRE_CONFIRM : pre_confirm_prompt != NULL?\nprotocol_send_confirm_request()
← {"type":"confirm_request","id":"btn1_press","prompt":"Нажмите кнопку Test_But_1","timeout_ms":10000}
[таргет ждёт bsp_button_get(BTN_TEST_1) == PRESSED, таймаут 10 с]
← {"type":"confirm_request","id":"btn2_press","prompt":"Нажмите кнопку Test_But_2","timeout_ms":10000}
[таргет ждёт bsp_button_get(BTN_TEST_2) == PRESSED, таймаут 10 с]
← {"type":"test_result","id":"buttons","status":"pass","ms":3821,"detail":""}
```
> **Важно:** для теста кнопок таргет не ждёт `{"type":"confirm",…}` от хоста. PRE_CONFIRM --> RUNNING : confirm получен или prompt == NULL
> Хост отображает `prompt` оператору и ждёт следующего события от таргета.
> Нажатие детектируется прошивкой через `bsp_button`, не через CDC.
--- RUNNING --> RUNNING : run_all — следующий тест
note right of RUNNING : protocol_send_test_begin()\nmod->init()\nresult = mod->run()\nmod->deinit()\nprotocol_send_test_result()
## Рекомендации для разработчика хостового ПО RUNNING --> IDLE : run завершён
RUNNING --> IDLE : run_all завершён\nprotocol_send_summary()
### Открытие порта
```bash
1. Найти CDC ACM устройство (VID/PID NXP или зарегистрированный).
2. Открыть порт (любой baud rate — CDC игнорирует его).
3. Ждать строку с "type":"session_start" — таймаут 10 с.
4. Если не получен — переоткрыть порт или перезагрузить таргет.
```
### Чтение событий
```bash
- Читать побайтово или буфером, буферизировать до '\n'.
- Одна строка = одно JSON-сообщение.
- Неизвестное поле "type" — игнорировать (forward-compatibility).
- Парсить минимально: поле "type" определяет дальнейший разбор.
```
### Отправка команд
```bash
- Завершать каждую строку '\n' (не '\r\n').
- Не отправлять следующую команду до получения финального события
предыдущей (test_result или error).
- Исключение: "ping" можно отправлять в любой момент, если таргет не BUSY.
```
### Обработка confirm_request
```bash
1. Получить "confirm_request" → отобразить "prompt" оператору.
2. Дождаться реакции оператора (кнопка в UI, клавиша в TUI).
3. Исключение — тест "buttons": не отправлять confirm, просто ждать
следующего события от таргета.
4. Для всех остальных тестов — отправить:
{"type":"confirm","id":"<тот же id>","confirmed":true/false}
5. Хост может показывать countdown по timeout_ms для UX,
но не обязан — таргет сам завершит по таймауту.
``` ```
--- ---
## Реализация на стороне таргета ## Реализация на стороне таргета
### Модули прошивки
```bash ```bash
firmware/test/src/ firmware/test/src/
├── main.c — инициализация, главный цикл, вызов cli_process() ├── main.c — инициализация, главный цикл, вызов cli_process()
├── cli.h / cli.c — IO-слой: буферизация строк, диспатч по "type" ├── cli.h / cli.c — IO-слой: буферизация строк, диспатч по "type"
├── protocol.h / .c — сериализация исходящих событий через cli_send() ├── protocol.h / .c — сериализация исходящих событий через cli_send()
├── test_module.h — интерфейс тест-модуля (структура test_module_t) ├── test_module.h — интерфейс тест-модуля (struct test_module_t)
├── test_runner.h/.c — реестр тестов, state machine, confirm механизм ├── test_runner.h/.c — реестр тестов, state machine, confirm механизм
└── tests/ └── tests/
├── test_sdram.c ├── test_sdram.c
@ -469,43 +340,13 @@ firmware/test/src/
└── test_opto.c └── test_opto.c
``` ```
### State machine test_runner
```
┌─────────────────────────────────────────┐
│ IDLE │◄──────────────────┐
│ Ждём команду от хоста │ │
└───────────────┬─────────────────────────┘ │
│ cmd: run / run_all │
▼ │
┌─────────────────────────────────────────┐ │
│ PRE_CONFIRM │ │
│ pre_confirm_prompt != NULL? │ │
│ → protocol_send_confirm_request() │ │
└───────────────┬─────────────────────────┘ │
│ confirm получен / NULL │
▼ │
┌─────────────────────────────────────────┐ │
│ RUNNING │ │
│ protocol_send_test_begin() │ │
│ mod->init() если задан │ │
│ result = mod->run() │ │
│ mod->deinit() если задан │ │
│ protocol_send_test_result() │ │
└───────────────┬─────────────────────────┘ │
│ │
├── run_all: следующий тест ──────────────────┤
│ │
└── run_all завершён: protocol_send_summary() ┘
run: сразу → IDLE
```
### Добавление нового теста ### Добавление нового теста
1. Создать `firmware/test/src/tests/test_foo.c` с реализацией `test_result_t test_foo_run(void)`. 1. Создать `firmware/test/src/tests/test_foo.c`.
2. Объявить дескриптор: 2. Объявить дескриптор:
```c
const test_module_t k_test_foo = { ```c
const test_module_t k_test_foo = {
.id = "foo", .id = "foo",
.name = "Foo Peripheral", .name = "Foo Peripheral",
.critical = false, .critical = false,
@ -514,19 +355,36 @@ firmware/test/src/
.init = NULL, .init = NULL,
.run = test_foo_run, .run = test_foo_run,
.deinit = NULL, .deinit = NULL,
}; };
``` ```
3. Добавить `&k_test_foo` в реестр `test_runner.c` — одна строка.
3. Добавить `&k_test_foo` в реестр `test_runner.c`.
4. Добавить `tests/test_foo.c` в `CMakeLists.txt` таргета. 4. Добавить `tests/test_foo.c` в `CMakeLists.txt` таргета.
--- ---
## Рекомендации для разработчика хостового ПО
**Открытие порта:** найти CDC ACM устройство → открыть порт → ждать
`"type":"session_start"` (таймаут 10 с) → при отсутствии переоткрыть порт.
**Чтение событий:** буферизировать до `'\n'`; одна строка = одно сообщение;
неизвестный `"type"` — игнорировать (forward-compatibility).
**Отправка команд:** завершать каждую строку `'\n'` (не `'\r\n'`); не
отправлять следующую команду до `test_result` или `error` от предыдущей.
Исключение: `"ping"` можно отправлять в любой момент, если таргет не BUSY.
**Обработка `confirm_request`:** отобразить `prompt` → ждать реакции
оператора → отправить `{"type":"confirm","id":"<тот же id>","confirmed":true/false}`.
Исключение — тест `buttons`: не отправлять `confirm`, просто ждать следующего
события от таргета.
---
## Версионирование протокола ## Версионирование протокола
Поле `"fw"` в `session_start` — версия прошивки, а не версия протокола. Поле `"fw"` в `session_start` — версия прошивки. При несовместимых изменениях
При несовместимых изменениях протокола (новое обязательное поле, изменение протокола — bump `FIRMWARE_TEST_VERSION` в `protocol.h` с обновлением этого
семантики существующего) — bumping `FIRMWARE_TEST_VERSION` в `protocol.h` документа. Хост должен проверять `"fw"` и предупреждать оператора при
с соответствующим обновлением этого документа. несовпадении ожидаемой версии.
Хост должен проверять `"fw"` и предупреждать оператора при несовпадении
ожидаемой версии.

View file

@ -2,30 +2,31 @@
## Обзор стека ## Обзор стека
```bash ```mermaid
devcontainer хост flowchart LR
───────────────────────────────── ──────────────────────────────────── subgraph DC["Devcontainer"]
tests/target/<name>/ tools/hil/ C["tests/target/&lt;name&gt;/\nmain.c — C-прошивка с CLI\nCMakeLists.txt"]
main.c ← C-прошивка с CLI test_<name>.py ← pytest-тесты CP["CMakePresets.json\ntarget-debug-build"]
CMakeLists.txt conftest.py ← фикстуры (общие) JB["just/build.just\nbuild-hil"]
m5/agent.py ← агент M5 (если нужен) C --> CP --> JB
CMakePresets.json end
target-debug-build just/host.just
└── targets: [test_<name>] hil-run, hil-<name>
just/build.just subgraph Host["Хост"]
build-hil PY["tools/hil/\ntest_&lt;name&gt;.py — pytest\nconftest.py — фикстуры"]
JH["just/host.just\nhil-run, hil-&lt;name&gt;"]
PY --> JH
end
``` ```
Три типа тестов: Три типа тестов:
| Тип | Использует M5 | Запуск | Когда применять | | Тип | Использует M5 | Запуск | Когда применять |
| ----------------- | ------------- | --------------------- | ----------------------------------------------------- | | ----------------- | ------------- | --------------------- | ----------------------------------------------------- |
| **Базовый** | Нет | `hil-run` | Тестирование UART CLI, алгоритмов, таймингов | | **Базовый** | Нет | `hil-run` | UART CLI, алгоритмы, тайминги |
| **С M5** | Да | `hil-run` | Тестирование GPIO, оптовходов, реле, питания | | **С M5** | Да | `hil-run` | GPIO, оптовходы, реле, питание |
| **Интерактивный** | Нет / Да | `hil-run-interactive` | Периферия требует действий оператора: кнопки, дисплей | | **Интерактивный** | Нет / Да | `hil-run-interactive` | Периферия требует действий оператора: кнопки, дисплей |
Интерактивные тесты помечаются `@pytest.mark.interactive` и **никогда не входят в `hil-run`** — они требуют живого оператора и не пригодны для CI. Интерактивные тесты помечаются `@pytest.mark.interactive` и **не входят в `hil-run`**.
--- ---
@ -42,7 +43,7 @@ just/build.just
#define CLI_BAUD_RATE 115200U #define CLI_BAUD_RATE 115200U
#define CLI_LINE_MAX 128U #define CLI_LINE_MAX 128U
#define CLI_RX_TIMEOUT 100U /* мс — увеличить если нужен частый process() */ #define CLI_RX_TIMEOUT 100U
static size_t cli_read_line(uint8_t *p_buf, size_t max_len) static size_t cli_read_line(uint8_t *p_buf, size_t max_len)
{ {
@ -77,7 +78,6 @@ int main(void)
bsp_uart_host_init(CLI_BAUD_RATE); bsp_uart_host_init(CLI_BAUD_RATE);
bsp_led_on(LED_HEARTBEAT); bsp_led_on(LED_HEARTBEAT);
/* Шлём READY пока хост не открыл порт */
while (bsp_uart_host_rx_available() == 0U) { while (bsp_uart_host_rx_available() == 0U) {
bsp_uart_host_write_str("READY\r\n"); bsp_uart_host_write_str("READY\r\n");
bsp_delay(200U); bsp_delay(200U);
@ -85,7 +85,6 @@ int main(void)
static uint8_t s_line_buf[CLI_LINE_MAX]; static uint8_t s_line_buf[CLI_LINE_MAX];
for (;;) { for (;;) {
/* Если тест использует прерывания/process() — вызывать здесь */
size_t len = cli_read_line(s_line_buf, sizeof(s_line_buf)); size_t len = cli_read_line(s_line_buf, sizeof(s_line_buf));
if (len > 0U) cli_process_line((const char *)s_line_buf); if (len > 0U) cli_process_line((const char *)s_line_buf);
} }
@ -132,7 +131,7 @@ add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
```cmake ```cmake
add_subdirectory(host_uart) add_subdirectory(host_uart)
add_subdirectory(hil_opto) add_subdirectory(hil_opto)
add_subdirectory(<name>) # ← добавить строку add_subdirectory(<name>) # ← добавить
``` ```
--- ---
@ -153,13 +152,13 @@ add_subdirectory(<name>) # ← добавить строку
--- ---
## Шаг 4 — Сборка ## Шаг 4 — Собрать
```bash ```bash
# В devcontainer: # В devcontainer:
just build::build-hil just build::build-hil
# Проверить что новый таргет собрался: # Проверить:
ls build/target-debug/tests/target/<name>/test_<name>.elf ls build/target-debug/tests/target/<name>/test_<name>.elf
``` ```
@ -167,16 +166,10 @@ ls build/target-debug/tests/target/<name>/test_<name>.elf
## Шаг 5 — `conftest.py`: добавить фикстуры ## Шаг 5 — `conftest.py`: добавить фикстуры
Открыть `tools/hil/conftest.py` и добавить:
1. Фикстуру загрузки `loaded_<n>` в конец раздела загрузок.
2. Одну строку в `_UART_FIXTURE_MAP` — фабрика `_make_uart_fixture` автоматически
создаст фикстуру `uart_<n>` через контекстный менеджер `_uart_context`.
### Базовый тест (без M5) ### Базовый тест (без M5)
```python ```python
# 1. Фикстура загрузки — добавить в раздел loaded_* # 1. Фикстура загрузки
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def loaded_<n>(request: pytest.FixtureRequest) -> None: def loaded_<n>(request: pytest.FixtureRequest) -> None:
_load_elf( _load_elf(
@ -184,22 +177,19 @@ def loaded_<n>(request: pytest.FixtureRequest) -> None:
Path(cfg.BUILD_DIR) / "tests/target/<n>/test_<n>.elf", Path(cfg.BUILD_DIR) / "tests/target/<n>/test_<n>.elf",
) )
# 2. UART-фикстура — добавить одну строку в словарь # 2. UART-фикстура — одна строка в словарь
_UART_FIXTURE_MAP = { _UART_FIXTURE_MAP = {
... ...
"uart_<n>": "loaded_<n>", # ← добавить "uart_<n>": "loaded_<n>",
} }
``` ```
### Тест с M5 (GPIO, реле, питание) ### Тест с M5
```python ```python
# 1. Фикстура загрузки — зависимость от m5 гарантирует питание # 1. Зависимость от m5 гарантирует что питание включено до загрузки ELF
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def loaded_<n>(request: pytest.FixtureRequest, m5: M5Agent) -> None: def loaded_<n>(request: pytest.FixtureRequest, m5: M5Agent) -> None:
"""
Зависит от m5 — питание таргета уже включено к моменту загрузки ELF.
"""
_load_elf( _load_elf(
request, request,
Path(cfg.BUILD_DIR) / "tests/target/<n>/test_<n>.elf", Path(cfg.BUILD_DIR) / "tests/target/<n>/test_<n>.elf",
@ -208,23 +198,18 @@ def loaded_<n>(request: pytest.FixtureRequest, m5: M5Agent) -> None:
# 2. UART-фикстура — та же одна строка # 2. UART-фикстура — та же одна строка
_UART_FIXTURE_MAP = { _UART_FIXTURE_MAP = {
... ...
"uart_<n>": "loaded_<n>", # ← добавить "uart_<n>": "loaded_<n>",
} }
``` ```
**Правило:** если тест управляет железом через M5 — `loaded_<n>` должен явно **Правило:** если тест управляет железом через M5 — `loaded_<n>` должен явно
зависеть от `m5`. Это гарантирует что питание включено до того как pyOCD зависеть от `m5`, иначе pyOCD попытается подключиться до включения питания.
попытается подключиться к MCU.
> Ручное написание `uart_<n>` фикстур больше не требуется — фабрика
> `_make_uart_fixture` создаёт фикстуру с `_uart_context` (контекстный менеджер,
> гарантирует `ser.close()` при любом исходе).
--- ---
## Шаг 6 — `tools/hil/test_<name>.py` ## Шаг 6 — `tools/hil/test_<name>.py`
### Базовый тест (без M5) ### Базовый тест
```python ```python
"""test_<name>.py — HIL тест <что тестируем>.""" """test_<name>.py — HIL тест <что тестируем>."""
@ -239,23 +224,21 @@ class Test<Name>:
self.ser = uart_<name> self.ser = uart_<name>
def test_ping(self): def test_ping(self):
"""Базовая проверка канала."""
assert uart_cmd(self.ser, "PING") == "PONG" assert uart_cmd(self.ser, "PING") == "PONG"
def test_something(self): def test_something(self):
resp = uart_cmd(self.ser, "MY_CMD") assert uart_cmd(self.ser, "MY_CMD") == "EXPECTED"
assert resp == "EXPECTED"
``` ```
### Тест с M5 ### Тест с M5
```python ```python
"""test_<name>.py — HIL тест <что тестируем> через M5StampPLC.""" """test_<name>.py — HIL тест через M5StampPLC."""
import time import time
import pytest import pytest
from conftest import uart_cmd from conftest import uart_cmd
SETTLE_S = 0.15 # ждать после переключения реле SETTLE_S = 0.15
class Test<Name>: class Test<Name>:
@ -264,24 +247,19 @@ class Test<Name>:
def _setup(self, uart_<name>, m5): def _setup(self, uart_<name>, m5):
self.ser = uart_<name> self.ser = uart_<name>
self.m5 = m5 self.m5 = m5
self.m5.opto_all_off() # или другой сброс состояния стенда self.m5.opto_all_off()
time.sleep(SETTLE_S) time.sleep(SETTLE_S)
def test_ping(self): def test_ping(self):
assert uart_cmd(self.ser, "PING") == "PONG" assert uart_cmd(self.ser, "PING") == "PONG"
def test_m5_ping(self):
self.m5.ping()
def test_something_with_relay(self): def test_something_with_relay(self):
self.m5.opto_set(1, True) self.m5.opto_set(1, True)
time.sleep(SETTLE_S) time.sleep(SETTLE_S)
assert uart_cmd(self.ser, "READ_INPUT") == "ACTIVE" assert uart_cmd(self.ser, "READ_INPUT") == "ACTIVE"
``` ```
**Важно про таймауты:** после переключения реле нужно ждать: Для критичных к скорости тестов — активное ожидание вместо фиксированного `sleep`:
реле (~10 мс) + оптопара (~0.1 мс) + дебаунс прошивки + один цикл `process()`.
Используй активное ожидание вместо фиксированного `sleep` там где важна скорость:
```python ```python
def wait_until(ser, cmd, expected, timeout_s=1.0): def wait_until(ser, cmd, expected, timeout_s=1.0):
@ -293,22 +271,20 @@ def wait_until(ser, cmd, expected, timeout_s=1.0):
raise TimeoutError(f"Ожидали {expected!r} от '{cmd}'") raise TimeoutError(f"Ожидали {expected!r} от '{cmd}'")
``` ```
### Интерактивный тест (оператор нажимает кнопки / смотрит на дисплей) ### Интерактивный тест
Добавить маркер на класс. Для ввода использовать `/dev/tty` напрямую — `input()` не работает под захватом pytest даже с `-s`:
```python ```python
"""test_<name>.py — интерактивный HIL-тест <что тестируем>.""" """test_<name>.py — интерактивный HIL-тест."""
import time import time
import pytest import pytest
from conftest import uart_cmd from conftest import uart_cmd
SETTLE_S = 0.10 # ждать после действия оператора (debounce и т.п.) SETTLE_S = 0.10
def _operator_prompt(msg: str) -> None: def _operator_prompt(msg: str) -> None:
"""Вывести подсказку и дождаться Enter от оператора. """Вывести подсказку и ждать Enter. Читает /dev/tty напрямую — работает
Читает /dev/tty напрямую — работает независимо от захвата pytest.""" независимо от захвата pytest."""
with open("/dev/tty", "w") as tty_out: with open("/dev/tty", "w") as tty_out:
tty_out.write(f"\n >>> {msg}\n Нажмите Enter когда готово...\n") tty_out.write(f"\n >>> {msg}\n Нажмите Enter когда готово...\n")
tty_out.flush() tty_out.flush()
@ -332,14 +308,7 @@ class Test<Name>:
assert uart_cmd(self.ser, "MY_CMD") == "EXPECTED" assert uart_cmd(self.ser, "MY_CMD") == "EXPECTED"
``` ```
**Запуск интерактивных тестов:** Запуск: `just host::hil-run-interactive` или `just host::hil-<name>` (с флагом `-s`).
```bash
just host::hil-run-interactive # все интерактивные
just host::hil-button # конкретный интерактивный
```
**Правило:** интерактивные тесты **не добавлять** в `hil-run` — они входят только в `hil-run-interactive`.
--- ---
@ -365,68 +334,32 @@ hil-<n>:
--- ---
## Полный цикл
```bash
# 1. devcontainer — собрать прошивку
just build::build-hil
# 2. хост — убедиться что стенд готов (если тест использует M5)
just host::m5-deploy # если менялся agent.py
just host::m5-scan # убедиться что M5 видна
# 3. хост — запустить только новый тест
just host::hil-<name>
# 4. хост — загрузить ELF вручную без тестов (для отладки)
uv run --directory tools/hil python load_and_run.py \
build/target-debug/tests/target/<name>/test_<name>.elf
# 5. хост — запустить один тест
uv run --directory tools/hil pytest test_<name>.py::Test<Name>::test_ping -v
```
---
## Как работают фикстуры ## Как работают фикстуры
### Цепочка зависимостей ```mermaid
flowchart TB
TF["test_foo()"]
SU["_setup\n(function scope, autouse)"]
UN["uart_&lt;n&gt;\n(module scope)"]
LN["loaded_&lt;n&gt;\n(module scope)"]
M5["m5\n(module scope, если нужен)"]
```bash TF --> SU
test_foo() SU --> UN --> LN
└── _setup (function scope, autouse) LN --> M5
├── uart_<n> (module scope) ← открыт один раз на весь файл
│ └── loaded_<n> ← ELF загружен один раз
│ └── m5 ← (если нужен) питание включено
└── m5 (module scope) ← (если нужен напрямую в тесте)
``` ```
`scope=module` — фикстура создаётся один раз на весь тест-файл, уничтожается `scope=module` — фикстура создаётся один раз на весь тест-файл. ELF грузится
после последнего теста. ELF грузится один раз, порт открывается один раз. один раз, порт открывается один раз.
### Порядок при запуске нескольких файлов **Порядок при нескольких файлах:** каждый файл — своя загрузка ELF, свой
UART-сеанс. MCU перезагружается между файлами.
```bash
pytest 01_test_uart.py test_<name>.py
01_test_uart.py test_<name>.py
───────────────────── ─────────────────────
loaded_host_uart m5 ← создаётся
uart ← создаётся loaded_<name>
test_ping uart_<name> ← создаётся
test_echo test_ping
uart.close() test_something
uart_<name>.close()
m5 teardown → power(False)
```
Каждый файл — своя загрузка ELF, свой UART-сеанс. MCU перезагружается между файлами.
--- ---
## Чеклист ## Чеклист
### Автоматический тест (базовый или с M5) ### Автоматический тест
```bash ```bash
[ ] tests/target/<n>/main.c — C-прошивка с CLI + READY-паттерн [ ] tests/target/<n>/main.c — C-прошивка с CLI + READY-паттерн
@ -440,11 +373,11 @@ uart.close() test_something
[ ] just host::hil-<n> — зелёный прогон [ ] just host::hil-<n> — зелёный прогон
``` ```
### Интерактивный тест (дополнительно к базовому чеклисту) ### Дополнительно для интерактивного теста
```bash ```bash
[ ] @pytest.mark.interactive — пометить класс в test_<n>.py [ ] @pytest.mark.interactive — пометить класс в test_<n>.py
[ ] just/host.just — рецепт hil-<n> с флагом -s [ ] just/host.just — рецепт hil-<n> с флагом -s
[ ] just host::hil-run-interactive — зелёный прогон [ ] just host::hil-run-interactive — зелёный прогон
[ ] убедиться что just host::hil-run — NOT в выборке (маркер исключает) [ ] убедиться что just host::hil-run — НЕ включает этот тест
``` ```

View file

@ -1,376 +1,232 @@
# Добавление нового host-теста # Отладка прошивок через SWD + GDB
## Обзор стека ## Обзор архитектуры
Отладка построена на пробросе GDB-сервера с хоста в devcontainer по TCP. Это
позволяет держать весь инструментарий сборки и языковой сервер внутри контейнера,
не проводя USB-пробник внутрь Docker.
```mermaid
flowchart LR
subgraph Host["Хост (macOS / Linux)"]
DS["just host::debug-server\npyocd gdbserver :3333"]
ML["MCU-Link (CMSIS-DAP)"]
DS --> ML
end
subgraph DC["Devcontainer"]
CD["cortex-debug\n(VSCode F5)"]
GDB["arm-none-eabi-gdb\nсимволы из .elf"]
CD --> GDB
end
Board["MIMXRT1052\nFlash / SDRAM\nSEGGER RTT буфер"]
GDB -->|"TCP host.docker.internal:3333"| DS
ML -->|"SWD"| Board
```
**Ключевой принцип:** `pyocd gdbserver` слушает на `0.0.0.0:3333`. Из контейнера
GDB подключается через `host.docker.internal:3333` — специальный DNS-алиас Docker,
резолвится в IP хост-машины.
---
## Компоненты
### На хосте
| Компонент | Роль | Источник |
| --------------------------------- | ------------------------------- | -------------------------- |
| `pyocd` | GDB-сервер + flash-программатор | `tools/hil/uv.lock` |
| `MCU-Link` | CMSIS-DAP v2 пробник | USB к плате |
| `just host::debug-server` | Запуск GDB-сервера | `just/host.just` |
| `just host::flash-swd-*` | Прошивка через SWD | `just/host.just` |
| `tools/host/flash_swd.py` | Сборка FCB+HAB образа и запись | `tools/host/` |
| `tools/host/dcd/w25q128_fdcb.bin` | FCB для W25Q128 (Quad SPI) | NXP SecureProvisioningTool |
### В devcontainer
| Компонент | Роль |
| -------------------------------------- | ---------------------------------------------- |
| `arm-none-eabi-gdb` | GDB клиент, подключается к серверу на хосте |
| `cortex-debug` (VSCode extension) | UI для GDB: брейкпоинты, стек, регистры |
| `.vscode/launch.json` | Конфигурации запуска отладки |
| `.vscode/tasks.json` | `preLaunchTask` — пересборка ELF перед стартом |
| `build/Debug/*.elf` | Символы для GDB (DWARF debug info) |
| `bsp/generated/startup/MIMXRT1052.xml` | SVD — описание регистров периферии |
### Конфигурация
Параметры отладки задаются в `.env`:
```bash ```bash
devcontainer GDB_PORT=3333
───────────────────────────────────────────────────────────────── PYOCD_TARGET=mimxrt1050_quadspi
tests/host/<n>/test_<n>.c ← тест (Unity + опционально fff) PYOCD_FREQUENCY=4000000
tests/host/CMakeLists.txt ← регистрация через add_host_test() FCB_PATH=tools/host/dcd/w25q128_fdcb.bin
tests/host/mocks/ ← stub-хедеры NXP SDK (если нужны)
CMakePresets.json just/build.just
host-debug-build test-host
└── targets: [test_<n>] cmake --build + ctest
``` ```
--- ---
## Шаг 0 — Определить категорию теста ## Поддерживаемые прошивки
Перед написанием кода определи к какой категории относится модуль: | Конфигурация VSCode | ELF | Особенности |
| ----------------------------- | ------------------------------- | ---------------------------- |
| `🐛 Debug: firmware_test` | `build/Debug/firmware_test.elf` | Bare-metal, входной контроль |
| `🐛 Debug: bootloader` | `build/Debug/bootloader.elf` | Bare-metal, A/B обновление |
| `🐛 Debug: tft_app (FreeRTOS)` | `build/Debug/app.elf` | FreeRTOS, task view |
| Категория | Описание | Инструментарий | Все три — XIP-прошивки, исполняются из QuadSPI NOR Flash (`0x60000000`).
| --------- | -------------------------------------------------------------- | ------------------------- |
| **A** | Нет вызовов NXP SDK: алгоритмы, парсеры, FSM, структуры данных | Unity |
| **B** | BSP-модуль вызывает `fsl_*.h`, USB-стек и т.д. | Unity + fff + stub-хедеры |
**Признак категории A:** в `.c` файле модуля нет ни одного `#include "fsl_*.h"`.
**Признак категории B:** есть хотя бы один такой include.
--- ---
## Шаг 1 — Создать тестовый файл ## Режимы запуска отладки
### Категория A — платформонезависимый модуль ### Режим А — прошивка уже в Flash
```c
/* tests/host/<n>/test_<n>.c */
#include "unity.h"
#include "<n>.h" /* тестируемый модуль */
void setUp(void) { /* сброс состояния перед каждым тестом */ }
void tearDown(void) { /* очистка после каждого теста */ }
void test_something(void)
{
/* Arrange */
int input = 42;
/* Act */
int result = module_process(input);
/* Assert */
TEST_ASSERT_EQUAL(expected, result);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_something);
return UNITY_END();
}
```
### Категория B — BSP-модуль с fff-фейками
Порядок `#include` принципиален — нарушение порядка вызовет ошибки компиляции:
```c
/* tests/host/<n>/test_<n>.c */
#include "unity.h"
#include "fff.h"
DEFINE_FFF_GLOBALS; /* 1. ровно один раз на весь .c файл */
#include "fsl_<driver>.h" /* 2. stub-хедер с типами (из mocks/) */
/* 3. объявить фейки для всех SDK-функций, которые вызывает тестируемый модуль */
FAKE_VOID_FUNC(SDK_Function_A, ArgType1, ArgType2);
FAKE_VALUE_FUNC(status_t, SDK_Function_B, ArgType1);
#include "bsp/<module>.h" /* 4. тестируемый модуль — всегда последним */
void setUp(void)
{
RESET_FAKE(SDK_Function_A);
RESET_FAKE(SDK_Function_B);
FFF_RESET_HISTORY();
/* при необходимости задать дефолтные return_val */
}
void tearDown(void) { }
void test_something(void)
{
/* Arrange: настроить поведение фейков */
SDK_Function_B_fake.return_val = kStatus_Success;
/* Act */
bsp_status_t status = bsp_module_do_something();
/* Assert: проверить результат и вызовы */
TEST_ASSERT_EQUAL(BSP_OK, status);
TEST_ASSERT_EQUAL(1, SDK_Function_A_fake.call_count);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_something);
return UNITY_END();
}
```
---
## Шаг 2 — Stub-хедеры (только категория B)
Если тестируемый модуль использует NXP SDK хедеры которых ещё нет в `tests/host/mocks/` — нужно создать stub.
### Что такое stub-хедер и зачем он нужен
NXP SDK хедеры (`fsl_gpio.h` и др.) тянут платформенные регистровые определения для Cortex-M7 — они не компилируются на хосте. Stub-хедер в `tests/host/mocks/` содержит только минимально необходимые типы и сигнатуры функций. CMake подключает `mocks/` **до** SDK, поэтому компилятор находит stub раньше оригинала.
### Шаблон stub-хедера
Добавляй в stub только то, что реально используется в тестируемом `.c` файле:
```c
/* tests/host/mocks/fsl_<driver>.h */
#pragma once
#include <stdint.h>
/* Минимально необходимые типы */
typedef struct { uint32_t reserved[64]; } DRIVER_Type;
typedef enum {
kStatus_Success = 0,
kStatus_Fail = 1,
} status_t;
/* Сигнатуры функций — реализации предоставляет fff */
void SDK_Function_A(DRIVER_Type *base, uint32_t arg);
status_t SDK_Function_B(DRIVER_Type *base, const uint8_t *data, size_t len);
```
### Уже существующие stubs в `tests/host/mocks/`
| Файл | Что заменяет | Используется в |
| ------------ | ----------------------------- | -------------- |
| `fsl_gpio.h` | GPIO драйвер | `test_bsp_led` |
| `pin_mux.h` | Макросы пинов из `generated/` | `test_bsp_led` |
| `board.h` | `board_hw_init()` | `test_bsp_led` |
Если нужный stub уже есть — ничего создавать не нужно, просто укажи `mocks/` в `MOCKS` аргументе `add_host_test()`.
---
## Шаг 3 — Зарегистрировать тест в `tests/host/CMakeLists.txt`
Используй функцию `add_host_test()`. Она создаёт исполняемый файл и регистрирует его в CTest:
```cmake
# Категория A — без mocks
add_host_test(
NAME test_<n>
SOURCES <n>/test_<n>.c
${PROJECT_SOURCE_DIR}/<path_to_module>/<module>.c
INCLUDES
${PROJECT_SOURCE_DIR}/<path_to_module>/include
)
# Категория B — с mocks
add_host_test(
NAME test_<n>
SOURCES <n>/test_<n>.c
${PROJECT_SOURCE_DIR}/bsp/<module>/src/<module>.c
INCLUDES
${PROJECT_SOURCE_DIR}/bsp/<module>/include
MOCKS
${BSP_MOCKS_DIR} # = tests/host/mocks/
)
```
### Аргументы `add_host_test()`
| Аргумент | Обязателен | Описание |
| ---------- | ---------- | --------------------------------------------------------------- |
| `NAME` | ✓ | Имя исполняемого файла и теста в CTest |
| `SOURCES` | ✓ | Тестовый `.c` + исходники тестируемых модулей |
| `INCLUDES` | — | Дополнительные include-пути (для `#include "bsp/led.h"` и т.д.) |
| `MOCKS` | — | Директории со stub-хедерами (подключаются с высшим приоритетом) |
`lib_external` (Unity + fff) подключается автоматически — добавлять не нужно.
---
## Шаг 4 — Добавить таргет в `CMakePresets.json`
```json
{
"name": "host-debug-build",
"configurePreset": "host-debug",
"targets": [
"test_bsp_led",
"test_ring_buffer",
"test_timeout_pattern",
"uart_host_mock_example",
"test_<n>"
]
}
```
То же самое для `host-release-build` если нужен Release-прогон.
---
## Шаг 5 — Запустить
```bash ```bash
# Сборка + все тесты одной командой (devcontainer) # 1. Хост — запустить GDB-сервер (оставить в отдельном терминале)
just build::test-host just host::debug-server
# Только новый тест # 2. DevContainer — VSCode
ctest --preset host-debug-test -R test_<n> -V # Run & Debug (Ctrl+Shift+D) → выбрать конфигурацию → F5
# Напрямую — виден полный вывод Unity без CTest-обёртки
./build/host-debug/tests/host/test_<n>
``` ```
--- GDB сбрасывает MCU, загружает символы из ELF и останавливается на входе
в `main`. Flash не перезаписывается.
## Справочник: Unity assertions ### Режим Б — прошить через SWD, затем отладить
```c
/* Целые числа */
TEST_ASSERT_EQUAL(expected, actual)
TEST_ASSERT_EQUAL_INT8 / INT16 / INT32 / UINT8 / UINT32(expected, actual)
TEST_ASSERT_NOT_EQUAL(expected, actual)
TEST_ASSERT_INT_WITHIN(delta, expected, actual)
/* Булевые / указатели */
TEST_ASSERT_TRUE(condition)
TEST_ASSERT_FALSE(condition)
TEST_ASSERT_NULL(pointer)
TEST_ASSERT_NOT_NULL(pointer)
/* Строки / память */
TEST_ASSERT_EQUAL_STRING(expected, actual)
TEST_ASSERT_EQUAL_MEMORY(expected, actual, len)
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, len)
/* Явный провал / пропуск */
TEST_FAIL_MESSAGE("причина")
TEST_IGNORE_MESSAGE("в процессе")
```
---
## Справочник: fff-фейки
### Объявление
```c
FAKE_VOID_FUNC(func, ArgType1, ArgType2); /* void-функция */
FAKE_VALUE_FUNC(RetType, func, ArgType1, ArgType2);/* с возвращаемым значением */
FAKE_VOID_FUNC_VARARG(func, const char *, ...); /* variadic */
```
### Управление поведением
```c
/* Фиксированное возвращаемое значение */
func_fake.return_val = kStatus_Fail;
/* Последовательность значений */
status_t seq[] = {kStatus_Success, kStatus_Success, kStatus_Fail};
SET_RETURN_SEQ(func, seq, 3);
/* Кастомная реализация — высший приоритет, перекрывает return_val */
func_fake.custom_fake = my_impl;
```
### Проверка вызовов
```c
TEST_ASSERT_EQUAL(2, func_fake.call_count); /* сколько раз вызвана */
TEST_ASSERT_EQUAL(expected, func_fake.arg0_val); /* аргумент последнего вызова */
TEST_ASSERT_EQUAL_PTR(func, fff.call_history[0]); /* порядок вызовов */
TEST_ASSERT_EQUAL(0, func_fake.call_count); /* не была вызвана */
```
### Сброс в setUp
```c
void setUp(void)
{
RESET_FAKE(func_a); /* сбрасывает счётчик, историю, return_val, custom_fake */
RESET_FAKE(func_b);
FFF_RESET_HISTORY(); /* сбрасывает глобальную историю порядка вызовов */
}
```
---
## Ловушки
**Dangling pointer из `arg_history[]`.**
`arg_history[]` хранит указатели, не копии. Если функция получала указатель на стековую переменную — после возврата это UB. Использовать `custom_fake` с копированием по значению:
```c
static gpio_pin_config_t s_captured;
static void capture(GPIO_Type *base, uint32_t pin, const gpio_pin_config_t *cfg)
{
s_captured = *cfg; /* копия по значению пока стек ещё жив */
}
void setUp(void) {
RESET_FAKE(GPIO_PinInit);
GPIO_PinInit_fake.custom_fake = capture;
bsp_led_init();
}
void test_init_output(void) {
TEST_ASSERT_EQUAL(kGPIO_DigitalOutput, s_captured.direction);
}
```
**`static` функции не мокаются.**
FFF не видит `static` функции снаружи translation unit. Решение — compile-time seam:
```c
#ifdef UNIT_TEST
void internal_fn(void); /* тест подставит свою реализацию */
#else
static void internal_fn(void) { ... }
#endif
```
**Отсутствие стандартных хедеров в BSP.**
BSP-модули должны явно включать `<stdint.h>`, `<stdbool.h>`, `<stddef.h>` — не полагаться на транзитивное подтягивание через NXP SDK. На хосте этот транзит отсутствует, компиляция упадёт с `undeclared identifier 'size_t'`.
---
## Полный цикл
```bash ```bash
# 1. Создать тестовый файл # 1. DevContainer
tests/host/<n>/test_<n>.c just build::hab-firmware-test-debug
# 2. Создать stub-хедер (если категория B и stub не существует) # 2. Хост
tests/host/mocks/fsl_<driver>.h just host::flash-swd-test-debug
# 3. Добавить вызов add_host_test() в # 3. ⚡ Power cycle платы (обязательно)
tests/host/CMakeLists.txt
# 4. Добавить "test_<n>" в targets в # 4. Хост
CMakePresets.json ← host-debug-build и host-release-build just host::debug-server
# 5. Запустить в devcontainer # 5. DevContainer — VSCode → 🐛 Debug: firmware_test → F5
just build::test-host ```
### Режим В — прошить через USB SDP, затем отладить
```bash
# 1. DevContainer
just build::build-firmware-test-debug
# 2. Хост — перевести плату в SDP-режим, затем:
just host::flash-test-debug
# 3. Хост
just host::debug-server
# 4. DevContainer — VSCode → 🐛 Debug: firmware_test → F5
``` ```
--- ---
## Чеклист ## Почему flash через SWD требует FCB
При USB SDP ROM-загрузчик инициализирует FlexSPI по DCD из HAB-образа — FCB
не нужен. При SWD flash-алгоритм pyOCD пишет в NOR Flash напрямую. При
cold-start Boot ROM сначала читает FCB по адресу `0x60000000`, конфигурирует
FlexSPI, и только потом ищет IVT. Без FCB бутлоадер не стартует.
`flash_swd.py` решает это, собирая образ перед записью:
```bash ```bash
[ ] Определена категория (A или B) 0x60000000 w25q128_fdcb.bin (512 байт) — FCB
[ ] tests/host/<n>/test_<n>.c — тест с main(), setUp(), tearDown() 0x60000200 0xFF × 3584 байт — padding
[ ] tests/host/mocks/fsl_<driver>.h — stub (только категория B, если нет) 0x60001000 firmware_test_hab.bin — IVT + DCD + код
[ ] tests/host/CMakeLists.txt — add_host_test(NAME test_<n> ...) ```
[ ] CMakePresets.json — добавить test_<n> в host-debug-build
[ ] just build::test-host — зелёный прогон Весь диапазон `0x600000000x6000FFFF` — один 64KB сектор: стирается и
записывается за одну транзакцию.
---
## RTT-логи
SEGGER RTT включён только в Debug-сборках (`SEGGER_RTT_ENABLED=ON`).
После старта отладки вкладка `TERMINAL → RTT` принимает вывод канала 0.
`cortex-debug` находит адрес буфера по символу `_SEGGER_RTT` из ELF.
```c
#include "SEGGER_RTT.h"
SEGGER_RTT_printf(0, "value = %d\n", value);
```
---
## FreeRTOS task view
Конфигурация `🐛 Debug: tft_app (FreeRTOS)` включает `"rtos": "FreeRTOS"`
cortex-debug разбирает структуры планировщика и показывает вкладку `RTOS`
с таблицей задач: имя, состояние, использование стека, приоритет.
---
## Просмотр регистров периферии
Вкладка `Peripherals` показывает все блоки MIMXRT1052 по SVD-файлу
`bsp/generated/startup/MIMXRT1052.xml`. Значения обновляются при каждой паузе.
---
## Ограничения
**MCU-Link монопольный ресурс.** `debug-server` и `flash-swd` не могут
работать одновременно. Перед `flash-swd` остановите сервер (Ctrl+C).
**HIL-тесты vs отладка.** pyOCD также используется для HIL. Перед
`just host::hil-run` остановите GDB-сервер.
**Power cycle после flash-swd обязателен.** VECTRESET не реинициализирует
FlexSPI — только полное отключение питания гарантирует корректный cold-start.
**Только Debug-сборки.** Release компилируется с `-O2` без DWARF-символов.
---
## Быстрый старт (первый запуск)
```bash
# 1. Убедиться что в .devcontainer/devcontainer.json есть (для Linux):
# "runArgs": ["--add-host=host.docker.internal:host-gateway"]
# 2. Залить прошивку
just host::flash-test-debug
# 3. Хост — запустить GDB-сервер
just host::debug-server
# 4. DevContainer — VSCode
# Ctrl+Shift+D → 🐛 Debug: firmware_test → F5
```
---
## Дерево файлов отладки
```bash
.
├── .env # GDB_PORT, PYOCD_TARGET, PYOCD_FREQUENCY, FCB_PATH
├── .vscode/
│ ├── launch.json # cortex-debug конфигурации (3 проекта)
│ └── tasks.json # preLaunchTask: build:*-debug
├── bsp/generated/startup/
│ └── MIMXRT1052.xml # SVD — регистры периферии
├── just/
│ └── host.just # debug-server, flash-swd-*
└── tools/
├── hil/ # uv-проект с pyocd
└── host/
├── flash_swd.py # FCB + HAB → Flash через pyOCD
└── dcd/
└── w25q128_fdcb.bin # FCB для W25Q128 Quad SPI
``` ```

View file

@ -15,6 +15,7 @@ add_executable(
src/tests/test_qspi.c src/tests/test_qspi.c
src/tests/test_usd.c src/tests/test_usd.c
src/tests/test_display.c src/tests/test_display.c
src/tests/test_buttons.c
${BSP_GENERATED}/clock_config.c ${BSP_GENERATED}/clock_config.c
${BSP_STARTUP_FILE} ${BSP_STARTUP_FILE}
${BSP_SYSCALLS_FILE}) ${BSP_SYSCALLS_FILE})
@ -38,6 +39,7 @@ target_link_libraries(
${TARGET_NAME} ${TARGET_NAME}
PRIVATE bsp_board PRIVATE bsp_board
bsp_led bsp_led
bsp_button
bsp_display bsp_display
bsp_tick bsp_tick
bsp_boot_xip bsp_boot_xip

View file

@ -1,6 +1,6 @@
# firmware_test — Plan of Development # firmware_test — План разработки
> Версия: 0.4 | Обновлён после завершения Этапов 13 (bsp_sdram + bsp_qspi_flash). > Версия: 0.5 | Обновлён после завершения Этапа 5 (display + buttons).
--- ---
@ -19,17 +19,16 @@
## Текущий статус ## Текущий статус
| Компонент | Статус | Примечание | | Компонент | Статус | Примечание |
| -------------------------- | ------ | -------------------------------------- | | ------------------------------ | ------ | -------------------------------------------- |
| `bsp_usb_cdc` | ✅ | HIL тест пройден | | `bsp_usb_cdc` | ✅ | HIL тест пройден |
| firmware_test скелет | ✅ | `main.c` + `cli.c` | | firmware_test скелет | ✅ | `main.c` + `cli.c` |
| Протокол v2 + test_runner | ✅ | JSON-lines event-driven | | Протокол v2 + test_runner | ✅ | JSON-lines event-driven |
| `bsp_sdram` + `test_sdram` | ✅ | 4 фазы: addr/data/seq/retention | | `bsp_sdram` + `test_sdram` | ✅ | 4 фазы: addr/data/seq/retention |
| `bsp_qspi_flash` | ✅ | W25Q64/128/256/512, ITCM, IRQ lock | | `bsp_qspi_flash` | ✅ | W25Q64/128/256/512, ITCM, IRQ lock |
| `test_qspi` | ✅ | JEDEC + erase + rw + addr range | | `test_qspi` | ✅ | JEDEC + erase + rw + addr range |
| `bsp_usd` | ✅ | bsp_sd + FatFS (firmware_test_fatfs) | | `bsp_sd` + `test_usd` | ✅ | bsp_sd + FatFS, pre_confirm, 4 шага |
| `test_usd` | ✅ | pre_confirm + 4 шага + progress events | | `bsp_display` + `test_display` | ✅ | 4 цвета + ротация, hardware-verified |
| Display test | ⬜ | Этап 5 | | `bsp_button` + `test_buttons` | ✅ | 2 кнопки, physical detect, hardware-verified |
| Button test | ⬜ | Этап 5 |
| CAN test | ⬜ | Этап 6 (bsp_can ✅) | | CAN test | ⬜ | Этап 6 (bsp_can ✅) |
| UART TTL test | ⬜ | Этап 6 (bsp_uart_host ✅) | | UART TTL test | ⬜ | Этап 6 (bsp_uart_host ✅) |
| UART ISO test | ⬜ | Этап 6 | | UART ISO test | ⬜ | Этап 6 |
@ -52,10 +51,13 @@
- **Порядок init в main.c:** `bsp_qspi_init()` до `bsp_tick_init()`. - **Порядок init в main.c:** `bsp_qspi_init()` до `bsp_tick_init()`.
- **IR и RTC:** не реализуются. - **IR и RTC:** не реализуются.
- **Производственный runner:** Вариант D — отдельный `tools/production/` без pytest. - **Производственный runner:** Вариант D — отдельный `tools/production/` без pytest.
- **bsp_button_init():** вызывается в `init()` тест-модуля, не в `main.c`.
- **Тест дисплея:** 4 цвета + 2 ротации (TFT ≠ TFT4). Таймаут confirm 15 с → FAIL.
- **Тест кнопок:** физическая детекция через `bsp_button`. Хост не отправляет JSON confirm. Таймаут 10 с → SKIP.
--- ---
## Этап 4 — `bsp_usd` + `test_usd` ## Этап 4 — bsp_sd + test_usd ✅
### Аппаратный контекст ### Аппаратный контекст
@ -64,66 +66,9 @@
| Интерфейс | USDHC (SDIO) | | Интерфейс | USDHC (SDIO) |
| Карта | microSD, вставляется оператором перед тестом | | Карта | microSD, вставляется оператором перед тестом |
| Файловая система | FatFS (SDK middleware) | | Файловая система | FatFS (SDK middleware) |
| Детект карты | GPIO (CD pin) или опрос через USDHC status | | Детект карты | `USDHC_GetPresentStatusFlags` |
### BSP API (предварительно) ### Закрытые решения — Этап 4
```c
/* bsp/usd/include/bsp/usd.h */
typedef enum {
BSP_USD_OK = 0,
BSP_USD_ERR_NO_CARD, /* карта не вставлена */
BSP_USD_ERR_INIT, /* USDHC или FatFS init failed */
BSP_USD_ERR_MOUNT, /* f_mount() failed */
BSP_USD_ERR_RW, /* read/write/compare failed */
} bsp_usd_status_t;
bsp_usd_status_t bsp_usd_init(void);
bsp_usd_status_t bsp_usd_is_card_present(void);
bsp_usd_status_t bsp_usd_test_rw(void); /* write + read + compare тестового файла */
void bsp_usd_deinit(void);
```
### test_usd — шаги
| Шаг | Действие | Время |
| ----------------- | ------------------------------ | -------- |
| 1: Card detect | `bsp_usd_is_card_present()` | < 1 мс |
| 2: Mount | `f_mount()` — FAT/exFAT | < 200 мс |
| 3: Write | Записать 4 KB тестовый файл | < 500 мс |
| 4: Read + Compare | Прочитать и сравнить побайтово | < 200 мс |
| 5: Unmount | `f_unmount()` | < 50 мс |
### Интерактивность
Тест помечен `requires_hil = false`, `pre_confirm_prompt = "Вставьте microSD и нажмите OK"`.
test_runner ждёт `{"type":"confirm","id":"usd_insert","confirmed":true}` до вызова `run()`.
Отказ или таймаут 30 с`TEST_STATUS_SKIP`.
### Файлы
```
bsp/usd/
├── CMakeLists.txt
├── README.md
├── include/bsp/usd.h
└── src/usd.c
firmware/test/src/tests/test_usd.c
```
### CMake
```cmake
# bsp/usd/CMakeLists.txt
target_link_libraries(bsp_usd
PUBLIC bsp_status
PRIVATE bsp_board sdk_usdhc middleware_fatfs
)
```
### Закрытые решения (Этап 4)
- BSP-слой: `bsp_sd` (host init/deinit/card detect) + `firmware_test_fatfs` (FatFS). - BSP-слой: `bsp_sd` (host init/deinit/card detect) + `firmware_test_fatfs` (FatFS).
`bsp_usd` как отдельный модуль не создавался — тест работает напрямую через `bsp_sd` + `ff.h`. `bsp_usd` как отдельный модуль не создавался — тест работает напрямую через `bsp_sd` + `ff.h`.
@ -133,48 +78,43 @@ target_link_libraries(bsp_usd
- Паттерн: `byte[i] = i & 0xFF`, 4096 байт. - Паттерн: `byte[i] = i & 0xFF`, 4096 байт.
- `critical = false`: тест не блокирует HIL-тесты при отсутствии карты. - `critical = false`: тест не блокирует HIL-тесты при отсутствии карты.
- Confirm timeout: 30 000 мс (`PROTOCOL_CONFIRM_TIMEOUT_MS`). - Confirm timeout: 30 000 мс (`PROTOCOL_CONFIRM_TIMEOUT_MS`).
- `SD_HostInit` не вызывается в `bsp_sd_init()``sd_disk_initialize` делает полный init. Двойной init даёт `FR_NOT_READY`.
- Отдельный `test_usd.h` не создавался — `extern K_TEST_USD` объявлен в `test_runner.c`. - Отдельный `test_usd.h` не создавался — `extern K_TEST_USD` объявлен в `test_runner.c`.
--- ---
## Этап 5 — Display + Button (интерактивные) ## Этап 5 — Display + Buttons ✅
### test_display
| Параметр | Значение |
| -------- | ------------------------------------ |
| Critical | ❌ |
| HIL | ❌ |
| Confirm | Внутри `run()` — 4 отдельных confirm |
Шаги: заливка Red → confirm → Green → confirm → Blue → confirm → White → confirm.
Каждый шаг посылает `confirm_request`, ждёт `confirm` с таймаутом 15 с.
Итог = AND всех четырёх подтверждений.
`detail` при FAIL содержит ID первого непрошедшего шага: `"display_blue not confirmed"`.
### test_buttons
| Параметр | Значение |
| -------- | ------------------------------------- |
| Critical | ❌ |
| HIL | ❌ |
| Confirm | prompt only (детект через bsp_button) |
Шаги: Test_But_1 → Test_But_2. Таргет посылает `confirm_request` как инструкцию
оператору, детектирует нажатие через `bsp_button` — JSON confirm не нужен.
Таймаут 10 с на каждую кнопку.
### Аппаратный контекст кнопок ### Аппаратный контекст кнопок
| Кнопка | Пин MCU | GPIO | | Кнопка | Пин MCU | GPIO | Схема | Нажатие |
| ---------- | ---------- | --------- | | ---------- | ---------- | --------- | ----------------------------- | ------- |
| Test_But_1 | GPIO_B1_14 | GPIO2[30] | | Test_But_1 | GPIO_B1_14 | GPIO2[30] | SWT6x6, pull-up к 3V3 внешний | LOW |
| Test_But_2 | GPIO_B1_15 | GPIO2[31] | | Test_But_2 | GPIO_B1_15 | GPIO2[31] | SWT6x6, pull-up к 3V3 внешний | LOW |
### Закрытые решения — test_display
- `pre_confirm_prompt = NULL` — нет pre-confirm, `test_begin` отправляется сразу.
- 6 шагов confirm: 4 цвета (Red/Green/Blue/White) + 2 ротации (только для TFT ≠ TFT4).
- Таймаут каждого confirm: 15 000 мс. Не подтверждён → FAIL с `detail = "<id> not confirmed"`.
- Ротация: `ROTATE_0` + `FLIP_HORIZONTAL`. Восстановить `ROTATE_0` в любом исходе.
- Фреймбуфер: статический в NonCacheable SDRAM (`AT_NONCACHEABLE_SECTION_ALIGN`, 64-byte align).
- Тип дисплея: `DISPLAY_TEST_TYPE=BSP_DISPLAY_TFT8` через CMake compile definition.
### Закрытые решения — test_buttons
- `pre_confirm_prompt = NULL``confirm_request` используется только как UI-подсказка оператору.
- Хост **не** отправляет `{"type":"confirm",...}`. Детект нажатия — через `bsp_button_get_event_pressed()`.
- Таймаут: 10 000 мс → `TEST_STATUS_SKIP` (не FAIL).
- Порядок: But_1 → But_2.
- `bsp_button_poll()` вызывается каждые 5 мс через rate-limiting по `bsp_tick_get_ms()`.
- На каждом poll дренируются события **обеих** кнопок: предотвращает stale-событие от нецелевой кнопки.
- `bsp_button_init()` вызывается в `init()` тест-модуля — сброс debounce-счётчиков перед тестом.
- `bsp_tick_delay_ms()` не используется — polling pattern аналогичен `test_runner_wait_confirm()`.
--- ---
## Этап 6 — CAN + UART + Opto (HIL, M5StampPLC) ## Этап 6 — CAN + UART + Opto (HIL, M5StampPLC) ← ТЕКУЩИЙ
Все три теста `requires_hil = true`. Запускаются только при наличии стенда. Все три теста `requires_hil = true`. Запускаются только при наличии стенда.
BSP для всех трёх уже готов. BSP для всех трёх уже готов.
@ -184,9 +124,10 @@ BSP для всех трёх уже готов.
M5StampPLC отправляет CAN-фрейм → плата принимает → сравниваем ID и payload. M5StampPLC отправляет CAN-фрейм → плата принимает → сравниваем ID и payload.
**Шаги:** **Шаги:**
1. M5 → `{"cmd":"can_send","id":0x100,"data":[0xDE,0xAD,0xBE,0xEF]}` (через `confirm_request`) 1. Таргет посылает `confirm_request` → M5 получает команду `can_send`
2. Таргет: `uart_cmd("CAN_RECV 500")``"100 DEADBEEF"` или `"TIMEOUT"` 2. M5 → `{"cmd":"can_send","id":0x100,"data":[0xDE,0xAD,0xBE,0xEF]}`
3. Ответный: таргет посылает → M5 `can_recv` → верификация 3. Таргет: ожидает CAN-фрейм, 500 мс → верификация ID и payload
4. Ответный: таргет посылает → M5 `can_recv` → верификация
### test_uart_ttl ### test_uart_ttl
@ -202,6 +143,7 @@ M5 RLY2 → RS_RX оптовход (BSP_OPTO_CH_RS) → детект ACTIVE/INAC
M5 RLY3/RLY4 → EXT_IN1/IN2 → детект ACTIVE/INACTIVE через `bsp_opto`. M5 RLY3/RLY4 → EXT_IN1/IN2 → детект ACTIVE/INACTIVE через `bsp_opto`.
**Параметры стенда (из HIL_BENCH.md):** **Параметры стенда (из HIL_BENCH.md):**
``` ```
RLY2 → RS_RX (BSP_OPTO_CH_RS) GPIO1[23] RLY2 → RS_RX (BSP_OPTO_CH_RS) GPIO1[23]
RLY3 → EXT_IN1 (BSP_OPTO_CH_IN1) GPIO1[22] RLY3 → EXT_IN1 (BSP_OPTO_CH_IN1) GPIO1[22]
@ -221,7 +163,7 @@ tools/hil/
``` ```
Фикстура `firmware_cdc` открывает CDC порт firmware_test (прошит в Flash), Фикстура `firmware_cdc` открывает CDC порт firmware_test (прошит в Flash),
посылает JSON команды, читает события. Аналог `uart_cmd` для USB CDC. посылает JSON команды, читает события.
--- ---
@ -241,9 +183,9 @@ tools/hil/
bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len); /* 8 байт из OCOTP */ bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len); /* 8 байт из OCOTP */
``` ```
### Открытые вопросы ### Открытые вопросы — Этап 7
- [ ] Что именно записывать как "пройдено": флаг в Flash или только отправить UID? - [ ] Что именно записывать как «пройдено»: флаг в Flash или только отправить UID?
- [ ] Нужна ли защита от повторного provisioning (write-once)? - [ ] Нужна ли защита от повторного provisioning (write-once)?
--- ---
@ -251,15 +193,15 @@ bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len); /* 8 байт из
## Матрица тестов — итоговая ## Матрица тестов — итоговая
| ID | Название | Тип | Critical | HIL (M5) | BSP | Статус | | ID | Название | Тип | Critical | HIL (M5) | BSP | Статус |
| ---------- | -------------- | ----------- | -------- | -------- | ----------------- | ------ | | ---------- | -------------- | ----------- | -------- | -------- | ------------------ | ------ |
| — | PING | cmd | — | ❌ | — | ✅ | | — | PING | cmd | — | ❌ | — | ✅ |
| `sdram` | SDRAM 32MB | self | ✅ | ❌ | `bsp_sdram` ✅ | ✅ | | `sdram` | SDRAM 32 MB | self | ✅ | ❌ | `bsp_sdram` | ✅ |
| `qspi` | QSPI Flash | self | ✅ | ❌ | `bsp_qspi_flash`✅ | ✅ | | `qspi` | QSPI Flash | self | ✅ | ❌ | `bsp_qspi_flash` ✅ | ✅ |
| `usd` | uSD (SDIO) | interactive | ❌ | ❌ | `bsp_sd` ✅ | ✅ | | `usd` | uSD (SDIO) | interactive | ❌ | ❌ | `bsp_sd` ✅ | ✅ |
| `display` | Display RGB888 | interactive | ❌ | ❌ | существующий BSP | ⬜ | | `display` | Display RGB888 | interactive | ❌ | ❌ | `bsp_display` ✅ | ✅ |
| `buttons` | Test_But_1/2 | interactive | ❌ | ❌ | `bsp_button`| ⬜ | | `buttons` | Test_But_1/2 | interactive | ❌ | ❌ | `bsp_button` | ✅ |
| `can` | CAN loopback | HIL | ❌ | ✅ | `bsp_can` ✅ | ⬜ | | `can` | CAN loopback | HIL | ❌ | ✅ | `bsp_can` ✅ | ⬜ |
| `uart_ttl` | UART TTL | HIL | ❌ | ✅ | `bsp_uart_host`✅ | ⬜ | | `uart_ttl` | UART TTL | HIL | ❌ | ✅ | `bsp_uart_host` ✅ | ⬜ |
| `uart_iso` | UART ISO +24V | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ | | `uart_iso` | UART ISO +24V | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ |
| `opto` | Opto-in EXT | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ | | `opto` | Opto-in EXT | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ |
@ -272,8 +214,8 @@ bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len); /* 8 байт из
✅ Этап 2 (bsp_sdram + test_sdram) ✅ Этап 2 (bsp_sdram + test_sdram)
✅ Этап 3 (bsp_qspi_flash + test_qspi) ✅ Этап 3 (bsp_qspi_flash + test_qspi)
✅ Этап 4 (bsp_sd + test_usd) ✅ Этап 4 (bsp_sd + test_usd)
⬜ Этап 5 (display + buttons) ← ТЕКУЩИЙ ✅ Этап 5 (display + buttons)
⬜ Этап 6 (CAN + UART + Opto, HIL) ⬜ Этап 6 (CAN + UART + Opto, HIL) ← ТЕКУЩИЙ
⬜ Этап 7 (provisioning) ⬜ Этап 7 (provisioning)
⬜ Этап 8 (tools/production/ TUI runner) ← параллельно с 6-7 ⬜ Этап 8 (tools/production/ TUI runner) ← параллельно с 6-7
``` ```
@ -282,13 +224,9 @@ bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len); /* 8 байт из
## Хостовое ПО производственного прогона (Этап 8) ## Хостовое ПО производственного прогона (Этап 8)
**Решение принято (Вариант D):** отдельное приложение `tools/production/`, **Решение принято (Вариант D):** отдельное приложение `tools/production/`, без pytest, с TUI (Textual).
без pytest, с TUI (Textual).
Подробная архитектура описана в предыдущей версии плана (v0.2, раздел ### Открытые вопросы — Этап 8
"Открытый вопрос: ПО на стороне хоста").
### Открытые вопросы (перед Этапом 8)
- [ ] TUI: Textual или Rich или plain print на первой итерации? - [ ] TUI: Textual или Rich или plain print на первой итерации?
- [ ] БД: SQLite локально или REST API? - [ ] БД: SQLite локально или REST API?

View file

@ -29,13 +29,11 @@ extern const test_module_t K_TEST_SDRAM;
extern const test_module_t K_TEST_QSPI; extern const test_module_t K_TEST_QSPI;
extern const test_module_t K_TEST_USD; extern const test_module_t K_TEST_USD;
extern const test_module_t K_TEST_DISPLAY; extern const test_module_t K_TEST_DISPLAY;
extern const test_module_t K_TEST_BUTTONS;
static const test_module_t *const k_registry[] = { static const test_module_t *const k_registry[] = {
/* populated starting from Этап 2 */
&K_TEST_SDRAM, &K_TEST_SDRAM, &K_TEST_QSPI, &K_TEST_USD, &K_TEST_DISPLAY, &K_TEST_BUTTONS,
&K_TEST_QSPI,
&K_TEST_USD,
&K_TEST_DISPLAY,
}; };
#define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0])) #define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0]))

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,175 @@
/**
* @file test_buttons.c
* @brief Тест-модуль firmware_test: тактовые кнопки Test_But_1 / Test_But_2.
*
* Логика теста:
* 1. Отправить confirm_request("btn1_press") как инструкцию оператору.
* 2. Поллить bsp_button ждать события нажатия Test_But_1,
* таймаут BTN_PRESS_TIMEOUT_MS SKIP.
* 3. То же для Test_But_2 ("btn2_press").
*
* Хост НЕ отправляет {"type":"confirm",...}.
* confirm_request только UI-подсказка оператору.
* Физическое событие детектируется через bsp_button_get_event_pressed().
*
* Аппаратура:
* Test_But_1 GPIO_B1_14 / GPIO2[30], pull-up 3V3, нажатие = LOW
* Test_But_2 GPIO_B1_15 / GPIO2[31], pull-up 3V3, нажатие = LOW
*/
#include "bsp/button.h"
#include "bsp/tick.h"
#include "bsp/usb_cdc.h"
#include "cli.h"
#include "protocol.h"
#include "test_module.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
/* ── Константы ─────────────────────────────────────────────────────────── */
/** @brief Таймаут ожидания нажатия кнопки, мс. */
#define BTN_PRESS_TIMEOUT_MS 10000U
/** @brief Период вызова bsp_button_poll(), мс. */
#define BTN_POLL_PERIOD_MS 5U
/* ── Вспомогательные функции ────────────────────────────────────────────── */
/**
* @brief Создать результат FAIL с текстовым описанием.
*
* @param p_detail Строка описания (копируется в result.detail).
* @return Заполненный test_result_t со статусом TEST_STATUS_FAIL.
*/
static test_result_t make_fail(const char *p_detail)
{
test_result_t result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U };
(void) snprintf(result.detail, TEST_DETAIL_SIZE, "%s", p_detail);
return result;
}
/**
* @brief Создать результат SKIP с текстовым описанием.
*
* @param p_detail Строка описания (копируется в result.detail).
* @return Заполненный test_result_t со статусом TEST_STATUS_SKIP.
*/
static test_result_t make_skip(const char *p_detail)
{
test_result_t result = { .status = TEST_STATUS_SKIP, .duration_ms = 0U };
(void) snprintf(result.detail, TEST_DETAIL_SIZE, "%s", p_detail);
return result;
}
/**
* @brief Ждать нажатия кнопки с debounce-поллингом.
*
* Отправляет confirm_request как инструкцию оператору хост не отвечает
* JSON-confirm. Физическое нажатие детектируется через bsp_button.
* USB CDC поллится в каждой итерации; bsp_button_poll() вызывается
* каждые BTN_POLL_PERIOD_MS мс через rate-limiting по bsp_tick_get_ms().
*
* @param btn Кнопка для ожидания.
* @param p_id Идентификатор confirm_request.
* @param p_prompt Инструкция оператору.
* @param timeout_ms Таймаут ожидания, мс.
* @return true если нажатие зафиксировано до таймаута, false при таймауте.
*/
static bool wait_button_press(bsp_button_t btn, const char *p_id, const char *p_prompt,
uint32_t timeout_ms)
{
const confirm_params_t params = {
.id = p_id,
.prompt = p_prompt,
.timeout_ms = timeout_ms,
};
protocol_send_confirm_request(&params);
const uint32_t DEADLINE = bsp_tick_get_ms() + timeout_ms;
uint32_t next_poll_ms = bsp_tick_get_ms();
while (bsp_tick_get_ms() < DEADLINE)
{
bsp_usb_cdc_poll();
cli_process();
if (bsp_tick_get_ms() >= next_poll_ms)
{
bsp_button_poll();
next_poll_ms += BTN_POLL_PERIOD_MS;
/* Дренировать события обеих кнопок сразу после poll.
* Нажатие не-целевой кнопки отбрасывается здесь же
* иначе stale-событие засчитается в следующем вызове. */
bool ev1 = bsp_button_get_event_pressed(BSP_BUTTON_1);
bool ev2 = bsp_button_get_event_pressed(BSP_BUTTON_2);
if ((btn == BSP_BUTTON_1 && ev1) || (btn == BSP_BUTTON_2 && ev2))
{
return true;
}
}
}
return false;
}
/* ── Реализация тест-модуля ─────────────────────────────────────────────── */
/**
* @brief Инициализация: сброс debounce-состояния перед тестом.
*
* GPIO уже настроен в BOARD_InitPins(). bsp_button_init() только
* сбрасывает счётчики и флаги событий не трогает железо.
* Гарантирует, что удержание кнопки до старта теста не даёт
* ложного события: get_event_pressed() срабатывает только на переход.
*/
static void buttons_test_init(void)
{
bsp_button_init();
}
/**
* @brief Выполнить тест кнопок.
*
* Шаг 1: ждать нажатия Test_But_1.
* Шаг 2: ждать нажатия Test_But_2.
* Таймаут любого шага SKIP.
*
* @return test_result_t с итогом теста.
*/
static test_result_t buttons_test_run(void)
{
if (!wait_button_press(BSP_BUTTON_1, "btn1_press", "Press Test_But_1", BTN_PRESS_TIMEOUT_MS))
{
return make_skip("btn1_press timeout");
}
if (!wait_button_press(BSP_BUTTON_2, "btn2_press", "Press Test_But_2", BTN_PRESS_TIMEOUT_MS))
{
return make_skip("btn2_press timeout");
}
return (test_result_t){
.status = TEST_STATUS_PASS,
.duration_ms = 0U,
.detail = { 0 },
};
}
/* ── Дескриптор модуля ──────────────────────────────────────────────────── */
/** @brief Дескриптор тест-модуля кнопок для реестра test_runner. */
const test_module_t K_TEST_BUTTONS = {
.id = "buttons",
.name = "Test Buttons",
.critical = false,
.requires_hil = false,
.pre_confirm_prompt = NULL,
.init = buttons_test_init,
.run = buttons_test_run,
.deinit = NULL,
};

View file

@ -1,34 +1,39 @@
# port/ — porting layer # port/ — porting layer
Glue-код между сторонними библиотеками (`lib/`) и платформой (`bsp/`). Glue-код между сторонними библиотеками (`lib/`, `utils/`) и платформой (`bsp/`).
--- ---
## Зачем нужен отдельный каталог ## Концепция
В проекте три слоя кода с чёткими границами: В проекте четыре слоя с чёткими границами:
```bash ```mermaid
lib/ сторонний код — ничего не знает о проекте graph TB
bsp/ железо — драйверы периферии MIMXRT1052 FW["firmware/*\nбизнес-логика"]
utils/ платформонезависимые алгоритмы (ring_buffer, log и др.) PORT["port/\nglue: адаптирует utils/ и lib/ к платформе"]
port/ ← glue: адаптирует lib/ и utils/ к конкретной платформе BSP["bsp/\nдрайверы периферии MIMXRT1052"]
firmware/ бизнес-логика — использует всё вышеперечисленное UTILS["utils/\nплатформонезависимые алгоритмы"]
LIB["lib/ + sdk/\nсторонний код"]
FW --> PORT
FW --> BSP
FW --> UTILS
PORT --> BSP
PORT --> UTILS
PORT --> LIB
``` ```
Код попадает в `port/` если выполняются оба условия: Код попадает в `port/` если выполняются оба условия:
1. Связывает платформонезависимую библиотеку/утилиту с конкретным BSP. 1. Связывает платформонезависимую библиотеку / утилиту с конкретным BSP.
2. Сам по себе не является ни библиотекой, ни драйвером. 2. Сам по себе не является ни библиотекой, ни драйвером.
Примеры: адаптер логгера к UART, diskio-реализация FatFS поверх bsp_sdio, **Не попадает в `port/`:**
FreeRTOS heap и assert-хуки.
Код НЕ попадает в `port/` если:
- Не зависит от `bsp/` → идёт в `utils/` - Не зависит от `bsp/` → идёт в `utils/`
- Является самостоятельным драйвером периферии → идёт в `bsp/` - Самостоятельный драйвер периферии → идёт в `bsp/`
- Это сторонняя библиотека без изменений → идёт в `lib/` - Сторонняя библиотека без изменений → идёт в `lib/`
--- ---
@ -38,12 +43,35 @@ FreeRTOS heap и assert-хуки.
port/ port/
├── CMakeLists.txt ├── CMakeLists.txt
├── README.md ← этот файл ├── README.md ← этот файл
└── log/ ← UART-адаптер для utils/log ├── log/ ← UART-адаптер для utils/log
├── fatfs/ ← diskio поверх bsp_sd / bsp_qspi │ ├── CMakeLists.txt # таргет port_log_uart
# Планируется: │ ├── README.md
└── freertos/ ← heap_4.c, configASSERT, vApplicationHooks │ ├── include/port/
│ │ └── log_uart.h
│ └── src/
│ └── log_uart.c
└── fatfs/ ← diskio поверх bsp_sd (FatFS)
├── CMakeLists.txt # таргет port_fatfs_sd (INTERFACE)
└── sd/
├── CMakeLists.txt
├── include/port/fatfs/
│ └── diskio_sd.h
└── src/
└── diskio_sd.c
``` ```
**Планируется:** `port/freertos/``heap_4.c`, `configASSERT`,
`vApplicationHooks`.
---
## Таблица адаптеров
| Таргет CMake | Что адаптирует | Куда | Тип |
| --------------- | -------------- | --------------- | --------- |
| `port_log_uart` | `utils/log` | `bsp_uart_host` | STATIC |
| `port_fatfs_sd` | FatFS diskio | `bsp_sd` | INTERFACE |
--- ---
## Соглашения ## Соглашения
@ -51,8 +79,8 @@ port/
**Именование таргетов:** `port_<что>_<транспорт>` — например `port_log_uart`, **Именование таргетов:** `port_<что>_<транспорт>` — например `port_log_uart`,
`port_fatfs_sd`. Позволяет иметь несколько адаптеров для одной библиотеки. `port_fatfs_sd`. Позволяет иметь несколько адаптеров для одной библиотеки.
**Include-путь:** `#include "port/<модуль>.h"`публичные заголовки **Include-путь:** публичные заголовки в `port/<модуль>/include/port/`,
всегда в `port/<модуль>/include/port/`. подключение через `#include "port/<модуль>.h"`.
**Не компилируется для host-тестов:** `bsp/` недоступен на хосте, поэтому **Host-сборка:** `port/CMakeLists.txt` содержит ранний `return()` при
`port/CMakeLists.txt` возвращает управление при `BUILD_TESTS_HOST=ON`. `BUILD_TESTS_HOST=ON` — `bsp/` недоступен на хосте.

89
port/fatfs/README.md Normal file
View file

@ -0,0 +1,89 @@
# port/fatfs — FatFS diskio поверх bsp_sd
Адаптирует FatFS diskio-интерфейс к `bsp_sd` через NXP `fsl_sd_disk`.
Предоставляет тонкую обёртку `microsd_disk_*``sd_disk_*` и изолирует
номер диска от вызывающего кода.
---
## Архитектура
```mermaid
graph TB
FW["firmware_test / tft_app\nf_mount / f_read / f_write"]
DISKIO["diskio.c\nв каждом бинарнике\n(диспетчер по pdrv)"]
PORT["port/fatfs/sd\nmicrosd_disk_*"]
FSL["fsl_sd_disk\nsd_disk_* (NXP SDK)"]
BSP["bsp_sd\nSD_HostInit / SD_Read / SD_Write"]
HW["USDHC1 / SD карта"]
FW --> DISKIO --> PORT --> FSL --> BSP --> HW
```
**Почему `diskio_sd.c` не компилируется самим `port_fatfs_sd`:**
`diskio_sd.c` включает `diskio.h``ff.h``ffconf.h`. Этот файл
конфигурации разный у `firmware_test` (bare-metal, `FF_FS_REENTRANT=0`)
и `tft_app` (FreeRTOS, `FF_FS_REENTRANT=1`). Поэтому `port_fatfs_sd`
INTERFACE-библиотека: предоставляет include path и зависимости, а каждый
бинарник компилирует `diskio_sd.c` в свой FatFS-таргет самостоятельно
через переменную `PORT_FATFS_SD_SRC`.
---
## Использование в бинарнике
```cmake
# firmware/test/CMakeLists.txt
add_library(firmware_test_fatfs STATIC
fatfs/ff.c
fatfs/diskio.c
${PORT_FATFS_SD_SRC} # ← diskio_sd.c с правильным ffconf.h
)
target_link_libraries(firmware_test_fatfs
PRIVATE port_fatfs_sd # include path + bsp_sd + sdk_fatfs_headers
)
```
```c
#include "port/fatfs/diskio_sd.h"
/* diskio.c вызывает microsd_disk_* через диспетчер по pdrv: */
DSTATUS disk_initialize(BYTE pdrv)
{
if (pdrv == SDDISK) return microsd_disk_initialize(pdrv);
return STA_NOINIT;
}
```
---
## API (`microsd_disk_*`)
```c
DSTATUS microsd_disk_initialize(BYTE pdrv);
DSTATUS microsd_disk_status(BYTE pdrv);
DRESULT microsd_disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count);
DRESULT microsd_disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count);
DRESULT microsd_disk_ioctl(BYTE pdrv, BYTE cmd, void *buff);
```
Функции — тонкие обёртки над `sd_disk_*` из NXP SDK. Не вызывать из
прикладного кода напрямую — только через диспетчер `diskio.c`.
---
## CMake
```cmake
# Получить путь к diskio_sd.c
target_link_libraries(<fatfs_target> PRIVATE port_fatfs_sd)
# PORT_FATFS_SD_SRC автоматически доступна после add_subdirectory(port)
```
**Зависимости модуля:**
| Зависимость | Тип | Описание |
| ------------------- | --------- | ------------------------------------------ |
| `sdk_fatfs_headers` | INTERFACE | `ff.h`, `fsl_sd_disk.h`, `SDK_FATFS_*_SRC` |
| `bsp_sd` | INTERFACE | `g_sd`, `bsp_sd_is_inserted()` |

View file

@ -6,7 +6,40 @@
--- ---
## Использование ## Архитектура
```mermaid
flowchart LR
APP["LOG_I / LOG_D / …\nлюбой .c файл"]
CORE["utils/log\nlog_write()"]
ADAPTER["port_log_uart\nuart_write callback\nlog_get_timestamp_ms()"]
BSP_U["bsp_uart_host\nbsp_uart_host_write()"]
BSP_T["bsp_tick\nbsp_tick_get_ms()"]
APP --> CORE --> ADAPTER
ADAPTER --> BSP_U
ADAPTER --> BSP_T
```
`log_uart_init()` делает две вещи:
1. Регистрирует `bsp_uart_host_write()` как write callback через `log_init()`.
2. Предоставляет strong-реализацию weak-хука `log_get_timestamp_ms()`
`bsp_tick_get_ms()`.
---
## API
```c
void log_uart_init(void);
```
Предусловия: `bsp_uart_host_init()` и `bsp_tick_init()` уже вызваны.
---
## Быстрый старт
```c ```c
#include "port/log_uart.h" #include "port/log_uart.h"
@ -18,27 +51,38 @@ int main(void)
bsp_tick_init(); bsp_tick_init();
bsp_uart_host_init(115200U); bsp_uart_host_init(115200U);
log_uart_init(); // регистрирует транспорт, после этого LOG_* работают log_uart_init(); /* регистрирует транспорт и timestamp */
LOG_I("BOOT", "Ready"); LOG_I("BOOT", "Ready");
} }
``` ```
Для **tft_app (FreeRTOS)** — добавить мьютекс до `log_uart_init()`:
```c
log_mutex_init(); // создать FreeRTOS-семафор
log_uart_init(); // зарегистрировать транспорт
```
Реализация мьютекса: `firmware/tft_app/src/log_mutex.c`.
--- ---
## Что делает `log_uart_init()` ## Интеграция с FreeRTOS (`tft_app`)
1. Регистрирует `bsp_uart_host_write()` как write callback через `log_init()`. Для потокобезопасности создать мьютекс **до** `log_uart_init()`:
2. Предоставляет strong-реализацию `log_get_timestamp_ms()``bsp_tick_get_ms()`.
```c
log_mutex_init(); /* создать FreeRTOS-семафор */
log_uart_init(); /* зарегистрировать транспорт */
```
Реализация мьютекса — в `firmware/tft_app/src/log_mutex.c`:
```c
#include "FreeRTOS.h"
#include "log/log.h"
#include "semphr.h"
static SemaphoreHandle_t s_log_mutex;
void log_mutex_init(void) { s_log_mutex = xSemaphoreCreateMutex(); }
void log_mutex_lock(void) { xSemaphoreTake(s_log_mutex, portMAX_DELAY); }
void log_mutex_unlock(void) { xSemaphoreGive(s_log_mutex); }
```
> `LOG_*` нельзя вызывать из ISR — `bsp_uart_host_write()` блокирующий.
--- ---
@ -48,4 +92,13 @@ log_uart_init(); // зарегистрировать транспорт
target_link_libraries(<target> PRIVATE port_log_uart) target_link_libraries(<target> PRIVATE port_log_uart)
``` ```
Транзитивно подтягивает `utils` (содержит `log.h`) и `LOG_LEVEL`. Транзитивно подтягивает `utils` (содержит `log.h` и `LOG_LEVEL`),
`bsp_uart_host` и `bsp_tick`.
**Зависимости модуля:**
| Зависимость | Тип | Описание |
| --------------- | ------ | -------------------------------- |
| `utils` | PUBLIC | `log.h`, `LOG_LEVEL` транзитивно |
| `bsp_uart_host` | PUBLIC | write callback |
| `bsp_tick` | PUBLIC | `log_get_timestamp_ms()` |

View file

@ -1,300 +0,0 @@
.
├── bootstrap.sh
├── bsp
│   ├── button
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   ├── README.md
│   │   └── src
│   ├── can
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   ├── mocks
│   │   ├── README.md
│   │   └── src
│   ├── CMakeLists.txt
│   ├── common
│   │   ├── CMakeLists.txt
│   │   └── include
│   ├── display
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   ├── README.md
│   │   └── src
│   ├── generated
│   │   ├── board
│   │   ├── board.c
│   │   ├── board.h
│   │   ├── clock_config.c
│   │   ├── clock_config.h
│   │   ├── pin_mux.c
│   │   ├── pin_mux.h
│   │   ├── README.md
│   │   ├── startup
│   │   ├── syscalls.c
│   │   └── TFT_Board.mex
│   ├── led
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   ├── README.md
│   │   └── src
│   ├── opto
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   ├── README.md
│   │   └── src
│   ├── qspi_flash
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   ├── README.md
│   │   ├── REFACTORING.md
│   │   └── src
│   ├── README.md
│   ├── sdram
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   ├── README.md
│   │   └── src
│   ├── tick
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   ├── README.md
│   │   └── src
│   ├── uart_host
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   ├── mocks
│   │   ├── README.md
│   │   └── src
│   └── usb_cdc
│   ├── CMakeLists.txt
│   ├── include
│   ├── README.md
│   └── src
├── cmake
│   ├── linker
│   │   ├── MIMXRT1052xxxxx_flexspi_nor_sdram.ld
│   │   ├── MIMXRT1052xxxxx_flexspi_nor.ld
│   │   ├── MIMXRT1052xxxxx_ram.ld
│   │   ├── MIMXRT1052xxxxx_sdram_txt.ld
│   │   └── MIMXRT1052xxxxx_sdram.ld
│   ├── toolchain_arm.cmake
│   └── toolchain_host.cmake
├── CMakeLists.txt
├── CMakePresets.json
├── docs
│   ├── DEV_ARCH.md
│   ├── hardware
│   │   ├── board
│   │   ├── m5stamPLC
│   │   ├── MCULINKINSERT.pdf
│   │   └── tft_displays
│   ├── HOW_TO_DEBUG.md
│   ├── HOW_TO_FLASH.md
│   ├── mimxrt1052
│   │   ├── BOOT_FLAGS.md
│   │   ├── HAB_GUIDE.md
│   │   ├── manufacturing_user's_guide.pdf
│   │   └── mcu_rm.pdf
│   └── testing
│   ├── hil
│   ├── host
│   └── PROTOCOL.md
├── firmware
│   ├── bootloader
│   ├── test
│   │   ├── CMakeLists.txt
│   │   ├── PLAN.md
│   │   ├── README.md
│   │   └── src
│   └── tft_app
├── just
│   ├── build.just
│   ├── ci_workflow.md
│   ├── ci.just
│   └── host.just
├── Justfile
├── lib
│   ├── CMakeLists.txt
│   ├── fff
│   │   ├── buildandtest
│   │   ├── CMakeLists.txt
│   │   ├── examples
│   │   ├── fakegen.rb
│   │   ├── fff.h
│   │   ├── LICENSE
│   │   ├── README.md
│   │   └── test
│   ├── SEGGER
│   │   ├── RTT
│   │   └── wrapper
│   └── Unity
│   ├── auto
│   ├── CMakeLists.txt
│   ├── docs
│   ├── examples
│   ├── extras
│   ├── library.json
│   ├── LICENSE.txt
│   ├── meson_options.txt
│   ├── meson.build
│   ├── platformio-build.py
│   ├── README.md
│   ├── src
│   ├── test
│   └── unityConfig.cmake
├── port
│   ├── CMakeLists.txt
│   ├── log
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   ├── README.md
│   │   └── src
│   └── README.md
├── project_tree.txt
├── pyocd_debug.yaml
├── pyocd.yaml
├── README.md
├── sdk
│   ├── boards
│   │   └── evkbimxrt1050
│   ├── CMakeLists.txt
│   ├── CMSIS
│   │   ├── Core
│   │   ├── Driver
│   │   ├── DSP
│   │   ├── LICENSE.txt
│   │   ├── NN
│   │   └── RTOS2
│   ├── components
│   │   ├── audio
│   │   ├── button
│   │   ├── codec
│   │   ├── common_task
│   │   ├── crc
│   │   ├── display
│   │   ├── exception_handling
│   │   ├── flash
│   │   ├── gpio
│   │   ├── i2c
│   │   ├── internal_flash
│   │   ├── led
│   │   ├── lists
│   │   ├── log
│   │   ├── mem_manager
│   │   ├── messaging
│   │   ├── mx25r_flash
│   │   ├── osa
│   │   ├── panic
│   │   ├── phy
│   │   ├── pmic
│   │   ├── reset
│   │   ├── rng
│   │   ├── rtt
│   │   ├── sensor
│   │   ├── serial_manager
│   │   ├── silicon_id
│   │   ├── spi
│   │   ├── timer
│   │   ├── timer_manager
│   │   ├── touch
│   │   ├── uart
│   │   ├── video
│   │   └── wifi_bt_module
│   ├── COPYING-BSD-3
│   ├── devices
│   │   └── MIMXRT1052
│   ├── docs
│   │   └── readme.md
│   ├── LA_OPT_NXP_Software_License.txt
│   ├── middleware
│   │   ├── audio_voice
│   │   ├── cjson
│   │   ├── dhara
│   │   ├── fatfs
│   │   ├── freemaster
│   │   ├── libjpeg
│   │   ├── littlefs
│   │   ├── mcuboot_opensource
│   │   ├── pkcs11
│   │   ├── pngdec
│   │   ├── sdmmc
│   │   ├── tfm
│   │   ├── tinycbor
│   │   └── usb
│   ├── MIMXRT1052xxxxB_manifest_v3_15.xml
│   ├── README.md
│   ├── rtos
│   │   └── freertos
│   ├── sdk_tree.txt
│   ├── SW-Content-Register.txt
│   └── tools
│   └── cmake_toolchain_files
├── tests
│   ├── CMakeLists.txt
│   ├── host
│   │   ├── button
│   │   ├── can
│   │   ├── cli
│   │   ├── CMakeLists.txt
│   │   ├── led
│   │   ├── log
│   │   ├── mocks
│   │   ├── opto
│   │   ├── prio_queue
│   │   ├── protocol
│   │   ├── README.md
│   │   ├── ring_buffer
│   │   ├── runner
│   │   ├── timeout
│   │   └── uart_host
│   └── target
│   ├── CMakeLists.txt
│   ├── hil_button
│   ├── hil_can
│   ├── hil_opto
│   ├── hil_usb_cdc
│   └── host_uart
├── tools
│   ├── hil
│   │   ├── __pycache__
│   │   ├── 01_test_uart.py
│   │   ├── 02_test_opto.py
│   │   ├── 03_test_can.py
│   │   ├── 04_test_button.py
│   │   ├── 05_test_usb_cdc.py
│   │   ├── conftest.py
│   │   ├── env_config.py
│   │   ├── load_and_run.py
│   │   ├── m5
│   │   ├── pyocd_utils.py
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── host
│   │   ├── dcd
│   │   ├── flash_swd.py
│   │   ├── flash_usb.py
│   │   ├── hab
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   └── uv.lock
│   ├── production
│   └── shared
└── utils
├── CMakeLists.txt
├── log
│   ├── log.c
│   ├── log.h
│   └── README.md
├── prio_queue
│   ├── prio_queue.c
│   ├── prio_queue.h
│   └── README.md
├── README.md
└── ring_buffer
├── README.md
├── ring_buffer.c
└── ring_buffer.h
174 directories, 124 files

View file

@ -1,8 +1,7 @@
# utils # utils — платформонезависимые утилиты
Платформонезависимые утилиты проекта. Общие структуры данных и сервисы проекта. Код попадает сюда только если
выполняются оба условия:
**Правило включения** — код попадает сюда только если выполняются оба условия:
- не зависит от железа (нет `fsl_*`, CMSIS, FreeRTOS, BSP); - не зависит от железа (нет `fsl_*`, CMSIS, FreeRTOS, BSP);
- используется более чем в одном месте проекта. - используется более чем в одном месте проекта.
@ -14,6 +13,35 @@
## Модули ## Модули
| Модуль | Путь | Описание | | Модуль | Путь | Описание |
| ------------- | ------------------------------------- | -------------------------------------------------- | | ------------- | ---------------------------------------------- | ------------------------------------ |
| `ring_buffer` | [ring_buffer/](ring_buffer/README.md) | SPSC кольцевой буфер байт, lock-free | | `ring_buffer` | [ring_buffer/README.md](ring_buffer/README.md) | SPSC кольцевой буфер байт, lock-free |
| `log` | [log/](log/README.md) | Платформонезависимый логгер с callback-транспортом | | `prio_queue` | [prio_queue/README.md](prio_queue/README.md) | Приоритетная очередь с вытеснением |
| `log` | [log/README.md](log/README.md) | Логгер с callback-транспортом |
---
## CMake
Все три модуля собираются в одну статическую библиотеку `utils`:
```cmake
target_link_libraries(<target> PRIVATE utils)
```
Корень `utils/` автоматически добавляется в include path — `#include`
указывает полный путь от корня:
```c
#include "ring_buffer/ring_buffer.h"
#include "prio_queue/prio_queue.h"
#include "log/log.h"
```
`LOG_LEVEL` пробрасывается из CMake-пресета или командной строки:
```cmake
target_compile_definitions(utils PUBLIC LOG_LEVEL=4)
```
Если не задан — `log.h` выбирает уровень сам через `NDEBUG`
(Verbose в Debug, Off в Release).

View file

@ -1,46 +1,71 @@
# utils/log # log — платформонезависимый логгер
Платформонезависимый логгер с callback-транспортом. Ядро логгера (`log.c` / `log.h`) не знает о конкретном транспорте. Транспорт
подключается через `log_init()` в виде callback-функции. Адаптеры живут в
Ядро логгера (`log.c` / `log.h`) не знает о конкретном транспорте — UART, `port/log/`.
USB CDC, Flash и т.д. Транспорт подключается через `log_init()` в виде
callback-функции. Адаптеры живут в `port/log/`.
| Параметр | Значение | | Параметр | Значение |
| ------------------ | ------------------------------------------------- | | ------------------ | ------------------------------------------------- |
| Формат | `[ timestamp][L][TAG] сообщение\r\n` | | Формат строки | `[ timestamp][L][TAG] сообщение\r\n` |
| Буфер строки | 256 байт (переопределяется через `LOG_BUF_SIZE`) | | Буфер строки | 256 байт (переопределяется через `LOG_BUF_SIZE`) |
| Управление уровнем | `LOG_LEVEL` через CMake `-DLOG_LEVEL=N` | | Управление уровнем | `LOG_LEVEL` через CMake `-DLOG_LEVEL=N` |
| Thread-safety | мьютекс через weak-хуки (`log_mutex_lock/unlock`) | | Thread-safety | мьютекс через weak-хуки (`log_mutex_lock/unlock`) |
| Зависимости | `<stdarg.h>`, `<stdio.h>`, `<stddef.h>` | | Зависимости | `<stdarg.h>`, `<stdio.h>`, `<stddef.h>` |
---
## Уровни ## Уровни
| N | Макрос | Имя | | N | Макрос | Уровень | Дефолт |
| --- | ------- | -------------------------------------------- | | --- | ------- | ------- | ------------------ |
| 0 | — | off — все `LOG_*``((void)0)`, нулевой ROM | | 0 | — | off | Release (`NDEBUG`) |
| 1 | `LOG_E` | error | | 1 | `LOG_E` | error | |
| 2 | `LOG_W` | warn | | 2 | `LOG_W` | warn | |
| 3 | `LOG_I` | info | | 3 | `LOG_I` | info | |
| 4 | `LOG_D` | debug | | 4 | `LOG_D` | debug | |
| 5 | `LOG_V` | verbose | | 5 | `LOG_V` | verbose | Debug |
По умолчанию: `VERBOSE` в Debug-сборке, `OFF` в Release (`NDEBUG`). При `LOG_LEVEL=0` макросы разворачиваются в `((void)0)` — нулевой ROM,
`log_write()` не вызывается вообще.
---
## API
```c
void log_init(log_write_cb_t p_write_cb, void *p_ctx);
void log_write(int level, const char *p_tag, const char *p_fmt, ...);
/* Макросы — используй их, не log_write напрямую: */
LOG_E(tag, fmt, ...)
LOG_W(tag, fmt, ...)
LOG_I(tag, fmt, ...)
LOG_D(tag, fmt, ...)
LOG_V(tag, fmt, ...)
```
**Транспортный callback:**
```c
typedef void (*log_write_cb_t)(const char *p_buf, size_t len, void *p_ctx);
```
---
## Быстрый старт ## Быстрый старт
```c ```c
// main.c — зарегистрировать транспорт /* main.c */
#include "log/log.h" #include "log/log.h"
#include "port/log_uart.h" #include "port/log_uart.h"
log_uart_init(); // инициализировать адаптер транспорта log_uart_init();
log_init(uart_log_write, NULL); log_init(uart_log_write, NULL);
// Любой .c файл /* Любой .c файл: */
#include "log/log.h" #include "log/log.h"
LOG_I("BOOT", "Started, tick=%lu", (unsigned long) bsp_tick_get_ms()); LOG_I("BOOT", "Started, tick=%lu", (unsigned long)bsp_tick_get_ms());
LOG_W("SDIO", "Card not detected"); LOG_W("SDIO", "Card not detected");
LOG_D("UART", "RX=%u bytes", bsp_uart_host_rx_available()); LOG_D("UART", "RX=%u bytes", bsp_uart_host_rx_available());
LOG_E("CAN", "Bus-off, err=%d", err); LOG_E("CAN", "Bus-off, err=%d", err);
@ -53,13 +78,9 @@ LOG_E("CAN", "Bus-off, err=%d", err);
[ 1235][W][SDIO] Card not detected [ 1235][W][SDIO] Card not detected
``` ```
## Транспортный адаптер ---
Адаптер — функция типа `log_write_cb_t`: ## Транспортные адаптеры
```c
typedef void (*log_write_cb_t)(const char *p_buf, size_t len, void *p_ctx);
```
Готовые адаптеры в `port/log/`: Готовые адаптеры в `port/log/`:
@ -67,14 +88,30 @@ typedef void (*log_write_cb_t)(const char *p_buf, size_t len, void *p_ctx);
| ---------- | ---------------------------------------- | | ---------- | ---------------------------------------- |
| `log_uart` | `bsp_uart_host` (LPUART1, MCU-Link VCOM) | | `log_uart` | `bsp_uart_host` (LPUART1, MCU-Link VCOM) |
## Мьютекс и временна́я метка (FreeRTOS) Написать свой — реализовать функцию типа `log_write_cb_t` и передать в
`log_init()`.
Для bare-metal ничего делать не нужно — weak-хуки по умолчанию NOP, временна́я метка возвращает 0. ---
## Тестирование
Host unit-тесты: `tests/host/log/` (Unity + fff).
```bash
just build::test-host
```
---
## Интеграция с FreeRTOS
Для bare-metal ничего делать не нужно — weak-хуки по умолчанию NOP,
временна́я метка возвращает 0.
Для FreeRTOS переопределить в одном `.c` файле прошивки: Для FreeRTOS переопределить в одном `.c` файле прошивки:
```c ```c
// firmware/tft_app/src/log_os.c /* firmware/tft_app/src/log_os.c */
#include "log/log.h" #include "log/log.h"
#include "FreeRTOS.h" #include "FreeRTOS.h"
#include "semphr.h" #include "semphr.h"
@ -89,8 +126,18 @@ void log_mutex_unlock(void) { xSemaphoreGive(s_mutex); }
uint32_t log_get_timestamp_ms(void) { return bsp_tick_get_ms(); } uint32_t log_get_timestamp_ms(void) { return bsp_tick_get_ms(); }
``` ```
> ⚠️ `LOG_*` нельзя вызывать из ISR если callback транспорта блокирующий. > `LOG_*` нельзя вызывать из ISR если callback транспорта блокирующий.
## Тесты ---
`tests/host/log/` — host unit-тесты (Unity + fff). ## CMake
```cmake
target_link_libraries(<target> PRIVATE utils)
```
`LOG_LEVEL` задаётся в пресете или явно:
```cmake
target_compile_definitions(utils PUBLIC LOG_LEVEL=4)
```

View file

@ -1,22 +1,45 @@
# util/prio_queue # prio_queue — приоритетная очередь с вытеснением
Отсортированный массив с фиксированной ёмкостью — generic приоритетная очередь Отсортированный массив с фиксированной ёмкостью и generic элементами.
с вытеснением. При полном буфере новый элемент с более высоким приоритетом вытесняет
наименее приоритетный. Оптимален для небольших очередей (n ≤ 32):
**Типичное использование:** очередь задач, событий или медиадорожек с приоритетами, задачи, события, медиадорожки с приоритетами.
где число элементов заранее известно и невелико (≤ ~32). При полном буфере новый
элемент с более высоким приоритетом вытесняет наименее приоритетный.
| Параметр | Значение | | Параметр | Значение |
| ------------- | ----------------------------------------------------------------------- | | ------------- | ----------------------------------------------------------------------- |
| Элемент | любой тип, задаётся через `item_size` | | Элемент | любой тип, задаётся через `item_size` |
| Ёмкость | любая, задаётся при `prio_queue_init`; фиксирована на всё время жизни | | Ёмкость | фиксирована на всё время жизни, задаётся при `prio_queue_init` |
| Порядок | определяется `cmp`-функцией пользователя (аналог `qsort`) | | Порядок | определяется `cmp`-функцией пользователя (сигнатура как у `qsort`) |
| Вставка | O(n) сдвиг; оптимально при n ≤ 32 | | Вставка | O(n) сдвиг |
| Peek-top | O(1) | | Peek-top | O(1) |
| Thread-safety | нет; при использовании из нескольких контекстов — внешняя синхронизация | | Thread-safety | нет; при использовании из нескольких контекстов — внешняя синхронизация |
| Зависимости | `<stdint.h>`, `<stddef.h>`, `<string.h>` | | Зависимости | `<stdint.h>`, `<stddef.h>`, `<string.h>` |
---
## API
```c
void prio_queue_init(prio_queue_t *q, void *buf, uint8_t capacity,
size_t item_size, pq_cmp_fn cmp);
pq_status_t prio_queue_insert(prio_queue_t *q, const void *item);
const void *prio_queue_peek(const prio_queue_t *q);
uint8_t prio_queue_size(const prio_queue_t *q);
void *prio_queue_at(prio_queue_t *q, uint8_t idx);
void prio_queue_remove_at(prio_queue_t *q, uint8_t idx);
```
**Коды возврата `prio_queue_insert`:**
| Код | Условие |
| ------------ | ----------------------------------------------------- |
| `PQ_OK` | Вставлен, место было |
| `PQ_EVICTED` | Вставлен, наименее приоритетный вытеснен |
| `PQ_FULL` | Отклонён — новый элемент наименее приоритетен из всех |
---
## Быстрый старт ## Быстрый старт
```c ```c
@ -25,7 +48,8 @@
typedef struct { int priority; const char *name; } task_t; typedef struct { int priority; const char *name; } task_t;
/* Comparator: меньший priority → ближе к голове */ /* Comparator: меньший priority → ближе к голове */
static int task_cmp(const void *a, const void *b) { static int task_cmp(const void *a, const void *b)
{
int pa = ((const task_t *)a)->priority; int pa = ((const task_t *)a)->priority;
int pb = ((const task_t *)b)->priority; int pb = ((const task_t *)b)->priority;
return (pa < pb) ? -1 : (pa > pb) ? 1 : 0; return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
@ -34,27 +58,20 @@ static int task_cmp(const void *a, const void *b) {
static task_t storage[8]; static task_t storage[8];
static prio_queue_t q; static prio_queue_t q;
/* Инициализация */
prio_queue_init(&q, storage, 8, sizeof(task_t), task_cmp); prio_queue_init(&q, storage, 8, sizeof(task_t), task_cmp);
/* Вставка */
task_t t = { .priority = 2, .name = "send_data" }; task_t t = { .priority = 2, .name = "send_data" };
pq_status_t st = prio_queue_insert(&q, &t); pq_status_t st = prio_queue_insert(&q, &t);
/* st == PQ_OK — вставлен
st == PQ_EVICTED — вставлен, наименее приоритетный вытеснен
st == PQ_FULL — отклонён, новый элемент наименее приоритетен из всех */
/* Чтение верхнего элемента без удаления */
const task_t *top = prio_queue_peek(&q); const task_t *top = prio_queue_peek(&q);
if (top != NULL) { /* обработать top */ } if (top != NULL) { /* обработать */ }
/* Удаление верхнего элемента после обработки */ prio_queue_remove_at(&q, 0); /* удалить верхний после обработки */
prio_queue_remove_at(&q, 0);
``` ```
## Политика вытеснения при полном буфере **Политика вытеснения при полном буфере:**
```bash ```
Очередь полна [A(1) B(2) C(3)], вставляем D(2): Очередь полна [A(1) B(2) C(3)], вставляем D(2):
→ D приоритетнее C(3) → C вытесняется → [A(1) B(2) D(2)] PQ_EVICTED → D приоритетнее C(3) → C вытесняется → [A(1) B(2) D(2)] PQ_EVICTED
@ -62,6 +79,8 @@ prio_queue_remove_at(&q, 0);
→ E менее приоритетен чем все → отклоняется PQ_FULL → E менее приоритетен чем все → отклоняется PQ_FULL
``` ```
---
## Comparator ## Comparator
Сигнатура идентична `qsort`: Сигнатура идентична `qsort`:
@ -76,17 +95,12 @@ typedef int (*pq_cmp_fn)(const void *a, const void *b);
| `> 0` | `b` стоит перед `a` | | `> 0` | `b` стоит перед `a` |
| `0` | равнозначны; порядок вставки сохраняется | | `0` | равнозначны; порядок вставки сохраняется |
## API ---
```c ## Тестирование
void prio_queue_init(prio_queue_t *q, void *buf, uint8_t capacity,
size_t item_size, pq_cmp_fn cmp);
pq_status_t prio_queue_insert(prio_queue_t *q, const void *item); Host unit-тесты: `tests/host/prio_queue/`.
const void *prio_queue_peek(const prio_queue_t *q);
uint8_t prio_queue_size(const prio_queue_t *q); ```bash
void *prio_queue_at(prio_queue_t *q, uint8_t idx); just build::test-host
void prio_queue_remove_at(prio_queue_t *q, uint8_t idx);
``` ```
Тесты: `tests/host/test_prio_queue.c`

View file

@ -1,10 +1,8 @@
# util/ring_buffer # ring_buffer — SPSC кольцевой буфер байт
Кольцевой буфер байт — SPSC (single-producer / single-consumer), lock-free. Lock-free кольцевой буфер для сценария единственный producer / единственный
consumer. Типичное использование: ISR пишет принятые байты, задача или main
**Типичное использование:** ISR пишет принятые байты, задача или основной цикл loop читает — без отключения прерываний.
читает. Не требует отключения прерываний при условии единственного producer и
единственного consumer.
| Параметр | Значение | | Параметр | Значение |
| ------------- | ------------------------------------------------------------------------------ | | ------------- | ------------------------------------------------------------------------------ |
@ -13,23 +11,76 @@
| Thread-safety | SPSC без блокировок; multi-producer/consumer — только с внешней синхронизацией | | Thread-safety | SPSC без блокировок; multi-producer/consumer — только с внешней синхронизацией |
| Зависимости | `<stdint.h>`, `<stddef.h>`, `<stdbool.h>` | | Зависимости | `<stdint.h>`, `<stddef.h>`, `<stdbool.h>` |
---
## API
```c
/* Инициализация */
bool ring_buffer_init(ring_buffer_desc_t *p_desc, uint8_t *p_buf, size_t size);
void ring_buffer_reset(ring_buffer_desc_t *p_desc);
/* Состояние */
bool ring_buffer_is_empty(const ring_buffer_desc_t *p_desc);
bool ring_buffer_is_full(const ring_buffer_desc_t *p_desc);
size_t ring_buffer_count(const ring_buffer_desc_t *p_desc);
size_t ring_buffer_free(const ring_buffer_desc_t *p_desc);
/* Запись (producer) */
bool ring_buffer_put(ring_buffer_desc_t *p_desc, uint8_t byte);
size_t ring_buffer_write(ring_buffer_desc_t *p_desc, const uint8_t *p_data, size_t len);
/* Чтение (consumer) */
bool ring_buffer_get(ring_buffer_desc_t *p_desc, uint8_t *p_byte);
size_t ring_buffer_read(ring_buffer_desc_t *p_desc, uint8_t *p_data, size_t len);
```
`ring_buffer_init` требует `size` — степень двойки; возвращает `false` при
невалидных аргументах. `ring_buffer_write` / `ring_buffer_read` возвращают
фактически переданное количество байт.
---
## Быстрый старт ## Быстрый старт
```c ```c
#include "ring_buffer/ring_buffer.h" #include "ring_buffer/ring_buffer.h"
static uint8_t storage[256]; static uint8_t storage[256]; /* степень двойки */
static ring_buffer_t rb; static ring_buffer_desc_t rb;
// Инициализация (размер — степень двойки)
ring_buffer_init(&rb, storage, sizeof(storage)); ring_buffer_init(&rb, storage, sizeof(storage));
// Запись (например, из ISR) /* Producer (например, из ISR): */
ring_buffer_put(&rb, byte); ring_buffer_put(&rb, received_byte);
// Чтение (например, из задачи) /* Consumer (например, из main loop): */
uint8_t b; uint8_t b;
if (ring_buffer_get(&rb, &b)) { /* обработать b */ } if (ring_buffer_get(&rb, &b)) {
/* обработать b */
}
``` ```
Тесты: `tests/host/test_ring_buffer.c` (24 теста, включая wraparound и SPSC-симуляцию). ---
## Тестирование
Host unit-тесты: `tests/host/ring_buffer/` — 24 теста, покрывают wraparound,
граничные значения и SPSC-симуляцию.
```bash
just build::test-host
```
---
## Примечания по реализации
**Wraparound.** Индексы `head` и `tail` — монотонно возрастающие `size_t`.
Маскирование через `& mask` (где `mask = size - 1`) даёт корректный индекс
ячейки. Беззнаковый wraparound арифметически корректен: `(0 - 1) == SIZE_MAX`,
подсчёт заполненности через `tail - head` работает без явной обёртки.
**Memory ordering.** На Cortex-M7 (strongly-ordered) барьер памяти не нужен.
На weakly-ordered архитектурах (ARM64, RISC-V) потребуется store-release /
load-acquire — добавить `__atomic_store_n` / `__atomic_load_n`.