# firmware_test: can & opto done
This commit is contained in:
parent
20bebf6d5e
commit
73b20d9f3e
13 changed files with 303 additions and 81 deletions
|
|
@ -259,6 +259,12 @@ bsp_status_t bsp_can_init(const bsp_can_config_t *p_config)
|
||||||
|
|
||||||
(void) memcpy(&flexcan_cfg.timingConfig, &timing_cfg, sizeof(timing_cfg));
|
(void) memcpy(&flexcan_cfg.timingConfig, &timing_cfg, sizeof(timing_cfg));
|
||||||
|
|
||||||
|
/* Workaround ERRATA 50235: FLEXCAN_Init() содержит assert который проверяет
|
||||||
|
* что CCM_CCGR5_CG12 (LPUART clock gate) открыт когда CAN тактируется
|
||||||
|
* от осциллятора. Открываем gate и оставляем открытым — закрывать не нужно,
|
||||||
|
* LPUART1 тактируется с минимальным потреблением. */
|
||||||
|
CLOCK_EnableClock(kCLOCK_Lpuart1);
|
||||||
|
|
||||||
FLEXCAN_Init(BSP_CAN_BASE, &flexcan_cfg, BSP_CAN_CLK_FREQ_HZ);
|
FLEXCAN_Init(BSP_CAN_BASE, &flexcan_cfg, BSP_CAN_CLK_FREQ_HZ);
|
||||||
|
|
||||||
/* MB1 — рабочий TX. */
|
/* MB1 — рабочий TX. */
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,15 @@ bsp_status_t bsp_opto_init(const bsp_opto_config_t *p_cfg);
|
||||||
void bsp_opto_process(void); /* вызывать из main loop */
|
void bsp_opto_process(void); /* вызывать из main loop */
|
||||||
bsp_opto_state_t bsp_opto_read(bsp_opto_ch_t ch);
|
bsp_opto_state_t bsp_opto_read(bsp_opto_ch_t ch);
|
||||||
bsp_status_t bsp_opto_proto_arm(bsp_opto_ch_t ch);
|
bsp_status_t bsp_opto_proto_arm(bsp_opto_ch_t ch);
|
||||||
|
bsp_opto_state_t bsp_opto_force_read(bsp_opto_ch_t ch);
|
||||||
```
|
```
|
||||||
|
|
||||||
`bsp_opto_read()` всегда возвращает `BSP_OPTO_STATE_INACTIVE` для каналов
|
- `bsp_opto_read()` всегда возвращает `BSP_OPTO_STATE_INACTIVE` для каналов
|
||||||
в `MODE_PROTO` — используй `GPIO_PinRead` напрямую при побитовом сэмплировании.
|
в `MODE_PROTO` — используй `GPIO_PinRead` напрямую при побитовом сэмплировании.
|
||||||
|
- `bsp_opto_force_read()` синхронно читает пин напрямую, обновляет
|
||||||
|
`confirmed_state` и сбрасывает `pending`. Используется в тестах после
|
||||||
|
гарантированной стабилизации сигнала — когда дебаунс уже отработал,
|
||||||
|
но `confirmed_state` мог не обновиться из-за чётного числа ISR при дребезге реле.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,16 @@ extern "C"
|
||||||
*/
|
*/
|
||||||
void bsp_opto_proto_arm(bsp_opto_ch_t ch);
|
void bsp_opto_proto_arm(bsp_opto_ch_t ch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Прочитать мгновенное состояние пина канала напрямую (без дебаунса).
|
||||||
|
*
|
||||||
|
* Используется после гарантированной стабилизации сигнала для синхронного
|
||||||
|
* чтения в тестах. Обновляет confirmed_state.
|
||||||
|
*
|
||||||
|
* @param ch канал (MODE_LEVEL)
|
||||||
|
* @return текущее состояние пина
|
||||||
|
*/
|
||||||
|
bsp_opto_state_t bsp_opto_force_read(bsp_opto_ch_t ch);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -314,3 +314,18 @@ void bsp_opto_process(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bsp_opto_state_t bsp_opto_force_read(bsp_opto_ch_t ch)
|
||||||
|
{
|
||||||
|
if (ch >= BSP_OPTO_CH_COUNT || !g_s_opto.channels[ch].enabled ||
|
||||||
|
g_s_opto.channels[ch].mode == BSP_OPTO_MODE_PROTO)
|
||||||
|
{
|
||||||
|
return BSP_OPTO_STATE_INACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Принудительно перечитать пин и обновить confirmed_state. */
|
||||||
|
bsp_opto_state_t state = OPTO_PIN_TO_STATE(read_pin(g_s_opto.channels[ch].pin));
|
||||||
|
g_s_opto.channels[ch].confirmed_state = state;
|
||||||
|
g_s_opto.channels[ch].pending = false;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
@ -67,8 +67,8 @@ target_link_options(
|
||||||
-Wl,--gc-sections
|
-Wl,--gc-sections
|
||||||
-Wl,--print-memory-usage
|
-Wl,--print-memory-usage
|
||||||
-Wl,-Map=${CMAKE_BINARY_DIR}/firmware_test.map
|
-Wl,-Map=${CMAKE_BINARY_DIR}/firmware_test.map
|
||||||
-Wl,--defsym=__stack_size__=0x1000
|
-Wl,--defsym=__stack_size__=0x2000
|
||||||
-Wl,--defsym=__heap_size__=0x1000
|
-Wl,--defsym=__heap_size__=0x2000
|
||||||
-T${PROJECT_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_flexspi_nor_sdram.ld)
|
-T${PROJECT_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_flexspi_nor_sdram.ld)
|
||||||
|
|
||||||
set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY
|
set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# firmware_test — План разработки
|
# firmware_test — План разработки
|
||||||
|
|
||||||
> Версия: 0.6 | Обновлён после завершения Этапа 5 (display + buttons) и архитектурных решений по Этапам 6–8.
|
> Версия: 0.7 | Обновлён после завершения Этапов 6а–6д, 6е, 6ж (протокол, test_opto, test_can, HIL pytest firmware_cdc).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -19,24 +19,26 @@
|
||||||
|
|
||||||
## Текущий статус
|
## Текущий статус
|
||||||
|
|
||||||
| Компонент | Статус | Примечание |
|
| Компонент | Статус | Примечание |
|
||||||
| ------------------------------ | ------ | -------------------------------------------- |
|
| ------------------------------ | ------ | ------------------------------------------------ |
|
||||||
| `bsp_usb_cdc` | ✅ | HIL тест пройден |
|
| `bsp_usb_cdc` | ✅ | HIL тест пройден |
|
||||||
| firmware_test скелет | ✅ | `main.c` + `cli.c` |
|
| firmware_test скелет | ✅ | `main.c` + `cli.c` |
|
||||||
| Протокол v2 + test_runner | ✅ | JSON-lines event-driven |
|
| Протокол v2 + test_runner | ✅ | JSON-lines event-driven |
|
||||||
| `bsp_sdram` + `test_sdram` | ✅ | 4 фазы: addr/data/seq/retention |
|
| `bsp_sdram` + `test_sdram` | ✅ | 4 фазы: addr/data/seq/retention |
|
||||||
| `bsp_qspi_flash` + `test_qspi` | ✅ | JEDEC + erase + rw + addr range |
|
| `bsp_qspi_flash` + `test_qspi` | ✅ | JEDEC + erase + rw + addr range |
|
||||||
| `bsp_sd` + `test_usd` | ✅ | bsp_sd + FatFS, pre_confirm, 4 шага |
|
| `bsp_sd` + `test_usd` | ✅ | bsp_sd + FatFS, pre_confirm, 4 шага |
|
||||||
| `bsp_display` + `test_display` | ✅ | 4 цвета + ротация, hardware-verified |
|
| `bsp_display` + `test_display` | ✅ | 4 цвета + ротация, hardware-verified |
|
||||||
| `bsp_button` + `test_buttons` | ✅ | 2 кнопки, physical detect, hardware-verified |
|
| `bsp_button` + `test_buttons` | ✅ | 2 кнопки, physical detect, hardware-verified |
|
||||||
| Протокол: `list_tests` | ⬜ | Этап 6а |
|
| Протокол: `list_tests` | ✅ | Этап 6а, hardware-verified |
|
||||||
| Протокол: `run_selected` | ⬜ | Этап 6а |
|
| Протокол: `run_selected` | ✅ | Этап 6а, hardware-verified |
|
||||||
| `test_opto` | ⬜ | Этап 6б (bsp_opto ✅) |
|
| `test_opto` | ✅ | Этап 6б, hardware-verified |
|
||||||
| `test_can` | ⬜ | Этап 6в (bsp_can ✅) |
|
| `test_can` | ✅ | Этап 6в, hardware-verified |
|
||||||
| `bsp_mqs` + `test_mqs` | ⬜ | Этап 6г |
|
| HIL pytest firmware_cdc | ✅ | Этап 6д, `FirmwareCdc` + `firmware_cdc` фикстура |
|
||||||
| HIL pytest firmware_cdc | ⬜ | Этап 6д |
|
| HIL pytest firmware_opto | ✅ | Этап 6е, `06_test_firmware_opto.py` |
|
||||||
| Provisioning | ⬜ | Этап 7 |
|
| HIL pytest firmware_can | ✅ | Этап 6ж, `06_test_firmware_can.py` |
|
||||||
| TUI сервисного инженера | ⬜ | Этап 8 |
|
| `bsp_mqs` + `test_mqs` | ⬜ | Этап 6г |
|
||||||
|
| Provisioning | ⬜ | Этап 7 |
|
||||||
|
| TUI сервисного инженера | ⬜ | Этап 8 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -50,8 +52,8 @@
|
||||||
| `display` | TFT Display RGB888 | ❌ | ❌ | interactive | `bsp_display` ✅ | ✅ |
|
| `display` | TFT Display RGB888 | ❌ | ❌ | interactive | `bsp_display` ✅ | ✅ |
|
||||||
| `buttons` | Test Buttons 1/2 | ❌ | ❌ | interactive | `bsp_button` ✅ | ✅ |
|
| `buttons` | Test Buttons 1/2 | ❌ | ❌ | interactive | `bsp_button` ✅ | ✅ |
|
||||||
| `mqs` | MQS Audio Out | ❌ | ❌ | interactive | `bsp_mqs` (⬜ новый) | ⬜ |
|
| `mqs` | MQS Audio Out | ❌ | ❌ | interactive | `bsp_mqs` (⬜ новый) | ⬜ |
|
||||||
| `can` | CAN loopback | ❌ | ✅ | HIL | `bsp_can` ✅ | ⬜ |
|
| `can` | CAN loopback | ❌ | ✅ | HIL | `bsp_can` ✅ | ✅ |
|
||||||
| `opto` | Оптовходы IN1/2+RS | ❌ | ✅ | HIL | `bsp_opto` ✅ | ⬜ |
|
| `opto` | Оптовходы IN1/2+RS | ❌ | ✅ | HIL | `bsp_opto` ✅ | ✅ |
|
||||||
|
|
||||||
**Убранные тесты (закрытые решения):**
|
**Убранные тесты (закрытые решения):**
|
||||||
|
|
||||||
|
|
@ -93,6 +95,23 @@
|
||||||
один канал. Буфер всегда стерео (L+R идентичны).
|
один канал. Буфер всегда стерео (L+R идентичны).
|
||||||
- **MQS тест:** захардкоженная мелодия 3–5 с, `confirm_request("mqs_tone")`,
|
- **MQS тест:** захардкоженная мелодия 3–5 с, `confirm_request("mqs_tone")`,
|
||||||
оператор слышит → OK/FAIL. `critical=false`, `requires_hil=false`.
|
оператор слышит → OK/FAIL. `critical=false`, `requires_hil=false`.
|
||||||
|
- **ERRATA 50235 (FlexCAN + USB):** `FLEXCAN_Init()` содержит assert на
|
||||||
|
`CCM_CCGR5_CG12` (LPUART clock gate). После `bsp_usb_cdc_init()` gate
|
||||||
|
может быть закрыт → assert → HardFault. Workaround: `CLOCK_EnableClock(kCLOCK_Lpuart1)`
|
||||||
|
перед `FLEXCAN_Init()` внутри `bsp_can_init()`. Gate оставляется открытым —
|
||||||
|
закрывать не нужно, LPUART1 тактируется с минимальным потреблением.
|
||||||
|
`bsp_can_init()` вызывается из `main()` после `bsp_usb_cdc_init()`.
|
||||||
|
- **`firmware_cdc` фикстура:** не ждёт `session_start` (одноразовое событие при
|
||||||
|
старте, может быть пропущено). Проверяет живость через `ping → pong`.
|
||||||
|
- **`bsp_opto_force_read()`:** добавлен в BSP API для синхронного чтения пина
|
||||||
|
без дебаунса. Обновляет `confirmed_state`, сбрасывает `pending`. Используется
|
||||||
|
в `test_opto.c` после settle — обходит race condition когда чётное число ISR
|
||||||
|
при дребезге реле оставляет `pending=false` с устаревшим `confirmed_state`.
|
||||||
|
- **`bsp_opto_process()` в `test_opto.c`:** вызывается в settle loop после confirm,
|
||||||
|
но не в `test_runner_wait_confirm()`. Финальное чтение — через `bsp_opto_force_read()`.
|
||||||
|
- **Оркестратор оpto:** `RELAY_ON_S=0.15`, `RELAY_OFF_S=0.5` в `06_test_firmware_opto.py`.
|
||||||
|
Фиксированный sleep достаточен — реле переключается до отправки `confirmed:true`,
|
||||||
|
`bsp_opto_force_read()` читает финальное состояние пина напрямую.
|
||||||
|
|
||||||
### Этап 8 (TUI решения)
|
### Этап 8 (TUI решения)
|
||||||
|
|
||||||
|
|
@ -431,14 +450,14 @@ just host::service-flash <bin> # прошить без TUI (для автом
|
||||||
✅ Этап 3 bsp_qspi_flash + test_qspi
|
✅ Этап 3 bsp_qspi_flash + test_qspi
|
||||||
✅ Этап 4 bsp_sd + test_usd
|
✅ Этап 4 bsp_sd + test_usd
|
||||||
✅ Этап 5 display + buttons
|
✅ Этап 5 display + buttons
|
||||||
|
✅ Этап 6а протокол: list_tests + run_selected
|
||||||
|
✅ Этап 6б test_opto.c + hardware верификация
|
||||||
|
✅ Этап 6в test_can.c + hardware верификация
|
||||||
|
✅ Этап 6д HIL pytest: firmware_cdc фикстура (FirmwareCdc + firmware_cdc)
|
||||||
|
✅ Этап 6е HIL pytest: 06_test_firmware_opto.py
|
||||||
|
✅ Этап 6ж HIL pytest: 06_test_firmware_can.py
|
||||||
|
|
||||||
⬜ Этап 6а протокол: list_tests + run_selected ← СЛЕДУЮЩИЙ ШАГ
|
⬜ Этап 6г bsp_mqs + test_mqs.c ← СЛЕДУЮЩИЙ ШАГ
|
||||||
⬜ Этап 6б test_opto.c + hardware верификация
|
|
||||||
⬜ Этап 6в test_can.c + hardware верификация
|
|
||||||
⬜ Этап 6г bsp_mqs + test_mqs.c (после получения наработок)
|
|
||||||
⬜ Этап 6д HIL pytest: firmware_cdc фикстура
|
|
||||||
⬜ Этап 6е HIL pytest: 06_test_firmware_opto.py
|
|
||||||
⬜ Этап 6ж HIL pytest: 06_test_firmware_can.py
|
|
||||||
|
|
||||||
⬜ Этап 7 Provisioning (OCOTP UID + Flash-флаг)
|
⬜ Этап 7 Provisioning (OCOTP UID + Flash-флаг)
|
||||||
|
|
||||||
|
|
@ -456,11 +475,11 @@ just host::service-flash <bin> # прошить без TUI (для автом
|
||||||
## Зависимости между этапами
|
## Зависимости между этапами
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
6а (протокол) → 6б (opto) → 6в (can) → 6г (mqs)
|
✅ 6а (протокол) → ✅ 6б (opto) → ✅ 6в (can) → ⬜ 6г (mqs)
|
||||||
↓
|
↓
|
||||||
6д (conftest) → 6е (opto pytest) → 6ж (can pytest)
|
✅ 6д (conftest) → ✅ 6е (opto pytest) → ✅ 6ж (can pytest)
|
||||||
↓
|
↓
|
||||||
7 (provisioning)
|
⬜ 7 (provisioning)
|
||||||
↓
|
↓
|
||||||
8 (TUI)
|
⬜ 8 (TUI)
|
||||||
```
|
```
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
* 8. Главный цикл — poll + cli_process
|
* 8. Главный цикл — poll + cli_process
|
||||||
*/
|
*/
|
||||||
#include "board.h"
|
#include "board.h"
|
||||||
|
#include "bsp/can.h"
|
||||||
#include "bsp/led.h"
|
#include "bsp/led.h"
|
||||||
#include "bsp/tick.h"
|
#include "bsp/tick.h"
|
||||||
#include "bsp/usb_cdc.h"
|
#include "bsp/usb_cdc.h"
|
||||||
|
|
@ -29,8 +30,10 @@
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
const uint32_t CONNECT_BLINK_MS = 200U;
|
const uint32_t CONNECT_BLINK_MS = 200U;
|
||||||
const uint32_t ERROR_BLINK_MS = 250;
|
const uint32_t ERROR_BLINK_MS = 250;
|
||||||
|
static const bsp_can_config_t K_CAN_CFG = { .bitrate = 125000U };
|
||||||
|
|
||||||
board_hw_init();
|
board_hw_init();
|
||||||
|
|
||||||
bsp_led_init();
|
bsp_led_init();
|
||||||
|
|
|
||||||
|
|
@ -430,6 +430,7 @@ bool test_runner_wait_confirm(const confirm_params_t *p_params)
|
||||||
{
|
{
|
||||||
return false; /* таймаут */
|
return false; /* таймаут */
|
||||||
}
|
}
|
||||||
|
|
||||||
bsp_usb_cdc_poll();
|
bsp_usb_cdc_poll();
|
||||||
cli_process();
|
cli_process();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,25 +12,28 @@
|
||||||
|
|
||||||
### Входящие сообщения (host → target)
|
### Входящие сообщения (host → target)
|
||||||
|
|
||||||
| Тип | Пример | Описание |
|
| Тип | Пример | Описание |
|
||||||
| --------- | ------------------------------------------------ | --------------------------- |
|
| --------- | -------------------------------------------------------------- | ------------------------------------ |
|
||||||
| `cmd` | `{"type":"cmd","cmd":"ping"}` | Проверка канала |
|
| `cmd` | `{"type":"cmd","cmd":"ping"}` | Проверка канала |
|
||||||
| `cmd` | `{"type":"cmd","cmd":"run","id":"sdram"}` | Запустить один тест по ID |
|
| `cmd` | `{"type":"cmd","cmd":"run","id":"sdram"}` | Запустить один тест по ID |
|
||||||
| `cmd` | `{"type":"cmd","cmd":"run_all"}` | Запустить все тесты реестра |
|
| `cmd` | `{"type":"cmd","cmd":"run_all"}` | Запустить все тесты реестра |
|
||||||
| `confirm` | `{"type":"confirm","id":"usd","confirmed":true}` | Ответ оператора на запрос |
|
| `cmd` | `{"type":"cmd","cmd":"list_tests"}` | Получить реестр тестов с метаданными |
|
||||||
|
| `cmd` | `{"type":"cmd","cmd":"run_selected","tests":["sdram","qspi"]}` | Запустить подмножество тестов |
|
||||||
|
| `confirm` | `{"type":"confirm","id":"usd","confirmed":true}` | Ответ оператора на запрос |
|
||||||
|
|
||||||
### Исходящие события (target → host)
|
### Исходящие события (target → host)
|
||||||
|
|
||||||
| Тип | Ключевые поля | Описание |
|
| Тип | Ключевые поля | Описание |
|
||||||
| ---------------------------- | ---------------------------------------- | ------------------------ |
|
| ---------------------------- | -------------------------------------------- | ------------------------------- |
|
||||||
| `session_start` | `fw`, `target`, `uptime_ms` | Прошивка готова к работе |
|
| `session_start` | `fw`, `target`, `uptime_ms` | Прошивка готова к работе |
|
||||||
| `test_begin` | `id`, `name`, `critical` | Тест запущен |
|
| `test_list` | `tests[]` (id, name, critical, requires_hil) | Ответ на `list_tests` |
|
||||||
| `test_result` | `id`, `status`, `ms`, `detail` | Результат теста |
|
| `test_begin` | `id`, `name`, `critical` | Тест запущен |
|
||||||
| `confirm_request` | `id`, `prompt`, `timeout_ms` | Запрос оператору |
|
| `test_result` | `id`, `status`, `ms`, `detail` | Результат теста |
|
||||||
| `progress` | `test`, `step`, `status` | Прогресс внутри теста |
|
| `confirm_request` | `id`, `prompt`, `timeout_ms` | Запрос оператору |
|
||||||
| `summary` | `passed`, `failed`, `skipped`, `overall` | Итог `run_all` |
|
| `progress` | `test`, `step`, `status` | Прогресс внутри теста |
|
||||||
| `pong` | — | Ответ на `ping` |
|
| `summary` | `passed`, `failed`, `skipped`, `overall` | Итог `run_all` / `run_selected` |
|
||||||
| `{"ok":false,"error":"..."}` | `error` | Ошибка протокола |
|
| `pong` | — | Ответ на `ping` |
|
||||||
|
| `{"ok":false,"error":"..."}` | `error` | Ошибка протокола |
|
||||||
|
|
||||||
**Возможные статусы `test_result`:** `pass` / `fail` / `skip`
|
**Возможные статусы `test_result`:** `pass` / `fail` / `skip`
|
||||||
|
|
||||||
|
|
@ -408,13 +411,15 @@ sequenceDiagram
|
||||||
|
|
||||||
## Реестр тестов — порядок выполнения
|
## Реестр тестов — порядок выполнения
|
||||||
|
|
||||||
| № | ID | Название | Critical | Тип |
|
| № | ID | Название | Critical | HIL | Тип |
|
||||||
| --- | --------- | ------------------ | -------- | ---------------------------- |
|
| --- | --------- | ------------------ | -------- | --- | ---------------------------- |
|
||||||
| 1 | `sdram` | SDRAM 32 MB | ✅ | Self-test |
|
| 1 | `sdram` | SDRAM 32 MB | ✅ | ❌ | Self-test |
|
||||||
| 2 | `qspi` | QSPI Flash W25Qxx | ✅ | Self-test |
|
| 2 | `qspi` | QSPI Flash W25Qxx | ✅ | ❌ | Self-test |
|
||||||
| 3 | `usd` | microSD (SDIO) | ❌ | Interactive (pre-confirm) |
|
| 3 | `usd` | microSD (SDIO) | ❌ | ❌ | Interactive (pre-confirm) |
|
||||||
| 4 | `display` | TFT Display RGB888 | ❌ | Interactive (in-run confirm) |
|
| 4 | `display` | TFT Display RGB888 | ❌ | ❌ | Interactive (in-run confirm) |
|
||||||
| 5 | `buttons` | Test Buttons | ❌ | Interactive (physical) |
|
| 5 | `buttons` | Test Buttons | ❌ | ❌ | Interactive (physical) |
|
||||||
|
| 6 | `opto` | Opto Inputs | ❌ | ✅ | HIL (M5StampPLC RLY2/3/4) |
|
||||||
|
| 7 | `can` | CAN loopback | ❌ | ✅ | HIL (M5StampPLC CAN) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -437,6 +442,151 @@ sequenceDiagram
|
||||||
| `display` | `<id> not confirmed` | Оператор не подтвердил / истёк таймаут 15 с |
|
| `display` | `<id> not confirmed` | Оператор не подтвердил / истёк таймаут 15 с |
|
||||||
| `buttons` | `btn1_press timeout` | Test_But_1 не нажата за 10 с |
|
| `buttons` | `btn1_press timeout` | Test_But_1 не нажата за 10 с |
|
||||||
| `buttons` | `btn2_press timeout` | Test_But_2 не нажата за 10 с |
|
| `buttons` | `btn2_press timeout` | Test_But_2 не нажата за 10 с |
|
||||||
|
| `opto` | `<id> mismatch: expected ACTIVE got INACTIVE` | Реле не переключило оптовход |
|
||||||
|
| `can` | `can_rx_ready: no frame received` | M5 не отправил фрейм / CAN не подключён |
|
||||||
|
| `can` | `rx id mismatch: expected 0x100 got 0x...` | Неверный ID принятого фрейма |
|
||||||
|
| `can` | `rx data mismatch: got XX XX XX XX` | Данные фрейма не совпадают |
|
||||||
|
| `can` | `tx failed: bsp_can_send returned <N>` | TX timeout или шина недоступна |
|
||||||
|
| `can` | `can_tx_verify: M5 did not confirm tx frame` | M5 не получил фрейм от таргета |
|
||||||
| любой | `confirm timeout` | pre-confirm не получен за 30 с |
|
| любой | `confirm timeout` | pre-confirm не получен за 30 с |
|
||||||
| любой | `operator declined` | Получен `"confirmed":false` |
|
| любой | `operator declined` | Получен `"confirmed":false` |
|
||||||
| любой | `critical test failed` | Предшествующий критичный тест провалился |
|
| любой | `critical test failed` | Предшествующий критичный тест провалился |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## list_tests — получить реестр тестов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
→ {"type":"cmd","cmd":"list_tests"}
|
||||||
|
← {"type":"test_list","tests":[
|
||||||
|
{"id":"sdram","name":"SDRAM 32 MB","critical":true,"requires_hil":false},
|
||||||
|
{"id":"qspi","name":"QSPI Flash W25Qxx","critical":true,"requires_hil":false},
|
||||||
|
{"id":"usd","name":"microSD (SDIO)","critical":false,"requires_hil":false},
|
||||||
|
{"id":"display","name":"TFT Display RGB888","critical":false,"requires_hil":false},
|
||||||
|
{"id":"buttons","name":"Test Buttons","critical":false,"requires_hil":false},
|
||||||
|
{"id":"opto","name":"Opto Inputs","critical":false,"requires_hil":true},
|
||||||
|
{"id":"can","name":"CAN loopback","critical":false,"requires_hil":true}
|
||||||
|
]}
|
||||||
|
```
|
||||||
|
|
||||||
|
TUI использует этот ответ для динамического построения списка тестов.
|
||||||
|
HIL-тесты (`requires_hil=true`) недоступны если M5StampPLC не подключён.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## run_selected — запустить подмножество тестов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
→ {"type":"cmd","cmd":"run_selected","tests":["sdram","opto"]}
|
||||||
|
← {"type":"test_begin","id":"sdram","name":"SDRAM 32 MB","critical":true}
|
||||||
|
← {"type":"test_result","id":"sdram","status":"pass","ms":15304,"detail":""}
|
||||||
|
← {"type":"test_begin","id":"opto","name":"Opto Inputs","critical":false}
|
||||||
|
...confirm цикл 6 шагов (HIL)...
|
||||||
|
← {"type":"test_result","id":"opto","status":"pass","ms":3210,"detail":""}
|
||||||
|
← {"type":"summary","passed":2,"failed":0,"skipped":0,"overall":"pass"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Порядок выполнения — как в реестре таргета, не как в запросе.
|
||||||
|
Если хотя бы один ID не найден — вся команда отклоняется:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
→ {"type":"cmd","cmd":"run_selected","tests":["sdram","unknown_test"]}
|
||||||
|
← {"ok":false,"error":"UNKNOWN_TEST"}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Оптоизолированные входы (HIL)
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
| ---------------- | -------------------------------- |
|
||||||
|
| ID | `opto` |
|
||||||
|
| Критичный | ❌ Нет |
|
||||||
|
| HIL | ✅ Да — M5StampPLC RLY2/RLY3/RLY4 |
|
||||||
|
| Тип | HIL (автоматический оркестратор) |
|
||||||
|
| Время выполнения | ~3–5 с (6 шагов) |
|
||||||
|
|
||||||
|
| Шаг | confirm_request id | M5 действие | Проверка |
|
||||||
|
| --- | ------------------- | ----------- | ----------------------------- |
|
||||||
|
| 1 | `opto_in1_active` | RLY3 ON | `BSP_OPTO_CH_IN1 == ACTIVE` |
|
||||||
|
| 2 | `opto_in1_inactive` | RLY3 OFF | `BSP_OPTO_CH_IN1 == INACTIVE` |
|
||||||
|
| 3 | `opto_in2_active` | RLY4 ON | `BSP_OPTO_CH_IN2 == ACTIVE` |
|
||||||
|
| 4 | `opto_in2_inactive` | RLY4 OFF | `BSP_OPTO_CH_IN2 == INACTIVE` |
|
||||||
|
| 5 | `opto_rs_active` | RLY2 ON | `BSP_OPTO_CH_RS == ACTIVE` |
|
||||||
|
| 6 | `opto_rs_inactive` | RLY2 OFF | `BSP_OPTO_CH_RS == INACTIVE` |
|
||||||
|
|
||||||
|
HIL confirm полностью автоматический — TUI командует M5 и отправляет confirm
|
||||||
|
без участия оператора.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
→ {"type":"cmd","cmd":"run","id":"opto"}
|
||||||
|
← {"type":"test_begin","id":"opto","name":"Opto Inputs","critical":false}
|
||||||
|
← {"type":"confirm_request","id":"opto_in1_active","prompt":"M5: RLY3 ON -> IN1 ACTIVE","timeout_ms":30000}
|
||||||
|
→ {"type":"confirm","id":"opto_in1_active","confirmed":true}
|
||||||
|
... 5 аналогичных шагов ...
|
||||||
|
← {"type":"test_result","id":"opto","status":"pass","ms":3210,"detail":""}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CAN loopback (HIL)
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
| ---------------- | -------------------------------- |
|
||||||
|
| ID | `can` |
|
||||||
|
| Критичный | ❌ Нет |
|
||||||
|
| HIL | ✅ Да — M5StampPLC CAN (SIT1044) |
|
||||||
|
| Тип | HIL (автоматический оркестратор) |
|
||||||
|
| Битрейт | 125 kbit/s |
|
||||||
|
| Время выполнения | ~1–2 с (2 шага) |
|
||||||
|
|
||||||
|
**Шаг 1 — RX (M5 → таргет):** M5 отправляет фрейм `id=0x100 data=[DE AD BE EF]`
|
||||||
|
до `confirmed:true`. Таргет принимает через `bsp_can_receive()` и верифицирует id + data.
|
||||||
|
|
||||||
|
**Шаг 2 — TX (таргет → M5):** таргет отправляет `id=0x200 data=[CA FE BA BE]`
|
||||||
|
до `confirm_request`. M5 принимает и верифицирует. TUI отправляет `confirmed:true/false`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
→ {"type":"cmd","cmd":"run","id":"can"}
|
||||||
|
← {"type":"test_begin","id":"can","name":"CAN loopback","critical":false}
|
||||||
|
← {"type":"confirm_request","id":"can_rx_ready","prompt":"M5: can_send id=0x100 data=[DE AD BE EF]","timeout_ms":30000}
|
||||||
|
→ {"type":"confirm","id":"can_rx_ready","confirmed":true}
|
||||||
|
← {"type":"confirm_request","id":"can_tx_verify","prompt":"M5: verify can_recv id=0x200 data=[CA FE BA BE]","timeout_ms":30000}
|
||||||
|
→ {"type":"confirm","id":"can_tx_verify","confirmed":true}
|
||||||
|
← {"type":"test_result","id":"can","status":"pass","ms":1240,"detail":""}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## HIL pytest — автоматическая верификация через firmware_test CDC
|
||||||
|
|
||||||
|
Два файла тестируют `test_opto` и `test_can` через реальный CDC-протокол v2.
|
||||||
|
Оркестратор (`FirmwareCdc`) управляет M5 автоматически при каждом `confirm_request`.
|
||||||
|
|
||||||
|
| Файл | Тест | Рецепт Just |
|
||||||
|
| -------------------------- | ------ | ------------------------------ |
|
||||||
|
| `06_test_firmware_opto.py` | `opto` | `just host::hil-firmware-opto` |
|
||||||
|
| `06_test_firmware_can.py` | `can` | `just host::hil-firmware-can` |
|
||||||
|
|
||||||
|
Запуск обоих сразу:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just host::hil-firmware
|
||||||
|
```
|
||||||
|
|
||||||
|
**Предусловие:** `firmware_test` прошита в Flash и запущена. Порт задаётся
|
||||||
|
через `HIL_USB_CDC_PORT` в `.env`. Фикстура `firmware_cdc` проверяет живость
|
||||||
|
через `ping → pong` (не ждёт `session_start` — он отправляется при старте и
|
||||||
|
может быть пропущен к моменту подключения).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just host::hil-firmware-opto
|
||||||
|
# 06_test_firmware_opto.py::TestFirmwareOpto::test_ping PASSED
|
||||||
|
# 06_test_firmware_opto.py::TestFirmwareOpto::test_opto_pass PASSED
|
||||||
|
# 06_test_firmware_opto.py::TestFirmwareOpto::test_opto_in1_fail_on_inactive PASSED
|
||||||
|
|
||||||
|
just host::hil-firmware-can
|
||||||
|
# 06_test_firmware_can.py::TestFirmwareCan::test_ping PASSED
|
||||||
|
# 06_test_firmware_can.py::TestFirmwareCan::test_can_pass PASSED
|
||||||
|
# 06_test_firmware_can.py::TestFirmwareCan::test_can_rx_fail_no_frame PASSED
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@
|
||||||
|
|
||||||
#include "bsp/can.h"
|
#include "bsp/can.h"
|
||||||
#include "bsp/tick.h"
|
#include "bsp/tick.h"
|
||||||
#include "bsp/usb_cdc.h"
|
|
||||||
#include "protocol.h"
|
|
||||||
#include "test_module.h"
|
#include "test_module.h"
|
||||||
#include "test_runner.h"
|
#include "test_runner.h"
|
||||||
|
|
||||||
|
|
@ -65,8 +63,8 @@ static const uint8_t K_TX_DATA[] = { 0xCAU, 0xFEU, 0xBAU, 0xBEU };
|
||||||
*/
|
*/
|
||||||
static void can_init(void)
|
static void can_init(void)
|
||||||
{
|
{
|
||||||
const bsp_can_config_t k_cfg = { .bitrate = CAN_BITRATE };
|
const bsp_can_config_t K_CFG = { .bitrate = CAN_BITRATE };
|
||||||
(void) bsp_can_init(&k_cfg);
|
(void) bsp_can_init(&K_CFG);
|
||||||
(void) bsp_can_accept_all();
|
(void) bsp_can_accept_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,13 +88,13 @@ static test_result_t can_run(void)
|
||||||
* M5 отправляет фрейм ДО того как подтвердить confirm(true).
|
* M5 отправляет фрейм ДО того как подтвердить confirm(true).
|
||||||
* После confirm таргет вызывает bsp_can_receive().
|
* После confirm таргет вызывает bsp_can_receive().
|
||||||
*/
|
*/
|
||||||
const confirm_params_t k_rx_params = {
|
const confirm_params_t K_RX_PARAMS = {
|
||||||
.id = "can_rx_ready",
|
.id = "can_rx_ready",
|
||||||
.prompt = "M5: can_send id=0x100 data=[DE AD BE EF]",
|
.prompt = "M5: can_send id=0x100 data=[DE AD BE EF]",
|
||||||
.timeout_ms = 0U,
|
.timeout_ms = 0U,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool confirmed = test_runner_wait_confirm(&k_rx_params);
|
bool confirmed = test_runner_wait_confirm(&K_RX_PARAMS);
|
||||||
|
|
||||||
if (!confirmed)
|
if (!confirmed)
|
||||||
{
|
{
|
||||||
|
|
@ -175,13 +173,13 @@ static test_result_t can_run(void)
|
||||||
* Запросить верификацию у TUI: M5 должен был принять наш фрейм.
|
* Запросить верификацию у TUI: M5 должен был принять наш фрейм.
|
||||||
* TUI проверяет id+data и шлёт confirmed(true) или confirmed(false).
|
* TUI проверяет id+data и шлёт confirmed(true) или confirmed(false).
|
||||||
*/
|
*/
|
||||||
const confirm_params_t k_tx_params = {
|
const confirm_params_t K_TX_PARAMS = {
|
||||||
.id = "can_tx_verify",
|
.id = "can_tx_verify",
|
||||||
.prompt = "M5: verify can_recv id=0x200 data=[CA FE BA BE]",
|
.prompt = "M5: verify can_recv id=0x200 data=[CA FE BA BE]",
|
||||||
.timeout_ms = 0U,
|
.timeout_ms = 0U,
|
||||||
};
|
};
|
||||||
|
|
||||||
confirmed = test_runner_wait_confirm(&k_tx_params);
|
confirmed = test_runner_wait_confirm(&K_TX_PARAMS);
|
||||||
|
|
||||||
if (!confirmed)
|
if (!confirmed)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
#include "bsp/opto.h"
|
#include "bsp/opto.h"
|
||||||
#include "bsp/tick.h"
|
#include "bsp/tick.h"
|
||||||
#include "bsp/usb_cdc.h"
|
#include "bsp/usb_cdc.h"
|
||||||
#include "protocol.h"
|
|
||||||
#include "test_module.h"
|
#include "test_module.h"
|
||||||
#include "test_runner.h"
|
#include "test_runner.h"
|
||||||
|
|
||||||
|
|
@ -161,7 +160,7 @@ static test_result_t opto_run(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 4. Верификация */
|
/* 4. Верификация */
|
||||||
bsp_opto_state_t got = bsp_opto_read(p_step->channel);
|
bsp_opto_state_t got = bsp_opto_force_read(p_step->channel);
|
||||||
if (got != p_step->expected_state)
|
if (got != p_step->expected_state)
|
||||||
{
|
{
|
||||||
const char *p_exp_str =
|
const char *p_exp_str =
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ class TestFirmwareCan:
|
||||||
self.cdc = firmware_cdc
|
self.cdc = firmware_cdc
|
||||||
self.m5 = m5
|
self.m5 = m5
|
||||||
|
|
||||||
|
|
||||||
def _on_confirm(self, confirm_id: str) -> bool:
|
def _on_confirm(self, confirm_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Оркестратор CAN-теста.
|
Оркестратор CAN-теста.
|
||||||
|
|
@ -49,8 +50,6 @@ class TestFirmwareCan:
|
||||||
Таргет уже отправил фрейм ДО confirm_request.
|
Таргет уже отправил фрейм ДО confirm_request.
|
||||||
M5 принимает фрейм, проверяет id+data, возвращает результат.
|
M5 принимает фрейм, проверяет id+data, возвращает результат.
|
||||||
"""
|
"""
|
||||||
import logging
|
|
||||||
logging.getLogger(__name__).info("on_confirm called: %r", confirm_id)
|
|
||||||
|
|
||||||
if confirm_id == "can_rx_ready":
|
if confirm_id == "can_rx_ready":
|
||||||
self.m5.can_send(CAN_RX_ID, CAN_RX_DATA)
|
self.m5.can_send(CAN_RX_ID, CAN_RX_DATA)
|
||||||
|
|
@ -80,10 +79,19 @@ class TestFirmwareCan:
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def test_list_tests(self) -> None:
|
||||||
|
"""Проверить что 'can' есть в реестре таргета."""
|
||||||
|
self.cdc.send({"type": "cmd", "cmd": "list_tests"})
|
||||||
|
msg = self.cdc.wait_event("test_list", timeout_s=5.0)
|
||||||
|
ids = [t["id"] for t in msg.get("tests", [])]
|
||||||
|
print(f"\nЗарегистрированные тесты: {ids}")
|
||||||
|
assert "can" in ids, f"'can' не найден в реестре: {ids}"
|
||||||
|
|
||||||
def test_ping(self) -> None:
|
def test_ping(self) -> None:
|
||||||
"""Базовая проверка CDC-канала."""
|
"""Базовая проверка CDC-канала."""
|
||||||
self.cdc.ping()
|
self.cdc.ping()
|
||||||
|
|
||||||
|
|
||||||
def test_can_pass(self) -> None:
|
def test_can_pass(self) -> None:
|
||||||
"""
|
"""
|
||||||
Полный прогон test_can: RX + TX через M5.
|
Полный прогон test_can: RX + TX через M5.
|
||||||
|
|
@ -92,7 +100,7 @@ class TestFirmwareCan:
|
||||||
result = self.cdc.run_hil_test(
|
result = self.cdc.run_hil_test(
|
||||||
test_id="can",
|
test_id="can",
|
||||||
on_confirm=self._on_confirm,
|
on_confirm=self._on_confirm,
|
||||||
timeout_s=60.0,
|
timeout_s=10.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result.get("status") == "pass", (
|
assert result.get("status") == "pass", (
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ import pytest
|
||||||
from conftest import FirmwareCdc, M5Agent
|
from conftest import FirmwareCdc, M5Agent
|
||||||
|
|
||||||
|
|
||||||
RELAY_ON_S = 0.15 # реле замыкается быстро
|
RELAY_ON_S = 0.1 # реле замыкается быстро
|
||||||
RELAY_OFF_S = 0.5 # размыкание + дебаунс прошивки с запасом
|
RELAY_OFF_S = 0.25 # размыкание + дебаунс прошивки с запасом
|
||||||
|
|
||||||
|
|
||||||
# Маппинг confirm_id → (opto_ch, state)
|
# Маппинг confirm_id → (opto_ch, state)
|
||||||
|
|
@ -65,6 +65,14 @@ class TestFirmwareOpto:
|
||||||
"""Базовая проверка CDC-канала."""
|
"""Базовая проверка CDC-канала."""
|
||||||
self.cdc.ping()
|
self.cdc.ping()
|
||||||
|
|
||||||
|
def test_list_tests(self) -> None:
|
||||||
|
"""Проверить что 'can' есть в реестре таргета."""
|
||||||
|
self.cdc.send({"type": "cmd", "cmd": "list_tests"})
|
||||||
|
msg = self.cdc.wait_event("test_list", timeout_s=5.0)
|
||||||
|
ids = [t["id"] for t in msg.get("tests", [])]
|
||||||
|
print(f"\nЗарегистрированные тесты: {ids}")
|
||||||
|
assert "can" in ids, f"'can' не найден в реестре: {ids}"
|
||||||
|
|
||||||
def test_opto_pass(self) -> None:
|
def test_opto_pass(self) -> None:
|
||||||
"""
|
"""
|
||||||
Запустить test_opto через firmware_test.
|
Запустить test_opto через firmware_test.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue