# test_buttons: finished, all documentation refactored
This commit is contained in:
parent
8d85ffe877
commit
293b61fff3
34 changed files with 2917 additions and 3945 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -75,3 +75,4 @@ __pycache__/
|
|||
tools/host/.venv-host/
|
||||
tools/host/.venv-host-win/
|
||||
.zed/
|
||||
project_tree.txt
|
||||
|
|
|
|||
129
CHANGELOG.md
129
CHANGELOG.md
|
|
@ -1,6 +1,6 @@
|
|||
# Журнал изменений: `tft_manufacture_test`
|
||||
|
||||
Репозиторий: https://github.com/OSabuser/tft_manufacture_test.git
|
||||
Репозиторий: <https://github.com/OSabuser/tft_manufacture_test.git>
|
||||
Отслеживаемая ветка: `dev`
|
||||
Базовый диапазон: 2026-03-05 .. 2026-05-08, 52 коммита
|
||||
Базовый HEAD: `22f98c311a4564d8f18ed72d11635bf908046b1e`
|
||||
|
|
@ -50,27 +50,67 @@
|
|||
|
||||
## [Не выпущено]
|
||||
|
||||
Диапазон: после `22f98c311a4564d8f18ed72d11635bf908046b1e`
|
||||
|
||||
### Кратко
|
||||
- После базового среза новые изменения ещё не обрабатывались.
|
||||
|
||||
### Что отслеживать
|
||||
|
||||
- Проверять, остаётся ли `.github/workflows/ci.yml` только сборочным workflow через `just ci::build` или начинает запускать host-тесты.
|
||||
- Отслеживать появление self-hosted runner, регулярных аппаратных прогонов или отчётов по HIL.
|
||||
- Проверять новые firmware-test модули в `firmware/test/src/tests/` и синхронные обновления протокола/документации.
|
||||
- Следить за развитием BSP: MQS, RGB, bootloader или `tft_app`.
|
||||
- Отслеживать локальные патчи поверх 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
|
||||
|
||||
Диапазон: вся история репозитория от initial commit до `22f98c311a4564d8f18ed72d11635bf908046b1e`
|
||||
|
||||
### Кратко
|
||||
|
||||
- Репозиторий представляет собой firmware-монорепозиторий для платы на базе NXP MIMXRT1052CVJ5B. Он охватывает manufacturing-test firmware, планируемые bootloader/application firmware, BSP-модули, host tooling, HIL tooling и документацию.
|
||||
- К базовому срезу в проекте уже есть полноценный BSP-слой, двухуровневая стратегия тестирования, USB-CDC архитектура тестовой прошивки, GitHub Actions CI и обширная инженерная документация.
|
||||
|
||||
### Текущая архитектура
|
||||
|
||||
- BSP-модули на момент базового среза: `led`, `tick`, `uart_host`, `opto`, `can`, `button`, `usb_cdc`, `sdram`, `qspi_flash`, `sd`, `display`.
|
||||
- Host-тесты используют Unity/fff-подобные моки и инструменты, рассчитанные на devcontainer.
|
||||
- HIL-тесты используют pyOCD, pyserial, pytest и поддержку M5StampPLC.
|
||||
|
|
@ -78,98 +118,121 @@
|
|||
- Документация покрывает архитектуру разработки, прошивку, отладку, HAB, HIL, host-тесты и поведение протокола.
|
||||
|
||||
### Состояние CI
|
||||
|
||||
- GitHub Actions уже есть и собирает проект внутри devcontainer.
|
||||
- Видимый пробел: host unit tests и HIL tests пока не входят в GitHub Actions workflow.
|
||||
|
||||
## [2026-05-08] — Display test module и выравнивание документации
|
||||
|
||||
### Кратко
|
||||
|
||||
- Базовый срез заканчивается тем, что поддержка display становится частью и BSP, и manufacturing-test firmware.
|
||||
- README и инженерные документы были синхронизированы между несколькими модулями, поэтому это documentation-heavy, но смысловое изменение состояния проекта.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Реализован `bsp/display` с API, исходниками и интеграцией в сборку.
|
||||
- Добавлен `firmware/test/src/tests/test_display.c` как firmware-side test module для display.
|
||||
|
||||
### Изменено
|
||||
|
||||
- README нескольких BSP-модулей и manufacturing-test firmware приведены к более единому стилю.
|
||||
- `docs/DEV_ARCH.md`, документы по прошивке/отладке и HIL-гайды обновлены под актуальную структуру проекта.
|
||||
- `just/ci_workflow.md` расширен дополнительными деталями CI workflow.
|
||||
|
||||
### Документация
|
||||
|
||||
- Документация по display, SD, CAN, button, USB-CDC и firmware-test получила содержательные обновления, а не только форматирование.
|
||||
|
||||
### Удалено
|
||||
|
||||
- Удалён `bsp/qspi_flash/REFACTORING.md`, так как временный план потерял актуальность.
|
||||
|
||||
### Отфильтрованный шум
|
||||
|
||||
- Чистая нормализация стиля в документации не учитывалась как отдельная feature, если она не меняла содержание или проектные инструкции.
|
||||
|
||||
## [2026-05-07] — USD/SD test flow
|
||||
|
||||
### Кратко
|
||||
|
||||
- SD/MMC testing перешёл от BSP/middleware-работ к firmware-test module с обновлением протокола и тестовой документации.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлен `firmware/test/src/tests/test_usd.c`, затем доведён до usable SD/MMC test module.
|
||||
|
||||
### Изменено
|
||||
|
||||
- `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.
|
||||
|
||||
### Тесты
|
||||
|
||||
- Firmware-side SD/MMC testing стал видимым отдельным модулем, хотя базовый срез всё ещё требует отслеживать стабильное HIL-покрытие SD.
|
||||
|
||||
## [2026-05-06] — SD card и FatFS integration
|
||||
|
||||
### Кратко
|
||||
|
||||
- Поддержка SD card и FatFS вошла в BSP и firmware-test stack.
|
||||
- NXP SD middleware был пропатчен, что создаёт будущую точку контроля для vendor SDK drift.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлен `bsp/sd` с API, реализацией, README и CMake-интеграцией.
|
||||
- Добавлены `firmware/test/fatfs` и `port/fatfs/sd` для подключения FatFS к SD BSP.
|
||||
- Добавлены generated SDMMC configuration files.
|
||||
|
||||
### Изменено
|
||||
|
||||
- SDK SD middleware и SDMMC host code изменены для поддержки smoke-test path.
|
||||
- Clock и pin configuration скорректированы под SD/MMC.
|
||||
|
||||
### Тесты
|
||||
|
||||
- Появился SD smoke-test, но стабильное HIL regression coverage ещё не закреплено в базовом срезе.
|
||||
|
||||
### Отфильтрованный шум
|
||||
|
||||
- `project_tree.txt` и `sdk/sdk_tree.txt` рассматривались как artifacts состояния репозитория, а не как функциональные изменения.
|
||||
|
||||
## [2026-04-23] — Добавлен GitHub Actions CI
|
||||
|
||||
### Кратко
|
||||
|
||||
- CI стал реальным: проект получил GitHub Actions workflow, который собирает проект внутри devcontainer.
|
||||
- Workflow ориентирован на сборку; host tests и HIL tests остаются будущей работой.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлен `.github/workflows/ci.yml` с triggers на push, PR и manual dispatch.
|
||||
- Workflow собирает devcontainer image, запускает `just ci::build` и загружает build tree как artifact.
|
||||
- Добавлен `just/ci_workflow.md` с описанием CI flow.
|
||||
|
||||
### Изменено
|
||||
|
||||
- `just/ci.just` скорректирован под CI build path.
|
||||
|
||||
### CI
|
||||
|
||||
- CI покрывает воспроизводимость containerized build.
|
||||
- CI пока не запускает host unit tests.
|
||||
- CI пока не запускает HIL tests, которым требуется подключённое железо или self-hosted runner.
|
||||
|
||||
### Удалено
|
||||
|
||||
- Удалён `.clang-tidy` override для generated code из `bsp/generated`.
|
||||
|
||||
## [2026-04-20 .. 2026-04-22] — SDRAM и QSPI firmware-test modules
|
||||
|
||||
### Кратко
|
||||
|
||||
- Manufacturing-test firmware получил memory-oriented test modules для SDRAM и QSPI Flash.
|
||||
- QSPI support появился и как BSP module, и как firmware-side test.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлены API и реализация `bsp/sdram`.
|
||||
- Добавлен `firmware/test/src/tests/test_sdram.c`.
|
||||
- Добавлен `bsp/qspi_flash` с API, реализацией и README.
|
||||
|
|
@ -177,21 +240,26 @@
|
|||
- Добавлен `firmware/test/src/tests/README.md` с описанием firmware-side test modules.
|
||||
|
||||
### Изменено
|
||||
|
||||
- `firmware/test/src/main.c` обновлён для интеграции новых test modules.
|
||||
|
||||
### Тесты
|
||||
|
||||
- SDRAM и QSPI вошли в firmware-test command model.
|
||||
|
||||
### Документация
|
||||
|
||||
- Документация по test modules начала описывать растущий firmware-test suite.
|
||||
|
||||
## [2026-04-17] — Binary protocol, test runner и hardware documentation
|
||||
|
||||
### Кратко
|
||||
|
||||
- Manufacturing-test firmware перешёл от простого CLI к protocol-driven test runner architecture.
|
||||
- Hardware reference documentation существенно расширилась.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- `protocol.c/.h` ввели binary protocol для управления тестами.
|
||||
- `test_module.h` и `test_runner.c/.h` ввели modular firmware-test runner.
|
||||
- Добавлены host tests для protocol и runner behavior.
|
||||
|
|
@ -199,69 +267,85 @@
|
|||
- Добавлены hardware PDFs и board/display reference materials в документацию.
|
||||
|
||||
### Изменено
|
||||
|
||||
- `firmware/test/README.md` сильно переписан под новую архитектуру.
|
||||
- Existing CLI host tests были расширены.
|
||||
|
||||
### Тесты
|
||||
|
||||
- Host coverage расширился на protocol, runner и CLI behavior.
|
||||
|
||||
### Удалено
|
||||
|
||||
- Старые planning/architecture artifacts в `firmware/test` и HIL refactor notes удалены после замены новой структурой.
|
||||
|
||||
### Отфильтрованный шум
|
||||
|
||||
- Большие добавления hardware PDF сведены по назначению, без перечисления каждого файла.
|
||||
|
||||
## [2026-04-07 .. 2026-04-09] — USB-CDC CLI и priority queue
|
||||
|
||||
### Кратко
|
||||
|
||||
- Test firmware получил modular USB-CDC CLI.
|
||||
- Добавлен priority queue utility и host coverage для него.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- `firmware/test/src/cli.c` и `cli.h` ввели отдельный CLI module.
|
||||
- `tests/host/cli/test_cli.c` добавил host coverage для CLI behavior.
|
||||
- Добавлен `utils/prio_queue` с README и большим host test.
|
||||
- Появилась placeholder structure для `bsp/display`.
|
||||
|
||||
### Изменено
|
||||
|
||||
- Старый monolithic `firmware/test/main.c` path заменён на modular source layout.
|
||||
- Добавлены firmware-test planning docs вокруг CLI/protocol roadmap.
|
||||
|
||||
### Тесты
|
||||
|
||||
- Host tests покрыли CLI и priority queue.
|
||||
|
||||
## [2026-04-03 .. 2026-04-06] — USB-CDC stack и HIL coverage
|
||||
|
||||
### Кратко
|
||||
|
||||
- USB-CDC ACM стал реальной BSP capability и был подключён к HIL validation.
|
||||
- HIL configuration и documentation были уточнены вокруг нового USB path.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Реализован `bsp/usb_cdc` с API, descriptors, Chapter 9 handling и hardware wrappers.
|
||||
- SDK USB middleware integration добавлена в сборку.
|
||||
- `tests/target/hil_usb_cdc` и `tools/hil/05_test_usb_cdc.py` добавили USB-CDC HIL coverage.
|
||||
|
||||
### Изменено
|
||||
|
||||
- Обработка HIL configuration была отрефакторена, `.env.example` получил новые переменные.
|
||||
- `bsp/usb_cdc/README.md` переписан с фокусом на API documentation.
|
||||
|
||||
### Тесты
|
||||
|
||||
- USB-CDC вошёл в numbered HIL suite после UART, opto, CAN и button.
|
||||
|
||||
### Документация
|
||||
|
||||
- HIL docs, включая bench, creation guide, fixtures и how-to, были обновлены.
|
||||
|
||||
## [2026-04-01 .. 2026-04-02] — Clock, pin и MPU setup
|
||||
|
||||
### Кратко
|
||||
|
||||
- Board generated files стали полнее: в firmware base вошли clock, pin и MPU configuration.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлены generated clock и pin-mux configuration files.
|
||||
- Добавлен `TFT_Board.mex` как project state NXP Config Tools.
|
||||
- Появились initial empty `bsp/sdram` placeholders.
|
||||
|
||||
### Изменено
|
||||
|
||||
- Board startup/configuration получил MPU initialization.
|
||||
- Linker scripts скорректированы для FlexSPI NOR и RAM layout.
|
||||
- `firmware/test/main.c` упрощён вокруг нового initialization path.
|
||||
|
|
@ -269,94 +353,116 @@
|
|||
## [2026-03-30 .. 2026-03-31] — CAN и button BSP с host/HIL tests
|
||||
|
||||
### Кратко
|
||||
|
||||
- CAN и button support стали полноценными BSP modules с host и HIL validation.
|
||||
- HIL tests были пронумерованы в ordered suite.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлен `bsp/can` с API, реализацией, README и mocks.
|
||||
- Добавлены host tests и HIL target/test code для CAN.
|
||||
- Добавлен `bsp/button` с API, реализацией и README.
|
||||
- Добавлены host tests и HIL target/test code для button.
|
||||
|
||||
### Изменено
|
||||
|
||||
- HIL pytest files переименованы в ordered sequence: UART, opto, CAN, button.
|
||||
- HIL documentation обновлена по мере конкретизации test suite.
|
||||
|
||||
### Тесты
|
||||
|
||||
- CAN и button получили host test coverage.
|
||||
- CAN и button получили HIL coverage.
|
||||
|
||||
### Удалено
|
||||
|
||||
- Удалён `bsp/can/PLAN.md` после переноса полезного содержания в README.
|
||||
|
||||
## [2026-03-26 .. 2026-03-28] — M5StampPLC HIL bench и fixture documentation
|
||||
|
||||
### Кратко
|
||||
|
||||
- HIL стал больше чем pyOCD prototype: появились M5StampPLC support, power/control helpers и fixture documentation.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлена поддержка M5StampPLC в `tools/hil`, включая agent/CLI logic и MicroPython firmware assets.
|
||||
- HIL support code реорганизован в M5-specific helpers.
|
||||
- `tests/target/hil_opto` и `tools/hil/test_opto.py` добавили opto HIL coverage.
|
||||
- `docs/testing/hil/HIL_FIXTURES.md` описал pytest fixtures для HIL.
|
||||
|
||||
### Изменено
|
||||
|
||||
- `just/host.just` и `tools/hil/conftest.py` существенно расширены под HIL workflows.
|
||||
- Документация реорганизована в более понятные иерархии hardware, MIMXRT1052 и testing.
|
||||
|
||||
### Тесты
|
||||
|
||||
- Opto inputs получили HIL-level validation.
|
||||
|
||||
### Удалено
|
||||
|
||||
- Старые locations CMake/test guides заменены новой иерархией `docs/testing`.
|
||||
|
||||
### Отфильтрованный шум
|
||||
|
||||
- Перемещения PDF и документов учтены как изменение структуры документации, а не как отдельное content change для каждого файла.
|
||||
|
||||
## [2026-03-23] — Logging infrastructure и BSP opto
|
||||
|
||||
### Кратко
|
||||
|
||||
- Появились logging infrastructure и opto input BSP вместе с host coverage.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- `port/log` и `utils/log` ввели logging abstractions и UART-oriented logging support.
|
||||
- Добавлены host tests для logging behavior.
|
||||
- Добавлен `bsp/opto` с API, реализацией, README, GPIO mocks и host tests.
|
||||
|
||||
### Тесты
|
||||
|
||||
- Host coverage расширился на logging и opto behavior.
|
||||
|
||||
### Удалено
|
||||
|
||||
- Корневой `TODO.md` удалён после переноса планирования в другие места.
|
||||
|
||||
## [2026-03-18 .. 2026-03-20] — Первый HIL skeleton и flashing/debug docs
|
||||
|
||||
### Кратко
|
||||
|
||||
- Проект получил первый HIL skeleton и target-side UART validation path.
|
||||
- Flashing и debugging tooling стали документированными и scriptable.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлен `tools/hil` с pytest/pyOCD/pyserial-oriented utilities.
|
||||
- `tests/target/host_uart` предоставил target firmware для UART HIL validation.
|
||||
- `tools/host/flash_swd.py` добавил SWD flashing support.
|
||||
- Добавлены `docs/HOW_TO_DEBUG.md`, расширенные flash docs и tool README.
|
||||
|
||||
### Изменено
|
||||
|
||||
- Main README и development architecture docs расширены вокруг host/container workflow и testing.
|
||||
- Добавлены host и HIL test creation guides.
|
||||
|
||||
### Тесты
|
||||
|
||||
- Появился первый UART HIL path.
|
||||
|
||||
### Удалено
|
||||
|
||||
- Temporary flash logs удалены после окончания диагностической пользы.
|
||||
|
||||
## [2026-03-16 .. 2026-03-17] — Первые BSP modules и host test infrastructure
|
||||
|
||||
### Кратко
|
||||
|
||||
- Репозиторий получил первые concrete BSP modules и host-test layout.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлены `bsp_led` и `bsp_tick` с API, реализацией и README files.
|
||||
- `bsp/uart_host` добавил LPUART1/MCU-Link VCOM support с API, реализацией, README, mocks и host tests.
|
||||
- Введены shared BSP status codes.
|
||||
|
|
@ -365,29 +471,36 @@
|
|||
- `TODO-HIL.md` зафиксировал initial HIL plan.
|
||||
|
||||
### Тесты
|
||||
|
||||
- Host testing начался с Unity/fff-style mocks и isolated test directories.
|
||||
|
||||
## [2026-03-13 .. 2026-03-15] — Draft архитектуры manufacturing-test firmware
|
||||
|
||||
### Кратко
|
||||
|
||||
- Архитектура manufacturing-test firmware была задокументирована до последующей реализации protocol/runner.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- `firmware/test/README.md` и `firmware/test/arch.svg` описали первый architecture concept.
|
||||
- BSP и USB-CDC README зафиксировали early design intent.
|
||||
|
||||
### Изменено
|
||||
|
||||
- `bootstrap.sh` был упрощён.
|
||||
|
||||
### Удалено
|
||||
|
||||
- Ранняя VS Code launch configuration удалена при cleanup bootstrap.
|
||||
|
||||
## [2026-03-10 .. 2026-03-12] — Project environment, BSP base и HAB flow
|
||||
|
||||
### Кратко
|
||||
|
||||
- Проект перешёл от пустого scaffold к buildable embedded workspace с generated board support, HAB assets и containerized tooling.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлен board support для MIMXRT1052: startup code, generated config, linker scripts и FlexSPI NOR-related assets.
|
||||
- Добавлен HAB signing/configuration flow для app, bootloader и firmware-test images.
|
||||
- Добавлены formatting/lint/editor configuration.
|
||||
|
|
@ -396,19 +509,23 @@
|
|||
- Добавлена development architecture и CMake hints documentation.
|
||||
|
||||
### Изменено
|
||||
|
||||
- Generated NXP Config Tools content перенесён из `bsp/board` в `bsp/generated`.
|
||||
- Логика `Justfile` разделена на modules под `just/`.
|
||||
- Добавлена flashing documentation, bootstrap logic переработана.
|
||||
|
||||
### Документация
|
||||
|
||||
- Early docs явно отмечали, что CI и tests ещё не покрыты.
|
||||
|
||||
## [2026-03-05] — Initial repository scaffold
|
||||
|
||||
### Кратко
|
||||
|
||||
- Репозиторий инициализирован с базовой metadata и README placeholder.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Добавлены `.gitattributes`, `.gitignore` и начальный `README.md`.
|
||||
|
||||
## Отфильтрованный шум базового среза
|
||||
|
|
|
|||
48
README.md
48
README.md
|
|
@ -1,59 +1,34 @@
|
|||
# tft_manufacture_test
|
||||
|
||||
Монорепозиторий для **MIMXRT1052CVJ5B**. Содержит три независимых firmware-проекта с общей инфраструктурой сборки, тестирования и инструментарием.
|
||||
Монорепозиторий для **MIMXRT1052CVJ5B**. Содержит три независимых firmware-проекта
|
||||
с общей инфраструктурой сборки, тестирования и инструментарием.
|
||||
|
||||
> Архитектура рабочего окружения — [docs/DEV_ARCH.md](docs/DEV_ARCH.md)
|
||||
|
||||
---
|
||||
|
||||
## Три firmware-проекта
|
||||
## Firmware-проекты
|
||||
|
||||
| Проект | Путь | Описание |
|
||||
| ------------------- | ---------------------- | -------------------------------------------------------------------------------------- |
|
||||
| Тестовая прошивка | `firmware/test/` | Входной контроль платы: CAN, UART, SDRAM, QSPI, SDIO, RGB, оптовходы, LED, кнопки, MQS |
|
||||
| Загрузчик | `firmware/bootloader/` | A/B обновление через uSD. Сам обновляется только через USB ROM + blhost / SWD |
|
||||
| Production прошивка | `firmware/tft_app/` | FreeRTOS + FatFS + бизнес-логика. Обновляется загрузчиком |
|
||||
| Загрузчик | `firmware/bootloader/` | A/B обновление через uSD. Обновляется только через USB ROM + blhost / SWD |
|
||||
| Production прошивка | `firmware/tft_app/` | Приложение с реализацией логики лифтового индикатора. Обновляется загрузчиком |
|
||||
|
||||
---
|
||||
|
||||
## BSP
|
||||
|
||||
| Модуль | Путь | Описание |
|
||||
| --------------- | ---------------- | --------------------------------------------------------- |
|
||||
| `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 |
|
||||
Описание модулей, правила написания компонентов и CMake-шаблоны — [bsp/README.md](bsp/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Тестирование
|
||||
|
||||
| Уровень | Где | Инструменты | Запуск |
|
||||
| ---------------- | ------------------------------ | -------------------------------------- | -------------------------------------- |
|
||||
| Host unit-тесты | `tests/host/` | Unity + fff, clang | `just build::test-host` (devcontainer) |
|
||||
| HIL target-тесты | `tests/target/` + `tools/hil/` | 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 unit-тесты | Unity + fff, clang | `just build::test-host` (devcontainer) |
|
||||
| HIL target-тесты | pyOCD + pyserial + pytest + M5StampPLC | `just host::hil-run` (хост) |
|
||||
|
||||
- Как добавить 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)
|
||||
|
|
@ -92,7 +67,8 @@ just host::debug-server # GDB-сервер для отладки
|
|||
| pyOCD, pyserial, pytest, mpremote | `tools/hil/uv.lock` |
|
||||
| spsdk (nxpimage, blhost, sdphost) | `tools/host/uv.lock` |
|
||||
|
||||
Всё что не меняется — vendored. Сборка работает после `git clone` без интернета (кроме Python-зависимостей).
|
||||
Всё что не меняется — vendored. Сборка работает после `git clone` без интернета
|
||||
(кроме Python-зависимостей).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -7,20 +7,17 @@
|
|||
|
||||
## Концепция
|
||||
|
||||
BSP — единственное место в монорепо где есть знание о конкретном железе. Все три прошивки работают с периферией только через BSP. Прямых вызовов NXP SDK (`fsl_*.h`) за пределами `bsp/` быть не должно.
|
||||
BSP — единственное место в монорепо где есть знание о конкретном железе. Все три
|
||||
прошивки работают с периферией только через BSP. Прямых вызовов NXP SDK (`fsl_*.h`)
|
||||
за пределами `bsp/` быть не должно.
|
||||
|
||||
```bash
|
||||
firmware/test firmware/bootloader firmware/tft_app
|
||||
↓ ↓ ↓
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ BSP │
|
||||
│ bsp_led bsp_opto bsp_tick bsp_uart_host ... │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
↓ ↓ ↓
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ NXP SDK / middleware │
|
||||
│ fsl_lpuart fsl_gpio fsl_iomuxc ... │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```mermaid
|
||||
graph TB
|
||||
FW["firmware/test · firmware/bootloader · firmware/tft_app"]
|
||||
BSP["BSP"]
|
||||
SDK["NXP SDK / middleware\nfsl_lpuart · fsl_gpio · fsl_iomuxc · …"]
|
||||
|
||||
FW --> BSP --> SDK
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -42,18 +39,18 @@ bsp/
|
|||
│ └── startup_MIMXRT1052.S
|
||||
│
|
||||
├── common/ # bsp_status_t и общие типы
|
||||
├── led/ # bsp_led — два UserLed (GPIO3[3], GPIO3[4])
|
||||
├── tick/ # bsp_tick — SysTick / FreeRTOS-совместимый таймер
|
||||
├── uart_host/ # bsp_uart_host — LPUART1 (MCU-Link VCOM, J2)
|
||||
├── led/ # bsp_led — два UserLed (GPIO3[3], GPIO3[4])
|
||||
├── tick/ # bsp_tick — SysTick / FreeRTOS-совместимый таймер
|
||||
├── uart_host/ # bsp_uart_host — LPUART1 (MCU-Link VCOM, J2)
|
||||
│ └── mocks/ # fff-заглушки для host-тестов
|
||||
├── opto/ # bsp_opto — оптоизолированные входы PS2801-4
|
||||
├── can/ # bsp_can — FlexCAN2 (трансивер SN65HVD230D)
|
||||
├── button/ # bsp_button — тактовые кнопки SWT6x6 с debounce
|
||||
├── display/ # bsp_display — TFT-дисплей
|
||||
├── usb_cdc/ # bsp_usb_cdc — USB CDC ACM
|
||||
├── sdram/ # bsp_sdram — внешний SDRAM через SEMC
|
||||
├── qspi_flash/ # bsp_qspi_flash — QSPI Flash W25Q64/128/256/512
|
||||
└── sd/ # bsp_sd — SD host-контроллер (USDHC1)
|
||||
├── opto/ # bsp_opto — оптоизолированные входы PS2801-4
|
||||
├── can/ # bsp_can — FlexCAN2 (трансивер SN65HVD230D)
|
||||
├── button/ # bsp_button — тактовые кнопки SWT6x6 с debounce
|
||||
├── display/ # bsp_display — TFT-дисплей
|
||||
├── usb_cdc/ # bsp_usb_cdc — USB CDC ACM
|
||||
├── sdram/ # bsp_sdram — внешний SDRAM через SEMC
|
||||
├── qspi_flash/ # bsp_qspi_flash — QSPI Flash W25Q64/128/256/512
|
||||
└── sd/ # bsp_sd — SD host-контроллер (USDHC1)
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -73,10 +70,10 @@ target_link_libraries(bsp_<любой_компонент> PUBLIC bsp_board)
|
|||
|
||||
### Boot-стратегии — INTERFACE-библиотеки
|
||||
|
||||
| Таргет CMake | Сценарий | Кто использует |
|
||||
|---|---|---|
|
||||
| `bsp_boot_xip` | XIP — исполнение из Flash | `firmware/test`, `firmware/tft_app` |
|
||||
| `bsp_boot_ram` | исполнение из ITCM/DTCM | HIL target-прошивки (`tests/target/`) |
|
||||
| Таргет CMake | Сценарий | Кто использует |
|
||||
| -------------- | ------------------------- | ------------------------------------- |
|
||||
| `bsp_boot_xip` | XIP — исполнение из Flash | `firmware/test`, `firmware/tft_app` |
|
||||
| `bsp_boot_ram` | Исполнение из ITCM/DTCM | HIL target-прошивки (`tests/target/`) |
|
||||
|
||||
Подключается явно в каждом проекте:
|
||||
|
||||
|
|
@ -87,19 +84,19 @@ target_link_libraries(test_hil_opto PRIVATE bsp_board bsp_boot_ram ...)
|
|||
|
||||
### Компоненты периферии
|
||||
|
||||
| Библиотека | Модуль | README |
|
||||
|---|---|---|
|
||||
| `bsp_led` | `led/` | [led/README.md](led/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_opto` | `opto/` | [opto/README.md](opto/README.md) |
|
||||
| `bsp_can` | `can/` | [can/README.md](can/README.md) |
|
||||
| `bsp_button` | `button/` | [button/README.md](button/README.md) |
|
||||
| `bsp_display` | `display/` | [display/README.md](display/README.md) |
|
||||
| `bsp_usb_cdc` | `usb_cdc/` | [usb_cdc/README.md](usb_cdc/README.md) |
|
||||
| `bsp_sdram` | `sdram/` | [sdram/README.md](sdram/README.md) |
|
||||
| Библиотека | Модуль | README |
|
||||
| ---------------- | ------------- | -------------------------------------------- |
|
||||
| `bsp_led` | `led/` | [led/README.md](led/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_opto` | `opto/` | [opto/README.md](opto/README.md) |
|
||||
| `bsp_can` | `can/` | [can/README.md](can/README.md) |
|
||||
| `bsp_button` | `button/` | [button/README.md](button/README.md) |
|
||||
| `bsp_display` | `display/` | [display/README.md](display/README.md) |
|
||||
| `bsp_usb_cdc` | `usb_cdc/` | [usb_cdc/README.md](usb_cdc/README.md) |
|
||||
| `bsp_sdram` | `sdram/` | [sdram/README.md](sdram/README.md) |
|
||||
| `bsp_qspi_flash` | `qspi_flash/` | [qspi_flash/README.md](qspi_flash/README.md) |
|
||||
| `bsp_sd` | `sd/` | [sd/README.md](sd/README.md) |
|
||||
| `bsp_sd` | `sd/` | [sd/README.md](sd/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
|
||||
/* ПРАВИЛЬНО — bsp/opto/include/bsp/opto.h */
|
||||
|
|
|
|||
|
|
@ -1,49 +1,52 @@
|
|||
# bsp_button — тактовые кнопки
|
||||
# bsp_button — тактовые кнопки (SWT6x6)
|
||||
|
||||
Чтение двух тактовых кнопок SWT6x6 с программным debounce.
|
||||
Предоставляет мгновенное сырое чтение, стабильное состояние и одноразовые
|
||||
события нажатия/отпускания.
|
||||
Чтение двух тактовых кнопок с программным дебаунсом. Предоставляет мгновенное
|
||||
сырое чтение, стабильное состояние и одноразовые события нажатия/отпускания.
|
||||
|
||||
---
|
||||
|
||||
## Аппаратура
|
||||
|
||||
| Кнопка | Пин MCU | GPIO | Схема | Нажатие |
|
||||
| -------------- | ---------- | --------- | ----------------------------- | ------- |
|
||||
| `BSP_BUTTON_1` | GPIO_B1_14 | GPIO2[30] | SWT6x6, pull-up к 3V3 внешний | LOW |
|
||||
| `BSP_BUTTON_2` | GPIO_B1_15 | GPIO2[31] | SWT6x6, pull-up к 3V3 внешний | LOW |
|
||||
| Идентификатор | Сигнал | Пин MCU | Корпус | GPIO | Нажатие |
|
||||
| -------------- | -------- | ---------- | ------ | --------- | ------- |
|
||||
| `BSP_BUTTON_1` | TactBut1 | GPIO_B1_14 | C14 | GPIO2[30] | LOW |
|
||||
| `BSP_BUTTON_2` | TactBut2 | GPIO_B1_15 | B14 | GPIO2[31] | LOW |
|
||||
|
||||
Пины настроены в `BOARD_InitPins()` (`generated/pin_mux.c`) как INPUT с включённым
|
||||
гистерезисом, без внутренней подтяжки (`0x0100B0`). `bsp_button_init()` не трогает
|
||||
Пины настроены в `BOARD_InitPins()` как INPUT с гистерезисом, без внутренней
|
||||
подтяжки (`0x0100B0`) — внешний pull-up к 3V3. `bsp_button_init()` не трогает
|
||||
GPIO — только сбрасывает внутреннее состояние модуля.
|
||||
|
||||
---
|
||||
|
||||
## Принцип работы
|
||||
## Архитектура
|
||||
|
||||
```bash
|
||||
GPIO2[30] / GPIO2[31]
|
||||
↓
|
||||
GPIO_ReadPinInput() ← вызывается в bsp_button_poll()
|
||||
↓
|
||||
debounce (счётчик) ← 4 одинаковых сэмпла подряд = 20 мс
|
||||
↓
|
||||
stable state + события ← evt_pressed / evt_released
|
||||
↓
|
||||
bsp_button_is_pressed()
|
||||
bsp_button_get_event_pressed()
|
||||
bsp_button_get_event_released()
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["GPIO2[30] / GPIO2[31]"] --> B["GPIO_ReadPinInput()\nв bsp_button_poll()"]
|
||||
B --> C["счётчик дебаунса\n4 одинаковых сэмпла = 20 мс"]
|
||||
C --> D["stable state\nevt_pressed / evt_released"]
|
||||
D --> E["bsp_button_is_pressed()\nbsp_button_get_event_pressed()\nbsp_button_get_event_released()"]
|
||||
```
|
||||
|
||||
- **Polling** — `bsp_button_poll()` вызывается снаружи каждые 5 мс. Без прерываний.
|
||||
- **Debounce** — счётчик подтверждений: 4 одинаковых сэмпла подряд фиксируют
|
||||
переход. Глитч (смена уровня до набора порога) сбрасывает счётчик.
|
||||
- **События** — одноразовые флаги `evt_pressed` / `evt_released`, сбрасываются
|
||||
- **Polling** — `bsp_button_poll()` вызывается каждые 5 мс. Без прерываний.
|
||||
- **Дебаунс** — 4 одинаковых сэмпла подряд (`BUTTON_DEBOUNCE_SAMPLES = 4`,
|
||||
4 × 5 мс = 20 мс). Глитч сбрасывает счётчик.
|
||||
- **События** — одноразовые флаги `evt_pressed`/`evt_released`, сбрасываются
|
||||
при первом обращении через `get_event_*()`.
|
||||
|
||||
Максимальная задержка реакции = период поллинга = 5 мс. Для навигации по меню
|
||||
и производственного теста этого достаточно — порог восприятия задержки UI
|
||||
около 50–100 мс.
|
||||
---
|
||||
|
||||
## 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-коллбэка: */
|
||||
bsp_button_poll();
|
||||
|
||||
/* В основном цикле: */
|
||||
if (**bsp_button_get_event_pressed(BSP_BUTTON_1)**) {
|
||||
/* В main loop: */
|
||||
if (bsp_button_get_event_pressed(BSP_BUTTON_1)) {
|
||||
/* однократное срабатывание по нажатию */
|
||||
}
|
||||
|
||||
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
|
||||
/* Сырое чтение без debounce — сразу после board_hw_init(),
|
||||
до вызова bsp_button_init(). */
|
||||
/* До bsp_button_init(), сразу после board_hw_init(): */
|
||||
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 (рекомендуется 15–30 мс).
|
||||
|
||||
---
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Host unit-тесты
|
||||
|
||||
Категория **B** — `button.c` вызывает `GPIO_ReadPinInput()` из `fsl_gpio.h`.
|
||||
SDK-функция мокируется через fff в тестовом файле.
|
||||
Stub `fsl_gpio.h` уже существует в `tests/host/mocks/`.
|
||||
SDK-функция мокируется через fff. Stub `fsl_gpio.h` в `tests/host/mocks/`.
|
||||
|
||||
```cmake
|
||||
add_host_test(
|
||||
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}
|
||||
)
|
||||
```bash
|
||||
just build::test-host # покрытие: init, дебаунс нажатия/отпускания,
|
||||
# потребление событий, сброс при глитче, независимость кнопок
|
||||
```
|
||||
|
||||
Покрытие: инициализация, сырое чтение, debounce нажатия/отпускания,
|
||||
потребление событий, сброс счётчика при глитче, независимость кнопок,
|
||||
граничные значения индекса.
|
||||
|
||||
### HIL-тест (интерактивный)
|
||||
|
||||
Кнопки расположены на плате таргета — оператор нажимает вручную по подсказкам.
|
||||
Оператор нажимает кнопки вручную по подсказкам. Не входит в `hil-run`.
|
||||
|
||||
```bash
|
||||
just host::hil-button # запускать отдельно, не входит в hil-run
|
||||
just host::hil-button
|
||||
```
|
||||
|
||||
C-прошивка: `tests/target/hil_button/` — CLI через `bsp_uart_host`.
|
||||
pytest: `tools/hil/04_test_button.py` — помечен `@pytest.mark.interactive`.
|
||||
|
||||
Команды CLI прошивки:
|
||||
|
||||
| Команда | Ответ | Описание |
|
||||
| --------------- | --------- | ------------------------------------- |
|
||||
| `PING` | `PONG` | Проверка канала |
|
||||
| `READ <idx>` | `1` / `0` | Сырое состояние (без debounce) |
|
||||
| `STATE <idx>` | `1` / `0` | Стабильное состояние после debounce |
|
||||
| `READ <idx>` | `1` / `0` | Сырое состояние без дебаунса |
|
||||
| `STATE <idx>` | `1` / `0` | Стабильное состояние после дебаунса |
|
||||
| `EVENT_P <idx>` | `1` / `0` | `get_event_pressed`, сбрасывает флаг |
|
||||
| `EVENT_R <idx>` | `1` / `0` | `get_event_released`, сбрасывает флаг |
|
||||
|
||||
---
|
||||
|
||||
## Зависимости
|
||||
## Интеграция
|
||||
|
||||
| Зависимость | Тип | Описание |
|
||||
| ------------ | ------- | -------------------------------------------- |
|
||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||
| `bsp_board` | PRIVATE | Транзитивно: `pin_mux.h`, clock, SDK headers |
|
||||
| `sdk_gpio` | PRIVATE | `fsl_gpio.h` — `GPIO_ReadPinInput()` |
|
||||
```c
|
||||
/* FreeRTOS — из задачи: */
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
bsp_button_poll();
|
||||
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
|
||||
# bsp/CMakeLists.txt
|
||||
add_subdirectory(button)
|
||||
|
||||
# firmware/test/CMakeLists.txt или firmware/tft_app/CMakeLists.txt
|
||||
target_link_libraries(firmware_test PRIVATE
|
||||
bsp_board
|
||||
bsp_tick
|
||||
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()` |
|
||||
|
|
|
|||
|
|
@ -1,47 +1,93 @@
|
|||
# bsp_can
|
||||
# bsp_can — FlexCAN2 (CAN 2.0)
|
||||
|
||||
Приём и передача CAN 2.0 фреймов через FlexCAN2 (трансивер SN65HVD230D, разъём P1 контакты 3–4).
|
||||
Приём и передача CAN 2.0 фреймов через FlexCAN2 (трансивер SN65HVD230D,
|
||||
разъём P1 контакты 3–4). Применяется для обмена с управляющими модулями
|
||||
по 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 — отправка фреймов |
|
||||
| 2–17 | RX — до 16 индивидуальных фильтров |
|
||||
|
||||
ERR005829 — errata FlexCAN на i.MX RT1050/1052: при гонке TX/RX арбитража
|
||||
MB 0 может зависнуть. Workaround: MB 0 всегда `kFLEXCAN_TxMbInactive`.
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
```bash
|
||||
bsp_can_send() ← blocking polling + таймаут
|
||||
↓
|
||||
[FlexCAN2 TX MB1] → SN65HVD230D → CAN bus
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph TX
|
||||
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]
|
||||
↓
|
||||
poll_rx_mailboxes() ← опрос флагов MB
|
||||
↓
|
||||
ring_buffer ← внутренний FIFO
|
||||
↓
|
||||
bsp_can_receive() ← polling + таймаут
|
||||
subgraph RX
|
||||
E["CAN bus → SN65HVD230D"] --> F["FlexCAN2 RX MB2–17"]
|
||||
F --> G["poll_rx_mailboxes()"]
|
||||
G --> H["ring_buffer (внутренний FIFO)"]
|
||||
H --> I["bsp_can_receive()"]
|
||||
end
|
||||
```
|
||||
|
||||
- **TX** — blocking polling с таймаутом. `bsp_can_send()` записывает фрейм
|
||||
в TX MB и ждёт флага завершения. Worst case при 500 kbit/s — ~260 мкс на фрейм.
|
||||
- **RX** — polling. `bsp_can_receive()` опрашивает все активные RX MB,
|
||||
складывает найденные фреймы во внутренний ring buffer, возвращает первый
|
||||
доступный. Без прерываний.
|
||||
- **TX** — blocking polling с таймаутом. Worst case при 500 kbit/s — ~260 мкс на фрейм.
|
||||
- **RX** — polling без прерываний. `bsp_can_receive()` опрашивает все активные
|
||||
RX MB, складывает фреймы в ring buffer, возвращает первый доступный.
|
||||
- **Singleton** — один экземпляр, одна CAN-шина.
|
||||
|
||||
---
|
||||
|
||||
## Распределение Message Buffers
|
||||
## API
|
||||
|
||||
| MB | Назначение |
|
||||
| ----- | -------------------------------------------------- |
|
||||
| 0 | Зарезервирован (ERR005829 workaround: inactive TX) |
|
||||
| 1 | TX — отправка фреймов |
|
||||
| 2..17 | RX — до 16 индивидуальных фильтров |
|
||||
```c
|
||||
bsp_status_t bsp_can_init(const bsp_can_config_t *p_cfg);
|
||||
|
||||
ERR005829 — errata FlexCAN на i.MX RT1050/1052: при гонке TX/RX арбитража
|
||||
MB 0 может зависнуть. Workaround: MB 0 всегда `kFLEXCAN_TxMbInactive`.
|
||||
bsp_status_t bsp_can_send(const bsp_can_frame_t *p_frame, uint32_t timeout_ms);
|
||||
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_init(&cfg);
|
||||
|
||||
/* Принимать всё */
|
||||
/* Принимать все фреймы */
|
||||
bsp_can_accept_all();
|
||||
|
||||
/* TX — blocking, таймаут 500 мс */
|
||||
|
|
@ -71,129 +117,31 @@ if (bsp_can_receive(&rx, 100) == BSP_OK) {
|
|||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Фильтрация
|
||||
|
||||
Каждый фильтр занимает один RX MB. Максимум 16 фильтров (`BSP_CAN_FILTER_MAX`).
|
||||
**Фильтрация:**
|
||||
|
||||
```c
|
||||
/* Принимать только STD ID 0x200 с точным совпадением */
|
||||
/* STD ID 0x200 — точное совпадение */
|
||||
bsp_can_set_filter(0, 0x200, 0x7FF, false);
|
||||
|
||||
/* Принимать STD ID 0x300..0x30F (маска 0x7F0, младшие 4 бита игнорируются) */
|
||||
/* STD ID 0x300–0x30F */
|
||||
bsp_can_set_filter(1, 0x300, 0x7F0, false);
|
||||
|
||||
/* Принимать EXT ID 0x1ABCDEF0 с точным совпадением */
|
||||
/* EXT ID 0x1ABCDEF0 */
|
||||
bsp_can_set_filter(2, 0x1ABCDEF0, 0x1FFFFFFF, true);
|
||||
|
||||
/* Сбросить фильтры — принимать всё (STD + EXT) */
|
||||
bsp_can_accept_all();
|
||||
```
|
||||
|
||||
`bsp_can_accept_all()` настраивает два MB: один для STD (маска 0), один
|
||||
для EXT (маска 0). Все остальные MB деактивируются.
|
||||
|
||||
---
|
||||
|
||||
## Блокирующее поведение
|
||||
|
||||
### 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
|
||||
)
|
||||
```
|
||||
Каждый фильтр занимает один RX MB. Максимум 16 фильтров (`BSP_CAN_FILTER_MAX`).
|
||||
`bsp_can_accept_all()` настраивает два MB (STD + EXT с маской 0),
|
||||
деактивирует остальные.
|
||||
|
||||
---
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Host unit-тесты (тестирование логики bsp_can)
|
||||
### Host unit-тесты
|
||||
|
||||
Категория **B** — модуль вызывает NXP SDK (`fsl_flexcan.h`).
|
||||
SDK-функции мокаются через fff в тестовом файле.
|
||||
Stubs: `fsl_flexcan.h`, `fsl_common.h`, `clock_config.h` в `tests/host/mocks/`.
|
||||
Категория **B** — `bsp_can.c` вызывает `fsl_flexcan.h`. SDK-функции мокируются
|
||||
через fff. Stub `fsl_flexcan.h` в `tests/host/mocks/`.
|
||||
|
||||
```cmake
|
||||
add_host_test(
|
||||
|
|
@ -205,39 +153,60 @@ add_host_test(
|
|||
${PROJECT_SOURCE_DIR}/bsp/can/include
|
||||
${PROJECT_SOURCE_DIR}/bsp/common/include
|
||||
${PROJECT_SOURCE_DIR}/utils/ring_buffer
|
||||
MOCKS
|
||||
${BSP_MOCKS_DIR}
|
||||
MOCKS ${BSP_MOCKS_DIR}
|
||||
)
|
||||
```
|
||||
|
||||
### Humble Object (тестирование потребителей bsp_can)
|
||||
|
||||
Модуль предоставляет fff-заглушки в `bsp/can/mocks/`:
|
||||
**Humble Object** — fff-заглушки для потребителей в `bsp/can/mocks/`:
|
||||
|
||||
```c
|
||||
#include "fff.h"
|
||||
DEFINE_FFF_GLOBALS;
|
||||
|
||||
#include "bsp/can.h"
|
||||
#include "can_mock.h"
|
||||
|
||||
void setUp(void) { CAN_MOCK_RESET_ALL(); }
|
||||
|
||||
void test_protocol_sends_response(void) {
|
||||
bsp_can_send_fake.return_val = BSP_OK;
|
||||
/* ... вызываем protocol_handle_frame() ... */
|
||||
/* ... */
|
||||
TEST_ASSERT_EQUAL(1, bsp_can_send_fake.call_count);
|
||||
}
|
||||
```
|
||||
|
||||
### HIL-тесты
|
||||
|
||||
C-прошивка `tests/target/can/` с CLI через UART + pytest `tools/hil/test_can.py`.
|
||||
CAN-адаптер на стороне хоста — M5Stack с CAN-модулем.
|
||||
C-прошивка: `tests/target/hil_can/` — CLI через `bsp_uart_host`.
|
||||
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
|
||||
)
|
||||
```
|
||||
|
||||
**Зависимости модуля:**
|
||||
|
||||
| Зависимость | Тип | Описание |
|
||||
| -------------- | ------- | -------------------------------------- |
|
||||
|
|
|
|||
|
|
@ -1,96 +1,51 @@
|
|||
# bsp_display — ELCDIF RGB-дисплей (TFT4 / TFT7 / TFT8 / TFT10)
|
||||
# bsp_display — ELCDIF RGB-дисплей (TFT7 / TFT8 / TFT4 / TFT10)
|
||||
|
||||
> Расположение: `bsp/display/`
|
||||
> Публичный заголовок: `bsp/display/include/bsp/display.h`
|
||||
> Реализация: `bsp/display/src/display.c`
|
||||
|
||||
Модуль инициализирует ELCDIF в RGB-режиме, настраивает пиксельный клок,
|
||||
управляет GPIO подсветки и пинами ориентации/режима LR, UD, MODE, DITHB.
|
||||
Предоставляет минимальное API для смены фреймбуфера, поворота и оповещения
|
||||
о завершении кадра через callback. Bare-metal совместим (без FreeRTOS).
|
||||
Инициализация ELCDIF в RGB-режиме, настройка пиксельного клока, управление
|
||||
GPIO подсветки и пинами ориентации/режима. Предоставляет API для смены
|
||||
фреймбуфера, поворота и нотификации о завершении кадра через ISR-safe callback.
|
||||
Bare-metal совместим, без FreeRTOS.
|
||||
|
||||
---
|
||||
|
||||
## Аппаратный контекст
|
||||
## Аппаратура
|
||||
|
||||
| Сигнал / параметр | Аппаратное назначение |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| 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` |
|
||||
**Управляющие GPIO:**
|
||||
|
||||
`IOMUXC` конфигурируется в `BOARD_InitPins()` за пределами модуля — здесь
|
||||
выполняется только `GPIO_PinWrite`.
|
||||
| Сигнал | Пин MCU | Корпус | GPIO | Назначение |
|
||||
| ------------ | ------------- | ------ | --------- | ---------------------------- |
|
||||
| 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 | Эффективная частота |
|
||||
| ------- | --------- | -------- | --- | ------------------------------ |
|
||||
| TFT7 | PLL2 | 2 | 4 | `528/3/5 = 35.2 МГц` |
|
||||
| TFT8 | PLL2 | 2 | 3 | `528/3/4 = 44.0 МГц` |
|
||||
| TFT4 | Video PLL | — (TODO) | — | требует `CLOCK_InitVideoPll` |
|
||||
| TFT10 | — | — | — | таблица не заполнена (`{ 0 }`) |
|
||||
| Сигнал | Пины MCU | Примечание |
|
||||
| ------------ | ---------------- | ------------------------------- |
|
||||
| LCDIF_CLK | GPIO_B0_00 (D7) | |
|
||||
| LCDIF_ENABLE | GPIO_B0_01 (E7) | |
|
||||
| LCDIF_HSYNC | GPIO_B0_02 (E8) | |
|
||||
| LCDIF_VSYNC | GPIO_B0_03 (D8) | |
|
||||
| DATA[0–7] | GPIO_B0_04–B0_11 | C8, B8, A8, A9, B9, C9, D9, A10 |
|
||||
| DATA[8–15] | GPIO_B0_12–B1_03 | |
|
||||
| DATA[16–23] | GPIO_B1_04–B1_11 | |
|
||||
|
||||
Тайминги HSW/HFP/HBP/VSW/VFP/VBP заданы константами в `display.c`
|
||||
(`DISPLAY_TFT7_*`, `DISPLAY_TFT8_*`, `DISPLAY_TFT4_*`).
|
||||
Пины настроены в `BOARD_InitPins()`. `LCDIF_IRQHandler` размещён в ITCM
|
||||
(`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 | — | — | — | зарезервировано |
|
||||
|
||||
---
|
||||
|
||||
## Состав модуля
|
||||
|
||||
```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
|
||||
## API
|
||||
|
||||
```c
|
||||
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_status_t bsp_display_deinit(void);
|
||||
|
||||
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);
|
||||
|
||||
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`), включает
|
||||
`kCLOCK_LcdPixel`, поднимает подсветку, для TFT7/TFT8 инициализирует ножки
|
||||
ориентации (`ROTATE_0`: LR=1, UD=0) и режима (MODE=1: DE-mode, DITHB=1),
|
||||
конфигурирует ELCDIF через `ELCDIF_RgbModeInit` и запускает его через
|
||||
`ELCDIF_RgbModeStart`. Включает прерывание `kELCDIF_CurFrameDoneInterruptEnable`
|
||||
с приоритетом `DISPLAY_IRQ_PRIORITY = 2`.
|
||||
```c
|
||||
typedef enum {
|
||||
BSP_DISPLAY_TFT4 = 0U, /* 480 × 272 */
|
||||
BSP_DISPLAY_TFT7, /* 1024 × 600 */
|
||||
BSP_DISPLAY_TFT8, /* 800 × 600 */
|
||||
BSP_DISPLAY_TFT10, /* зарезервировано */
|
||||
BSP_DISPLAY_COUNT,
|
||||
} bsp_display_type_t;
|
||||
```
|
||||
|
||||
Требования к аргументам и поведение:
|
||||
**Ротация → LR/UD (TFT7/TFT8):**
|
||||
|
||||
- `framebuffer_addr` — физический адрес первого фреймбуфера. Согласно
|
||||
заголовку, ожидается выравнивание по 64 байтам и размещение в NonCacheable
|
||||
SDRAM. (Само значение модуль не проверяет — это контракт потребителя.)
|
||||
- `p_on_frame_done` — ISR-safe callback; `NULL` означает «без callback».
|
||||
Сохраняется до включения IRQ, чтобы избежать гонки.
|
||||
- Повторный вызов без `bsp_display_deinit()` — идемпотентен, возвращает
|
||||
`BSP_OK` без побочных эффектов.
|
||||
```c
|
||||
typedef enum {
|
||||
BSP_DISPLAY_ROTATE_0 = 0U, /* LR=1, UD=0 */
|
||||
BSP_DISPLAY_FLIP_VERTICAL, /* LR=1, UD=1 */
|
||||
BSP_DISPLAY_FLIP_BOTH, /* LR=0, UD=1 */
|
||||
BSP_DISPLAY_FLIP_HORIZONTAL, /* LR=0, UD=0 */
|
||||
} bsp_display_rotation_t;
|
||||
```
|
||||
|
||||
Коды возврата:
|
||||
**Коды возврата `bsp_display_init()`:**
|
||||
|
||||
| Код | Когда |
|
||||
| ----------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `BSP_OK` | Дисплей инициализирован (или уже был инициализирован). |
|
||||
| `BSP_ERR_PARAM` | `type >= BSP_DISPLAY_COUNT`. |
|
||||
| `BSP_ERR_NOT_SUPPORTED` | `type` требует Video PLL (TFT4) — `init_pixelclock` возвращает ошибку до реализации `CLOCK_InitVideoPll`. |
|
||||
| Код | Условие |
|
||||
| ----------------------- | ------------------------------ |
|
||||
| `BSP_OK` | Инициализирован (или уже был) |
|
||||
| `BSP_ERR_PARAM` | `type >= BSP_DISPLAY_COUNT` |
|
||||
| `BSP_ERR_NOT_SUPPORTED` | TFT4 — Video PLL не реализован |
|
||||
|
||||
> Поведение для `BSP_DISPLAY_TFT10` целостно не описано в коде: запись в
|
||||
> `K_HW_CFG[BSP_DISPLAY_TFT10]` сделана как `{ 0 }`. Использовать TFT10 как
|
||||
> рабочий параметр сейчас не гарантируется — типу зарезервировано место в
|
||||
> enum.
|
||||
`bsp_display_init()` идемпотентен: повторный вызов без `deinit` возвращает
|
||||
`BSP_OK` без побочных эффектов. Callback `p_on_frame_done` должен быть
|
||||
ISR-safe (`NULL` допускается).
|
||||
|
||||
### `bsp_display_deinit`
|
||||
|
||||
Останавливает 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()` и т.п.; любые
|
||||
блокирующие операции запрещены (требование из заголовка).
|
||||
`bsp_display_set_next_buffer()` безопасна из ISR и из задачи; переключение
|
||||
происходит аппаратно по окончании текущего кадра.
|
||||
|
||||
---
|
||||
|
||||
## Порядок использования
|
||||
## Быстрый старт
|
||||
|
||||
```c
|
||||
#include "bsp/display.h"
|
||||
|
||||
/* Фреймбуфер — статический, в NonCacheable SDRAM, выровнен по 64 байтам. */
|
||||
/* Фреймбуфер — в NonCacheable SDRAM, выровнен по 64 байтам */
|
||||
static AT_NONCACHEABLE_SECTION_ALIGN(
|
||||
uint32_t fb[BSP_DISPLAY_MAX_HEIGHT * BSP_DISPLAY_MAX_WIDTH], 64U);
|
||||
|
||||
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() / CLOCK_*/
|
||||
/* После board_hw_init(): */
|
||||
bsp_display_init(BSP_DISPLAY_TFT8, (uint32_t)fb, on_frame_done);
|
||||
bsp_display_set_rotation(BSP_DISPLAY_ROTATE_0);
|
||||
|
||||
bsp_status_t s = bsp_display_init(BSP_DISPLAY_TFT8,
|
||||
(uint32_t) fb,
|
||||
on_frame_done);
|
||||
if (s != BSP_OK) { /* обработать */ }
|
||||
|
||||
(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) { /* ждать */ }
|
||||
}
|
||||
/* Показать кадр */
|
||||
g_frame_done = false;
|
||||
bsp_display_set_next_buffer((uint32_t)fb);
|
||||
while (!g_frame_done) {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Зависимости и CMake
|
||||
## CMake
|
||||
|
||||
```cmake
|
||||
# bsp/display/CMakeLists.txt
|
||||
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_link_libraries(firmware_test PRIVATE bsp_display)
|
||||
|
||||
target_compile_definitions(firmware_test PRIVATE
|
||||
DISPLAY_TEST_TYPE=BSP_DISPLAY_TFT8
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
`DISPLAY_TEST_TYPE` — параметр **тест-модуля** (`test_display.c`), а не самого
|
||||
`bsp_display`; см. ниже.
|
||||
`DISPLAY_TEST_TYPE` — параметр тест-модуля `test_display.c`, не самого BSP.
|
||||
|
||||
---
|
||||
**Зависимости модуля:**
|
||||
|
||||
## Связь с firmware_test (`test_display.c`)
|
||||
|
||||
Тест-модуль `firmware/test/src/tests/test_display.c` использует это BSP так:
|
||||
|
||||
- Тип дисплея определяется макросом `DISPLAY_TEST_TYPE` (по умолчанию
|
||||
`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.
|
||||
| Зависимость | Тип | Описание |
|
||||
| ------------ | ------- | ------------------------------------------- |
|
||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||
| `bsp_board` | PRIVATE | Транзитивно: `BOARD_*`, IOMUXC |
|
||||
| `sdk_elcdif` | PRIVATE | `fsl_elcdif.h`, `fsl_clock.h`, `fsl_gpio.h` |
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
# bsp_led
|
||||
# bsp_led — пользовательские светодиоды
|
||||
|
||||
Драйвер двух пользовательских светодиодов на плате.
|
||||
Управление двумя пользовательскими светодиодами на плате.
|
||||
Используется для индикации heartbeat и состояния приложения.
|
||||
|
||||
---
|
||||
|
||||
## Аппаратная часть
|
||||
## Аппаратура
|
||||
|
||||
| `led_id_t` | Сигнал | GPIO | Pin | Координата | Активный уровень |
|
||||
| --------------- | ---------- | ----- | --- | ---------- | ---------------- |
|
||||
| `LED_HEARTBEAT` | `UserLed1` | GPIO3 | 3 | M4 | LOW (0 = горит) |
|
||||
| `LED_APP` | `UserLed2` | GPIO3 | 4 | P2 | LOW (0 = горит) |
|
||||
| Идентификатор | Сигнал | Пин MCU | Корпус | GPIO | Активный уровень |
|
||||
| --------------- | -------- | ------------- | ------ | -------- | ---------------- |
|
||||
| `LED_HEARTBEAT` | UserLed1 | GPIO_SD_B1_03 | M4 | GPIO3[3] | LOW (0 = горит) |
|
||||
| `LED_APP` | UserLed2 | GPIO_SD_B1_04 | P2 | GPIO3[4] | LOW (0 = горит) |
|
||||
|
||||
Пины сконфигурированы в `generated/pin_mux.h` (MCUXpresso Config Tools).
|
||||
`INIT_GPIO_VALUE = 1U` — оба LED выключены сразу после `led_init()`.
|
||||
Пины настроены в `BOARD_InitPins()` (`generated/pin_mux.c`) как OUTPUT,
|
||||
`INIT_GPIO_VALUE = 1U` — оба LED выключены сразу после `bsp_led_init()`.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```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_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);
|
||||
```
|
||||
|
||||
Вызвать `bsp_led_init()` один раз после `board_hw_init()`.
|
||||
|
||||
---
|
||||
|
||||
## Использование
|
||||
## Быстрый старт
|
||||
|
||||
```c
|
||||
#include "bsp/led.h"
|
||||
|
||||
// инициализация
|
||||
bsp_led_init();
|
||||
|
||||
// heartbeat
|
||||
/* heartbeat в main loop */
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
|
||||
// прикладная индикация
|
||||
bsp_led_on(LED_APP); // пакет принят / тест запущен
|
||||
bsp_led_off(LED_APP); // сброс
|
||||
/* индикация события */
|
||||
bsp_led_on(LED_APP);
|
||||
/* ... */
|
||||
bsp_led_off(LED_APP);
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -51,20 +54,13 @@ bsp_led_off(LED_APP); // сброс
|
|||
## 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` на уровне теста.
|
||||
**Зависимости модуля:**
|
||||
|
||||
---
|
||||
|
||||
## Файлы
|
||||
|
||||
```bash
|
||||
led/
|
||||
├── CMakeLists.txt
|
||||
├── include/led.h # публичный API — без NXP хедеров
|
||||
├── src/led.c # реализация, fsl_gpio.h только здесь
|
||||
└── README.md # этот файл
|
||||
```
|
||||
| Зависимость | Тип | Описание |
|
||||
| ------------ | ------- | -------------------------------------------- |
|
||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||
| `bsp_board` | PRIVATE | Транзитивно: `pin_mux.h`, clock, SDK headers |
|
||||
| `sdk_gpio` | PRIVATE | `fsl_gpio.h` — `GPIO_PinWrite/Read()` |
|
||||
|
|
|
|||
|
|
@ -1,75 +1,85 @@
|
|||
# bsp_opto — оптоизолированные входы
|
||||
# bsp_opto — оптоизолированные входы (PS2801-4)
|
||||
|
||||
Три оптоизолированных входа на базе PS2801-4. Два канала (`IN1`, `IN2`)
|
||||
предназначены для детектирования уровня с дебаунсом; третий (`RS`) —
|
||||
для захвата старт-бита бинарного протокола с минимальной задержкой.
|
||||
|
||||
---
|
||||
|
||||
## Аппаратура
|
||||
|
||||
| Канал | Пин MCU | GPIO | Схема |
|
||||
|--------------------|------------------|------------|--------------------------|
|
||||
| `BSP_OPTO_CH_IN1` | GPIO_AD_B1_06 | GPIO1[22] | PS2801-4 + 1K pull-up |
|
||||
| `BSP_OPTO_CH_IN2` | GPIO_AD_B1_05 | GPIO1[21] | PS2801-4 + 1K pull-up |
|
||||
| `BSP_OPTO_CH_RS` | GPIO_AD_B1_07 | GPIO1[23] | PS2801-4 + 1K pull-up |
|
||||
| Канал | Сигнал | Пин MCU | Корпус | GPIO | Логика |
|
||||
| ----------------- | ------ | ------------- | ------ | --------- | ----------- |
|
||||
| `BSP_OPTO_CH_IN1` | ExtIn1 | GPIO_AD_B1_06 | J12 | GPIO1[22] | active-HIGH |
|
||||
| `BSP_OPTO_CH_IN2` | ExtIn2 | GPIO_AD_B1_05 | K12 | GPIO1[21] | active-HIGH |
|
||||
| `BSP_OPTO_CH_RS` | RsRx | GPIO_AD_B1_07 | K10 | GPIO1[23] | active-HIGH |
|
||||
|
||||
**Логика:** active-HIGH. Оптопары неинвертирующие (PS2801-4):
|
||||
|
||||
- Пин 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).
|
||||
PS2801-4 неинвертирующие: пин HIGH (ток есть) → `BSP_OPTO_STATE_ACTIVE`.
|
||||
|
||||
Все три пина принадлежат 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[]` конфигурации.
|
||||
|
||||
### `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`-переменную, никакой бизнес-логики.
|
||||
Одновременное использование невозможно. `rs_as_gpio = true` в конфигурации
|
||||
активирует канал RS; `false` — пин остаётся под LPUART3.
|
||||
|
||||
---
|
||||
|
||||
## Использование
|
||||
## Архитектура
|
||||
|
||||
```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)
|
||||
|
||||
```c
|
||||
#include "bsp/opto.h"
|
||||
|
||||
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) {
|
||||
/* IN1 активирован */
|
||||
}
|
||||
if (ch == BSP_OPTO_CH_IN1 && state == BSP_OPTO_STATE_ACTIVE) { /* ... */ }
|
||||
}
|
||||
|
||||
bsp_opto_config_t cfg = {
|
||||
|
|
@ -81,31 +91,22 @@ bsp_opto_config_t cfg = {
|
|||
};
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
/* в main loop: */
|
||||
for (;;) {
|
||||
bsp_opto_process();
|
||||
}
|
||||
```
|
||||
|
||||
Полинг без коллбэков:
|
||||
|
||||
```c
|
||||
bsp_opto_state_t state = bsp_opto_read(BSP_OPTO_CH_IN1);
|
||||
```
|
||||
|
||||
### MODE_PROTO (RS) совместно с MODE_LEVEL (IN1, IN2)
|
||||
|
||||
```c
|
||||
/* Коллбэк вызывается из ISR — только атомарные операции */
|
||||
static volatile bool s_start_bit_detected = false;
|
||||
static volatile bool s_start_bit = false;
|
||||
|
||||
/* Вызывается из ISR — только volatile-запись */
|
||||
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 = {
|
||||
.callbacks = { on_level_change, on_level_change, on_rs_start_bit },
|
||||
.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);
|
||||
|
||||
/* в main loop: */
|
||||
for (;;) {
|
||||
bsp_opto_process(); /* обслуживает IN1, IN2 */
|
||||
bsp_opto_process();
|
||||
|
||||
if (s_start_bit_detected) {
|
||||
s_start_bit_detected = false;
|
||||
/* запустить декодер протокола по таймеру... */
|
||||
|
||||
/* по завершении приёма пакета — взвести для следующего старт-бита */
|
||||
bsp_opto_proto_arm(BSP_OPTO_CH_RS);
|
||||
if (s_start_bit) {
|
||||
s_start_bit = false;
|
||||
/* запустить декодер... */
|
||||
bsp_opto_proto_arm(BSP_OPTO_CH_RS); /* взвести для следующего старт-бита */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Совместное использование RS_RX
|
||||
## Тестирование
|
||||
|
||||
Пин GPIO_AD_B1_07 может работать в двух режимах:
|
||||
### Host unit-тесты
|
||||
|
||||
| Режим | BSP-модуль | pin_mux функция |
|
||||
|----------------|-----------------|-------------------------|
|
||||
| LPUART3 RX | `bsp_uart_rs` | `BOARD_InitRS_UART()` |
|
||||
| GPIO input | `bsp_opto` | `BOARD_InitRS_GPIO()` |
|
||||
Категория **B** — `bsp_opto.c` вызывает `fsl_gpio.h`. SDK-функции мокируются
|
||||
через fff. Stub `fsl_gpio.h` в `tests/host/mocks/`.
|
||||
|
||||
Одновременно использовать оба нельзя. В `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 RLY2–4.
|
||||
|
||||
```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()` |
|
||||
|
|
|
|||
|
|
@ -1,215 +1,153 @@
|
|||
# bsp_qspi_flash — QSPI Flash W25Q64/128/256/512
|
||||
|
||||
> Расположение: `bsp/qspi_flash/`
|
||||
> Публичный заголовок: `bsp/qspi_flash/include/bsp/qspi_flash.h`
|
||||
> Реализация: `bsp/qspi_flash/src/qspi_flash.c`
|
||||
Драйвер QSPI Flash на FlexSPI1 с поддержкой четырёх чипов Winbond.
|
||||
XIP-безопасен: все функции, трогающие FlexSPI IP-регистры, размещены в ITCM
|
||||
и выполняются под IRQ lock.
|
||||
|
||||
---
|
||||
|
||||
## Поддерживаемое железо
|
||||
## Аппаратура
|
||||
|
||||
| Чип | JEDEC mfr | JEDEC cap | Размер | LUT-таблица |
|
||||
|---------|-----------|-----------|--------|-------------|
|
||||
| W25Q64 | 0xEF | 0x17 | 8 MB | `K_LUT_3B` |
|
||||
| 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 |
|
||||
|
||||
Интерфейс 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-интерфейс.
|
||||
CPU непрерывно фетчит инструкции из Flash по AHB — через LUT-слот 0.
|
||||
Любая IP-команда FlexSPI блокирует AHB-путь на время выполнения.
|
||||
Если в этот момент CPU попытается фетчить инструкцию из Flash — **HardFault**.
|
||||
Прошивка исполняется XIP из Flash по AHB. Любая IP-команда FlexSPI блокирует
|
||||
AHB-путь — если в этот момент CPU фетчит инструкцию из Flash, происходит
|
||||
**HardFault**.
|
||||
|
||||
### Решение: ITCM + IRQ lock
|
||||
Решение — два уровня защиты:
|
||||
|
||||
Все функции, обращающиеся к регистрам FlexSPI, размещены в **ITCM**
|
||||
(`0x00000000`) через `AT_QUICKACCESS_SECTION_CODE`. ITCM подключён к CPU
|
||||
по выделенной шине (не AHB), поэтому фетч инструкций из ITCM не конкурирует
|
||||
с IP-командами FlexSPI.
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["публичная функция\nbsp_qspi_*(...)"] --> B["IRQ lock\n__get_PRIMASK + DSB + ISB"]
|
||||
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**
|
||||
(`qspi_irq_lock` / `qspi_irq_unlock` с `__get_PRIMASK()` + `DSB` + `ISB`):
|
||||
это гарантирует, что прерывание не застанет FlexSPI в середине IP-транзакции.
|
||||
IRQ unlock восстанавливает предыдущий `PRIMASK`, не включает IRQ безусловно —
|
||||
вызов из уже заблокированного контекста корректен.
|
||||
**ITCM** (`0x00000000`) подключён к CPU по выделенной шине — фетч инструкций
|
||||
не конкурирует с AHB. **IRQ lock** гарантирует, что прерывание не застанет
|
||||
FlexSPI в середине IP-транзакции; `PRIMASK` восстанавливается, не сбрасывается
|
||||
безусловно — вызов из уже заблокированного контекста корректен.
|
||||
|
||||
AHB prefetch отключается (`AHBCR.PREFETCHEN = 0`) перед серией IP-транзакций
|
||||
и восстанавливается после.
|
||||
|
||||
### Обязательный дефайн в CMakeLists потребителя
|
||||
**Обязательный дефайн в CMakeLists потребителя:**
|
||||
|
||||
```cmake
|
||||
target_compile_definitions(firmware_test PRIVATE
|
||||
__STARTUP_INITIALIZE_RAMFUNCTION # ← обязательно
|
||||
__STARTUP_CLEAR_BSS)
|
||||
__STARTUP_INITIALIZE_RAMFUNCTION # ← без этого ITCM содержит нули → HardFault
|
||||
__STARTUP_CLEAR_BSS
|
||||
)
|
||||
```
|
||||
|
||||
Без `__STARTUP_INITIALIZE_RAMFUNCTION` startup-файл NXP SDK не копирует
|
||||
`CodeQuickAccess` секцию из Flash в ITCM. В ITCM остаются нули. Первый же
|
||||
вызов любой ITCM-функции вызывает **HardFault**.
|
||||
### Адресация W25Q256/512
|
||||
|
||||
Вместо `Enter 4-Byte Mode (0xB7)` используются dedicated 4-byte opcodes — XIP-слот 0
|
||||
(24-bit адресация) не изменяется:
|
||||
|
||||
| Операция | W25Q64/128 | W25Q256/512 |
|
||||
| ---------------- | ---------- | ----------- |
|
||||
| Sector Erase 4KB | `0x20` | `0x21` |
|
||||
| Block Erase 32KB | `0x52` | `0x5C` |
|
||||
| Block Erase 64KB | `0xD8` | `0xDC` |
|
||||
| Quad Page Prog | `0x32` | `0x34` |
|
||||
| IP Quad Out Read | `0x6B` | `0x6C` |
|
||||
|
||||
---
|
||||
|
||||
## Стратегия адресации 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` |
|
||||
| Block Erase 32KB | `0x52` | `0x5C` |
|
||||
| Block Erase 64KB | `0xD8` | `0xDC` |
|
||||
| Quad Page Program | `0x32` | `0x34` |
|
||||
| IP Quad Out Read | `0x6B` | `0x6C` |
|
||||
|
||||
Слот 0 (XIP) **не изменяется**. XIP работает непрерывно на всех чипах.
|
||||
|
||||
---
|
||||
|
||||
## LUT-слоты
|
||||
|
||||
| Слот | Константа | Команда | Зависит от чипа |
|
||||
|------|----------------|-----------------------------|-----------------|
|
||||
| 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) | Да |
|
||||
|
||||
Слоты 1–11 обновляются в `bsp_qspi_init()` под конкретный чип.
|
||||
Слот 0 никогда не изменяется BSP-кодом.
|
||||
|
||||
---
|
||||
|
||||
## Watermark FIFO
|
||||
|
||||
Размер watermark-юнита читается из регистров `IPRXFCR.RXWMRK` и
|
||||
`IPTXFCR.TXWMRK` в рантайме — не зашит константой. Это гарантирует
|
||||
корректную работу если FDCB или SDK изменили настройки watermark по
|
||||
умолчанию.
|
||||
|
||||
---
|
||||
|
||||
## Публичный API
|
||||
|
||||
Все публичные функции размещены в ITCM (`AT_QUICKACCESS_SECTION_CODE`) и
|
||||
выполняются под IRQ lock.
|
||||
## API
|
||||
|
||||
```c
|
||||
/* Инициализация — вызвать до bsp_tick_init() и любой другой функции модуля */
|
||||
/* Инициализация — вызвать до bsp_tick_init() */
|
||||
bsp_status_t bsp_qspi_init(void);
|
||||
|
||||
/* Чтение JEDEC ID (0x9F) */
|
||||
/* Идентификация */
|
||||
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_block_32k(uint32_t addr); /* 32KB, ~120 мс */
|
||||
bsp_status_t bsp_qspi_erase_block_64k(uint32_t addr); /* 64KB, ~150 мс */
|
||||
bsp_status_t bsp_qspi_erase_sector(uint32_t addr); /* 4 KB, ~45 мс */
|
||||
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); /* 64 KB, ~150 мс */
|
||||
|
||||
/* Запись одной страницы (256 байт) */
|
||||
bsp_status_t bsp_qspi_write_page(uint32_t addr, const uint8_t *p_data); /* ~3 мс */
|
||||
/* Запись одной страницы (256 байт, адрес выровнен на BSP_QSPI_PAGE_SIZE) */
|
||||
bsp_status_t bsp_qspi_write_page(uint32_t addr, const uint8_t *p_data); /* ~3 мс */
|
||||
|
||||
/* Чтение через IP-команду (не AHB/XIP) */
|
||||
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` при ошибке
|
||||
FlexSPI / неверном аргументе. `BSP_ERR_HW` должен присутствовать в
|
||||
`bsp/common/include/bsp/status.h`.
|
||||
Все функции возвращают `BSP_OK` или `BSP_ERR_HW`.
|
||||
|
||||
**Выбор операции стирания:**
|
||||
|
||||
| Объём | Рекомендация | Время |
|
||||
| ------------ | ------------------ | -------------- |
|
||||
| < 32 KB | `erase_sector` 4KB | ~45 мс × N |
|
||||
| 32 KB – 1 MB | `erase_block_32k` | ~120 мс / 32KB |
|
||||
| > 1 MB | `erase_block_64k` | ~150 мс / 64KB |
|
||||
|
||||
Пример: 4 MB через 64KB = 64 × 150 мс ≈ **9.6 с** против 46 с через 4KB.
|
||||
|
||||
---
|
||||
|
||||
## Порядок инициализации
|
||||
|
||||
`bsp_qspi_init()` должна вызываться **до** `bsp_tick_init()`:
|
||||
## Быстрый старт
|
||||
|
||||
```c
|
||||
/* main.c */
|
||||
#include "bsp/qspi_flash.h"
|
||||
|
||||
/* main.c — порядок инициализации: */
|
||||
board_hw_init();
|
||||
bsp_qspi_init(); /* ← сначала QSPI, до SysTick */
|
||||
bsp_tick_init(); /* ← потом SysTick */
|
||||
bsp_usb_cdc_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));
|
||||
```
|
||||
|
||||
Причина: `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 |
|
||||
|
||||
Пример: 4 MB через 64KB = 64 × 150 мс ≈ **9.6 с**
|
||||
против 1024 × 45 мс ≈ **46 с** через 4KB.
|
||||
|
||||
---
|
||||
|
||||
## CMake
|
||||
|
||||
```cmake
|
||||
# bsp/qspi_flash/CMakeLists.txt
|
||||
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_link_libraries(firmware_test PRIVATE bsp_qspi_flash)
|
||||
|
||||
target_compile_definitions(firmware_test PRIVATE
|
||||
__STARTUP_INITIALIZE_RAMFUNCTION # обязательно для ITCM-функций
|
||||
__STARTUP_INITIALIZE_RAMFUNCTION
|
||||
__STARTUP_CLEAR_BSS
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
**Зависимости модуля:**
|
||||
|
||||
## Известные ограничения
|
||||
|
||||
- `bsp_qspi_write_page()` — строго одна страница (256 байт). Адрес обязан
|
||||
быть выровнен на `BSP_QSPI_PAGE_SIZE`. Запись через границу страницы не
|
||||
поддерживается.
|
||||
- Нет timeout в `qspi_wait_not_busy()`. Зависание из-за дефектного чипа
|
||||
потребует watchdog reset. Для диагностической прошивки это приемлемо.
|
||||
- Chip Erase (0xC7) не реализован — слишком деструктивно при XIP-исполнении.
|
||||
| Зависимость | Тип | Описание |
|
||||
| ------------- | ------- | ------------------------------------ |
|
||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||
| `bsp_board` | PRIVATE | Транзитивно: clock, SDK headers |
|
||||
| `sdk_flexspi` | PRIVATE | `fsl_flexspi.h` — FlexSPI IP-команды |
|
||||
|
|
|
|||
206
bsp/sd/README.md
206
bsp/sd/README.md
|
|
@ -1,130 +1,114 @@
|
|||
# bsp_sd — SD host-контроллер (USDHC1)
|
||||
|
||||
Модуль инициализирует SD host-контроллер и проверяет наличие карты.
|
||||
Файловой системой не занимается — это ответственность `bsp_usd` (поверх) или
|
||||
приложения напрямую.
|
||||
|
||||
## Место в архитектуре
|
||||
|
||||
Каждый слой знает только о слое ниже — зависимости не пересекают границы.
|
||||
|
||||
```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
|
||||
|
||||
```
|
||||
Инициализация SD host-контроллера и детект карты. Файловой системой не
|
||||
занимается — это ответственность слоя `bsp_usd` / `port_fatfs_sd` поверх.
|
||||
|
||||
---
|
||||
|
||||
## Аппаратный контекст
|
||||
## Аппаратура
|
||||
|
||||
| Сигнал | Пин MCU | Конфигурация |
|
||||
| ------ | ---------------- | ------------------------------------------------ |
|
||||
| CLK | GPIO_SD_B0_01 | USDHC1_CLK, периферийный режим |
|
||||
| CMD | GPIO_SD_B0_00 | USDHC1_CMD, периферийный режим |
|
||||
| D0–D3 | GPIO_SD_B0_02–05 | USDHC1_DATA0–3, периферийный режим |
|
||||
| CD_B | GPIO_B1_12 | USDHC1_CD_B — детект через GPIO2[28] |
|
||||
| SdPwr | GPIO_AD_B1_03 | GPIO1[19], active-low, управляется SDK через BSP |
|
||||
| Сигнал | Пин MCU | Корпус | Конфигурация |
|
||||
| ------ | ------------- | ------ | --------------------------------------------- |
|
||||
| CLK | GPIO_SD_B0_01 | J3 | USDHC1_CLK, периферийный режим |
|
||||
| CMD | GPIO_SD_B0_00 | J4 | USDHC1_CMD, периферийный режим |
|
||||
| D0 | GPIO_SD_B0_02 | J1 | USDHC1_DATA0, периферийный режим |
|
||||
| D1 | GPIO_SD_B0_03 | K1 | USDHC1_DATA1, периферийный режим |
|
||||
| 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. Детект карты
|
||||
читается через `USDHC_GetPresentStatusFlags` → `kUSDHC_CardInsertedFlag`.
|
||||
GPIO-прерывание на CD не используется (`kSD_DetectCardByHostCD`).
|
||||
**CD_B** подключён как периферийный сигнал USDHC1 — детект читается через
|
||||
`USDHC_GetPresentStatusFlags` → `kUSDHC_CardInsertedFlag`. GPIO-прерывание
|
||||
не используется (`kSD_DetectCardByHostCD`).
|
||||
|
||||
**SdPwr** инициализируется в `BOARD_SD_Config()` как GPIO-выход, выключен при старте.
|
||||
SDK включает питание автоматически в процессе `SD_HostInit()` через callback.
|
||||
**SdPwr** инициализируется в `BOARD_SD_Config()` как GPIO-выход, выключен
|
||||
при старте. 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
|
||||
|
||||
### `bsp_sd_init(void)`
|
||||
|
||||
Конфигурирует SDMMC host однократно (`BOARD_SD_Config`) и запускает
|
||||
host-контроллер (`SD_HostInit`).
|
||||
|
||||
Повторный вызов без `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
|
||||
)
|
||||
```c
|
||||
bsp_status_t bsp_sd_init(void);
|
||||
bsp_status_t bsp_sd_deinit(void);
|
||||
bool bsp_sd_is_inserted(void);
|
||||
```
|
||||
|
||||
`sdk_sdmmc_sd` — транзитивно через `bsp_sdmmc_config`.
|
||||
`sdk_usdhc` — транзитивно через `sdk_sdmmc_sd`.
|
||||
**`bsp_sd_init()`** — конфигурирует SDMMC host однократно (`BOARD_SD_Config`)
|
||||
и запускает 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` — единственный дескриптор).
|
||||
- `bsp_sd_is_inserted()` читает аппаратный регистр без дебаунса. При
|
||||
механическом детекте возможны ложные срабатывания в момент вставки/извлечения —
|
||||
добавляй дебаунс в вызывающем коде если нужно.
|
||||
- Hot-swap не поддерживается: `bsp_sd_deinit()` + `bsp_sd_init()` между сессиями.
|
||||
```c
|
||||
#include "bsp/sd.h"
|
||||
|
||||
if (!bsp_sd_is_inserted()) {
|
||||
/* карта отсутствует */
|
||||
}
|
||||
|
||||
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`.
|
||||
|
|
|
|||
|
|
@ -1,228 +1,105 @@
|
|||
# bsp_sdram — внешняя SDRAM MT48LC16M16A2 (32 МБ)
|
||||
|
||||
> Расположение: `bsp/sdram/`
|
||||
> Публичный заголовок: `bsp/sdram/include/bsp/sdram.h`
|
||||
> Реализация: `bsp/sdram/src/sdram.c`
|
||||
|
||||
Модуль обеспечивает минимальную верификацию доступности внешней SDRAM,
|
||||
подключённой к SEMC. Подробное тестирование (паттерны, шина адреса/данных,
|
||||
retention) выполняется не здесь, а в тест-модуле `firmware_test/test_sdram.c`,
|
||||
который опирается на константы и API этого модуля.
|
||||
Минимальная верификация доступности внешней SDRAM, подключённой к SEMC.
|
||||
Подробное тестирование (паттерны, шина адреса/данных, retention) выполняется
|
||||
в тест-модуле `firmware_test/test_sdram.c`, который использует константы
|
||||
и API этого модуля.
|
||||
|
||||
---
|
||||
|
||||
## Аппаратный контекст
|
||||
## Аппаратура
|
||||
|
||||
| Параметр | Значение |
|
||||
| --------------------- | ------------------------------------------------ |
|
||||
| Чип | MT48LC16M16A2 |
|
||||
| Объём | 32 МБ |
|
||||
| Ширина шины данных | 16 бит |
|
||||
| Интерфейс MCU | SEMC, регион BR0 |
|
||||
| Базовый адрес | `0x80000000` (`BSP_SDRAM_BASE_ADDR`) |
|
||||
| Конец региона | `0x81FFFFFF` (`+ BSP_SDRAM_SIZE_BYTES = 32 МБ`) |
|
||||
| Параметр | Значение |
|
||||
| ------------------ | ------------------------------------ |
|
||||
| Чип | MT48LC16M16A2 |
|
||||
| Объём | 32 МБ |
|
||||
| Ширина шины данных | 16 бит |
|
||||
| Интерфейс MCU | SEMC, регион BR0 |
|
||||
| Базовый адрес | `0x80000000` (`BSP_SDRAM_BASE_ADDR`) |
|
||||
|
||||
Карта тестового региона (из `sdram.h`):
|
||||
**Карта тестового региона:**
|
||||
|
||||
| Адрес | Назначение |
|
||||
| ------------ | ---------------------------------------------------------- |
|
||||
| `0x80000000` | Начало SDRAM (SEMC BR0) |
|
||||
| `0x80200000` | `BSP_SDRAM_TEST_BASE_ADDR` — база тестового региона |
|
||||
| `0x81E00000` | Начало non-cacheable региона (USB DMA, 2 MB) |
|
||||
| `0x81FFFFFF` | Конец SDRAM |
|
||||
| Адрес | Назначение |
|
||||
| ------------ | --------------------------------------------------- |
|
||||
| `0x80000000` | Начало SDRAM (SEMC BR0) |
|
||||
| `0x80200000` | `BSP_SDRAM_TEST_BASE_ADDR` — база тестового региона |
|
||||
| `0x81E00000` | Начало non-cacheable региона (USB DMA, 2 MB) |
|
||||
| `0x81FFFFFF` | Конец SDRAM |
|
||||
|
||||
Тестовая база смещена на 2 МБ от начала SDRAM, что согласно комментариям в
|
||||
заголовке гарантированно выше `.data`/`.bss` прошивки и ниже non-cacheable
|
||||
региона.
|
||||
Тестовая база смещена на 2 МБ от начала — гарантированно выше `.data`/`.bss`
|
||||
прошивки и ниже non-cacheable региона.
|
||||
|
||||
**Важно:** SEMC инициализируется через DCD **до вызова `main()`**. Этот модуль
|
||||
не настраивает SEMC и не трогает его регистры. Если DCD не отработал —
|
||||
`bsp_sdram_init()` вернёт ошибку, но исправить ситуацию из модуля нельзя.
|
||||
|
||||
---
|
||||
|
||||
## Архитектурное ограничение: SEMC инициализируется DCD до `main()`
|
||||
|
||||
Модуль **не настраивает** контроллер 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
|
||||
## API
|
||||
|
||||
```c
|
||||
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`,
|
||||
таймаут — `SDRAM_SEMC_IDLE_TIMEOUT_MS = 10 мс` (через `bsp_tick_get_ms()`).
|
||||
2. Записывает по `BSP_SDRAM_TEST_BASE_ADDR` паттерн `0xA5A5A5A5`,
|
||||
Константы размеров — для потребителей; `bsp_sdram` не запускает по ним
|
||||
внутренних проходов.
|
||||
|
||||
**Поведение `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()`,
|
||||
читает обратно и сверяет.
|
||||
3. Повторяет то же для инверсного паттерна `0x5A5A5A5A`.
|
||||
4. При успехе устанавливает внутренний флаг готовности и возвращает `BSP_OK`.
|
||||
3. Повторяет для инверсного паттерна `0x5A5A5A5A`.
|
||||
|
||||
Коды возврата:
|
||||
**Коды возврата:**
|
||||
|
||||
| Код | Когда |
|
||||
| ----------------- | --------------------------------------------------------------------- |
|
||||
| `BSP_OK` | SDRAM доступна, оба паттерна успешно прочитаны обратно. |
|
||||
| `BSP_ERR_TIMEOUT` | SEMC не перешёл в IDLE за `SDRAM_SEMC_IDLE_TIMEOUT_MS` (10 мс). |
|
||||
| `BSP_ERR_INIT` | Хотя бы один паттерн не совпал при readback (DCD/SDRAM не готовы). |
|
||||
|
||||
`bsp_sdram_init()` затрагивает только 4 байта по адресу
|
||||
`BSP_SDRAM_TEST_BASE_ADDR` (две записи 32-битных слов) и не пересекается с
|
||||
`.data`/`.bss` прошивки благодаря смещению на 2 МБ от базы.
|
||||
| Код | Условие |
|
||||
| ----------------- | ------------------------------------------- |
|
||||
| `BSP_OK` | SDRAM доступна, оба паттерна совпали |
|
||||
| `BSP_ERR_TIMEOUT` | SEMC не перешёл в IDLE за 10 мс |
|
||||
| `BSP_ERR_INIT` | Хотя бы один паттерн не совпал при readback |
|
||||
|
||||
---
|
||||
|
||||
## Порядок использования
|
||||
## Быстрый старт
|
||||
|
||||
```c
|
||||
#include "bsp/sdram.h"
|
||||
|
||||
if (bsp_sdram_init() != BSP_OK) {
|
||||
/* SEMC/SDRAM недоступны — это критическая ошибка для прошивки,
|
||||
которая использует SDRAM под фреймбуферы и тестовые регионы. */
|
||||
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
|
||||
# bsp/sdram/CMakeLists.txt
|
||||
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)
|
||||
target_link_libraries(firmware_test PRIVATE bsp_sdram)
|
||||
```
|
||||
|
||||
- `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()`.
|
||||
|
||||
Потребитель (пример из `firmware/test/CMakeLists.txt`):
|
||||
|
||||
```cmake
|
||||
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`.
|
||||
| Зависимость | Тип | Описание |
|
||||
| ------------ | ------- | -------------------------------------------------- |
|
||||
| `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-уровневые символы |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
target_link_libraries(firmware_test PRIVATE bsp_tick)
|
||||
```
|
||||
|
||||
**Порядок инициализации в main()**:
|
||||
|
||||
```c
|
||||
board_hw_init(); // тактирование и пины (BOARD_BootClockRUN внутри)
|
||||
bsp_tick_init(); // SysTick — после того как SystemCoreClock актуален
|
||||
bsp_uart_init(115200); // и далее всё что зависит от времени
|
||||
/* main.c — порядок инициализации: */
|
||||
board_hw_init(); /* устанавливает SystemCoreClock */
|
||||
bsp_tick_init(); /* после board_hw_init() */
|
||||
```
|
||||
|
||||
---
|
||||
### FreeRTOS (`firmware/tft_app`)
|
||||
|
||||
## firmware/tft_app (FreeRTOS)
|
||||
|
||||
**FreeRTOSConfig.h** — убедиться:
|
||||
В FreeRTOS-режиме SysTick захватывается планировщиком. `bsp_tick_init()` —
|
||||
no-op, `bsp_tick_inc()` вызывается из `vApplicationTickHook`.
|
||||
|
||||
```c
|
||||
#define configTICK_RATE_HZ 1000 // 1 тик = 1 мс
|
||||
#define configUSE_TICK_HOOK 1 // включить vApplicationTickHook
|
||||
/* FreeRTOSConfig.h */
|
||||
#define configTICK_RATE_HZ 1000
|
||||
#define configUSE_TICK_HOOK 1
|
||||
```
|
||||
|
||||
**board.c** — добавить hook (см. раздел про хуки ниже):
|
||||
|
||||
```c
|
||||
/* board.c */
|
||||
#include "bsp/tick.h"
|
||||
|
||||
void vApplicationTickHook(void)
|
||||
{
|
||||
bsp_tick_inc(); // no-op в FreeRTOS-режиме, но оставляем для единообразия
|
||||
bsp_tick_inc();
|
||||
}
|
||||
```
|
||||
|
||||
**CMakeLists.txt**:
|
||||
|
||||
```cmake
|
||||
target_link_libraries(tft_app PRIVATE bsp_tick freertos_kernel)
|
||||
target_compile_definitions(tft_app PRIVATE BSP_TICK_FREERTOS_MODE)
|
||||
```
|
||||
|
||||
**Инициализация**:
|
||||
### Добавление логики в SysTick — weak hook
|
||||
|
||||
`tick.c` объявляет `bsp_systick_hook()` с атрибутом `weak`. Определи
|
||||
эту функцию в любом `.c` файле проекта — линкер подхватит автоматически.
|
||||
Никаких изменений в `tick.c` не требуется.
|
||||
|
||||
```c
|
||||
board_hw_init();
|
||||
bsp_tick_init(); // no-op, но вызываем для симметрии с bare-metal
|
||||
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 по умолчанию */ }
|
||||
/* tick.c (уже реализовано): */
|
||||
__attribute__((weak)) void bsp_systick_hook(void) {}
|
||||
|
||||
void SysTick_Handler(void)
|
||||
{
|
||||
|
|
@ -85,54 +107,27 @@ void SysTick_Handler(void)
|
|||
}
|
||||
```
|
||||
|
||||
### **Пример: watchdog из board.c**
|
||||
|
||||
```c
|
||||
// board.c
|
||||
#include "bsp/tick.h"
|
||||
#include "bsp/wdog.h"
|
||||
|
||||
// Переопределяем слабый хук — линкер возьмёт эту версию
|
||||
/* board.c — пример: watchdog из хука */
|
||||
void bsp_systick_hook(void)
|
||||
{
|
||||
bsp_wdog_feed();
|
||||
}
|
||||
```
|
||||
|
||||
### **Пример: два действия в хуке**
|
||||
|
||||
```c
|
||||
// board.c
|
||||
void bsp_systick_hook(void)
|
||||
{
|
||||
bsp_wdog_feed();
|
||||
bsp_some_other_periodic_task();
|
||||
}
|
||||
```
|
||||
|
||||
**Важно:** хук вызывается из ISR-контекста. Никаких блокирующих
|
||||
операций, мьютексов или `bsp_delay()` внутри.
|
||||
**Важно:** хук вызывается из ISR-контекста. Блокирующие операции, мьютексы
|
||||
и `bsp_delay()` внутри запрещены.
|
||||
|
||||
---
|
||||
|
||||
## Использование в BSP-модулях и приложении
|
||||
## CMake
|
||||
|
||||
```c
|
||||
#include "bsp/tick.h"
|
||||
|
||||
// Таймаут (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();
|
||||
// ... действие ...
|
||||
}
|
||||
```cmake
|
||||
target_link_libraries(firmware_test PRIVATE bsp_tick)
|
||||
```
|
||||
|
||||
**Зависимости модуля:**
|
||||
|
||||
| Зависимость | Тип | Описание |
|
||||
| ----------- | ------ | ------------------------------ |
|
||||
| `bsp_board` | PUBLIC | Транзитивно: `SystemCoreClock` |
|
||||
|
|
|
|||
|
|
@ -1,109 +1,153 @@
|
|||
# bsp_uart_host
|
||||
# bsp_uart_host — LPUART1 (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
|
||||
[LPUART1 RX] → LPUART1_IRQHandler → ring_buffer_put()
|
||||
↓
|
||||
bsp_uart_host_read() ← polling + таймаут
|
||||
bsp_uart_host_read_byte()
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph TX
|
||||
A["bsp_uart_host_write()"] --> B["LPUART_WriteBlocking()\nблокирующий polling"]
|
||||
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`). Пакеты короткие, задержка 1–2 мс приемлема.
|
||||
- **RX** — ISR пишет в ring buffer, задача/main читает с таймаутом.
|
||||
- **ISR** — `LPUART1_IRQHandler` определён в модуле, модуль владеет прерыванием целиком.
|
||||
- **TX** — `LPUART_WriteBlocking`. Пакеты короткие, задержка 1–2 мс приемлема.
|
||||
- **RX** — ISR пишет в ring buffer; main loop читает с таймаутом.
|
||||
- **ISR** — `LPUART1_IRQHandler` определён в модуле, владеет прерыванием целиком.
|
||||
- **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
|
||||
#include "bsp/uart_host.h"
|
||||
|
||||
// В main(), после board_hw_init():
|
||||
/* После board_hw_init(): */
|
||||
bsp_uart_host_init(115200);
|
||||
|
||||
// TX
|
||||
/* TX */
|
||||
bsp_uart_host_write_str("hello\r\n");
|
||||
|
||||
// RX — ждать байт до 100 мс
|
||||
/* RX — ждать байт до 100 мс */
|
||||
int32_t byte = bsp_uart_host_read_byte(100);
|
||||
if (byte < 0) { /* таймаут */ }
|
||||
|
||||
// RX — прочитать пакет целиком
|
||||
/* RX — прочитать пакет */
|
||||
uint8_t buf[64];
|
||||
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
|
||||
target_compile_definitions(firmware_test PRIVATE
|
||||
BSP_UART_HOST_RX_BUFFER_SIZE=256 # степень двойки, дефолт 256
|
||||
BSP_UART_HOST_SRC_CLOCK_HZ=24000000
|
||||
BSP_UART_HOST_IRQ_PRIORITY=5
|
||||
add_host_test(
|
||||
NAME uart_host_mock_example
|
||||
SOURCES uart_host/test_uart_host.c
|
||||
${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
|
||||
// Без ожидания — вернёт только то, что уже есть в буфере
|
||||
bsp_uart_host_read(buf, len, 0);
|
||||
#include "bsp/uart_host_mock.h"
|
||||
|
||||
// Ждать с таймаутом (межбайтовый: сбрасывается после каждого принятого байта)
|
||||
bsp_uart_host_read(buf, len, 100);
|
||||
void setUp(void) { UART_HOST_MOCK_RESET_ALL(); }
|
||||
|
||||
// Ждать вечно
|
||||
bsp_uart_host_read(buf, len, BSP_UART_HOST_WAIT_FOREVER);
|
||||
void test_something(void) {
|
||||
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` — частичное чтение при таймауте
|
||||
не является ошибкой, caller сам решает что делать с полученным количеством байт.
|
||||
### HIL-тесты
|
||||
|
||||
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 без отдельной реализации. При сборке с
|
||||
`BSP_TICK_FREERTOS_MODE` в цикле ожидания добавляется `vTaskDelay(1)` —
|
||||
задача отдаёт управление планировщику вместо busy-wait.
|
||||
| Контекст | TX | RX |
|
||||
| ---------- | --------------------- | ------------------------------ |
|
||||
| bare-metal | `write()` — blocking | `read()` — polling с таймаутом |
|
||||
| FreeRTOS | `write()` — из задачи | `read()` — из задачи с yield |
|
||||
|
||||
`BSP_UART_HOST_IRQ_PRIORITY` должен быть установлен ниже
|
||||
`configMAX_SYSCALL_INTERRUPT_PRIORITY` (числовое значение выше).
|
||||
В FreeRTOS-режиме (`BSP_TICK_FREERTOS_MODE`) цикл ожидания добавляет
|
||||
`vTaskDelay(1)` вместо busy-wait. `BSP_UART_HOST_IRQ_PRIORITY` должен быть
|
||||
выше `configMAX_SYSCALL_INTERRUPT_PRIORITY` (числовое значение ниже).
|
||||
|
||||
---
|
||||
|
||||
## Подключение
|
||||
## CMake
|
||||
|
||||
```cmake
|
||||
# bsp/CMakeLists.txt
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(uart_host)
|
||||
|
||||
# firmware/test/CMakeLists.txt
|
||||
target_link_libraries(firmware_test PRIVATE
|
||||
bsp_board
|
||||
bsp_tick
|
||||
|
|
@ -111,50 +155,17 @@ target_link_libraries(firmware_test PRIVATE
|
|||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Тестирование
|
||||
|
||||
Для host unit-тестов модуль предоставляет fff-заглушки через **Humble Object**:
|
||||
в тестовой сборке вместо `uart_host.c` линкуется `mocks/uart_host_mock.c`.
|
||||
Конфигурация задаётся в CMakeLists.txt **потребителя**, не модуля:
|
||||
|
||||
```cmake
|
||||
# tests/host/CMakeLists.txt
|
||||
add_host_test(
|
||||
NAME
|
||||
uart_host_mock_example
|
||||
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)
|
||||
target_compile_definitions(firmware_test PRIVATE
|
||||
BSP_UART_HOST_RX_BUFFER_SIZE=256 # степень двойки
|
||||
BSP_UART_HOST_SRC_CLOCK_HZ=24000000
|
||||
BSP_UART_HOST_IRQ_PRIORITY=5
|
||||
)
|
||||
```
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Зависимости
|
||||
**Зависимости модуля:**
|
||||
|
||||
| Зависимость | Тип | Описание |
|
||||
| --------------------- | ------- | --------------------------------- |
|
||||
|
|
|
|||
|
|
@ -1,123 +1,85 @@
|
|||
# bsp_usb_cdc — USB CDC ACM (Virtual COM Port)
|
||||
|
||||
USB CDC ACM device на USB1 (EHCI0). Хост видит устройство как виртуальный COM-порт
|
||||
(`/dev/ttyACM*` на Linux/macOS, `COMx` на Windows).
|
||||
|
||||
Используется для передачи данных между платой и ПК: отладочные лог-каналы,
|
||||
CLI команды, обновление конфигурации. Работает параллельно с `bsp_uart_host`
|
||||
(LPUART1) — два независимых канала.
|
||||
USB CDC ACM device на USB1 (EHCI0). Хост видит устройство как виртуальный
|
||||
COM-порт (`/dev/ttyACM*` на Linux/macOS, `COMx` на Windows). Используется
|
||||
для передачи данных между платой и ПК: CLI команды, отладочные лог-каналы.
|
||||
Работает параллельно с `bsp_uart_host` (LPUART1) — два независимых канала.
|
||||
|
||||
---
|
||||
|
||||
## Аппаратура
|
||||
|
||||
| Сигнал | Пин MCU | Назначение |
|
||||
| ------------- | ------------- | -------------------------- |
|
||||
| USB_OTG1_DN | USB_OTG1_DN | USB1 Data− |
|
||||
| USB_OTG1_DP | USB_OTG1_DP | USB1 Data+ |
|
||||
| USB_OTG1_VBUS | USB_OTG1_VBUS | VBUS detect (self-powered) |
|
||||
| Сигнал | Пин MCU | Назначение |
|
||||
| ------------- | ------------- | ----------- |
|
||||
| USB_OTG1_DN | USB_OTG1_DN | USB1 Data− |
|
||||
| USB_OTG1_DP | USB_OTG1_DP | USB1 Data+ |
|
||||
| USB_OTG1_VBUS | USB_OTG1_VBUS | VBUS detect |
|
||||
|
||||
Встроенный 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` — стандартные
|
||||
значения для EVKB, подходят для кабелей до 3 м.
|
||||
|
||||
**VID/PID**: `0x1234` / `0x0001` (placeholder, заменить на производственные).
|
||||
**VID/PID**: `0x1234` / `0x0001` — placeholder, заменить на производственные.
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
```bash
|
||||
bsp_usb_cdc_write()
|
||||
↓
|
||||
memcpy → s_sendBuf (NonCacheable OCRAM)
|
||||
↓
|
||||
USB_DeviceSendRequest()
|
||||
↓
|
||||
[EHCI0 DMA] → USB1_DP/DN → Host
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph TX
|
||||
A["bsp_usb_cdc_write()"] --> B["memcpy → s_sendBuf\n(NonCacheable OCRAM)"]
|
||||
B --> C["USB_DeviceSendRequest()"]
|
||||
C --> D["EHCI0 DMA → USB1_DP/DN → Host"]
|
||||
D --> E["BulkIn callback\ns_txIdle = 1 (volatile)"]
|
||||
end
|
||||
|
||||
Host → USB1_DP/DN → [EHCI0 DMA]
|
||||
↓
|
||||
USB_OTG1_IRQHandler → BulkOut callback
|
||||
↓
|
||||
s_recvBuf (NonCacheable OCRAM)
|
||||
↓
|
||||
s_recvSize = len (volatile)
|
||||
↓
|
||||
bsp_usb_cdc_read() ← main loop polling
|
||||
subgraph RX
|
||||
F["Host → USB1_DP/DN → EHCI0 DMA"] --> G["USB_OTG1_IRQHandler\nBulkOut callback"]
|
||||
G --> H["s_recvBuf (NonCacheable OCRAM)\ns_recvSize = len (volatile)"]
|
||||
H --> I["bsp_usb_cdc_read()\nmain loop polling"]
|
||||
end
|
||||
```
|
||||
|
||||
Все DMA-буферы (`s_sendBuf`, `s_recvBuf`, дескрипторы) размещены в секции
|
||||
`NonCacheable` (OCRAM `0x20200000`). MPU region 9 настраивает эту область
|
||||
как Normal non-cacheable — записи CPU видны DMA без `SCB_CleanDCache()`.
|
||||
Все DMA-буферы размещены в секции `NonCacheable` (OCRAM `0x20200000`).
|
||||
MPU region 9 настраивает эту область как Normal non-cacheable — записи CPU
|
||||
видны DMA без `SCB_CleanDCache()`.
|
||||
|
||||
**Lite stack** — сознательное решение вместо full NXP class framework:
|
||||
|
||||
| Аспект | Full stack | Lite stack (наш выбор) |
|
||||
| --------------- | --------------------- | ---------------------- |
|
||||
| Class framework | `usb_device_class.h` | Отсутствует |
|
||||
| Размер кода | ~12 KB | ~6 KB |
|
||||
| Гибкость | Multi-class composite | Один CDC ACM |
|
||||
|
||||
Переход на full stack понадобится только при добавлении composite device (CDC + MSC).
|
||||
|
||||
---
|
||||
|
||||
## USB стек — lite архитектура
|
||||
## API
|
||||
|
||||
Модуль использует **lite** вариант NXP USB стека (не full class framework).
|
||||
Это сознательное решение:
|
||||
```c
|
||||
bsp_status_t bsp_usb_cdc_init(void);
|
||||
bool bsp_usb_cdc_is_ready(void);
|
||||
bool bsp_usb_cdc_write_ready(void);
|
||||
|
||||
| Аспект | Full stack | Lite stack (наш выбор) |
|
||||
| ------------------ | -------------------------------------- | ----------------------------- |
|
||||
| Class framework | `usb_device_class.h`, `class_handle_t` | Отсутствует |
|
||||
| `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 |
|
||||
| Гибкость | Multi-class composite | Один CDC ACM |
|
||||
|
||||
Lite stack достаточен для одного CDC ACM интерфейса. Переход на full stack
|
||||
понадобится только при добавлении composite device (CDC + MSC).
|
||||
|
||||
### Стек зависимостей
|
||||
|
||||
```bash
|
||||
bsp_usb_cdc
|
||||
├── src/usb_cdc.c ← BSP API + USB device callbacks
|
||||
├── src/usb_cdc_descriptors.c ← дескрипторы + descriptor callbacks
|
||||
├── src/usb_cdc_hw.c ← clock, PHY, IRQ handler
|
||||
├── src/usb_device_ch9.c ← lite Chapter 9 (приватная копия)
|
||||
│
|
||||
├── SDK (PRIVATE):
|
||||
│ ├── sdk_usb_device_ehci ← EHCI контроллер + DCI абстракция
|
||||
│ │ ├── usb_device_ehci.c
|
||||
│ │ └── 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
|
||||
bsp_status_t bsp_usb_cdc_write(const uint8_t *p_data, size_t len);
|
||||
size_t bsp_usb_cdc_read(uint8_t *p_buf, size_t max_len);
|
||||
void bsp_usb_cdc_poll(void); /* зарезервировано */
|
||||
```
|
||||
|
||||
### Проброс конфиг-хедеров (sdk_usb_config)
|
||||
**`bsp_usb_cdc_is_ready()`** — `true` когда enumeration завершён **и** хост
|
||||
открыл COM-порт (DTR установлен через `SET_CONTROL_LINE_STATE`).
|
||||
|
||||
NXP USB middleware при компиляции ищет `usb_device_config.h` и
|
||||
`fsl_os_abstraction_config.h` через include path. Эти файлы —
|
||||
application-specific, живут в `bsp/usb_cdc/src/`.
|
||||
**Коды возврата `bsp_usb_cdc_write()`:**
|
||||
|
||||
Проблема: SDK таргеты (`sdk_usb_device_ehci`, `sdk_usb_phy`, `sdk_osa_bm`)
|
||||
компилируются независимо от `bsp_usb_cdc` и не видят его include paths.
|
||||
|
||||
Решение: INTERFACE библиотека `sdk_usb_config` в `sdk/CMakeLists.txt`:
|
||||
|
||||
```cmake
|
||||
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` не содержит кода.
|
||||
| Код | Условие |
|
||||
| ------------------- | ------------------------------------------ |
|
||||
| `BSP_OK` | Transfer поставлен в очередь |
|
||||
| `BSP_ERR_BUSY` | Предыдущий transfer не завершён |
|
||||
| `BSP_ERR_NOT_READY` | Хост не подключён |
|
||||
| `BSP_ERR_INVALID` | `data == NULL`, `len == 0` или `len > 512` |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -129,152 +91,16 @@ target_include_directories(sdk_usb_config SYSTEM
|
|||
/* После board_hw_init() + bsp_tick_init(): */
|
||||
bsp_usb_cdc_init();
|
||||
|
||||
/* Ждём подключения хоста */
|
||||
while (!bsp_usb_cdc_is_ready()) {
|
||||
/* USB enumeration в процессе */
|
||||
}
|
||||
while (!bsp_usb_cdc_is_ready()) { /* ждём enumeration */ }
|
||||
|
||||
/* 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));
|
||||
|
||||
/* RX — polling в main loop */
|
||||
uint8_t buf[64];
|
||||
size_t n = bsp_usb_cdc_read(buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
/* обработать 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
|
||||
)
|
||||
if (n > 0) { /* обработать buf[0..n-1] */ }
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -283,70 +109,46 @@ target_link_libraries(firmware_test PRIVATE
|
|||
|
||||
### HIL-тест
|
||||
|
||||
USB CDC появляется как второй COM-порт на хосте (помимо MCU-Link VCOM).
|
||||
C-прошивка `tests/target/hil_usb_cdc/` — CLI через USB CDC.
|
||||
pytest: `tools/hil/test_usb_cdc.py` — отправка/приём через pyserial.
|
||||
|
||||
Переменная окружения `HIL_USB_CDC_PORT` — порт USB CDC устройства таргета.
|
||||
USB CDC появляется как второй COM-порт (помимо MCU-Link VCOM).
|
||||
C-прошивка: `tests/target/hil_usb_cdc/` — CLI через USB CDC.
|
||||
pytest: `tools/hil/05_test_usb_cdc.py` — через pyserial (`HIL_USB_CDC_PORT`).
|
||||
|
||||
```bash
|
||||
just host::hil-usb-cdc
|
||||
```
|
||||
|
||||
Команды CLI прошивки:
|
||||
| Команда | Ответ | Описание |
|
||||
| ------------- | -------- | --------------- |
|
||||
| `PING` | `PONG` | Проверка канала |
|
||||
| `ECHO <data>` | `<data>` | Echo-back |
|
||||
|
||||
| Команда | Ответ | Описание |
|
||||
| ------------- | -------- | ---------------- |
|
||||
| `PING` | `PONG` | Проверка канала |
|
||||
| `ECHO <data>` | `<data>` | Echo-back данных |
|
||||
|
||||
### Host unit-тесты
|
||||
|
||||
Не применяются — модуль полностью завязан на USB hardware и NXP middleware.
|
||||
Тестирование только через HIL.
|
||||
Host unit-тесты не применяются — модуль полностью завязан на USB hardware.
|
||||
|
||||
---
|
||||
|
||||
## Конфигурация
|
||||
## Интеграция
|
||||
|
||||
Все настройки находятся в приватных хедерах `src/`:
|
||||
| Контекст | TX | RX |
|
||||
| ---------- | -------------------------------------------- | ------------------------------ |
|
||||
| bare-metal | `write()` — non-blocking | `read()` — polling в main loop |
|
||||
| FreeRTOS | `write_ready()` + `write()` + `vTaskDelay()` | `read()` из задачи с yield |
|
||||
|
||||
| Файл | Настройка | Значение | Описание |
|
||||
| ------------------------- | ------------------------------- | -------- | -------------------------------- |
|
||||
| `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 калибровка |
|
||||
`USB_DEVICE_INTERRUPT_PRIORITY = 3` должен быть ниже
|
||||
`configMAX_SYSCALL_INTERRUPT_PRIORITY` при использовании FreeRTOS API из ISR.
|
||||
|
||||
---
|
||||
|
||||
## Файловая структура
|
||||
## CMake
|
||||
|
||||
```bash
|
||||
bsp/usb_cdc/
|
||||
├── CMakeLists.txt
|
||||
├── README.md
|
||||
├── include/
|
||||
│ └── 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 конфиг
|
||||
```cmake
|
||||
target_link_libraries(firmware_test PRIVATE
|
||||
bsp_board
|
||||
bsp_tick
|
||||
bsp_usb_cdc
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Зависимости
|
||||
**Зависимости модуля:**
|
||||
|
||||
| Зависимость | Тип | Описание |
|
||||
| --------------------- | --------------------- | ------------------------------------------------------- |
|
||||
|
|
@ -354,19 +156,5 @@ bsp/usb_cdc/
|
|||
| `bsp_board` | PRIVATE | Транзитивно: `clock_config.h`, `pin_mux.h`, SDK headers |
|
||||
| `sdk_usb_device_ehci` | PRIVATE | EHCI контроллер + DCI абстракция |
|
||||
| `sdk_usb_phy` | PRIVATE | USB PHY инициализация (480 MHz PLL) |
|
||||
| `sdk_osa_bm` | PRIVATE | OS Abstraction Layer (bare-metal, generic list) |
|
||||
| `sdk_usb_common` | PRIVATE (транзитивно) | USB common headers (`usb.h`, `usb_misc.h`) |
|
||||
| `sdk_osa_bm` | PRIVATE | OS Abstraction Layer (bare-metal) |
|
||||
| `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`.
|
||||
|
|
|
|||
271
docs/DEV_ARCH.md
271
docs/DEV_ARCH.md
|
|
@ -22,43 +22,50 @@ HIL-тесты через pyOCD + pytest, GDB-сервер для отладки
|
|||
|
||||
## 2. Компоненты окружения
|
||||
|
||||
```bash
|
||||
ПК разработчика
|
||||
│
|
||||
├── Хост (Linux / macOS / Windows + Git Bash)
|
||||
│ ├── just ← запуск задач хостового уровня (just host::*)
|
||||
│ ├── docker ← управление devcontainer
|
||||
│ ├── uv + spsdk ← прошивка через USB ROM (flash_usb.py, sdphost, blhost)
|
||||
│ │ venv: tools/host/
|
||||
│ ├── uv + pyocd ← GDB-сервер отладки + HIL-тесты
|
||||
│ │ + pyserial venv: tools/hil/
|
||||
│ │ + pytest
|
||||
│ │ + mpremote ← деплой агента на M5StampPLC
|
||||
│ └── VSCode ← IDE (Dev Containers extension)
|
||||
│
|
||||
├── Devcontainer (Docker)
|
||||
│ ├── arm-none-eabi-gcc ← кросс-компилятор (firmware + HIL target-прошивки)
|
||||
│ ├── cmake + ninja ← система сборки
|
||||
│ ├── clang-17 ← компилятор для host-тестов
|
||||
│ ├── clangd-17 ← LSP (автодополнение, диагностика)
|
||||
│ ├── clang-tidy-17 ← статический анализ
|
||||
│ ├── clang-format-17 ← форматирование кода
|
||||
│ ├── just ← запуск задач внутри контейнера (just build::*)
|
||||
│ ├── uv + spsdk ← сборка HAB-образов (только nxpimage)
|
||||
│ └── Unity + fff ← фреймворки host-тестов
|
||||
│
|
||||
├── Плата TFT (MIMXRT1052)
|
||||
│ ├── USB ──────────────────────▶ хост (SDP-режим, прошивка через ROM)
|
||||
│ └── MCU-Link (USB) ───────────▶ хост (CMSIS-DAP)
|
||||
│ ├── SWD ← pyOCD: GDB-сервер отладки + прошивка Flash + загрузка HIL ELF в RAM
|
||||
│ └── VCOM ← pytest общается с HIL прошивкой через UART CLI
|
||||
│
|
||||
└── HIL стенд (M5Stack StamPLC)
|
||||
├── USB ──────────────────────▶ хост (M5 агент, JSON-lines CLI)
|
||||
├── RLY1 ─────────────────────▶ VIN таргета (управление питанием)
|
||||
├── RLY2 ─────────────────────▶ RS_RX таргета (BSP_OPTO_CH_RS)
|
||||
├── RLY3 ─────────────────────▶ EXT_IN1 таргета (BSP_OPTO_CH_IN1)
|
||||
└── RLY4 ─────────────────────▶ EXT_IN2 таргета (BSP_OPTO_CH_IN2)
|
||||
### Физические связи
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Host["Хост"]
|
||||
|
||||
subgraph Board["Плата TFT (MIMXRT1052)"]
|
||||
USB_SDP["USB"]
|
||||
MCULink["MCU-Link"]
|
||||
end
|
||||
|
||||
subgraph M5["HIL стенд (M5StampPLC)"]
|
||||
M5_USB["USB"]
|
||||
RLY["RLY1–4"]
|
||||
end
|
||||
|
||||
USB_SDP -->|"SDP — прошивка через ROM"| Host
|
||||
MCULink -->|"SWD — GDB-сервер, прошивка Flash, загрузка HIL ELF"| Host
|
||||
MCULink -->|"VCOM — UART CLI (pytest ↔ HIL firmware)"| Host
|
||||
M5_USB -->|"JSON-lines CLI"| Host
|
||||
RLY -->|"VIN · RS_RX · EXT_IN1 · EXT_IN2"| Board
|
||||
```
|
||||
|
||||
### Состав инструментов
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Host["Хост (Linux / macOS / Windows + Git Bash)"]
|
||||
H1["just host::*\nзапуск задач хостового уровня"]
|
||||
H2["docker\nуправление devcontainer"]
|
||||
H3["uv + spsdk — tools/host/\nsdphost · blhost · nxpimage"]
|
||||
H4["uv + pyocd + pyserial + pytest — tools/hil/\nGDB-сервер · HIL-тесты"]
|
||||
H5["mpremote\nдеплой агента на M5StampPLC"]
|
||||
H6["VSCode (Dev Containers extension)"]
|
||||
end
|
||||
|
||||
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
|
||||
.env
|
||||
│
|
||||
├─▶ just (set dotenv-load + set export)
|
||||
│ ├─▶ just-рецепты: {{BOOTROM_VID}}, {{HIL_VCOM_PORT}}, {{GDB_PORT}}
|
||||
│ └─▶ uv run python ← наследует os.environ автоматически
|
||||
│ ├─▶ flash_usb.py: os.environ["BOOTROM_VID"]
|
||||
│ ├─▶ flash_swd.py: os.environ["PYOCD_TARGET"]
|
||||
│ └─▶ env_config.py: os.environ["HIL_VCOM_PORT"]
|
||||
│
|
||||
└─▶ .vscode/launch.json ← через ${env:GDB_PORT}
|
||||
```mermaid
|
||||
flowchart LR
|
||||
ENV[".env"]
|
||||
|
||||
subgraph Just["just (dotenv-load + export)"]
|
||||
JR["just-рецепты\n{{BOOTROM_VID}}\n{{HIL_VCOM_PORT}}\n{{GDB_PORT}}"]
|
||||
UV["uv run python\n(наследует os.environ)"]
|
||||
end
|
||||
|
||||
subgraph Python["Python-скрипты"]
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -227,7 +246,7 @@ HIL_USB_CDC_TIMEOUT=5.0
|
|||
└── testing/
|
||||
├── hil/
|
||||
│ ├── HIL_HOW_TO.md ← как проводить HIL-тесты
|
||||
│ ├── HIL_BENCH.md ← стенд: оборудование, подключение
|
||||
│ ├── HIL_BENCH.md ← стенд: оборудование, подключение
|
||||
│ └── HIL_CREATE_TEST.md ← как добавить новый HIL-тест
|
||||
└── host/
|
||||
└── HOST_CREATE_TEST.md ← как добавить host unit-тест
|
||||
|
|
@ -254,21 +273,23 @@ git clone <repo-url> && cd <repo>
|
|||
|
||||
### 5.3 Что делает bootstrap
|
||||
|
||||
```bash
|
||||
bootstrap.sh (уровень 0)
|
||||
│
|
||||
├── определить платформу (Linux / macOS / Windows Git Bash)
|
||||
├── проверить/установить uv >= 0.4.0
|
||||
├── проверить/установить just >= 1.36.0 (через uv tool)
|
||||
│
|
||||
└── exec just host::bootstrap
|
||||
├── [1/3] check-deps — just · uv · docker
|
||||
├── [2/3] setup-udev — udev-правила NXP USB (только Linux)
|
||||
│ 1FC9:0130 ← BootROM SDP
|
||||
│ 15A2:0073 ← Flashloader
|
||||
│ dialout ← группа для /dev/ttyACM* (M5StampPLC)
|
||||
└── [3/3] setup-tools — uv sync в tools/host/
|
||||
SHA-256 uv.lock кешируется → повторный вызов мгновенный
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["bootstrap.sh\n(уровень 0)"]
|
||||
A --> B["определить платформу\nLinux / macOS / Windows Git Bash"]
|
||||
B --> C["проверить/установить\nuv >= 0.4.0"]
|
||||
C --> D["проверить/установить\njust >= 1.36.0\n(через uv tool)"]
|
||||
D --> E["exec just host::bootstrap"]
|
||||
|
||||
E --> F["[1/3] check-deps\njust · uv · docker"]
|
||||
E --> G["[2/3] setup-udev\n(только Linux)"]
|
||||
E --> H["[3/3] setup-tools\nuv sync → tools/host/"]
|
||||
|
||||
G --> G1["1FC9:0130 — BootROM SDP"]
|
||||
G --> G2["15A2:0073 — Flashloader"]
|
||||
G --> G3["dialout — /dev/ttyACM*"]
|
||||
|
||||
H --> H1["SHA-256 uv.lock кешируется\nповторный вызов мгновенный"]
|
||||
```
|
||||
|
||||
### 5.4 После bootstrap
|
||||
|
|
@ -323,7 +344,7 @@ buildPresets (HIL):
|
|||
target-debug-build: test_host_uart, test_hil_button, test_hil_can,
|
||||
test_hil_usb_cdc, test_hil_opto
|
||||
|
||||
Источник истины по списку целей — [CMakePresets.json](../CMakePresets.json).
|
||||
Источник истины по списку целей — CMakePresets.json.
|
||||
```
|
||||
|
||||
### 6.3 Boot-стратегии
|
||||
|
|
@ -431,22 +452,36 @@ HIL-тесты проверяют периферию на реальном же
|
|||
|
||||
Только MCU-Link: SWD загружает ELF в RAM, VCOM обеспечивает UART CLI.
|
||||
|
||||
```bash
|
||||
pytest → uart_cmd("PING")
|
||||
↓ pyserial / VCOM
|
||||
MCU-Link
|
||||
↓ LPUART1
|
||||
RT1052 → "PONG"
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant PT as pytest
|
||||
participant ML as MCU-Link VCOM
|
||||
participant RT as RT1052
|
||||
|
||||
PT->>ML: uart_cmd("PING")\n(pyserial)
|
||||
ML->>RT: LPUART1
|
||||
RT-->>ML: "PONG"
|
||||
ML-->>PT: "PONG"
|
||||
```
|
||||
|
||||
### С M5StampPLC — `02_test_opto.py` и другие
|
||||
|
||||
`M5StampPLC` управляет входными сигналами таргета через реле. pytest оркестрирует оба канала одновременно.
|
||||
|
||||
```bash
|
||||
pytest
|
||||
├─▶ m5.opto_set(1, True) → M5 (JSON) → RLY3 → EXT_IN1 таргета
|
||||
└─▶ uart_cmd("OPTO_READ 1") → MCU-Link VCOM → RT1052 → "ACTIVE"
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant PT as pytest
|
||||
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.
|
||||
|
|
@ -503,17 +538,26 @@ timeout-паттерн.
|
|||
|
||||
Подробно — [docs/HOW_TO_DEBUG.md](HOW_TO_DEBUG.md). Краткая схема:
|
||||
|
||||
```bash
|
||||
Хост
|
||||
├── just host::debug-server
|
||||
│ └── pyocd gdbserver :3333
|
||||
│ USB/SWD → MCU-Link → плата
|
||||
└── host.docker.internal:3333 ← доступен из devcontainer
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph DC["Devcontainer"]
|
||||
CD["cortex-debug\n(VSCode F5)"]
|
||||
GDB["arm-none-eabi-gdb"]
|
||||
CD --> GDB
|
||||
end
|
||||
|
||||
Devcontainer
|
||||
└── cortex-debug (VSCode)
|
||||
↔ arm-none-eabi-gdb
|
||||
target remote host.docker.internal:3333
|
||||
subgraph Host["Хост"]
|
||||
DS["just host::debug-server"]
|
||||
PO["pyocd gdbserver :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`:
|
||||
|
|
@ -529,28 +573,41 @@ RTT-логи доступны в Debug-сборках (`SEGGER_RTT_ENABLED=ON`);
|
|||
|
||||
## 12. Жизненный цикл изменений
|
||||
|
||||
```bash
|
||||
feature-ветка
|
||||
│
|
||||
├── devcontainer
|
||||
│ just build::test-host ← зелёные host-тесты?
|
||||
│ just build::build-firmware-test-debug
|
||||
│ just build::build-hil ← HIL-прошивки собираются?
|
||||
│
|
||||
├── хост
|
||||
│ just host::flash-test-debug ← прошить, проверить на железе
|
||||
│ just host::hil-run ← HIL зелёные?
|
||||
│
|
||||
├── подготовка к MR
|
||||
│ just build::hab-all-release
|
||||
│ just host::flash firmware_test release
|
||||
│
|
||||
└── Merge Request → CI
|
||||
host-тесты · сборка · HIL · публикация артефактов
|
||||
↓
|
||||
Производственный сервер
|
||||
just host::incoming → firmware_test release → HIL
|
||||
just host::production → bootloader + tft_app release
|
||||
```mermaid
|
||||
flowchart TD
|
||||
FB["feature-ветка"]
|
||||
|
||||
subgraph DC["Devcontainer"]
|
||||
T1["just build::test-host\nhost-тесты зелёные?"]
|
||||
T2["just build::build-firmware-test-debug"]
|
||||
T3["just build::build-hil\nHIL-прошивки собираются?"]
|
||||
end
|
||||
|
||||
subgraph HostW["Хост"]
|
||||
T4["just host::flash-test-debug\nпрошить, проверить на железе"]
|
||||
T5["just host::hil-run\nHIL зелёные?"]
|
||||
end
|
||||
|
||||
subgraph MR["Подготовка к MR"]
|
||||
T6["just build::hab-all-release"]
|
||||
T7["just host::flash firmware_test release"]
|
||||
end
|
||||
|
||||
CI["Merge Request → CI\nhost-тесты · сборка · HIL\nпубликация артефактов"]
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,23 +2,33 @@
|
|||
|
||||
## Обзор архитектуры
|
||||
|
||||
Отладка построена на проброске GDB-сервера с хоста в devcontainer по TCP. Это позволяет держать весь инструментарий сборки и языковой сервер внутри контейнера, не проводя USB-пробник внутрь Docker.
|
||||
Отладка построена на пробросе GDB-сервера с хоста в devcontainer по TCP. Это
|
||||
позволяет держать весь инструментарий сборки и языковой сервер внутри контейнера,
|
||||
не проводя USB-пробник внутрь Docker.
|
||||
|
||||
```bash
|
||||
┌─────────────────────────────────────┐ ┌──────────────────────────────────┐
|
||||
│ Хост (macOS/Linux) │ │ DevContainer │
|
||||
│ │ │ │
|
||||
│ just host::debug-server │ │ VSCode + cortex-debug │
|
||||
│ └─ pyocd gdbserver :3333 ──────────┼─────┼──► arm-none-eabi-gdb │
|
||||
│ │TCP │ └─ символы из .elf │
|
||||
│ MCU-Link (CMSIS-DAP) │3333 │ │
|
||||
│ └─ SWD ──► MIMXRT1052 │ │ RTT Console (SEGGER RTT логи) │
|
||||
│ Flash / SDRAM │ │ Peripherals (SVD регистры) │
|
||||
│ SEGGER RTT буфер │ │ RTOS view (FreeRTOS задачи) │
|
||||
└─────────────────────────────────────┘ └──────────────────────────────────┘
|
||||
```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 gdbserver` слушает на `0.0.0.0:3333`. Из контейнера
|
||||
GDB подключается через `host.docker.internal:3333` — специальный DNS-алиас Docker,
|
||||
резолвится в IP хост-машины.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -48,10 +58,9 @@
|
|||
|
||||
### Конфигурация
|
||||
|
||||
Параметры отладки задаются в `.env` и автоматически экспортируются через `just` (`set export`), откуда наследуются скриптами:
|
||||
Параметры отладки задаются в `.env`:
|
||||
|
||||
```bash
|
||||
# .env — секция Debug / SWD
|
||||
GDB_PORT=3333
|
||||
PYOCD_TARGET=mimxrt1050_quadspi
|
||||
PYOCD_FREQUENCY=4000000
|
||||
|
|
@ -60,7 +69,7 @@ FCB_PATH=tools/host/dcd/w25q128_fdcb.bin
|
|||
|
||||
---
|
||||
|
||||
## Прошивки, поддерживаемые отладкой
|
||||
## Поддерживаемые прошивки
|
||||
|
||||
| Конфигурация 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: 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
|
||||
|
||||
Стандартный ежедневный сценарий. Прошивка была залита ранее любым способом и исполняется на плате.
|
||||
|
||||
```bash
|
||||
# 1. Хост — запустить GDB-сервер (оставить работать в отдельном терминале)
|
||||
# 1. Хост — запустить GDB-сервер (оставить в отдельном терминале)
|
||||
just host::debug-server
|
||||
|
||||
# 2. DevContainer — VSCode
|
||||
# Run & Debug (Ctrl+Shift+D) → выбрать конфигурацию → F5
|
||||
```
|
||||
|
||||
GDB сбрасывает MCU, загружает символы из ELF и останавливается на входе в `main`. Flash не перезаписывается.
|
||||
GDB сбрасывает MCU, загружает символы из ELF и останавливается на входе
|
||||
в `main`. Flash не перезаписывается.
|
||||
|
||||
### Режим Б — прошить через SWD, затем отладить
|
||||
|
||||
Когда нужно обновить прошивку без перевода платы в режим Serial Downloader. Удобно при итеративной разработке когда плата закреплена в стенде.
|
||||
|
||||
```bash
|
||||
# 1. DevContainer — собрать HAB-образ
|
||||
# 1. DevContainer
|
||||
just build::hab-firmware-test-debug
|
||||
|
||||
# 2. Хост — прошить через SWD (MCU-Link, без смены BOOT_MODE)
|
||||
# 2. Хост
|
||||
just host::flash-swd-test-debug
|
||||
|
||||
# 3. ⚡ Power cycle платы (обязательно — VECTRESET не реинициализирует FlexSPI)
|
||||
# 3. ⚡ Power cycle платы (обязательно)
|
||||
|
||||
# 4. Хост — запустить GDB-сервер
|
||||
# 4. Хост
|
||||
just host::debug-server
|
||||
|
||||
# 5. DevContainer — VSCode → 🐛 Debug: firmware_test → F5
|
||||
|
|
@ -109,16 +115,14 @@ just host::debug-server
|
|||
|
||||
### Режим В — прошить через USB SDP, затем отладить
|
||||
|
||||
Классический способ. Требует перевода платы в режим Serial Downloader (BOOT_MODE = 01).
|
||||
|
||||
```bash
|
||||
# 1. DevContainer — собрать
|
||||
# 1. DevContainer
|
||||
just build::build-firmware-test-debug
|
||||
|
||||
# 2. Хост — перевести плату в Serial Downloader mode, затем:
|
||||
# 2. Хост — перевести плату в SDP-режим, затем:
|
||||
just host::flash-test-debug
|
||||
|
||||
# 3. Хост — запустить GDB-сервер
|
||||
# 3. Хост
|
||||
just host::debug-server
|
||||
|
||||
# 4. DevContainer — VSCode → 🐛 Debug: firmware_test → F5
|
||||
|
|
@ -128,33 +132,32 @@ just host::debug-server
|
|||
|
||||
## Почему 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
|
||||
0x60000000 w25q128_fdcb.bin (512 байт) — FCB: параметры W25Q128, Quad SPI
|
||||
0x60000200 0xFF × 3584 байт — padding (значение стёртой ячейки)
|
||||
0x60001000 firmware_test_hab.bin — IVT + DCD + код (ivtOffset = 0x1000)
|
||||
0x60000000 w25q128_fdcb.bin (512 байт) — FCB
|
||||
0x60000200 0xFF × 3584 байт — padding
|
||||
0x60001000 firmware_test_hab.bin — IVT + DCD + код
|
||||
```
|
||||
|
||||
Весь диапазон `0x60000000–0x6000FFFF` умещается в один 64KB-сектор Flash, поэтому стирается и записывается за одну транзакцию — FCB и HAB не перезаписывают друг друга.
|
||||
Весь диапазон `0x60000000–0x6000FFFF` — один 64KB сектор: стирается и
|
||||
записывается за одну транзакцию.
|
||||
|
||||
---
|
||||
|
||||
## RTT-логи
|
||||
|
||||
SEGGER RTT включён только в Debug-сборках (`SEGGER_RTT_ENABLED=ON` в `CMakePresets.json`). В Release-сборках RTT отключён и символ `_SEGGER_RTT` в ELF отсутствует.
|
||||
|
||||
После старта отладки вкладка `TERMINAL → RTT` в VSCode принимает вывод из RTT-буфера канала 0. `cortex-debug` находит адрес буфера автоматически по символу `_SEGGER_RTT` из ELF (`address: auto` в `launch.json`).
|
||||
|
||||
Использование в коде:
|
||||
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);
|
||||
```
|
||||
|
||||
|
|
@ -162,46 +165,47 @@ SEGGER_RTT_printf(0, "value = %d\n", value);
|
|||
|
||||
## 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
|
||||
# 1. Убедиться что cortex-debug установлен в devcontainer
|
||||
# .devcontainer/devcontainer.json → extensions: ["marus25.cortex-debug"]
|
||||
|
||||
# 2. Убедиться что в .devcontainer/devcontainer.json есть (для Linux-хостов):
|
||||
# 1. Убедиться что в .devcontainer/devcontainer.json есть (для Linux):
|
||||
# "runArgs": ["--add-host=host.docker.internal:host-gateway"]
|
||||
|
||||
# 3. Залить прошивку любым способом (один раз)
|
||||
just host::flash-test-debug # USB SDP
|
||||
# или
|
||||
just host::flash-swd-test-debug # SWD (после just build::hab-firmware-test-debug)
|
||||
# 2. Залить прошивку
|
||||
just host::flash-test-debug
|
||||
|
||||
# 4. Запустить GDB-сервер на хосте
|
||||
# 3. Хост — запустить GDB-сервер
|
||||
just host::debug-server
|
||||
|
||||
# 5. В VSCode (devcontainer)
|
||||
# 4. DevContainer — VSCode
|
||||
# Ctrl+Shift+D → 🐛 Debug: firmware_test → F5
|
||||
```
|
||||
|
||||
|
|
@ -213,7 +217,7 @@ just host::debug-server
|
|||
.
|
||||
├── .env # GDB_PORT, PYOCD_TARGET, PYOCD_FREQUENCY, FCB_PATH
|
||||
├── .vscode/
|
||||
│ ├── launch.json # Конфигурации cortex-debug (3 проекта)
|
||||
│ ├── launch.json # cortex-debug конфигурации (3 проекта)
|
||||
│ └── tasks.json # preLaunchTask: build:*-debug
|
||||
├── bsp/generated/startup/
|
||||
│ └── MIMXRT1052.xml # SVD — регистры периферии
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
|
||||
## Способ 1 — USB SDP (Serial Download Protocol)
|
||||
|
||||
Стандартный производственный способ. ROM-загрузчик принимает образ по USB и записывает его во Flash через Flashloader. Требует физического переключения пина `BOOT_MOD_1`.
|
||||
Стандартный производственный способ. ROM-загрузчик принимает образ по USB и
|
||||
записывает его во Flash через Flashloader. Требует физического переключения
|
||||
пина `BOOT_MOD_1`.
|
||||
|
||||
### 1.1 Перевести плату в SDP-режим
|
||||
|
||||
|
|
@ -58,17 +60,15 @@ just host::flash-production # bootloader release + app release (с подт
|
|||
|
||||
### 1.4 Что происходит при прошивке через USB SDP
|
||||
|
||||
```bash
|
||||
Плата в SDP-режиме (1FC9:0130)
|
||||
│
|
||||
├── sdphost: загрузить ivt_flashloader.bin в RAM (0x20001C00)
|
||||
└── sdphost: jump-address → Flashloader поднимается как 15A2:0073
|
||||
│
|
||||
├── configure-memory (0xC0000007) — инициализация FlexSPI NOR
|
||||
├── flash-erase-region 0x60000000
|
||||
├── configure-memory (0xF000000F) — запись FCB в 0x60000000
|
||||
├── write-memory 0x60001000 ← HAB-образ
|
||||
└── reset
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Плата в SDP-режиме\n1FC9:0130"] --> B["sdphost\nзагрузить ivt_flashloader.bin\nв RAM 0x20001C00"]
|
||||
B --> C["sdphost jump-address\nFlashloader поднимается\nкак 15A2:0073"]
|
||||
C --> D["configure-memory 0xC0000007\nинициализация FlexSPI NOR"]
|
||||
D --> E["flash-erase-region 0x60000000"]
|
||||
E --> F["configure-memory 0xF000000F\nзапись FCB в 0x60000000"]
|
||||
F --> G["write-memory 0x60001000\nHAB-образ"]
|
||||
G --> H["reset"]
|
||||
```
|
||||
|
||||
ROM-загрузчик сам конфигурирует FlexSPI через DCD из HAB-образа, поэтому FCB
|
||||
|
|
@ -80,13 +80,14 @@ ROM-загрузчик сам конфигурирует FlexSPI через DCD
|
|||
|
||||
Прошивка через отладочный пробник (MCU-Link, CMSIS-DAP). Плата остаётся
|
||||
в нормальном режиме загрузки — переключать `BOOT_MOD_1` не нужно. Удобно
|
||||
при итеративной разработке когда плата закреплена в стенде, а также как
|
||||
часть отладочного цикла.
|
||||
при итеративной разработке когда плата закреплена в стенде.
|
||||
|
||||
**Ограничения:**
|
||||
|
||||
- После записи обязателен **power cycle** (не reset) — VECTRESET не реинициализирует FlexSPI, Boot ROM не стартует
|
||||
- MCU-Link используется монопольно: нельзя запускать одновременно с `debug-server` или HIL-тестами
|
||||
- После записи обязателен **power cycle** (не reset) — VECTRESET не
|
||||
реинициализирует FlexSPI, Boot ROM не стартует
|
||||
- MCU-Link используется монопольно: нельзя запускать одновременно с
|
||||
`debug-server` или HIL-тестами
|
||||
|
||||
### 2.1 Подготовить HAB-образ (внутри devcontainer)
|
||||
|
||||
|
|
@ -99,8 +100,8 @@ just build::hab-app-debug
|
|||
### 2.2 Прошить (хостовый терминал)
|
||||
|
||||
```bash
|
||||
just host::flash-swd-test-debug # firmware_test Debug
|
||||
just host::flash-swd-test-release # firmware_test Release
|
||||
just host::flash-swd-test-debug
|
||||
just host::flash-swd-test-release
|
||||
just host::flash-swd-bootloader-debug
|
||||
just host::flash-swd-bootloader-release
|
||||
just host::flash-swd-app-debug
|
||||
|
|
@ -134,7 +135,7 @@ just host::flash-swd-app-release
|
|||
| `tools/hil/` (uv-проект) | pyocd, вызывается через `uv run` |
|
||||
|
||||
FCB-бинарник (`w25q128_fdcb.bin`) генерируется в NXP SecureProvisioningTool
|
||||
для W25Q128 в режиме Quad SPI и хранится в репозитории — пересоздавать не нужно.
|
||||
и хранится в репозитории — пересоздавать не нужно.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -15,29 +15,22 @@
|
|||
инженером через 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
|
||||
[Хост-ПК сервисного инженера]
|
||||
│ USB CDC ACM (J2)
|
||||
│ JSON-lines, 1 строка = 1 сообщение
|
||||
▼
|
||||
[Плата MIMXRT1052 с firmware_test]
|
||||
│ GPIO / LPUART / SEMC / FlexSPI / USDHC
|
||||
▼
|
||||
[Периферия: SDRAM, QSPI Flash, uSD, Display, CAN, UART, Opto]
|
||||
▲
|
||||
[M5StampPLC — управление внешними сигналами для HIL тестов]
|
||||
Host -->|"USB CDC ACM J2\nJSON-lines, 1 строка = 1 сообщение"| FW
|
||||
FW -->|"GPIO / LPUART / SEMC\nFlexSPI / USDHC"| Periph
|
||||
M5 -->|"реле → оптовходы / CAN / UART"| FW
|
||||
```
|
||||
|
||||
**Принцип работы:**
|
||||
|
||||
- Вся тест-логика живёт **на таргете** (`test_runner.c`, `tests/*.c`).
|
||||
- Хост — тонкий клиент: отправляет команды, отображает события, управляет
|
||||
интерактивными шагами.
|
||||
- Инженер запускает тесты **атомарно** (один тест за раз) или все подряд
|
||||
(`run_all`). Последовательность не фиксирована — инженер сам решает,
|
||||
что проверять.
|
||||
**Принцип работы:** вся тест-логика живёт на таргете (`test_runner.c`,
|
||||
`tests/*.c`). Хост — тонкий клиент: отправляет команды, отображает события,
|
||||
управляет интерактивными шагами. Инженер запускает тесты атомарно или все
|
||||
подряд (`run_all`).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -53,113 +46,90 @@
|
|||
| CR+LF | Принимается (таргет отбрасывает `\r` перед `\n`) |
|
||||
| Направление | Двунаправленный, half-duplex по логике |
|
||||
|
||||
Нет хэндшейка, нет sequence number, нет подтверждений доставки. При потере
|
||||
строки хост повторяет команду — таргет идемпотентен для `ping` и `run`.
|
||||
Нет хэндшейка, нет sequence number, нет подтверждений доставки.
|
||||
|
||||
---
|
||||
|
||||
## Формат сообщений
|
||||
|
||||
Все сообщения — JSON-объекты в одну строку (`\n` в конце).
|
||||
Все сообщения — JSON-объекты в одну строку (`\n` в конце). Поле `"type"`
|
||||
определяет смысл сообщения:
|
||||
|
||||
### Ключевые поля
|
||||
```bash
|
||||
Хост → Таргет: "type": "cmd" — команда
|
||||
"type": "confirm" — ответ оператора на интерактивный шаг
|
||||
|
||||
Каждое сообщение содержит поле `"type"`, определяющее его смысл:
|
||||
|
||||
```
|
||||
Хост → Таргет: "type": "cmd" — команда
|
||||
"type": "confirm" — ответ оператора на интерактивный шаг
|
||||
|
||||
Таргет → Хост: "type": "session_start" — таргет готов
|
||||
"type": "pong" — ответ на ping
|
||||
"type": "test_begin" — тест стартовал
|
||||
"type": "test_result" — тест завершён
|
||||
"type": "confirm_request" — ожидание действия оператора
|
||||
"type": "summary" — итог run_all
|
||||
"ok": false, "error": "…" — ошибка протокола
|
||||
Таргет → Хост: "type": "session_start" — таргет готов
|
||||
"type": "pong" — ответ на ping
|
||||
"type": "test_begin" — тест стартовал
|
||||
"type": "progress" — промежуточный шаг теста
|
||||
"type": "test_result" — тест завершён
|
||||
"type": "confirm_request" — ожидание действия оператора
|
||||
"type": "summary" — итог run_all
|
||||
"ok": false, "error": "…" — ошибка протокола
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Жизненный цикл сессии
|
||||
|
||||
```bash
|
||||
Хост Таргет
|
||||
│ │
|
||||
│ [прошивка загружена через USB SDP] │
|
||||
│ [USB CDC установлен] │
|
||||
│◄─── {"type":"session_start","fw":"0.1.0", │
|
||||
│ "target":"IMXRT1052","uptime_ms":0} │
|
||||
│ │
|
||||
│──── {"type":"cmd","cmd":"ping"} ──────────►│
|
||||
│◄─── {"type":"pong"} ────────────────────── │
|
||||
│ │
|
||||
│ [инженер выбирает тест] │
|
||||
│ │
|
||||
│──── {"type":"cmd","cmd":"run","id":"sdram"} ►│
|
||||
│◄─── {"type":"test_begin","id":"sdram",...} │
|
||||
│◄─── {"type":"test_result","id":"sdram",...} │
|
||||
│ │
|
||||
│──── {"type":"cmd","cmd":"run_all"} ────────►│
|
||||
│◄─── {"type":"test_begin","id":"sdram",...} │
|
||||
│◄─── {"type":"test_result","id":"sdram",...} │
|
||||
│ … (каждый тест в реестре) … │
|
||||
│◄─── {"type":"summary","overall":"pass",...} │
|
||||
│ │
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant H as Хост
|
||||
participant T as Таргет
|
||||
|
||||
Note over T: прошивка загружена через USB SDP
|
||||
T-->>H: {"type":"session_start","fw":"0.1.0","target":"IMXRT1052","uptime_ms":0}
|
||||
|
||||
H->>T: {"type":"cmd","cmd":"ping"}
|
||||
T-->>H: {"type":"pong"}
|
||||
|
||||
Note over H: инженер выбирает тест
|
||||
H->>T: {"type":"cmd","cmd":"run","id":"sdram"}
|
||||
T-->>H: {"type":"test_begin","id":"sdram","name":"SDRAM 32 MB","critical":true}
|
||||
T-->>H: {"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":""}
|
||||
|
||||
H->>T: {"type":"cmd","cmd":"run_all"}
|
||||
T-->>H: {"type":"test_begin","id":"sdram",...}
|
||||
T-->>H: {"type":"test_result","id":"sdram","status":"pass",...}
|
||||
Note over T: ...каждый тест в реестре...
|
||||
T-->>H: {"type":"summary","overall":"pass","passed":7,"failed":0,"skipped":1}
|
||||
```
|
||||
|
||||
`session_start` отправляется **автоматически** при каждом старте таргета,
|
||||
до получения первой команды. Хост должен быть готов принять его сразу после
|
||||
открытия CDC порта.
|
||||
`session_start` отправляется автоматически при каждом старте таргета, до
|
||||
получения первой команды.
|
||||
|
||||
---
|
||||
|
||||
## Команды хоста → таргет (`"type":"cmd"`)
|
||||
## Команды хоста → таргет
|
||||
|
||||
### `ping`
|
||||
|
||||
Проверка связи. Таргет отвечает немедленно.
|
||||
|
||||
```json
|
||||
→ {"type":"cmd","cmd":"ping"}
|
||||
← {"type":"pong"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `run` — запуск одного теста
|
||||
|
||||
Запустить тест по идентификатору. Если тест требует предварительного
|
||||
подтверждения оператора (`pre_confirm_prompt` задан), таргет сначала пошлёт
|
||||
`confirm_request`.
|
||||
|
||||
```json
|
||||
→ {"type":"cmd","cmd":"run","id":"sdram"}
|
||||
← {"type":"test_begin","id":"sdram","name":"SDRAM 32 MB","critical":true}
|
||||
← {"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":""}
|
||||
```
|
||||
|
||||
Если `id` не найден в реестре:
|
||||
Если `id` не найден: `← {"ok":false,"error":"UNKNOWN_TEST"}`
|
||||
|
||||
```json
|
||||
← {"ok":false,"error":"UNKNOWN_TEST"}
|
||||
```
|
||||
### `run_all` — запуск всех тестов
|
||||
|
||||
---
|
||||
|
||||
### `run_all` — запуск всех тестов по реестру
|
||||
|
||||
Запускает все тест-модули в порядке реестра. Если тест помечен `"critical":true`
|
||||
и вернул `"status":"fail"` — выполнение прерывается, остальные тесты
|
||||
получают `"status":"skip"` в итоге (но `summary` всё равно отправляется).
|
||||
Если тест помечен `"critical":true` и вернул `"fail"` — выполнение
|
||||
прерывается, остальные получают `"skip"`.
|
||||
|
||||
```json
|
||||
→ {"type":"cmd","cmd":"run_all"}
|
||||
← {"type":"test_begin","id":"sdram","name":"SDRAM 32 MB","critical":true}
|
||||
← {"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":""}
|
||||
← {"type":"test_begin","id":"qspi","name":"QSPI Flash 8 MB","critical":true}
|
||||
← {"type":"test_result","id":"qspi","status":"pass","ms":88,"detail":""}
|
||||
← ... (остальные тесты) ...
|
||||
← {"type":"test_begin","id":"sdram",...}
|
||||
← {"type":"test_result","id":"sdram","status":"pass",...}
|
||||
← ... (каждый тест в реестре) ...
|
||||
← {"type":"summary","passed":7,"failed":0,"skipped":1,"overall":"pass"}
|
||||
```
|
||||
|
||||
|
|
@ -169,70 +139,40 @@
|
|||
|
||||
### `session_start`
|
||||
|
||||
Таргет готов к работе. Отправляется автоматически при старте.
|
||||
|
||||
```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`
|
||||
|
||||
Тест начат. Отправляется непосредственно перед вызовом `run()`.
|
||||
|
||||
```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`
|
||||
|
||||
Тест завершён (pass / fail / skip).
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "test_result",
|
||||
"id": "sdram",
|
||||
"status": "pass",
|
||||
"ms": 312,
|
||||
"detail": ""
|
||||
}
|
||||
{"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":""}
|
||||
```
|
||||
|
||||
| `status` | Смысл |
|
||||
| -------- | ---------------------------------------------------------------------------- |
|
||||
| `"pass"` | Тест пройден |
|
||||
| `"fail"` | Тест провален; поле `detail` содержит описание |
|
||||
| `"skip"` | Тест пропущен (нет оборудования, таймаут оператора, прерван `critical` fail) |
|
||||
| `status` | Смысл |
|
||||
| -------- | ----------------------------------------------------------------------- |
|
||||
| `"pass"` | Тест пройден |
|
||||
| `"fail"` | Тест провален; `detail` содержит описание |
|
||||
| `"skip"` | Пропущен (нет оборудования, таймаут оператора, прерван `critical` fail) |
|
||||
|
||||
Поле `detail` — произвольная ASCII-строка до 95 символов. При `pass` — пустая.
|
||||
Примеры: `"addr=0x80001000 expected=0xA5 got=0x00"`, `"JEDEC ID mismatch"`.
|
||||
`detail` — ASCII-строка до 95 символов. При `pass` — пустая.
|
||||
|
||||
---
|
||||
### `progress`
|
||||
|
||||
```json
|
||||
{"type":"progress","test":"usd","step":"mount","status":"ok"}
|
||||
```
|
||||
|
||||
Промежуточные шаги внутри теста. Используется в `usd`.
|
||||
|
||||
### `confirm_request`
|
||||
|
||||
Таргет ожидает действия оператора. Используется интерактивными тестами:
|
||||
display (подтвердить цвет), кнопки (нажать кнопку), uSD (вставить карту).
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "confirm_request",
|
||||
|
|
@ -242,58 +182,28 @@ display (подтвердить цвет), кнопки (нажать кнопк
|
|||
}
|
||||
```
|
||||
|
||||
Хост должен отобразить `prompt` оператору и ждать его реакции. Если оператор
|
||||
не ответил за `timeout_ms` — таргет переходит в `SKIP` для этого шага
|
||||
автоматически. Хост может дублировать таймаут на своей стороне для UX, но
|
||||
авторитетный таймаут — на таргете.
|
||||
|
||||
---
|
||||
Хост отображает `prompt` оператору. Авторитетный таймаут — на таргете;
|
||||
если оператор не ответил за `timeout_ms` — таргет переходит в `SKIP`.
|
||||
|
||||
### `summary`
|
||||
|
||||
Итог `run_all`. Отправляется после завершения последнего теста в реестре или
|
||||
после прерывания по critical fail.
|
||||
|
||||
```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": "pass"` если все `critical` тесты прошли (non-critical могут fail).
|
||||
|
||||
---
|
||||
|
||||
### `confirm` (хост → таргет)
|
||||
|
||||
Ответ оператора на `confirm_request`. Поле `"id"` должно совпадать с `id`
|
||||
из `confirm_request`.
|
||||
|
||||
```json
|
||||
→ {"type":"confirm","id":"display_red","confirmed":true}
|
||||
```
|
||||
|
||||
Если `"confirmed": false` — таргет записывает `TEST_STATUS_FAIL` для этого шага.
|
||||
Если ответ пришёл после истечения `timeout_ms` — таргет игнорирует его
|
||||
(уже перешёл в SKIP).
|
||||
|
||||
---
|
||||
`"id"` должен совпадать с `id` из `confirm_request`. Ответ после `timeout_ms`
|
||||
игнорируется.
|
||||
|
||||
### Ошибки протокола
|
||||
|
||||
```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 запросом |
|
||||
|
|
@ -318,12 +228,8 @@ display (подтвердить цвет), кнопки (нажать кнопк
|
|||
| `uart_iso` | UART ISO / RS_RX Opto | HIL | ❌ | ✅ | ❌ |
|
||||
| `opto` | Opto-in EXT_IN1/IN2 | HIL | ❌ | ✅ | ❌ |
|
||||
|
||||
**Типы тестов:**
|
||||
|
||||
- **self** — таргет тестирует периферию самостоятельно, без внешних сигналов.
|
||||
- **interactive** — требует действия оператора через механизм `confirm_request`.
|
||||
- **HIL** — требует M5StampPLC для генерации внешних сигналов
|
||||
(реле, CAN фреймы, UART echo).
|
||||
**Типы:** **self** — таргет тестирует периферию самостоятельно; **interactive** —
|
||||
требует `confirm_request`; **HIL** — требует M5StampPLC.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -331,131 +237,96 @@ display (подтвердить цвет), кнопки (нажать кнопк
|
|||
|
||||
### uSD
|
||||
|
||||
Карта вставляется оператором по запросу. Тест не входит в критический путь.
|
||||
Pre-confirm обрабатывается `test_runner` до вызова `run()`.
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant H as Хост
|
||||
participant T as Таргет
|
||||
|
||||
```bash
|
||||
← {"type":"confirm_request","id":"usd","prompt":"Insert microSD card and press OK","timeout_ms":30000}
|
||||
→ {"type":"confirm","id":"usd","confirmed":true}
|
||||
← {"type":"test_begin","id":"usd","name":"microSD (SDIO)","critical":false}
|
||||
← {"type":"progress","test":"usd","step":"card_detect","status":"ok"}
|
||||
← {"type":"progress","test":"usd","step":"mount","status":"ok"}
|
||||
← {"type":"progress","test":"usd","step":"write","status":"ok"}
|
||||
← {"type":"progress","test":"usd","step":"read_compare","status":"ok"}
|
||||
← {"type":"test_result","id":"usd","status":"pass","ms":741,"detail":""}
|
||||
T-->>H: {"type":"confirm_request","id":"usd","prompt":"Insert microSD card","timeout_ms":30000}
|
||||
H->>T: {"type":"confirm","id":"usd","confirmed":true}
|
||||
T-->>H: {"type":"test_begin","id":"usd","name":"microSD (SDIO)","critical":false}
|
||||
T-->>H: {"type":"progress","test":"usd","step":"card_detect","status":"ok"}
|
||||
T-->>H: {"type":"progress","test":"usd","step":"mount","status":"ok"}
|
||||
T-->>H: {"type":"progress","test":"usd","step":"write","status":"ok"}
|
||||
T-->>H: {"type":"progress","test":"usd","step":"read_compare","status":"ok"}
|
||||
T-->>H: {"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_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)
|
||||
|
||||
Четыре шага: красный, зелёный, синий, белый. Итог — AND всех подтверждений.
|
||||
Одновременно верифицируется подсветка (PWM включён).
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant H as Хост
|
||||
participant T as Таргет
|
||||
|
||||
```bash
|
||||
← {"type":"test_begin","id":"display",...}
|
||||
← {"type":"confirm_request","id":"display_red","prompt":"Экран залит красным?","timeout_ms":15000}
|
||||
→ {"type":"confirm","id":"display_red","confirmed":true}
|
||||
← {"type":"confirm_request","id":"display_green","prompt":"Экран залит зелёным?","timeout_ms":15000}
|
||||
→ {"type":"confirm","id":"display_green","confirmed":true}
|
||||
← {"type":"confirm_request","id":"display_blue","prompt":"Экран залит синим?","timeout_ms":15000}
|
||||
→ {"type":"confirm","id":"display_blue","confirmed":true}
|
||||
← {"type":"confirm_request","id":"display_white","prompt":"Экран залит белым?","timeout_ms":15000}
|
||||
→ {"type":"confirm","id":"display_white","confirmed":false}
|
||||
← {"type":"test_result","id":"display","status":"fail","ms":22103,"detail":"display_white not confirmed"}
|
||||
T-->>H: {"type":"test_begin","id":"display",...}
|
||||
T-->>H: {"type":"confirm_request","id":"display_red","prompt":"Экран залит красным?","timeout_ms":15000}
|
||||
H->>T: {"type":"confirm","id":"display_red","confirmed":true}
|
||||
T-->>H: {"type":"confirm_request","id":"display_green","prompt":"Экран залит зелёным?","timeout_ms":15000}
|
||||
H->>T: {"type":"confirm","id":"display_green","confirmed":true}
|
||||
T-->>H: {"type":"confirm_request","id":"display_blue","prompt":"Экран залит синим?","timeout_ms":15000}
|
||||
H->>T: {"type":"confirm","id":"display_blue","confirmed":true}
|
||||
T-->>H: {"type":"confirm_request","id":"display_white","prompt":"Экран залит белым?","timeout_ms":15000}
|
||||
H->>T: {"type":"confirm","id":"display_white","confirmed":false}
|
||||
T-->>H: {"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.
|
||||
`confirm_request` здесь используется как инструкция оператору — ответом является
|
||||
не JSON, а сам факт нажатия кнопки, который таргет детектирует самостоятельно.
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> IDLE
|
||||
|
||||
```bash
|
||||
← {"type":"test_begin","id":"buttons",...}
|
||||
← {"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":""}
|
||||
```
|
||||
IDLE --> PRE_CONFIRM : cmd run / run_all
|
||||
note right of PRE_CONFIRM : pre_confirm_prompt != NULL?\nprotocol_send_confirm_request()
|
||||
|
||||
> **Важно:** для теста кнопок таргет не ждёт `{"type":"confirm",…}` от хоста.
|
||||
> Хост отображает `prompt` оператору и ждёт следующего события от таргета.
|
||||
> Нажатие детектируется прошивкой через `bsp_button`, не через CDC.
|
||||
PRE_CONFIRM --> RUNNING : confirm получен или prompt == NULL
|
||||
|
||||
---
|
||||
RUNNING --> RUNNING : run_all — следующий тест
|
||||
note right of RUNNING : protocol_send_test_begin()\nmod->init()\nresult = mod->run()\nmod->deinit()\nprotocol_send_test_result()
|
||||
|
||||
## Рекомендации для разработчика хостового ПО
|
||||
|
||||
### Открытие порта
|
||||
|
||||
```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,
|
||||
но не обязан — таргет сам завершит по таймауту.
|
||||
RUNNING --> IDLE : run завершён
|
||||
RUNNING --> IDLE : run_all завершён\nprotocol_send_summary()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Реализация на стороне таргета
|
||||
|
||||
### Модули прошивки
|
||||
|
||||
```bash
|
||||
firmware/test/src/
|
||||
├── main.c — инициализация, главный цикл, вызов cli_process()
|
||||
├── cli.h / cli.c — IO-слой: буферизация строк, диспатч по "type"
|
||||
├── 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 механизм
|
||||
└── tests/
|
||||
├── test_sdram.c
|
||||
|
|
@ -469,64 +340,51 @@ firmware/test/src/
|
|||
└── 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. Объявить дескриптор:
|
||||
```c
|
||||
const test_module_t k_test_foo = {
|
||||
.id = "foo",
|
||||
.name = "Foo Peripheral",
|
||||
.critical = false,
|
||||
.requires_hil = false,
|
||||
.pre_confirm_prompt = NULL,
|
||||
.init = NULL,
|
||||
.run = test_foo_run,
|
||||
.deinit = NULL,
|
||||
};
|
||||
```
|
||||
3. Добавить `&k_test_foo` в реестр `test_runner.c` — одна строка.
|
||||
|
||||
```c
|
||||
const test_module_t k_test_foo = {
|
||||
.id = "foo",
|
||||
.name = "Foo Peripheral",
|
||||
.critical = false,
|
||||
.requires_hil = false,
|
||||
.pre_confirm_prompt = NULL,
|
||||
.init = NULL,
|
||||
.run = test_foo_run,
|
||||
.deinit = NULL,
|
||||
};
|
||||
```
|
||||
|
||||
3. Добавить `&k_test_foo` в реестр `test_runner.c`.
|
||||
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` — версия прошивки, а не версия протокола.
|
||||
При несовместимых изменениях протокола (новое обязательное поле, изменение
|
||||
семантики существующего) — bumping `FIRMWARE_TEST_VERSION` в `protocol.h`
|
||||
с соответствующим обновлением этого документа.
|
||||
|
||||
Хост должен проверять `"fw"` и предупреждать оператора при несовпадении
|
||||
ожидаемой версии.
|
||||
Поле `"fw"` в `session_start` — версия прошивки. При несовместимых изменениях
|
||||
протокола — bump `FIRMWARE_TEST_VERSION` в `protocol.h` с обновлением этого
|
||||
документа. Хост должен проверять `"fw"` и предупреждать оператора при
|
||||
несовпадении ожидаемой версии.
|
||||
|
|
|
|||
|
|
@ -2,30 +2,31 @@
|
|||
|
||||
## Обзор стека
|
||||
|
||||
```bash
|
||||
devcontainer хост
|
||||
───────────────────────────────── ────────────────────────────────────
|
||||
tests/target/<name>/ tools/hil/
|
||||
main.c ← C-прошивка с CLI test_<name>.py ← pytest-тесты
|
||||
CMakeLists.txt conftest.py ← фикстуры (общие)
|
||||
m5/agent.py ← агент M5 (если нужен)
|
||||
CMakePresets.json
|
||||
target-debug-build just/host.just
|
||||
└── targets: [test_<name>] hil-run, hil-<name>
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph DC["Devcontainer"]
|
||||
C["tests/target/<name>/\nmain.c — C-прошивка с CLI\nCMakeLists.txt"]
|
||||
CP["CMakePresets.json\ntarget-debug-build"]
|
||||
JB["just/build.just\nbuild-hil"]
|
||||
C --> CP --> JB
|
||||
end
|
||||
|
||||
just/build.just
|
||||
build-hil
|
||||
subgraph Host["Хост"]
|
||||
PY["tools/hil/\ntest_<name>.py — pytest\nconftest.py — фикстуры"]
|
||||
JH["just/host.just\nhil-run, hil-<name>"]
|
||||
PY --> JH
|
||||
end
|
||||
```
|
||||
|
||||
Три типа тестов:
|
||||
|
||||
| Тип | Использует M5 | Запуск | Когда применять |
|
||||
| ----------------- | ------------- | --------------------- | ----------------------------------------------------- |
|
||||
| **Базовый** | Нет | `hil-run` | Тестирование UART CLI, алгоритмов, таймингов |
|
||||
| **С M5** | Да | `hil-run` | Тестирование GPIO, оптовходов, реле, питания |
|
||||
| **Базовый** | Нет | `hil-run` | UART CLI, алгоритмы, тайминги |
|
||||
| **С M5** | Да | `hil-run` | GPIO, оптовходы, реле, питание |
|
||||
| **Интерактивный** | Нет / Да | `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_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)
|
||||
{
|
||||
|
|
@ -77,7 +78,6 @@ int main(void)
|
|||
bsp_uart_host_init(CLI_BAUD_RATE);
|
||||
bsp_led_on(LED_HEARTBEAT);
|
||||
|
||||
/* Шлём READY пока хост не открыл порт */
|
||||
while (bsp_uart_host_rx_available() == 0U) {
|
||||
bsp_uart_host_write_str("READY\r\n");
|
||||
bsp_delay(200U);
|
||||
|
|
@ -85,7 +85,6 @@ int main(void)
|
|||
|
||||
static uint8_t s_line_buf[CLI_LINE_MAX];
|
||||
for (;;) {
|
||||
/* Если тест использует прерывания/process() — вызывать здесь */
|
||||
size_t len = cli_read_line(s_line_buf, sizeof(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
|
||||
add_subdirectory(host_uart)
|
||||
add_subdirectory(hil_opto)
|
||||
add_subdirectory(<name>) # ← добавить строку
|
||||
add_subdirectory(<name>) # ← добавить
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -153,13 +152,13 @@ add_subdirectory(<name>) # ← добавить строку
|
|||
|
||||
---
|
||||
|
||||
## Шаг 4 — Сборка
|
||||
## Шаг 4 — Собрать
|
||||
|
||||
```bash
|
||||
# В devcontainer:
|
||||
just build::build-hil
|
||||
|
||||
# Проверить что новый таргет собрался:
|
||||
# Проверить:
|
||||
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`: добавить фикстуры
|
||||
|
||||
Открыть `tools/hil/conftest.py` и добавить:
|
||||
|
||||
1. Фикстуру загрузки `loaded_<n>` в конец раздела загрузок.
|
||||
2. Одну строку в `_UART_FIXTURE_MAP` — фабрика `_make_uart_fixture` автоматически
|
||||
создаст фикстуру `uart_<n>` через контекстный менеджер `_uart_context`.
|
||||
|
||||
### Базовый тест (без M5)
|
||||
|
||||
```python
|
||||
# 1. Фикстура загрузки — добавить в раздел loaded_*
|
||||
# 1. Фикстура загрузки
|
||||
@pytest.fixture(scope="module")
|
||||
def loaded_<n>(request: pytest.FixtureRequest) -> None:
|
||||
_load_elf(
|
||||
|
|
@ -184,22 +177,19 @@ def loaded_<n>(request: pytest.FixtureRequest) -> None:
|
|||
Path(cfg.BUILD_DIR) / "tests/target/<n>/test_<n>.elf",
|
||||
)
|
||||
|
||||
# 2. UART-фикстура — добавить одну строку в словарь
|
||||
# 2. UART-фикстура — одна строка в словарь
|
||||
_UART_FIXTURE_MAP = {
|
||||
...
|
||||
"uart_<n>": "loaded_<n>", # ← добавить
|
||||
"uart_<n>": "loaded_<n>",
|
||||
}
|
||||
```
|
||||
|
||||
### Тест с M5 (GPIO, реле, питание)
|
||||
### Тест с M5
|
||||
|
||||
```python
|
||||
# 1. Фикстура загрузки — зависимость от m5 гарантирует питание
|
||||
# 1. Зависимость от m5 гарантирует что питание включено до загрузки ELF
|
||||
@pytest.fixture(scope="module")
|
||||
def loaded_<n>(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
||||
"""
|
||||
Зависит от m5 — питание таргета уже включено к моменту загрузки ELF.
|
||||
"""
|
||||
_load_elf(
|
||||
request,
|
||||
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-фикстура — та же одна строка
|
||||
_UART_FIXTURE_MAP = {
|
||||
...
|
||||
"uart_<n>": "loaded_<n>", # ← добавить
|
||||
"uart_<n>": "loaded_<n>",
|
||||
}
|
||||
```
|
||||
|
||||
**Правило:** если тест управляет железом через M5 — `loaded_<n>` должен явно
|
||||
зависеть от `m5`. Это гарантирует что питание включено до того как pyOCD
|
||||
попытается подключиться к MCU.
|
||||
|
||||
> Ручное написание `uart_<n>` фикстур больше не требуется — фабрика
|
||||
> `_make_uart_fixture` создаёт фикстуру с `_uart_context` (контекстный менеджер,
|
||||
> гарантирует `ser.close()` при любом исходе).
|
||||
зависеть от `m5`, иначе pyOCD попытается подключиться до включения питания.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 6 — `tools/hil/test_<name>.py`
|
||||
|
||||
### Базовый тест (без M5)
|
||||
### Базовый тест
|
||||
|
||||
```python
|
||||
"""test_<name>.py — HIL тест <что тестируем>."""
|
||||
|
|
@ -239,23 +224,21 @@ class Test<Name>:
|
|||
self.ser = uart_<name>
|
||||
|
||||
def test_ping(self):
|
||||
"""Базовая проверка канала."""
|
||||
assert uart_cmd(self.ser, "PING") == "PONG"
|
||||
|
||||
def test_something(self):
|
||||
resp = uart_cmd(self.ser, "MY_CMD")
|
||||
assert resp == "EXPECTED"
|
||||
assert uart_cmd(self.ser, "MY_CMD") == "EXPECTED"
|
||||
```
|
||||
|
||||
### Тест с M5
|
||||
|
||||
```python
|
||||
"""test_<name>.py — HIL тест <что тестируем> через M5StampPLC."""
|
||||
"""test_<name>.py — HIL тест через M5StampPLC."""
|
||||
import time
|
||||
import pytest
|
||||
from conftest import uart_cmd
|
||||
|
||||
SETTLE_S = 0.15 # ждать после переключения реле
|
||||
SETTLE_S = 0.15
|
||||
|
||||
|
||||
class Test<Name>:
|
||||
|
|
@ -264,24 +247,19 @@ class Test<Name>:
|
|||
def _setup(self, uart_<name>, m5):
|
||||
self.ser = uart_<name>
|
||||
self.m5 = m5
|
||||
self.m5.opto_all_off() # или другой сброс состояния стенда
|
||||
self.m5.opto_all_off()
|
||||
time.sleep(SETTLE_S)
|
||||
|
||||
def test_ping(self):
|
||||
assert uart_cmd(self.ser, "PING") == "PONG"
|
||||
|
||||
def test_m5_ping(self):
|
||||
self.m5.ping()
|
||||
|
||||
def test_something_with_relay(self):
|
||||
self.m5.opto_set(1, True)
|
||||
time.sleep(SETTLE_S)
|
||||
assert uart_cmd(self.ser, "READ_INPUT") == "ACTIVE"
|
||||
```
|
||||
|
||||
**Важно про таймауты:** после переключения реле нужно ждать:
|
||||
реле (~10 мс) + оптопара (~0.1 мс) + дебаунс прошивки + один цикл `process()`.
|
||||
Используй активное ожидание вместо фиксированного `sleep` там где важна скорость:
|
||||
Для критичных к скорости тестов — активное ожидание вместо фиксированного `sleep`:
|
||||
|
||||
```python
|
||||
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}'")
|
||||
```
|
||||
|
||||
### Интерактивный тест (оператор нажимает кнопки / смотрит на дисплей)
|
||||
|
||||
Добавить маркер на класс. Для ввода использовать `/dev/tty` напрямую — `input()` не работает под захватом pytest даже с `-s`:
|
||||
### Интерактивный тест
|
||||
|
||||
```python
|
||||
"""test_<name>.py — интерактивный HIL-тест <что тестируем>."""
|
||||
"""test_<name>.py — интерактивный HIL-тест."""
|
||||
import time
|
||||
import pytest
|
||||
from conftest import uart_cmd
|
||||
|
||||
SETTLE_S = 0.10 # ждать после действия оператора (debounce и т.п.)
|
||||
SETTLE_S = 0.10
|
||||
|
||||
|
||||
def _operator_prompt(msg: str) -> None:
|
||||
"""Вывести подсказку и дождаться Enter от оператора.
|
||||
Читает /dev/tty напрямую — работает независимо от захвата pytest."""
|
||||
"""Вывести подсказку и ждать Enter. Читает /dev/tty напрямую — работает
|
||||
независимо от захвата pytest."""
|
||||
with open("/dev/tty", "w") as tty_out:
|
||||
tty_out.write(f"\n >>> {msg}\n Нажмите Enter когда готово...\n")
|
||||
tty_out.flush()
|
||||
|
|
@ -332,14 +308,7 @@ class Test<Name>:
|
|||
assert uart_cmd(self.ser, "MY_CMD") == "EXPECTED"
|
||||
```
|
||||
|
||||
**Запуск интерактивных тестов:**
|
||||
|
||||
```bash
|
||||
just host::hil-run-interactive # все интерактивные
|
||||
just host::hil-button # конкретный интерактивный
|
||||
```
|
||||
|
||||
**Правило:** интерактивные тесты **не добавлять** в `hil-run` — они входят только в `hil-run-interactive`.
|
||||
Запуск: `just host::hil-run-interactive` или `just host::hil-<name>` (с флагом `-s`).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -365,86 +334,50 @@ 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_<n>\n(module scope)"]
|
||||
LN["loaded_<n>\n(module scope)"]
|
||||
M5["m5\n(module scope, если нужен)"]
|
||||
|
||||
```bash
|
||||
test_foo()
|
||||
└── _setup (function scope, autouse)
|
||||
├── uart_<n> (module scope) ← открыт один раз на весь файл
|
||||
│ └── loaded_<n> ← ELF загружен один раз
|
||||
│ └── m5 ← (если нужен) питание включено
|
||||
└── m5 (module scope) ← (если нужен напрямую в тесте)
|
||||
TF --> SU
|
||||
SU --> UN --> LN
|
||||
LN --> M5
|
||||
```
|
||||
|
||||
`scope=module` — фикстура создаётся один раз на весь тест-файл, уничтожается
|
||||
после последнего теста. ELF грузится один раз, порт открывается один раз.
|
||||
`scope=module` — фикстура создаётся один раз на весь тест-файл. ELF грузится
|
||||
один раз, порт открывается один раз.
|
||||
|
||||
### Порядок при запуске нескольких файлов
|
||||
|
||||
```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 перезагружается между файлами.
|
||||
**Порядок при нескольких файлах:** каждый файл — своя загрузка ELF, свой
|
||||
UART-сеанс. MCU перезагружается между файлами.
|
||||
|
||||
---
|
||||
|
||||
## Чеклист
|
||||
|
||||
### Автоматический тест (базовый или с M5)
|
||||
### Автоматический тест
|
||||
|
||||
```bash
|
||||
[ ] tests/target/<n>/main.c — C-прошивка с CLI + READY-паттерн
|
||||
[ ] tests/target/<n>/CMakeLists.txt — сборка с bsp_boot_ram
|
||||
[ ] tests/target/<n>/main.c — C-прошивка с CLI + READY-паттерн
|
||||
[ ] tests/target/<n>/CMakeLists.txt — сборка с bsp_boot_ram
|
||||
[ ] tests/target/CMakeLists.txt — add_subdirectory(<n>)
|
||||
[ ] CMakePresets.json — добавить test_<n> в targets
|
||||
[ ] tools/hil/conftest.py — loaded_<n> + строка в _UART_FIXTURE_MAP
|
||||
[ ] tools/hil/test_<n>.py — pytest-тесты
|
||||
[ ] tools/hil/test_<n>.py — pytest-тесты
|
||||
[ ] just/host.just — рецепт hil-<n> (опционально)
|
||||
[ ] just build::build-hil — зелёная сборка
|
||||
[ ] just host::hil-<n> — зелёный прогон
|
||||
[ ] just host::hil-<n> — зелёный прогон
|
||||
```
|
||||
|
||||
### Интерактивный тест (дополнительно к базовому чеклисту)
|
||||
### Дополнительно для интерактивного теста
|
||||
|
||||
```bash
|
||||
[ ] @pytest.mark.interactive — пометить класс в test_<n>.py
|
||||
[ ] just/host.just — рецепт hil-<n> с флагом -s
|
||||
[ ] just host::hil-run-interactive — зелёный прогон
|
||||
[ ] убедиться что just host::hil-run — NOT в выборке (маркер исключает)
|
||||
[ ] убедиться что just host::hil-run — НЕ включает этот тест
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
devcontainer
|
||||
─────────────────────────────────────────────────────────────────
|
||||
tests/host/<n>/test_<n>.c ← тест (Unity + опционально fff)
|
||||
tests/host/CMakeLists.txt ← регистрация через add_host_test()
|
||||
tests/host/mocks/ ← stub-хедеры NXP SDK (если нужны)
|
||||
|
||||
CMakePresets.json just/build.just
|
||||
host-debug-build test-host
|
||||
└── targets: [test_<n>] cmake --build + ctest
|
||||
GDB_PORT=3333
|
||||
PYOCD_TARGET=mimxrt1050_quadspi
|
||||
PYOCD_FREQUENCY=4000000
|
||||
FCB_PATH=tools/host/dcd/w25q128_fdcb.bin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 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 |
|
||||
|
||||
| Категория | Описание | Инструментарий |
|
||||
| --------- | -------------------------------------------------------------- | ------------------------- |
|
||||
| **A** | Нет вызовов NXP SDK: алгоритмы, парсеры, FSM, структуры данных | Unity |
|
||||
| **B** | BSP-модуль вызывает `fsl_*.h`, USB-стек и т.д. | Unity + fff + stub-хедеры |
|
||||
|
||||
**Признак категории A:** в `.c` файле модуля нет ни одного `#include "fsl_*.h"`.
|
||||
**Признак категории B:** есть хотя бы один такой include.
|
||||
Все три — XIP-прошивки, исполняются из QuadSPI NOR Flash (`0x60000000`).
|
||||
|
||||
---
|
||||
|
||||
## Шаг 1 — Создать тестовый файл
|
||||
## Режимы запуска отладки
|
||||
|
||||
### Категория A — платформонезависимый модуль
|
||||
|
||||
```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 — Запустить
|
||||
### Режим А — прошивка уже в Flash
|
||||
|
||||
```bash
|
||||
# Сборка + все тесты одной командой (devcontainer)
|
||||
just build::test-host
|
||||
# 1. Хост — запустить GDB-сервер (оставить в отдельном терминале)
|
||||
just host::debug-server
|
||||
|
||||
# Только новый тест
|
||||
ctest --preset host-debug-test -R test_<n> -V
|
||||
|
||||
# Напрямую — виден полный вывод Unity без CTest-обёртки
|
||||
./build/host-debug/tests/host/test_<n>
|
||||
# 2. DevContainer — VSCode
|
||||
# Run & Debug (Ctrl+Shift+D) → выбрать конфигурацию → F5
|
||||
```
|
||||
|
||||
---
|
||||
GDB сбрасывает MCU, загружает символы из ELF и останавливается на входе
|
||||
в `main`. Flash не перезаписывается.
|
||||
|
||||
## Справочник: Unity assertions
|
||||
|
||||
```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'`.
|
||||
|
||||
---
|
||||
|
||||
## Полный цикл
|
||||
### Режим Б — прошить через SWD, затем отладить
|
||||
|
||||
```bash
|
||||
# 1. Создать тестовый файл
|
||||
tests/host/<n>/test_<n>.c
|
||||
# 1. DevContainer
|
||||
just build::hab-firmware-test-debug
|
||||
|
||||
# 2. Создать stub-хедер (если категория B и stub не существует)
|
||||
tests/host/mocks/fsl_<driver>.h
|
||||
# 2. Хост
|
||||
just host::flash-swd-test-debug
|
||||
|
||||
# 3. Добавить вызов add_host_test() в
|
||||
tests/host/CMakeLists.txt
|
||||
# 3. ⚡ Power cycle платы (обязательно)
|
||||
|
||||
# 4. Добавить "test_<n>" в targets в
|
||||
CMakePresets.json ← host-debug-build и host-release-build
|
||||
# 4. Хост
|
||||
just host::debug-server
|
||||
|
||||
# 5. Запустить в devcontainer
|
||||
just build::test-host
|
||||
# 5. DevContainer — VSCode → 🐛 Debug: firmware_test → F5
|
||||
```
|
||||
|
||||
### Режим В — прошить через 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
|
||||
[ ] Определена категория (A или B)
|
||||
[ ] tests/host/<n>/test_<n>.c — тест с main(), setUp(), tearDown()
|
||||
[ ] tests/host/mocks/fsl_<driver>.h — stub (только категория B, если нет)
|
||||
[ ] tests/host/CMakeLists.txt — add_host_test(NAME test_<n> ...)
|
||||
[ ] CMakePresets.json — добавить test_<n> в host-debug-build
|
||||
[ ] just build::test-host — зелёный прогон
|
||||
0x60000000 w25q128_fdcb.bin (512 байт) — FCB
|
||||
0x60000200 0xFF × 3584 байт — padding
|
||||
0x60001000 firmware_test_hab.bin — IVT + DCD + код
|
||||
```
|
||||
|
||||
Весь диапазон `0x60000000–0x6000FFFF` — один 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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ add_executable(
|
|||
src/tests/test_qspi.c
|
||||
src/tests/test_usd.c
|
||||
src/tests/test_display.c
|
||||
src/tests/test_buttons.c
|
||||
${BSP_GENERATED}/clock_config.c
|
||||
${BSP_STARTUP_FILE}
|
||||
${BSP_SYSCALLS_FILE})
|
||||
|
|
@ -38,6 +39,7 @@ target_link_libraries(
|
|||
${TARGET_NAME}
|
||||
PRIVATE bsp_board
|
||||
bsp_led
|
||||
bsp_button
|
||||
bsp_display
|
||||
bsp_tick
|
||||
bsp_boot_xip
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# firmware_test — Plan of Development
|
||||
# firmware_test — План разработки
|
||||
|
||||
> Версия: 0.4 | Обновлён после завершения Этапов 1–3 (bsp_sdram + bsp_qspi_flash).
|
||||
> Версия: 0.5 | Обновлён после завершения Этапа 5 (display + buttons).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -18,23 +18,22 @@
|
|||
|
||||
## Текущий статус
|
||||
|
||||
| Компонент | Статус | Примечание |
|
||||
| -------------------------- | ------ | -------------------------------------- |
|
||||
| `bsp_usb_cdc` | ✅ | HIL тест пройден |
|
||||
| firmware_test скелет | ✅ | `main.c` + `cli.c` |
|
||||
| Протокол v2 + test_runner | ✅ | JSON-lines event-driven |
|
||||
| `bsp_sdram` + `test_sdram` | ✅ | 4 фазы: addr/data/seq/retention |
|
||||
| `bsp_qspi_flash` | ✅ | W25Q64/128/256/512, ITCM, IRQ lock |
|
||||
| `test_qspi` | ✅ | JEDEC + erase + rw + addr range |
|
||||
| `bsp_usd` | ✅ | bsp_sd + FatFS (firmware_test_fatfs) |
|
||||
| `test_usd` | ✅ | pre_confirm + 4 шага + progress events |
|
||||
| Display test | ⬜ | Этап 5 |
|
||||
| Button test | ⬜ | Этап 5 |
|
||||
| CAN test | ⬜ | Этап 6 (bsp_can ✅) |
|
||||
| UART TTL test | ⬜ | Этап 6 (bsp_uart_host ✅) |
|
||||
| UART ISO test | ⬜ | Этап 6 |
|
||||
| Opto test | ⬜ | Этап 6 (bsp_opto ✅) |
|
||||
| Provisioning | ⬜ | Этап 7 |
|
||||
| Компонент | Статус | Примечание |
|
||||
| ------------------------------ | ------ | -------------------------------------------- |
|
||||
| `bsp_usb_cdc` | ✅ | HIL тест пройден |
|
||||
| firmware_test скелет | ✅ | `main.c` + `cli.c` |
|
||||
| Протокол v2 + test_runner | ✅ | JSON-lines event-driven |
|
||||
| `bsp_sdram` + `test_sdram` | ✅ | 4 фазы: addr/data/seq/retention |
|
||||
| `bsp_qspi_flash` | ✅ | W25Q64/128/256/512, ITCM, IRQ lock |
|
||||
| `test_qspi` | ✅ | JEDEC + erase + rw + addr range |
|
||||
| `bsp_sd` + `test_usd` | ✅ | bsp_sd + FatFS, pre_confirm, 4 шага |
|
||||
| `bsp_display` + `test_display` | ✅ | 4 цвета + ротация, hardware-verified |
|
||||
| `bsp_button` + `test_buttons` | ✅ | 2 кнопки, physical detect, hardware-verified |
|
||||
| CAN test | ⬜ | Этап 6 (bsp_can ✅) |
|
||||
| UART TTL test | ⬜ | Этап 6 (bsp_uart_host ✅) |
|
||||
| UART ISO test | ⬜ | Этап 6 |
|
||||
| Opto test | ⬜ | Этап 6 (bsp_opto ✅) |
|
||||
| Provisioning | ⬜ | Этап 7 |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -52,10 +51,13 @@
|
|||
- **Порядок init в main.c:** `bsp_qspi_init()` до `bsp_tick_init()`.
|
||||
- **IR и RTC:** не реализуются.
|
||||
- **Производственный 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) |
|
||||
| Карта | microSD, вставляется оператором перед тестом |
|
||||
| Файловая система | FatFS (SDK middleware) |
|
||||
| Детект карты | GPIO (CD pin) или опрос через USDHC status |
|
||||
| Детект карты | `USDHC_GetPresentStatusFlags` |
|
||||
|
||||
### BSP API (предварительно)
|
||||
|
||||
```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)
|
||||
### Закрытые решения — Этап 4
|
||||
|
||||
- BSP-слой: `bsp_sd` (host init/deinit/card detect) + `firmware_test_fatfs` (FatFS).
|
||||
`bsp_usd` как отдельный модуль не создавался — тест работает напрямую через `bsp_sd` + `ff.h`.
|
||||
|
|
@ -133,48 +78,43 @@ target_link_libraries(bsp_usd
|
|||
- Паттерн: `byte[i] = i & 0xFF`, 4096 байт.
|
||||
- `critical = false`: тест не блокирует HIL-тесты при отсутствии карты.
|
||||
- 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`.
|
||||
|
||||
---
|
||||
|
||||
## Этап 5 — Display + Button (интерактивные)
|
||||
|
||||
### 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 с на каждую кнопку.
|
||||
## Этап 5 — Display + Buttons ✅
|
||||
|
||||
### Аппаратный контекст кнопок
|
||||
|
||||
| Кнопка | Пин MCU | GPIO |
|
||||
| ---------- | ---------- | --------- |
|
||||
| Test_But_1 | GPIO_B1_14 | GPIO2[30] |
|
||||
| Test_But_2 | GPIO_B1_15 | GPIO2[31] |
|
||||
| Кнопка | Пин MCU | GPIO | Схема | Нажатие |
|
||||
| ---------- | ---------- | --------- | ----------------------------- | ------- |
|
||||
| Test_But_1 | GPIO_B1_14 | GPIO2[30] | SWT6x6, pull-up к 3V3 внешний | LOW |
|
||||
| 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`. Запускаются только при наличии стенда.
|
||||
BSP для всех трёх уже готов.
|
||||
|
|
@ -184,9 +124,10 @@ BSP для всех трёх уже готов.
|
|||
M5StampPLC отправляет CAN-фрейм → плата принимает → сравниваем ID и payload.
|
||||
|
||||
**Шаги:**
|
||||
1. M5 → `{"cmd":"can_send","id":0x100,"data":[0xDE,0xAD,0xBE,0xEF]}` (через `confirm_request`)
|
||||
2. Таргет: `uart_cmd("CAN_RECV 500")` → `"100 DEADBEEF"` или `"TIMEOUT"`
|
||||
3. Ответный: таргет посылает → M5 `can_recv` → верификация
|
||||
1. Таргет посылает `confirm_request` → M5 получает команду `can_send`
|
||||
2. M5 → `{"cmd":"can_send","id":0x100,"data":[0xDE,0xAD,0xBE,0xEF]}`
|
||||
3. Таргет: ожидает CAN-фрейм, 500 мс → верификация ID и payload
|
||||
4. Ответный: таргет посылает → M5 `can_recv` → верификация
|
||||
|
||||
### 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`.
|
||||
|
||||
**Параметры стенда (из HIL_BENCH.md):**
|
||||
|
||||
```
|
||||
RLY2 → RS_RX (BSP_OPTO_CH_RS) GPIO1[23]
|
||||
RLY3 → EXT_IN1 (BSP_OPTO_CH_IN1) GPIO1[22]
|
||||
|
|
@ -221,7 +163,7 @@ tools/hil/
|
|||
```
|
||||
|
||||
Фикстура `firmware_cdc` открывает CDC порт firmware_test (прошит в Flash),
|
||||
посылает JSON команды, читает события. Аналог `uart_cmd` для USB CDC.
|
||||
посылает JSON команды, читает события.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -241,27 +183,27 @@ tools/hil/
|
|||
bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len); /* 8 байт из OCOTP */
|
||||
```
|
||||
|
||||
### Открытые вопросы
|
||||
### Открытые вопросы — Этап 7
|
||||
|
||||
- [ ] Что именно записывать как "пройдено": флаг в Flash или только отправить UID?
|
||||
- [ ] Что именно записывать как «пройдено»: флаг в Flash или только отправить UID?
|
||||
- [ ] Нужна ли защита от повторного provisioning (write-once)?
|
||||
|
||||
---
|
||||
|
||||
## Матрица тестов — итоговая
|
||||
|
||||
| ID | Название | Тип | Critical | HIL (M5) | BSP | Статус |
|
||||
| ---------- | -------------- | ----------- | -------- | -------- | ----------------- | ------ |
|
||||
| — | PING | cmd | — | ❌ | — | ✅ |
|
||||
| `sdram` | SDRAM 32MB | self | ✅ | ❌ | `bsp_sdram` ✅ | ✅ |
|
||||
| `qspi` | QSPI Flash | self | ✅ | ❌ | `bsp_qspi_flash`✅ | ✅ |
|
||||
| `usd` | uSD (SDIO) | interactive | ❌ | ❌ | `bsp_sd` ✅ | ✅ |
|
||||
| `display` | Display RGB888 | interactive | ❌ | ❌ | существующий BSP | ⬜ |
|
||||
| `buttons` | Test_But_1/2 | interactive | ❌ | ❌ | `bsp_button` ✅ | ⬜ |
|
||||
| `can` | CAN loopback | HIL | ❌ | ✅ | `bsp_can` ✅ | ⬜ |
|
||||
| `uart_ttl` | UART TTL | HIL | ❌ | ✅ | `bsp_uart_host`✅ | ⬜ |
|
||||
| `uart_iso` | UART ISO +24V | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ |
|
||||
| `opto` | Opto-in EXT | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ |
|
||||
| ID | Название | Тип | Critical | HIL (M5) | BSP | Статус |
|
||||
| ---------- | -------------- | ----------- | -------- | -------- | ------------------ | ------ |
|
||||
| — | PING | cmd | — | ❌ | — | ✅ |
|
||||
| `sdram` | SDRAM 32 MB | self | ✅ | ❌ | `bsp_sdram` ✅ | ✅ |
|
||||
| `qspi` | QSPI Flash | self | ✅ | ❌ | `bsp_qspi_flash` ✅ | ✅ |
|
||||
| `usd` | uSD (SDIO) | interactive | ❌ | ❌ | `bsp_sd` ✅ | ✅ |
|
||||
| `display` | Display RGB888 | interactive | ❌ | ❌ | `bsp_display` ✅ | ✅ |
|
||||
| `buttons` | Test_But_1/2 | interactive | ❌ | ❌ | `bsp_button` ✅ | ✅ |
|
||||
| `can` | CAN loopback | HIL | ❌ | ✅ | `bsp_can` ✅ | ⬜ |
|
||||
| `uart_ttl` | UART TTL | HIL | ❌ | ✅ | `bsp_uart_host` ✅ | ⬜ |
|
||||
| `uart_iso` | UART ISO +24V | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ |
|
||||
| `opto` | Opto-in EXT | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -272,23 +214,19 @@ bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len); /* 8 байт из
|
|||
✅ Этап 2 (bsp_sdram + test_sdram)
|
||||
✅ Этап 3 (bsp_qspi_flash + test_qspi)
|
||||
✅ Этап 4 (bsp_sd + test_usd)
|
||||
⬜ Этап 5 (display + buttons) ← ТЕКУЩИЙ
|
||||
⬜ Этап 6 (CAN + UART + Opto, HIL)
|
||||
✅ Этап 5 (display + buttons)
|
||||
⬜ Этап 6 (CAN + UART + Opto, HIL) ← ТЕКУЩИЙ
|
||||
⬜ Этап 7 (provisioning)
|
||||
⬜ Этап 8 (tools/production/ TUI runner) ← параллельно с 6-7
|
||||
⬜ Этап 8 (tools/production/ TUI runner) ← параллельно с 6-7
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Хостовое ПО производственного прогона (Этап 8)
|
||||
|
||||
**Решение принято (Вариант D):** отдельное приложение `tools/production/`,
|
||||
без pytest, с TUI (Textual).
|
||||
**Решение принято (Вариант D):** отдельное приложение `tools/production/`, без pytest, с TUI (Textual).
|
||||
|
||||
Подробная архитектура описана в предыдущей версии плана (v0.2, раздел
|
||||
"Открытый вопрос: ПО на стороне хоста").
|
||||
|
||||
### Открытые вопросы (перед Этапом 8)
|
||||
### Открытые вопросы — Этап 8
|
||||
|
||||
- [ ] TUI: Textual или Rich или plain print на первой итерации?
|
||||
- [ ] БД: SQLite локально или REST API?
|
||||
|
|
|
|||
|
|
@ -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_USD;
|
||||
extern const test_module_t K_TEST_DISPLAY;
|
||||
extern const test_module_t K_TEST_BUTTONS;
|
||||
|
||||
static const test_module_t *const k_registry[] = {
|
||||
/* populated starting from Этап 2 */
|
||||
&K_TEST_SDRAM,
|
||||
&K_TEST_QSPI,
|
||||
&K_TEST_USD,
|
||||
&K_TEST_DISPLAY,
|
||||
|
||||
&K_TEST_SDRAM, &K_TEST_QSPI, &K_TEST_USD, &K_TEST_DISPLAY, &K_TEST_BUTTONS,
|
||||
};
|
||||
|
||||
#define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0]))
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
175
firmware/test/src/tests/test_buttons.c
Normal file
175
firmware/test/src/tests/test_buttons.c
Normal 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(¶ms);
|
||||
|
||||
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,
|
||||
};
|
||||
|
|
@ -1,34 +1,39 @@
|
|||
# port/ — porting layer
|
||||
|
||||
Glue-код между сторонними библиотеками (`lib/`) и платформой (`bsp/`).
|
||||
Glue-код между сторонними библиотеками (`lib/`, `utils/`) и платформой (`bsp/`).
|
||||
|
||||
---
|
||||
|
||||
## Зачем нужен отдельный каталог
|
||||
## Концепция
|
||||
|
||||
В проекте три слоя кода с чёткими границами:
|
||||
В проекте четыре слоя с чёткими границами:
|
||||
|
||||
```bash
|
||||
lib/ сторонний код — ничего не знает о проекте
|
||||
bsp/ железо — драйверы периферии MIMXRT1052
|
||||
utils/ платформонезависимые алгоритмы (ring_buffer, log и др.)
|
||||
port/ ← glue: адаптирует lib/ и utils/ к конкретной платформе
|
||||
firmware/ бизнес-логика — использует всё вышеперечисленное
|
||||
```mermaid
|
||||
graph TB
|
||||
FW["firmware/*\nбизнес-логика"]
|
||||
PORT["port/\nglue: адаптирует utils/ и lib/ к платформе"]
|
||||
BSP["bsp/\nдрайверы периферии MIMXRT1052"]
|
||||
UTILS["utils/\nплатформонезависимые алгоритмы"]
|
||||
LIB["lib/ + sdk/\nсторонний код"]
|
||||
|
||||
FW --> PORT
|
||||
FW --> BSP
|
||||
FW --> UTILS
|
||||
PORT --> BSP
|
||||
PORT --> UTILS
|
||||
PORT --> LIB
|
||||
```
|
||||
|
||||
Код попадает в `port/` если выполняются оба условия:
|
||||
|
||||
1. Связывает платформонезависимую библиотеку/утилиту с конкретным BSP.
|
||||
1. Связывает платформонезависимую библиотеку / утилиту с конкретным BSP.
|
||||
2. Сам по себе не является ни библиотекой, ни драйвером.
|
||||
|
||||
Примеры: адаптер логгера к UART, diskio-реализация FatFS поверх bsp_sdio,
|
||||
FreeRTOS heap и assert-хуки.
|
||||
|
||||
Код НЕ попадает в `port/` если:
|
||||
**Не попадает в `port/`:**
|
||||
|
||||
- Не зависит от `bsp/` → идёт в `utils/`
|
||||
- Является самостоятельным драйвером периферии → идёт в `bsp/`
|
||||
- Это сторонняя библиотека без изменений → идёт в `lib/`
|
||||
- Самостоятельный драйвер периферии → идёт в `bsp/`
|
||||
- Сторонняя библиотека без изменений → идёт в `lib/`
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -38,12 +43,35 @@ FreeRTOS heap и assert-хуки.
|
|||
port/
|
||||
├── CMakeLists.txt
|
||||
├── README.md ← этот файл
|
||||
└── log/ ← UART-адаптер для utils/log
|
||||
├── fatfs/ ← diskio поверх bsp_sd / bsp_qspi
|
||||
# Планируется:
|
||||
└── freertos/ ← heap_4.c, configASSERT, vApplicationHooks
|
||||
├── log/ ← UART-адаптер для utils/log
|
||||
│ ├── CMakeLists.txt # таргет port_log_uart
|
||||
│ ├── README.md
|
||||
│ ├── 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_fatfs_sd`. Позволяет иметь несколько адаптеров для одной библиотеки.
|
||||
|
||||
**Include-путь:** `#include "port/<модуль>.h"` — публичные заголовки
|
||||
всегда в `port/<модуль>/include/port/`.
|
||||
**Include-путь:** публичные заголовки в `port/<модуль>/include/port/`,
|
||||
подключение через `#include "port/<модуль>.h"`.
|
||||
|
||||
**Не компилируется для host-тестов:** `bsp/` недоступен на хосте, поэтому
|
||||
`port/CMakeLists.txt` возвращает управление при `BUILD_TESTS_HOST=ON`.
|
||||
**Host-сборка:** `port/CMakeLists.txt` содержит ранний `return()` при
|
||||
`BUILD_TESTS_HOST=ON` — `bsp/` недоступен на хосте.
|
||||
|
|
|
|||
89
port/fatfs/README.md
Normal file
89
port/fatfs/README.md
Normal 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()` |
|
||||
|
|
@ -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
|
||||
#include "port/log_uart.h"
|
||||
|
|
@ -18,27 +51,38 @@ int main(void)
|
|||
bsp_tick_init();
|
||||
bsp_uart_host_init(115200U);
|
||||
|
||||
log_uart_init(); // регистрирует транспорт, после этого LOG_* работают
|
||||
log_uart_init(); /* регистрирует транспорт и timestamp */
|
||||
|
||||
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()`.
|
||||
2. Предоставляет strong-реализацию `log_get_timestamp_ms()` → `bsp_tick_get_ms()`.
|
||||
Для потокобезопасности создать мьютекс **до** `log_uart_init()`:
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
Транзитивно подтягивает `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()` |
|
||||
|
|
|
|||
300
project_tree.txt
300
project_tree.txt
|
|
@ -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
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
# utils
|
||||
# utils — платформонезависимые утилиты
|
||||
|
||||
Платформонезависимые утилиты проекта.
|
||||
|
||||
**Правило включения** — код попадает сюда только если выполняются оба условия:
|
||||
Общие структуры данных и сервисы проекта. Код попадает сюда только если
|
||||
выполняются оба условия:
|
||||
|
||||
- не зависит от железа (нет `fsl_*`, CMSIS, FreeRTOS, BSP);
|
||||
- используется более чем в одном месте проекта.
|
||||
|
|
@ -13,7 +12,36 @@
|
|||
|
||||
## Модули
|
||||
|
||||
| Модуль | Путь | Описание |
|
||||
| ------------- | ------------------------------------- | -------------------------------------------------- |
|
||||
| `ring_buffer` | [ring_buffer/](ring_buffer/README.md) | SPSC кольцевой буфер байт, lock-free |
|
||||
| `log` | [log/](log/README.md) | Платформонезависимый логгер с callback-транспортом |
|
||||
| Модуль | Путь | Описание |
|
||||
| ------------- | ---------------------------------------------- | ------------------------------------ |
|
||||
| `ring_buffer` | [ring_buffer/README.md](ring_buffer/README.md) | SPSC кольцевой буфер байт, lock-free |
|
||||
| `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).
|
||||
|
|
|
|||
|
|
@ -1,46 +1,71 @@
|
|||
# utils/log
|
||||
# log — платформонезависимый логгер
|
||||
|
||||
Платформонезависимый логгер с callback-транспортом.
|
||||
|
||||
Ядро логгера (`log.c` / `log.h`) не знает о конкретном транспорте — UART,
|
||||
USB CDC, Flash и т.д. Транспорт подключается через `log_init()` в виде
|
||||
callback-функции. Адаптеры живут в `port/log/`.
|
||||
Ядро логгера (`log.c` / `log.h`) не знает о конкретном транспорте. Транспорт
|
||||
подключается через `log_init()` в виде callback-функции. Адаптеры живут в
|
||||
`port/log/`.
|
||||
|
||||
| Параметр | Значение |
|
||||
| ------------------ | ------------------------------------------------- |
|
||||
| Формат | `[ timestamp][L][TAG] сообщение\r\n` |
|
||||
| Формат строки | `[ timestamp][L][TAG] сообщение\r\n` |
|
||||
| Буфер строки | 256 байт (переопределяется через `LOG_BUF_SIZE`) |
|
||||
| Управление уровнем | `LOG_LEVEL` через CMake `-DLOG_LEVEL=N` |
|
||||
| Thread-safety | мьютекс через weak-хуки (`log_mutex_lock/unlock`) |
|
||||
| Зависимости | `<stdarg.h>`, `<stdio.h>`, `<stddef.h>` |
|
||||
|
||||
---
|
||||
|
||||
## Уровни
|
||||
|
||||
| N | Макрос | Имя |
|
||||
| --- | ------- | -------------------------------------------- |
|
||||
| 0 | — | off — все `LOG_*` → `((void)0)`, нулевой ROM |
|
||||
| 1 | `LOG_E` | error |
|
||||
| 2 | `LOG_W` | warn |
|
||||
| 3 | `LOG_I` | info |
|
||||
| 4 | `LOG_D` | debug |
|
||||
| 5 | `LOG_V` | verbose |
|
||||
| N | Макрос | Уровень | Дефолт |
|
||||
| --- | ------- | ------- | ------------------ |
|
||||
| 0 | — | off | Release (`NDEBUG`) |
|
||||
| 1 | `LOG_E` | error | |
|
||||
| 2 | `LOG_W` | warn | |
|
||||
| 3 | `LOG_I` | info | |
|
||||
| 4 | `LOG_D` | debug | |
|
||||
| 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
|
||||
// main.c — зарегистрировать транспорт
|
||||
/* main.c */
|
||||
#include "log/log.h"
|
||||
#include "port/log_uart.h"
|
||||
|
||||
log_uart_init(); // инициализировать адаптер транспорта
|
||||
log_uart_init();
|
||||
log_init(uart_log_write, NULL);
|
||||
|
||||
// Любой .c файл
|
||||
/* Любой .c файл: */
|
||||
#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_D("UART", "RX=%u bytes", bsp_uart_host_rx_available());
|
||||
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
|
||||
```
|
||||
|
||||
## Транспортный адаптер
|
||||
---
|
||||
|
||||
Адаптер — функция типа `log_write_cb_t`:
|
||||
|
||||
```c
|
||||
typedef void (*log_write_cb_t)(const char *p_buf, size_t len, void *p_ctx);
|
||||
```
|
||||
## Транспортные адаптеры
|
||||
|
||||
Готовые адаптеры в `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) |
|
||||
|
||||
## Мьютекс и временна́я метка (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` файле прошивки:
|
||||
|
||||
```c
|
||||
// firmware/tft_app/src/log_os.c
|
||||
/* firmware/tft_app/src/log_os.c */
|
||||
#include "log/log.h"
|
||||
#include "FreeRTOS.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(); }
|
||||
```
|
||||
|
||||
> ⚠️ `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)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,22 +1,45 @@
|
|||
# util/prio_queue
|
||||
# prio_queue — приоритетная очередь с вытеснением
|
||||
|
||||
Отсортированный массив с фиксированной ёмкостью — generic приоритетная очередь
|
||||
с вытеснением.
|
||||
|
||||
**Типичное использование:** очередь задач, событий или медиадорожек с приоритетами,
|
||||
где число элементов заранее известно и невелико (≤ ~32). При полном буфере новый
|
||||
элемент с более высоким приоритетом вытесняет наименее приоритетный.
|
||||
Отсортированный массив с фиксированной ёмкостью и generic элементами.
|
||||
При полном буфере новый элемент с более высоким приоритетом вытесняет
|
||||
наименее приоритетный. Оптимален для небольших очередей (n ≤ 32):
|
||||
задачи, события, медиадорожки с приоритетами.
|
||||
|
||||
| Параметр | Значение |
|
||||
| ------------- | ----------------------------------------------------------------------- |
|
||||
| Элемент | любой тип, задаётся через `item_size` |
|
||||
| Ёмкость | любая, задаётся при `prio_queue_init`; фиксирована на всё время жизни |
|
||||
| Порядок | определяется `cmp`-функцией пользователя (аналог `qsort`) |
|
||||
| Вставка | O(n) сдвиг; оптимально при n ≤ 32 |
|
||||
| Ёмкость | фиксирована на всё время жизни, задаётся при `prio_queue_init` |
|
||||
| Порядок | определяется `cmp`-функцией пользователя (сигнатура как у `qsort`) |
|
||||
| Вставка | O(n) сдвиг |
|
||||
| Peek-top | O(1) |
|
||||
| Thread-safety | нет; при использовании из нескольких контекстов — внешняя синхронизация |
|
||||
| Зависимости | `<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
|
||||
|
|
@ -25,43 +48,39 @@
|
|||
typedef struct { int priority; const char *name; } task_t;
|
||||
|
||||
/* 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 pb = ((const task_t *)b)->priority;
|
||||
return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
|
||||
}
|
||||
|
||||
static task_t storage[8];
|
||||
static task_t storage[8];
|
||||
static prio_queue_t q;
|
||||
|
||||
/* Инициализация */
|
||||
prio_queue_init(&q, storage, 8, sizeof(task_t), task_cmp);
|
||||
|
||||
/* Вставка */
|
||||
task_t t = { .priority = 2, .name = "send_data" };
|
||||
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);
|
||||
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):
|
||||
→ D приоритетнее C(3) → C вытесняется → [A(1) B(2) D(2)] PQ_EVICTED
|
||||
→ D приоритетнее C(3) → C вытесняется → [A(1) B(2) D(2)] PQ_EVICTED
|
||||
|
||||
Очередь полна [A(1) B(2) C(3)], вставляем E(5):
|
||||
→ E менее приоритетен чем все → отклоняется PQ_FULL
|
||||
→ E менее приоритетен чем все → отклоняется PQ_FULL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comparator
|
||||
|
||||
Сигнатура идентична `qsort`:
|
||||
|
|
@ -76,17 +95,12 @@ typedef int (*pq_cmp_fn)(const void *a, const void *b);
|
|||
| `> 0` | `b` стоит перед `a` |
|
||||
| `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);
|
||||
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);
|
||||
Host unit-тесты: `tests/host/prio_queue/`.
|
||||
|
||||
```bash
|
||||
just build::test-host
|
||||
```
|
||||
|
||||
Тесты: `tests/host/test_prio_queue.c`
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
# util/ring_buffer
|
||||
# ring_buffer — SPSC кольцевой буфер байт
|
||||
|
||||
Кольцевой буфер байт — SPSC (single-producer / single-consumer), lock-free.
|
||||
|
||||
**Типичное использование:** ISR пишет принятые байты, задача или основной цикл
|
||||
читает. Не требует отключения прерываний при условии единственного producer и
|
||||
единственного consumer.
|
||||
Lock-free кольцевой буфер для сценария единственный producer / единственный
|
||||
consumer. Типичное использование: ISR пишет принятые байты, задача или main
|
||||
loop читает — без отключения прерываний.
|
||||
|
||||
| Параметр | Значение |
|
||||
| ------------- | ------------------------------------------------------------------------------ |
|
||||
|
|
@ -13,23 +11,76 @@
|
|||
| Thread-safety | SPSC без блокировок; multi-producer/consumer — только с внешней синхронизацией |
|
||||
| Зависимости | `<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
|
||||
#include "ring_buffer/ring_buffer.h"
|
||||
|
||||
static uint8_t storage[256];
|
||||
static ring_buffer_t rb;
|
||||
static uint8_t storage[256]; /* степень двойки */
|
||||
static ring_buffer_desc_t rb;
|
||||
|
||||
// Инициализация (размер — степень двойки)
|
||||
ring_buffer_init(&rb, storage, sizeof(storage));
|
||||
|
||||
// Запись (например, из ISR)
|
||||
ring_buffer_put(&rb, byte);
|
||||
/* Producer (например, из ISR): */
|
||||
ring_buffer_put(&rb, received_byte);
|
||||
|
||||
// Чтение (например, из задачи)
|
||||
/* Consumer (например, из main loop): */
|
||||
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`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue