# firmware_test: provisionig - done
This commit is contained in:
parent
2d6aaaecd2
commit
3aa8974def
17 changed files with 689 additions and 296 deletions
|
|
@ -21,6 +21,7 @@ add_subdirectory(sdram)
|
||||||
add_subdirectory(qspi_flash)
|
add_subdirectory(qspi_flash)
|
||||||
add_subdirectory(sd)
|
add_subdirectory(sd)
|
||||||
add_subdirectory(mqs)
|
add_subdirectory(mqs)
|
||||||
|
add_subdirectory(provisioning)
|
||||||
|
|
||||||
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
||||||
target_compile_options(bsp_board PRIVATE -w)
|
target_compile_options(bsp_board PRIVATE -w)
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,15 @@ bsp/
|
||||||
│ └── mocks/ # fff-заглушки для host-тестов
|
│ └── mocks/ # fff-заглушки для host-тестов
|
||||||
├── opto/ # bsp_opto — оптоизолированные входы PS2801-4
|
├── opto/ # bsp_opto — оптоизолированные входы PS2801-4
|
||||||
├── can/ # bsp_can — FlexCAN2 (трансивер SN65HVD230D)
|
├── can/ # bsp_can — FlexCAN2 (трансивер SN65HVD230D)
|
||||||
|
│ └── mocks/ # fff-заглушки для host-тестов
|
||||||
├── button/ # bsp_button — тактовые кнопки SWT6x6 с debounce
|
├── button/ # bsp_button — тактовые кнопки SWT6x6 с debounce
|
||||||
├── display/ # bsp_display — TFT-дисплей
|
├── display/ # bsp_display — TFT-дисплей
|
||||||
├── usb_cdc/ # bsp_usb_cdc — USB CDC ACM
|
├── usb_cdc/ # bsp_usb_cdc — USB CDC ACM
|
||||||
├── sdram/ # bsp_sdram — внешний SDRAM через SEMC
|
├── sdram/ # bsp_sdram — внешний SDRAM через SEMC
|
||||||
├── qspi_flash/ # bsp_qspi_flash — QSPI Flash W25Q64/128/256/512
|
├── qspi_flash/ # bsp_qspi_flash — QSPI Flash W25Q64/128/256/512
|
||||||
└── sd/ # bsp_sd — SD host-контроллер (USDHC1)
|
├── sd/ # bsp_sd — SD host-контроллер (USDHC1)
|
||||||
|
├── mqs/ # bsp_mqs — MQS аудио-выход (SAI3, MQS, eDMA + управление громкостью)
|
||||||
|
└── provisioning/ # bsp_provisioning — OCOTP UID (уникальный идентификатор чипа)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -84,19 +87,21 @@ target_link_libraries(test_hil_opto PRIVATE bsp_board bsp_boot_ram ...)
|
||||||
|
|
||||||
### Компоненты периферии
|
### Компоненты периферии
|
||||||
|
|
||||||
| Библиотека | Модуль | README |
|
| Библиотека | Модуль | README |
|
||||||
| ---------------- | ------------- | -------------------------------------------- |
|
| ------------------ | --------------- | ------------------------------------------------ |
|
||||||
| `bsp_led` | `led/` | [led/README.md](led/README.md) |
|
| `bsp_led` | `led/` | [led/README.md](led/README.md) |
|
||||||
| `bsp_tick` | `tick/` | [tick/README.md](tick/README.md) |
|
| `bsp_tick` | `tick/` | [tick/README.md](tick/README.md) |
|
||||||
| `bsp_uart_host` | `uart_host/` | [uart_host/README.md](uart_host/README.md) |
|
| `bsp_uart_host` | `uart_host/` | [uart_host/README.md](uart_host/README.md) |
|
||||||
| `bsp_opto` | `opto/` | [opto/README.md](opto/README.md) |
|
| `bsp_opto` | `opto/` | [opto/README.md](opto/README.md) |
|
||||||
| `bsp_can` | `can/` | [can/README.md](can/README.md) |
|
| `bsp_can` | `can/` | [can/README.md](can/README.md) |
|
||||||
| `bsp_button` | `button/` | [button/README.md](button/README.md) |
|
| `bsp_button` | `button/` | [button/README.md](button/README.md) |
|
||||||
| `bsp_display` | `display/` | [display/README.md](display/README.md) |
|
| `bsp_display` | `display/` | [display/README.md](display/README.md) |
|
||||||
| `bsp_usb_cdc` | `usb_cdc/` | [usb_cdc/README.md](usb_cdc/README.md) |
|
| `bsp_usb_cdc` | `usb_cdc/` | [usb_cdc/README.md](usb_cdc/README.md) |
|
||||||
| `bsp_sdram` | `sdram/` | [sdram/README.md](sdram/README.md) |
|
| `bsp_sdram` | `sdram/` | [sdram/README.md](sdram/README.md) |
|
||||||
| `bsp_qspi_flash` | `qspi_flash/` | [qspi_flash/README.md](qspi_flash/README.md) |
|
| `bsp_qspi_flash` | `qspi_flash/` | [qspi_flash/README.md](qspi_flash/README.md) |
|
||||||
| `bsp_sd` | `sd/` | [sd/README.md](sd/README.md) |
|
| `bsp_sd` | `sd/` | [sd/README.md](sd/README.md) |
|
||||||
|
| `bsp_mqs` | `mqs/` | [mqs/README.md](mqs/README.md) |
|
||||||
|
| `bsp_provisioning` | `provisioning/` | [provisioning/README.md](provisioning/README.md) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,263 @@
|
||||||
|
# bsp_mqs — MQS аудио-выход (SAI3 + eDMA + LM4875M)
|
||||||
|
|
||||||
|
Воспроизведение PCM16 аудио через MQS-периферию MIMXRT1052CVJ5B.
|
||||||
|
Физический выход — один канал (`MQS_RIGHT`, `GPIO_AD_B0_04`); SAI3 требует
|
||||||
|
стерео-буфер — оба канала буфера всегда идентичны.
|
||||||
|
Управление коэффициентом усиления — FlexPWM4 SM0 PWM_A (`GPIO_AD_B1_08`)
|
||||||
|
через RC-фильтр и буферный ОУ LM358 на вход `DC_VOL` усилителя LM4875M.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Аппаратура
|
||||||
|
|
||||||
|
### MQS / SAI3
|
||||||
|
|
||||||
|
| Сигнал | Пин MCU | Корпус | Интерфейс | Примечание |
|
||||||
|
| --------- | ------------- | ------ | --------- | ----------------------------------------------------- |
|
||||||
|
| MQS_RIGHT | GPIO_AD_B0_04 | F11 | MQS RIGHT | Выход SAI3 TX, мультиплексирован в `BOARD_InitPins()` |
|
||||||
|
|
||||||
|
### Усилитель LM4875M
|
||||||
|
|
||||||
|
| Сигнал | Пин MCU | Корпус | Интерфейс | Примечание |
|
||||||
|
| ------------- | ------------- | ------ | ----------- | ------------------------------------------ |
|
||||||
|
| VolumeControl | GPIO_AD_B1_08 | H13 | PWM4 A, SM0 | Управление DC_VOL усилителя через ОУ LM358 |
|
||||||
|
|
||||||
|
**Сигнальный тракт:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MQS_RIGHT → R53+C97 → R54+C98 → R55+C99 → SOUND_OUT → C100 → LM4875M -Vin
|
||||||
|
GPIO_AD_B1_08 (PWM4_A) → R56 (1K) → LM358 +IN1 → R57 (1K) → LM4875M DC_VOL
|
||||||
|
```
|
||||||
|
|
||||||
|
RC-фильтр на сигнальном пути: три каскада R=2.4 кОм, C=0.1 мкФ (τ = 240 мкс каждый).
|
||||||
|
RC-фильтр на DC_VOL: R56 (1 кОм) + C101 (1 мкФ), τ = 1 мс.
|
||||||
|
`HP_SEN` LM4875M подтянут к GND — усилитель включается автоматически при
|
||||||
|
наличии сигнала на входе.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Тактирование
|
||||||
|
|
||||||
|
### SAI3 / MQS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Audio PLL = 24 MHz × (30 + 66/625) = 722.534 MHz
|
||||||
|
SAI3_CLK_ROOT = Audio PLL / 8 / 8 = 11 289 600 Hz
|
||||||
|
(kCLOCK_Sai3Mux=2, Sai3PreDiv=7, Sai3Div=7)
|
||||||
|
|
||||||
|
BCLK = 44100 × 16 бит × 2 канала = 1 411 200 Hz
|
||||||
|
Делитель MCLK = 11 289 600 / 1 411 200 = 8 (точно)
|
||||||
|
|
||||||
|
MQS_MCLK = SAI3_CLK_ROOT / 64 / (3+1) = 11 289 600 / 256 = 44 100 Hz
|
||||||
|
(Oversample=64, CLK_DIV=3, выставляется в bsp_mqs_init())
|
||||||
|
```
|
||||||
|
|
||||||
|
### FlexPWM4 (усилитель)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
IPG clock = AHB / 4 = 600 / 4 = 150 MHz
|
||||||
|
PWM clock = IPG / 16 = 9.375 MHz (Prescale_Divide_16)
|
||||||
|
Fpwm = 9 375 000 / 586 / 2 = 12 000 Hz (SignedCenterAligned)
|
||||||
|
```
|
||||||
|
|
||||||
|
Тактирование настраивается в `BOARD_BootClockRUN()` (`generated/clock_config.c`).
|
||||||
|
`bsp_mqs_init()` и `bsp_mqs_amp_init()` только открывают clock gates —
|
||||||
|
PLL и делители не трогают.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph HOST
|
||||||
|
A["bsp_mqs_play() / bsp_mqs_play_blocking()"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph SAI3_DMA
|
||||||
|
A --> B["SAI_TransferSendEDMA()\nDMA0 канал 0"]
|
||||||
|
B --> C["SAI3 TX FIFO\nwatermark = FIFO/2 = 16"]
|
||||||
|
C --> D["MQS_RIGHT\nGPIO_AD_B0_04"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph AMP
|
||||||
|
E["bsp_mqs_amp_init()"] --> F["PWM4 SM0 PWM_A\n12 kHz, duty 50%"]
|
||||||
|
F --> G["RC-фильтр\nR56+C101"]
|
||||||
|
G --> H["LM358 буфер"]
|
||||||
|
H --> I["DC_VOL LM4875M"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph CALLBACK
|
||||||
|
B --> J["mqs_edma_callback() ISR"]
|
||||||
|
J --> K["g_s_dma_done = true\nuser_cb()"]
|
||||||
|
end
|
||||||
|
|
||||||
|
D --> L["RC-фильтр 3×\nR53-55 + C97-99"]
|
||||||
|
L --> M["LM4875M\nSOUND_OUT → P8"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **TX** — `SAI_TransferSendEDMA()`. DMA0 канал 0 зарезервирован за `bsp_mqs`.
|
||||||
|
- **Blocking** — polling флага `g_s_dma_done` (взводится из ISR).
|
||||||
|
- **Async** — колбэк `bsp_mqs_done_cb_t` из ISR-контекста.
|
||||||
|
- **Singleton** — один экземпляр, один DMA-канал.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* SAI3 + eDMA + MQS */
|
||||||
|
bsp_status_t bsp_mqs_init(void);
|
||||||
|
void bsp_mqs_deinit(void);
|
||||||
|
|
||||||
|
bsp_status_t bsp_mqs_play(const int16_t *p_buf, size_t n_frames,
|
||||||
|
bsp_mqs_done_cb_t cb, void *p_user);
|
||||||
|
bsp_status_t bsp_mqs_play_blocking(const int16_t *p_buf, size_t n_frames);
|
||||||
|
|
||||||
|
void bsp_mqs_stop(void);
|
||||||
|
bool bsp_mqs_is_busy(void);
|
||||||
|
|
||||||
|
/* Усилитель LM4875M (PWM4) */
|
||||||
|
bsp_status_t bsp_mqs_amp_init(void);
|
||||||
|
void bsp_mqs_amp_set_volume(uint8_t percent);
|
||||||
|
void bsp_mqs_amp_deinit(void);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Коды возврата `bsp_mqs_play()` / `bsp_mqs_play_blocking()`:**
|
||||||
|
|
||||||
|
| Код | Условие |
|
||||||
|
| ----------------- | -------------------------------------- |
|
||||||
|
| `BSP_OK` | Передача запущена / завершена |
|
||||||
|
| `BSP_ERR_BUSY` | Предыдущая передача ещё не завершена |
|
||||||
|
| `BSP_ERR_INVALID` | `p_buf == NULL` или `n_frames == 0` |
|
||||||
|
| `BSP_ERR_HW` | `SAI_TransferSendEDMA()` вернул ошибку |
|
||||||
|
|
||||||
|
**Коды возврата `bsp_mqs_amp_init()`:**
|
||||||
|
|
||||||
|
| Код | Условие |
|
||||||
|
| ------------ | ----------------------------------------------- |
|
||||||
|
| `BSP_OK` | PWM4 настроен и запущен |
|
||||||
|
| `BSP_ERR_HW` | `PWM_Init()` или `PWM_SetupPwm()` вернул ошибку |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Параметры аудио-потока
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
| --------------------- | ---------------------- |
|
||||||
|
| Частота дискретизации | 44 100 Гц |
|
||||||
|
| Разрядность | 16 бит (signed PCM) |
|
||||||
|
| Каналы в буфере | 2 (stereo interleaved) |
|
||||||
|
| Байт на фрейм | 4 (L + R, по 2 байта) |
|
||||||
|
| DMA канал | DMA0 канал 0 |
|
||||||
|
| DMAMUX source | `kDmaRequestMuxSai3Tx` |
|
||||||
|
| FIFO watermark | 16 слов (FIFO/2) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "bsp/mqs.h"
|
||||||
|
|
||||||
|
/* Буфер — в некэшируемой памяти, выровнен по 4 байтам */
|
||||||
|
#define N_FRAMES (44100U) /* 1 секунда стерео */
|
||||||
|
static AT_NONCACHEABLE_SECTION_ALIGN(int16_t buf[N_FRAMES * BSP_MQS_CHANNELS], 4U);
|
||||||
|
|
||||||
|
/* После board_hw_init() + bsp_tick_init(): */
|
||||||
|
|
||||||
|
/* 1. Усилитель — первым, до MQS */
|
||||||
|
bsp_mqs_amp_init();
|
||||||
|
bsp_delay(300); /* дать C103/C105 LM4875M зарядиться */
|
||||||
|
|
||||||
|
/* 2. SAI3 + DMA + MQS */
|
||||||
|
bsp_mqs_init();
|
||||||
|
|
||||||
|
/* 3. Заполнить буфер */
|
||||||
|
for (size_t i = 0; i < N_FRAMES; i++) {
|
||||||
|
int16_t s = /* ваш сэмпл */;
|
||||||
|
buf[i * 2 + 0] = s; /* L */
|
||||||
|
buf[i * 2 + 1] = s; /* R */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4a. Синхронное воспроизведение (firmware_test) */
|
||||||
|
bsp_mqs_play_blocking(buf, N_FRAMES);
|
||||||
|
|
||||||
|
/* 4b. Асинхронное воспроизведение (tft_app) */
|
||||||
|
static volatile bool done;
|
||||||
|
static void on_done(void *p) { done = true; }
|
||||||
|
done = false;
|
||||||
|
bsp_mqs_play(buf, N_FRAMES, on_done, NULL);
|
||||||
|
while (!done) { /* keepalive */ }
|
||||||
|
|
||||||
|
/* Регулировка громкости: 0–100% */
|
||||||
|
bsp_mqs_amp_set_volume(75U);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Формат буфера:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
buf[0] = L0, buf[1] = R0, /* фрейм 0 */
|
||||||
|
buf[2] = L1, buf[3] = R1, /* фрейм 1 */
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Физически выводится только `MQS_RIGHT` — левый и правый каналы должны
|
||||||
|
быть идентичны.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ограничения и требования
|
||||||
|
|
||||||
|
- Буфер обязан находиться в **некэшируемой памяти** (`AT_NONCACHEABLE_SECTION_ALIGN`)
|
||||||
|
или кэш должен быть явно вычищен перед вызовом `bsp_mqs_play()`.
|
||||||
|
- Одновременно может воспроизводиться **только один буфер**. Повторный вызов
|
||||||
|
до завершения вернёт `BSP_ERR_BUSY`.
|
||||||
|
- `bsp_mqs_amp_init()` вызывать **до** `bsp_mqs_init()` и до первого `bsp_mqs_play()`.
|
||||||
|
- Поле `pwmchannelenable = true` в `pwm_signal_param_t` **обязательно** для
|
||||||
|
новых версий NXP SDK — без него `PWM_SetupPwm()` не устанавливает `OUTEN`
|
||||||
|
и ШИМ не выходит на пин.
|
||||||
|
- `bsp_mqs_play_blocking()` крутит polling-цикл. Не вызывать из ISR.
|
||||||
|
- В длинных тест-функциях вызывать `bsp_usb_cdc_poll()` каждые ~4 KB
|
||||||
|
для поддержания USB живым.
|
||||||
|
- DMA0 канал 0 зарезервирован за `bsp_mqs`. Остальные модули — каналы 1+.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Известные особенности SDK
|
||||||
|
|
||||||
|
**`pwmchannelenable` (критично):** в NXP SDK начиная с версии 2.13+ структура
|
||||||
|
`pwm_signal_param_t` содержит поле `pwmchannelenable`. При инициализации
|
||||||
|
через designated initializers без явного указания этого поля оно равно `false`,
|
||||||
|
и `PWM_SetupPwm()` не устанавливает бит `OUTEN` → ШИМ не выходит на пин.
|
||||||
|
После отладочной сессии ошибка маскируется: отладчик оставляет `OUTEN`
|
||||||
|
установленным от предыдущего сеанса, и при software reset PWM работает.
|
||||||
|
Воспроизводится только при cold reset (power cycle).
|
||||||
|
|
||||||
|
**Fault disable mapping:** без явного вызова `PWM_SetupFaultDisableMap()`
|
||||||
|
регистр `DISA` после cold reset равен `0x0000`, и PWM может быть заблокирован
|
||||||
|
неинициализированными Fault1/2/3 входами. `bsp_mqs_amp_init()` явно вызывает
|
||||||
|
`PWM_SetupFaultDisableMap()` для всех четырёх fault-входов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CMake
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
# firmware/test/CMakeLists.txt
|
||||||
|
target_link_libraries(firmware_test PRIVATE bsp_mqs)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Зависимости модуля:**
|
||||||
|
|
||||||
|
| Зависимость | Тип | Описание |
|
||||||
|
| -------------- | ------- | ------------------------------------------- |
|
||||||
|
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||||
|
| `bsp_board` | PUBLIC | Транзитивно: `BOARD_*`, IOMUXC, clock gates |
|
||||||
|
| `sdk_sai_edma` | PRIVATE | `fsl_sai_edma.h` — SAI TX через eDMA |
|
||||||
|
| `sdk_edma` | PRIVATE | `fsl_edma.h` — DMA0 handle |
|
||||||
|
| `sdk_dmamux` | PRIVATE | `fsl_dmamux.h` — маршрутизация DMA-запросов |
|
||||||
|
| `sdk_clock` | PRIVATE | `fsl_clock.h` — clock gates |
|
||||||
|
| `sdk_pwm` | PRIVATE | `fsl_pwm.h` — FlexPWM4 SM0 (усилитель) |
|
||||||
|
| `sdk_xbara` | PRIVATE | `fsl_xbara.h` — XBARA |
|
||||||
|
|
||||||
17
bsp/provisioning/CMakeLists.txt
Normal file
17
bsp/provisioning/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# bsp/provisioning/CMakeLists.txt
|
||||||
|
|
||||||
|
if(BUILD_TESTS_HOST)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(bsp_provisioning STATIC src/provisioning.c)
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
bsp_provisioning
|
||||||
|
PUBLIC include/
|
||||||
|
PRIVATE src/)
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
bsp_provisioning
|
||||||
|
PUBLIC bsp_status
|
||||||
|
PRIVATE bsp_board)
|
||||||
87
bsp/provisioning/README.md
Normal file
87
bsp/provisioning/README.md
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
# bsp_provisioning — Provisioning / OCOTP UID
|
||||||
|
|
||||||
|
Компонент читает уникальный 64-битный идентификатор чипа из OCOTP shadow registers.
|
||||||
|
Используется в `firmware_test` для команды `get_uid` протокола v2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Аппаратура
|
||||||
|
|
||||||
|
| Периферия | Назначение |
|
||||||
|
| --------- | ---------------------- |
|
||||||
|
| OCOTP | On-Chip OTP Controller |
|
||||||
|
|
||||||
|
Shadow registers загружаются из eFuse-массива при каждом сбросе (MIMXRT1052RM §46.3.1).
|
||||||
|
Чтение немедленное через memory-mapped регистры `OCOTP->CFG0` / `OCOTP->CFG1` —
|
||||||
|
инициализация контроллера, fuse programming и дополнительные задержки не нужны.
|
||||||
|
|
||||||
|
**UID-ячейки (MIMXRT1052RM Table 46-2):**
|
||||||
|
|
||||||
|
| Регистр | Содержимое |
|
||||||
|
| ---------- | ------------ |
|
||||||
|
| OCOTP_CFG0 | UID\[31:0\] |
|
||||||
|
| OCOTP_CFG1 | UID\[63:32\] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "bsp/provisioning.h"
|
||||||
|
|
||||||
|
#define BSP_PROV_UID_LEN 8U /* байт */
|
||||||
|
|
||||||
|
bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len);
|
||||||
|
```
|
||||||
|
|
||||||
|
`bsp_prov_read_uid()` — единственная публичная функция.
|
||||||
|
|
||||||
|
- Читает `OCOTP->CFG0` и `OCOTP->CFG1` напрямую через memory-mapped регистры
|
||||||
|
(паттерн идентичен `fsl_silicon_id_soc.c` из NXP SDK).
|
||||||
|
- Заполняет `p_uid[0..7]` в little-endian порядке: `p_uid[0..3]` = CFG0, `p_uid[4..7]` = CFG1.
|
||||||
|
Порядок байт совпадает с NXP-инструментами (MCUXpresso, blhost).
|
||||||
|
- Возвращает `BSP_ERR` если `p_uid == NULL` или `len < 8`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "bsp/provisioning.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
uint8_t uid[BSP_PROV_UID_LEN];
|
||||||
|
if (bsp_prov_read_uid(uid, sizeof(uid)) == BSP_OK)
|
||||||
|
{
|
||||||
|
/* hex: "AABBCCDDEEFF0011" */
|
||||||
|
for (size_t i = 0; i < BSP_PROV_UID_LEN; i++)
|
||||||
|
{
|
||||||
|
printf("%02X", uid[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CMake
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
target_link_libraries(firmware_test PRIVATE bsp_provisioning)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Зависимости:**
|
||||||
|
|
||||||
|
| Зависимость | Тип | Описание |
|
||||||
|
| ------------ | ------- | ------------------------------- |
|
||||||
|
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||||
|
| `bsp_board` | PRIVATE | Транзитивно: clock, SDK headers |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Особенности
|
||||||
|
|
||||||
|
- Прямое чтение регистров не требует `OCOTP_Init()` — инициализация контроллера
|
||||||
|
нужна только при записи fuse. Попытка вызвать `OCOTP_Init()` перед чтением
|
||||||
|
приводит к зависанию (контроллер занят после USB CDC init).
|
||||||
|
- Компонент не имеет host-stub: защищён `if(BUILD_TESTS_HOST) return()` в CMakeLists.
|
||||||
41
bsp/provisioning/include/bsp/provisioning.h
Normal file
41
bsp/provisioning/include/bsp/provisioning.h
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* @file bsp/provisioning/include/bsp/provisioning.h
|
||||||
|
* @brief BSP Provisioning — чтение уникального идентификатора чипа (OCOTP UID).
|
||||||
|
*
|
||||||
|
* Предоставляет единственный примитив: чтение 8-байтового UID из OCOTP_CFG0/CFG1.
|
||||||
|
* Не выполняет записи в OTP-ячейки и не управляет Flash.
|
||||||
|
*
|
||||||
|
* Аппаратура: MIMXRT1052RM §46 "On-Chip OTP Controller (OCOTP_CTRL)".
|
||||||
|
* Shadow registers загружаются из eFuse-массива при сбросе — чтение немедленное,
|
||||||
|
* fuse-programming не требуется.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BSP_PROVISIONING_H_
|
||||||
|
#define BSP_PROVISIONING_H_
|
||||||
|
|
||||||
|
#include "bsp/status.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** @brief Длина UID в байтах (OCOTP_CFG0 + OCOTP_CFG1). */
|
||||||
|
#define BSP_PROV_UID_LEN 8U
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Прочитать уникальный идентификатор чипа из OCOTP.
|
||||||
|
*
|
||||||
|
* Читает OCOTP_CFG0 (UID[31:0]) и OCOTP_CFG1 (UID[63:32]).
|
||||||
|
* Результат записывается в big-endian порядке: p_uid[0] — старший байт CFG1,
|
||||||
|
* p_uid[7] — младший байт CFG0. Hex-строка совпадает с визуальным порядком слов
|
||||||
|
* в Reference Manual (MIMXRT1052RM Table 46-2).
|
||||||
|
*
|
||||||
|
* Функция выполняет OCOTP_Init() и включает clock gate перед чтением.
|
||||||
|
* Clock gate остаётся открытым после вызова (паттерн проекта).
|
||||||
|
*
|
||||||
|
* @param[out] p_uid Буфер для UID. Должен быть не менее BSP_PROV_UID_LEN байт.
|
||||||
|
* @param[in] len Размер буфера в байтах. Должен быть >= BSP_PROV_UID_LEN.
|
||||||
|
* @return BSP_OK при успехе, BSP_ERR если p_uid == NULL или len < BSP_PROV_UID_LEN.
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len);
|
||||||
|
|
||||||
|
#endif /* BSP_PROVISIONING_H_ */
|
||||||
54
bsp/provisioning/src/provisioning.c
Normal file
54
bsp/provisioning/src/provisioning.c
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* @file bsp/provisioning/src/bsp_provisioning.c
|
||||||
|
* @brief Реализация bsp_provisioning: чтение UID из OCOTP_CFG0/CFG1.
|
||||||
|
*
|
||||||
|
* На i.MX RT1052 FSL_FEATURE_OCOTP_HAS_TIMING_CTRL == 0, поэтому
|
||||||
|
* OCOTP_Init(OCOTP, 0U) вызывается без частоты (частота не используется драйвером).
|
||||||
|
*
|
||||||
|
* Shadow registers загружаются из eFuse при сбросе (MIMXRT1052RM §46.3.1).
|
||||||
|
* OCOTP_ReadFuseShadowRegister() читает уже готовые значения без fuse programming.
|
||||||
|
*
|
||||||
|
* Индексы shadow registers (MIMXRT1052RM Table 46-2):
|
||||||
|
* Bank 0, Word 1 (index 1) → OCOTP_CFG0 → UID[31:0]
|
||||||
|
* Bank 0, Word 2 (index 2) → OCOTP_CFG1 → UID[63:32]
|
||||||
|
* index = bank * 4 + word
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bsp/provisioning.h"
|
||||||
|
|
||||||
|
#include "fsl_clock.h"
|
||||||
|
#include "fsl_common.h"
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
* Константы
|
||||||
|
* ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/** @brief Shadow register index для OCOTP_CFG0 (Bank 0, Word 1 → UID[31:0]). */
|
||||||
|
#define PROV_OCOTP_IDX_CFG0 1U
|
||||||
|
|
||||||
|
/** @brief Shadow register index для OCOTP_CFG1 (Bank 0, Word 2 → UID[63:32]). */
|
||||||
|
#define PROV_OCOTP_IDX_CFG1 2U
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
* Public API
|
||||||
|
* ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len)
|
||||||
|
{
|
||||||
|
if ((p_uid == NULL) || (len < BSP_PROV_UID_LEN))
|
||||||
|
{
|
||||||
|
return BSP_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shadow registers загружаются из eFuse при сбросе и доступны напрямую
|
||||||
|
* через memory-mapped адреса без инициализации контроллера.
|
||||||
|
* Паттерн идентичен fsl_silicon_id_soc.c из NXP SDK.
|
||||||
|
* CFG0 = UID[31:0], CFG1 = UID[63:32].
|
||||||
|
*/
|
||||||
|
/* NOLINTNEXTLINE(bugprone-casting-through-void) */
|
||||||
|
*((uint32_t *) (uintptr_t) &p_uid[0U]) = OCOTP->CFG0;
|
||||||
|
/* NOLINTNEXTLINE(bugprone-casting-through-void) */
|
||||||
|
*((uint32_t *) (uintptr_t) &p_uid[4U]) = OCOTP->CFG1;
|
||||||
|
|
||||||
|
return BSP_OK;
|
||||||
|
}
|
||||||
|
|
@ -152,6 +152,23 @@ sequenceDiagram
|
||||||
]}
|
]}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `get_uid` — чтение уникального идентификатора чипа
|
||||||
|
|
||||||
|
Запрос UID из OCOTP. Может быть отправлен в любой момент когда runner не BUSY.
|
||||||
|
|
||||||
|
```json
|
||||||
|
→ {"type":"cmd","cmd":"get_uid"}
|
||||||
|
← {"type":"uid_response","uid":"AABBCCDDEEFF0011"}
|
||||||
|
```
|
||||||
|
|
||||||
|
`uid` — 16 hex-символов (8 байт big-endian): CFG1[63:32] + CFG0[31:0].
|
||||||
|
|
||||||
|
При ошибке чтения OCOTP:
|
||||||
|
|
||||||
|
```json
|
||||||
|
← {"ok":false,"error":"UID_READ_ERR"}
|
||||||
|
```
|
||||||
|
|
||||||
### `run_selected` — запуск подмножества тестов
|
### `run_selected` — запуск подмножества тестов
|
||||||
|
|
||||||
Запускает тесты по списку ID. Порядок выполнения — по реестру таргета,
|
Запускает тесты по списку ID. Порядок выполнения — по реестру таргета,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ target_link_libraries(
|
||||||
bsp_tick
|
bsp_tick
|
||||||
bsp_boot_xip
|
bsp_boot_xip
|
||||||
bsp_usb_cdc
|
bsp_usb_cdc
|
||||||
|
bsp_provisioning
|
||||||
bsp_sdram
|
bsp_sdram
|
||||||
bsp_qspi_flash
|
bsp_qspi_flash
|
||||||
bsp_uart_host
|
bsp_uart_host
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# firmware_test — План разработки
|
# firmware_test — План разработки
|
||||||
|
|
||||||
> Версия: 0.7 | Обновлён после завершения Этапов 6а–6д, 6е, 6ж (протокол, test_opto, test_can, HIL pytest firmware_cdc).
|
> Версия: 0.8 | Обновлён после завершения Этапа 6г (bsp_mqs + test_mqs, hardware-verified).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
| HIL pytest firmware_cdc | ✅ | Этап 6д, `FirmwareCdc` + `firmware_cdc` фикстура |
|
| HIL pytest firmware_cdc | ✅ | Этап 6д, `FirmwareCdc` + `firmware_cdc` фикстура |
|
||||||
| HIL pytest firmware_opto | ✅ | Этап 6е, `06_test_firmware_opto.py` |
|
| HIL pytest firmware_opto | ✅ | Этап 6е, `06_test_firmware_opto.py` |
|
||||||
| HIL pytest firmware_can | ✅ | Этап 6ж, `06_test_firmware_can.py` |
|
| HIL pytest firmware_can | ✅ | Этап 6ж, `06_test_firmware_can.py` |
|
||||||
| `bsp_mqs` + `test_mqs` | ⬜ | Этап 6г |
|
| `bsp_mqs` + `test_mqs` | ✅ | Этап 6г, hardware-verified |
|
||||||
| Provisioning | ⬜ | Этап 7 |
|
| Provisioning | ⬜ | Этап 7 |
|
||||||
| TUI сервисного инженера | ⬜ | Этап 8 |
|
| TUI сервисного инженера | ⬜ | Этап 8 |
|
||||||
|
|
||||||
|
|
@ -44,16 +44,16 @@
|
||||||
|
|
||||||
## Матрица тестов — итоговая
|
## Матрица тестов — итоговая
|
||||||
|
|
||||||
| ID | Название | Critical | HIL | Тип | BSP | Статус |
|
| ID | Название | Critical | HIL | Тип | BSP | Статус |
|
||||||
| --------- | ------------------ | -------- | --- | ----------- | ------------------- | ------ |
|
| --------- | ------------------ | -------- | --- | ----------- | ------------------ | ------ |
|
||||||
| `sdram` | SDRAM 32 MB | ✅ | ❌ | self | `bsp_sdram` ✅ | ✅ |
|
| `sdram` | SDRAM 32 MB | ✅ | ❌ | self | `bsp_sdram` ✅ | ✅ |
|
||||||
| `qspi` | QSPI Flash W25Qxx | ✅ | ❌ | self | `bsp_qspi_flash` ✅ | ✅ |
|
| `qspi` | QSPI Flash W25Qxx | ✅ | ❌ | self | `bsp_qspi_flash` ✅ | ✅ |
|
||||||
| `usd` | microSD (SDIO) | ❌ | ❌ | interactive | `bsp_sd` ✅ | ✅ |
|
| `usd` | microSD (SDIO) | ❌ | ❌ | interactive | `bsp_sd` ✅ | ✅ |
|
||||||
| `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,8 +93,19 @@
|
||||||
- **Фильтрация HIL на стороне TUI:** таргет не фильтрует по `requires_hil`.
|
- **Фильтрация HIL на стороне TUI:** таргет не фильтрует по `requires_hil`.
|
||||||
- **MQS стерео:** MQS MIMXRT1052 требует стерео PCM-буфер. На плате выведен
|
- **MQS стерео:** MQS MIMXRT1052 требует стерео PCM-буфер. На плате выведен
|
||||||
один канал. Буфер всегда стерео (L+R идентичны).
|
один канал. Буфер всегда стерео (L+R идентичны).
|
||||||
- **MQS тест:** захардкоженная мелодия 3–5 с, `confirm_request("mqs_tone")`,
|
- **MQS тест:** захардкоженная мелодия ~4 с (A4 + E5, по 2 с каждая),
|
||||||
оператор слышит → OK/FAIL. `critical=false`, `requires_hil=false`.
|
`confirm_request("mqs_tone")`, оператор слышит → PASS/FAIL.
|
||||||
|
`critical=false`, `requires_hil=false`, `pre_confirm_prompt=NULL`.
|
||||||
|
- **MQS порядок init:** `bsp_mqs_amp_init()` → `bsp_delay(300)` → `bsp_mqs_init()`.
|
||||||
|
Усилитель запускается первым, чтобы успели зарядиться конденсаторы C103/C105 LM4875M.
|
||||||
|
Нарушение порядка приводит к щелчку при старте или отсутствию звука.
|
||||||
|
- **MQS USB keepalive:** воспроизведение через `bsp_mqs_play()` (async, не blocking),
|
||||||
|
параллельно крутится `bsp_usb_cdc_poll()`. Blocking-вариант голодает USB за ~4 с.
|
||||||
|
- **`pwmchannelenable` (NXP SDK ≥ 2.13):** поле в `pwm_signal_param_t` обязательно
|
||||||
|
выставлять в `true`. При инициализации через designated initializers без явного
|
||||||
|
указания равно `false` → `PWM_SetupPwm()` не выставляет `OUTEN` → ШИМ не выходит
|
||||||
|
на пин. Маскируется после отладочной сессии (отладчик оставляет `OUTEN` от прошлого
|
||||||
|
прогона). Воспроизводится только при cold reset.
|
||||||
- **ERRATA 50235 (FlexCAN + USB):** `FLEXCAN_Init()` содержит assert на
|
- **ERRATA 50235 (FlexCAN + USB):** `FLEXCAN_Init()` содержит assert на
|
||||||
`CCM_CCGR5_CG12` (LPUART clock gate). После `bsp_usb_cdc_init()` gate
|
`CCM_CCGR5_CG12` (LPUART clock gate). После `bsp_usb_cdc_init()` gate
|
||||||
может быть закрыт → assert → HardFault. Workaround: `CLOCK_EnableClock(kCLOCK_Lpuart1)`
|
может быть закрыт → assert → HardFault. Workaround: `CLOCK_EnableClock(kCLOCK_Lpuart1)`
|
||||||
|
|
@ -126,9 +137,9 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Этап 6 — test_can + test_opto + test_mqs + протокол ← ТЕКУЩИЙ
|
## Этап 6 — test_can + test_opto + test_mqs + протокол ✅ ЗАВЕРШЁН
|
||||||
|
|
||||||
### 6а — Расширение протокола
|
### 6а — Расширение протокола ✅
|
||||||
|
|
||||||
**Файлы:** `protocol.h`, `protocol.c`, `cli.c`, `test_runner.c`, `PROTOCOL.md`
|
**Файлы:** `protocol.h`, `protocol.c`, `cli.c`, `test_runner.c`, `PROTOCOL.md`
|
||||||
|
|
||||||
|
|
@ -173,7 +184,7 @@
|
||||||
- Статический bool-массив `g_s_selected[REGISTRY_SIZE]` — без malloc
|
- Статический bool-массив `g_s_selected[REGISTRY_SIZE]` — без malloc
|
||||||
- `test_runner_run_selected(const char **pp_ids, size_t count)` — новая публичная функция
|
- `test_runner_run_selected(const char **pp_ids, size_t count)` — новая публичная функция
|
||||||
|
|
||||||
### 6б — test_opto.c
|
### 6б — test_opto.c ✅
|
||||||
|
|
||||||
**Файл:** `firmware/test/src/tests/test_opto.c`
|
**Файл:** `firmware/test/src/tests/test_opto.c`
|
||||||
|
|
||||||
|
|
@ -193,7 +204,7 @@
|
||||||
- FAIL при несоответствии: `detail = "<id> state mismatch: expected ACTIVE got INACTIVE"`
|
- FAIL при несоответствии: `detail = "<id> state mismatch: expected ACTIVE got INACTIVE"`
|
||||||
- Таймаут: `PROTOCOL_CONFIRM_TIMEOUT_MS` (30 с) на каждый шаг
|
- Таймаут: `PROTOCOL_CONFIRM_TIMEOUT_MS` (30 с) на каждый шаг
|
||||||
|
|
||||||
### 6в — test_can.c
|
### 6в — test_can.c ✅
|
||||||
|
|
||||||
**Файл:** `firmware/test/src/tests/test_can.c`
|
**Файл:** `firmware/test/src/tests/test_can.c`
|
||||||
|
|
||||||
|
|
@ -223,28 +234,32 @@ confirm_request("can_tx_verify")
|
||||||
- `disableSelfReception=true` — таргет не слышит свой TX, только M5 верифицирует
|
- `disableSelfReception=true` — таргет не слышит свой TX, только M5 верифицирует
|
||||||
- Init: `bsp_can_init(&cfg)` + `bsp_can_accept_all()`
|
- Init: `bsp_can_init(&cfg)` + `bsp_can_accept_all()`
|
||||||
|
|
||||||
### 6г — bsp_mqs + test_mqs.c
|
### 6г — bsp_mqs + test_mqs.c ✅
|
||||||
|
|
||||||
**Файлы:** `bsp/mqs/` (новый BSP-модуль) + `firmware/test/src/tests/test_mqs.c`
|
**Файлы:** `bsp/mqs/` + `firmware/test/src/tests/test_mqs.c`
|
||||||
|
|
||||||
**bsp_mqs:**
|
**bsp_mqs:**
|
||||||
|
|
||||||
- MQS требует стерео PCM (L+R), на плате один физический канал
|
- SAI3 + eDMA (DMA0 канал 0) + MQS периферия
|
||||||
- Буфер: всегда стерео (L == R, оба канала идентичны)
|
- Стерео PCM16 буфер (L+R идентичны), один физический выход `MQS_RIGHT`
|
||||||
- API минимальный: `bsp_mqs_init()`, `bsp_mqs_play(buf, len)`, `bsp_mqs_stop()`, `bsp_mqs_deinit()`
|
- Усилитель LM4875M управляется PWM4 SM0 через RC-фильтр и буферный ОУ LM358
|
||||||
- Реализация — на основе наработок (предоставит разработчик)
|
- API: `bsp_mqs_init/deinit`, `bsp_mqs_play/play_blocking`, `bsp_mqs_stop`,
|
||||||
|
`bsp_mqs_is_busy`, `bsp_mqs_amp_init/deinit`, `bsp_mqs_amp_set_volume`
|
||||||
|
|
||||||
**test_mqs:**
|
**test_mqs:**
|
||||||
- Захардкоженная мелодия, 3–5 секунд
|
|
||||||
- `confirm_request("mqs_tone")` → оператор слышит → OK/FAIL
|
- Мелодия ~4 с: A4 (440 Гц) + E5 (659 Гц), по 2 с каждая, целочисленная LUT-синусоида
|
||||||
- Таймаут: 15 с (аналогично display)
|
- Воспроизведение через `bsp_mqs_play()` (async) с `bsp_usb_cdc_poll()` в цикле
|
||||||
|
- `confirm_request("mqs_tone", "Do you hear a tone?", 15000)` → PASS/FAIL
|
||||||
|
- Порядок init: amp → delay 300 мс → mqs → build_melody (однократно, флаг)
|
||||||
- `critical=false`, `requires_hil=false`, `pre_confirm_prompt=NULL`
|
- `critical=false`, `requires_hil=false`, `pre_confirm_prompt=NULL`
|
||||||
|
|
||||||
### 6д — HIL pytest для firmware_test
|
### 6д — HIL pytest для firmware_test ✅
|
||||||
|
|
||||||
**Файлы:**
|
**Файлы:**
|
||||||
|
|
||||||
```
|
```
|
||||||
tools/hil/conftest.py ← новая фикстура firmware_cdc
|
tools/hil/conftest.py ← фикстура firmware_cdc
|
||||||
tools/hil/06_test_firmware_opto.py
|
tools/hil/06_test_firmware_opto.py
|
||||||
tools/hil/06_test_firmware_can.py
|
tools/hil/06_test_firmware_can.py
|
||||||
```
|
```
|
||||||
|
|
@ -257,7 +272,7 @@ def firmware_cdc(m5):
|
||||||
"""
|
"""
|
||||||
Открывает USB CDC порт firmware_test.
|
Открывает USB CDC порт firmware_test.
|
||||||
firmware_test уже прошит в Flash (не загружается pyOCD).
|
firmware_test уже прошит в Flash (не загружается pyOCD).
|
||||||
Ждёт session_start, возвращает FirmwareCdcClient.
|
Проверяет живость через ping → pong.
|
||||||
"""
|
"""
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -268,19 +283,6 @@ def firmware_cdc(m5):
|
||||||
- `confirm(id, ok)` — отправить `{"type":"confirm","id":"...","confirmed":true/false}`
|
- `confirm(id, ok)` — отправить `{"type":"confirm","id":"...","confirmed":true/false}`
|
||||||
- `run_test(id)` — запустить тест, вернуть test_result dict
|
- `run_test(id)` — запустить тест, вернуть test_result dict
|
||||||
|
|
||||||
**Сценарий `06_test_firmware_can.py`:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def test_can_rx(firmware_cdc, m5):
|
|
||||||
# Запустить тест can через firmware_test
|
|
||||||
# При confirm_request("can_rx_ready") — M5 шлёт фрейм, затем confirm
|
|
||||||
...
|
|
||||||
|
|
||||||
def test_can_tx(firmware_cdc, m5):
|
|
||||||
# При confirm_request("can_tx_verify") — M5 принимает фрейм, верифицирует
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
**Justfile:**
|
**Justfile:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -348,7 +350,7 @@ tools/shared/
|
||||||
|
|
||||||
**Режим A — Прошивка** (триггер: VID/PID 1FC9:0130 обнаружен — BootROM SDP)
|
**Режим A — Прошивка** (триггер: VID/PID 1FC9:0130 обнаружен — BootROM SDP)
|
||||||
|
|
||||||
```bash
|
```
|
||||||
┌─ Прошивка платы ─────────────────────────────────┐
|
┌─ Прошивка платы ─────────────────────────────────┐
|
||||||
│ Обнаружен BootROM (SDP режим) │
|
│ Обнаружен BootROM (SDP режим) │
|
||||||
│ │
|
│ │
|
||||||
|
|
@ -366,7 +368,7 @@ tools/shared/
|
||||||
|
|
||||||
**Режим B — Диагностика** (триггер: session_start получен по CDC)
|
**Режим B — Диагностика** (триггер: session_start получен по CDC)
|
||||||
|
|
||||||
```bash
|
```
|
||||||
┌─ Диагностика платы fw:0.1.0 ─────────────────────┐
|
┌─ Диагностика платы fw:0.1.0 ─────────────────────┐
|
||||||
│ M5StampPLC: ✓ подключён │ Плата: IMXRT1052 │
|
│ M5StampPLC: ✓ подключён │ Плата: IMXRT1052 │
|
||||||
├────────────────────────────────────────────────────┤
|
├────────────────────────────────────────────────────┤
|
||||||
|
|
@ -377,8 +379,8 @@ tools/shared/
|
||||||
│ ☑ TFT Display │ mount failed: 5 │
|
│ ☑ TFT Display │ mount failed: 5 │
|
||||||
│ ☑ Кнопки │ display ✓ PASS │
|
│ ☑ Кнопки │ display ✓ PASS │
|
||||||
│ ☑ MQS Audio │ buttons ✓ PASS │
|
│ ☑ MQS Audio │ buttons ✓ PASS │
|
||||||
│ ☑ CAN loopback [HIL] │ ... │
|
│ ☑ CAN loopback [HIL] │ mqs ✓ PASS │
|
||||||
│ ☑ Оптовходы [HIL] │ │
|
│ ☑ Оптовходы [HIL] │ ... │
|
||||||
├────────────────────────────────────────────────────┤
|
├────────────────────────────────────────────────────┤
|
||||||
│ [ Запустить выбранные ] [ Все тесты ] │
|
│ [ Запустить выбранные ] [ Все тесты ] │
|
||||||
│ ████████████████░░░░ 80% Тест: display │
|
│ ████████████████░░░░ 80% Тест: display │
|
||||||
|
|
@ -393,6 +395,7 @@ tools/shared/
|
||||||
| Тип теста | Источник confirm | Действие TUI |
|
| Тип теста | Источник confirm | Действие TUI |
|
||||||
| -------------------- | ------------------ | --------------------------------------------- |
|
| -------------------- | ------------------ | --------------------------------------------- |
|
||||||
| standalone (display) | оператор | показать prompt, кнопки OK/FAIL, countdown |
|
| standalone (display) | оператор | показать prompt, кнопки OK/FAIL, countdown |
|
||||||
|
| standalone (mqs) | оператор | показать prompt, кнопки OK/FAIL, countdown |
|
||||||
| standalone (buttons) | физическое нажатие | показать инструкцию, ждать test_result |
|
| standalone (buttons) | физическое нажатие | показать инструкцию, ждать test_result |
|
||||||
| HIL (opto, can) | оркестратор | auto: M5 action → confirm (оператор не видит) |
|
| HIL (opto, can) | оркестратор | auto: M5 action → confirm (оператор не видит) |
|
||||||
|
|
||||||
|
|
@ -432,7 +435,8 @@ just host::service-flash <bin> # прошить без TUI (для автом
|
||||||
```
|
```
|
||||||
|
|
||||||
**Перепрошивка (нужна новая версия firmware_test или production):**
|
**Перепрошивка (нужна новая версия firmware_test или production):**
|
||||||
```
|
|
||||||
|
```bash
|
||||||
1. Перемычка BOOT_MOD_1 → 3V3
|
1. Перемычка BOOT_MOD_1 → 3V3
|
||||||
2. Reset, подключить USB
|
2. Reset, подключить USB
|
||||||
3. TUI обнаружил 1FC9:0130 → Режим A
|
3. TUI обнаружил 1FC9:0130 → Режим A
|
||||||
|
|
@ -456,10 +460,9 @@ just host::service-flash <bin> # прошить без TUI (для автом
|
||||||
✅ Этап 6д HIL pytest: firmware_cdc фикстура (FirmwareCdc + firmware_cdc)
|
✅ Этап 6д HIL pytest: firmware_cdc фикстура (FirmwareCdc + firmware_cdc)
|
||||||
✅ Этап 6е HIL pytest: 06_test_firmware_opto.py
|
✅ Этап 6е HIL pytest: 06_test_firmware_opto.py
|
||||||
✅ Этап 6ж HIL pytest: 06_test_firmware_can.py
|
✅ Этап 6ж HIL pytest: 06_test_firmware_can.py
|
||||||
|
✅ Этап 6г bsp_mqs + test_mqs.c + hardware верификация
|
||||||
|
|
||||||
⬜ Этап 6г bsp_mqs + test_mqs.c ← СЛЕДУЮЩИЙ ШАГ
|
⬜ Этап 7 Provisioning (OCOTP UID + Flash-флаг) ← СЛЕДУЮЩИЙ ШАГ
|
||||||
|
|
||||||
⬜ Этап 7 Provisioning (OCOTP UID + Flash-флаг)
|
|
||||||
|
|
||||||
⬜ Этап 8а tools/production/ скелет + models + clients
|
⬜ Этап 8а tools/production/ скелет + models + clients
|
||||||
⬜ Этап 8б orchestrator + базовый Textual UI (список тестов, запуск, результаты)
|
⬜ Этап 8б orchestrator + базовый Textual UI (список тестов, запуск, результаты)
|
||||||
|
|
@ -474,12 +477,12 @@ 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)
|
||||||
```
|
```
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
|
|
||||||
|
#include "bsp/provisioning.h"
|
||||||
#include "bsp/usb_cdc.h"
|
#include "bsp/usb_cdc.h"
|
||||||
#include "protocol.h"
|
#include "protocol.h"
|
||||||
#include "test_runner.h"
|
#include "test_runner.h"
|
||||||
|
|
@ -316,6 +317,20 @@ static void handle_cmd(const char *p_line)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strcmp(cmd_name, "get_uid") == 0)
|
||||||
|
{
|
||||||
|
uint8_t uid[BSP_PROV_UID_LEN];
|
||||||
|
if (bsp_prov_read_uid(uid, sizeof(uid)) == BSP_OK)
|
||||||
|
{
|
||||||
|
protocol_send_uid_response(uid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
protocol_send_error("UID_READ_ERR");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp(cmd_name, "list_tests") == 0)
|
if (strcmp(cmd_name, "list_tests") == 0)
|
||||||
{
|
{
|
||||||
test_runner_send_list();
|
test_runner_send_list();
|
||||||
|
|
|
||||||
|
|
@ -1,206 +1,4 @@
|
||||||
/**
|
|
||||||
* @file main_mqs_test.c
|
|
||||||
* @brief Изолированный тест bsp_mqs + bsp_mqs_amp.
|
|
||||||
*
|
|
||||||
* Подменяет firmware/test/src/main.c на время отладки BSP.
|
|
||||||
* Вывод диагностики — UART (MCU-Link VCOM, 115200 бод).
|
|
||||||
*
|
|
||||||
* Два режима — переключаются комментарием в main():
|
|
||||||
* TEST_MODE_POLLING — SAI_WriteBlocking напрямую, без DMA.
|
|
||||||
* Если звук есть → SAI/MQS OK, проблема в DMA.
|
|
||||||
* Если нет → проблема в SAI/MQS/усилителе.
|
|
||||||
* TEST_MODE_DMA — bsp_mqs_play_blocking через eDMA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "board.h"
|
|
||||||
#include "bsp/led.h"
|
|
||||||
#include "bsp/mqs.h"
|
|
||||||
#include "bsp/tick.h"
|
|
||||||
#include "bsp/uart_host.h"
|
|
||||||
#include "fsl_sai.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
* Выбор режима: раскомментируй один
|
|
||||||
* ----------------------------------------------------------------------- */
|
|
||||||
//#define TEST_MODE_POLLING
|
|
||||||
#define TEST_MODE_DMA
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
* Буфер мелодии в некэшируемой секции
|
|
||||||
* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#define MQS_NOTE_SAMPLES (44100U * 2U) /* 2 с на ноту */
|
|
||||||
#define MQS_TOTAL_FRAMES (MQS_NOTE_SAMPLES * 2U) /* 2 ноты */
|
|
||||||
#define MQS_BUF_ELEMENTS (MQS_TOTAL_FRAMES * BSP_MQS_CHANNELS)
|
|
||||||
|
|
||||||
static AT_NONCACHEABLE_SECTION_ALIGN(int16_t s_melody_buf[MQS_BUF_ELEMENTS], 4U);
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
* Синусоида (целочисленная, LUT по четвертям)
|
|
||||||
* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
static int16_t sine_sample(uint32_t phase, uint32_t period)
|
|
||||||
{
|
|
||||||
static const int16_t k_lut[25] = {
|
|
||||||
0, 2998, 5944, 8788, 11479, 13970, 16212, 18162, 19784, 21043, 21910, 22369, 22404,
|
|
||||||
22005, 21170, 19898, 18199, 16084, 13567, 10670, 7416, 3831, 0, -3831, -7416,
|
|
||||||
};
|
|
||||||
uint32_t idx = (phase * 96U) / period % 96U;
|
|
||||||
uint32_t quarter = idx / 24U;
|
|
||||||
uint32_t pos = idx % 24U;
|
|
||||||
int16_t val;
|
|
||||||
switch (quarter)
|
|
||||||
{
|
|
||||||
case 0U:
|
|
||||||
val = k_lut[pos];
|
|
||||||
break;
|
|
||||||
case 1U:
|
|
||||||
val = k_lut[24U - pos];
|
|
||||||
break;
|
|
||||||
case 2U:
|
|
||||||
val = -k_lut[pos];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
val = -k_lut[24U - pos];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void build_melody(void)
|
|
||||||
{
|
|
||||||
const uint32_t k_p_a4 = 100U; /* A4 ≈ 440 Гц */
|
|
||||||
const uint32_t k_p_e5 = 67U; /* E5 ≈ 659 Гц */
|
|
||||||
uint32_t out = 0U;
|
|
||||||
|
|
||||||
for (uint32_t i = 0U; i < MQS_NOTE_SAMPLES; i++)
|
|
||||||
{
|
|
||||||
int16_t s = sine_sample(i % k_p_a4, k_p_a4);
|
|
||||||
s_melody_buf[out++] = s; /* L */
|
|
||||||
s_melody_buf[out++] = s; /* R */
|
|
||||||
}
|
|
||||||
for (uint32_t i = 0U; i < MQS_NOTE_SAMPLES; i++)
|
|
||||||
{
|
|
||||||
int16_t s = sine_sample(i % k_p_e5, k_p_e5);
|
|
||||||
s_melody_buf[out++] = s; /* L */
|
|
||||||
s_melody_buf[out++] = s; /* R */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
* Режим A: SAI polling напрямую, без DMA
|
|
||||||
*
|
|
||||||
* SAI_WriteBlocking — блокирует до записи всех байт через FIFO.
|
|
||||||
* Не требует DMA вообще. Если звук есть — SAI и MQS живые.
|
|
||||||
* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#ifdef TEST_MODE_POLLING
|
|
||||||
static void run_test(void)
|
|
||||||
{
|
|
||||||
bsp_uart_host_write_str("[5A] Bare-metal SAI polling...\r\n");
|
|
||||||
|
|
||||||
/* 1. Глушим передатчик и отключаем запросы DMA, чтобы не мешали поллингу */
|
|
||||||
SAI3->TCSR &= ~(I2S_TCSR_TE_MASK | I2S_TCSR_BCE_MASK | I2S_TCSR_FRDE_MASK);
|
|
||||||
|
|
||||||
/* 2. Сбрасываем ошибку Underrun (FEF) и чистим FIFO (FR) */
|
|
||||||
SAI3->TCSR |= I2S_TCSR_FEF_MASK | I2S_TCSR_FR_MASK;
|
|
||||||
|
|
||||||
/* 3. Предзагружаем FIFO (закинем 16 сэмплов ДО включения) */
|
|
||||||
/* Это спасет аппаратуру от мгновенного Underrun'а при старте */
|
|
||||||
for (uint32_t i = 0; i < 16; i++)
|
|
||||||
{
|
|
||||||
SAI3->TDR[0] = s_melody_buf[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 4. Включаем передатчик и генерацию Bit Clock */
|
|
||||||
SAI3->TCSR |= (I2S_TCSR_TE_MASK | I2S_TCSR_BCE_MASK);
|
|
||||||
|
|
||||||
/* 5. Качаем остальной буфер вручную */
|
|
||||||
for (uint32_t i = 16; i < MQS_BUF_ELEMENTS; i++)
|
|
||||||
{
|
|
||||||
/* Ждем, пока в FIFO появится свободное место (FWF == 1) */
|
|
||||||
while ((SAI3->TCSR & I2S_TCSR_FWF_MASK) == 0)
|
|
||||||
{
|
|
||||||
/* Если всё-таки поймали опустошение - перезапускаем TX на лету */
|
|
||||||
if (SAI3->TCSR & I2S_TCSR_FEF_MASK)
|
|
||||||
{
|
|
||||||
SAI3->TCSR |= I2S_TCSR_FEF_MASK; /* Сброс ошибки */
|
|
||||||
SAI3->TCSR |= I2S_TCSR_TE_MASK | I2S_TCSR_BCE_MASK; /* Рестарт TX */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Пишем следующий сэмпл */
|
|
||||||
SAI3->TDR[0] = s_melody_buf[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 6. Дожидаемся, пока уйдут остатки из FIFO, и выключаем */
|
|
||||||
while (SAI3->TCSR & I2S_TCSR_FWF_MASK)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
SAI3->TCSR &= ~(I2S_TCSR_TE_MASK | I2S_TCSR_BCE_MASK);
|
|
||||||
|
|
||||||
bsp_uart_host_write_str("[5A] Done.\r\n");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
* Режим B: DMA через bsp_mqs_play_blocking
|
|
||||||
* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#ifdef TEST_MODE_DMA
|
|
||||||
static void run_test(void)
|
|
||||||
{
|
|
||||||
bsp_uart_host_write_str("[5B] DMA play, ~4s...\r\n");
|
|
||||||
|
|
||||||
bsp_status_t st = bsp_mqs_play_blocking(s_melody_buf, MQS_TOTAL_FRAMES);
|
|
||||||
|
|
||||||
bsp_uart_host_write_str(st == BSP_OK ? "[5B] OK\r\n" : "[5B] FAIL\r\n");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
|
||||||
* main
|
|
||||||
* ----------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
board_hw_init();
|
|
||||||
bsp_tick_init();
|
|
||||||
bsp_led_init();
|
|
||||||
(void) bsp_uart_host_init(115200U);
|
|
||||||
|
|
||||||
bsp_led_on(LED_HEARTBEAT);
|
|
||||||
bsp_uart_host_write_str("\r\n=== MQS BSP TEST ===\r\n");
|
|
||||||
|
|
||||||
/* Шаг 1 — усилитель */
|
|
||||||
bsp_uart_host_write_str("[1] amp_init...\r\n");
|
|
||||||
bsp_status_t st = bsp_mqs_amp_init();
|
|
||||||
bsp_uart_host_write_str(st == BSP_OK ? "[1] OK\r\n" : "[1] FAIL\r\n");
|
|
||||||
|
|
||||||
/* Шаг 2 — SAI + DMA + MQS */
|
|
||||||
bsp_uart_host_write_str("[2] mqs_init...\r\n");
|
|
||||||
st = bsp_mqs_init();
|
|
||||||
bsp_uart_host_write_str(st == BSP_OK ? "[2] OK\r\n" : "[2] FAIL\r\n");
|
|
||||||
|
|
||||||
/* Шаг 3 — генерация буфера */
|
|
||||||
bsp_uart_host_write_str("[3] build_melody...\r\n");
|
|
||||||
build_melody();
|
|
||||||
bsp_uart_host_write_str("[3] done.\r\n");
|
|
||||||
|
|
||||||
/* Основной цикл */
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
bsp_led_toggle(LED_HEARTBEAT);
|
|
||||||
run_test();
|
|
||||||
bsp_uart_host_write_str("[pause] 3s\r\n");
|
|
||||||
bsp_delay(3000U);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#if 0
|
|
||||||
/**
|
/**
|
||||||
* @file main.c
|
* @file main.c
|
||||||
* @brief firmware_test — точка входа.
|
* @brief firmware_test — точка входа.
|
||||||
|
|
@ -220,7 +18,6 @@ int main(void)
|
||||||
* 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"
|
||||||
|
|
@ -232,9 +29,8 @@ int main(void)
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
|
@ -267,4 +63,3 @@ int main(void)
|
||||||
test_runner_process();
|
test_runner_process();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
@ -145,4 +145,16 @@ void protocol_send_error(const char *p_code)
|
||||||
char buf[PROTO_BUF_SIZE];
|
char buf[PROTO_BUF_SIZE];
|
||||||
(void) snprintf(buf, sizeof(buf), "{\"ok\":false,\"error\":\"%s\"}\n", p_code);
|
(void) snprintf(buf, sizeof(buf), "{\"ok\":false,\"error\":\"%s\"}\n", p_code);
|
||||||
cli_send(buf);
|
cli_send(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void protocol_send_uid_response(const uint8_t *p_uid)
|
||||||
|
{
|
||||||
|
char buf[PROTO_BUF_SIZE];
|
||||||
|
(void) snprintf(buf, sizeof(buf),
|
||||||
|
"{\"type\":\"uid_response\","
|
||||||
|
"\"uid\":\"%02X%02X%02X%02X%02X%02X%02X%02X\"}\n",
|
||||||
|
(unsigned int) p_uid[0U], (unsigned int) p_uid[1U], (unsigned int) p_uid[2U],
|
||||||
|
(unsigned int) p_uid[3U], (unsigned int) p_uid[4U], (unsigned int) p_uid[5U],
|
||||||
|
(unsigned int) p_uid[6U], (unsigned int) p_uid[7U]);
|
||||||
|
cli_send(buf);
|
||||||
}
|
}
|
||||||
|
|
@ -91,4 +91,13 @@ void protocol_send_test_list(const test_module_t *const *p_pp_registry, size_t c
|
||||||
*/
|
*/
|
||||||
void protocol_send_error(const char *p_code);
|
void protocol_send_error(const char *p_code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Отправить ответ на команду get_uid.
|
||||||
|
*
|
||||||
|
* Формат: {"type":"uid_response","uid":"AABBCCDDEEFF0011"}
|
||||||
|
* uid — 16 hex-символов (8 байт big-endian, без разделителей).+ *
|
||||||
|
* @param[in] p_uid Буфер с UID длиной BSP_PROV_UID_LEN байт.
|
||||||
|
*/
|
||||||
|
void protocol_send_uid_response(const uint8_t *p_uid);
|
||||||
|
|
||||||
#endif /* PROTOCOL_H_ */
|
#endif /* PROTOCOL_H_ */
|
||||||
|
|
@ -215,7 +215,7 @@ sequenceDiagram
|
||||||
|
|
||||||
**SKIP — оператор нажал Cancel:**
|
**SKIP — оператор нажал Cancel:**
|
||||||
|
|
||||||
```
|
```bash
|
||||||
→ {"type":"confirm","id":"usd","confirmed":false}
|
→ {"type":"confirm","id":"usd","confirmed":false}
|
||||||
← {"type":"test_result","id":"usd","status":"skip","ms":0,"detail":"operator declined"}
|
← {"type":"test_result","id":"usd","status":"skip","ms":0,"detail":"operator declined"}
|
||||||
```
|
```
|
||||||
|
|
@ -369,6 +369,59 @@ sequenceDiagram
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## MQS аудио-выход — слуховая проверка
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
| ---------------- | ----------------------------------------- |
|
||||||
|
| ID | `mqs` |
|
||||||
|
| Критичный | ❌ Нет |
|
||||||
|
| HIL | ❌ Нет |
|
||||||
|
| Тип | Interactive (in-run confirm) |
|
||||||
|
| Время выполнения | ~4 с воспроизведение + до 15 с на confirm |
|
||||||
|
|
||||||
|
Тест воспроизводит мелодию (~4 с: нота A4 затем E5) через MQS-выход
|
||||||
|
(`MQS_RIGHT`, `GPIO_AD_B0_04`) и усилитель LM4875M.
|
||||||
|
Оператор подтверждает слышимость тона.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant H as HOST
|
||||||
|
participant T as TARGET
|
||||||
|
H->>T: run("mqs")
|
||||||
|
T->>H: test_begin
|
||||||
|
Note over T: ~4 с воспроизведение A4 + E5
|
||||||
|
T->>H: confirm_request("mqs_tone", timeout=15s)
|
||||||
|
Note over H: оператор слышит тон
|
||||||
|
H->>T: confirm("mqs_tone", true)
|
||||||
|
T->>H: test_result: pass/fail
|
||||||
|
```
|
||||||
|
|
||||||
|
**PASS:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
→ {"type":"cmd","cmd":"run","id":"mqs"}
|
||||||
|
← {"type":"test_begin","id":"mqs","name":"MQS Audio Out","critical":false}
|
||||||
|
← {"type":"confirm_request","id":"mqs_tone","prompt":"Do you hear a tone?","timeout_ms":15000}
|
||||||
|
→ {"type":"confirm","id":"mqs_tone","confirmed":true}
|
||||||
|
← {"type":"test_result","id":"mqs","status":"pass","ms":19240,"detail":""}
|
||||||
|
```
|
||||||
|
|
||||||
|
**FAIL — оператор не слышит или истёк таймаут:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
← {"type":"test_result","id":"mqs","status":"fail","ms":15001,
|
||||||
|
"detail":"operator: no sound"}
|
||||||
|
```
|
||||||
|
|
||||||
|
**FAIL — ошибка воспроизведения (DMA/SAI):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
← {"type":"test_result","id":"mqs","status":"fail","ms":1,
|
||||||
|
"detail":"mqs play error"}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Запуск всего набора (run_all)
|
## Запуск всего набора (run_all)
|
||||||
|
|
||||||
Тесты запускаются строго в порядке реестра. При провале критичного теста
|
Тесты запускаются строго в порядке реестра. При провале критичного теста
|
||||||
|
|
@ -392,7 +445,17 @@ sequenceDiagram
|
||||||
← {"type":"confirm_request","id":"btn1_press","prompt":"Press Test_But_1","timeout_ms":10000}
|
← {"type":"confirm_request","id":"btn1_press","prompt":"Press Test_But_1","timeout_ms":10000}
|
||||||
...
|
...
|
||||||
← {"type":"test_result","id":"buttons","status":"pass","ms":6200,"detail":""}
|
← {"type":"test_result","id":"buttons","status":"pass","ms":6200,"detail":""}
|
||||||
← {"type":"summary","passed":5,"failed":0,"skipped":0,"overall":"pass"}
|
← {"type":"test_begin","id":"mqs","name":"MQS Audio Out","critical":false}
|
||||||
|
← {"type":"confirm_request","id":"mqs_tone","prompt":"Do you hear a tone?","timeout_ms":15000}
|
||||||
|
... оператор слышит и подтверждает ...
|
||||||
|
← {"type":"test_result","id":"mqs","status":"pass","ms":19240,"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":"test_begin","id":"can","name":"CAN loopback","critical":false}
|
||||||
|
...confirm цикл 2 HIL шагов...
|
||||||
|
← {"type":"test_result","id":"can","status":"pass","ms":1240,"detail":""}
|
||||||
|
← {"type":"summary","passed":8,"failed":0,"skipped":0,"overall":"pass"}
|
||||||
```
|
```
|
||||||
|
|
||||||
**SKIP-каскад при critical fail:**
|
**SKIP-каскад при critical fail:**
|
||||||
|
|
@ -404,7 +467,7 @@ sequenceDiagram
|
||||||
← {"type":"test_begin","id":"usd",...}
|
← {"type":"test_begin","id":"usd",...}
|
||||||
← {"type":"test_result","id":"usd","status":"skip","ms":0,"detail":"critical test failed"}
|
← {"type":"test_result","id":"usd","status":"skip","ms":0,"detail":"critical test failed"}
|
||||||
...
|
...
|
||||||
← {"type":"summary","passed":0,"failed":1,"skipped":4,"overall":"fail"}
|
← {"type":"summary","passed":0,"failed":1,"skipped":7,"overall":"fail"}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -418,8 +481,9 @@ sequenceDiagram
|
||||||
| 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) |
|
| 6 | `mqs` | MQS Audio Out | ❌ | ❌ | Interactive (in-run confirm) |
|
||||||
| 7 | `can` | CAN loopback | ❌ | ✅ | HIL (M5StampPLC CAN) |
|
| 7 | `opto` | Opto Inputs | ❌ | ✅ | HIL (M5StampPLC RLY2/3/4) |
|
||||||
|
| 8 | `can` | CAN loopback | ❌ | ✅ | HIL (M5StampPLC CAN) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -442,6 +506,8 @@ 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 с |
|
||||||
|
| `mqs` | `mqs play error` | SAI3/DMA не запустился |
|
||||||
|
| `mqs` | `operator: no sound` | Нет звука / усилитель не работает |
|
||||||
| `opto` | `<id> mismatch: expected ACTIVE got INACTIVE` | Реле не переключило оптовход |
|
| `opto` | `<id> mismatch: expected ACTIVE got INACTIVE` | Реле не переключило оптовход |
|
||||||
| `can` | `can_rx_ready: no frame received` | M5 не отправил фрейм / CAN не подключён |
|
| `can` | `can_rx_ready: no frame received` | M5 не отправил фрейм / CAN не подключён |
|
||||||
| `can` | `rx id mismatch: expected 0x100 got 0x...` | Неверный ID принятого фрейма |
|
| `can` | `rx id mismatch: expected 0x100 got 0x...` | Неверный ID принятого фрейма |
|
||||||
|
|
@ -464,6 +530,7 @@ sequenceDiagram
|
||||||
{"id":"usd","name":"microSD (SDIO)","critical":false,"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":"display","name":"TFT Display RGB888","critical":false,"requires_hil":false},
|
||||||
{"id":"buttons","name":"Test Buttons","critical":false,"requires_hil":false},
|
{"id":"buttons","name":"Test Buttons","critical":false,"requires_hil":false},
|
||||||
|
{"id":"mqs","name":"MQS Audio Out","critical":false,"requires_hil":false},
|
||||||
{"id":"opto","name":"Opto Inputs","critical":false,"requires_hil":true},
|
{"id":"opto","name":"Opto Inputs","critical":false,"requires_hil":true},
|
||||||
{"id":"can","name":"CAN loopback","critical":false,"requires_hil":true}
|
{"id":"can","name":"CAN loopback","critical":false,"requires_hil":true}
|
||||||
]}
|
]}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
/**
|
/**
|
||||||
* @file test_mqs.c
|
* @file test_mqs.c
|
||||||
* @brief Тест MQS: воспроизведение тестового тона через SAI1 + eDMA + MQS.
|
* @brief Тест MQS: воспроизведение тестового тона через SAI3 + eDMA + MQS.
|
||||||
*
|
*
|
||||||
* Алгоритм:
|
* Алгоритм:
|
||||||
* 1. init() — bsp_mqs_init(), генерация мелодии в s_melody_buf.
|
* 1. init() — bsp_mqs_amp_init(), задержка 300 мс, bsp_mqs_init(),
|
||||||
|
* генерация мелодии в s_melody_buf.
|
||||||
* 2. run() — воспроизвести мелодию (~4 с) с USB keepalive в цикле,
|
* 2. run() — воспроизвести мелодию (~4 с) с USB keepalive в цикле,
|
||||||
* затем confirm_request("mqs_tone") — оператор подтверждает.
|
* затем confirm_request("mqs_tone") — оператор подтверждает.
|
||||||
* 3. deinit() — bsp_mqs_stop(), bsp_mqs_deinit().
|
* 3. deinit() — bsp_mqs_stop(), bsp_mqs_amp_deinit(), bsp_mqs_deinit().
|
||||||
*
|
*
|
||||||
* Мелодия: стерео PCM16 44100 Гц, две ноты (A4/E5) по 2 с каждая.
|
* Мелодия: стерео PCM16 44100 Гц, две ноты (A4/E5) по 2 с каждая.
|
||||||
* Буфер — статический глобальный в некэшируемой секции (OCRAM NonCacheable).
|
* Буфер — статический глобальный в некэшируемой секции (OCRAM NonCacheable).
|
||||||
* L == R (монофонический выход на плате).
|
* L == R (монофонический выход на плате).
|
||||||
*
|
*
|
||||||
* Протокол:
|
* Протокол:
|
||||||
* ← confirm_request("mqs_tone", "Слышен звуковой сигнал?", 15000)
|
* ← confirm_request("mqs_tone", "Do you hear a tone?", 15000)
|
||||||
* → confirm("mqs_tone", true/false)
|
* → confirm("mqs_tone", true/false)
|
||||||
* ← test_result pass/fail/skip
|
* ← test_result pass/fail/skip
|
||||||
*/
|
*/
|
||||||
|
|
@ -159,9 +160,17 @@ static bsp_status_t mqs_play_with_poll(void)
|
||||||
|
|
||||||
static void test_mqs_init(void)
|
static void test_mqs_init(void)
|
||||||
{
|
{
|
||||||
mqs_build_melody();
|
/*
|
||||||
(void) bsp_mqs_init();
|
* Порядок инициализации критичен (см. bsp/mqs/README.md):
|
||||||
|
* 1. amp_init — запускает PWM4, начинает заряд C103/C105 LM4875M.
|
||||||
|
* 2. bsp_delay(300) — ждём заряда фильтрующих конденсаторов усилителя.
|
||||||
|
* 3. mqs_init — запускает SAI3 + DMA.
|
||||||
|
* 4. mqs_build_melody — генерация буфера (однократно, флаг g_s_melody_ready).
|
||||||
|
*/
|
||||||
(void) bsp_mqs_amp_init();
|
(void) bsp_mqs_amp_init();
|
||||||
|
bsp_delay(300U);
|
||||||
|
(void) bsp_mqs_init();
|
||||||
|
mqs_build_melody();
|
||||||
}
|
}
|
||||||
|
|
||||||
static test_result_t test_mqs_run(void)
|
static test_result_t test_mqs_run(void)
|
||||||
|
|
@ -175,22 +184,20 @@ static test_result_t test_mqs_run(void)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Запрашиваем подтверждение оператора.
|
/*
|
||||||
* API: bool test_runner_wait_confirm(const confirm_params_t *p_params)
|
* Запрашиваем подтверждение оператора.
|
||||||
* Возвращает true если оператор подтвердил, false при отказе или таймауте.
|
* Таймаут 15 с — аналогично display-тесту (закрытое решение, Этап 5).
|
||||||
* Таймаут детектируется отдельно через флаг в confirm_params_t. */
|
*/
|
||||||
const confirm_params_t PARAMS = {
|
const confirm_params_t K_PARAMS = {
|
||||||
.id = "mqs_tone",
|
.id = "mqs_tone",
|
||||||
.prompt = "Have you heard a tone?",
|
.prompt = "Do you hear a tone?",
|
||||||
.timeout_ms = 15000U,
|
.timeout_ms = 15000U,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool confirmed = test_runner_wait_confirm(&PARAMS);
|
bool confirmed = test_runner_wait_confirm(&K_PARAMS);
|
||||||
|
|
||||||
if (!confirmed)
|
if (!confirmed)
|
||||||
{
|
{
|
||||||
/* Различаем таймаут и явный отказ через поле last_confirm_timed_out
|
|
||||||
* если оно есть в API, иначе унифицируем как fail. */
|
|
||||||
return (test_result_t){
|
return (test_result_t){
|
||||||
.status = TEST_STATUS_FAIL,
|
.status = TEST_STATUS_FAIL,
|
||||||
.detail = "operator: no sound",
|
.detail = "operator: no sound",
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,6 @@ add_sdk_driver(sai_edma fsl_sai_edma.c)
|
||||||
add_sdk_driver(edma fsl_edma.c)
|
add_sdk_driver(edma fsl_edma.c)
|
||||||
add_sdk_driver(dmamux fsl_dmamux.c)
|
add_sdk_driver(dmamux fsl_dmamux.c)
|
||||||
add_sdk_driver(xbara fsl_xbara.c)
|
add_sdk_driver(xbara fsl_xbara.c)
|
||||||
add_sdk_driver(xbarb fsl_xbarb.c)
|
|
||||||
|
|
||||||
# Драйверы которые зависят от clock
|
# Драйверы которые зависят от clock
|
||||||
target_link_libraries(sdk_lpuart PUBLIC sdk_clock)
|
target_link_libraries(sdk_lpuart PUBLIC sdk_clock)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue