# test_display: bsp_display + test_template
This commit is contained in:
parent
da48fbf75d
commit
22f98c311a
37 changed files with 1282 additions and 458 deletions
46
README.md
46
README.md
|
|
@ -8,33 +8,33 @@
|
||||||
|
|
||||||
## Три firmware-проекта
|
## Три firmware-проекта
|
||||||
|
|
||||||
| Проект | Путь | Описание |
|
| Проект | Путь | Описание |
|
||||||
|--------|------|----------|
|
| ------------------- | ---------------------- | -------------------------------------------------------------------------------------- |
|
||||||
| Тестовая прошивка | `firmware/test/` | Входной контроль платы: CAN, UART, SDRAM, QSPI, SDIO, RGB, оптовходы, LED, кнопки, MQS |
|
| Тестовая прошивка | `firmware/test/` | Входной контроль платы: CAN, UART, SDRAM, QSPI, SDIO, RGB, оптовходы, LED, кнопки, MQS |
|
||||||
| Загрузчик | `firmware/bootloader/` | A/B обновление через uSD. Сам обновляется только через USB ROM + blhost |
|
| Загрузчик | `firmware/bootloader/` | A/B обновление через uSD. Сам обновляется только через USB ROM + blhost / SWD |
|
||||||
| Боевая прошивка | `firmware/tft_app/` | FreeRTOS + FatFS + бизнес-логика. Обновляется загрузчиком |
|
| Production прошивка | `firmware/tft_app/` | FreeRTOS + FatFS + бизнес-логика. Обновляется загрузчиком |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## BSP
|
## BSP
|
||||||
|
|
||||||
| Модуль | Путь | Описание |
|
| Модуль | Путь | Описание |
|
||||||
|--------|------|----------|
|
| --------------- | ---------------- | --------------------------------------------------------- |
|
||||||
| `bsp_led` | `bsp/led/` | Два UserLed (GPIO3[3], GPIO3[4]) |
|
| `bsp_led` | `bsp/led/` | Два UserLed (GPIO3[3], GPIO3[4]) |
|
||||||
| `bsp_tick` | `bsp/tick/` | SysTick / FreeRTOS-совместимый таймер |
|
| `bsp_tick` | `bsp/tick/` | SysTick / FreeRTOS-совместимый таймер |
|
||||||
| `bsp_uart_host` | `bsp/uart_host/` | LPUART1 — MCU-Link VCOM (J2) |
|
| `bsp_uart_host` | `bsp/uart_host/` | LPUART1 — MCU-Link VCOM (J2) |
|
||||||
| `bsp_opto` | `bsp/opto/` | Оптоизолированные входы PS2801-4: EXT_IN1, EXT_IN2, RS_RX |
|
| `bsp_opto` | `bsp/opto/` | Оптоизолированные входы PS2801-4: EXT_IN1, EXT_IN2, RS_RX |
|
||||||
| `bsp_usb_cdc` | `bsp/usb_cdc/` | USB CDC ACM |
|
| `bsp_usb_cdc` | `bsp/usb_cdc/` | USB CDC ACM |
|
||||||
| generated | `bsp/generated/` | NXP Config Tools: pin_mux, clock_config, board, startup |
|
| generated | `bsp/generated/` | NXP Config Tools: pin_mux, clock_config, board, startup |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Тестирование
|
## Тестирование
|
||||||
|
|
||||||
| Уровень | Где | Инструменты | Запуск |
|
| Уровень | Где | Инструменты | Запуск |
|
||||||
|---------|-----|-------------|--------|
|
| ---------------- | ------------------------------ | -------------------------------------- | -------------------------------------- |
|
||||||
| Host unit-тесты | `tests/host/` | Unity + fff, clang | `just build::test-host` (devcontainer) |
|
| Host unit-тесты | `tests/host/` | Unity + fff, clang | `just build::test-host` (devcontainer) |
|
||||||
| HIL target-тесты | `tests/target/` + `tools/hil/` | pyOCD + pyserial + pytest + M5StampPLC | `just host::hil-run` (хост) |
|
| HIL target-тесты | `tests/target/` + `tools/hil/` | pyOCD + pyserial + pytest + M5StampPLC | `just host::hil-run` (хост) |
|
||||||
|
|
||||||
**Host-тесты** запускаются в devcontainer без железа. BSP-модули тестируются через fff-фейки и stub-хедеры.
|
**Host-тесты** запускаются в devcontainer без железа. BSP-модули тестируются через fff-фейки и stub-хедеры.
|
||||||
|
|
||||||
|
|
@ -75,12 +75,12 @@ just host::debug-server # GDB-сервер для отладки
|
||||||
|
|
||||||
## Зависимости
|
## Зависимости
|
||||||
|
|
||||||
| | Подход |
|
| | Подход |
|
||||||
|--|--------|
|
| --------------------------------------------- | -------------------- |
|
||||||
| NXP MCUXpresso SDK, FreeRTOS, FatFS, LittleFS | vendored |
|
| NXP MCUXpresso SDK, FreeRTOS, FatFS, LittleFS | vendored |
|
||||||
| Unity, fff, SEGGER RTT | vendored |
|
| Unity, fff, SEGGER RTT | vendored |
|
||||||
| pyOCD, pyserial, pytest, mpremote | `tools/hil/uv.lock` |
|
| pyOCD, pyserial, pytest, mpremote | `tools/hil/uv.lock` |
|
||||||
| spsdk (nxpimage, blhost, sdphost) | `tools/host/uv.lock` |
|
| spsdk (nxpimage, blhost, sdphost) | `tools/host/uv.lock` |
|
||||||
|
|
||||||
Всё что не меняется — vendored. Сборка работает после `git clone` без интернета (кроме Python-зависимостей).
|
Всё что не меняется — vendored. Сборка работает после `git clone` без интернета (кроме Python-зависимостей).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ add_subdirectory(uart_host)
|
||||||
add_subdirectory(opto)
|
add_subdirectory(opto)
|
||||||
add_subdirectory(can)
|
add_subdirectory(can)
|
||||||
add_subdirectory(button)
|
add_subdirectory(button)
|
||||||
|
add_subdirectory(display)
|
||||||
add_subdirectory(usb_cdc)
|
add_subdirectory(usb_cdc)
|
||||||
add_subdirectory(sdram)
|
add_subdirectory(sdram)
|
||||||
add_subdirectory(qspi_flash)
|
add_subdirectory(qspi_flash)
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
|
|
||||||
## Аппаратура
|
## Аппаратура
|
||||||
|
|
||||||
| Кнопка | Пин MCU | GPIO | Схема | Нажатие |
|
| Кнопка | Пин MCU | GPIO | Схема | Нажатие |
|
||||||
|----------------|--------------|-----------|--------------------------------|---------|
|
| -------------- | ---------- | --------- | ----------------------------- | ------- |
|
||||||
| `BSP_BUTTON_1` | GPIO_B1_14 | GPIO2[30] | SWT6x6, pull-up к 3V3 внешний | LOW |
|
| `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 |
|
| `BSP_BUTTON_2` | GPIO_B1_15 | GPIO2[31] | SWT6x6, pull-up к 3V3 внешний | LOW |
|
||||||
|
|
||||||
Пины настроены в `BOARD_InitPins()` (`generated/pin_mux.c`) как INPUT с включённым
|
Пины настроены в `BOARD_InitPins()` (`generated/pin_mux.c`) как INPUT с включённым
|
||||||
гистерезисом, без внутренней подтяжки (`0x0100B0`). `bsp_button_init()` не трогает
|
гистерезисом, без внутренней подтяжки (`0x0100B0`). `bsp_button_init()` не трогает
|
||||||
|
|
@ -189,20 +189,20 @@ pytest: `tools/hil/04_test_button.py` — помечен `@pytest.mark.interacti
|
||||||
|
|
||||||
Команды CLI прошивки:
|
Команды CLI прошивки:
|
||||||
|
|
||||||
| Команда | Ответ | Описание |
|
| Команда | Ответ | Описание |
|
||||||
|--------------|-------------|--------------------------------------------|
|
| --------------- | --------- | ------------------------------------- |
|
||||||
| `PING` | `PONG` | Проверка канала |
|
| `PING` | `PONG` | Проверка канала |
|
||||||
| `READ <idx>` | `1` / `0` | Сырое состояние (без debounce) |
|
| `READ <idx>` | `1` / `0` | Сырое состояние (без debounce) |
|
||||||
| `STATE <idx>`| `1` / `0` | Стабильное состояние после debounce |
|
| `STATE <idx>` | `1` / `0` | Стабильное состояние после debounce |
|
||||||
| `EVENT_P <idx>`| `1` / `0` | `get_event_pressed`, сбрасывает флаг |
|
| `EVENT_P <idx>` | `1` / `0` | `get_event_pressed`, сбрасывает флаг |
|
||||||
| `EVENT_R <idx>`| `1` / `0` | `get_event_released`, сбрасывает флаг |
|
| `EVENT_R <idx>` | `1` / `0` | `get_event_released`, сбрасывает флаг |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Зависимости
|
## Зависимости
|
||||||
|
|
||||||
| Зависимость | Тип | Описание |
|
| Зависимость | Тип | Описание |
|
||||||
|--------------|---------|----------------------------------------------|
|
| ------------ | ------- | -------------------------------------------- |
|
||||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||||
| `bsp_board` | PRIVATE | Транзитивно: `pin_mux.h`, clock, SDK headers |
|
| `bsp_board` | PRIVATE | Транзитивно: `pin_mux.h`, clock, SDK headers |
|
||||||
| `sdk_gpio` | PRIVATE | `fsl_gpio.h` — `GPIO_ReadPinInput()` |
|
| `sdk_gpio` | PRIVATE | `fsl_gpio.h` — `GPIO_ReadPinInput()` |
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
#include "bsp/status.h"
|
#include "bsp/status.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------
|
||||||
* Типы
|
* Типы
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,11 @@
|
||||||
|
|
||||||
## Распределение Message Buffers
|
## Распределение Message Buffers
|
||||||
|
|
||||||
| MB | Назначение |
|
| MB | Назначение |
|
||||||
|-------|-----------------------------------------------------|
|
| ----- | -------------------------------------------------- |
|
||||||
| 0 | Зарезервирован (ERR005829 workaround: inactive TX) |
|
| 0 | Зарезервирован (ERR005829 workaround: inactive TX) |
|
||||||
| 1 | TX — отправка фреймов |
|
| 1 | TX — отправка фреймов |
|
||||||
| 2..17 | RX — до 16 индивидуальных фильтров |
|
| 2..17 | RX — до 16 индивидуальных фильтров |
|
||||||
|
|
||||||
ERR005829 — errata FlexCAN на i.MX RT1050/1052: при гонке TX/RX арбитража
|
ERR005829 — errata FlexCAN на i.MX RT1050/1052: при гонке TX/RX арбитража
|
||||||
MB 0 может зависнуть. Workaround: MB 0 всегда `kFLEXCAN_TxMbInactive`.
|
MB 0 может зависнуть. Workaround: MB 0 всегда `kFLEXCAN_TxMbInactive`.
|
||||||
|
|
@ -104,12 +104,12 @@ bsp_can_accept_all();
|
||||||
затем ждёт флага завершения с polling `FLEXCAN_GetMbStatusFlags()`.
|
затем ждёт флага завершения с polling `FLEXCAN_GetMbStatusFlags()`.
|
||||||
Возвращается по одному из условий:
|
Возвращается по одному из условий:
|
||||||
|
|
||||||
| Условие | Возврат |
|
| Условие | Возврат |
|
||||||
|---------|---------|
|
| -------------------------------- | ----------------- |
|
||||||
| Фрейм успешно отправлен | `BSP_OK` |
|
| Фрейм успешно отправлен | `BSP_OK` |
|
||||||
| TX MB занят предыдущей передачей | `BSP_ERR_BUSY` |
|
| TX MB занят предыдущей передачей | `BSP_ERR_BUSY` |
|
||||||
| Истёк `timeout_ms` | `BSP_ERR_TIMEOUT` |
|
| Истёк `timeout_ms` | `BSP_ERR_TIMEOUT` |
|
||||||
| Невалидные параметры | `BSP_ERR_PARAM` |
|
| Невалидные параметры | `BSP_ERR_PARAM` |
|
||||||
|
|
||||||
При 500 kbit/s максимальное время отправки одного фрейма — ~260 мкс.
|
При 500 kbit/s максимальное время отправки одного фрейма — ~260 мкс.
|
||||||
Для bare-metal и FreeRTOS-задачи это приемлемо.
|
Для bare-metal и FreeRTOS-задачи это приемлемо.
|
||||||
|
|
@ -119,11 +119,11 @@ bsp_can_accept_all();
|
||||||
Polling с таймаутом. Обходит все активные RX MB, читает готовые фреймы
|
Polling с таймаутом. Обходит все активные RX MB, читает готовые фреймы
|
||||||
во внутренний ring buffer, пытается извлечь один фрейм:
|
во внутренний ring buffer, пытается извлечь один фрейм:
|
||||||
|
|
||||||
| Условие | Возврат |
|
| Условие | Возврат |
|
||||||
|---------|---------|
|
| ------------------------------- | ----------------- |
|
||||||
| Фрейм найден (из буфера или MB) | `BSP_OK` |
|
| Фрейм найден (из буфера или MB) | `BSP_OK` |
|
||||||
| Истёк `timeout_ms` | `BSP_ERR_TIMEOUT` |
|
| Истёк `timeout_ms` | `BSP_ERR_TIMEOUT` |
|
||||||
| Невалидные параметры | `BSP_ERR_PARAM` |
|
| Невалидные параметры | `BSP_ERR_PARAM` |
|
||||||
|
|
||||||
```c
|
```c
|
||||||
/* Неблокирующий опрос — timeout_ms = 0 */
|
/* Неблокирующий опрос — timeout_ms = 0 */
|
||||||
|
|
@ -161,10 +161,10 @@ bsp_can_register_rx_callback(can_isr_to_queue, NULL);
|
||||||
|
|
||||||
Модуль не зависит от FreeRTOS и работает в обоих контекстах:
|
Модуль не зависит от FreeRTOS и работает в обоих контекстах:
|
||||||
|
|
||||||
| Контекст | TX | RX |
|
| Контекст | TX | RX |
|
||||||
|----------|----|----|
|
| --------------------------------- | ----------------------------------- | ---------------------------------------------- |
|
||||||
| bare-metal (`firmware/test`, HIL) | `bsp_can_send()` — blocking polling | `bsp_can_receive()` — polling |
|
| 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 (`firmware/tft_app`) | `bsp_can_send()` — из задачи | `bsp_can_receive()` — из задачи с `timeout_ms` |
|
||||||
|
|
||||||
Для FreeRTOS с минимальной латентностью — будущий callback + `xQueueSendFromISR()`.
|
Для FreeRTOS с минимальной латентностью — будущий callback + `xQueueSendFromISR()`.
|
||||||
Polling с `timeout_ms = 10` из задачи подходит для протоколов с интервалом > 10 мс.
|
Polling с `timeout_ms = 10` из задачи подходит для протоколов с интервалом > 10 мс.
|
||||||
|
|
@ -239,10 +239,10 @@ CAN-адаптер на стороне хоста — M5Stack с CAN-модул
|
||||||
|
|
||||||
## Зависимости
|
## Зависимости
|
||||||
|
|
||||||
| Зависимость | Тип | Описание |
|
| Зависимость | Тип | Описание |
|
||||||
|-------------|-----|----------|
|
| -------------- | ------- | -------------------------------------- |
|
||||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||||
| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаутов |
|
| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаутов |
|
||||||
| `ring_buffer` | PRIVATE | Внутренний RX FIFO |
|
| `ring_buffer` | PRIVATE | Внутренний RX FIFO |
|
||||||
| `sdk_flexcan` | PRIVATE | `fsl_flexcan.h` — FlexCAN2 SDK драйвер |
|
| `sdk_flexcan` | PRIVATE | `fsl_flexcan.h` — FlexCAN2 SDK драйвер |
|
||||||
| `clock_config` | PRIVATE | `BOARD_BOOTCLOCKRUN_CAN_CLK_ROOT` |
|
| `clock_config` | PRIVATE | `BOARD_BOOTCLOCKRUN_CAN_CLK_ROOT` |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# bsp/display/CMakeLists.txt BSP ELCDIF RGB Display
|
||||||
|
|
||||||
|
if(BUILD_TESTS_HOST)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
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)
|
||||||
127
bsp/display/include/bsp/display.h
Normal file
127
bsp/display/include/bsp/display.h
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
/**
|
||||||
|
* @file display.h
|
||||||
|
* @brief BSP: ELCDIF display driver — TFT4 / TFT7 / TFT8 / TFT10.
|
||||||
|
*
|
||||||
|
* Bare-metal compatible: callback-based FRAME_DONE notification.
|
||||||
|
* No FreeRTOS dependency in this layer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BSP_DISPLAY_DISPLAY_H_
|
||||||
|
#define BSP_DISPLAY_DISPLAY_H_
|
||||||
|
|
||||||
|
#include "bsp/status.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* ── Максимальные размеры (для статического выделения буферов) ───────── */
|
||||||
|
|
||||||
|
/** @brief Максимальная ширина среди поддерживаемых дисплеев (TFT7: 1024). */
|
||||||
|
#define BSP_DISPLAY_MAX_WIDTH 1024U
|
||||||
|
|
||||||
|
/** @brief Максимальная высота среди поддерживаемых дисплеев (TFT7/TFT8: 600). */
|
||||||
|
#define BSP_DISPLAY_MAX_HEIGHT 600U
|
||||||
|
|
||||||
|
/* ── Типы дисплеев ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
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, /**< Вертикальная CW 90°. LR=1 UD=1. */
|
||||||
|
BSP_DISPLAY_ROTATE_180, /**< Горизонтальная перевёрнутая. LR=0 UD=1. */
|
||||||
|
BSP_DISPLAY_ROTATE_270, /**< Вертикальная CCW 90°. LR=0 UD=0. */
|
||||||
|
} bsp_display_rotation_t;
|
||||||
|
|
||||||
|
/* ── Размер дисплея ──────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
typedef struct bsp_display_size_s
|
||||||
|
{
|
||||||
|
uint16_t width;
|
||||||
|
uint16_t height;
|
||||||
|
} bsp_display_size_t;
|
||||||
|
|
||||||
|
/* ── Callback FRAME_DONE ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вызывается из LCDIF ISR при завершении передачи кадра.
|
||||||
|
*
|
||||||
|
* Реализация ДОЛЖНА быть ISR-safe:
|
||||||
|
* — допустимо: запись в volatile bool, xSemaphoreGiveFromISR() и т.п.
|
||||||
|
* — запрещено: любые блокирующие вызовы.
|
||||||
|
*/
|
||||||
|
typedef void (*bsp_display_frame_cb_t)(void);
|
||||||
|
|
||||||
|
/* ── API ─────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Инициализировать ELCDIF для выбранного типа дисплея.
|
||||||
|
*
|
||||||
|
* Настраивает пиксельный клок, GPIO подсветки и ножки ориентации
|
||||||
|
* (TFT7/TFT8/TFT10), запускает ELCDIF в RGB-режиме, включает IRQ FRAME_DONE.
|
||||||
|
*
|
||||||
|
* @note TFT4 требует инициализации Video PLL — возвращает BSP_ERR_NOT_SUPPORTED
|
||||||
|
* до реализации (TODO).
|
||||||
|
* @note Вызывать однократно. Повторный вызов без deinit — no-op, не ошибка.
|
||||||
|
*
|
||||||
|
* @param type Тип дисплея.
|
||||||
|
* @param framebuffer_addr Физический адрес первого фреймбуфера.
|
||||||
|
* Должен быть выровнен по 64 байт, в NonCacheable SDRAM.
|
||||||
|
* @param on_frame_done ISR-safe callback по завершении кадра; NULL — без callback.
|
||||||
|
* @return BSP_OK | BSP_ERR_PARAM | BSP_ERR_NOT_SUPPORTED
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_display_init(bsp_display_type_t type, uint32_t framebuffer_addr,
|
||||||
|
bsp_display_frame_cb_t p_on_frame_done);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Остановить ELCDIF, выключить подсветку, деинициализировать.
|
||||||
|
*
|
||||||
|
* Безопасен при вызове до bsp_display_init() или повторно.
|
||||||
|
* Всегда возвращает BSP_OK.
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_display_deinit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Переключить ориентацию через ножки LR/UD (TFT7/TFT8/TFT10).
|
||||||
|
*
|
||||||
|
* TFT4 поддерживает только ROTATE_0; остальные варианты → BSP_ERR_NOT_SUPPORTED.
|
||||||
|
*
|
||||||
|
* @return BSP_OK | BSP_ERR_NOT_SUPPORTED | BSP_ERR_INIT
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_display_set_rotation(bsp_display_rotation_t rotation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Указать ELCDIF следующий фреймбуфер.
|
||||||
|
*
|
||||||
|
* Безопасен из ISR и из задачи. Переключение произойдёт аппаратно
|
||||||
|
* по окончании текущего кадра.
|
||||||
|
*
|
||||||
|
* @param framebuffer_addr Физический адрес следующего буфера.
|
||||||
|
*/
|
||||||
|
void bsp_display_set_next_buffer(uint32_t framebuffer_addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Вернуть реальные размеры активного дисплея.
|
||||||
|
*
|
||||||
|
* @return Указатель на bsp_display_size_t; NULL до вызова bsp_display_init().
|
||||||
|
*/
|
||||||
|
const bsp_display_size_t *bsp_display_get_size(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Вернуть тип активного дисплея.
|
||||||
|
*
|
||||||
|
* @return BSP_DISPLAY_COUNT если не инициализирован.
|
||||||
|
*/
|
||||||
|
bsp_display_type_t bsp_display_get_type(void);
|
||||||
|
|
||||||
|
#endif /* BSP_DISPLAY_DISPLAY_H_ */
|
||||||
414
bsp/display/src/display.c
Normal file
414
bsp/display/src/display.c
Normal file
|
|
@ -0,0 +1,414 @@
|
||||||
|
/**
|
||||||
|
* @file display.c
|
||||||
|
* @brief BSP: ELCDIF display driver — TFT4 / TFT7 / TFT8 / TFT10.
|
||||||
|
*
|
||||||
|
* Пиксельный клок:
|
||||||
|
* TFT7 — PLL2 (mux=0), pre_div=2, div=4 → 528/3/5 = 35.2 МГц
|
||||||
|
* TFT8 — PLL2 (mux=0), pre_div=2, div=3 → 528/3/4 = 44.0 МГц
|
||||||
|
* TFT4 — Video PLL (mux=2) — TODO: требует CLOCK_InitVideoPll.
|
||||||
|
* Возвращает BSP_ERR_NOT_SUPPORTED до реализации.
|
||||||
|
*
|
||||||
|
* Управляющие GPIO (TFT7/TFT8/TFT10):
|
||||||
|
* LcdLed GPIO1[20] — подсветка, active-high
|
||||||
|
* Lcdlr_value GPIO1[28] — горизонтальный скан SHlr_value
|
||||||
|
* LcdMode GPIO1[29] — DE/SYNC mode: HIGH=DE mode (обязательно для ELCDIF)
|
||||||
|
* LcdUd GPIO1[30] — вертикальный скан UPDN
|
||||||
|
* LcdDithb GPIO1[31] — dithering bypass: HIGH=disable (IC default)
|
||||||
|
*
|
||||||
|
* IOMUXC конфигурируется в BOARD_InitPins() — здесь только GPIO_PinWrite.
|
||||||
|
*
|
||||||
|
* ISR LCDIF_IRQHandler размещён в ITCM (AT_QUICKACCESS_SECTION_CODE).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bsp/display.h"
|
||||||
|
|
||||||
|
#include "fsl_clock.h"
|
||||||
|
#include "fsl_elcdif.h"
|
||||||
|
#include "fsl_gpio.h"
|
||||||
|
|
||||||
|
/* ── GPIO пины ───────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
#define DISPLAY_BACKLIGHT_GPIO GPIO1
|
||||||
|
#define DISPLAY_BACKLIGHT_PIN 20U
|
||||||
|
|
||||||
|
#define DISPLAY_lr_value_GPIO GPIO1
|
||||||
|
#define DISPLAY_lr_value_PIN 28U
|
||||||
|
#define DISPLAY_MODE_GPIO GPIO1
|
||||||
|
#define DISPLAY_MODE_PIN 29U
|
||||||
|
#define DISPLAY_UD_GPIO GPIO1
|
||||||
|
#define DISPLAY_UD_PIN 30U
|
||||||
|
#define DISPLAY_DITHB_GPIO GPIO1
|
||||||
|
#define DISPLAY_DITHB_PIN 31U
|
||||||
|
|
||||||
|
/* ── Делители пиксельного клока (PLL2 = 528 МГц) ───────────────────── */
|
||||||
|
|
||||||
|
#define DISPLAY_CLK_MUX_PLL2 0U /**< kCLOCK_LcdifPreMux = PLL2. */
|
||||||
|
#define DISPLAY_CLK_MUX_VIDEO_PLL 2U /**< kCLOCK_LcdifPreMux = Video PLL (TFT4). */
|
||||||
|
|
||||||
|
#define DISPLAY_TFT7_CLK_PREDIV 2U /**< ÷3 → 528/3 = 176 МГц. */
|
||||||
|
#define DISPLAY_TFT7_CLK_DIV 4U /**< ÷5 → 176/5 = 35.2 МГц. */
|
||||||
|
#define DISPLAY_TFT8_CLK_PREDIV 2U /**< ÷3 → 528/3 = 176 МГц. */
|
||||||
|
#define DISPLAY_TFT8_CLK_DIV 3U /**< ÷4 → 176/4 = 44.0 МГц. */
|
||||||
|
|
||||||
|
/* ── Тайминги TFT4 (480 × 272) ───────────────────────────────────────── */
|
||||||
|
|
||||||
|
#define DISPLAY_TFT4_W 480U
|
||||||
|
#define DISPLAY_TFT4_H 272U
|
||||||
|
#define DISPLAY_TFT4_HSW 41U
|
||||||
|
#define DISPLAY_TFT4_HFP 2U
|
||||||
|
#define DISPLAY_TFT4_HBP 2U
|
||||||
|
#define DISPLAY_TFT4_VSW 10U
|
||||||
|
#define DISPLAY_TFT4_VFP 2U
|
||||||
|
#define DISPLAY_TFT4_VBP 2U
|
||||||
|
|
||||||
|
/* ── Тайминги TFT7 (1024 × 600) ──────────────────────────────────────── */
|
||||||
|
|
||||||
|
#define DISPLAY_TFT7_W 1024U
|
||||||
|
#define DISPLAY_TFT7_H 600U
|
||||||
|
#define DISPLAY_TFT7_HSW 1U
|
||||||
|
#define DISPLAY_TFT7_HFP 210U
|
||||||
|
#define DISPLAY_TFT7_HBP 46U
|
||||||
|
#define DISPLAY_TFT7_VSW 1U
|
||||||
|
#define DISPLAY_TFT7_VFP 12U
|
||||||
|
#define DISPLAY_TFT7_VBP 23U
|
||||||
|
|
||||||
|
/* ── Тайминги TFT8 (800 × 600) ───────────────────────────────────────── */
|
||||||
|
|
||||||
|
#define DISPLAY_TFT8_W 800U
|
||||||
|
#define DISPLAY_TFT8_H 600U
|
||||||
|
#define DISPLAY_TFT8_HSW 1U
|
||||||
|
#define DISPLAY_TFT8_HFP 210U
|
||||||
|
#define DISPLAY_TFT8_HBP 46U
|
||||||
|
#define DISPLAY_TFT8_VSW 1U
|
||||||
|
#define DISPLAY_TFT8_VFP 12U
|
||||||
|
#define DISPLAY_TFT8_VBP 23U
|
||||||
|
|
||||||
|
/* ── Полярность (одинакова для всех трёх дисплеев) ───────────────────── */
|
||||||
|
|
||||||
|
#define DISPLAY_POL_FLAGS \
|
||||||
|
(kELCDIF_DataEnableActiveHigh | kELCDIF_VsyncActiveLow | kELCDIF_HsyncActiveLow | \
|
||||||
|
kELCDIF_DriveDataOnRisingClkEdge)
|
||||||
|
|
||||||
|
/* ── IRQ приоритет ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
#define DISPLAY_IRQ_PRIORITY 2U
|
||||||
|
|
||||||
|
/* ── Внутренние типы ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
typedef struct display_hw_cfg_s
|
||||||
|
{
|
||||||
|
uint16_t width;
|
||||||
|
uint16_t height;
|
||||||
|
uint8_t hsw;
|
||||||
|
uint8_t hfp;
|
||||||
|
uint8_t hbp;
|
||||||
|
uint8_t vsw;
|
||||||
|
uint8_t vfp;
|
||||||
|
uint8_t vbp;
|
||||||
|
uint32_t pol_flags;
|
||||||
|
uint8_t clk_mux;
|
||||||
|
uint8_t clk_pre_div;
|
||||||
|
uint8_t clk_div;
|
||||||
|
bool has_orientation_pins; /**< false = TFT4. */
|
||||||
|
} display_hw_cfg_t;
|
||||||
|
|
||||||
|
typedef struct display_state_s
|
||||||
|
{
|
||||||
|
bsp_display_type_t type;
|
||||||
|
bsp_display_size_t size;
|
||||||
|
bsp_display_frame_cb_t frame_cb;
|
||||||
|
bool initialised;
|
||||||
|
} display_state_t;
|
||||||
|
|
||||||
|
/* ── Таблица конфигураций ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static const display_hw_cfg_t K_HW_CFG[BSP_DISPLAY_COUNT] = {
|
||||||
|
|
||||||
|
[BSP_DISPLAY_TFT4] = {
|
||||||
|
.width = DISPLAY_TFT4_W,
|
||||||
|
.height = DISPLAY_TFT4_H,
|
||||||
|
.hsw = DISPLAY_TFT4_HSW,
|
||||||
|
.hfp = DISPLAY_TFT4_HFP,
|
||||||
|
.hbp = DISPLAY_TFT4_HBP,
|
||||||
|
.vsw = DISPLAY_TFT4_VSW,
|
||||||
|
.vfp = DISPLAY_TFT4_VFP,
|
||||||
|
.vbp = DISPLAY_TFT4_VBP,
|
||||||
|
.pol_flags = DISPLAY_POL_FLAGS,
|
||||||
|
.clk_mux = DISPLAY_CLK_MUX_VIDEO_PLL,
|
||||||
|
.clk_pre_div = 0U, /* placeholder — Video PLL TODO */
|
||||||
|
.clk_div = 0U,
|
||||||
|
.has_orientation_pins = false,
|
||||||
|
},
|
||||||
|
|
||||||
|
[BSP_DISPLAY_TFT7] = {
|
||||||
|
.width = DISPLAY_TFT7_W,
|
||||||
|
.height = DISPLAY_TFT7_H,
|
||||||
|
.hsw = DISPLAY_TFT7_HSW,
|
||||||
|
.hfp = DISPLAY_TFT7_HFP,
|
||||||
|
.hbp = DISPLAY_TFT7_HBP,
|
||||||
|
.vsw = DISPLAY_TFT7_VSW,
|
||||||
|
.vfp = DISPLAY_TFT7_VFP,
|
||||||
|
.vbp = DISPLAY_TFT7_VBP,
|
||||||
|
.pol_flags = DISPLAY_POL_FLAGS,
|
||||||
|
.clk_mux = DISPLAY_CLK_MUX_PLL2,
|
||||||
|
.clk_pre_div = DISPLAY_TFT7_CLK_PREDIV,
|
||||||
|
.clk_div = DISPLAY_TFT7_CLK_DIV,
|
||||||
|
.has_orientation_pins = true,
|
||||||
|
},
|
||||||
|
|
||||||
|
[BSP_DISPLAY_TFT8] = {
|
||||||
|
.width = DISPLAY_TFT8_W,
|
||||||
|
.height = DISPLAY_TFT8_H,
|
||||||
|
.hsw = DISPLAY_TFT8_HSW,
|
||||||
|
.hfp = DISPLAY_TFT8_HFP,
|
||||||
|
.hbp = DISPLAY_TFT8_HBP,
|
||||||
|
.vsw = DISPLAY_TFT8_VSW,
|
||||||
|
.vfp = DISPLAY_TFT8_VFP,
|
||||||
|
.vbp = DISPLAY_TFT8_VBP,
|
||||||
|
.pol_flags = DISPLAY_POL_FLAGS,
|
||||||
|
.clk_mux = DISPLAY_CLK_MUX_PLL2,
|
||||||
|
.clk_pre_div = DISPLAY_TFT8_CLK_PREDIV,
|
||||||
|
.clk_div = DISPLAY_TFT8_CLK_DIV,
|
||||||
|
.has_orientation_pins = true,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* TFT10: спецификации не определены — TODO */
|
||||||
|
[BSP_DISPLAY_TFT10] = { 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ── Состояние модуля ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static display_state_t g_s_display = {
|
||||||
|
.type = BSP_DISPLAY_COUNT,
|
||||||
|
.frame_cb = NULL,
|
||||||
|
.initialised = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ── ISR ─────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ISR размещён в ITCM для минимальной задержки и исключения кэш-промахов.
|
||||||
|
* Только вызывает зарегистрированный callback — не трогает состояние.
|
||||||
|
*/
|
||||||
|
// NOLINTNEXTLINE(readability-identifier-naming) — SDK-mandated ISR symbol
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(void LCDIF_IRQHandler(void));
|
||||||
|
void LCDIF_IRQHandler(void) // NOLINT(readability-identifier-naming)
|
||||||
|
{
|
||||||
|
uint32_t flags = ELCDIF_GetInterruptStatus(LCDIF);
|
||||||
|
ELCDIF_ClearInterruptStatus(LCDIF, flags);
|
||||||
|
|
||||||
|
if (((flags & (uint32_t) kELCDIF_CurFrameDone) != 0U) && (g_s_display.frame_cb != NULL))
|
||||||
|
{
|
||||||
|
g_s_display.frame_cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
__DSB();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Вспомогательные функции ─────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Настроить делители пиксельного клока.
|
||||||
|
*
|
||||||
|
* TFT4 требует инициализации Video PLL (TODO) — возвращает ошибку.
|
||||||
|
* TFT7/TFT8 используют PLL2 (528 МГц), который уже инициализирован
|
||||||
|
* в BOARD_BootClockRUN().
|
||||||
|
*/
|
||||||
|
static bsp_status_t init_pixelclock(const display_hw_cfg_t *p_cfg)
|
||||||
|
{
|
||||||
|
if (p_cfg->clk_mux == DISPLAY_CLK_MUX_VIDEO_PLL)
|
||||||
|
{
|
||||||
|
/* TODO: CLOCK_InitVideoPll() для TFT4.
|
||||||
|
* Video PLL деинициализирован в BOARD_BootClockRUN() — нужна
|
||||||
|
* дополнительная инициализация перед использованием. */
|
||||||
|
return BSP_ERR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLOCK_SetMux(kCLOCK_LcdifPreMux, p_cfg->clk_mux);
|
||||||
|
CLOCK_SetDiv(kCLOCK_LcdifPreDiv, p_cfg->clk_pre_div);
|
||||||
|
CLOCK_SetDiv(kCLOCK_LcdifDiv, p_cfg->clk_div);
|
||||||
|
return BSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Включить подсветку (GPIO1[20] = HIGH). */
|
||||||
|
static void init_backlight(void)
|
||||||
|
{
|
||||||
|
GPIO_PinWrite(DISPLAY_BACKLIGHT_GPIO, DISPLAY_BACKLIGHT_PIN, 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Установить ножки lr_value/UD в состояние ROTATE_0 по умолчанию.
|
||||||
|
*
|
||||||
|
* Итоговая ориентация задаётся вызовом bsp_display_set_rotation()
|
||||||
|
* после bsp_display_init().
|
||||||
|
*/
|
||||||
|
static void init_orientation_pins(void)
|
||||||
|
{
|
||||||
|
GPIO_PinWrite(DISPLAY_lr_value_GPIO, DISPLAY_lr_value_PIN, 1U); /* ROTATE_0: lr_value=1 */
|
||||||
|
GPIO_PinWrite(DISPLAY_UD_GPIO, DISPLAY_UD_PIN, 0U); /* ROTATE_0: UD=0 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Инициализировать MODE и DITHB пины контроллера HX8264-D02.
|
||||||
|
*
|
||||||
|
* MODE=1: DE mode (обязательно для ELCDIF, использующего DE/enable сигнал).
|
||||||
|
* DITHB=1: отключить дизеринг (IC default согласно даташиту, "normally pull high").
|
||||||
|
*
|
||||||
|
* Вызывается только для дисплеев с has_orientation_pins = true.
|
||||||
|
*/
|
||||||
|
static void init_mode_dither_pins(void)
|
||||||
|
{
|
||||||
|
GPIO_PinWrite(DISPLAY_MODE_GPIO, DISPLAY_MODE_PIN, 1U); /* MODE=1: DE mode */
|
||||||
|
GPIO_PinWrite(DISPLAY_DITHB_GPIO, DISPLAY_DITHB_PIN, 1U); /* DITHB=1: disable */
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Включить IRQ FRAME_DONE с фиксированным приоритетом. */
|
||||||
|
static void enable_lcd_interrupt(void)
|
||||||
|
{
|
||||||
|
NVIC_SetPriority(LCDIF_IRQn, DISPLAY_IRQ_PRIORITY);
|
||||||
|
EnableIRQ(LCDIF_IRQn);
|
||||||
|
ELCDIF_EnableInterrupts(LCDIF, kELCDIF_CurFrameDoneInterruptEnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Заполнить конфигурацию ELCDIF из таблицы + адрес буфера. */
|
||||||
|
static void build_elcdif_cfg(const display_hw_cfg_t *p_cfg, uint32_t framebuffer_addr,
|
||||||
|
elcdif_rgb_mode_config_t *p_out)
|
||||||
|
{
|
||||||
|
p_out->panelWidth = p_cfg->width;
|
||||||
|
p_out->panelHeight = p_cfg->height;
|
||||||
|
p_out->hsw = p_cfg->hsw;
|
||||||
|
p_out->hfp = p_cfg->hfp;
|
||||||
|
p_out->hbp = p_cfg->hbp;
|
||||||
|
p_out->vsw = p_cfg->vsw;
|
||||||
|
p_out->vfp = p_cfg->vfp;
|
||||||
|
p_out->vbp = p_cfg->vbp;
|
||||||
|
p_out->polarityFlags = p_cfg->pol_flags;
|
||||||
|
p_out->bufferAddr = framebuffer_addr;
|
||||||
|
p_out->pixelFormat = kELCDIF_PixelFormatXRGB8888;
|
||||||
|
p_out->dataBus = kELCDIF_DataBus24Bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Реализация API ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
bsp_status_t bsp_display_init(bsp_display_type_t type, uint32_t framebuffer_addr,
|
||||||
|
bsp_display_frame_cb_t p_on_frame_done)
|
||||||
|
{
|
||||||
|
if ((uint32_t) type >= (uint32_t) BSP_DISPLAY_COUNT)
|
||||||
|
{
|
||||||
|
return BSP_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_s_display.initialised)
|
||||||
|
{
|
||||||
|
return BSP_OK; /* идемпотентен */
|
||||||
|
}
|
||||||
|
|
||||||
|
const display_hw_cfg_t *p_cfg = &K_HW_CFG[type];
|
||||||
|
|
||||||
|
bsp_status_t status = init_pixelclock(p_cfg);
|
||||||
|
if (status != BSP_OK)
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLOCK_EnableClock(kCLOCK_LcdPixel);
|
||||||
|
init_backlight();
|
||||||
|
|
||||||
|
if (p_cfg->has_orientation_pins)
|
||||||
|
{
|
||||||
|
init_orientation_pins();
|
||||||
|
init_mode_dither_pins();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Сохранить callback ДО включения IRQ — исключить гонку */
|
||||||
|
g_s_display.frame_cb = p_on_frame_done;
|
||||||
|
|
||||||
|
elcdif_rgb_mode_config_t elcdif_cfg;
|
||||||
|
build_elcdif_cfg(p_cfg, framebuffer_addr, &elcdif_cfg);
|
||||||
|
ELCDIF_RgbModeInit(LCDIF, &elcdif_cfg);
|
||||||
|
enable_lcd_interrupt();
|
||||||
|
ELCDIF_RgbModeStart(LCDIF);
|
||||||
|
|
||||||
|
g_s_display.type = type;
|
||||||
|
g_s_display.size.width = p_cfg->width;
|
||||||
|
g_s_display.size.height = p_cfg->height;
|
||||||
|
g_s_display.initialised = true;
|
||||||
|
|
||||||
|
return BSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bsp_status_t bsp_display_deinit(void)
|
||||||
|
{
|
||||||
|
if (g_s_display.initialised)
|
||||||
|
{
|
||||||
|
ELCDIF_RgbModeStop(LCDIF);
|
||||||
|
DisableIRQ(LCDIF_IRQn);
|
||||||
|
ELCDIF_Deinit(LCDIF);
|
||||||
|
CLOCK_DisableClock(kCLOCK_LcdPixel);
|
||||||
|
GPIO_PinWrite(DISPLAY_BACKLIGHT_GPIO, DISPLAY_BACKLIGHT_PIN, 0U);
|
||||||
|
|
||||||
|
g_s_display.frame_cb = NULL;
|
||||||
|
g_s_display.type = BSP_DISPLAY_COUNT;
|
||||||
|
g_s_display.initialised = false;
|
||||||
|
}
|
||||||
|
return BSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bsp_status_t bsp_display_set_rotation(bsp_display_rotation_t rotation)
|
||||||
|
{
|
||||||
|
if (!g_s_display.initialised)
|
||||||
|
{
|
||||||
|
return BSP_ERR_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
const display_hw_cfg_t *p_cfg = &K_HW_CFG[g_s_display.type];
|
||||||
|
|
||||||
|
if (!p_cfg->has_orientation_pins)
|
||||||
|
{
|
||||||
|
return (rotation == BSP_DISPLAY_ROTATE_0) ? BSP_OK : BSP_ERR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t lr_value;
|
||||||
|
uint8_t ud_value;
|
||||||
|
|
||||||
|
switch (rotation)
|
||||||
|
{
|
||||||
|
case BSP_DISPLAY_ROTATE_0:
|
||||||
|
lr_value = 1U;
|
||||||
|
ud_value = 0U;
|
||||||
|
break;
|
||||||
|
case BSP_DISPLAY_ROTATE_90:
|
||||||
|
lr_value = 1U;
|
||||||
|
ud_value = 1U;
|
||||||
|
break;
|
||||||
|
case BSP_DISPLAY_ROTATE_180:
|
||||||
|
lr_value = 0U;
|
||||||
|
ud_value = 1U;
|
||||||
|
break;
|
||||||
|
case BSP_DISPLAY_ROTATE_270:
|
||||||
|
lr_value = 0U;
|
||||||
|
ud_value = 0U;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return BSP_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
GPIO_PinWrite(DISPLAY_lr_value_GPIO, DISPLAY_lr_value_PIN, lr_value);
|
||||||
|
GPIO_PinWrite(DISPLAY_UD_GPIO, DISPLAY_UD_PIN, ud_value);
|
||||||
|
return BSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bsp_display_set_next_buffer(uint32_t framebuffer_addr)
|
||||||
|
{
|
||||||
|
ELCDIF_SetNextBufferAddr(LCDIF, framebuffer_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bsp_display_size_t *bsp_display_get_size(void)
|
||||||
|
{
|
||||||
|
return g_s_display.initialised ? &g_s_display.size : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bsp_display_type_t bsp_display_get_type(void)
|
||||||
|
{
|
||||||
|
return g_s_display.type;
|
||||||
|
}
|
||||||
|
|
@ -43,12 +43,12 @@ bsp/
|
||||||
|
|
||||||
Внешний кварц **24 МГц**. Режим максимальной производительности (внешнее питание, энергопотребление не критично).
|
Внешний кварц **24 МГц**. Режим максимальной производительности (внешнее питание, энергопотребление не критично).
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| ------------------------ | ----------------------------------------- |
|
||||||
| ARM PLL (PLL1) | 1200 МГц (VDIV = 50) |
|
| ARM PLL (PLL1) | 1200 МГц (VDIV = 50) |
|
||||||
| CPU / AHB | 600 МГц (ARM_PODF /2, AHB_PODF /1) |
|
| CPU / AHB | 600 МГц (ARM_PODF /2, AHB_PODF /1) |
|
||||||
| IPG | 150 МГц (IPG_PODF /4) |
|
| IPG | 150 МГц (IPG_PODF /4) |
|
||||||
| PLL1 bypass clock | REF_CLK_24M (внешний кварц) |
|
| PLL1 bypass clock | REF_CLK_24M (внешний кварц) |
|
||||||
| Внутренний RC осциллятор | включён (используется BootROM при старте) |
|
| Внутренний RC осциллятор | включён (используется BootROM при старте) |
|
||||||
|
|
||||||
**SEMC и FlexSPI** в Clocks Tool настроены формально — реальная инициализация выполняется через **DCD** (SDRAM) и **FDCB** (QSPI Flash) до передачи управления прикладному коду. Для корректной сборки необходимы следующие дефайны:
|
**SEMC и FlexSPI** в Clocks Tool настроены формально — реальная инициализация выполняется через **DCD** (SDRAM) и **FDCB** (QSPI Flash) до передачи управления прикладному коду. Для корректной сборки необходимы следующие дефайны:
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
## Аппаратная часть
|
## Аппаратная часть
|
||||||
|
|
||||||
| `led_id_t` | Сигнал | GPIO | Pin | Координата | Активный уровень |
|
| `led_id_t` | Сигнал | GPIO | Pin | Координата | Активный уровень |
|
||||||
|-----------------|------------|-------|-----|------------|-----------------|
|
| --------------- | ---------- | ----- | --- | ---------- | ---------------- |
|
||||||
| `LED_HEARTBEAT` | `UserLed1` | GPIO3 | 3 | M4 | LOW (0 = горит) |
|
| `LED_HEARTBEAT` | `UserLed1` | GPIO3 | 3 | M4 | LOW (0 = горит) |
|
||||||
| `LED_APP` | `UserLed2` | GPIO3 | 4 | P2 | LOW (0 = горит) |
|
| `LED_APP` | `UserLed2` | GPIO3 | 4 | P2 | LOW (0 = горит) |
|
||||||
|
|
||||||
Пины сконфигурированы в `generated/pin_mux.h` (MCUXpresso Config Tools).
|
Пины сконфигурированы в `generated/pin_mux.h` (MCUXpresso Config Tools).
|
||||||
`INIT_GPIO_VALUE = 1U` — оба LED выключены сразу после `led_init()`.
|
`INIT_GPIO_VALUE = 1U` — оба LED выключены сразу после `led_init()`.
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ extern "C"
|
||||||
* Должна вызываться из main loop на каждой итерации. НЕ вызывать из ISR.
|
* Должна вызываться из main loop на каждой итерации. НЕ вызывать из ISR.
|
||||||
* Каналы MODE_PROTO пропускаются — для них используется коллбэк из ISR.
|
* Каналы MODE_PROTO пропускаются — для них используется коллбэк из ISR.
|
||||||
*/
|
*/
|
||||||
//FIXME: poll
|
//FIXME: poll
|
||||||
void bsp_opto_process(void);
|
void bsp_opto_process(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
Нашёл и закрыл основную причину HardFault.
|
|
||||||
|
|
||||||
Что было не так
|
|
||||||
|
|
||||||
Ваша CodeQuickAccess/.ram_function секция не копировалась в ITCM при старте, потому что для startup_MIMXRT1052.S не были заданы макросы __STARTUP_INITIALIZE_RAMFUNCTION (и __STARTUP_CLEAR_BSS).
|
|
||||||
SysTick включался до bsp_qspi_init(), а в драйвере не было маскирования IRQ на время FlexSPI IP-операций, что опасно при XIP с той же флешки.
|
|
||||||
Что я изменил
|
|
||||||
|
|
||||||
Добавил startup-дефайны в firmware/test/CMakeLists.txt (line 20):
|
|
||||||
|
|
||||||
- `__STARTUP_INITIALIZE_RAMFUNCTION`
|
|
||||||
- `__STARTUP_CLEAR_BSS`
|
|
||||||
-
|
|
||||||
Переставил порядок инициализации в firmware/test/src/main.c (line 38):
|
|
||||||
bsp_qspi_init() теперь до bsp_tick_init().
|
|
||||||
|
|
||||||
Усилил XIP-safe поведение в bsp/qspi_flash/src/qspi_flash.c (line 205):
|
|
||||||
добавил qspi_irq_lock()/qspi_irq_unlock();
|
|
||||||
|
|
||||||
обернул критичные операции (init/read/write/erase) под IRQ lock;
|
|
||||||
публичные QSPI API размещены в CodeQuickAccess (ITCM).
|
|
||||||
|
|
||||||
|
|
||||||
Нашёл вероятный корень: в драйвере жёстко зашит watermark = 8 bytes, а в железе он может быть другим, из-за чего TX/RX FIFO-петли начинают висеть. Сейчас внесу фикс: динамический watermark из регистров + ожидание IPCMDDONE как в fsl_flexspi.
|
|
||||||
|
|
||||||
Да, это мой хвост после рефактора watermark-констант. Сейчас поправлю qspi_write_fifo на динамический watermark и сразу проверю сборку, чтобы не оставить таких разрывов.
|
|
||||||
|
|
@ -38,13 +38,13 @@ sdk_usdhc ← NXP HAL: fsl_usdhc
|
||||||
|
|
||||||
## Аппаратный контекст
|
## Аппаратный контекст
|
||||||
|
|
||||||
| Сигнал | Пин MCU | Конфигурация |
|
| Сигнал | Пин MCU | Конфигурация |
|
||||||
|----------|-----------------|---------------------------------------------------|
|
| ------ | ---------------- | ------------------------------------------------ |
|
||||||
| CLK | GPIO_SD_B0_01 | USDHC1_CLK, периферийный режим |
|
| CLK | GPIO_SD_B0_01 | USDHC1_CLK, периферийный режим |
|
||||||
| CMD | GPIO_SD_B0_00 | USDHC1_CMD, периферийный режим |
|
| CMD | GPIO_SD_B0_00 | USDHC1_CMD, периферийный режим |
|
||||||
| D0–D3 | GPIO_SD_B0_02–05| USDHC1_DATA0–3, периферийный режим |
|
| D0–D3 | GPIO_SD_B0_02–05 | USDHC1_DATA0–3, периферийный режим |
|
||||||
| CD_B | GPIO_B1_12 | USDHC1_CD_B — детект через GPIO2[28] |
|
| CD_B | GPIO_B1_12 | USDHC1_CD_B — детект через GPIO2[28] |
|
||||||
| SdPwr | GPIO_AD_B1_03 | GPIO1[19], active-low, управляется SDK через BSP |
|
| SdPwr | GPIO_AD_B1_03 | GPIO1[19], active-low, управляется SDK через BSP |
|
||||||
|
|
||||||
**CD_B** подключён как периферийный сигнал USDHC1, а не как GPIO. Детект карты
|
**CD_B** подключён как периферийный сигнал USDHC1, а не как GPIO. Детект карты
|
||||||
читается через `USDHC_GetPresentStatusFlags` → `kUSDHC_CardInsertedFlag`.
|
читается через `USDHC_GetPresentStatusFlags` → `kUSDHC_CardInsertedFlag`.
|
||||||
|
|
@ -92,13 +92,13 @@ host-контроллер (`SD_HostInit`).
|
||||||
|
|
||||||
## Разделение ответственности: bsp_sd vs sdmmc_config vs port_fatfs_sd
|
## Разделение ответственности: bsp_sd vs sdmmc_config vs port_fatfs_sd
|
||||||
|
|
||||||
| Слой | Что делает | Где живёт |
|
| Слой | Что делает | Где живёт |
|
||||||
|-----------------------|---------------------------------------------------------|------------------------------------|
|
| --------------------- | ------------------------------------------------------- | ----------------------------------- |
|
||||||
| `sdmmc_config` | Константы платы, `BOARD_SD_Config`, GPIO питания, pads | `bsp/generated/` |
|
| `sdmmc_config` | Константы платы, `BOARD_SD_Config`, GPIO питания, pads | `bsp/generated/` |
|
||||||
| `bsp_sd` | `SD_HostInit/Deinit`, идемпотентность, card detect | `bsp/sd/` |
|
| `bsp_sd` | `SD_HostInit/Deinit`, идемпотентность, card detect | `bsp/sd/` |
|
||||||
| `port_fatfs_sd` | `microsd_disk_*` → `fsl_sd_disk` (FatFS diskio glue) | `port/fatfs/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/` |
|
| `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/` (будущее)|
|
| `tft_app_fatfs` | `ff.c` + `fsl_sd_disk` + `diskio.c` (FreeRTOS ffconf) | `firmware/tft_app/fatfs/` (будущее) |
|
||||||
|
|
||||||
**Почему `ff.c` и `fsl_sd_disk.c` не компилируются один раз как общая библиотека:**
|
**Почему `ff.c` и `fsl_sd_disk.c` не компилируются один раз как общая библиотека:**
|
||||||
оба включают `ff.h` → `ffconf.h`, который разный для `firmware_test` (bare-metal,
|
оба включают `ff.h` → `ffconf.h`, который разный для `firmware_test` (bare-metal,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# Вопросы
|
|
||||||
|
|
||||||
> Да, про HIL в SDRAM точно подмечено: мы грузим в RAM elf, а не подготовленные с помощью nxpimage hab образы. То есть, dcd у нас не вшит и BootROM его не прочитает, тем более, что при загрузке в RAM теста, насколько я понимаю мы вообще игнорируем стадию bootROM и заставляем контроллер прыгнуть на предопределенный нами ProgrammCounter. Поправь меня если я ошибаюсь в своих выводах. Мне видится вариант 1 самым логичным и корректным. Главное понимать, что частый прогон HIL теста SDRAM будет изнашивать Flash -память - то есть этот тест - не совсем частая история. Тут же возникает другой вопрос - firmware_test должен быть написанным полностью? Понятно, что можно реализовать только ту его часть, что отвечает за SDRAM тесты. Но тут ведь тоже есть нюанс - мы установили, что firmware_test связывается с хостом по USB-CDC, этот момент тоже надо учитывать, и похоже он ломает всю парадигму наших HIL тестов. Отсюда я заключаю: возможно не стоит делать HIL тест для SDRAM, а нужно просто заложить возможность проведения длительных тестов на производстве при загрузке firmware_test на таргет. По умолчанию будут прогоняться быстрые тесты, но по желанию оператора, либо раз в десять устройств будет выполняться долгий тест (что думаешь на этот счет?)
|
|
||||||
|
|
@ -32,8 +32,6 @@
|
||||||
|
|
||||||
#include "bsp/status.h"
|
#include "bsp/status.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Размер расширенного тестового региона, байт (27 MB).
|
* @brief Размер расширенного тестового региона, байт (27 MB).
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -59,11 +59,11 @@ target_compile_definitions(firmware_test PRIVATE
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
| Define | Дефолт | Описание |
|
| Define | Дефолт | Описание |
|
||||||
|--------|--------|----------|
|
| ------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------- |
|
||||||
| `BSP_UART_HOST_RX_BUFFER_SIZE` | `256` | Размер RX ring buffer. **Должен быть степенью двойки.** |
|
| `BSP_UART_HOST_RX_BUFFER_SIZE` | `256` | Размер RX ring buffer. **Должен быть степенью двойки.** |
|
||||||
| `BSP_UART_HOST_SRC_CLOCK_HZ` | `24000000` | Частота источника тактирования LPUART1. |
|
| `BSP_UART_HOST_SRC_CLOCK_HZ` | `24000000` | Частота источника тактирования LPUART1. |
|
||||||
| `BSP_UART_HOST_IRQ_PRIORITY` | `5` | Приоритет `LPUART1_IRQn`. Должен быть ≥ `configMAX_SYSCALL_INTERRUPT_PRIORITY` при использовании FreeRTOS. |
|
| `BSP_UART_HOST_IRQ_PRIORITY` | `5` | Приоритет `LPUART1_IRQn`. Должен быть ≥ `configMAX_SYSCALL_INTERRUPT_PRIORITY` при использовании FreeRTOS. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -156,9 +156,9 @@ void test_something(void) {
|
||||||
|
|
||||||
## Зависимости
|
## Зависимости
|
||||||
|
|
||||||
| Зависимость | Тип | Описание |
|
| Зависимость | Тип | Описание |
|
||||||
|-------------|-----|----------|
|
| --------------------- | ------- | --------------------------------- |
|
||||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||||
| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаутов |
|
| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаутов |
|
||||||
| `utils` (ring_buffer) | PRIVATE | RX ring buffer |
|
| `utils` (ring_buffer) | PRIVATE | RX ring buffer |
|
||||||
| `sdk_lpuart` | PRIVATE | `fsl_lpuart.h`, `fsl_clock.h` |
|
| `sdk_lpuart` | PRIVATE | `fsl_lpuart.h`, `fsl_clock.h` |
|
||||||
|
|
@ -11,11 +11,11 @@ CLI команды, обновление конфигурации. Работа
|
||||||
|
|
||||||
## Аппаратура
|
## Аппаратура
|
||||||
|
|
||||||
| Сигнал | Пин MCU | Назначение |
|
| Сигнал | Пин MCU | Назначение |
|
||||||
|--------------|----------------|----------------------------------|
|
| ------------- | ------------- | -------------------------- |
|
||||||
| USB_OTG1_DN | USB_OTG1_DN | USB1 Data− |
|
| USB_OTG1_DN | USB_OTG1_DN | USB1 Data− |
|
||||||
| USB_OTG1_DP | USB_OTG1_DP | USB1 Data+ |
|
| USB_OTG1_DP | USB_OTG1_DP | USB1 Data+ |
|
||||||
| USB_OTG1_VBUS| USB_OTG1_VBUS | VBUS detect (self-powered) |
|
| USB_OTG1_VBUS | USB_OTG1_VBUS | VBUS detect (self-powered) |
|
||||||
|
|
||||||
Встроенный HS PHY (480 MHz PLL). Контроллер: EHCI0 (`kUSB_ControllerEhci0`).
|
Встроенный HS PHY (480 MHz PLL). Контроллер: EHCI0 (`kUSB_ControllerEhci0`).
|
||||||
Скорость: High-Speed (480 Mbit/s) при поддержке хоста, fallback Full-Speed (12 Mbit/s).
|
Скорость: High-Speed (480 Mbit/s) при поддержке хоста, fallback Full-Speed (12 Mbit/s).
|
||||||
|
|
@ -60,14 +60,14 @@ Host → USB1_DP/DN → [EHCI0 DMA]
|
||||||
Модуль использует **lite** вариант NXP USB стека (не full class framework).
|
Модуль использует **lite** вариант NXP USB стека (не full class framework).
|
||||||
Это сознательное решение:
|
Это сознательное решение:
|
||||||
|
|
||||||
| Аспект | Full stack | Lite stack (наш выбор) |
|
| Аспект | Full stack | Lite stack (наш выбор) |
|
||||||
|--------|-----------|----------------------|
|
| ------------------ | -------------------------------------- | ----------------------------- |
|
||||||
| Class framework | `usb_device_class.h`, `class_handle_t` | Отсутствует |
|
| Class framework | `usb_device_class.h`, `class_handle_t` | Отсутствует |
|
||||||
| `usb_device_ch9.c` | SDK middleware, тянет class driver | Приватная копия в `src/` |
|
| `usb_device_ch9.c` | SDK middleware, тянет class driver | Приватная копия в `src/` |
|
||||||
| CDC ACM хедер | Полный: struct + API функции | Только define-ы request codes |
|
| CDC ACM хедер | Полный: struct + API функции | Только define-ы request codes |
|
||||||
| Callbacks | Через class driver dispatch | Напрямую в `usb_cdc.c` |
|
| Callbacks | Через class driver dispatch | Напрямую в `usb_cdc.c` |
|
||||||
| Размер кода | ~12 KB | ~6 KB |
|
| Размер кода | ~12 KB | ~6 KB |
|
||||||
| Гибкость | Multi-class composite | Один CDC ACM |
|
| Гибкость | Multi-class composite | Один CDC ACM |
|
||||||
|
|
||||||
Lite stack достаточен для одного CDC ACM интерфейса. Переход на full stack
|
Lite stack достаточен для одного CDC ACM интерфейса. Переход на full stack
|
||||||
понадобится только при добавлении composite device (CDC + MSC).
|
понадобится только при добавлении composite device (CDC + MSC).
|
||||||
|
|
@ -169,12 +169,12 @@ NVIC enable → USB_DeviceRun. Включает задержку 5 мс для
|
||||||
Неблокирующая отправка. Копирует данные в NonCacheable TX буфер и ставит в очередь
|
Неблокирующая отправка. Копирует данные в NonCacheable TX буфер и ставит в очередь
|
||||||
USB IN transfer. Максимум `BSP_USB_CDC_MAX_PACKET_SIZE` (512) байт за вызов.
|
USB IN transfer. Максимум `BSP_USB_CDC_MAX_PACKET_SIZE` (512) байт за вызов.
|
||||||
|
|
||||||
| Возврат | Условие |
|
| Возврат | Условие |
|
||||||
|---------|---------|
|
| ------------------- | ------------------------------------------ |
|
||||||
| `BSP_OK` | Transfer поставлен в очередь |
|
| `BSP_OK` | Transfer поставлен в очередь |
|
||||||
| `BSP_ERR_BUSY` | Предыдущий transfer не завершён |
|
| `BSP_ERR_BUSY` | Предыдущий transfer не завершён |
|
||||||
| `BSP_ERR_NOT_READY` | Хост не подключён |
|
| `BSP_ERR_NOT_READY` | Хост не подключён |
|
||||||
| `BSP_ERR_INVALID` | `data == NULL`, `len == 0` или `len > 512` |
|
| `BSP_ERR_INVALID` | `data == NULL`, `len == 0` или `len > 512` |
|
||||||
|
|
||||||
Проверить готовность TX канала перед отправкой: `bsp_usb_cdc_write_ready()`.
|
Проверить готовность TX канала перед отправкой: `bsp_usb_cdc_write_ready()`.
|
||||||
|
|
||||||
|
|
@ -250,10 +250,10 @@ USB_OTG1_IRQHandler (usb_cdc_hw.c)
|
||||||
|
|
||||||
Модуль работает без изменений в контексте FreeRTOS-задачи:
|
Модуль работает без изменений в контексте FreeRTOS-задачи:
|
||||||
|
|
||||||
| Контекст | TX | RX |
|
| Контекст | TX | RX |
|
||||||
|----------|----|----|
|
| ---------- | ------------------------------------------- | ------------------------------ |
|
||||||
| bare-metal | `bsp_usb_cdc_write()` — non-blocking | `bsp_usb_cdc_read()` — polling |
|
| bare-metal | `bsp_usb_cdc_write()` — non-blocking | `bsp_usb_cdc_read()` — polling |
|
||||||
| FreeRTOS | Из задачи, `write_ready()` + `vTaskDelay()` | Из задачи с yield |
|
| FreeRTOS | Из задачи, `write_ready()` + `vTaskDelay()` | Из задачи с yield |
|
||||||
|
|
||||||
Для минимальной латентности в FreeRTOS — будущий `USB_DEVICE_CONFIG_USE_TASK=1`
|
Для минимальной латентности в FreeRTOS — будущий `USB_DEVICE_CONFIG_USE_TASK=1`
|
||||||
с `bsp_usb_cdc_poll()` из выделенной задачи.
|
с `bsp_usb_cdc_poll()` из выделенной задачи.
|
||||||
|
|
@ -295,9 +295,9 @@ just host::hil-usb-cdc
|
||||||
|
|
||||||
Команды CLI прошивки:
|
Команды CLI прошивки:
|
||||||
|
|
||||||
| Команда | Ответ | Описание |
|
| Команда | Ответ | Описание |
|
||||||
|---------|-------|----------|
|
| ------------- | -------- | ---------------- |
|
||||||
| `PING` | `PONG` | Проверка канала |
|
| `PING` | `PONG` | Проверка канала |
|
||||||
| `ECHO <data>` | `<data>` | Echo-back данных |
|
| `ECHO <data>` | `<data>` | Echo-back данных |
|
||||||
|
|
||||||
### Host unit-тесты
|
### Host unit-тесты
|
||||||
|
|
@ -311,15 +311,15 @@ just host::hil-usb-cdc
|
||||||
|
|
||||||
Все настройки находятся в приватных хедерах `src/`:
|
Все настройки находятся в приватных хедерах `src/`:
|
||||||
|
|
||||||
| Файл | Настройка | Значение | Описание |
|
| Файл | Настройка | Значение | Описание |
|
||||||
|------|-----------|----------|----------|
|
| ------------------------- | ------------------------------- | -------- | -------------------------------- |
|
||||||
| `usb_device_config.h` | `USB_DEVICE_CONFIG_EHCI` | `1` | Контроллер EHCI0 |
|
| `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_ENDPOINTS` | `4` | EP0 + interrupt IN + bulk IN/OUT |
|
||||||
| `usb_device_config.h` | `USB_DEVICE_CONFIG_SELF_POWER` | `1` | Self-powered device |
|
| `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_VID` | `0x1234` | Vendor ID (placeholder) |
|
||||||
| `usb_device_descriptor.h` | `USB_DEVICE_PID` | `0x0001` | Product 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` | `USB_DEVICE_INTERRUPT_PRIORITY` | `3` | NVIC приоритет |
|
||||||
| `usb_cdc_hw.c` | `BOARD_USB_PHY_D_CAL` | `0x0C` | PHY калибровка |
|
| `usb_cdc_hw.c` | `BOARD_USB_PHY_D_CAL` | `0x0C` | PHY калибровка |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -348,15 +348,15 @@ bsp/usb_cdc/
|
||||||
|
|
||||||
## Зависимости
|
## Зависимости
|
||||||
|
|
||||||
| Зависимость | Тип | Описание |
|
| Зависимость | Тип | Описание |
|
||||||
|-------------|-----|----------|
|
| --------------------- | --------------------- | ------------------------------------------------------- |
|
||||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||||
| `bsp_board` | PRIVATE | Транзитивно: `clock_config.h`, `pin_mux.h`, SDK headers |
|
| `bsp_board` | PRIVATE | Транзитивно: `clock_config.h`, `pin_mux.h`, SDK headers |
|
||||||
| `sdk_usb_device_ehci` | PRIVATE | EHCI контроллер + DCI абстракция |
|
| `sdk_usb_device_ehci` | PRIVATE | EHCI контроллер + DCI абстракция |
|
||||||
| `sdk_usb_phy` | PRIVATE | USB PHY инициализация (480 MHz PLL) |
|
| `sdk_usb_phy` | PRIVATE | USB PHY инициализация (480 MHz PLL) |
|
||||||
| `sdk_osa_bm` | PRIVATE | OS Abstraction Layer (bare-metal, generic list) |
|
| `sdk_osa_bm` | PRIVATE | OS Abstraction Layer (bare-metal, generic list) |
|
||||||
| `sdk_usb_common` | PRIVATE (транзитивно) | USB common headers (`usb.h`, `usb_misc.h`) |
|
| `sdk_usb_common` | PRIVATE (транзитивно) | USB common headers (`usb.h`, `usb_misc.h`) |
|
||||||
| `sdk_usb_config` | PRIVATE (транзитивно) | INTERFACE: проброс конфиг-хедеров в SDK |
|
| `sdk_usb_config` | PRIVATE (транзитивно) | INTERFACE: проброс конфиг-хедеров в SDK |
|
||||||
|
|
||||||
### Зависимости на уровне SDK CMake
|
### Зависимости на уровне SDK CMake
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ ENTRY(Reset_Handler)
|
||||||
HEAP_SIZE = DEFINED(__heap_size__) ? __heap_size__ : 0x0400;
|
HEAP_SIZE = DEFINED(__heap_size__) ? __heap_size__ : 0x0400;
|
||||||
STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x0400;
|
STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x0400;
|
||||||
VECTOR_RAM_SIZE = DEFINED(__ram_vector_table__) ? 0x00000400 : 0;
|
VECTOR_RAM_SIZE = DEFINED(__ram_vector_table__) ? 0x00000400 : 0;
|
||||||
NCACHE_HEAP_START = DEFINED(__heap_noncacheable__) ? 0x82000000 - HEAP_SIZE : 0x81E00000 - HEAP_SIZE;
|
NCACHE_HEAP_START = DEFINED(__heap_noncacheable__) ? 0x82000000 - HEAP_SIZE : 0x82000000 - HEAP_SIZE;
|
||||||
NCACHE_HEAP_SIZE = DEFINED(__heap_noncacheable__) ? HEAP_SIZE : 0x0000;
|
NCACHE_HEAP_SIZE = DEFINED(__heap_noncacheable__) ? HEAP_SIZE : 0x0000;
|
||||||
|
|
||||||
/* Specify the memory areas */
|
/* Specify the memory areas */
|
||||||
|
|
@ -40,8 +40,8 @@ MEMORY
|
||||||
m_interrupts (RX) : ORIGIN = 0x60002000, LENGTH = 0x00000400
|
m_interrupts (RX) : ORIGIN = 0x60002000, LENGTH = 0x00000400
|
||||||
m_text (RX) : ORIGIN = 0x60002400, LENGTH = 0x03FFDC00
|
m_text (RX) : ORIGIN = 0x60002400, LENGTH = 0x03FFDC00
|
||||||
m_qacode (RX) : ORIGIN = 0x00000000, LENGTH = 0x00020000
|
m_qacode (RX) : ORIGIN = 0x00000000, LENGTH = 0x00020000
|
||||||
m_data (RW) : ORIGIN = 0x80000000, LENGTH = DEFINED(__heap_noncacheable__) ? 0x01E00000 : 0x01E00000 - HEAP_SIZE
|
m_data (RW) : ORIGIN = 0x80000000, LENGTH = 0x01600000 /* 22 MB — хватает для всего */
|
||||||
m_ncache (RW) : ORIGIN = 0x81E00000, LENGTH = DEFINED(__heap_noncacheable__) ? 0x00200000 - HEAP_SIZE : 0x00200000
|
m_ncache (RW) : ORIGIN = 0x81600000, LENGTH = DEFINED(__heap_noncacheable__) ? 0x00A00000 - HEAP_SIZE : 0x00A00000 /* 10 MB — хватает на tft_app */
|
||||||
m_data2 (RW) : ORIGIN = 0x20000000, LENGTH = 0x00020000
|
m_data2 (RW) : ORIGIN = 0x20000000, LENGTH = 0x00020000
|
||||||
m_data3 (RW) : ORIGIN = 0x20200000, LENGTH = 0x00040000
|
m_data3 (RW) : ORIGIN = 0x20200000, LENGTH = 0x00040000
|
||||||
m_heap (RW) : ORIGIN = NCACHE_HEAP_START, LENGTH = HEAP_SIZE
|
m_heap (RW) : ORIGIN = NCACHE_HEAP_START, LENGTH = HEAP_SIZE
|
||||||
|
|
|
||||||
|
|
@ -65,18 +65,18 @@ HIL-тесты через pyOCD + pytest, GDB-сервер для отладки
|
||||||
|
|
||||||
## 3. Что устанавливается и где
|
## 3. Что устанавливается и где
|
||||||
|
|
||||||
| Инструмент | Хост | Devcontainer |
|
| Инструмент | Хост | Devcontainer |
|
||||||
|------------|------|--------------|
|
| ----------------------------------- | --------------- | --------------- |
|
||||||
| `just` | ✅ | ✅ Dockerfile |
|
| `just` | ✅ | ✅ Dockerfile |
|
||||||
| `docker` | ✅ | — |
|
| `docker` | ✅ | — |
|
||||||
| `uv` | ✅ | ✅ Dockerfile |
|
| `uv` | ✅ | ✅ Dockerfile |
|
||||||
| `spsdk` (sdphost, blhost, nxpimage) | ✅ `tools/host/` | ✅ `tools/host/` |
|
| `spsdk` (sdphost, blhost, nxpimage) | ✅ `tools/host/` | ✅ `tools/host/` |
|
||||||
| `pyocd` + `pyserial` + `pytest` | ✅ `tools/hil/` | — |
|
| `pyocd` + `pyserial` + `pytest` | ✅ `tools/hil/` | — |
|
||||||
| `mpremote` | ✅ `tools/hil/` | — |
|
| `mpremote` | ✅ `tools/hil/` | — |
|
||||||
| ARM GCC toolchain | — | ✅ |
|
| ARM GCC toolchain | — | ✅ |
|
||||||
| `cmake` / `ninja` | — | ✅ |
|
| `cmake` / `ninja` | — | ✅ |
|
||||||
| `clang` / `clangd` / `clang-tidy` | — | ✅ |
|
| `clang` / `clangd` / `clang-tidy` | — | ✅ |
|
||||||
| Unity / fff | — | ✅ vendored |
|
| Unity / fff | — | ✅ vendored |
|
||||||
|
|
||||||
`spsdk` и `pyocd` — отдельные uv-проекты с разными ролями:
|
`spsdk` и `pyocd` — отдельные uv-проекты с разными ролями:
|
||||||
|
|
||||||
|
|
@ -226,11 +226,11 @@ HIL_USB_CDC_TIMEOUT=5.0
|
||||||
|
|
||||||
### 5.1 Предварительные требования
|
### 5.1 Предварительные требования
|
||||||
|
|
||||||
| Платформа | Что нужно до bootstrap |
|
| Платформа | Что нужно до bootstrap |
|
||||||
|-----------|------------------------|
|
| --------- | ---------------------------------------------- |
|
||||||
| Linux | `docker`, `git`, `curl` |
|
| Linux | `docker`, `git`, `curl` |
|
||||||
| macOS | Docker Desktop, `git` (Xcode CLT) |
|
| macOS | Docker Desktop, `git` (Xcode CLT) |
|
||||||
| Windows | Docker Desktop, Git for Windows → **Git Bash** |
|
| Windows | Docker Desktop, Git for Windows → **Git Bash** |
|
||||||
|
|
||||||
### 5.2 Единственная команда
|
### 5.2 Единственная команда
|
||||||
|
|
||||||
|
|
@ -279,11 +279,11 @@ just host::m5-deploy
|
||||||
|
|
||||||
### 6.1 Четыре типа сборки
|
### 6.1 Четыре типа сборки
|
||||||
|
|
||||||
| Пресет | Toolchain | Назначение | Линкер-скрипт |
|
| Пресет | Toolchain | Назначение | Линкер-скрипт |
|
||||||
|--------|-----------|------------|---------------|
|
| ----------------------------- | ------------ | ---------------------------------- | ---------------- |
|
||||||
| `Debug` / `Release` | ARM GCC | firmware_test, bootloader, tft_app | `flexspi_nor.ld` |
|
| `Debug` / `Release` | ARM GCC | firmware_test, bootloader, tft_app | `flexspi_nor.ld` |
|
||||||
| `host-debug` / `host-release` | clang (хост) | Unity + fff тесты | — |
|
| `host-debug` / `host-release` | clang (хост) | Unity + fff тесты | — |
|
||||||
| `target-debug` | ARM GCC | HIL target-прошивки | `ram.ld` |
|
| `target-debug` | ARM GCC | HIL target-прошивки | `ram.ld` |
|
||||||
|
|
||||||
### 6.2 CMake пресеты
|
### 6.2 CMake пресеты
|
||||||
|
|
||||||
|
|
@ -310,12 +310,12 @@ buildPresets (HIL):
|
||||||
|
|
||||||
### 6.3 Boot-стратегии
|
### 6.3 Boot-стратегии
|
||||||
|
|
||||||
| Прошивка | Стратегия | Инструмент загрузки |
|
| Прошивка | Стратегия | Инструмент загрузки |
|
||||||
|----------|-----------|---------------------|
|
| ---------------------------- | ---------------------------------- | ------------------- |
|
||||||
| `firmware_test` | XIP из Flash (`flexspi_nor.ld`) | SPSDK → Flash |
|
| `firmware_test` | XIP из Flash (`flexspi_nor.ld`) | SPSDK → Flash |
|
||||||
| `bootloader` | Копирование в ITCM | SPSDK → Flash |
|
| `bootloader` | Копирование в ITCM | SPSDK → Flash |
|
||||||
| `tft_app` | XIP + буферы в SDRAM | SPSDK → Flash |
|
| `tft_app` | XIP + буферы в SDRAM | SPSDK → Flash |
|
||||||
| HIL target (`tests/target/`) | Исполнение из ITCM/DTCM (`ram.ld`) | pyOCD → RAM |
|
| HIL target (`tests/target/`) | Исполнение из ITCM/DTCM (`ram.ld`) | pyOCD → RAM |
|
||||||
|
|
||||||
**HIL boot-стратегия:** pyOCD настраивает FLEXRAM (128 KB ITCM + 128 KB DTCM + 256 KB OCRAM), записывает PT_LOAD сегменты ELF по физическим адресам, устанавливает SP/PC из таблицы векторов и запускает выполнение. Flash не используется — прошивка исчезает при отключении питания.
|
**HIL boot-стратегия:** pyOCD настраивает FLEXRAM (128 KB ITCM + 128 KB DTCM + 256 KB OCRAM), записывает PT_LOAD сегменты ELF по физическим адресам, устанавливает SP/PC из таблицы векторов и запускает выполнение. Flash не используется — прошивка исчезает при отключении питания.
|
||||||
|
|
||||||
|
|
@ -325,19 +325,19 @@ buildPresets (HIL):
|
||||||
|
|
||||||
### 7.1 Карта задач по контекстам
|
### 7.1 Карта задач по контекстам
|
||||||
|
|
||||||
| Задача | Где |
|
| Задача | Где |
|
||||||
|--------|-----|
|
| -------------------------------------- | -------------------------- |
|
||||||
| Написание кода, clangd, форматирование | devcontainer |
|
| Написание кода, clangd, форматирование | devcontainer |
|
||||||
| Статический анализ (clang-tidy) | devcontainer |
|
| Статический анализ (clang-tidy) | devcontainer |
|
||||||
| Host unit-тесты (Unity + fff) | devcontainer |
|
| Host unit-тесты (Unity + fff) | devcontainer |
|
||||||
| Сборка ARM firmware (ELF) | devcontainer |
|
| Сборка ARM firmware (ELF) | devcontainer |
|
||||||
| Сборка HIL target-прошивок | devcontainer |
|
| Сборка HIL target-прошивок | devcontainer |
|
||||||
| Подготовка HAB-образов (nxpimage) | devcontainer |
|
| Подготовка HAB-образов (nxpimage) | devcontainer |
|
||||||
| Прошивка платы через USB ROM | хост |
|
| Прошивка платы через USB ROM | хост |
|
||||||
| Прошивка платы через SWD | хост |
|
| Прошивка платы через SWD | хост |
|
||||||
| HIL-тесты (pyOCD + pytest + M5) | хост |
|
| HIL-тесты (pyOCD + pytest + M5) | хост |
|
||||||
| Отладка — GDB-сервер (pyOCD) | хост |
|
| Отладка — GDB-сервер (pyOCD) | хост |
|
||||||
| Отладка — GDB-клиент (cortex-debug) | devcontainer → хост по TCP |
|
| Отладка — GDB-клиент (cortex-debug) | devcontainer → хост по TCP |
|
||||||
|
|
||||||
### 7.2 Типичная сессия разработки
|
### 7.2 Типичная сессия разработки
|
||||||
|
|
||||||
|
|
@ -369,15 +369,15 @@ just host::debug-server # запустить pyOCD GDB-серв
|
||||||
|
|
||||||
### 7.3 VSCode Tasks (внутри devcontainer)
|
### 7.3 VSCode Tasks (внутри devcontainer)
|
||||||
|
|
||||||
| Таск | Команда |
|
| Таск | Команда |
|
||||||
|------|---------|
|
| ------------------------- | ------------------------------------ |
|
||||||
| 🔨 Build | `just build::build-<project>-<type>` |
|
| 🔨 Build | `just build::build-<project>-<type>` |
|
||||||
| 🧪 Host Tests (Debug) | `just build::test-host` |
|
| 🧪 Host Tests (Debug) | `just build::test-host` |
|
||||||
| 🧪 Host Tests (Release) | `just build::test-host-release` |
|
| 🧪 Host Tests (Release) | `just build::test-host-release` |
|
||||||
| 🎯 Build HIL Target Tests | `just build::build-hil` |
|
| 🎯 Build HIL Target Tests | `just build::build-hil` |
|
||||||
| 📦 HAB Image | `just build::hab-<project>-<type>` |
|
| 📦 HAB Image | `just build::hab-<project>-<type>` |
|
||||||
| 📦 HAB All (Debug/Release) | `just build::hab-all-debug/release` |
|
| 📦 HAB All (Debug/Release) | `just build::hab-all-debug/release` |
|
||||||
| 🗑️ Clean | `just build::clean` |
|
| 🗑️ Clean | `just build::clean` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
Отладка построена на проброске GDB-сервера с хоста в devcontainer по TCP. Это позволяет держать весь инструментарий сборки и языковой сервер внутри контейнера, не проводя USB-пробник внутрь Docker.
|
Отладка построена на проброске GDB-сервера с хоста в devcontainer по TCP. Это позволяет держать весь инструментарий сборки и языковой сервер внутри контейнера, не проводя USB-пробник внутрь Docker.
|
||||||
|
|
||||||
```
|
```bash
|
||||||
┌─────────────────────────────────────┐ ┌──────────────────────────────────┐
|
┌─────────────────────────────────────┐ ┌──────────────────────────────────┐
|
||||||
│ Хост (macOS/Linux) │ │ DevContainer │
|
│ Хост (macOS/Linux) │ │ DevContainer │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
|
|
@ -26,25 +26,25 @@
|
||||||
|
|
||||||
### На хосте
|
### На хосте
|
||||||
|
|
||||||
| Компонент | Роль | Источник |
|
| Компонент | Роль | Источник |
|
||||||
|---|---|---|
|
| --------------------------------- | ------------------------------- | -------------------------- |
|
||||||
| `pyocd` | GDB-сервер + flash-программатор | `tools/hil/uv.lock` |
|
| `pyocd` | GDB-сервер + flash-программатор | `tools/hil/uv.lock` |
|
||||||
| `MCU-Link` | CMSIS-DAP v2 пробник | USB к плате |
|
| `MCU-Link` | CMSIS-DAP v2 пробник | USB к плате |
|
||||||
| `just host::debug-server` | Запуск GDB-сервера | `just/host.just` |
|
| `just host::debug-server` | Запуск GDB-сервера | `just/host.just` |
|
||||||
| `just host::flash-swd-*` | Прошивка через SWD | `just/host.just` |
|
| `just host::flash-swd-*` | Прошивка через SWD | `just/host.just` |
|
||||||
| `tools/host/flash_swd.py` | Сборка FCB+HAB образа и запись | `tools/host/` |
|
| `tools/host/flash_swd.py` | Сборка FCB+HAB образа и запись | `tools/host/` |
|
||||||
| `tools/host/dcd/w25q128_fdcb.bin` | FCB для W25Q128 (Quad SPI) | NXP SecureProvisioningTool |
|
| `tools/host/dcd/w25q128_fdcb.bin` | FCB для W25Q128 (Quad SPI) | NXP SecureProvisioningTool |
|
||||||
|
|
||||||
### В devcontainer
|
### В devcontainer
|
||||||
|
|
||||||
| Компонент | Роль |
|
| Компонент | Роль |
|
||||||
|---|---|
|
| -------------------------------------- | ---------------------------------------------- |
|
||||||
| `arm-none-eabi-gdb` | GDB клиент, подключается к серверу на хосте |
|
| `arm-none-eabi-gdb` | GDB клиент, подключается к серверу на хосте |
|
||||||
| `cortex-debug` (VSCode extension) | UI для GDB: брейкпоинты, стек, регистры |
|
| `cortex-debug` (VSCode extension) | UI для GDB: брейкпоинты, стек, регистры |
|
||||||
| `.vscode/launch.json` | Конфигурации запуска отладки |
|
| `.vscode/launch.json` | Конфигурации запуска отладки |
|
||||||
| `.vscode/tasks.json` | `preLaunchTask` — пересборка ELF перед стартом |
|
| `.vscode/tasks.json` | `preLaunchTask` — пересборка ELF перед стартом |
|
||||||
| `build/Debug/*.elf` | Символы для GDB (DWARF debug info) |
|
| `build/Debug/*.elf` | Символы для GDB (DWARF debug info) |
|
||||||
| `bsp/generated/startup/MIMXRT1052.xml` | SVD — описание регистров периферии |
|
| `bsp/generated/startup/MIMXRT1052.xml` | SVD — описание регистров периферии |
|
||||||
|
|
||||||
### Конфигурация
|
### Конфигурация
|
||||||
|
|
||||||
|
|
@ -62,11 +62,11 @@ FCB_PATH=tools/host/dcd/w25q128_fdcb.bin
|
||||||
|
|
||||||
## Прошивки, поддерживаемые отладкой
|
## Прошивки, поддерживаемые отладкой
|
||||||
|
|
||||||
| Конфигурация VSCode | ELF | Особенности |
|
| Конфигурация VSCode | ELF | Особенности |
|
||||||
|---|---|---|
|
| ----------------------------- | ------------------------------- | ---------------------------- |
|
||||||
| `🐛 Debug: firmware_test` | `build/Debug/firmware_test.elf` | Bare-metal, входной контроль |
|
| `🐛 Debug: firmware_test` | `build/Debug/firmware_test.elf` | Bare-metal, входной контроль |
|
||||||
| `🐛 Debug: bootloader` | `build/Debug/bootloader.elf` | Bare-metal, A/B обновление |
|
| `🐛 Debug: bootloader` | `build/Debug/bootloader.elf` | Bare-metal, A/B обновление |
|
||||||
| `🐛 Debug: tft_app (FreeRTOS)` | `build/Debug/app.elf` | FreeRTOS, task view |
|
| `🐛 Debug: tft_app (FreeRTOS)` | `build/Debug/app.elf` | FreeRTOS, task view |
|
||||||
|
|
||||||
Все три — XIP-прошивки, исполняются напрямую из QuadSPI NOR Flash (`0x60000000`).
|
Все три — XIP-прошивки, исполняются напрямую из QuadSPI NOR Flash (`0x60000000`).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
Поддерживаются два независимых способа прошивки:
|
Поддерживаются два независимых способа прошивки:
|
||||||
|
|
||||||
| Способ | Интерфейс | Требование | Инструмент |
|
| Способ | Интерфейс | Требование | Инструмент |
|
||||||
|---|---|---|---|
|
| ----------- | -------------------- | ---------------------------------- | -------------------------- |
|
||||||
| **USB SDP** | USB ↔ ROM-загрузчик | BOOT_MODE = 01 (Serial Downloader) | `spsdk` (sdphost + blhost) |
|
| **USB SDP** | USB ↔ ROM-загрузчик | BOOT_MODE = 01 (Serial Downloader) | `spsdk` (sdphost + blhost) |
|
||||||
| **SWD** | MCU-Link ↔ CMSIS-DAP | Плата в любом режиме загрузки | `pyocd` |
|
| **SWD** | MCU-Link ↔ CMSIS-DAP | Плата в любом режиме загрузки | `pyocd` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -127,11 +127,11 @@ just host::flash-swd-app-release
|
||||||
|
|
||||||
### 2.4 Зависимости
|
### 2.4 Зависимости
|
||||||
|
|
||||||
| Файл | Назначение |
|
| Файл | Назначение |
|
||||||
|---|---|
|
| --------------------------------- | ----------------------------------- |
|
||||||
| `tools/host/flash_swd.py` | Скрипт сборки образа и вызова pyOCD |
|
| `tools/host/flash_swd.py` | Скрипт сборки образа и вызова pyOCD |
|
||||||
| `tools/host/dcd/w25q128_fdcb.bin` | FCB для W25Q128 в режиме Quad SPI |
|
| `tools/host/dcd/w25q128_fdcb.bin` | FCB для W25Q128 в режиме Quad SPI |
|
||||||
| `tools/hil/` (uv-проект) | pyocd, вызывается через `uv run` |
|
| `tools/hil/` (uv-проект) | pyocd, вызывается через `uv run` |
|
||||||
|
|
||||||
FCB-бинарник (`w25q128_fdcb.bin`) генерируется в NXP SecureProvisioningTool
|
FCB-бинарник (`w25q128_fdcb.bin`) генерируется в NXP SecureProvisioningTool
|
||||||
для W25Q128 в режиме Quad SPI и хранится в репозитории — пересоздавать не нужно.
|
для W25Q128 в режиме Quad SPI и хранится в репозитории — пересоздавать не нужно.
|
||||||
|
|
@ -140,15 +140,15 @@ FCB-бинарник (`w25q128_fdcb.bin`) генерируется в NXP Secure
|
||||||
|
|
||||||
## Сравнение способов
|
## Сравнение способов
|
||||||
|
|
||||||
| | USB SDP | SWD |
|
| | USB SDP | SWD |
|
||||||
|---|---|---|
|
| ------------------------- | -------------------------------- | -------------------- |
|
||||||
| Переключение BOOT_MODE | Нужно | Не нужно |
|
| Переключение BOOT_MODE | Нужно | Не нужно |
|
||||||
| Power cycle после записи | Не нужен | **Обязателен** |
|
| Power cycle после записи | Не нужен | **Обязателен** |
|
||||||
| FCB в образе | Не нужен (Flashloader пишет сам) | **Обязателен** |
|
| FCB в образе | Не нужен (Flashloader пишет сам) | **Обязателен** |
|
||||||
| Скорость записи | ~50–100 kB/s | ~8–10 kB/s |
|
| Скорость записи | ~50–100 kB/s | ~8–10 kB/s |
|
||||||
| Совместимость с отладкой | Раздельно | MCU-Link монопольный |
|
| Совместимость с отладкой | Раздельно | MCU-Link монопольный |
|
||||||
| Производственный сценарий | ✓ | — |
|
| Производственный сценарий | ✓ | — |
|
||||||
| Итеративная разработка | Неудобно (смена режима) | ✓ |
|
| Итеративная разработка | Неудобно (смена режима) | ✓ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@
|
||||||
|
|
||||||
## Оборудование
|
## Оборудование
|
||||||
|
|
||||||
| Устройство | Роль |
|
| Устройство | Роль |
|
||||||
|-------------------|-------------------------------------------------------------|
|
| -------------------- | ---------------------------------------------------------- |
|
||||||
| MIMXRT1052CVJ5B | Таргет — плата под тестом |
|
| MIMXRT1052CVJ5B | Таргет — плата под тестом |
|
||||||
| M5Stack StamPLC | Промежуточная платформа стенда: питание таргета + сигналы |
|
| M5Stack StamPLC | Промежуточная платформа стенда: питание таргета + сигналы |
|
||||||
| MCU-Link (CMSIS-DAP) | SWD-probe + VCOM (pyOCD загружает ELF, pytest читает UART) |
|
| MCU-Link (CMSIS-DAP) | SWD-probe + VCOM (pyOCD загружает ELF, pytest читает UART) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -18,9 +18,9 @@
|
||||||
|
|
||||||
### Питание таргета
|
### Питание таргета
|
||||||
|
|
||||||
| M5 реле | Куда | Назначение |
|
| M5 реле | Куда | Назначение |
|
||||||
|---------|------------------|---------------------|
|
| ------- | ----------- | ------------------- |
|
||||||
| RLY1 | VIN таргета | Управление питанием |
|
| RLY1 | VIN таргета | Управление питанием |
|
||||||
|
|
||||||
Питание включается и выключается автоматически фикстурой `m5` в `conftest.py`:
|
Питание включается и выключается автоматически фикстурой `m5` в `conftest.py`:
|
||||||
|
|
||||||
|
|
@ -32,11 +32,11 @@
|
||||||
Таргет: оптопары PS2801-4, **неинвертирующие** (active-HIGH).
|
Таргет: оптопары PS2801-4, **неинвертирующие** (active-HIGH).
|
||||||
M5: реле AW9523B через ULN2003A, нормально разомкнутые (NO).
|
M5: реле AW9523B через ULN2003A, нормально разомкнутые (NO).
|
||||||
|
|
||||||
| M5 реле | Сигнал таргета | BSP канал | MCU пин | GPIO |
|
| M5 реле | Сигнал таргета | BSP канал | MCU пин | GPIO |
|
||||||
|---------|----------------|--------------------|---------------|-----------|
|
| ------- | -------------- | ----------------- | ------------- | --------- |
|
||||||
| RLY2 | RS_RX | `BSP_OPTO_CH_RS` | GPIO_AD_B1_07 | GPIO1[23] |
|
| RLY2 | RS_RX | `BSP_OPTO_CH_RS` | GPIO_AD_B1_07 | GPIO1[23] |
|
||||||
| RLY3 | EXT_IN1 | `BSP_OPTO_CH_IN1` | GPIO_AD_B1_06 | GPIO1[22] |
|
| RLY3 | EXT_IN1 | `BSP_OPTO_CH_IN1` | GPIO_AD_B1_06 | GPIO1[22] |
|
||||||
| RLY4 | EXT_IN2 | `BSP_OPTO_CH_IN2` | GPIO_AD_B1_05 | GPIO1[21] |
|
| RLY4 | EXT_IN2 | `BSP_OPTO_CH_IN2` | GPIO_AD_B1_05 | GPIO1[21] |
|
||||||
|
|
||||||
**Логика сигнала:**
|
**Логика сигнала:**
|
||||||
|
|
||||||
|
|
@ -57,13 +57,13 @@ _OPTO_TO_RELAY = {1: 3, 2: 4, 3: 2}
|
||||||
|
|
||||||
### Порты и переменные окружения
|
### Порты и переменные окружения
|
||||||
|
|
||||||
| Переменная | Значение по умолчанию | Назначение |
|
| Переменная | Значение по умолчанию | Назначение |
|
||||||
|----------------------|-----------------------|---------------------------|
|
| --------------- | --------------------- | ------------------------ |
|
||||||
| `HIL_VCOM_PORT` | `/dev/ttyACM0` | MCU-Link VCOM (UART CLI) |
|
| `HIL_VCOM_PORT` | `/dev/ttyACM0` | MCU-Link VCOM (UART CLI) |
|
||||||
| `HIL_VCOM_BAUD` | `115200` | Скорость UART CLI |
|
| `HIL_VCOM_BAUD` | `115200` | Скорость UART CLI |
|
||||||
| `HIL_M5_PORT` | `/dev/ttyACM1` | M5StampPLC USB CDC |
|
| `HIL_M5_PORT` | `/dev/ttyACM1` | M5StampPLC USB CDC |
|
||||||
| `HIL_M5_BAUD` | `115200` | Скорость M5 агента |
|
| `HIL_M5_BAUD` | `115200` | Скорость M5 агента |
|
||||||
| `HIL_BUILD_DIR` | `build/target-debug` | Путь к собранным ELF |
|
| `HIL_BUILD_DIR` | `build/target-debug` | Путь к собранным ELF |
|
||||||
|
|
||||||
На macOS порты выглядят как `/dev/cu.usbmodem*`. Задаются в `.env` в корне репозитория.
|
На macOS порты выглядят как `/dev/cu.usbmodem*`. Задаются в `.env` в корне репозитория.
|
||||||
|
|
||||||
|
|
@ -73,11 +73,11 @@ _OPTO_TO_RELAY = {1: 3, 2: 4, 3: 2}
|
||||||
|
|
||||||
### M5Stack StamPLC — `tools/hil/m5/`
|
### M5Stack StamPLC — `tools/hil/m5/`
|
||||||
|
|
||||||
| Файл | Назначение |
|
| Файл | Назначение |
|
||||||
|--------------|----------------------------------------------------------------|
|
| ---------- | --------------------------------------------------------------------------------------- |
|
||||||
| `agent.py` | MicroPython агент на M5. Принимает JSON-команды через USB CDC, подает сигналы на таргет |
|
| `agent.py` | MicroPython агент на M5. Принимает JSON-команды через USB CDC, подает сигналы на таргет |
|
||||||
| `cli.py` | Интерактивный CLI для ручного тестирования агента |
|
| `cli.py` | Интерактивный CLI для ручного тестирования агента |
|
||||||
| `power.py` | Скрипт управления питанием таргета (RLY1) из командной строки |
|
| `power.py` | Скрипт управления питанием таргета (RLY1) из командной строки |
|
||||||
|
|
||||||
**Протокол агента:** JSON-lines через USB CDC (115200 бод).
|
**Протокол агента:** JSON-lines через USB CDC (115200 бод).
|
||||||
|
|
||||||
|
|
@ -88,19 +88,19 @@ M5 → хост: {"ok": true, "opto_ch": 1, "relay": 3, "state": true}\r\n
|
||||||
|
|
||||||
**Доступные команды агента:**
|
**Доступные команды агента:**
|
||||||
|
|
||||||
| Команда | Параметры | Действие |
|
| Команда | Параметры | Действие |
|
||||||
|----------------|------------------------------------|---------------------------------------------------|
|
| --------------- | ------------------------------------------- | ---------------------------------------------- |
|
||||||
| `ping` | — | Проверка связи |
|
| `ping` | — | Проверка связи |
|
||||||
| `info` | — | Версия, состояние CAN и AW9523, статус реле |
|
| `info` | — | Версия, состояние CAN и AW9523, статус реле |
|
||||||
| `power` | `state: bool` | RLY1 — питание таргета |
|
| `power` | `state: bool` | RLY1 — питание таргета |
|
||||||
| `relay_set` | `ch: 1-4, state: bool` | Прямое управление реле |
|
| `relay_set` | `ch: 1-4, state: bool` | Прямое управление реле |
|
||||||
| `relay_get` | `ch: 1-4` | Прочитать текущее состояние реле |
|
| `relay_get` | `ch: 1-4` | Прочитать текущее состояние реле |
|
||||||
| `relay_all_off`| — | Выключить все реле |
|
| `relay_all_off` | — | Выключить все реле |
|
||||||
| `opto_set` | `ch: 1-3, state: bool` | Управление оптоканалом таргета (через маппинг) |
|
| `opto_set` | `ch: 1-3, state: bool` | Управление оптоканалом таргета (через маппинг) |
|
||||||
| `opto_all_off` | — | Выключить все оптоканалы |
|
| `opto_all_off` | — | Выключить все оптоканалы |
|
||||||
| `input_read` | `ch: 1-8` | Прочитать вход стенда SYS_IN (оптопара на M5) |
|
| `input_read` | `ch: 1-8` | Прочитать вход стенда SYS_IN (оптопара на M5) |
|
||||||
| `can_send` | `id: int, data: list[int], ext: bool=false` | Отправить CAN-фрейм с шины M5 |
|
| `can_send` | `id: int, data: list[int], ext: bool=false` | Отправить CAN-фрейм с шины M5 |
|
||||||
| `can_recv` | `timeout_ms: int=500` | Принять CAN-фрейм на M5 (ошибка при таймауте) |
|
| `can_recv` | `timeout_ms: int=500` | Принять CAN-фрейм на M5 (ошибка при таймауте) |
|
||||||
|
|
||||||
**Деплой агента на M5:**
|
**Деплой агента на M5:**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@ just/build.just
|
||||||
|
|
||||||
Три типа тестов:
|
Три типа тестов:
|
||||||
|
|
||||||
| Тип | Использует M5 | Запуск | Когда применять |
|
| Тип | Использует M5 | Запуск | Когда применять |
|
||||||
|-----|--------------|--------|-----------------|
|
| ----------------- | ------------- | --------------------- | ----------------------------------------------------- |
|
||||||
| **Базовый** | Нет | `hil-run` | Тестирование UART CLI, алгоритмов, таймингов |
|
| **Базовый** | Нет | `hil-run` | Тестирование UART CLI, алгоритмов, таймингов |
|
||||||
| **С M5** | Да | `hil-run` | Тестирование GPIO, оптовходов, реле, питания |
|
| **С M5** | Да | `hil-run` | Тестирование GPIO, оптовходов, реле, питания |
|
||||||
| **Интерактивный** | Нет / Да | `hil-run-interactive` | Периферия требует действий оператора: кнопки, дисплей |
|
| **Интерактивный** | Нет / Да | `hil-run-interactive` | Периферия требует действий оператора: кнопки, дисплей |
|
||||||
|
|
||||||
Интерактивные тесты помечаются `@pytest.mark.interactive` и **никогда не входят в `hil-run`** — они требуют живого оператора и не пригодны для CI.
|
Интерактивные тесты помечаются `@pytest.mark.interactive` и **никогда не входят в `hil-run`** — они требуют живого оператора и не пригодны для CI.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,10 @@ def uart(request):
|
||||||
|
|
||||||
## 2. `yield` vs `return`
|
## 2. `yield` vs `return`
|
||||||
|
|
||||||
| Конструкция | Setup | Teardown | Когда использовать |
|
| Конструкция | Setup | Teardown | Когда использовать |
|
||||||
|-------------|-------|----------|--------------------|
|
| ----------- | ----- | -------- | --------------------------------------------- |
|
||||||
| `return` | ✅ | ❌ | Ресурс не требует очистки (загрузка ELF) |
|
| `return` | ✅ | ❌ | Ресурс не требует очистки (загрузка ELF) |
|
||||||
| `yield` | ✅ | ✅ | Ресурс нужно освободить (порт, реле, питание) |
|
| `yield` | ✅ | ✅ | Ресурс нужно освободить (порт, реле, питание) |
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# return — teardown не нужен
|
# return — teardown не нужен
|
||||||
|
|
@ -56,12 +56,12 @@ def m5(request):
|
||||||
|
|
||||||
Scope определяет **как долго живёт** экземпляр фикстуры.
|
Scope определяет **как долго живёт** экземпляр фикстуры.
|
||||||
|
|
||||||
| Scope | Создаётся | Уничтожается | Типичное применение в HIL |
|
| Scope | Создаётся | Уничтожается | Типичное применение в HIL |
|
||||||
|-------|-----------|--------------|---------------------------|
|
| ---------- | ------------------------------ | ----------------------------- | ---------------------------------- |
|
||||||
| `function` | Перед каждым `test_*()` | После каждого `test_*()` | Сброс состояния стенда |
|
| `function` | Перед каждым `test_*()` | После каждого `test_*()` | Сброс состояния стенда |
|
||||||
| `class` | Перед первым тестом класса | После последнего теста класса | Группа связанных тестов |
|
| `class` | Перед первым тестом класса | После последнего теста класса | Группа связанных тестов |
|
||||||
| `module` | Перед первым тестом файла | После последнего теста файла | **Загрузка ELF, открытие UART** |
|
| `module` | Перед первым тестом файла | После последнего теста файла | **Загрузка ELF, открытие UART** |
|
||||||
| `session` | Один раз на весь pytest-запуск | В самом конце | Подключение к M5, глобальный setup |
|
| `session` | Один раз на весь pytest-запуск | В самом конце | Подключение к M5, глобальный setup |
|
||||||
|
|
||||||
### Почему для HIL основной scope — `module`
|
### Почему для HIL основной scope — `module`
|
||||||
|
|
||||||
|
|
@ -202,18 +202,18 @@ tools/hil/
|
||||||
|
|
||||||
### Что живёт в `conftest.py` проекта
|
### Что живёт в `conftest.py` проекта
|
||||||
|
|
||||||
| Фикстура / функция | Scope | Назначение |
|
| Фикстура / функция | Scope | Назначение |
|
||||||
|---------------------|-------|------------|
|
| -------------------------- | -------------------- | ------------------------------------------------------------ |
|
||||||
| `_load_elf()` | вспомогательная | pyOCD: halt → FLEXRAM → load ELF → run |
|
| `_load_elf()` | вспомогательная | pyOCD: halt → FLEXRAM → load ELF → run |
|
||||||
| `_uart_context()` | контекстный менеджер | Открыть VCOM, дождаться `READY\r\n`, гарантировать `close()` |
|
| `_uart_context()` | контекстный менеджер | Открыть VCOM, дождаться `READY\r\n`, гарантировать `close()` |
|
||||||
| `_make_uart_fixture()` | фабрика | Генерирует `uart_*` фикстуры из `_UART_FIXTURE_MAP` |
|
| `_make_uart_fixture()` | фабрика | Генерирует `uart_*` фикстуры из `_UART_FIXTURE_MAP` |
|
||||||
| `_UART_FIXTURE_MAP` | словарь | Связь `uart_<n>` → `loaded_<n>` для всех тестов |
|
| `_UART_FIXTURE_MAP` | словарь | Связь `uart_<n>` → `loaded_<n>` для всех тестов |
|
||||||
| `loaded_host_uart` | module | Загрузить `test_host_uart.elf` |
|
| `loaded_host_uart` | module | Загрузить `test_host_uart.elf` |
|
||||||
| `loaded_hil_opto` | module | Загрузить `test_hil_opto.elf`, зависит от `m5` |
|
| `loaded_hil_opto` | module | Загрузить `test_hil_opto.elf`, зависит от `m5` |
|
||||||
| `uart` / `uart_opto` / ... | module | Создаются автоматически через `_UART_FIXTURE_MAP` |
|
| `uart` / `uart_opto` / ... | module | Создаются автоматически через `_UART_FIXTURE_MAP` |
|
||||||
| `usb_cdc_port` | module | USB CDC порт таргета, использует `cfg.TARGET_VCOM_*` |
|
| `usb_cdc_port` | module | USB CDC порт таргета, использует `cfg.TARGET_VCOM_*` |
|
||||||
| `m5` | module | Подключиться к M5, включить питание |
|
| `m5` | module | Подключиться к M5, включить питание |
|
||||||
| `uart_cmd()` | обычная функция | Отправить команду, прочитать ответ |
|
| `uart_cmd()` | обычная функция | Отправить команду, прочитать ответ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ CMakePresets.json just/build.just
|
||||||
|
|
||||||
Перед написанием кода определи к какой категории относится модуль:
|
Перед написанием кода определи к какой категории относится модуль:
|
||||||
|
|
||||||
| Категория | Описание | Инструментарий |
|
| Категория | Описание | Инструментарий |
|
||||||
|-----------|----------|----------------|
|
| --------- | -------------------------------------------------------------- | ------------------------- |
|
||||||
| **A** | Нет вызовов NXP SDK: алгоритмы, парсеры, FSM, структуры данных | Unity |
|
| **A** | Нет вызовов NXP SDK: алгоритмы, парсеры, FSM, структуры данных | Unity |
|
||||||
| **B** | BSP-модуль вызывает `fsl_*.h`, USB-стек и т.д. | Unity + fff + stub-хедеры |
|
| **B** | BSP-модуль вызывает `fsl_*.h`, USB-стек и т.д. | Unity + fff + stub-хедеры |
|
||||||
|
|
||||||
**Признак категории A:** в `.c` файле модуля нет ни одного `#include "fsl_*.h"`.
|
**Признак категории A:** в `.c` файле модуля нет ни одного `#include "fsl_*.h"`.
|
||||||
**Признак категории B:** есть хотя бы один такой include.
|
**Признак категории B:** есть хотя бы один такой include.
|
||||||
|
|
@ -146,11 +146,11 @@ status_t SDK_Function_B(DRIVER_Type *base, const uint8_t *data, size_t len);
|
||||||
|
|
||||||
### Уже существующие stubs в `tests/host/mocks/`
|
### Уже существующие stubs в `tests/host/mocks/`
|
||||||
|
|
||||||
| Файл | Что заменяет | Используется в |
|
| Файл | Что заменяет | Используется в |
|
||||||
|------|-------------|----------------|
|
| ------------ | ----------------------------- | -------------- |
|
||||||
| `fsl_gpio.h` | GPIO драйвер | `test_bsp_led` |
|
| `fsl_gpio.h` | GPIO драйвер | `test_bsp_led` |
|
||||||
| `pin_mux.h` | Макросы пинов из `generated/` | `test_bsp_led` |
|
| `pin_mux.h` | Макросы пинов из `generated/` | `test_bsp_led` |
|
||||||
| `board.h` | `board_hw_init()` | `test_bsp_led` |
|
| `board.h` | `board_hw_init()` | `test_bsp_led` |
|
||||||
|
|
||||||
Если нужный stub уже есть — ничего создавать не нужно, просто укажи `mocks/` в `MOCKS` аргументе `add_host_test()`.
|
Если нужный stub уже есть — ничего создавать не нужно, просто укажи `mocks/` в `MOCKS` аргументе `add_host_test()`.
|
||||||
|
|
||||||
|
|
@ -184,12 +184,12 @@ add_host_test(
|
||||||
|
|
||||||
### Аргументы `add_host_test()`
|
### Аргументы `add_host_test()`
|
||||||
|
|
||||||
| Аргумент | Обязателен | Описание |
|
| Аргумент | Обязателен | Описание |
|
||||||
|----------|-----------|----------|
|
| ---------- | ---------- | --------------------------------------------------------------- |
|
||||||
| `NAME` | ✓ | Имя исполняемого файла и теста в CTest |
|
| `NAME` | ✓ | Имя исполняемого файла и теста в CTest |
|
||||||
| `SOURCES` | ✓ | Тестовый `.c` + исходники тестируемых модулей |
|
| `SOURCES` | ✓ | Тестовый `.c` + исходники тестируемых модулей |
|
||||||
| `INCLUDES` | — | Дополнительные include-пути (для `#include "bsp/led.h"` и т.д.) |
|
| `INCLUDES` | — | Дополнительные include-пути (для `#include "bsp/led.h"` и т.д.) |
|
||||||
| `MOCKS` | — | Директории со stub-хедерами (подключаются с высшим приоритетом) |
|
| `MOCKS` | — | Директории со stub-хедерами (подключаются с высшим приоритетом) |
|
||||||
|
|
||||||
`lib_external` (Unity + fff) подключается автоматически — добавлять не нужно.
|
`lib_external` (Unity + fff) подключается автоматически — добавлять не нужно.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ add_executable(
|
||||||
src/tests/test_sdram.c
|
src/tests/test_sdram.c
|
||||||
src/tests/test_qspi.c
|
src/tests/test_qspi.c
|
||||||
src/tests/test_usd.c
|
src/tests/test_usd.c
|
||||||
|
src/tests/test_display.c
|
||||||
${BSP_GENERATED}/clock_config.c
|
${BSP_GENERATED}/clock_config.c
|
||||||
${BSP_STARTUP_FILE}
|
${BSP_STARTUP_FILE}
|
||||||
${BSP_SYSCALLS_FILE})
|
${BSP_SYSCALLS_FILE})
|
||||||
|
|
@ -26,8 +27,8 @@ target_include_directories(firmware_test PRIVATE src/)
|
||||||
target_compile_definitions(
|
target_compile_definitions(
|
||||||
${TARGET_NAME}
|
${TARGET_NAME}
|
||||||
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512 BOARD_MPU_SDRAM=1
|
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512 BOARD_MPU_SDRAM=1
|
||||||
__STARTUP_INITIALIZE_RAMFUNCTION __STARTUP_CLEAR_BSS
|
DISPLAY_TEST_TYPE=BSP_DISPLAY_TFT8 __STARTUP_INITIALIZE_RAMFUNCTION
|
||||||
__STARTUP_INITIALIZE_NONCACHEDATA)
|
__STARTUP_CLEAR_BSS __STARTUP_INITIALIZE_NONCACHEDATA)
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Зависимости — только то что нужно для входного контроля bsp_board транзитивно
|
# Зависимости — только то что нужно для входного контроля bsp_board транзитивно
|
||||||
|
|
@ -37,6 +38,7 @@ target_link_libraries(
|
||||||
${TARGET_NAME}
|
${TARGET_NAME}
|
||||||
PRIVATE bsp_board
|
PRIVATE bsp_board
|
||||||
bsp_led
|
bsp_led
|
||||||
|
bsp_display
|
||||||
bsp_tick
|
bsp_tick
|
||||||
bsp_boot_xip
|
bsp_boot_xip
|
||||||
bsp_usb_cdc
|
bsp_usb_cdc
|
||||||
|
|
|
||||||
|
|
@ -190,17 +190,17 @@ main.c
|
||||||
|
|
||||||
**BSP-зависимости тест-модулей:**
|
**BSP-зависимости тест-модулей:**
|
||||||
|
|
||||||
| Тест | BSP модуль |
|
| Тест | BSP модуль |
|
||||||
|---|---|
|
| --------------- | ------------------------------ |
|
||||||
| `test_sdram` | `bsp_sdram` |
|
| `test_sdram` | `bsp_sdram` |
|
||||||
| `test_qspi` | `bsp_qspi` |
|
| `test_qspi` | `bsp_qspi` |
|
||||||
| `test_usd` | `bsp_usd` |
|
| `test_usd` | `bsp_usd` |
|
||||||
| `test_display` | существующий display BSP |
|
| `test_display` | существующий display BSP |
|
||||||
| `test_buttons` | `bsp_button` ✅ |
|
| `test_buttons` | `bsp_button` ✅ |
|
||||||
| `test_can` | `bsp_can` ✅ |
|
| `test_can` | `bsp_can` ✅ |
|
||||||
| `test_uart_ttl` | `bsp_uart_host` ✅ |
|
| `test_uart_ttl` | `bsp_uart_host` ✅ |
|
||||||
| `test_uart_iso` | `bsp_opto` (rs_as_gpio=true) ✅ |
|
| `test_uart_iso` | `bsp_opto` (rs_as_gpio=true) ✅ |
|
||||||
| `test_opto` | `bsp_opto` ✅ |
|
| `test_opto` | `bsp_opto` ✅ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -253,13 +253,13 @@ main.c
|
||||||
|
|
||||||
### Транспорт
|
### Транспорт
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| ------------------------- | ---------------------------------------------------------- |
|
||||||
| Интерфейс | USB CDC ACM, разъём J2 |
|
| Интерфейс | USB CDC ACM, разъём J2 |
|
||||||
| Кодировка | UTF-8 |
|
| Кодировка | UTF-8 |
|
||||||
| Фреймирование | JSON-lines: одна строка = одно сообщение, завершается `\n` |
|
| Фреймирование | JSON-lines: одна строка = одно сообщение, завершается `\n` |
|
||||||
| Максимальная длина строки | 128 байт включая `\n` |
|
| Максимальная длина строки | 128 байт включая `\n` |
|
||||||
| CR+LF | Принимается (таргет отбрасывает `\r`) |
|
| CR+LF | Принимается (таргет отбрасывает `\r`) |
|
||||||
|
|
||||||
Нет хэндшейка, нет sequence number, нет подтверждений доставки.
|
Нет хэндшейка, нет sequence number, нет подтверждений доставки.
|
||||||
Таргет идемпотентен для `ping` и `run` — при потере строки хост повторяет.
|
Таргет идемпотентен для `ping` и `run` — при потере строки хост повторяет.
|
||||||
|
|
@ -378,10 +378,10 @@ main.c
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| `status` | Смысл |
|
| `status` | Смысл |
|
||||||
|---|---|
|
| -------- | ------------------------------------------------------------------------ |
|
||||||
| `"pass"` | Тест пройден |
|
| `"pass"` | Тест пройден |
|
||||||
| `"fail"` | Тест провален; `detail` содержит описание (до 95 символов) |
|
| `"fail"` | Тест провален; `detail` содержит описание (до 95 символов) |
|
||||||
| `"skip"` | Пропущен: нет оборудования, таймаут, отказ оператора, critical fail выше |
|
| `"skip"` | Пропущен: нет оборудования, таймаут, отказ оператора, critical fail выше |
|
||||||
|
|
||||||
Примеры `detail`: `"addr=0x80001000 expected=0xA5 got=0x00"`, `"JEDEC ID mismatch"`.
|
Примеры `detail`: `"addr=0x80001000 expected=0xA5 got=0x00"`, `"JEDEC ID mismatch"`.
|
||||||
|
|
@ -482,17 +482,17 @@ main.c
|
||||||
|
|
||||||
## Матрица тестов
|
## Матрица тестов
|
||||||
|
|
||||||
| ID | Название | Тип | Critical | M5 HIL | Confirm |
|
| ID | Название | Тип | Critical | M5 HIL | Confirm |
|
||||||
|---|---|---|---|---|---|
|
| ---------- | ---------------- | ---------------- | -------- | ------ | ------------- |
|
||||||
| `sdram` | SDRAM 32 MB | self | ✅ | ❌ | ❌ |
|
| `sdram` | SDRAM 32 MB | self | ✅ | ❌ | ❌ |
|
||||||
| `qspi` | QSPI Flash 8 MB | self | ✅ | ❌ | ❌ |
|
| `qspi` | QSPI Flash 8 MB | self | ✅ | ❌ | ❌ |
|
||||||
| `usd` | uSD SDIO | self+interactive | ❌ | ❌ | ✅ pre_confirm |
|
| `usd` | uSD SDIO | self+interactive | ❌ | ❌ | ✅ pre_confirm |
|
||||||
| `display` | Display RGB888 | interactive | ❌ | ❌ | ✅ 4×в run() |
|
| `display` | Display RGB888 | interactive | ❌ | ❌ | ✅ 4×в run() |
|
||||||
| `buttons` | Test_But_1/2 | interactive | ❌ | ❌ | prompt only |
|
| `buttons` | Test_But_1/2 | interactive | ❌ | ❌ | prompt only |
|
||||||
| `can` | CAN loopback | HIL | ❌ | ✅ | ❌ |
|
| `can` | CAN loopback | HIL | ❌ | ✅ | ❌ |
|
||||||
| `uart_ttl` | UART TTL | HIL | ❌ | ✅ | ❌ |
|
| `uart_ttl` | UART TTL | HIL | ❌ | ✅ | ❌ |
|
||||||
| `uart_iso` | UART ISO / RS_RX | HIL | ❌ | ✅ | ❌ |
|
| `uart_iso` | UART ISO / RS_RX | HIL | ❌ | ✅ | ❌ |
|
||||||
| `opto` | Opto EXT_IN1/IN2 | HIL | ❌ | ✅ | ❌ |
|
| `opto` | Opto EXT_IN1/IN2 | HIL | ❌ | ✅ | ❌ |
|
||||||
|
|
||||||
**Типы confirm:**
|
**Типы confirm:**
|
||||||
|
|
||||||
|
|
@ -668,10 +668,10 @@ const test_module_t k_test_foo = {
|
||||||
|
|
||||||
### Что покрыто
|
### Что покрыто
|
||||||
|
|
||||||
| Таргет | Что тестирует | Тест-файл |
|
| Таргет | Что тестирует | Тест-файл |
|
||||||
|---|---|---|
|
| ---------------------- | ------------------------------------------------ | ------------------------------------------ |
|
||||||
| `test_protocol` | сериализация JSON (все event types) | `tests/host/protocol/test_protocol.c` |
|
| `test_protocol` | сериализация JSON (все event types) | `tests/host/protocol/test_protocol.c` |
|
||||||
| `test_cli` | парсинг входящих строк, диспатч по type | `tests/host/cli/test_cli.c` |
|
| `test_cli` | парсинг входящих строк, диспатч по type | `tests/host/cli/test_cli.c` |
|
||||||
| `test_firmware_runner` | state machine (IDLE/PRE_CONFIRM/RUNNING), реестр | `tests/host/runner/test_firmware_runner.c` |
|
| `test_firmware_runner` | state machine (IDLE/PRE_CONFIRM/RUNNING), реестр | `tests/host/runner/test_firmware_runner.c` |
|
||||||
|
|
||||||
### Запуск
|
### Запуск
|
||||||
|
|
@ -719,13 +719,13 @@ void test_run_all_critical_fail_skips_remaining(void)
|
||||||
|
|
||||||
Стандартный список моков для каждого теста:
|
Стандартный список моков для каждого теста:
|
||||||
|
|
||||||
| Зависимость | fff fake |
|
| Зависимость | fff fake |
|
||||||
|---|---|
|
| ----------------------------- | --------------------------------------------------------------- |
|
||||||
| `cli_send()` | `FAKE_VOID_FUNC(cli_send, const char *)` + custom_fake с копией |
|
| `cli_send()` | `FAKE_VOID_FUNC(cli_send, const char *)` + custom_fake с копией |
|
||||||
| `bsp_tick_get_ms()` | `FAKE_VALUE_FUNC(uint32_t, bsp_tick_get_ms)` |
|
| `bsp_tick_get_ms()` | `FAKE_VALUE_FUNC(uint32_t, bsp_tick_get_ms)` |
|
||||||
| `bsp_usb_cdc_poll()` | `FAKE_VOID_FUNC(bsp_usb_cdc_poll)` |
|
| `bsp_usb_cdc_poll()` | `FAKE_VOID_FUNC(bsp_usb_cdc_poll)` |
|
||||||
| `cli_process()` | `FAKE_VOID_FUNC(cli_process)` |
|
| `cli_process()` | `FAKE_VOID_FUNC(cli_process)` |
|
||||||
| `protocol_send_test_result()` | custom_fake — копируем `*p_result` по значению |
|
| `protocol_send_test_result()` | custom_fake — копируем `*p_result` по значению |
|
||||||
|
|
||||||
> **Ловушка dangling pointer:** `protocol_send_test_result` получает указатель
|
> **Ловушка dangling pointer:** `protocol_send_test_result` получает указатель
|
||||||
> на стековую переменную внутри `execute_test()`. После возврата указатель
|
> на стековую переменную внутри `execute_test()`. После возврата указатель
|
||||||
|
|
@ -750,11 +750,11 @@ void test_run_all_critical_fail_skips_remaining(void)
|
||||||
|
|
||||||
> Не пересматривать без явного запроса.
|
> Не пересматривать без явного запроса.
|
||||||
|
|
||||||
| Решение | Обоснование |
|
| Решение | Обоснование |
|
||||||
|---|---|
|
| -------------------------------------------- | ------------------------------------------------------------------ |
|
||||||
| Единственный транспорт — USB CDC ACM | HIL ELF-прошивки используют отдельный канал (UART + bsp_uart_host) |
|
| Единственный транспорт — USB CDC ACM | HIL ELF-прошивки используют отдельный канал (UART + bsp_uart_host) |
|
||||||
| Парсинг JSON — strstr без cJSON | Схема фиксирована, cJSON избыточен |
|
| Парсинг JSON — strstr без cJSON | Схема фиксирована, cJSON избыточен |
|
||||||
| SDRAM тест — через firmware_test, не HIL ELF | Тест идёт командами по USB CDC |
|
| SDRAM тест — через firmware_test, не HIL ELF | Тест идёт командами по USB CDC |
|
||||||
| IR и RTC — не реализуются | Вне scope рекламационной диагностики |
|
| IR и RTC — не реализуются | Вне scope рекламационной диагностики |
|
||||||
| Тесты атомарны | Инженер сам решает что проверять |
|
| Тесты атомарны | Инженер сам решает что проверять |
|
||||||
| Тест-логика на таргете | Хост — тонкий клиент, нет дублирования логики |
|
| Тест-логика на таргете | Хост — тонкий клиент, нет дублирования логики |
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@
|
||||||
#include "protocol.h"
|
#include "protocol.h"
|
||||||
#include "test_runner.h"
|
#include "test_runner.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/** @brief Строка версии прошивки, вставляемая в session_start. */
|
/** @brief Строка версии прошивки, вставляемая в session_start. */
|
||||||
#define FIRMWARE_TEST_VERSION "0.1.0"
|
#define FIRMWARE_TEST_VERSION "0.1.4"
|
||||||
|
|
||||||
/** @brief Таймаут подтверждения по умолчанию, мс. */
|
/** @brief Таймаут подтверждения по умолчанию, мс. */
|
||||||
#define PROTOCOL_CONFIRM_TIMEOUT_MS 30000U
|
#define PROTOCOL_CONFIRM_TIMEOUT_MS 30000U
|
||||||
|
|
|
||||||
|
|
@ -23,33 +23,19 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
/* ── Реестр тестов ─────────────────────────────────────────────────────────
|
/* ── Реестр тестов ─────────────────────────────────────────────────────────*/
|
||||||
*
|
|
||||||
* Добавление нового теста (Этап 2+):
|
|
||||||
* 1. Раскомментировать extern-объявление нужного модуля.
|
|
||||||
* 2. Добавить &k_test_<name> в k_registry[].
|
|
||||||
*
|
|
||||||
* 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;
|
|
||||||
*extern const test_module_t k_test_can;
|
|
||||||
*extern const test_module_t k_test_uart_ttl;
|
|
||||||
*extern const test_module_t k_test_uart_iso;
|
|
||||||
*extern const test_module_t k_test_opto;
|
|
||||||
* ─────────────────────────────────────────────────────────────────────────*/
|
|
||||||
|
|
||||||
#ifndef UNIT_TEST
|
#ifndef UNIT_TEST
|
||||||
extern const test_module_t K_TEST_SDRAM;
|
extern const test_module_t K_TEST_SDRAM;
|
||||||
extern const test_module_t K_TEST_QSPI;
|
extern const test_module_t K_TEST_QSPI;
|
||||||
extern const test_module_t K_TEST_USD;
|
extern const test_module_t K_TEST_USD;
|
||||||
|
extern const test_module_t K_TEST_DISPLAY;
|
||||||
|
|
||||||
static const test_module_t *const k_registry[] = {
|
static const test_module_t *const k_registry[] = {
|
||||||
/* populated starting from Этап 2 */
|
/* populated starting from Этап 2 */
|
||||||
&K_TEST_SDRAM,
|
&K_TEST_SDRAM,
|
||||||
&K_TEST_QSPI,
|
&K_TEST_QSPI,
|
||||||
&K_TEST_USD,
|
&K_TEST_USD,
|
||||||
|
&K_TEST_DISPLAY,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0]))
|
#define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0]))
|
||||||
|
|
|
||||||
311
firmware/test/src/tests/test_display.c
Normal file
311
firmware/test/src/tests/test_display.c
Normal file
|
|
@ -0,0 +1,311 @@
|
||||||
|
/**
|
||||||
|
* @file test_display.c
|
||||||
|
* @brief Тест-модуль firmware_test: TFT-дисплей (ELCDIF RGB888).
|
||||||
|
*
|
||||||
|
* Два этапа:
|
||||||
|
*
|
||||||
|
* Этап 1 — Цвет (все дисплеи, ~1 мин)
|
||||||
|
* 4 шага: Red → Green → Blue → White.
|
||||||
|
* Каждый шаг: заливка сплошным цветом → ожидание FRAME_DONE
|
||||||
|
* → confirm оператора (таймаут 15 с).
|
||||||
|
*
|
||||||
|
* Этап 2 — Ротация (только TFT7/TFT8/TFT10, ~30 с)
|
||||||
|
* Диагностирует непропаянные LR/UD пины.
|
||||||
|
* Паттерн: левая половина RED, правая BLUE.
|
||||||
|
* ROTATE_0 confirm → ROTATE_90 confirm → восстановить ROTATE_0.
|
||||||
|
*
|
||||||
|
* Тип дисплея определяется через DISPLAY_TEST_TYPE:
|
||||||
|
* — сейчас: жёсткий define (BSP_DISPLAY_TFT8) через CMake.
|
||||||
|
* — TODO: читать из Flash-конфига (Вариант C, см. PLAN.md Этап 5).
|
||||||
|
*
|
||||||
|
* Фреймбуфер — статический, в NonCacheable SDRAM (секция NonCacheable).
|
||||||
|
* Размер по максимальному дисплею: BSP_DISPLAY_MAX_HEIGHT × BSP_DISPLAY_MAX_WIDTH.
|
||||||
|
*
|
||||||
|
* Frame sync: volatile bool g_s_frame_done — устанавливается в ISR callback,
|
||||||
|
* сбрасывается перед каждым bsp_display_set_next_buffer().
|
||||||
|
* USB CDC поллится в цикле ожидания — стек остаётся живым.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bsp/display.h"
|
||||||
|
#include "bsp/usb_cdc.h"
|
||||||
|
#include "fsl_common.h"
|
||||||
|
#include "test_module.h"
|
||||||
|
#include "test_runner.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
/* ── Тип дисплея ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO (Вариант C): заменить на чтение из Flash-конфига платы.
|
||||||
|
* До реализации конфига тип задаётся через CMake:
|
||||||
|
* target_compile_definitions(firmware_test PRIVATE
|
||||||
|
* DISPLAY_TEST_TYPE=BSP_DISPLAY_TFT8)
|
||||||
|
*/
|
||||||
|
#ifndef DISPLAY_TEST_TYPE
|
||||||
|
#define DISPLAY_TEST_TYPE BSP_DISPLAY_TFT8
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ── Константы ───────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/** @brief XRGB8888 цвета для тестовых заливок. */
|
||||||
|
#define DISPLAY_COLOR_RED 0x00FF0000UL
|
||||||
|
#define DISPLAY_COLOR_GREEN 0x0000FF00UL
|
||||||
|
#define DISPLAY_COLOR_BLUE 0x000000FFUL
|
||||||
|
#define DISPLAY_COLOR_WHITE 0x00FFFFFFUL
|
||||||
|
|
||||||
|
/** @brief Таймаут confirm для каждого шага, мс. */
|
||||||
|
#define DISPLAY_CONFIRM_TIMEOUT_MS 15000U
|
||||||
|
|
||||||
|
/* ── Фреймбуфер в NonCacheable SDRAM ────────────────────────────────── */
|
||||||
|
|
||||||
|
/** @brief Один кадр максимального разрешения, XRGB8888. */
|
||||||
|
static AT_NONCACHEABLE_SECTION_ALIGN(
|
||||||
|
uint32_t g_s_framebuf[BSP_DISPLAY_MAX_HEIGHT * BSP_DISPLAY_MAX_WIDTH], 64U);
|
||||||
|
|
||||||
|
/* ── Состояние модуля ────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static bool g_s_ready;
|
||||||
|
static volatile bool g_s_frame_done;
|
||||||
|
|
||||||
|
/* ── Frame-done callback ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/** @brief Вызывается из LCDIF ISR. ISR-safe: только запись в volatile. */
|
||||||
|
static void display_frame_cb(void)
|
||||||
|
{
|
||||||
|
g_s_frame_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Вспомогательные функции ─────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Залить весь буфер одним цветом XRGB8888.
|
||||||
|
*
|
||||||
|
* Поллит USB CDC каждые 16 KB для поддержания USB-стека.
|
||||||
|
*/
|
||||||
|
static void fill_solid(uint32_t color)
|
||||||
|
{
|
||||||
|
const bsp_display_size_t *p_sz = bsp_display_get_size();
|
||||||
|
const uint32_t total = (uint32_t) p_sz->width * (uint32_t) p_sz->height;
|
||||||
|
|
||||||
|
for (uint32_t i = 0U; i < total; i++)
|
||||||
|
{
|
||||||
|
if ((i & 0x3FFFU) == 0U)
|
||||||
|
{
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
}
|
||||||
|
g_s_framebuf[i] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Залить левую половину color_l, правую — color_r.
|
||||||
|
*
|
||||||
|
* Используется в тесте ротации для детектирования непропаянных LR/UD пинов.
|
||||||
|
*/
|
||||||
|
static void fill_half(uint32_t color_l, uint32_t color_r)
|
||||||
|
{
|
||||||
|
const bsp_display_size_t *p_sz = bsp_display_get_size();
|
||||||
|
const uint16_t HALF = p_sz->width / 2U;
|
||||||
|
|
||||||
|
for (uint16_t row = 0U; row < p_sz->height; row++)
|
||||||
|
{
|
||||||
|
const uint32_t BASE = (uint32_t) row * (uint32_t) p_sz->width;
|
||||||
|
for (uint16_t col = 0U; col < p_sz->width; col++)
|
||||||
|
{
|
||||||
|
g_s_framebuf[BASE + col] = (col < HALF) ? color_l : color_r;
|
||||||
|
}
|
||||||
|
if ((row & 0x1FU) == 0U)
|
||||||
|
{
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Отправить буфер в ELCDIF и заблокироваться до завершения кадра.
|
||||||
|
*
|
||||||
|
* Поллит USB CDC в цикле ожидания.
|
||||||
|
*/
|
||||||
|
static void wait_frame(void)
|
||||||
|
{
|
||||||
|
g_s_frame_done = false;
|
||||||
|
bsp_display_set_next_buffer((uint32_t) g_s_framebuf);
|
||||||
|
while (!g_s_frame_done)
|
||||||
|
{
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Шаги теста ──────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Залить цвет, отобразить, запросить подтверждение оператора.
|
||||||
|
*
|
||||||
|
* @param color XRGB8888 цвет заливки.
|
||||||
|
* @param p_id ID confirm_request.
|
||||||
|
* @param p_prompt Инструкция оператору.
|
||||||
|
* @param p_out Заполняется при FAIL.
|
||||||
|
* @return true при подтверждении оператором.
|
||||||
|
*/
|
||||||
|
static bool step_color(uint32_t color, const char *p_id, const char *p_prompt, test_result_t *p_out)
|
||||||
|
{
|
||||||
|
fill_solid(color);
|
||||||
|
wait_frame();
|
||||||
|
|
||||||
|
const confirm_params_t params = {
|
||||||
|
.id = p_id,
|
||||||
|
.prompt = p_prompt,
|
||||||
|
.timeout_ms = DISPLAY_CONFIRM_TIMEOUT_MS,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!test_runner_wait_confirm(¶ms))
|
||||||
|
{
|
||||||
|
(void) snprintf(p_out->detail, TEST_DETAIL_SIZE, "%s not confirmed", p_id);
|
||||||
|
p_out->status = TEST_STATUS_FAIL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Применить ротацию, залить паттерн, запросить confirm.
|
||||||
|
*
|
||||||
|
* Вспомогательная функция для step_rotation() — держит её в лимите.
|
||||||
|
*
|
||||||
|
* @param rotation Ориентация для установки.
|
||||||
|
* @param p_id ID confirm_request.
|
||||||
|
* @param p_prompt Инструкция оператору.
|
||||||
|
* @param p_out Заполняется при FAIL.
|
||||||
|
* @return true при подтверждении.
|
||||||
|
*/
|
||||||
|
static bool step_rot_apply_and_confirm(bsp_display_rotation_t rotation, const char *p_id,
|
||||||
|
const char *p_prompt, test_result_t *p_out)
|
||||||
|
{
|
||||||
|
(void) bsp_display_set_rotation(rotation);
|
||||||
|
fill_half(DISPLAY_COLOR_RED, DISPLAY_COLOR_BLUE);
|
||||||
|
wait_frame();
|
||||||
|
|
||||||
|
const confirm_params_t params = {
|
||||||
|
.id = p_id,
|
||||||
|
.prompt = p_prompt,
|
||||||
|
.timeout_ms = DISPLAY_CONFIRM_TIMEOUT_MS,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!test_runner_wait_confirm(¶ms))
|
||||||
|
{
|
||||||
|
(void) snprintf(p_out->detail, TEST_DETAIL_SIZE, "%s not confirmed", p_id);
|
||||||
|
p_out->status = TEST_STATUS_FAIL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Этап 2: тест ротации — диагностика непропаянных LR/UD пинов.
|
||||||
|
*
|
||||||
|
* ROTATE_0: левая зона RED, правая BLUE → confirm.
|
||||||
|
* ROTATE_90: те же данные в буфере, аппаратный флип → confirm.
|
||||||
|
* Восстанавливает ROTATE_0 независимо от результата.
|
||||||
|
*/
|
||||||
|
static bool step_rotation(test_result_t *p_out)
|
||||||
|
{
|
||||||
|
bool ok = step_rot_apply_and_confirm(BSP_DISPLAY_ROTATE_0, "display_rot0",
|
||||||
|
"Screen: left RED, right BLUE?", p_out);
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
ok = step_rot_apply_and_confirm(BSP_DISPLAY_ROTATE_90, "display_rot90",
|
||||||
|
"Color zones changed orientation?", p_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Восстановить ROTATE_0 в любом исходе */
|
||||||
|
(void) bsp_display_set_rotation(BSP_DISPLAY_ROTATE_0);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Реализация тест-модуля ───────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static void display_test_init(void)
|
||||||
|
{
|
||||||
|
g_s_ready = false;
|
||||||
|
g_s_frame_done = false;
|
||||||
|
|
||||||
|
bsp_status_t status = bsp_display_init((bsp_display_type_t) DISPLAY_TEST_TYPE,
|
||||||
|
(uint32_t) g_s_framebuf, display_frame_cb);
|
||||||
|
if (status != BSP_OK)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Явная установка ROTATE_0 — детерминированное начальное состояние */
|
||||||
|
(void) bsp_display_set_rotation(BSP_DISPLAY_ROTATE_0);
|
||||||
|
g_s_ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static test_result_t display_test_run(void)
|
||||||
|
{
|
||||||
|
if (!g_s_ready)
|
||||||
|
{
|
||||||
|
test_result_t result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U };
|
||||||
|
(void) snprintf(result.detail, TEST_DETAIL_SIZE, "%s",
|
||||||
|
"display init failed — check DISPLAY_TEST_TYPE");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_result_t fail_result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U, .detail = { 0 } };
|
||||||
|
|
||||||
|
/* Этап 1: цвет */
|
||||||
|
if (!step_color(DISPLAY_COLOR_RED, "display_red", "Screen is solid red?", &fail_result))
|
||||||
|
{
|
||||||
|
return fail_result;
|
||||||
|
}
|
||||||
|
if (!step_color(DISPLAY_COLOR_GREEN, "display_green", "Screen is solid green?", &fail_result))
|
||||||
|
{
|
||||||
|
return fail_result;
|
||||||
|
}
|
||||||
|
if (!step_color(DISPLAY_COLOR_BLUE, "display_blue", "Screen is solid blue?", &fail_result))
|
||||||
|
{
|
||||||
|
return fail_result;
|
||||||
|
}
|
||||||
|
if (!step_color(DISPLAY_COLOR_WHITE, "display_white", "Screen is solid white?", &fail_result))
|
||||||
|
{
|
||||||
|
return fail_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Этап 2: ротация — только для дисплеев с ножками LR/UD */
|
||||||
|
if (bsp_display_get_type() != BSP_DISPLAY_TFT4)
|
||||||
|
{
|
||||||
|
if (!step_rotation(&fail_result))
|
||||||
|
{
|
||||||
|
return fail_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (test_result_t){
|
||||||
|
.status = TEST_STATUS_PASS,
|
||||||
|
.duration_ms = 0U,
|
||||||
|
.detail = { 0 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_test_deinit(void)
|
||||||
|
{
|
||||||
|
(void) bsp_display_deinit();
|
||||||
|
g_s_ready = false;
|
||||||
|
g_s_frame_done = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Дескриптор модуля ────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
const test_module_t K_TEST_DISPLAY = {
|
||||||
|
.id = "display",
|
||||||
|
.name = "TFT Display RGB888",
|
||||||
|
.critical = false,
|
||||||
|
.requires_hil = false,
|
||||||
|
.pre_confirm_prompt = NULL,
|
||||||
|
.init = display_test_init,
|
||||||
|
.run = display_test_run,
|
||||||
|
.deinit = display_test_deinit,
|
||||||
|
};
|
||||||
|
|
@ -2,84 +2,83 @@
|
||||||
|
|
||||||
## Обзор
|
## Обзор
|
||||||
|
|
||||||
В репозитории настроен рабочий GitHub Actions pipeline, который успешно запускается на событиях `push`, `pull_request` и при ручном запуске через `workflow_dispatch`.[cite:23][cite:99] Пайплайн специально привязан к реальному окружению разработки проекта: сборка и тесты выполняются внутри того же devcontainer-образа, который описан в `.devcontainer/Dockerfile`, а не в вручную собранной среде на `ubuntu-latest`.[cite:125]
|
В репозитории настроен рабочий GitHub Actions pipeline, который успешно запускается на событиях `push`, `pull_request` и при ручном запуске через `workflow_dispatch`. Пайплайн специально привязан к реальному окружению разработки проекта: сборка и тесты выполняются внутри того же devcontainer-образа, который описан в `.devcontainer/Dockerfile`, а не в вручную собранной среде на `ubuntu-latest`.
|
||||||
|
|
||||||
Такой подход уже устранил основные проблемы, которые проявились при первичном поднятии CI: отсутствие ARM toolchain, отсутствие `ninja`, установка неправильного `just` и несовместимость прав доступа при работе с bind-mounted workspace внутри Docker.[cite:125][cite:119][cite:127]
|
Такой подход уже устранил основные проблемы, которые проявились при первичном поднятии CI: отсутствие ARM toolchain, отсутствие `ninja`, установка неправильного `just` и несовместимость прав доступа при работе с bind-mounted workspace внутри Docker.
|
||||||
|
|
||||||
## Текущая архитектура
|
## Текущая архитектура
|
||||||
|
|
||||||
Workflow разделён на две job’ы: `build` и `test`.[cite:156] Такое разделение делает пайплайн проще для сопровождения, позволяет отдельно анализировать результаты стадии сборки и создаёт хороший фундамент для следующих этапов — `lint`, coverage, release packaging и аппаратных проверок.[cite:156][cite:147]
|
Workflow разделён на две job’ы: `build` и `test`.[cite:156] Такое разделение делает пайплайн проще для сопровождения, позволяет отдельно анализировать результаты стадии сборки и создаёт хороший фундамент для следующих этапов — `lint`, coverage, release packaging и аппаратных проверок.
|
||||||
|
|
||||||
Среда выполнения строится из Dockerfile devcontainer-а проекта, в котором уже определены ARM GCC toolchain в `/opt/arm-toolchain`, обновлённый `PATH`, а также установлены `cmake`, `ninja-build`, `clang-17`, `uv` и `just`.[cite:125] Поскольку все ключевые зависимости уже зафиксированы именно там, использование этого же образа в CI делает поведение раннера максимально близким к локальной разработке.[cite:125]
|
Среда выполнения строится из Dockerfile devcontainer-а проекта, в котором уже определены ARM GCC toolchain в `/opt/arm-toolchain`, обновлённый `PATH`, а также установлены `cmake`, `ninja-build`, `clang-17`, `uv` и `just`.[cite:125] Поскольку все ключевые зависимости уже зафиксированы именно там, использование этого же образа в CI делает поведение раннера максимально близким к локальной разработке.[cite:125]
|
||||||
|
|
||||||
## Как работает workflow
|
## Как работает workflow
|
||||||
|
|
||||||
Workflow реагирует на три типа событий: `push`, `pull_request` и `workflow_dispatch`.[cite:23][cite:99] Это даёт удобный баланс между автоматической проверкой обычных коммитов и возможностью вручную перезапускать pipeline для отладки инфраструктурных или нестабильных падений без обязательного нового изменения в коде.[cite:99][cite:106]
|
Workflow реагирует на три типа событий: `push`, `pull_request` и `workflow_dispatch`. Это даёт удобный баланс между автоматической проверкой обычных коммитов и возможностью вручную перезапускать pipeline для отладки инфраструктурных или нестабильных падений без обязательного нового изменения в коде.
|
||||||
|
Внутри job используется Docker Buildx и `docker/build-push-action`, а кэширование слоёв контейнера подключено через backend GitHub Actions cache с помощью `cache-from: type=gha` и `cache-to: type=gha`. За счёт этого повторные прогоны не пересобирают devcontainer с нуля, а переиспользуют уже собранные Docker-слои, что заметно ускоряет пайплайн после первого успешного заполнения кэша.
|
||||||
Внутри job используется Docker Buildx и `docker/build-push-action`, а кэширование слоёв контейнера подключено через backend GitHub Actions cache с помощью `cache-from: type=gha` и `cache-to: type=gha`.[cite:142][cite:143][cite:146] За счёт этого повторные прогоны не пересобирают devcontainer с нуля, а переиспользуют уже собранные Docker-слои, что заметно ускоряет пайплайн после первого успешного заполнения кэша.[cite:142][cite:143]
|
|
||||||
|
|
||||||
## Что делает job `build`
|
## Что делает job `build`
|
||||||
|
|
||||||
Job `build` выполняет checkout репозитория, инициализирует Buildx, собирает devcontainer image с поддержкой кэша, проверяет версии инструментов внутри контейнера, синхронизирует Python tooling в `tools/host` через `uv sync`, а затем запускает `just ci::build` внутри контейнера.[cite:142][cite:143][cite:125] После успешной сборки workflow выгружает директорию `build/` как GitHub artifact, чтобы результаты можно было сохранить и использовать на следующих стадиях.[cite:156][cite:153]
|
Job `build` выполняет checkout репозитория, инициализирует Buildx, собирает devcontainer image с поддержкой кэша, проверяет версии инструментов внутри контейнера, синхронизирует Python tooling в `tools/host` через `uv sync`, а затем запускает `just ci::build` внутри контейнера. После успешной сборки workflow выгружает директорию `build/` как GitHub artifact, чтобы результаты можно было сохранить и использовать на следующих стадиях.
|
||||||
|
|
||||||
Важная техническая деталь — команды внутри контейнера запускаются с `--user root`.[cite:127][cite:135] Это требуется из-за того, что `GITHUB_WORKSPACE` подключается в контейнер как bind mount, а в GitHub Actions non-root пользователь внутри Docker часто не получает права на запись в такую директорию; ранее это как раз ломало создание `.venv` во время `uv sync`.[cite:127][cite:129][cite:135]
|
Важная техническая деталь — команды внутри контейнера запускаются с `--user root`.Это требуется из-за того, что `GITHUB_WORKSPACE` подключается в контейнер как bind mount, а в GitHub Actions non-root пользователь внутри Docker часто не получает права на запись в такую директорию; ранее это как раз ломало создание `.venv` во время `uv sync`.
|
||||||
|
|
||||||
## Что делает job `test`
|
## Что делает job `test`
|
||||||
|
|
||||||
Job `test` зависит от `build`, скачивает artifact с директорией `build/`, заново поднимает тот же devcontainer image с использованием cached layers, синхронизирует `tools/host` и запускает `just ci::test` внутри контейнера.[cite:156][cite:142][cite:143] На практике это означает, что host unit-тесты работают в той же программной среде, что и стадия сборки, но при этом выделены в отдельный CI-этап.[cite:125][cite:156]
|
Job `test` зависит от `build`, скачивает artifact с директорией `build/`, заново поднимает тот же devcontainer image с использованием cached layers, синхронизирует `tools/host` и запускает `just ci::test` внутри контейнера. На практике это означает, что host unit-тесты работают в той же программной среде, что и стадия сборки, но при этом выделены в отдельный CI-этап.
|
||||||
|
|
||||||
Так как GitHub-hosted runner’ы эфемерны, сам Docker image не передаётся напрямую между job’ами.[cite:143] Поэтому обмен между `build` и `test` организован двумя способами: ускорение повторной сборки образа идёт через Docker layer cache, а результаты проекта передаются через GitHub artifacts.[cite:143][cite:153][cite:156]
|
Так как GitHub-hosted runner’ы эфемерны, сам Docker image не передаётся напрямую между job’ами.[cite:143] Поэтому обмен между `build` и `test` организован двумя способами: ускорение повторной сборки образа идёт через Docker layer cache, а результаты проекта передаются через GitHub artifacts.
|
||||||
|
|
||||||
## Почему эта схема хорошо подходит проекту
|
## Почему эта схема хорошо подходит проекту
|
||||||
|
|
||||||
Этот репозиторий нельзя считать обычным desktop C-проектом: он завязан на фиксированное расположение embedded toolchain и на специально подготовленный devcontainer.[cite:125] Ранние попытки выполнять pipeline прямо на runner’е падали, потому что проект ожидал наличие `/opt/arm-toolchain`, установленный `Ninja` и современный бинарник `just`, который понимает атрибуты вроде `[doc(...)]`.[cite:125][cite:97][cite:119]
|
Этот репозиторий нельзя считать обычным desktop C-проектом: он завязан на фиксированное расположение embedded toolchain и на специально подготовленный devcontainer.[cite:125] Ранние попытки выполнять pipeline прямо на runner’е падали, потому что проект ожидал наличие `/opt/arm-toolchain`, установленный `Ninja` и современный бинарник `just`, который понимает атрибуты вроде `[doc(...)]`.
|
||||||
|
|
||||||
Перенос CI внутрь devcontainer image устраняет этот класс расхождений и делает Dockerfile единым источником истины для окружения, версий и путей.[cite:125] Это упрощает дальнейшее сопровождение: при изменении инструментария достаточно обновить Dockerfile, и эти же изменения автоматически начнут действовать как локально, так и в CI.[cite:125]
|
Перенос CI внутрь devcontainer image устраняет этот класс расхождений и делает Dockerfile единым источником истины для окружения, версий и путей.[cite:125] Это упрощает дальнейшее сопровождение: при изменении инструментария достаточно обновить Dockerfile, и эти же изменения автоматически начнут действовать как локально, так и в CI.[cite:125]
|
||||||
|
|
||||||
## Чего workflow пока не делает
|
## Чего workflow пока не делает
|
||||||
|
|
||||||
Текущий pipeline пока не включает обязательную стадию `lint` и статический анализ, потому что в `just/ci.just` для `lint` пока ещё оставлена заглушка, а не полноценный вызов `clang-format` и `clang-tidy`.[cite:1] Он также пока не формирует release/HAB artifacts в CI, хотя в репозитории уже есть соответствующие рецепты `just ci::release` и связанные сборочные шаги.[cite:1]
|
Текущий pipeline пока не включает обязательную стадию `lint` и статический анализ, потому что в `just/ci.just` для `lint` пока ещё оставлена заглушка, а не полноценный вызов `clang-format` и `clang-tidy`. Он также пока не формирует release/HAB artifacts в CI, хотя в репозитории уже есть соответствующие рецепты `just ci::release` и связанные сборочные шаги.
|
||||||
|
|
||||||
Также pipeline пока не запускает HIL-сценарии.[cite:1] Это ожидаемо и правильно для текущего этапа: hardware-in-the-loop проверки требуют физического оборудования и в дальнейшем должны выполняться отдельно на self-hosted runner рядом с bench-стендом, а не на GitHub-hosted машинах.[cite:1]
|
Также pipeline пока не запускает HIL-сценарии. Это ожидаемо и правильно для текущего этапа: hardware-in-the-loop проверки требуют физического оборудования и в дальнейшем должны выполняться отдельно на self-hosted runner рядом с bench-стендом, а не на GitHub-hosted машинах.
|
||||||
|
|
||||||
## Сильные стороны текущего решения
|
## Сильные стороны текущего решения
|
||||||
|
|
||||||
У текущей реализации уже есть несколько сильных сторон:
|
У текущей реализации уже есть несколько сильных сторон:
|
||||||
|
|
||||||
- Она воспроизводима, потому что сборка и тесты выполняются в том же образе, что и локальная разработка.[cite:125]
|
- Она воспроизводима, потому что сборка и тесты выполняются в том же образе, что и локальная разработка.
|
||||||
- Она ускоряется на повторных прогонах за счёт Docker layer caching через GitHub Actions cache backend.[cite:142][cite:143]
|
- Она ускоряется на повторных прогонах за счёт Docker layer caching через GitHub Actions cache backend.
|
||||||
- Она модульна, потому что `build` и `test` вынесены в отдельные job’ы, связанные артефактами.[cite:156][cite:153]
|
- Она модульна, потому что `build` и `test` вынесены в отдельные job’ы, связанные артефактами.
|
||||||
- Она удобна для отладки, потому что build outputs сохраняются как artifacts, а workflow можно запускать вручную через `workflow_dispatch`.[cite:99][cite:156]
|
- Она удобна для отладки, потому что build outputs сохраняются как artifacts, а workflow можно запускать вручную через `workflow_dispatch`.
|
||||||
- Она хорошо вписана в структуру проекта, потому что использует уже существующие `just`-точки входа, а не дублирует build-логику в YAML.[cite:1]
|
- Она хорошо вписана в структуру проекта, потому что использует уже существующие `just`-точки входа, а не дублирует build-логику в YAML.
|
||||||
|
|
||||||
## Текущие ограничения
|
## Текущие ограничения
|
||||||
|
|
||||||
Главное ограничение сейчас состоит в том, что pipeline проверяет собираемость и host unit-тесты, но ещё не закрывает style gate, static analysis, coverage и release packaging.[cite:1] Второе ограничение — Docker image пересобирается в каждой job, поэтому даже при наличии кэша остаётся неизбежный накладной расход по времени по сравнению с вариантом, где используется заранее опубликованный образ из registry.[cite:143]
|
Главное ограничение сейчас состоит в том, что pipeline проверяет собираемость и host unit-тесты, но ещё не закрывает style gate, static analysis, coverage и release packaging. Второе ограничение — Docker image пересобирается в каждой job, поэтому даже при наличии кэша остаётся неизбежный накладной расход по времени по сравнению с вариантом, где используется заранее опубликованный образ из registry.[cite:143]
|
||||||
|
|
||||||
Есть и архитектурное ограничение GitHub-hosted runner’ов для аппаратной части.[cite:127][cite:1] Прошивка через USB, pyOCD-сценарии и управление стендом должны в будущем быть вынесены в отдельную hardware lane на self-hosted runner.[cite:1]
|
Есть и архитектурное ограничение GitHub-hosted runner’ов для аппаратной части.[cite:127] Прошивка через USB, pyOCD-сценарии и управление стендом должны в будущем быть вынесены в отдельную hardware lane на self-hosted runner.
|
||||||
|
|
||||||
## Рекомендуемые следующие шаги
|
## Рекомендуемые следующие шаги
|
||||||
|
|
||||||
### Шаг 1 — добавить `lint` job
|
### Шаг 1 — добавить `lint` job
|
||||||
|
|
||||||
Самое логичное следующее улучшение — реализовать полноценную стадию `lint` в `just/ci.just` и подключить отдельную job в workflow.[cite:1] В эту стадию стоит включить `clang-format --dry-run --Werror`, `clang-tidy` и необходимые исключения для generated-кода или vendor-зависимостей, чтобы избежать лишнего шума в CI.[cite:1]
|
Самое логичное следующее улучшение — реализовать полноценную стадию `lint` в `just/ci.just` и подключить отдельную job в workflow. В эту стадию стоит включить `clang-format --dry-run --Werror`, `clang-tidy` и необходимые исключения для generated-кода или vendor-зависимостей, чтобы избежать лишнего шума в CI.
|
||||||
|
|
||||||
### Шаг 2 — добавить coverage
|
### Шаг 2 — добавить coverage
|
||||||
|
|
||||||
После стабилизации `lint` полезно подключить экспорт coverage для host-тестов.[cite:1] В `ci.just` уже существует закрытый рецепт `_coverage`, и его можно развить до генерации XML-отчёта, выгрузки артефактов и последующей интеграции с внешним coverage-сервисом, если это будет нужно.[cite:1]
|
После стабилизации `lint` полезно подключить экспорт coverage для host-тестов. В `ci.just` уже существует закрытый рецепт `_coverage`, и его можно развить до генерации XML-отчёта, выгрузки артефактов и последующей интеграции с внешним coverage-сервисом, если это будет нужно.
|
||||||
|
|
||||||
### Шаг 3 — выделить release workflow
|
### Шаг 3 — выделить release workflow
|
||||||
|
|
||||||
Release packaging лучше оформлять отдельным workflow или отдельной gated job, запускаемой только по тегам, на `main` или вручную через `workflow_dispatch`.[cite:1] Это позволит не замедлять обычный PR-цикл, но при этом использовать `just ci::release` и публикацию HAB-артефактов тогда, когда это действительно нужно.[cite:1]
|
Release packaging лучше оформлять отдельным workflow или отдельной gated job, запускаемой только по тегам, на `main` или вручную через `workflow_dispatch`. Это позволит не замедлять обычный PR-цикл, но при этом использовать `just ci::release` и публикацию HAB-артефактов тогда, когда это действительно нужно.
|
||||||
|
|
||||||
### Шаг 4 — публиковать devcontainer image в GHCR
|
### Шаг 4 — публиковать devcontainer image в GHCR
|
||||||
|
|
||||||
Следующий сильный шаг по оптимизации — публиковать devcontainer image в GHCR и затем запускать CI уже на базе заранее собранного образа, а не пересобирать его в каждой job.[cite:142][cite:146] Это ещё сильнее сократит время старта pipeline и сделает масштабирование на `lint`, `coverage` и `release` заметно проще.[cite:142][cite:143]
|
Следующий сильный шаг по оптимизации — публиковать devcontainer image в GHCR и затем запускать CI уже на базе заранее собранного образа, а не пересобирать его в каждой job.Это ещё сильнее сократит время старта pipeline и сделает масштабирование на `lint`, `coverage` и `release` заметно проще.
|
||||||
|
|
||||||
### Шаг 5 — добавить self-hosted HIL lane
|
### Шаг 5 — добавить self-hosted HIL lane
|
||||||
|
|
||||||
Финальное крупное направление развития — выделенный аппаратный workflow на self-hosted runner с доступом к MCU-Link, target board и M5StampPLC.[cite:1] Такую lane лучше запускать вручную, по расписанию или по label-триггеру, а не делать обязательной для каждого PR, поскольку аппаратные проверки медленнее, менее стабильны и по природе отличаются от быстрых software regression checks.[cite:1]
|
Финальное крупное направление развития — выделенный аппаратный workflow на self-hosted runner с доступом к MCU-Link, target board и M5StampPLC. Такую lane лучше запускать вручную, по расписанию или по label-триггеру, а не делать обязательной для каждого PR, поскольку аппаратные проверки медленнее, менее стабильны и по природе отличаются от быстрых software regression checks.
|
||||||
|
|
||||||
## Целевое состояние
|
## Целевое состояние
|
||||||
|
|
||||||
Зрелая версия этого CI/CD контура, вероятно, будет состоять из четырёх независимых линий: быстрый PR-pipeline (`build`, `test`, `lint`), optional coverage reporting, отдельный release workflow и отдельный self-hosted HIL pipeline.[cite:1][cite:156] Такая структура сохранит короткий feedback loop для обычной разработки и одновременно покроет полный жизненный цикл embedded-проекта: от изменений в исходниках до production artifacts и аппаратной валидации на стенде.[cite:1]
|
Зрелая версия этого CI/CD контура, вероятно, будет состоять из четырёх независимых линий: быстрый PR-pipeline (`build`, `test`, `lint`), optional coverage reporting, отдельный release workflow и отдельный self-hosted HIL pipeline.[cite:156] Такая структура сохранит короткий feedback loop для обычной разработки и одновременно покроет полный жизненный цикл embedded-проекта: от изменений в исходниках до production artifacts и аппаратной валидации на стенде.
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ void test_confirm_request_default_timeout_on_zero(void)
|
||||||
protocol_send_confirm_request(¶ms);
|
protocol_send_confirm_request(¶ms);
|
||||||
|
|
||||||
/* timeout_ms == 0 → подставляется PROTOCOL_CONFIRM_TIMEOUT_MS */
|
/* timeout_ms == 0 → подставляется PROTOCOL_CONFIRM_TIMEOUT_MS */
|
||||||
TEST_ASSERT_NOT_NULL(strstr(s_captured, "\"timeout_ms\":15000"));
|
TEST_ASSERT_NOT_NULL(strstr(s_captured, "\"timeout_ms\":30000"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Тесты: pong ───────────────────────────────────────────────────────── */
|
/* ── Тесты: pong ───────────────────────────────────────────────────────── */
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,11 @@ tools/hil/
|
||||||
|
|
||||||
Полное описание стека HIL-тестирования — в `docs/testing/hil/`:
|
Полное описание стека HIL-тестирования — в `docs/testing/hil/`:
|
||||||
|
|
||||||
| Документ | Содержимое |
|
| Документ | Содержимое |
|
||||||
|----------|-----------|
|
| --------------------------------------------------------------- | ---------------------------------------------- |
|
||||||
| [HIL_HOWTO.md](../../docs/testing/hil/HIL_HOWTO.md) | Как запускать HIL-тесты (пошагово) |
|
| [HIL_HOWTO.md](../../docs/testing/hil/HIL_HOWTO.md) | Как запускать HIL-тесты (пошагово) |
|
||||||
| [HIL_BENCH.md](../../docs/testing/hil/HIL_BENCH.md) | Стенд: оборудование, подключение, маппинг реле |
|
| [HIL_BENCH.md](../../docs/testing/hil/HIL_BENCH.md) | Стенд: оборудование, подключение, маппинг реле |
|
||||||
| [HIL_CREATE_TEST.md](../../docs/testing/hil/HIL_CREATE_TEST.md) | Как добавить новый HIL-тест |
|
| [HIL_CREATE_TEST.md](../../docs/testing/hil/HIL_CREATE_TEST.md) | Как добавить новый HIL-тест |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Python-окружение на базе [uv](https://docs.astral.sh/uv/) для
|
||||||
|
|
||||||
## Структура
|
## Структура
|
||||||
|
|
||||||
```иbash
|
```bash
|
||||||
tools/host/
|
tools/host/
|
||||||
├── flash_usb.py — прошивка через USB ROM: sdphost → Flashloader → Flash
|
├── flash_usb.py — прошивка через USB ROM: sdphost → Flashloader → Flash
|
||||||
├── flash_swd.py — прошивка через SWD: FCB + HAB → pyOCD → Flash
|
├── flash_swd.py — прошивка через SWD: FCB + HAB → pyOCD → Flash
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
## Модули
|
## Модули
|
||||||
|
|
||||||
| Модуль | Путь | Описание |
|
| Модуль | Путь | Описание |
|
||||||
|--------|------|----------|
|
| ------------- | ------------------------------------- | -------------------------------------------------- |
|
||||||
| `ring_buffer` | [ring_buffer/](ring_buffer/README.md) | SPSC кольцевой буфер байт, lock-free |
|
| `ring_buffer` | [ring_buffer/](ring_buffer/README.md) | SPSC кольцевой буфер байт, lock-free |
|
||||||
| `log` | [log/](log/README.md) | Платформонезависимый логгер с callback-транспортом |
|
| `log` | [log/](log/README.md) | Платформонезависимый логгер с callback-транспортом |
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue