# 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));
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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 */
|
||||
bsp_opto_state_t bsp_opto_read(bsp_opto_ch_t ch);
|
||||
bsp_status_t bsp_opto_proto_arm(bsp_opto_ch_t ch);
|
||||
bsp_opto_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` напрямую при побитовом сэмплировании.
|
||||
- `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);
|
||||
|
||||
/**
|
||||
* @brief Прочитать мгновенное состояние пина канала напрямую (без дебаунса).
|
||||
*
|
||||
* Используется после гарантированной стабилизации сигнала для синхронного
|
||||
* чтения в тестах. Обновляет confirmed_state.
|
||||
*
|
||||
* @param ch канал (MODE_LEVEL)
|
||||
* @return текущее состояние пина
|
||||
*/
|
||||
bsp_opto_state_t bsp_opto_force_read(bsp_opto_ch_t ch);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#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,--print-memory-usage
|
||||
-Wl,-Map=${CMAKE_BINARY_DIR}/firmware_test.map
|
||||
-Wl,--defsym=__stack_size__=0x1000
|
||||
-Wl,--defsym=__heap_size__=0x1000
|
||||
-Wl,--defsym=__stack_size__=0x2000
|
||||
-Wl,--defsym=__heap_size__=0x2000
|
||||
-T${PROJECT_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_flexspi_nor_sdram.ld)
|
||||
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# firmware_test — План разработки
|
||||
|
||||
> Версия: 0.6 | Обновлён после завершения Этапа 5 (display + buttons) и архитектурных решений по Этапам 6–8.
|
||||
> Версия: 0.7 | Обновлён после завершения Этапов 6а–6д, 6е, 6ж (протокол, test_opto, test_can, HIL pytest firmware_cdc).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
## Текущий статус
|
||||
|
||||
| Компонент | Статус | Примечание |
|
||||
| ------------------------------ | ------ | -------------------------------------------- |
|
||||
| ------------------------------ | ------ | ------------------------------------------------ |
|
||||
| `bsp_usb_cdc` | ✅ | HIL тест пройден |
|
||||
| firmware_test скелет | ✅ | `main.c` + `cli.c` |
|
||||
| Протокол v2 + test_runner | ✅ | JSON-lines event-driven |
|
||||
|
|
@ -29,12 +29,14 @@
|
|||
| `bsp_sd` + `test_usd` | ✅ | bsp_sd + FatFS, pre_confirm, 4 шага |
|
||||
| `bsp_display` + `test_display` | ✅ | 4 цвета + ротация, hardware-verified |
|
||||
| `bsp_button` + `test_buttons` | ✅ | 2 кнопки, physical detect, hardware-verified |
|
||||
| Протокол: `list_tests` | ⬜ | Этап 6а |
|
||||
| Протокол: `run_selected` | ⬜ | Этап 6а |
|
||||
| `test_opto` | ⬜ | Этап 6б (bsp_opto ✅) |
|
||||
| `test_can` | ⬜ | Этап 6в (bsp_can ✅) |
|
||||
| Протокол: `list_tests` | ✅ | Этап 6а, hardware-verified |
|
||||
| Протокол: `run_selected` | ✅ | Этап 6а, hardware-verified |
|
||||
| `test_opto` | ✅ | Этап 6б, hardware-verified |
|
||||
| `test_can` | ✅ | Этап 6в, hardware-verified |
|
||||
| HIL pytest firmware_cdc | ✅ | Этап 6д, `FirmwareCdc` + `firmware_cdc` фикстура |
|
||||
| HIL pytest firmware_opto | ✅ | Этап 6е, `06_test_firmware_opto.py` |
|
||||
| HIL pytest firmware_can | ✅ | Этап 6ж, `06_test_firmware_can.py` |
|
||||
| `bsp_mqs` + `test_mqs` | ⬜ | Этап 6г |
|
||||
| HIL pytest firmware_cdc | ⬜ | Этап 6д |
|
||||
| Provisioning | ⬜ | Этап 7 |
|
||||
| TUI сервисного инженера | ⬜ | Этап 8 |
|
||||
|
||||
|
|
@ -50,8 +52,8 @@
|
|||
| `display` | TFT Display RGB888 | ❌ | ❌ | interactive | `bsp_display` ✅ | ✅ |
|
||||
| `buttons` | Test Buttons 1/2 | ❌ | ❌ | interactive | `bsp_button` ✅ | ✅ |
|
||||
| `mqs` | MQS Audio Out | ❌ | ❌ | interactive | `bsp_mqs` (⬜ новый) | ⬜ |
|
||||
| `can` | CAN loopback | ❌ | ✅ | HIL | `bsp_can` ✅ | ⬜ |
|
||||
| `opto` | Оптовходы IN1/2+RS | ❌ | ✅ | HIL | `bsp_opto` ✅ | ⬜ |
|
||||
| `can` | CAN loopback | ❌ | ✅ | HIL | `bsp_can` ✅ | ✅ |
|
||||
| `opto` | Оптовходы IN1/2+RS | ❌ | ✅ | HIL | `bsp_opto` ✅ | ✅ |
|
||||
|
||||
**Убранные тесты (закрытые решения):**
|
||||
|
||||
|
|
@ -93,6 +95,23 @@
|
|||
один канал. Буфер всегда стерео (L+R идентичны).
|
||||
- **MQS тест:** захардкоженная мелодия 3–5 с, `confirm_request("mqs_tone")`,
|
||||
оператор слышит → 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 решения)
|
||||
|
||||
|
|
@ -431,14 +450,14 @@ just host::service-flash <bin> # прошить без TUI (для автом
|
|||
✅ Этап 3 bsp_qspi_flash + test_qspi
|
||||
✅ Этап 4 bsp_sd + test_usd
|
||||
✅ Этап 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б 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
|
||||
⬜ Этап 6г bsp_mqs + test_mqs.c ← СЛЕДУЮЩИЙ ШАГ
|
||||
|
||||
⬜ Этап 7 Provisioning (OCOTP UID + Flash-флаг)
|
||||
|
||||
|
|
@ -456,11 +475,11 @@ just host::service-flash <bin> # прошить без TUI (для автом
|
|||
## Зависимости между этапами
|
||||
|
||||
```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
|
||||
*/
|
||||
#include "board.h"
|
||||
#include "bsp/can.h"
|
||||
#include "bsp/led.h"
|
||||
#include "bsp/tick.h"
|
||||
#include "bsp/usb_cdc.h"
|
||||
|
|
@ -31,6 +32,8 @@ int main(void)
|
|||
{
|
||||
const uint32_t CONNECT_BLINK_MS = 200U;
|
||||
const uint32_t ERROR_BLINK_MS = 250;
|
||||
static const bsp_can_config_t K_CAN_CFG = { .bitrate = 125000U };
|
||||
|
||||
board_hw_init();
|
||||
|
||||
bsp_led_init();
|
||||
|
|
|
|||
|
|
@ -430,6 +430,7 @@ bool test_runner_wait_confirm(const confirm_params_t *p_params)
|
|||
{
|
||||
return false; /* таймаут */
|
||||
}
|
||||
|
||||
bsp_usb_cdc_poll();
|
||||
cli_process();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,22 +13,25 @@
|
|||
### Входящие сообщения (host → target)
|
||||
|
||||
| Тип | Пример | Описание |
|
||||
| --------- | ------------------------------------------------ | --------------------------- |
|
||||
| --------- | -------------------------------------------------------------- | ------------------------------------ |
|
||||
| `cmd` | `{"type":"cmd","cmd":"ping"}` | Проверка канала |
|
||||
| `cmd` | `{"type":"cmd","cmd":"run","id":"sdram"}` | Запустить один тест по ID |
|
||||
| `cmd` | `{"type":"cmd","cmd":"run_all"}` | Запустить все тесты реестра |
|
||||
| `cmd` | `{"type":"cmd","cmd":"list_tests"}` | Получить реестр тестов с метаданными |
|
||||
| `cmd` | `{"type":"cmd","cmd":"run_selected","tests":["sdram","qspi"]}` | Запустить подмножество тестов |
|
||||
| `confirm` | `{"type":"confirm","id":"usd","confirmed":true}` | Ответ оператора на запрос |
|
||||
|
||||
### Исходящие события (target → host)
|
||||
|
||||
| Тип | Ключевые поля | Описание |
|
||||
| ---------------------------- | ---------------------------------------- | ------------------------ |
|
||||
| ---------------------------- | -------------------------------------------- | ------------------------------- |
|
||||
| `session_start` | `fw`, `target`, `uptime_ms` | Прошивка готова к работе |
|
||||
| `test_list` | `tests[]` (id, name, critical, requires_hil) | Ответ на `list_tests` |
|
||||
| `test_begin` | `id`, `name`, `critical` | Тест запущен |
|
||||
| `test_result` | `id`, `status`, `ms`, `detail` | Результат теста |
|
||||
| `confirm_request` | `id`, `prompt`, `timeout_ms` | Запрос оператору |
|
||||
| `progress` | `test`, `step`, `status` | Прогресс внутри теста |
|
||||
| `summary` | `passed`, `failed`, `skipped`, `overall` | Итог `run_all` |
|
||||
| `summary` | `passed`, `failed`, `skipped`, `overall` | Итог `run_all` / `run_selected` |
|
||||
| `pong` | — | Ответ на `ping` |
|
||||
| `{"ok":false,"error":"..."}` | `error` | Ошибка протокола |
|
||||
|
||||
|
|
@ -408,13 +411,15 @@ sequenceDiagram
|
|||
|
||||
## Реестр тестов — порядок выполнения
|
||||
|
||||
| № | ID | Название | Critical | Тип |
|
||||
| --- | --------- | ------------------ | -------- | ---------------------------- |
|
||||
| 1 | `sdram` | SDRAM 32 MB | ✅ | Self-test |
|
||||
| 2 | `qspi` | QSPI Flash W25Qxx | ✅ | Self-test |
|
||||
| 3 | `usd` | microSD (SDIO) | ❌ | Interactive (pre-confirm) |
|
||||
| 4 | `display` | TFT Display RGB888 | ❌ | Interactive (in-run confirm) |
|
||||
| 5 | `buttons` | Test Buttons | ❌ | Interactive (physical) |
|
||||
| № | ID | Название | Critical | HIL | Тип |
|
||||
| --- | --------- | ------------------ | -------- | --- | ---------------------------- |
|
||||
| 1 | `sdram` | SDRAM 32 MB | ✅ | ❌ | Self-test |
|
||||
| 2 | `qspi` | QSPI Flash W25Qxx | ✅ | ❌ | Self-test |
|
||||
| 3 | `usd` | microSD (SDIO) | ❌ | ❌ | Interactive (pre-confirm) |
|
||||
| 4 | `display` | TFT Display RGB888 | ❌ | ❌ | Interactive (in-run confirm) |
|
||||
| 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 с |
|
||||
| `buttons` | `btn1_press timeout` | Test_But_1 не нажата за 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 с |
|
||||
| любой | `operator declined` | Получен `"confirmed":false` |
|
||||
| любой | `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/tick.h"
|
||||
#include "bsp/usb_cdc.h"
|
||||
#include "protocol.h"
|
||||
#include "test_module.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)
|
||||
{
|
||||
const bsp_can_config_t k_cfg = { .bitrate = CAN_BITRATE };
|
||||
(void) bsp_can_init(&k_cfg);
|
||||
const bsp_can_config_t K_CFG = { .bitrate = CAN_BITRATE };
|
||||
(void) bsp_can_init(&K_CFG);
|
||||
(void) bsp_can_accept_all();
|
||||
}
|
||||
|
||||
|
|
@ -90,13 +88,13 @@ static test_result_t can_run(void)
|
|||
* M5 отправляет фрейм ДО того как подтвердить confirm(true).
|
||||
* После confirm таргет вызывает bsp_can_receive().
|
||||
*/
|
||||
const confirm_params_t k_rx_params = {
|
||||
const confirm_params_t K_RX_PARAMS = {
|
||||
.id = "can_rx_ready",
|
||||
.prompt = "M5: can_send id=0x100 data=[DE AD BE EF]",
|
||||
.timeout_ms = 0U,
|
||||
};
|
||||
|
||||
bool confirmed = test_runner_wait_confirm(&k_rx_params);
|
||||
bool confirmed = test_runner_wait_confirm(&K_RX_PARAMS);
|
||||
|
||||
if (!confirmed)
|
||||
{
|
||||
|
|
@ -175,13 +173,13 @@ static test_result_t can_run(void)
|
|||
* Запросить верификацию у TUI: M5 должен был принять наш фрейм.
|
||||
* 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",
|
||||
.prompt = "M5: verify can_recv id=0x200 data=[CA FE BA BE]",
|
||||
.timeout_ms = 0U,
|
||||
};
|
||||
|
||||
confirmed = test_runner_wait_confirm(&k_tx_params);
|
||||
confirmed = test_runner_wait_confirm(&K_TX_PARAMS);
|
||||
|
||||
if (!confirmed)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
#include "bsp/opto.h"
|
||||
#include "bsp/tick.h"
|
||||
#include "bsp/usb_cdc.h"
|
||||
#include "protocol.h"
|
||||
#include "test_module.h"
|
||||
#include "test_runner.h"
|
||||
|
||||
|
|
@ -161,7 +160,7 @@ static test_result_t opto_run(void)
|
|||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
const char *p_exp_str =
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ class TestFirmwareCan:
|
|||
self.cdc = firmware_cdc
|
||||
self.m5 = m5
|
||||
|
||||
|
||||
def _on_confirm(self, confirm_id: str) -> bool:
|
||||
"""
|
||||
Оркестратор CAN-теста.
|
||||
|
|
@ -49,8 +50,6 @@ class TestFirmwareCan:
|
|||
Таргет уже отправил фрейм ДО confirm_request.
|
||||
M5 принимает фрейм, проверяет id+data, возвращает результат.
|
||||
"""
|
||||
import logging
|
||||
logging.getLogger(__name__).info("on_confirm called: %r", confirm_id)
|
||||
|
||||
if confirm_id == "can_rx_ready":
|
||||
self.m5.can_send(CAN_RX_ID, CAN_RX_DATA)
|
||||
|
|
@ -80,10 +79,19 @@ class TestFirmwareCan:
|
|||
|
||||
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:
|
||||
"""Базовая проверка CDC-канала."""
|
||||
self.cdc.ping()
|
||||
|
||||
|
||||
def test_can_pass(self) -> None:
|
||||
"""
|
||||
Полный прогон test_can: RX + TX через M5.
|
||||
|
|
@ -92,7 +100,7 @@ class TestFirmwareCan:
|
|||
result = self.cdc.run_hil_test(
|
||||
test_id="can",
|
||||
on_confirm=self._on_confirm,
|
||||
timeout_s=60.0,
|
||||
timeout_s=10.0,
|
||||
)
|
||||
|
||||
assert result.get("status") == "pass", (
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ import pytest
|
|||
from conftest import FirmwareCdc, M5Agent
|
||||
|
||||
|
||||
RELAY_ON_S = 0.15 # реле замыкается быстро
|
||||
RELAY_OFF_S = 0.5 # размыкание + дебаунс прошивки с запасом
|
||||
RELAY_ON_S = 0.1 # реле замыкается быстро
|
||||
RELAY_OFF_S = 0.25 # размыкание + дебаунс прошивки с запасом
|
||||
|
||||
|
||||
# Маппинг confirm_id → (opto_ch, state)
|
||||
|
|
@ -65,6 +65,14 @@ class TestFirmwareOpto:
|
|||
"""Базовая проверка CDC-канала."""
|
||||
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:
|
||||
"""
|
||||
Запустить test_opto через firmware_test.
|
||||
|
|
|
|||
Loading…
Reference in a new issue