# 5
- добавлен модуль bsp/uart_host + хост-тесты с моками
This commit is contained in:
parent
96be9ce023
commit
26d5df362b
18 changed files with 1183 additions and 11 deletions
|
|
@ -130,7 +130,8 @@
|
|||
"targets": [
|
||||
"test_bsp_led",
|
||||
"test_ring_buffer",
|
||||
"test_timeout_pattern"
|
||||
"test_timeout_pattern",
|
||||
"uart_host_mock_example"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -140,7 +141,8 @@
|
|||
"targets": [
|
||||
"test_bsp_led",
|
||||
"test_ring_buffer",
|
||||
"test_timeout_pattern"
|
||||
"test_timeout_pattern",
|
||||
"uart_host_mock_example"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -162,8 +162,8 @@ just build::build-firmware-test-debug # сборка firmware для тарге
|
|||
just build::hab-firmware-test-debug # подготовка HAB-образа
|
||||
|
||||
# На хосте (вне контейнера):
|
||||
just flash # прошивка через USB ROM
|
||||
just flash ... # прошивка через USB ROM
|
||||
```
|
||||
|
||||
> Подробнее о прошивке — [HOW_TO_FLASH.md](HOW_TO_FLASH.md)
|
||||
> Подробнее о прошивке — [HOW_TO_FLASH.md](docs/HOW_TO_FLASH.md)
|
||||
> Подробнее об окружении разработки — [docs/DEV_ARCH.md](docs/DEV_ARCH.md)
|
||||
|
|
|
|||
149
TODO-HIL.md
Normal file
149
TODO-HIL.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# HIL‑архитектура и окружение: итоговые решения
|
||||
|
||||
## 1. Общий контекст репозитория
|
||||
|
||||
- Монорепозиторий для MIMXRT1052 уже содержит:
|
||||
- три firmware‑проекта: `firmware/test`, `firmware/bootloader`, `firmware/tft_app`;
|
||||
- BSP, vendored SDK, Unity + fff, SEGGER RTT;
|
||||
- devcontainer, CMakePresets, just‑модули, двухуровневое тестирование (host + target).
|
||||
- Цель: добавить HIL‑контур так, чтобы он органично встроился в существующую архитектуру и CI/CD, без резкого усложнения.
|
||||
|
||||
---
|
||||
|
||||
## 2. Выбор инструментов для HIL‑тестов
|
||||
|
||||
### 2.1. Стек тестирования
|
||||
|
||||
Принята связка:
|
||||
|
||||
- **Host**: Python + pytest
|
||||
- оркестрация тестов и стенда;
|
||||
- фикстуры для управления питанием, прошивкой, реле, CAN/UART;
|
||||
- интеграция с CI, маркировка тестов, отчёты.
|
||||
|
||||
- **Target (DUT)**: Unity
|
||||
- тесты на C, запускаемые на RT1052;
|
||||
- вывод результатов по UART (или через RTT при необходимости);
|
||||
- тестовая прошивка может быть отдельным firmware‑проектом, как уже сделано с `firmware/test`.
|
||||
|
||||
- **Промежуточный HIL‑контроллер**: M5Stamp PLC
|
||||
- принимает команды от хоста;
|
||||
- управляет CAN, UART, реле, опто‑входами и прочей обвязкой DUT;
|
||||
- выполняет только примитивные операции (send frame, toggle relay, read input, reset DUT), вся логика сценариев — в pytest.
|
||||
|
||||
### 2.2. Почему так
|
||||
|
||||
- Unity уже используется в проекте — нет смысла вводить второй C‑фреймворк тестирования.
|
||||
- Python + pytest хорошо подходит для оркестрации внешних устройств (HIL), даёт удобные фикстуры, параметризацию и отчётность.
|
||||
- Разделение обязанностей:
|
||||
- DUT отвечает за внутреннюю логику и инварианты;
|
||||
- HIL‑контроллер — за физические воздействия/измерения;
|
||||
- pytest — за сценарии и проверки.
|
||||
|
||||
---
|
||||
|
||||
## 3. Выбор платформы и стека для HIL‑контроллера
|
||||
|
||||
### 3.1. Аппаратная платформа
|
||||
|
||||
- Выбран **M5Stamp PLC** на базе ESP32‑S3:
|
||||
- CAN‑шина, RS485, реле, опто‑входы — всё, что нужно для стенда;
|
||||
- промышленная обвязка (галваническая развязка на входах, реле на выходах).
|
||||
|
||||
### 3.2. Варианты ПО для M5Stamp
|
||||
|
||||
Рассматривались:
|
||||
|
||||
1. **MicroPython**
|
||||
- Плюсы: быстрый старт, код на Python, удобно писать CLI.
|
||||
- Минусы:
|
||||
- нет готового BSP именно под PLC‑вариант;
|
||||
- нужно вручную разбираться с разводкой, CAN/RS485/реле;
|
||||
- меньше детерминизма по таймингам.
|
||||
|
||||
2. **UIFlow**
|
||||
- Плюсы: визуальное программирование, быстрые демки.
|
||||
- Минусы:
|
||||
- ориентирован на учебные/IoT‑сценарии;
|
||||
- завязан на Web‑IDE/облако;
|
||||
- неудобен для строгого, текстового, версионируемого CLI под pytest/CI.
|
||||
|
||||
3. **Arduino‑фреймворк** (через Arduino core для ESP32)
|
||||
- Плюсы:
|
||||
- официальная поддержка M5‑плат, библиотек и BSP;
|
||||
- примеры по работе с реле, RS485, CAN;
|
||||
- легко поднять простой текстовый CLI по Serial;
|
||||
- хорошо работает в headless‑режиме через `arduino-cli`.
|
||||
- Минусы:
|
||||
- код на C++ (в отличие от MicroPython);
|
||||
- отдельный маленький проект.
|
||||
|
||||
### 3.3. Принятое решение
|
||||
|
||||
- Для M5Stamp PLC используется **Arduino‑фреймворк** + `arduino-cli`.
|
||||
- MicroPython и UIFlow откладываются:
|
||||
- MicroPython — как возможный эксперимент позже;
|
||||
- UIFlow — не подходит как основа HIL‑контроллера.
|
||||
|
||||
---
|
||||
|
||||
## 4. Архитектура HIL‑контроллера (M5Stamp PLC)
|
||||
|
||||
### 4.1. Роль M5Stamp
|
||||
|
||||
- Максимально «тупой» исполнитель команд от хоста:
|
||||
- НЕ содержит сценарной логики тестов;
|
||||
- предоставляет набор атомарных операций:
|
||||
- управление реле;
|
||||
- отправка/приём кадра по CAN/UART;
|
||||
- переключение питания/Reset DUT;
|
||||
- чтение опто‑входов/состояния линий.
|
||||
|
||||
- Все сложные сценарии, тайминги и проверки живут в pytest на хосте.
|
||||
|
||||
### 4.2. Протокол обмена host ↔ M5Stamp
|
||||
|
||||
- Простой текстовый CLI по UART/USB:
|
||||
- команды в виде строк, например:
|
||||
- `RELAY SET 1 ON`
|
||||
- `RELAY SET 1 OFF`
|
||||
- `GPIO READ IN3`
|
||||
- `CAN SEND 1 123#11223344`
|
||||
- `UART DUT SEND "AT+PING\r\n"`
|
||||
- `DUT RESET`
|
||||
- ответы в понятном формате:
|
||||
- `OK`
|
||||
- `ERR <code>`
|
||||
- `VAL 0|1`
|
||||
- `EVENT ...` (если понадобится).
|
||||
|
||||
- Преимущества:
|
||||
- легко писать и отлаживать;
|
||||
- удобно тестировать руками (через serial‑терминал);
|
||||
- протокол можно описать и версионировать как часть репозитория.
|
||||
|
||||
---
|
||||
|
||||
## 5. Интеграция с существующим репозиторием
|
||||
|
||||
### 5.1. Структура репозитория
|
||||
|
||||
HIL‑контроллер хранится в том же монорепо:
|
||||
|
||||
```text
|
||||
/
|
||||
├── firmware/
|
||||
│ ├── test/ # тестовая прошивка DUT
|
||||
│ ├── bootloader/
|
||||
│ ├── tft_app/
|
||||
│ └── hil_controller/ # Arduino-проект для M5Stamp PLC (HIL)
|
||||
├── tools/
|
||||
│ └── host/
|
||||
│ ├── hil/ # Python-код, pytest HIL тесты, утилиты
|
||||
│ ├── flash_usb.py
|
||||
│ └── ...
|
||||
├── just/
|
||||
│ ├── host.just # добавятся host::hil-* цели
|
||||
│ ├── build.just
|
||||
│ └── ci.just
|
||||
└── ...
|
||||
|
|
@ -11,8 +11,10 @@ add_library(
|
|||
generated/board.c generated/pin_mux.c generated/clock_config.c
|
||||
generated/startup/startup_MIMXRT1052.S)
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(led)
|
||||
add_subdirectory(tick)
|
||||
add_subdirectory(uart_host)
|
||||
|
||||
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
||||
target_compile_options(bsp_board PRIVATE -w)
|
||||
|
|
@ -20,18 +22,22 @@ target_compile_options(bsp_board PRIVATE -w)
|
|||
# SYSTEM подавляет предупреждения для всех внешних потребителей
|
||||
target_include_directories(bsp_board SYSTEM PUBLIC generated)
|
||||
|
||||
# Дефайны PUBLIC — транзитивно подтянутся во все firmware таргеты TODO: для
|
||||
# каждого приложения свои дефайны
|
||||
# Дефайны PUBLIC — транзитивно подтянутся во все firmware таргеты
|
||||
target_compile_definitions(
|
||||
bsp_board PUBLIC CPU_MIMXRT1052CVJ5B SKIP_SYSCLK_INIT XIP_EXTERNAL_FLASH=1
|
||||
XIP_BOOT_HEADER_ENABLE=1 XIP_BOOT_HEADER_DCD_ENABLE=1)
|
||||
|
||||
# sdk_device PUBLIC — транзитивно подтянется во все firmware таргеты
|
||||
target_link_libraries(bsp_board PUBLIC sdk_device sdk_clock sdk_common)
|
||||
target_link_libraries(bsp_board PUBLIC sdk_device sdk_clock sdk_common
|
||||
sdk_lpuart)
|
||||
|
||||
# # Сценарий A: XIP — код исполняется прямо из Flash add_library(bsp_boot_xip
|
||||
# INTERFACE) target_compile_definitions( bsp_boot_xip INTERFACE
|
||||
# XIP_EXTERNAL_FLASH=1 XIP_BOOT_HEADER_ENABLE=1 XIP_BOOT_HEADER_DCD_ENABLE=1)
|
||||
# Выбор boot стратегии (определяется в CMakePresets в зависимости от собираемого
|
||||
# target)
|
||||
|
||||
# TODO: длякаждого приложения свои дефайны # Сценарий A: XIP — код исполняется
|
||||
# прямо из Flash add_library(bsp_boot_xip INTERFACE) target_compile_definitions(
|
||||
# bsp_boot_xip INTERFACE XIP_EXTERNAL_FLASH=1 XIP_BOOT_HEADER_ENABLE=1
|
||||
# XIP_BOOT_HEADER_DCD_ENABLE=1)
|
||||
|
||||
# # Сценарий B: копирование в ITCM (для bootloader или критичного кода)
|
||||
# add_library(bsp_boot_itcm INTERFACE) target_compile_definitions( bsp_boot_itcm
|
||||
|
|
|
|||
15
bsp/common/CMakeLists.txt
Normal file
15
bsp/common/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# =============================================================================
|
||||
# bsp_status — общий тип возврата для BSP-модулей
|
||||
#
|
||||
# INTERFACE-библиотека: нет .c файлов, только пробрасывает include-путь.
|
||||
# Работает на любой платформе (ARM target и host unit-тесты) без ifdef.
|
||||
#
|
||||
# Использование: target_link_libraries(<bsp_module> PUBLIC bsp_status)
|
||||
# =============================================================================
|
||||
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
add_library(bsp_status INTERFACE)
|
||||
|
||||
target_include_directories(bsp_status
|
||||
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
46
bsp/common/include/bsp/status.h
Normal file
46
bsp/common/include/bsp/status.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* @file status.h
|
||||
* @brief Общий тип возврата для BSP-модулей.
|
||||
*
|
||||
* Используется модулями, операции которых могут завершиться ошибкой
|
||||
* в рантайме: uart_host, uart_iso, spi, can, usb, wdog и др.
|
||||
*
|
||||
* Не используется там, где ошибка невозможна или означает только
|
||||
* неправильную конфигурацию: tick, led, gpio (простое управление).
|
||||
*
|
||||
* PUBLIC — если bsp_status_t есть в публичном заголовке модуля
|
||||
* PRIVATE — если bsp_status_t используется только внутри .c файлов
|
||||
* target_link_libraries(<target> PUBLIC|PRIVATE bsp_status)
|
||||
* В коде:
|
||||
* #include "bsp/status.h"
|
||||
*/
|
||||
|
||||
#ifndef BSP_STATUS_H
|
||||
#define BSP_STATUS_H
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Тип */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Коды возврата BSP-модулей.
|
||||
*
|
||||
* Соглашение:
|
||||
* BSP_OK — операция завершена успешно.
|
||||
* BSP_ERR_* — операция завершена с ошибкой; подробности в имени кода.
|
||||
*
|
||||
* Значения фиксированы и не меняются: могут сохраняться в NVM / передаваться
|
||||
* по протоколу.
|
||||
*/
|
||||
typedef enum bsp_status_e
|
||||
{
|
||||
BSP_OK = 0, /**< Успех. */
|
||||
BSP_ERR_INIT = 1, /**< Ошибка инициализации / модуль не готов. */
|
||||
BSP_ERR_TIMEOUT = 2, /**< Истёк таймаут ожидания. */
|
||||
BSP_ERR_BUSY = 3, /**< Ресурс занят. */
|
||||
BSP_ERR_PARAM = 4, /**< Невалидный аргумент */
|
||||
BSP_ERR_OVERFLOW = 5, /**< Переполнение буфера, данные потеряны. */
|
||||
BSP_ERR_NOT_SUPPORTED = 6, /**< Функция не поддерживается */
|
||||
} bsp_status_t;
|
||||
|
||||
#endif /* BSP_STATUS_H */
|
||||
17
bsp/uart_host/CMakeLists.txt
Normal file
17
bsp/uart_host/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
if(BUILD_TESTS_HOST)
|
||||
set(UART_HOST_SOURCES mocks/uart_host_mock.c)
|
||||
else()
|
||||
set(UART_HOST_SOURCES src/uart_host.c)
|
||||
endif()
|
||||
|
||||
add_library(bsp_uart_host STATIC ${UART_HOST_SOURCES})
|
||||
|
||||
target_include_directories(
|
||||
bsp_uart_host
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
$<$<BOOL:${BUILD_TESTS_HOST}>:${CMAKE_CURRENT_SOURCE_DIR}/mocks>)
|
||||
|
||||
target_link_libraries(
|
||||
bsp_uart_host
|
||||
PUBLIC bsp_status
|
||||
PRIVATE bsp_tick utils sdk_lpuart $<$<BOOL:${BUILD_TESTS_HOST}>:fff>)
|
||||
164
bsp/uart_host/README.md
Normal file
164
bsp/uart_host/README.md
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
# bsp_uart_host
|
||||
|
||||
Коммуникационный канал с хост-машиной через LPUART1 (разъём J2, MCU-Link VCOM).
|
||||
|
||||
Применяется для HIL-тестов (pytest + pyserial), отладочного вывода и резервного
|
||||
канала связи.
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
```bash
|
||||
[LPUART1 RX] → LPUART1_IRQHandler → ring_buffer_put()
|
||||
↓
|
||||
bsp_uart_host_read() ← polling + таймаут
|
||||
bsp_uart_host_read_byte()
|
||||
|
||||
[LPUART1 TX] ← bsp_uart_host_write() ← LPUART_WriteBlocking()
|
||||
```
|
||||
|
||||
- **TX** — blocking polling (`LPUART_WriteBlocking`). Пакеты короткие, задержка 1–2 мс приемлема.
|
||||
- **RX** — ISR пишет в ring buffer, задача/main читает с таймаутом.
|
||||
- **ISR** — `LPUART1_IRQHandler` определён в модуле, модуль владеет прерыванием целиком.
|
||||
- **Singleton** — один экземпляр, один физический UART.
|
||||
|
||||
---
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```c
|
||||
#include "bsp/uart_host.h"
|
||||
|
||||
// В main(), после board_hw_init():
|
||||
bsp_uart_host_init(115200);
|
||||
|
||||
// TX
|
||||
bsp_uart_host_write_str("hello\r\n");
|
||||
|
||||
// RX — ждать байт до 100 мс
|
||||
int32_t byte = bsp_uart_host_read_byte(100);
|
||||
if (byte < 0) { /* таймаут */ }
|
||||
|
||||
// RX — прочитать пакет целиком
|
||||
uint8_t buf[64];
|
||||
size_t n = bsp_uart_host_read(buf, sizeof(buf), 500);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Конфигурация
|
||||
|
||||
Задаётся в CMakeLists.txt **firmware-таргета**, не модуля:
|
||||
|
||||
```cmake
|
||||
target_compile_definitions(firmware_test PRIVATE
|
||||
BSP_UART_HOST_RX_BUFFER_SIZE=256 # степень двойки, дефолт 256
|
||||
BSP_UART_HOST_SRC_CLOCK_HZ=24000000
|
||||
BSP_UART_HOST_IRQ_PRIORITY=5
|
||||
)
|
||||
```
|
||||
|
||||
| Define | Дефолт | Описание |
|
||||
|--------|--------|----------|
|
||||
| `BSP_UART_HOST_RX_BUFFER_SIZE` | `256` | Размер RX ring buffer. **Должен быть степенью двойки.** |
|
||||
| `BSP_UART_HOST_SRC_CLOCK_HZ` | `24000000` | Частота источника тактирования LPUART1. |
|
||||
| `BSP_UART_HOST_IRQ_PRIORITY` | `5` | Приоритет `LPUART1_IRQn`. Должен быть ≥ `configMAX_SYSCALL_INTERRUPT_PRIORITY` при использовании FreeRTOS. |
|
||||
|
||||
---
|
||||
|
||||
## Таймауты
|
||||
|
||||
```c
|
||||
// Без ожидания — вернёт только то, что уже есть в буфере
|
||||
bsp_uart_host_read(buf, len, 0);
|
||||
|
||||
// Ждать с таймаутом (межбайтовый: сбрасывается после каждого принятого байта)
|
||||
bsp_uart_host_read(buf, len, 100);
|
||||
|
||||
// Ждать вечно
|
||||
bsp_uart_host_read(buf, len, BSP_UART_HOST_WAIT_FOREVER);
|
||||
```
|
||||
|
||||
`bsp_uart_host_read()` возвращает `size_t` — частичное чтение при таймауте
|
||||
не является ошибкой, caller сам решает что делать с полученным количеством байт.
|
||||
|
||||
---
|
||||
|
||||
## FreeRTOS
|
||||
|
||||
Модуль работает в FreeRTOS без отдельной реализации. При сборке с
|
||||
`BSP_TICK_FREERTOS_MODE` в цикле ожидания добавляется `vTaskDelay(1)` —
|
||||
задача отдаёт управление планировщику вместо busy-wait.
|
||||
|
||||
`BSP_UART_HOST_IRQ_PRIORITY` должен быть установлен ниже
|
||||
`configMAX_SYSCALL_INTERRUPT_PRIORITY` (числовое значение выше).
|
||||
|
||||
---
|
||||
|
||||
## Подключение
|
||||
|
||||
```cmake
|
||||
# bsp/CMakeLists.txt
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(uart_host)
|
||||
|
||||
# firmware/test/CMakeLists.txt
|
||||
target_link_libraries(firmware_test PRIVATE
|
||||
bsp_board
|
||||
bsp_tick
|
||||
bsp_uart_host
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Тестирование
|
||||
|
||||
Для host unit-тестов модуль предоставляет fff-заглушки через **Humble Object**:
|
||||
в тестовой сборке вместо `uart_host.c` линкуется `mocks/uart_host_mock.c`.
|
||||
|
||||
```cmake
|
||||
# tests/host/CMakeLists.txt
|
||||
add_host_test(
|
||||
NAME
|
||||
uart_host_mock_example
|
||||
SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/uart_host/test_uart_host.c
|
||||
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks/uart_host_mock.c
|
||||
# Если тестируете "protocol.c" который использует uart_host:
|
||||
# ${CMAKE_SOURCE_DIR}/bsp/protocol/src/protocol.c
|
||||
INCLUDES
|
||||
${CMAKE_SOURCE_DIR}/bsp/uart_host/include # bsp/uart_host.h
|
||||
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks # uart_host_mock.h
|
||||
${CMAKE_SOURCE_DIR}/bsp/common/include # bsp/status.h
|
||||
# ${CMAKE_SOURCE_DIR}/bsp/protocol/include # protocol.h
|
||||
MOCKS
|
||||
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks/uart_host_mock.c)
|
||||
```
|
||||
|
||||
```c
|
||||
#include "fff.h"
|
||||
DEFINE_FFF_GLOBALS;
|
||||
|
||||
#include "bsp/uart_host_mock.h"
|
||||
|
||||
void setUp(void) { UART_HOST_MOCK_RESET_ALL(); }
|
||||
|
||||
void test_something(void) {
|
||||
bsp_uart_host_write_fake.return_val = BSP_OK;
|
||||
// ... вызываем тестируемый код ...
|
||||
TEST_ASSERT_EQUAL(1, bsp_uart_host_write_fake.call_count);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Зависимости
|
||||
|
||||
| Зависимость | Тип | Описание |
|
||||
|-------------|-----|----------|
|
||||
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||
| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаутов |
|
||||
| `utils` (ring_buffer) | PRIVATE | RX ring buffer |
|
||||
| `sdk_lpuart` | PRIVATE | `fsl_lpuart.h`, `fsl_clock.h` |
|
||||
120
bsp/uart_host/include/bsp/uart_host.h
Normal file
120
bsp/uart_host/include/bsp/uart_host.h
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* @file uart_host.h
|
||||
* @brief BSP: коммуникационный канал с хост-машиной (LPUART1, J2).
|
||||
*
|
||||
* Назначение:
|
||||
* - HIL-тесты (pytest + pyserial через MCU-Link VCOM)
|
||||
* - Резервный канал связи / отладочный вывод
|
||||
*
|
||||
* Архитектура:
|
||||
* TX — blocking polling (LPUART_WriteBlocking)
|
||||
* RX — ISR → ring_buffer → polling read с таймаутом
|
||||
*
|
||||
* Паттерн singleton: один экземпляр на всё приложение.
|
||||
*
|
||||
* Размер RX-буфера задаётся через CMake define:
|
||||
* target_compile_definitions(... PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=256)
|
||||
* Значение должно быть степенью двойки. Дефолт: 256.
|
||||
*/
|
||||
#ifndef BSP_UART_HOST_
|
||||
#define BSP_UART_HOST_
|
||||
|
||||
#include "bsp/status.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Константы */
|
||||
/* -------------------------------------------------s------------------------- */
|
||||
|
||||
/** Передать в timeout_ms чтобы ждать данные бесконечно. */
|
||||
#define BSP_UART_HOST_WAIT_FOREVER (UINT32_MAX)
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Инициализация */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Инициализирует LPUART1: тактирование, пины, прерывание, ring buffer.
|
||||
*
|
||||
* Вызывать один раз из main() после board_hw_init().
|
||||
* Повторный вызов без предварительного deinit вернёт BSP_ERR_INIT.
|
||||
*
|
||||
* @param baud_rate Скорость в бод, например 115200.
|
||||
* @return BSP_OK при успехе, BSP_ERR_INIT при ошибке.
|
||||
*/
|
||||
bsp_status_t bsp_uart_host_init(uint32_t baud_rate);
|
||||
|
||||
/**
|
||||
* @brief Деинициализирует LPUART1, отключает прерывание, сбрасывает буфер.
|
||||
*
|
||||
* После вызова модуль можно инициализировать повторно.
|
||||
*/
|
||||
void bsp_uart_host_deinit(void);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* TX — blocking polling */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Отправляет массив байт. Блокирует до завершения передачи.
|
||||
*
|
||||
* @param p_data Указатель на буфер данных.
|
||||
* @param len Количество байт для отправки.
|
||||
* @return BSP_OK или BSP_ERR_INIT если модуль не инициализирован.
|
||||
*/
|
||||
bsp_status_t bsp_uart_host_write(const uint8_t *p_data, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Отправляет C-строку (без нулевого терминатора).
|
||||
*
|
||||
* @param p_str Нуль-терминированная строка.
|
||||
* @return BSP_OK или BSP_ERR_INIT если модуль не инициализирован.
|
||||
*/
|
||||
bsp_status_t bsp_uart_host_write_str(const char *p_str);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* RX — ring buffer + polling с таймаутом */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Читает до @p len байт с таймаутом.
|
||||
*
|
||||
* Функция возвращает управление, как только:
|
||||
* - прочитано @p len байт, ИЛИ
|
||||
* - истёк @p timeout_ms с момента последнего поступившего байта,
|
||||
* ИЛИ буфер пуст и @p timeout_ms == 0.
|
||||
*
|
||||
* Частичное чтение — не ошибка; caller сам проверяет возвращённое значение.
|
||||
*
|
||||
* @param p_buf Буфер для записи принятых данных.
|
||||
* @param len Максимальное число байт для чтения.
|
||||
* @param timeout_ms Таймаут ожидания в мс. 0 — без ожидания,
|
||||
* BSP_UART_HOST_WAIT_FOREVER — ждать бесконечно.
|
||||
* @return Число фактически прочитанных байт (0..len).
|
||||
*/
|
||||
size_t bsp_uart_host_read(uint8_t *p_buf, size_t len, uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Читает один байт с таймаутом.
|
||||
*
|
||||
* @param timeout_ms Таймаут в мс. 0 — без ожидания,
|
||||
* BSP_UART_HOST_WAIT_FOREVER — ждать бесконечно.
|
||||
* @return Принятый байт (0..255) или -1 при таймауте / не инициализирован.
|
||||
*/
|
||||
int32_t bsp_uart_host_read_byte(uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Возвращает число байт, доступных в RX-буфере прямо сейчас.
|
||||
*
|
||||
* @return Число байт (0 если буфер пуст или модуль не инициализирован).
|
||||
*/
|
||||
size_t bsp_uart_host_rx_available(void);
|
||||
|
||||
/**
|
||||
* @brief Сбрасывает содержимое RX-буфера.
|
||||
*/
|
||||
void bsp_uart_host_rx_flush(void);
|
||||
|
||||
#endif //BSP_UART_HOST_
|
||||
41
bsp/uart_host/mocks/uart_host_mock.c
Normal file
41
bsp/uart_host/mocks/uart_host_mock.c
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* @file uart_host_mock.c
|
||||
* @brief fff-заглушки bsp_uart_host для host unit-тестов (Humble Object).
|
||||
*
|
||||
* Подключается вместо uart_host.c при BUILD_TESTS_HOST.
|
||||
* Не требует SDK, LPUART, прерываний.
|
||||
*
|
||||
* Использование в тесте:
|
||||
*
|
||||
* #include "fff.h"
|
||||
* DEFINE_FFF_GLOBALS;
|
||||
*
|
||||
* #include "bsp/uart_host.h"
|
||||
* #include "uart_host_mock.h" // объявления FAKE_*
|
||||
*
|
||||
* void setUp(void) { UART_HOST_MOCK_RESET_ALL(); }
|
||||
* void tearDown(void) {}
|
||||
*
|
||||
* void test_something(void) {
|
||||
* bsp_uart_host_write_fake.return_val = BSP_OK;
|
||||
* // ... вызываем код под тестом ...
|
||||
* TEST_ASSERT_EQUAL(1, bsp_uart_host_write_fake.call_count);
|
||||
* }
|
||||
*/
|
||||
|
||||
#include "uart_host_mock.h"
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Определения fff-заглушек */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
DEFINE_FAKE_VALUE_FUNC(bsp_status_t, bsp_uart_host_init, uint32_t);
|
||||
DEFINE_FAKE_VOID_FUNC(bsp_uart_host_deinit);
|
||||
|
||||
DEFINE_FAKE_VALUE_FUNC(bsp_status_t, bsp_uart_host_write, const uint8_t *, size_t);
|
||||
DEFINE_FAKE_VALUE_FUNC(bsp_status_t, bsp_uart_host_write_str, const char *);
|
||||
|
||||
DEFINE_FAKE_VALUE_FUNC(size_t, bsp_uart_host_read, uint8_t *, size_t, uint32_t);
|
||||
DEFINE_FAKE_VALUE_FUNC(int32_t, bsp_uart_host_read_byte, uint32_t);
|
||||
DEFINE_FAKE_VALUE_FUNC(size_t, bsp_uart_host_rx_available);
|
||||
DEFINE_FAKE_VOID_FUNC(bsp_uart_host_rx_flush);
|
||||
55
bsp/uart_host/mocks/uart_host_mock.h
Normal file
55
bsp/uart_host/mocks/uart_host_mock.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @file uart_host_mock.h
|
||||
* @brief Объявления fff-заглушек bsp_uart_host.
|
||||
*
|
||||
* Включать только в test-файлах, не в продакшн-коде.
|
||||
*
|
||||
* Порядок include в тест-файле:
|
||||
* 1. fff.h
|
||||
* 2. DEFINE_FFF_GLOBALS;
|
||||
* 3. bsp/uart_host.h ← публичный API (сигнатуры функций)
|
||||
* 4. uart_host_mock.h ← FAKE_* структуры
|
||||
*/
|
||||
|
||||
#ifndef UART_HOST_MOCK_H
|
||||
#define UART_HOST_MOCK_H
|
||||
|
||||
#include "bsp/status.h"
|
||||
#include "bsp/uart_host.h"
|
||||
#include "fff.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Объявления заглушек (DEFINE_ живут в uart_host_mock.c) */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
DECLARE_FAKE_VALUE_FUNC(bsp_status_t, bsp_uart_host_init, uint32_t);
|
||||
DECLARE_FAKE_VOID_FUNC(bsp_uart_host_deinit);
|
||||
|
||||
DECLARE_FAKE_VALUE_FUNC(bsp_status_t, bsp_uart_host_write, const uint8_t *, size_t);
|
||||
DECLARE_FAKE_VALUE_FUNC(bsp_status_t, bsp_uart_host_write_str, const char *);
|
||||
|
||||
DECLARE_FAKE_VALUE_FUNC(size_t, bsp_uart_host_read, uint8_t *, size_t, uint32_t);
|
||||
DECLARE_FAKE_VALUE_FUNC(int32_t, bsp_uart_host_read_byte, uint32_t);
|
||||
DECLARE_FAKE_VALUE_FUNC(size_t, bsp_uart_host_rx_available);
|
||||
DECLARE_FAKE_VOID_FUNC(bsp_uart_host_rx_flush);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Удобный макрос: сброс всех заглушек в setUp() */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* clang-format off */
|
||||
#define UART_HOST_MOCK_RESET_ALL() \
|
||||
RESET_FAKE(bsp_uart_host_init); \
|
||||
RESET_FAKE(bsp_uart_host_deinit); \
|
||||
RESET_FAKE(bsp_uart_host_write); \
|
||||
RESET_FAKE(bsp_uart_host_write_str); \
|
||||
RESET_FAKE(bsp_uart_host_read); \
|
||||
RESET_FAKE(bsp_uart_host_read_byte); \
|
||||
RESET_FAKE(bsp_uart_host_rx_available); \
|
||||
RESET_FAKE(bsp_uart_host_rx_flush)
|
||||
/* clang-format on */
|
||||
|
||||
#endif /* UART_HOST_MOCK_H */
|
||||
266
bsp/uart_host/src/uart_host.c
Normal file
266
bsp/uart_host/src/uart_host.c
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
/**
|
||||
* @file uart_host.c
|
||||
* @brief Реализация bsp_uart_host (ARM target only).
|
||||
*
|
||||
* Скомпилируется только при сборке под ARM (не при BUILD_TESTS_HOST).
|
||||
* Для host unit-тестов используется uart_host_mock.c.
|
||||
*/
|
||||
|
||||
#include "bsp/uart_host.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
/* SDK */
|
||||
#include "fsl_clock.h"
|
||||
#include "fsl_lpuart.h"
|
||||
|
||||
/* BSP */
|
||||
#include "bsp/tick.h"
|
||||
|
||||
/* Utils */
|
||||
#include "ring_buffer/ring_buffer.h"
|
||||
|
||||
#if defined(BSP_TICK_FREERTOS_MODE)
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Конфигурация */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#ifndef BSP_UART_HOST_RX_BUFFER_SIZE
|
||||
#define BSP_UART_HOST_RX_BUFFER_SIZE (256U)
|
||||
#endif
|
||||
|
||||
/* Частота источника тактирования LPUART1.
|
||||
* BOARD_BootClockRUN() настраивает OSC → 24 MHz на LPUART.
|
||||
* Скорректируй если у вас другой clock source. */
|
||||
#ifndef BSP_UART_HOST_SRC_CLOCK_HZ
|
||||
#define BSP_UART_HOST_SRC_CLOCK_HZ (24000000U)
|
||||
#endif
|
||||
|
||||
/* Приоритет прерывания LPUART1 (0 = наивысший на CM7). */
|
||||
#ifndef BSP_UART_HOST_IRQ_PRIORITY
|
||||
#define BSP_UART_HOST_IRQ_PRIORITY (5U)
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Статическое состояние модуля */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static uint8_t g_s_rx_buf[BSP_UART_HOST_RX_BUFFER_SIZE];
|
||||
static ring_buffer_desc_t g_s_rx_ring;
|
||||
static bool g_s_initialized = false;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ISR — владеет прерыванием LPUART1 */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void LPUART1_IRQHandler(void)
|
||||
{
|
||||
/* Читаем все байты, которые накопились в RX FIFO. */
|
||||
while (LPUART_GetStatusFlags(LPUART1) & kLPUART_RxDataRegFullFlag)
|
||||
{
|
||||
uint8_t byte = LPUART_ReadByte(LPUART1);
|
||||
/* Переполнение кольцевого буфера: байт молча теряется.
|
||||
* Caller должен читать достаточно быстро. */
|
||||
(void) ring_buffer_put(&g_s_rx_ring, byte);
|
||||
}
|
||||
|
||||
/* Сброс флага прерывания выполняется автоматически при чтении регистра. */
|
||||
SDK_ISR_EXIT_BARRIER;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Инициализация / деинициализация */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
bsp_status_t bsp_uart_host_init(uint32_t baud_rate)
|
||||
{
|
||||
if (g_s_initialized)
|
||||
{
|
||||
return BSP_ERR_INIT;
|
||||
}
|
||||
|
||||
/* Инициализация кольцевого буфера. */
|
||||
if (!ring_buffer_init(&g_s_rx_ring, g_s_rx_buf, BSP_UART_HOST_RX_BUFFER_SIZE))
|
||||
{
|
||||
/* Размер не степень двойки — ошибка конфигурации. */
|
||||
return BSP_ERR_INIT;
|
||||
}
|
||||
|
||||
/* Тактирование LPUART1. */
|
||||
CLOCK_EnableClock(kCLOCK_Lpuart1);
|
||||
|
||||
/* Настройка периферии. */
|
||||
lpuart_config_t config;
|
||||
LPUART_GetDefaultConfig(&config);
|
||||
config.baudRate_Bps = baud_rate;
|
||||
config.enableRx = true;
|
||||
config.enableTx = true;
|
||||
|
||||
status_t sdk_status = LPUART_Init(LPUART1, &config, BSP_UART_HOST_SRC_CLOCK_HZ);
|
||||
if (sdk_status != kStatus_Success)
|
||||
{
|
||||
CLOCK_DisableClock(kCLOCK_Lpuart1);
|
||||
return BSP_ERR_INIT;
|
||||
}
|
||||
|
||||
/* Включаем прерывание на приход байта. */
|
||||
LPUART_EnableInterrupts(LPUART1, kLPUART_RxDataRegFullInterruptEnable);
|
||||
NVIC_SetPriority(LPUART1_IRQn, BSP_UART_HOST_IRQ_PRIORITY);
|
||||
EnableIRQ(LPUART1_IRQn);
|
||||
|
||||
g_s_initialized = true;
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
void bsp_uart_host_deinit(void)
|
||||
{
|
||||
if (!g_s_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DisableIRQ(LPUART1_IRQn);
|
||||
LPUART_DisableInterrupts(LPUART1, kLPUART_RxDataRegFullInterruptEnable);
|
||||
LPUART_Deinit(LPUART1);
|
||||
CLOCK_DisableClock(kCLOCK_Lpuart1);
|
||||
ring_buffer_reset(&g_s_rx_ring);
|
||||
|
||||
g_s_initialized = false;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* TX */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
bsp_status_t bsp_uart_host_write(const uint8_t *p_data, size_t len)
|
||||
{
|
||||
if (!g_s_initialized || p_data == NULL || len == 0U)
|
||||
{
|
||||
return BSP_ERR_INIT;
|
||||
}
|
||||
|
||||
LPUART_WriteBlocking(LPUART1, p_data, len);
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
bsp_status_t bsp_uart_host_write_str(const char *p_str)
|
||||
{
|
||||
if (!g_s_initialized || p_str == NULL)
|
||||
{
|
||||
return BSP_ERR_INIT;
|
||||
}
|
||||
|
||||
size_t len = strlen(p_str);
|
||||
if (len == 0U)
|
||||
{
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
LPUART_WriteBlocking(LPUART1, (const uint8_t *) p_str, len);
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* RX */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
size_t bsp_uart_host_read(uint8_t *p_buf, size_t len, uint32_t timeout_ms)
|
||||
{
|
||||
if (!g_s_initialized || p_buf == NULL || len == 0U)
|
||||
{
|
||||
return 0U;
|
||||
}
|
||||
|
||||
size_t received = 0U;
|
||||
uint32_t start = bsp_tick_get_ms();
|
||||
|
||||
while (received < len)
|
||||
{
|
||||
uint8_t byte;
|
||||
if (ring_buffer_get(&g_s_rx_ring, &byte))
|
||||
{
|
||||
p_buf[received++] = byte;
|
||||
/* Сбрасываем таймер после каждого принятого байта:
|
||||
* timeout_ms — межбайтовый таймаут, а не общий. */
|
||||
start = bsp_tick_get_ms();
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Буфер пуст — проверяем таймаут. */
|
||||
if (timeout_ms == 0U)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (timeout_ms != BSP_UART_HOST_WAIT_FOREVER)
|
||||
{
|
||||
if ((bsp_tick_get_ms() - start) >= timeout_ms)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if defined(BSP_TICK_FREERTOS_MODE)
|
||||
vTaskDelay(1); // ← отдаём управление планировщику на 1 тик
|
||||
#endif
|
||||
}
|
||||
|
||||
return received;
|
||||
}
|
||||
|
||||
int32_t bsp_uart_host_read_byte(uint32_t timeout_ms)
|
||||
{
|
||||
if (!g_s_initialized)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t start = bsp_tick_get_ms();
|
||||
|
||||
while (true)
|
||||
{
|
||||
uint8_t byte;
|
||||
if (ring_buffer_get(&g_s_rx_ring, &byte))
|
||||
{
|
||||
return (int32_t) byte;
|
||||
}
|
||||
|
||||
if (timeout_ms == 0U)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (timeout_ms != BSP_UART_HOST_WAIT_FOREVER)
|
||||
{
|
||||
if ((bsp_tick_get_ms() - start) >= timeout_ms)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t bsp_uart_host_rx_available(void)
|
||||
{
|
||||
if (!g_s_initialized)
|
||||
{
|
||||
return 0U;
|
||||
}
|
||||
return ring_buffer_count(&g_s_rx_ring);
|
||||
}
|
||||
|
||||
void bsp_uart_host_rx_flush(void)
|
||||
{
|
||||
if (!g_s_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ring_buffer_reset(&g_s_rx_ring);
|
||||
}
|
||||
|
|
@ -6,6 +6,9 @@ add_executable(firmware_test)
|
|||
target_sources(firmware_test
|
||||
PRIVATE main.c ${PROJECT_SOURCE_DIR}/bsp/generated/syscalls.c)
|
||||
|
||||
target_compile_definitions(firmware_test
|
||||
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512)
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
# Зависимости — только то что нужно для входного контроля bsp_board транзитивно
|
||||
# даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ int main(void)
|
|||
board_hw_init();
|
||||
bsp_led_init();
|
||||
bsp_tick_init();
|
||||
const uint16_t DELAY_MS = 1000;
|
||||
const uint16_t DELAY_MS = 50;
|
||||
while (1)
|
||||
{
|
||||
bsp_led_on(LED_HEARTBEAT);
|
||||
|
|
|
|||
|
|
@ -78,3 +78,23 @@ add_host_test(
|
|||
NAME test_timeout_pattern SOURCES timeout/test_timeout_pattern.c
|
||||
# нет INCLUDES и MOCKS — чистая математика, нет зависимостей
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Тест кода, использующего bsp_uart_host через мок (замените SOURCES на свой
|
||||
# реальный тест и код под тестом)
|
||||
# -----------------------------------------------------------------------------
|
||||
add_host_test(
|
||||
NAME
|
||||
uart_host_mock_example
|
||||
SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/uart_host/test_uart_host.c
|
||||
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks/uart_host_mock.c
|
||||
# Если тестируете "protocol.c" который использует uart_host:
|
||||
# ${CMAKE_SOURCE_DIR}/bsp/protocol/src/protocol.c
|
||||
INCLUDES
|
||||
${CMAKE_SOURCE_DIR}/bsp/uart_host/include # bsp/uart_host.h
|
||||
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks # uart_host_mock.h
|
||||
${CMAKE_SOURCE_DIR}/bsp/common/include # bsp/status.h
|
||||
# ${CMAKE_SOURCE_DIR}/bsp/protocol/include # protocol.h
|
||||
MOCKS
|
||||
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks/uart_host_mock.c)
|
||||
|
|
|
|||
221
tests/host/uart_host/test_uart_host.c
Normal file
221
tests/host/uart_host/test_uart_host.c
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* @file test_uart_host.c
|
||||
* @brief Unit-тесты для кода использующего bsp_uart_host через fff-мок.
|
||||
*
|
||||
* Сборка: host (x86), без SDK, без железа.
|
||||
* Фреймворки: Unity (assertions) + fff (mocking).
|
||||
*
|
||||
* Что тестируем:
|
||||
* Не саму реализацию uart_host.c (она тестируется HIL-тестами через pyserial),
|
||||
* а то что код верхнего уровня правильно вызывает API uart_host:
|
||||
* корректные аргументы, правильная обработка возвращаемых значений,
|
||||
* поведение при ошибках (BSP_ERR_INIT, таймаут и т.д.).
|
||||
*/
|
||||
|
||||
/* ── 1. fff — до любых mock-заголовков ─────────────────────────────────── */
|
||||
#include "fff.h"
|
||||
DEFINE_FFF_GLOBALS;
|
||||
|
||||
/* ── 2. Мок uart_host (внутри подтягивает bsp/uart_host.h) ─────────────── */
|
||||
#include "uart_host_mock.h"
|
||||
|
||||
/* ── 3. Unity ───────────────────────────────────────────────────────────── */
|
||||
#include "unity.h"
|
||||
|
||||
/* ── 4. Тестируемый модуль — раскомментировать когда появится ──────────── */
|
||||
/* #include "protocol/protocol.h" */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
UART_HOST_MOCK_RESET_ALL();
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* init */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void test_init_called_once_with_correct_baud_rate(void)
|
||||
{
|
||||
bsp_uart_host_init_fake.return_val = BSP_OK;
|
||||
|
||||
bsp_status_t status = bsp_uart_host_init(115200U);
|
||||
|
||||
TEST_ASSERT_EQUAL(BSP_OK, status);
|
||||
TEST_ASSERT_EQUAL(1, bsp_uart_host_init_fake.call_count);
|
||||
TEST_ASSERT_EQUAL(115200U, bsp_uart_host_init_fake.arg0_val);
|
||||
}
|
||||
|
||||
void test_init_returns_err_on_failure(void)
|
||||
{
|
||||
bsp_uart_host_init_fake.return_val = BSP_ERR_INIT;
|
||||
|
||||
bsp_status_t status = bsp_uart_host_init(115200U);
|
||||
|
||||
TEST_ASSERT_EQUAL(BSP_ERR_INIT, status);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* write */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void test_write_called_with_correct_args(void)
|
||||
{
|
||||
const uint8_t payload[] = { 0xAA, 0xBB, 0xCC };
|
||||
bsp_uart_host_write_fake.return_val = BSP_OK;
|
||||
|
||||
bsp_status_t status = bsp_uart_host_write(payload, sizeof(payload));
|
||||
|
||||
TEST_ASSERT_EQUAL(BSP_OK, status);
|
||||
TEST_ASSERT_EQUAL(1, bsp_uart_host_write_fake.call_count);
|
||||
TEST_ASSERT_EQUAL_PTR(payload, bsp_uart_host_write_fake.arg0_val);
|
||||
TEST_ASSERT_EQUAL(sizeof(payload), bsp_uart_host_write_fake.arg1_val);
|
||||
}
|
||||
|
||||
void test_write_str_called_with_correct_string(void)
|
||||
{
|
||||
bsp_uart_host_write_str_fake.return_val = BSP_OK;
|
||||
|
||||
bsp_status_t status = bsp_uart_host_write_str("hello\r\n");
|
||||
|
||||
TEST_ASSERT_EQUAL(BSP_OK, status);
|
||||
TEST_ASSERT_EQUAL_STRING("hello\r\n", bsp_uart_host_write_str_fake.arg0_val);
|
||||
}
|
||||
|
||||
void test_write_returns_err_when_not_initialized(void)
|
||||
{
|
||||
bsp_uart_host_write_fake.return_val = BSP_ERR_INIT;
|
||||
|
||||
bsp_status_t status = bsp_uart_host_write(NULL, 0U);
|
||||
|
||||
TEST_ASSERT_EQUAL(BSP_ERR_INIT, status);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* read_byte */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void test_read_byte_returns_byte_on_success(void)
|
||||
{
|
||||
bsp_uart_host_read_byte_fake.return_val = 0x42;
|
||||
|
||||
int32_t byte = bsp_uart_host_read_byte(100U);
|
||||
|
||||
TEST_ASSERT_EQUAL(0x42, byte);
|
||||
TEST_ASSERT_EQUAL(100U, bsp_uart_host_read_byte_fake.arg0_val);
|
||||
}
|
||||
|
||||
void test_read_byte_returns_minus1_on_timeout(void)
|
||||
{
|
||||
bsp_uart_host_read_byte_fake.return_val = -1;
|
||||
|
||||
int32_t byte = bsp_uart_host_read_byte(100U);
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, byte);
|
||||
}
|
||||
|
||||
void test_read_byte_wait_forever_passes_correct_timeout(void)
|
||||
{
|
||||
bsp_uart_host_read_byte_fake.return_val = 0x01;
|
||||
|
||||
(void) bsp_uart_host_read_byte(BSP_UART_HOST_WAIT_FOREVER);
|
||||
|
||||
TEST_ASSERT_EQUAL(UINT32_MAX, bsp_uart_host_read_byte_fake.arg0_val);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* read */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void test_read_returns_number_of_bytes_read(void)
|
||||
{
|
||||
uint8_t buf[16];
|
||||
bsp_uart_host_read_fake.return_val = 5U;
|
||||
|
||||
size_t n = bsp_uart_host_read(buf, sizeof(buf), 200U);
|
||||
|
||||
TEST_ASSERT_EQUAL(5U, n);
|
||||
TEST_ASSERT_EQUAL_PTR(buf, bsp_uart_host_read_fake.arg0_val);
|
||||
TEST_ASSERT_EQUAL(sizeof(buf), bsp_uart_host_read_fake.arg1_val);
|
||||
TEST_ASSERT_EQUAL(200U, bsp_uart_host_read_fake.arg2_val);
|
||||
}
|
||||
|
||||
void test_read_partial_is_not_an_error(void)
|
||||
{
|
||||
uint8_t buf[64];
|
||||
/* Запросили 64, получили 3 — частичное чтение, не ошибка */
|
||||
bsp_uart_host_read_fake.return_val = 3U;
|
||||
|
||||
size_t n = bsp_uart_host_read(buf, sizeof(buf), 500U);
|
||||
|
||||
TEST_ASSERT_EQUAL(3U, n);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* rx helpers */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void test_rx_available_returns_count(void)
|
||||
{
|
||||
bsp_uart_host_rx_available_fake.return_val = 42U;
|
||||
|
||||
size_t n = bsp_uart_host_rx_available();
|
||||
|
||||
TEST_ASSERT_EQUAL(42U, n);
|
||||
}
|
||||
|
||||
void test_rx_flush_called_once(void)
|
||||
{
|
||||
bsp_uart_host_rx_flush();
|
||||
|
||||
TEST_ASSERT_EQUAL(1, bsp_uart_host_rx_flush_fake.call_count);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* setUp сбрасывает счётчики между тестами */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void test_mock_reset_clears_state(void)
|
||||
{
|
||||
bsp_uart_host_write_fake.return_val = BSP_OK;
|
||||
(void) bsp_uart_host_write(NULL, 0U);
|
||||
TEST_ASSERT_EQUAL(1, bsp_uart_host_write_fake.call_count);
|
||||
|
||||
/* setUp() следующего теста вызовет UART_HOST_MOCK_RESET_ALL() */
|
||||
UART_HOST_MOCK_RESET_ALL();
|
||||
TEST_ASSERT_EQUAL(0, bsp_uart_host_write_fake.call_count);
|
||||
TEST_ASSERT_EQUAL(0, bsp_uart_host_write_fake.return_val);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_init_called_once_with_correct_baud_rate);
|
||||
RUN_TEST(test_init_returns_err_on_failure);
|
||||
|
||||
RUN_TEST(test_write_called_with_correct_args);
|
||||
RUN_TEST(test_write_str_called_with_correct_string);
|
||||
RUN_TEST(test_write_returns_err_when_not_initialized);
|
||||
|
||||
RUN_TEST(test_read_byte_returns_byte_on_success);
|
||||
RUN_TEST(test_read_byte_returns_minus1_on_timeout);
|
||||
RUN_TEST(test_read_byte_wait_forever_passes_correct_timeout);
|
||||
|
||||
RUN_TEST(test_read_returns_number_of_bytes_read);
|
||||
RUN_TEST(test_read_partial_is_not_an_error);
|
||||
|
||||
RUN_TEST(test_rx_available_returns_count);
|
||||
RUN_TEST(test_rx_flush_called_once);
|
||||
|
||||
RUN_TEST(test_mock_reset_clears_state);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# firmware/test/CMakeLists.txt Тестовая прошивка — входной контроль платы на
|
||||
# производстве
|
||||
|
||||
add_executable(firmware_test)
|
||||
|
||||
target_sources(firmware_test
|
||||
PRIVATE main.c ${PROJECT_SOURCE_DIR}/bsp/generated/syscalls.c)
|
||||
|
||||
target_compile_definitions(firmware_test
|
||||
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Зависимости — только то что нужно для входного контроля bsp_board транзитивно
|
||||
# даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны
|
||||
# -----------------------------------------------------------------------------
|
||||
target_link_libraries(
|
||||
firmware_test
|
||||
PRIVATE bsp_board bsp_led bsp_tick
|
||||
# Опциональные библиотеки
|
||||
lib_external # SEGGER RTT если включён через SEGGER_RTT_ENABLED
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Linker script
|
||||
# -----------------------------------------------------------------------------
|
||||
# --gc-sections — удалять неиспользуемые секции (работает с
|
||||
# -ffunction/data-sections) --print-memory-usage — выводить таблицу
|
||||
# использования Flash/RAM после линковки -Map — генерировать
|
||||
# map-файл для анализа размещения символов -T — линкерный
|
||||
# скрипт с описанием карты памяти IMXRT1052
|
||||
target_link_options(
|
||||
firmware_test PRIVATE -Wl,--gc-sections -Wl,--print-memory-usage
|
||||
-Wl,-Map=${CMAKE_BINARY_DIR}/firmware_test.map
|
||||
-T${PROJECT_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_flexspi_nor.ld)
|
||||
|
||||
# TODO: дефайны BOOT
|
||||
# -----------------------------------------------------------------------------
|
||||
# Post-build: генерация .bin для прошивки через blhost
|
||||
# -----------------------------------------------------------------------------
|
||||
add_custom_command(
|
||||
TARGET firmware_test
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:firmware_test>
|
||||
${CMAKE_BINARY_DIR}/firmware_test.bin
|
||||
COMMAND ${CMAKE_OBJCOPY} -O ihex $<TARGET_FILE:firmware_test>
|
||||
${CMAKE_BINARY_DIR}/firmware_test.hex
|
||||
COMMAND ${CMAKE_SIZE} $<TARGET_FILE:firmware_test>
|
||||
COMMENT "Generating firmware_test.bin")
|
||||
Loading…
Reference in a new issue