# test_display: bsp_display + test_template
This commit is contained in:
parent
da48fbf75d
commit
22f98c311a
37 changed files with 1282 additions and 458 deletions
12
README.md
12
README.md
|
|
@ -9,17 +9,17 @@
|
|||
## Три firmware-проекта
|
||||
|
||||
| Проект | Путь | Описание |
|
||||
|--------|------|----------|
|
||||
| ------------------- | ---------------------- | -------------------------------------------------------------------------------------- |
|
||||
| Тестовая прошивка | `firmware/test/` | Входной контроль платы: CAN, UART, SDRAM, QSPI, SDIO, RGB, оптовходы, LED, кнопки, MQS |
|
||||
| Загрузчик | `firmware/bootloader/` | A/B обновление через uSD. Сам обновляется только через USB ROM + blhost |
|
||||
| Боевая прошивка | `firmware/tft_app/` | FreeRTOS + FatFS + бизнес-логика. Обновляется загрузчиком |
|
||||
| Загрузчик | `firmware/bootloader/` | A/B обновление через uSD. Сам обновляется только через USB ROM + blhost / SWD |
|
||||
| Production прошивка | `firmware/tft_app/` | FreeRTOS + FatFS + бизнес-логика. Обновляется загрузчиком |
|
||||
|
||||
---
|
||||
|
||||
## BSP
|
||||
|
||||
| Модуль | Путь | Описание |
|
||||
|--------|------|----------|
|
||||
| --------------- | ---------------- | --------------------------------------------------------- |
|
||||
| `bsp_led` | `bsp/led/` | Два UserLed (GPIO3[3], GPIO3[4]) |
|
||||
| `bsp_tick` | `bsp/tick/` | SysTick / FreeRTOS-совместимый таймер |
|
||||
| `bsp_uart_host` | `bsp/uart_host/` | LPUART1 — MCU-Link VCOM (J2) |
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
## Тестирование
|
||||
|
||||
| Уровень | Где | Инструменты | Запуск |
|
||||
|---------|-----|-------------|--------|
|
||||
| ---------------- | ------------------------------ | -------------------------------------- | -------------------------------------- |
|
||||
| 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` (хост) |
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ just host::debug-server # GDB-сервер для отладки
|
|||
## Зависимости
|
||||
|
||||
| | Подход |
|
||||
|--|--------|
|
||||
| --------------------------------------------- | -------------------- |
|
||||
| NXP MCUXpresso SDK, FreeRTOS, FatFS, LittleFS | vendored |
|
||||
| Unity, fff, SEGGER RTT | vendored |
|
||||
| pyOCD, pyserial, pytest, mpremote | `tools/hil/uv.lock` |
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ add_subdirectory(uart_host)
|
|||
add_subdirectory(opto)
|
||||
add_subdirectory(can)
|
||||
add_subdirectory(button)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(usb_cdc)
|
||||
add_subdirectory(sdram)
|
||||
add_subdirectory(qspi_flash)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
## Аппаратура
|
||||
|
||||
| Кнопка | Пин MCU | GPIO | Схема | Нажатие |
|
||||
|----------------|--------------|-----------|--------------------------------|---------|
|
||||
| -------------- | ---------- | --------- | ----------------------------- | ------- |
|
||||
| `BSP_BUTTON_1` | GPIO_B1_14 | GPIO2[30] | SWT6x6, pull-up к 3V3 внешний | LOW |
|
||||
| `BSP_BUTTON_2` | GPIO_B1_15 | GPIO2[31] | SWT6x6, pull-up к 3V3 внешний | LOW |
|
||||
|
||||
|
|
@ -190,7 +190,7 @@ pytest: `tools/hil/04_test_button.py` — помечен `@pytest.mark.interacti
|
|||
Команды CLI прошивки:
|
||||
|
||||
| Команда | Ответ | Описание |
|
||||
|--------------|-------------|--------------------------------------------|
|
||||
| --------------- | --------- | ------------------------------------- |
|
||||
| `PING` | `PONG` | Проверка канала |
|
||||
| `READ <idx>` | `1` / `0` | Сырое состояние (без debounce) |
|
||||
| `STATE <idx>` | `1` / `0` | Стабильное состояние после debounce |
|
||||
|
|
@ -202,7 +202,7 @@ pytest: `tools/hil/04_test_button.py` — помечен `@pytest.mark.interacti
|
|||
## Зависимости
|
||||
|
||||
| Зависимость | Тип | Описание |
|
||||
|--------------|---------|----------------------------------------------|
|
||||
| ------------ | ------- | -------------------------------------------- |
|
||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||
| `bsp_board` | PRIVATE | Транзитивно: `pin_mux.h`, clock, SDK headers |
|
||||
| `sdk_gpio` | PRIVATE | `fsl_gpio.h` — `GPIO_ReadPinInput()` |
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
#include "bsp/status.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Типы
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
## Распределение Message Buffers
|
||||
|
||||
| MB | Назначение |
|
||||
|-------|-----------------------------------------------------|
|
||||
| ----- | -------------------------------------------------- |
|
||||
| 0 | Зарезервирован (ERR005829 workaround: inactive TX) |
|
||||
| 1 | TX — отправка фреймов |
|
||||
| 2..17 | RX — до 16 индивидуальных фильтров |
|
||||
|
|
@ -105,7 +105,7 @@ bsp_can_accept_all();
|
|||
Возвращается по одному из условий:
|
||||
|
||||
| Условие | Возврат |
|
||||
|---------|---------|
|
||||
| -------------------------------- | ----------------- |
|
||||
| Фрейм успешно отправлен | `BSP_OK` |
|
||||
| TX MB занят предыдущей передачей | `BSP_ERR_BUSY` |
|
||||
| Истёк `timeout_ms` | `BSP_ERR_TIMEOUT` |
|
||||
|
|
@ -120,7 +120,7 @@ Polling с таймаутом. Обходит все активные RX MB, ч
|
|||
во внутренний ring buffer, пытается извлечь один фрейм:
|
||||
|
||||
| Условие | Возврат |
|
||||
|---------|---------|
|
||||
| ------------------------------- | ----------------- |
|
||||
| Фрейм найден (из буфера или MB) | `BSP_OK` |
|
||||
| Истёк `timeout_ms` | `BSP_ERR_TIMEOUT` |
|
||||
| Невалидные параметры | `BSP_ERR_PARAM` |
|
||||
|
|
@ -162,7 +162,7 @@ bsp_can_register_rx_callback(can_isr_to_queue, NULL);
|
|||
Модуль не зависит от FreeRTOS и работает в обоих контекстах:
|
||||
|
||||
| Контекст | TX | RX |
|
||||
|----------|----|----|
|
||||
| --------------------------------- | ----------------------------------- | ---------------------------------------------- |
|
||||
| bare-metal (`firmware/test`, HIL) | `bsp_can_send()` — blocking polling | `bsp_can_receive()` — polling |
|
||||
| FreeRTOS (`firmware/tft_app`) | `bsp_can_send()` — из задачи | `bsp_can_receive()` — из задачи с `timeout_ms` |
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ CAN-адаптер на стороне хоста — M5Stack с CAN-модул
|
|||
## Зависимости
|
||||
|
||||
| Зависимость | Тип | Описание |
|
||||
|-------------|-----|----------|
|
||||
| -------------- | ------- | -------------------------------------- |
|
||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||
| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаутов |
|
||||
| `ring_buffer` | PRIVATE | Внутренний RX FIFO |
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ bsp/
|
|||
Внешний кварц **24 МГц**. Режим максимальной производительности (внешнее питание, энергопотребление не критично).
|
||||
|
||||
| Параметр | Значение |
|
||||
|---|---|
|
||||
| ------------------------ | ----------------------------------------- |
|
||||
| ARM PLL (PLL1) | 1200 МГц (VDIV = 50) |
|
||||
| CPU / AHB | 600 МГц (ARM_PODF /2, AHB_PODF /1) |
|
||||
| IPG | 150 МГц (IPG_PODF /4) |
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
## Аппаратная часть
|
||||
|
||||
| `led_id_t` | Сигнал | GPIO | Pin | Координата | Активный уровень |
|
||||
|-----------------|------------|-------|-----|------------|-----------------|
|
||||
| --------------- | ---------- | ----- | --- | ---------- | ---------------- |
|
||||
| `LED_HEARTBEAT` | `UserLed1` | GPIO3 | 3 | M4 | LOW (0 = горит) |
|
||||
| `LED_APP` | `UserLed2` | GPIO3 | 4 | P2 | LOW (0 = горит) |
|
||||
|
||||
|
|
|
|||
|
|
@ -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 и сразу проверю сборку, чтобы не оставить таких разрывов.
|
||||
|
|
@ -39,7 +39,7 @@ sdk_usdhc ← NXP HAL: fsl_usdhc
|
|||
## Аппаратный контекст
|
||||
|
||||
| Сигнал | Пин MCU | Конфигурация |
|
||||
|----------|-----------------|---------------------------------------------------|
|
||||
| ------ | ---------------- | ------------------------------------------------ |
|
||||
| CLK | GPIO_SD_B0_01 | USDHC1_CLK, периферийный режим |
|
||||
| CMD | GPIO_SD_B0_00 | USDHC1_CMD, периферийный режим |
|
||||
| D0–D3 | GPIO_SD_B0_02–05 | USDHC1_DATA0–3, периферийный режим |
|
||||
|
|
@ -93,7 +93,7 @@ host-контроллер (`SD_HostInit`).
|
|||
## Разделение ответственности: bsp_sd vs sdmmc_config vs port_fatfs_sd
|
||||
|
||||
| Слой | Что делает | Где живёт |
|
||||
|-----------------------|---------------------------------------------------------|------------------------------------|
|
||||
| --------------------- | ------------------------------------------------------- | ----------------------------------- |
|
||||
| `sdmmc_config` | Константы платы, `BOARD_SD_Config`, GPIO питания, pads | `bsp/generated/` |
|
||||
| `bsp_sd` | `SD_HostInit/Deinit`, идемпотентность, card detect | `bsp/sd/` |
|
||||
| `port_fatfs_sd` | `microsd_disk_*` → `fsl_sd_disk` (FatFS diskio glue) | `port/fatfs/sd/` |
|
||||
|
|
|
|||
|
|
@ -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 <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Размер расширенного тестового региона, байт (27 MB).
|
||||
*
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ target_compile_definitions(firmware_test PRIVATE
|
|||
```
|
||||
|
||||
| Define | Дефолт | Описание |
|
||||
|--------|--------|----------|
|
||||
| ------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| `BSP_UART_HOST_RX_BUFFER_SIZE` | `256` | Размер RX ring buffer. **Должен быть степенью двойки.** |
|
||||
| `BSP_UART_HOST_SRC_CLOCK_HZ` | `24000000` | Частота источника тактирования LPUART1. |
|
||||
| `BSP_UART_HOST_IRQ_PRIORITY` | `5` | Приоритет `LPUART1_IRQn`. Должен быть ≥ `configMAX_SYSCALL_INTERRUPT_PRIORITY` при использовании FreeRTOS. |
|
||||
|
|
@ -157,7 +157,7 @@ void test_something(void) {
|
|||
## Зависимости
|
||||
|
||||
| Зависимость | Тип | Описание |
|
||||
|-------------|-----|----------|
|
||||
| --------------------- | ------- | --------------------------------- |
|
||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||
| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаутов |
|
||||
| `utils` (ring_buffer) | PRIVATE | RX ring buffer |
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ CLI команды, обновление конфигурации. Работа
|
|||
## Аппаратура
|
||||
|
||||
| Сигнал | Пин MCU | Назначение |
|
||||
|--------------|----------------|----------------------------------|
|
||||
| ------------- | ------------- | -------------------------- |
|
||||
| USB_OTG1_DN | USB_OTG1_DN | USB1 Data− |
|
||||
| USB_OTG1_DP | USB_OTG1_DP | USB1 Data+ |
|
||||
| USB_OTG1_VBUS | USB_OTG1_VBUS | VBUS detect (self-powered) |
|
||||
|
|
@ -61,7 +61,7 @@ Host → USB1_DP/DN → [EHCI0 DMA]
|
|||
Это сознательное решение:
|
||||
|
||||
| Аспект | Full stack | Lite stack (наш выбор) |
|
||||
|--------|-----------|----------------------|
|
||||
| ------------------ | -------------------------------------- | ----------------------------- |
|
||||
| Class framework | `usb_device_class.h`, `class_handle_t` | Отсутствует |
|
||||
| `usb_device_ch9.c` | SDK middleware, тянет class driver | Приватная копия в `src/` |
|
||||
| CDC ACM хедер | Полный: struct + API функции | Только define-ы request codes |
|
||||
|
|
@ -170,7 +170,7 @@ NVIC enable → USB_DeviceRun. Включает задержку 5 мс для
|
|||
USB IN transfer. Максимум `BSP_USB_CDC_MAX_PACKET_SIZE` (512) байт за вызов.
|
||||
|
||||
| Возврат | Условие |
|
||||
|---------|---------|
|
||||
| ------------------- | ------------------------------------------ |
|
||||
| `BSP_OK` | Transfer поставлен в очередь |
|
||||
| `BSP_ERR_BUSY` | Предыдущий transfer не завершён |
|
||||
| `BSP_ERR_NOT_READY` | Хост не подключён |
|
||||
|
|
@ -251,7 +251,7 @@ USB_OTG1_IRQHandler (usb_cdc_hw.c)
|
|||
Модуль работает без изменений в контексте FreeRTOS-задачи:
|
||||
|
||||
| Контекст | TX | RX |
|
||||
|----------|----|----|
|
||||
| ---------- | ------------------------------------------- | ------------------------------ |
|
||||
| bare-metal | `bsp_usb_cdc_write()` — non-blocking | `bsp_usb_cdc_read()` — polling |
|
||||
| FreeRTOS | Из задачи, `write_ready()` + `vTaskDelay()` | Из задачи с yield |
|
||||
|
||||
|
|
@ -296,7 +296,7 @@ just host::hil-usb-cdc
|
|||
Команды CLI прошивки:
|
||||
|
||||
| Команда | Ответ | Описание |
|
||||
|---------|-------|----------|
|
||||
| ------------- | -------- | ---------------- |
|
||||
| `PING` | `PONG` | Проверка канала |
|
||||
| `ECHO <data>` | `<data>` | Echo-back данных |
|
||||
|
||||
|
|
@ -312,7 +312,7 @@ just host::hil-usb-cdc
|
|||
Все настройки находятся в приватных хедерах `src/`:
|
||||
|
||||
| Файл | Настройка | Значение | Описание |
|
||||
|------|-----------|----------|----------|
|
||||
| ------------------------- | ------------------------------- | -------- | -------------------------------- |
|
||||
| `usb_device_config.h` | `USB_DEVICE_CONFIG_EHCI` | `1` | Контроллер EHCI0 |
|
||||
| `usb_device_config.h` | `USB_DEVICE_CONFIG_ENDPOINTS` | `4` | EP0 + interrupt IN + bulk IN/OUT |
|
||||
| `usb_device_config.h` | `USB_DEVICE_CONFIG_SELF_POWER` | `1` | Self-powered device |
|
||||
|
|
@ -349,7 +349,7 @@ bsp/usb_cdc/
|
|||
## Зависимости
|
||||
|
||||
| Зависимость | Тип | Описание |
|
||||
|-------------|-----|----------|
|
||||
| --------------------- | --------------------- | ------------------------------------------------------- |
|
||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||
| `bsp_board` | PRIVATE | Транзитивно: `clock_config.h`, `pin_mux.h`, SDK headers |
|
||||
| `sdk_usb_device_ehci` | PRIVATE | EHCI контроллер + DCI абстракция |
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ ENTRY(Reset_Handler)
|
|||
HEAP_SIZE = DEFINED(__heap_size__) ? __heap_size__ : 0x0400;
|
||||
STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x0400;
|
||||
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;
|
||||
|
||||
/* Specify the memory areas */
|
||||
|
|
@ -40,8 +40,8 @@ MEMORY
|
|||
m_interrupts (RX) : ORIGIN = 0x60002000, LENGTH = 0x00000400
|
||||
m_text (RX) : ORIGIN = 0x60002400, LENGTH = 0x03FFDC00
|
||||
m_qacode (RX) : ORIGIN = 0x00000000, LENGTH = 0x00020000
|
||||
m_data (RW) : ORIGIN = 0x80000000, LENGTH = DEFINED(__heap_noncacheable__) ? 0x01E00000 : 0x01E00000 - HEAP_SIZE
|
||||
m_ncache (RW) : ORIGIN = 0x81E00000, LENGTH = DEFINED(__heap_noncacheable__) ? 0x00200000 - HEAP_SIZE : 0x00200000
|
||||
m_data (RW) : ORIGIN = 0x80000000, LENGTH = 0x01600000 /* 22 MB — хватает для всего */
|
||||
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_data3 (RW) : ORIGIN = 0x20200000, LENGTH = 0x00040000
|
||||
m_heap (RW) : ORIGIN = NCACHE_HEAP_START, LENGTH = HEAP_SIZE
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ HIL-тесты через pyOCD + pytest, GDB-сервер для отладки
|
|||
## 3. Что устанавливается и где
|
||||
|
||||
| Инструмент | Хост | Devcontainer |
|
||||
|------------|------|--------------|
|
||||
| ----------------------------------- | --------------- | --------------- |
|
||||
| `just` | ✅ | ✅ Dockerfile |
|
||||
| `docker` | ✅ | — |
|
||||
| `uv` | ✅ | ✅ Dockerfile |
|
||||
|
|
@ -227,7 +227,7 @@ HIL_USB_CDC_TIMEOUT=5.0
|
|||
### 5.1 Предварительные требования
|
||||
|
||||
| Платформа | Что нужно до bootstrap |
|
||||
|-----------|------------------------|
|
||||
| --------- | ---------------------------------------------- |
|
||||
| Linux | `docker`, `git`, `curl` |
|
||||
| macOS | Docker Desktop, `git` (Xcode CLT) |
|
||||
| Windows | Docker Desktop, Git for Windows → **Git Bash** |
|
||||
|
|
@ -280,7 +280,7 @@ just host::m5-deploy
|
|||
### 6.1 Четыре типа сборки
|
||||
|
||||
| Пресет | Toolchain | Назначение | Линкер-скрипт |
|
||||
|--------|-----------|------------|---------------|
|
||||
| ----------------------------- | ------------ | ---------------------------------- | ---------------- |
|
||||
| `Debug` / `Release` | ARM GCC | firmware_test, bootloader, tft_app | `flexspi_nor.ld` |
|
||||
| `host-debug` / `host-release` | clang (хост) | Unity + fff тесты | — |
|
||||
| `target-debug` | ARM GCC | HIL target-прошивки | `ram.ld` |
|
||||
|
|
@ -311,7 +311,7 @@ buildPresets (HIL):
|
|||
### 6.3 Boot-стратегии
|
||||
|
||||
| Прошивка | Стратегия | Инструмент загрузки |
|
||||
|----------|-----------|---------------------|
|
||||
| ---------------------------- | ---------------------------------- | ------------------- |
|
||||
| `firmware_test` | XIP из Flash (`flexspi_nor.ld`) | SPSDK → Flash |
|
||||
| `bootloader` | Копирование в ITCM | SPSDK → Flash |
|
||||
| `tft_app` | XIP + буферы в SDRAM | SPSDK → Flash |
|
||||
|
|
@ -326,7 +326,7 @@ buildPresets (HIL):
|
|||
### 7.1 Карта задач по контекстам
|
||||
|
||||
| Задача | Где |
|
||||
|--------|-----|
|
||||
| -------------------------------------- | -------------------------- |
|
||||
| Написание кода, clangd, форматирование | devcontainer |
|
||||
| Статический анализ (clang-tidy) | devcontainer |
|
||||
| Host unit-тесты (Unity + fff) | devcontainer |
|
||||
|
|
@ -370,7 +370,7 @@ just host::debug-server # запустить pyOCD GDB-серв
|
|||
### 7.3 VSCode Tasks (внутри devcontainer)
|
||||
|
||||
| Таск | Команда |
|
||||
|------|---------|
|
||||
| ------------------------- | ------------------------------------ |
|
||||
| 🔨 Build | `just build::build-<project>-<type>` |
|
||||
| 🧪 Host Tests (Debug) | `just build::test-host` |
|
||||
| 🧪 Host Tests (Release) | `just build::test-host-release` |
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
Отладка построена на проброске GDB-сервера с хоста в devcontainer по TCP. Это позволяет держать весь инструментарий сборки и языковой сервер внутри контейнера, не проводя USB-пробник внутрь Docker.
|
||||
|
||||
```
|
||||
```bash
|
||||
┌─────────────────────────────────────┐ ┌──────────────────────────────────┐
|
||||
│ Хост (macOS/Linux) │ │ DevContainer │
|
||||
│ │ │ │
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
### На хосте
|
||||
|
||||
| Компонент | Роль | Источник |
|
||||
|---|---|---|
|
||||
| --------------------------------- | ------------------------------- | -------------------------- |
|
||||
| `pyocd` | GDB-сервер + flash-программатор | `tools/hil/uv.lock` |
|
||||
| `MCU-Link` | CMSIS-DAP v2 пробник | USB к плате |
|
||||
| `just host::debug-server` | Запуск GDB-сервера | `just/host.just` |
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
### В devcontainer
|
||||
|
||||
| Компонент | Роль |
|
||||
|---|---|
|
||||
| -------------------------------------- | ---------------------------------------------- |
|
||||
| `arm-none-eabi-gdb` | GDB клиент, подключается к серверу на хосте |
|
||||
| `cortex-debug` (VSCode extension) | UI для GDB: брейкпоинты, стек, регистры |
|
||||
| `.vscode/launch.json` | Конфигурации запуска отладки |
|
||||
|
|
@ -63,7 +63,7 @@ FCB_PATH=tools/host/dcd/w25q128_fdcb.bin
|
|||
## Прошивки, поддерживаемые отладкой
|
||||
|
||||
| Конфигурация VSCode | ELF | Особенности |
|
||||
|---|---|---|
|
||||
| ----------------------------- | ------------------------------- | ---------------------------- |
|
||||
| `🐛 Debug: firmware_test` | `build/Debug/firmware_test.elf` | Bare-metal, входной контроль |
|
||||
| `🐛 Debug: bootloader` | `build/Debug/bootloader.elf` | Bare-metal, A/B обновление |
|
||||
| `🐛 Debug: tft_app (FreeRTOS)` | `build/Debug/app.elf` | FreeRTOS, task view |
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Поддерживаются два независимых способа прошивки:
|
||||
|
||||
| Способ | Интерфейс | Требование | Инструмент |
|
||||
|---|---|---|---|
|
||||
| ----------- | -------------------- | ---------------------------------- | -------------------------- |
|
||||
| **USB SDP** | USB ↔ ROM-загрузчик | BOOT_MODE = 01 (Serial Downloader) | `spsdk` (sdphost + blhost) |
|
||||
| **SWD** | MCU-Link ↔ CMSIS-DAP | Плата в любом режиме загрузки | `pyocd` |
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ just host::flash-swd-app-release
|
|||
### 2.4 Зависимости
|
||||
|
||||
| Файл | Назначение |
|
||||
|---|---|
|
||||
| --------------------------------- | ----------------------------------- |
|
||||
| `tools/host/flash_swd.py` | Скрипт сборки образа и вызова pyOCD |
|
||||
| `tools/host/dcd/w25q128_fdcb.bin` | FCB для W25Q128 в режиме Quad SPI |
|
||||
| `tools/hil/` (uv-проект) | pyocd, вызывается через `uv run` |
|
||||
|
|
@ -141,7 +141,7 @@ FCB-бинарник (`w25q128_fdcb.bin`) генерируется в NXP Secure
|
|||
## Сравнение способов
|
||||
|
||||
| | USB SDP | SWD |
|
||||
|---|---|---|
|
||||
| ------------------------- | -------------------------------- | -------------------- |
|
||||
| Переключение BOOT_MODE | Нужно | Не нужно |
|
||||
| Power cycle после записи | Не нужен | **Обязателен** |
|
||||
| FCB в образе | Не нужен (Flashloader пишет сам) | **Обязателен** |
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
## Оборудование
|
||||
|
||||
| Устройство | Роль |
|
||||
|-------------------|-------------------------------------------------------------|
|
||||
| -------------------- | ---------------------------------------------------------- |
|
||||
| MIMXRT1052CVJ5B | Таргет — плата под тестом |
|
||||
| M5Stack StamPLC | Промежуточная платформа стенда: питание таргета + сигналы |
|
||||
| MCU-Link (CMSIS-DAP) | SWD-probe + VCOM (pyOCD загружает ELF, pytest читает UART) |
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
### Питание таргета
|
||||
|
||||
| M5 реле | Куда | Назначение |
|
||||
|---------|------------------|---------------------|
|
||||
| ------- | ----------- | ------------------- |
|
||||
| RLY1 | VIN таргета | Управление питанием |
|
||||
|
||||
Питание включается и выключается автоматически фикстурой `m5` в `conftest.py`:
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
M5: реле AW9523B через ULN2003A, нормально разомкнутые (NO).
|
||||
|
||||
| M5 реле | Сигнал таргета | BSP канал | MCU пин | GPIO |
|
||||
|---------|----------------|--------------------|---------------|-----------|
|
||||
| ------- | -------------- | ----------------- | ------------- | --------- |
|
||||
| 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] |
|
||||
| RLY4 | EXT_IN2 | `BSP_OPTO_CH_IN2` | GPIO_AD_B1_05 | GPIO1[21] |
|
||||
|
|
@ -58,7 +58,7 @@ _OPTO_TO_RELAY = {1: 3, 2: 4, 3: 2}
|
|||
### Порты и переменные окружения
|
||||
|
||||
| Переменная | Значение по умолчанию | Назначение |
|
||||
|----------------------|-----------------------|---------------------------|
|
||||
| --------------- | --------------------- | ------------------------ |
|
||||
| `HIL_VCOM_PORT` | `/dev/ttyACM0` | MCU-Link VCOM (UART CLI) |
|
||||
| `HIL_VCOM_BAUD` | `115200` | Скорость UART CLI |
|
||||
| `HIL_M5_PORT` | `/dev/ttyACM1` | M5StampPLC USB CDC |
|
||||
|
|
@ -74,7 +74,7 @@ _OPTO_TO_RELAY = {1: 3, 2: 4, 3: 2}
|
|||
### M5Stack StamPLC — `tools/hil/m5/`
|
||||
|
||||
| Файл | Назначение |
|
||||
|--------------|----------------------------------------------------------------|
|
||||
| ---------- | --------------------------------------------------------------------------------------- |
|
||||
| `agent.py` | MicroPython агент на M5. Принимает JSON-команды через USB CDC, подает сигналы на таргет |
|
||||
| `cli.py` | Интерактивный CLI для ручного тестирования агента |
|
||||
| `power.py` | Скрипт управления питанием таргета (RLY1) из командной строки |
|
||||
|
|
@ -89,7 +89,7 @@ M5 → хост: {"ok": true, "opto_ch": 1, "relay": 3, "state": true}\r\n
|
|||
**Доступные команды агента:**
|
||||
|
||||
| Команда | Параметры | Действие |
|
||||
|----------------|------------------------------------|---------------------------------------------------|
|
||||
| --------------- | ------------------------------------------- | ---------------------------------------------- |
|
||||
| `ping` | — | Проверка связи |
|
||||
| `info` | — | Версия, состояние CAN и AW9523, статус реле |
|
||||
| `power` | `state: bool` | RLY1 — питание таргета |
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ just/build.just
|
|||
Три типа тестов:
|
||||
|
||||
| Тип | Использует M5 | Запуск | Когда применять |
|
||||
|-----|--------------|--------|-----------------|
|
||||
| ----------------- | ------------- | --------------------- | ----------------------------------------------------- |
|
||||
| **Базовый** | Нет | `hil-run` | Тестирование UART CLI, алгоритмов, таймингов |
|
||||
| **С M5** | Да | `hil-run` | Тестирование GPIO, оптовходов, реле, питания |
|
||||
| **Интерактивный** | Нет / Да | `hil-run-interactive` | Периферия требует действий оператора: кнопки, дисплей |
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ def uart(request):
|
|||
## 2. `yield` vs `return`
|
||||
|
||||
| Конструкция | Setup | Teardown | Когда использовать |
|
||||
|-------------|-------|----------|--------------------|
|
||||
| ----------- | ----- | -------- | --------------------------------------------- |
|
||||
| `return` | ✅ | ❌ | Ресурс не требует очистки (загрузка ELF) |
|
||||
| `yield` | ✅ | ✅ | Ресурс нужно освободить (порт, реле, питание) |
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ def m5(request):
|
|||
Scope определяет **как долго живёт** экземпляр фикстуры.
|
||||
|
||||
| Scope | Создаётся | Уничтожается | Типичное применение в HIL |
|
||||
|-------|-----------|--------------|---------------------------|
|
||||
| ---------- | ------------------------------ | ----------------------------- | ---------------------------------- |
|
||||
| `function` | Перед каждым `test_*()` | После каждого `test_*()` | Сброс состояния стенда |
|
||||
| `class` | Перед первым тестом класса | После последнего теста класса | Группа связанных тестов |
|
||||
| `module` | Перед первым тестом файла | После последнего теста файла | **Загрузка ELF, открытие UART** |
|
||||
|
|
@ -203,7 +203,7 @@ tools/hil/
|
|||
### Что живёт в `conftest.py` проекта
|
||||
|
||||
| Фикстура / функция | Scope | Назначение |
|
||||
|---------------------|-------|------------|
|
||||
| -------------------------- | -------------------- | ------------------------------------------------------------ |
|
||||
| `_load_elf()` | вспомогательная | pyOCD: halt → FLEXRAM → load ELF → run |
|
||||
| `_uart_context()` | контекстный менеджер | Открыть VCOM, дождаться `READY\r\n`, гарантировать `close()` |
|
||||
| `_make_uart_fixture()` | фабрика | Генерирует `uart_*` фикстуры из `_UART_FIXTURE_MAP` |
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ CMakePresets.json just/build.just
|
|||
Перед написанием кода определи к какой категории относится модуль:
|
||||
|
||||
| Категория | Описание | Инструментарий |
|
||||
|-----------|----------|----------------|
|
||||
| --------- | -------------------------------------------------------------- | ------------------------- |
|
||||
| **A** | Нет вызовов NXP SDK: алгоритмы, парсеры, FSM, структуры данных | Unity |
|
||||
| **B** | BSP-модуль вызывает `fsl_*.h`, USB-стек и т.д. | Unity + fff + stub-хедеры |
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ status_t SDK_Function_B(DRIVER_Type *base, const uint8_t *data, size_t len);
|
|||
### Уже существующие stubs в `tests/host/mocks/`
|
||||
|
||||
| Файл | Что заменяет | Используется в |
|
||||
|------|-------------|----------------|
|
||||
| ------------ | ----------------------------- | -------------- |
|
||||
| `fsl_gpio.h` | GPIO драйвер | `test_bsp_led` |
|
||||
| `pin_mux.h` | Макросы пинов из `generated/` | `test_bsp_led` |
|
||||
| `board.h` | `board_hw_init()` | `test_bsp_led` |
|
||||
|
|
@ -185,7 +185,7 @@ add_host_test(
|
|||
### Аргументы `add_host_test()`
|
||||
|
||||
| Аргумент | Обязателен | Описание |
|
||||
|----------|-----------|----------|
|
||||
| ---------- | ---------- | --------------------------------------------------------------- |
|
||||
| `NAME` | ✓ | Имя исполняемого файла и теста в CTest |
|
||||
| `SOURCES` | ✓ | Тестовый `.c` + исходники тестируемых модулей |
|
||||
| `INCLUDES` | — | Дополнительные include-пути (для `#include "bsp/led.h"` и т.д.) |
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ add_executable(
|
|||
src/tests/test_sdram.c
|
||||
src/tests/test_qspi.c
|
||||
src/tests/test_usd.c
|
||||
src/tests/test_display.c
|
||||
${BSP_GENERATED}/clock_config.c
|
||||
${BSP_STARTUP_FILE}
|
||||
${BSP_SYSCALLS_FILE})
|
||||
|
|
@ -26,8 +27,8 @@ target_include_directories(firmware_test PRIVATE src/)
|
|||
target_compile_definitions(
|
||||
${TARGET_NAME}
|
||||
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512 BOARD_MPU_SDRAM=1
|
||||
__STARTUP_INITIALIZE_RAMFUNCTION __STARTUP_CLEAR_BSS
|
||||
__STARTUP_INITIALIZE_NONCACHEDATA)
|
||||
DISPLAY_TEST_TYPE=BSP_DISPLAY_TFT8 __STARTUP_INITIALIZE_RAMFUNCTION
|
||||
__STARTUP_CLEAR_BSS __STARTUP_INITIALIZE_NONCACHEDATA)
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
# Зависимости — только то что нужно для входного контроля bsp_board транзитивно
|
||||
|
|
@ -37,6 +38,7 @@ target_link_libraries(
|
|||
${TARGET_NAME}
|
||||
PRIVATE bsp_board
|
||||
bsp_led
|
||||
bsp_display
|
||||
bsp_tick
|
||||
bsp_boot_xip
|
||||
bsp_usb_cdc
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ main.c
|
|||
**BSP-зависимости тест-модулей:**
|
||||
|
||||
| Тест | BSP модуль |
|
||||
|---|---|
|
||||
| --------------- | ------------------------------ |
|
||||
| `test_sdram` | `bsp_sdram` |
|
||||
| `test_qspi` | `bsp_qspi` |
|
||||
| `test_usd` | `bsp_usd` |
|
||||
|
|
@ -254,7 +254,7 @@ main.c
|
|||
### Транспорт
|
||||
|
||||
| Параметр | Значение |
|
||||
|---|---|
|
||||
| ------------------------- | ---------------------------------------------------------- |
|
||||
| Интерфейс | USB CDC ACM, разъём J2 |
|
||||
| Кодировка | UTF-8 |
|
||||
| Фреймирование | JSON-lines: одна строка = одно сообщение, завершается `\n` |
|
||||
|
|
@ -379,7 +379,7 @@ main.c
|
|||
```
|
||||
|
||||
| `status` | Смысл |
|
||||
|---|---|
|
||||
| -------- | ------------------------------------------------------------------------ |
|
||||
| `"pass"` | Тест пройден |
|
||||
| `"fail"` | Тест провален; `detail` содержит описание (до 95 символов) |
|
||||
| `"skip"` | Пропущен: нет оборудования, таймаут, отказ оператора, critical fail выше |
|
||||
|
|
@ -483,7 +483,7 @@ main.c
|
|||
## Матрица тестов
|
||||
|
||||
| ID | Название | Тип | Critical | M5 HIL | Confirm |
|
||||
|---|---|---|---|---|---|
|
||||
| ---------- | ---------------- | ---------------- | -------- | ------ | ------------- |
|
||||
| `sdram` | SDRAM 32 MB | self | ✅ | ❌ | ❌ |
|
||||
| `qspi` | QSPI Flash 8 MB | self | ✅ | ❌ | ❌ |
|
||||
| `usd` | uSD SDIO | self+interactive | ❌ | ❌ | ✅ pre_confirm |
|
||||
|
|
@ -669,7 +669,7 @@ const test_module_t k_test_foo = {
|
|||
### Что покрыто
|
||||
|
||||
| Таргет | Что тестирует | Тест-файл |
|
||||
|---|---|---|
|
||||
| ---------------------- | ------------------------------------------------ | ------------------------------------------ |
|
||||
| `test_protocol` | сериализация JSON (все event types) | `tests/host/protocol/test_protocol.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` |
|
||||
|
|
@ -720,7 +720,7 @@ void test_run_all_critical_fail_skips_remaining(void)
|
|||
Стандартный список моков для каждого теста:
|
||||
|
||||
| Зависимость | fff 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_usb_cdc_poll()` | `FAKE_VOID_FUNC(bsp_usb_cdc_poll)` |
|
||||
|
|
@ -751,7 +751,7 @@ void test_run_all_critical_fail_skips_remaining(void)
|
|||
> Не пересматривать без явного запроса.
|
||||
|
||||
| Решение | Обоснование |
|
||||
|---|---|
|
||||
| -------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| Единственный транспорт — USB CDC ACM | HIL ELF-прошивки используют отдельный канал (UART + bsp_uart_host) |
|
||||
| Парсинг JSON — strstr без cJSON | Схема фиксирована, cJSON избыточен |
|
||||
| SDRAM тест — через firmware_test, не HIL ELF | Тест идёт командами по USB CDC |
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
#include "protocol.h"
|
||||
#include "test_runner.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int main(void)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
#include <stdint.h>
|
||||
|
||||
/** @brief Строка версии прошивки, вставляемая в session_start. */
|
||||
#define FIRMWARE_TEST_VERSION "0.1.0"
|
||||
#define FIRMWARE_TEST_VERSION "0.1.4"
|
||||
|
||||
/** @brief Таймаут подтверждения по умолчанию, мс. */
|
||||
#define PROTOCOL_CONFIRM_TIMEOUT_MS 30000U
|
||||
|
|
|
|||
|
|
@ -23,33 +23,19 @@
|
|||
#include <stdio.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
|
||||
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;
|
||||
|
||||
static const test_module_t *const k_registry[] = {
|
||||
/* populated starting from Этап 2 */
|
||||
&K_TEST_SDRAM,
|
||||
&K_TEST_QSPI,
|
||||
&K_TEST_USD,
|
||||
&K_TEST_DISPLAY,
|
||||
};
|
||||
|
||||
#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]
|
||||
|
||||
## Как работает workflow
|
||||
|
||||
Workflow реагирует на три типа событий: `push`, `pull_request` и `workflow_dispatch`.[cite:23][cite:99] Это даёт удобный баланс между автоматической проверкой обычных коммитов и возможностью вручную перезапускать pipeline для отладки инфраструктурных или нестабильных падений без обязательного нового изменения в коде.[cite:99][cite:106]
|
||||
|
||||
Внутри 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]
|
||||
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 `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` зависит от `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]
|
||||
|
||||
## Чего 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]
|
||||
- Она модульна, потому что `build` и `test` вынесены в отдельные job’ы, связанные артефактами.[cite:156][cite:153]
|
||||
- Она удобна для отладки, потому что build outputs сохраняются как artifacts, а workflow можно запускать вручную через `workflow_dispatch`.[cite:99][cite:156]
|
||||
- Она хорошо вписана в структуру проекта, потому что использует уже существующие `just`-точки входа, а не дублирует build-логику в YAML.[cite:1]
|
||||
- Она воспроизводима, потому что сборка и тесты выполняются в том же образе, что и локальная разработка.
|
||||
- Она ускоряется на повторных прогонах за счёт Docker layer caching через GitHub Actions cache backend.
|
||||
- Она модульна, потому что `build` и `test` вынесены в отдельные job’ы, связанные артефактами.
|
||||
- Она удобна для отладки, потому что build outputs сохраняются как artifacts, а workflow можно запускать вручную через `workflow_dispatch`.
|
||||
- Она хорошо вписана в структуру проекта, потому что использует уже существующие `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
|
||||
|
||||
Самое логичное следующее улучшение — реализовать полноценную стадию `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
|
||||
|
||||
После стабилизации `lint` полезно подключить экспорт coverage для host-тестов.[cite:1] В `ci.just` уже существует закрытый рецепт `_coverage`, и его можно развить до генерации XML-отчёта, выгрузки артефактов и последующей интеграции с внешним coverage-сервисом, если это будет нужно.[cite:1]
|
||||
После стабилизации `lint` полезно подключить экспорт coverage для host-тестов. В `ci.just` уже существует закрытый рецепт `_coverage`, и его можно развить до генерации XML-отчёта, выгрузки артефактов и последующей интеграции с внешним coverage-сервисом, если это будет нужно.
|
||||
|
||||
### Шаг 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
|
||||
|
||||
Следующий сильный шаг по оптимизации — публиковать 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
|
||||
|
||||
Финальное крупное направление развития — выделенный аппаратный 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);
|
||||
|
||||
/* 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 ───────────────────────────────────────────────────────── */
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ tools/hil/
|
|||
Полное описание стека HIL-тестирования — в `docs/testing/hil/`:
|
||||
|
||||
| Документ | Содержимое |
|
||||
|----------|-----------|
|
||||
| --------------------------------------------------------------- | ---------------------------------------------- |
|
||||
| [HIL_HOWTO.md](../../docs/testing/hil/HIL_HOWTO.md) | Как запускать HIL-тесты (пошагово) |
|
||||
| [HIL_BENCH.md](../../docs/testing/hil/HIL_BENCH.md) | Стенд: оборудование, подключение, маппинг реле |
|
||||
| [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/
|
||||
├── flash_usb.py — прошивка через USB ROM: sdphost → Flashloader → Flash
|
||||
├── flash_swd.py — прошивка через SWD: FCB + HAB → pyOCD → Flash
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@
|
|||
## Модули
|
||||
|
||||
| Модуль | Путь | Описание |
|
||||
|--------|------|----------|
|
||||
| ------------- | ------------------------------------- | -------------------------------------------------- |
|
||||
| `ring_buffer` | [ring_buffer/](ring_buffer/README.md) | SPSC кольцевой буфер байт, lock-free |
|
||||
| `log` | [log/](log/README.md) | Платформонезависимый логгер с callback-транспортом |
|
||||
|
|
|
|||
Loading…
Reference in a new issue