# 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(sd)
|
||||
add_subdirectory(mqs)
|
||||
add_subdirectory(provisioning)
|
||||
|
||||
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
||||
target_compile_options(bsp_board PRIVATE -w)
|
||||
|
|
|
|||
|
|
@ -45,12 +45,15 @@ bsp/
|
|||
│ └── mocks/ # fff-заглушки для host-тестов
|
||||
├── opto/ # bsp_opto — оптоизолированные входы PS2801-4
|
||||
├── can/ # bsp_can — FlexCAN2 (трансивер SN65HVD230D)
|
||||
│ └── mocks/ # fff-заглушки для host-тестов
|
||||
├── button/ # bsp_button — тактовые кнопки SWT6x6 с debounce
|
||||
├── display/ # bsp_display — TFT-дисплей
|
||||
├── usb_cdc/ # bsp_usb_cdc — USB CDC ACM
|
||||
├── sdram/ # bsp_sdram — внешний SDRAM через SEMC
|
||||
├── 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 |
|
||||
| ---------------- | ------------- | -------------------------------------------- |
|
||||
| `bsp_led` | `led/` | [led/README.md](led/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_opto` | `opto/` | [opto/README.md](opto/README.md) |
|
||||
| `bsp_can` | `can/` | [can/README.md](can/README.md) |
|
||||
| `bsp_button` | `button/` | [button/README.md](button/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_sdram` | `sdram/` | [sdram/README.md](sdram/README.md) |
|
||||
| `bsp_qspi_flash` | `qspi_flash/` | [qspi_flash/README.md](qspi_flash/README.md) |
|
||||
| `bsp_sd` | `sd/` | [sd/README.md](sd/README.md) |
|
||||
| Библиотека | Модуль | README |
|
||||
| ------------------ | --------------- | ------------------------------------------------ |
|
||||
| `bsp_led` | `led/` | [led/README.md](led/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_opto` | `opto/` | [opto/README.md](opto/README.md) |
|
||||
| `bsp_can` | `can/` | [can/README.md](can/README.md) |
|
||||
| `bsp_button` | `button/` | [button/README.md](button/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_sdram` | `sdram/` | [sdram/README.md](sdram/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_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` — запуск подмножества тестов
|
||||
|
||||
Запускает тесты по списку ID. Порядок выполнения — по реестру таргета,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ target_link_libraries(
|
|||
bsp_tick
|
||||
bsp_boot_xip
|
||||
bsp_usb_cdc
|
||||
bsp_provisioning
|
||||
bsp_sdram
|
||||
bsp_qspi_flash
|
||||
bsp_uart_host
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 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_opto | ✅ | Этап 6е, `06_test_firmware_opto.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 |
|
||||
| TUI сервисного инженера | ⬜ | Этап 8 |
|
||||
|
||||
|
|
@ -44,16 +44,16 @@
|
|||
|
||||
## Матрица тестов — итоговая
|
||||
|
||||
| ID | Название | Critical | HIL | Тип | BSP | Статус |
|
||||
| --------- | ------------------ | -------- | --- | ----------- | ------------------- | ------ |
|
||||
| `sdram` | SDRAM 32 MB | ✅ | ❌ | self | `bsp_sdram` ✅ | ✅ |
|
||||
| `qspi` | QSPI Flash W25Qxx | ✅ | ❌ | self | `bsp_qspi_flash` ✅ | ✅ |
|
||||
| `usd` | microSD (SDIO) | ❌ | ❌ | interactive | `bsp_sd` ✅ | ✅ |
|
||||
| `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` ✅ | ✅ |
|
||||
| ID | Название | Critical | HIL | Тип | BSP | Статус |
|
||||
| --------- | ------------------ | -------- | --- | ----------- | ------------------ | ------ |
|
||||
| `sdram` | SDRAM 32 MB | ✅ | ❌ | self | `bsp_sdram` ✅ | ✅ |
|
||||
| `qspi` | QSPI Flash W25Qxx | ✅ | ❌ | self | `bsp_qspi_flash` ✅ | ✅ |
|
||||
| `usd` | microSD (SDIO) | ❌ | ❌ | interactive | `bsp_sd` ✅ | ✅ |
|
||||
| `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` ✅ | ✅ |
|
||||
|
||||
**Убранные тесты (закрытые решения):**
|
||||
|
||||
|
|
@ -93,8 +93,19 @@
|
|||
- **Фильтрация HIL на стороне TUI:** таргет не фильтрует по `requires_hil`.
|
||||
- **MQS стерео:** MQS MIMXRT1052 требует стерео PCM-буфер. На плате выведен
|
||||
один канал. Буфер всегда стерео (L+R идентичны).
|
||||
- **MQS тест:** захардкоженная мелодия 3–5 с, `confirm_request("mqs_tone")`,
|
||||
оператор слышит → OK/FAIL. `critical=false`, `requires_hil=false`.
|
||||
- **MQS тест:** захардкоженная мелодия ~4 с (A4 + E5, по 2 с каждая),
|
||||
`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 на
|
||||
`CCM_CCGR5_CG12` (LPUART clock gate). После `bsp_usb_cdc_init()` gate
|
||||
может быть закрыт → 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`
|
||||
|
||||
|
|
@ -173,7 +184,7 @@
|
|||
- Статический bool-массив `g_s_selected[REGISTRY_SIZE]` — без malloc
|
||||
- `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`
|
||||
|
||||
|
|
@ -193,7 +204,7 @@
|
|||
- FAIL при несоответствии: `detail = "<id> state mismatch: expected ACTIVE got INACTIVE"`
|
||||
- Таймаут: `PROTOCOL_CONFIRM_TIMEOUT_MS` (30 с) на каждый шаг
|
||||
|
||||
### 6в — test_can.c
|
||||
### 6в — test_can.c ✅
|
||||
|
||||
**Файл:** `firmware/test/src/tests/test_can.c`
|
||||
|
||||
|
|
@ -223,28 +234,32 @@ confirm_request("can_tx_verify")
|
|||
- `disableSelfReception=true` — таргет не слышит свой TX, только M5 верифицирует
|
||||
- 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:**
|
||||
|
||||
- MQS требует стерео PCM (L+R), на плате один физический канал
|
||||
- Буфер: всегда стерео (L == R, оба канала идентичны)
|
||||
- API минимальный: `bsp_mqs_init()`, `bsp_mqs_play(buf, len)`, `bsp_mqs_stop()`, `bsp_mqs_deinit()`
|
||||
- Реализация — на основе наработок (предоставит разработчик)
|
||||
- SAI3 + eDMA (DMA0 канал 0) + MQS периферия
|
||||
- Стерео PCM16 буфер (L+R идентичны), один физический выход `MQS_RIGHT`
|
||||
- Усилитель 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:**
|
||||
- Захардкоженная мелодия, 3–5 секунд
|
||||
- `confirm_request("mqs_tone")` → оператор слышит → OK/FAIL
|
||||
- Таймаут: 15 с (аналогично display)
|
||||
|
||||
- Мелодия ~4 с: A4 (440 Гц) + E5 (659 Гц), по 2 с каждая, целочисленная LUT-синусоида
|
||||
- Воспроизведение через `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`
|
||||
|
||||
### 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_can.py
|
||||
```
|
||||
|
|
@ -257,7 +272,7 @@ def firmware_cdc(m5):
|
|||
"""
|
||||
Открывает USB CDC порт firmware_test.
|
||||
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}`
|
||||
- `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:**
|
||||
|
||||
```bash
|
||||
|
|
@ -348,7 +350,7 @@ tools/shared/
|
|||
|
||||
**Режим A — Прошивка** (триггер: VID/PID 1FC9:0130 обнаружен — BootROM SDP)
|
||||
|
||||
```bash
|
||||
```
|
||||
┌─ Прошивка платы ─────────────────────────────────┐
|
||||
│ Обнаружен BootROM (SDP режим) │
|
||||
│ │
|
||||
|
|
@ -366,7 +368,7 @@ tools/shared/
|
|||
|
||||
**Режим B — Диагностика** (триггер: session_start получен по CDC)
|
||||
|
||||
```bash
|
||||
```
|
||||
┌─ Диагностика платы fw:0.1.0 ─────────────────────┐
|
||||
│ M5StampPLC: ✓ подключён │ Плата: IMXRT1052 │
|
||||
├────────────────────────────────────────────────────┤
|
||||
|
|
@ -377,8 +379,8 @@ tools/shared/
|
|||
│ ☑ TFT Display │ mount failed: 5 │
|
||||
│ ☑ Кнопки │ display ✓ PASS │
|
||||
│ ☑ MQS Audio │ buttons ✓ PASS │
|
||||
│ ☑ CAN loopback [HIL] │ ... │
|
||||
│ ☑ Оптовходы [HIL] │ │
|
||||
│ ☑ CAN loopback [HIL] │ mqs ✓ PASS │
|
||||
│ ☑ Оптовходы [HIL] │ ... │
|
||||
├────────────────────────────────────────────────────┤
|
||||
│ [ Запустить выбранные ] [ Все тесты ] │
|
||||
│ ████████████████░░░░ 80% Тест: display │
|
||||
|
|
@ -393,6 +395,7 @@ tools/shared/
|
|||
| Тип теста | Источник confirm | Действие TUI |
|
||||
| -------------------- | ------------------ | --------------------------------------------- |
|
||||
| standalone (display) | оператор | показать prompt, кнопки OK/FAIL, countdown |
|
||||
| standalone (mqs) | оператор | показать prompt, кнопки OK/FAIL, countdown |
|
||||
| standalone (buttons) | физическое нажатие | показать инструкцию, ждать test_result |
|
||||
| HIL (opto, can) | оркестратор | auto: M5 action → confirm (оператор не видит) |
|
||||
|
||||
|
|
@ -432,7 +435,8 @@ just host::service-flash <bin> # прошить без TUI (для автом
|
|||
```
|
||||
|
||||
**Перепрошивка (нужна новая версия firmware_test или production):**
|
||||
```
|
||||
|
||||
```bash
|
||||
1. Перемычка BOOT_MOD_1 → 3V3
|
||||
2. Reset, подключить USB
|
||||
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: 06_test_firmware_opto.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б 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)
|
||||
↓
|
||||
⬜ 7 (provisioning)
|
||||
↓
|
||||
⬜ 8 (TUI)
|
||||
```
|
||||
```
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "cli.h"
|
||||
|
||||
#include "bsp/provisioning.h"
|
||||
#include "bsp/usb_cdc.h"
|
||||
#include "protocol.h"
|
||||
#include "test_runner.h"
|
||||
|
|
@ -316,6 +317,20 @@ static void handle_cmd(const char *p_line)
|
|||
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)
|
||||
{
|
||||
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
|
||||
* @brief firmware_test — точка входа.
|
||||
|
|
@ -220,7 +18,6 @@ int main(void)
|
|||
* 8. Главный цикл — poll + cli_process
|
||||
*/
|
||||
#include "board.h"
|
||||
#include "bsp/can.h"
|
||||
#include "bsp/led.h"
|
||||
#include "bsp/tick.h"
|
||||
#include "bsp/usb_cdc.h"
|
||||
|
|
@ -232,9 +29,8 @@ int main(void)
|
|||
|
||||
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 };
|
||||
const uint32_t CONNECT_BLINK_MS = 200U;
|
||||
const uint32_t ERROR_BLINK_MS = 250;
|
||||
|
||||
board_hw_init();
|
||||
|
||||
|
|
@ -267,4 +63,3 @@ int main(void)
|
|||
test_runner_process();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -145,4 +145,16 @@ void protocol_send_error(const char *p_code)
|
|||
char buf[PROTO_BUF_SIZE];
|
||||
(void) snprintf(buf, sizeof(buf), "{\"ok\":false,\"error\":\"%s\"}\n", p_code);
|
||||
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);
|
||||
|
||||
/**
|
||||
* @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_ */
|
||||
|
|
@ -215,7 +215,7 @@ sequenceDiagram
|
|||
|
||||
**SKIP — оператор нажал Cancel:**
|
||||
|
||||
```
|
||||
```bash
|
||||
→ {"type":"confirm","id":"usd","confirmed":false}
|
||||
← {"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)
|
||||
|
||||
Тесты запускаются строго в порядке реестра. При провале критичного теста
|
||||
|
|
@ -392,7 +445,17 @@ sequenceDiagram
|
|||
← {"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":"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:**
|
||||
|
|
@ -404,7 +467,7 @@ sequenceDiagram
|
|||
← {"type":"test_begin","id":"usd",...}
|
||||
← {"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) |
|
||||
| 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) |
|
||||
| 6 | `mqs` | MQS Audio Out | ❌ | ❌ | Interactive (in-run confirm) |
|
||||
| 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 с |
|
||||
| `buttons` | `btn1_press timeout` | Test_But_1 не нажата за 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` | Реле не переключило оптовход |
|
||||
| `can` | `can_rx_ready: no frame received` | M5 не отправил фрейм / CAN не подключён |
|
||||
| `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":"display","name":"TFT Display RGB888","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":"can","name":"CAN loopback","critical":false,"requires_hil":true}
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
/**
|
||||
* @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 в цикле,
|
||||
* затем 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 с каждая.
|
||||
* Буфер — статический глобальный в некэшируемой секции (OCRAM NonCacheable).
|
||||
* L == R (монофонический выход на плате).
|
||||
*
|
||||
* Протокол:
|
||||
* ← confirm_request("mqs_tone", "Слышен звуковой сигнал?", 15000)
|
||||
* ← confirm_request("mqs_tone", "Do you hear a tone?", 15000)
|
||||
* → confirm("mqs_tone", true/false)
|
||||
* ← test_result pass/fail/skip
|
||||
*/
|
||||
|
|
@ -159,9 +160,17 @@ static bsp_status_t mqs_play_with_poll(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();
|
||||
bsp_delay(300U);
|
||||
(void) bsp_mqs_init();
|
||||
mqs_build_melody();
|
||||
}
|
||||
|
||||
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 при отказе или таймауте.
|
||||
* Таймаут детектируется отдельно через флаг в confirm_params_t. */
|
||||
const confirm_params_t PARAMS = {
|
||||
/*
|
||||
* Запрашиваем подтверждение оператора.
|
||||
* Таймаут 15 с — аналогично display-тесту (закрытое решение, Этап 5).
|
||||
*/
|
||||
const confirm_params_t K_PARAMS = {
|
||||
.id = "mqs_tone",
|
||||
.prompt = "Have you heard a tone?",
|
||||
.prompt = "Do you hear a tone?",
|
||||
.timeout_ms = 15000U,
|
||||
};
|
||||
|
||||
bool confirmed = test_runner_wait_confirm(&PARAMS);
|
||||
bool confirmed = test_runner_wait_confirm(&K_PARAMS);
|
||||
|
||||
if (!confirmed)
|
||||
{
|
||||
/* Различаем таймаут и явный отказ через поле last_confirm_timed_out
|
||||
* если оно есть в API, иначе унифицируем как fail. */
|
||||
return (test_result_t){
|
||||
.status = TEST_STATUS_FAIL,
|
||||
.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(dmamux fsl_dmamux.c)
|
||||
add_sdk_driver(xbara fsl_xbara.c)
|
||||
add_sdk_driver(xbarb fsl_xbarb.c)
|
||||
|
||||
# Драйверы которые зависят от clock
|
||||
target_link_libraries(sdk_lpuart PUBLIC sdk_clock)
|
||||
|
|
|
|||
Loading…
Reference in a new issue