# 4
- добавлен модуль и тесты bsp/tick, tools/ring_buffer
This commit is contained in:
parent
72045dabb3
commit
96be9ce023
24 changed files with 1511 additions and 125 deletions
|
|
@ -12,25 +12,22 @@
|
|||
"extensions": [
|
||||
"actboy168.tasks",
|
||||
"bierner.markdown-preview-github-styles",
|
||||
"brobeson.ctest-lab",
|
||||
"cheshirekow.cmake-format",
|
||||
"codeium.codeium",
|
||||
"cschlosser.doxdocgen",
|
||||
"dan-c-underwood.arm",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"gruntfuggly.todo-tree",
|
||||
"kylinideteam.cmake-intellisence",
|
||||
"ms-vscode.cmake-tools",
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"mcu-debug.debug-tracker-vscode",
|
||||
"mcu-debug.memory-view",
|
||||
"mcu-debug.peripheral-viewer",
|
||||
"brobeson.ctest-lab",
|
||||
"mcu-debug.rtos-views",
|
||||
"ms-vscode.hexeditor",
|
||||
"ms-vscode.makefile-tools",
|
||||
"probe-rs.probe-rs-debugger",
|
||||
"nefrob.vscode-just-syntax",
|
||||
"rioj7.command-variable",
|
||||
"trond-snekvik.gnu-mapfiles",
|
||||
"twxs.cmake",
|
||||
"usernamehw.errorlens",
|
||||
"wayou.vscode-todo-highlight",
|
||||
"xaver.clang-format",
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ endif()
|
|||
# -----------------------------------------------------------------------------
|
||||
add_subdirectory(sdk)
|
||||
add_subdirectory(bsp)
|
||||
|
||||
add_subdirectory(utils)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Сторонние и внутренние библиотеки
|
||||
# -----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -128,7 +128,9 @@
|
|||
"displayName": "Host Tests — Debug",
|
||||
"configurePreset": "host-debug",
|
||||
"targets": [
|
||||
"test_bsp_led"
|
||||
"test_bsp_led",
|
||||
"test_ring_buffer",
|
||||
"test_timeout_pattern"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -136,7 +138,9 @@
|
|||
"displayName": "Host Tests — Release",
|
||||
"configurePreset": "host-release",
|
||||
"targets": [
|
||||
"test_bsp_led"
|
||||
"test_bsp_led",
|
||||
"test_ring_buffer",
|
||||
"test_timeout_pattern"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ add_library(
|
|||
generated/startup/startup_MIMXRT1052.S)
|
||||
|
||||
add_subdirectory(led)
|
||||
add_subdirectory(tick)
|
||||
|
||||
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
||||
target_compile_options(bsp_board PRIVATE -w)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
/*! @brief The board name */
|
||||
#define BOARD_NAME "TFT4.3_UB_BOARD"
|
||||
|
||||
/*! //TODO: @brief The board name */
|
||||
/*! @brief The board name */
|
||||
void board_hw_init(void);
|
||||
|
||||
#endif /* _BOARD_H_ */
|
||||
|
|
@ -19,13 +19,13 @@
|
|||
## API
|
||||
|
||||
```c
|
||||
void led_init(void); // вызвать один раз после BOARD_InitPins()
|
||||
void bsp_led_init(void); // вызвать один раз после board_hw_init()
|
||||
|
||||
void led_on(led_id_t id);
|
||||
void led_off(led_id_t id);
|
||||
void led_toggle(led_id_t id);
|
||||
void led_set(led_id_t id, bool on);
|
||||
bool led_get(led_id_t id);
|
||||
void bsp_led_on(led_id_t id);
|
||||
void bsp_led_off(led_id_t id);
|
||||
void bsp_led_toggle(led_id_t id);
|
||||
void bsp_led_set(led_id_t id, bool on);
|
||||
bool bsp_led_get(led_id_t id);
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -36,14 +36,14 @@ bool led_get(led_id_t id);
|
|||
#include "bsp/led.h"
|
||||
|
||||
// инициализация
|
||||
led_init();
|
||||
bsp_led_init();
|
||||
|
||||
// heartbeat
|
||||
led_toggle(LED_HEARTBEAT);
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
|
||||
// прикладная индикация
|
||||
led_on(LED_APP); // пакет принят / тест запущен
|
||||
led_off(LED_APP); // сброс
|
||||
bsp_led_on(LED_APP); // пакет принят / тест запущен
|
||||
bsp_led_off(LED_APP); // сброс
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#ifndef BSP_LED_H_
|
||||
#define BSP_LED_H_
|
||||
|
||||
/**
|
||||
* @file led.h
|
||||
|
|
@ -7,7 +8,7 @@
|
|||
* LED_HEARTBEAT — системный, мигает как признак жизни прошивки
|
||||
* LED_APP — прикладной, управляется из firmware по ситуации
|
||||
*
|
||||
* Пины сконфигурированы в generated/pin_mux. Этот хедер не знает
|
||||
* Пины сконфигурированы в generated/pin_mux.h. Этот хедер не знает
|
||||
* ни про GPIO-порты, ни про NXP SDK.
|
||||
*/
|
||||
|
||||
|
|
@ -25,25 +26,27 @@ typedef enum
|
|||
|
||||
/**
|
||||
* @brief Инициализация обоих светодиодов.
|
||||
* Вызвать один раз после BOARD_InitPins().
|
||||
* Вызвать один раз после board_init_hw().
|
||||
* После вызова оба LED выключены.
|
||||
*/
|
||||
void led_init(void);
|
||||
void bsp_led_init(void);
|
||||
|
||||
/** @brief Включить светодиод. */
|
||||
void led_on(led_id_t led_id);
|
||||
void bsp_led_on(led_id_t led_id);
|
||||
|
||||
/** @brief Выключить светодиод. */
|
||||
void led_off(led_id_t led_id);
|
||||
void bsp_led_off(led_id_t led_id);
|
||||
|
||||
/** @brief Переключить состояние светодиода. */
|
||||
void led_toggle(led_id_t led_id);
|
||||
void bsp_led_toggle(led_id_t led_id);
|
||||
|
||||
/**
|
||||
* @brief Установить состояние светодиода явно.
|
||||
* @param on true — включить, false — выключить
|
||||
*/
|
||||
void led_set(led_id_t led_id, bool is_enabled);
|
||||
void bsp_led_set(led_id_t led_id, bool is_enabled);
|
||||
|
||||
/** @brief Получить текущее состояние (true — горит). */
|
||||
bool led_get(led_id_t led_id);
|
||||
bool bsp_led_get(led_id_t led_id);
|
||||
|
||||
#endif //BSP_LED_H_
|
||||
|
|
@ -47,19 +47,19 @@ static led_desc_t s_leds[] = {
|
|||
/* ── Приватные хелперы ───────────────────────────────────────────────── */
|
||||
|
||||
/** Перевести логическое состояние в физический уровень GPIO (active LOW). */
|
||||
static inline uint8_t led_to_gpio_level(bool is_enabled)
|
||||
static inline uint8_t bsp_led_to_gpio_level(bool is_enabled)
|
||||
{
|
||||
return is_enabled ? 0U : 1U;
|
||||
}
|
||||
|
||||
static inline void led_apply(led_desc_t *p_led)
|
||||
static inline void bsp_led_apply(led_desc_t *p_led)
|
||||
{
|
||||
GPIO_PinWrite(p_led->gpio, p_led->pin, led_to_gpio_level(p_led->state));
|
||||
GPIO_PinWrite(p_led->gpio, p_led->pin, bsp_led_to_gpio_level(p_led->state));
|
||||
}
|
||||
|
||||
/* ── Публичный API ───────────────────────────────────────────────────── */
|
||||
|
||||
void led_init(void)
|
||||
void bsp_led_init(void)
|
||||
{
|
||||
gpio_pin_config_t cfg = {
|
||||
.direction = kGPIO_DigitalOutput,
|
||||
|
|
@ -74,28 +74,28 @@ void led_init(void)
|
|||
}
|
||||
}
|
||||
|
||||
void led_on(led_id_t led_id)
|
||||
void bsp_led_on(led_id_t led_id)
|
||||
{
|
||||
led_set(led_id, true);
|
||||
bsp_led_set(led_id, true);
|
||||
}
|
||||
|
||||
void led_off(led_id_t led_id)
|
||||
void bsp_led_off(led_id_t led_id)
|
||||
{
|
||||
led_set(led_id, false);
|
||||
bsp_led_set(led_id, false);
|
||||
}
|
||||
|
||||
void led_toggle(led_id_t led_id)
|
||||
void bsp_led_toggle(led_id_t led_id)
|
||||
{
|
||||
led_set(led_id, !s_leds[led_id].state);
|
||||
bsp_led_set(led_id, !s_leds[led_id].state);
|
||||
}
|
||||
|
||||
void led_set(led_id_t led_id, bool is_enabled)
|
||||
void bsp_led_set(led_id_t led_id, bool is_enabled)
|
||||
{
|
||||
s_leds[led_id].state = is_enabled;
|
||||
led_apply(&s_leds[led_id]);
|
||||
bsp_led_apply(&s_leds[led_id]);
|
||||
}
|
||||
|
||||
bool led_get(led_id_t led_id)
|
||||
bool bsp_led_get(led_id_t led_id)
|
||||
{
|
||||
return s_leds[led_id].state;
|
||||
}
|
||||
14
bsp/tick/CMakeLists.txt
Normal file
14
bsp/tick/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
if(BUILD_TESTS_HOST)
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_library(bsp_tick STATIC src/tick.c)
|
||||
|
||||
target_include_directories(
|
||||
bsp_tick
|
||||
PUBLIC include/
|
||||
PRIVATE src/)
|
||||
|
||||
target_link_libraries(
|
||||
bsp_tick PUBLIC bsp_board # board.h, pin_mux.h — транзитивно
|
||||
)
|
||||
138
bsp/tick/README.md
Normal file
138
bsp/tick/README.md
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# bsp_tick — интеграция в firmware-проекты
|
||||
|
||||
`SysTick_Handler` определён внутри `tick.c` и принадлежит модулю `bsp_tick`.
|
||||
Для добавления внешней логики в обработчик используй слабый хук (см. ниже).
|
||||
|
||||
---
|
||||
|
||||
## firmware/test и firmware/bootloader (bare-metal)
|
||||
|
||||
**CMakeLists.txt**:
|
||||
|
||||
```cmake
|
||||
target_link_libraries(firmware_test PRIVATE bsp_tick)
|
||||
```
|
||||
|
||||
**Порядок инициализации в main()**:
|
||||
|
||||
```c
|
||||
board_hw_init(); // тактирование и пины (BOARD_BootClockRUN внутри)
|
||||
bsp_tick_init(); // SysTick — после того как SystemCoreClock актуален
|
||||
bsp_uart_init(115200); // и далее всё что зависит от времени
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## firmware/tft_app (FreeRTOS)
|
||||
|
||||
**FreeRTOSConfig.h** — убедиться:
|
||||
|
||||
```c
|
||||
#define configTICK_RATE_HZ 1000 // 1 тик = 1 мс
|
||||
#define configUSE_TICK_HOOK 1 // включить vApplicationTickHook
|
||||
```
|
||||
|
||||
**board.c** — добавить hook (см. раздел про хуки ниже):
|
||||
|
||||
```c
|
||||
#include "bsp/tick.h"
|
||||
|
||||
void vApplicationTickHook(void)
|
||||
{
|
||||
bsp_tick_inc(); // no-op в FreeRTOS-режиме, но оставляем для единообразия
|
||||
}
|
||||
```
|
||||
|
||||
**CMakeLists.txt**:
|
||||
|
||||
```cmake
|
||||
target_link_libraries(tft_app PRIVATE bsp_tick freertos_kernel)
|
||||
target_compile_definitions(tft_app PRIVATE BSP_TICK_FREERTOS_MODE)
|
||||
```
|
||||
|
||||
**Инициализация**:
|
||||
|
||||
```c
|
||||
board_hw_init();
|
||||
bsp_tick_init(); // no-op, но вызываем для симметрии с bare-metal
|
||||
vTaskStartScheduler(); // FreeRTOS берёт SysTick себе здесь
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Добавление логики в SysTick — weak hook
|
||||
|
||||
Каждый BSP-модуль владеет своим прерыванием целиком. `SysTick_Handler`
|
||||
живёт в `tick.c` и принадлежит `bsp_tick`. Если другому модулю нужно
|
||||
выполнять работу каждый тик (watchdog, программный таймер и т.п.) —
|
||||
используй слабый хук, не трогая `tick.c`.
|
||||
|
||||
**Как это работает:**
|
||||
|
||||
`tick.c` объявляет и вызывает `bsp_systick_hook()` с атрибутом `weak`.
|
||||
Если никто не определил эту функцию — линкер подставляет пустую
|
||||
заглушку, накладные расходы нулевые. Как только в любом `.c` файле
|
||||
проекта появляется сильное определение — оно автоматически подхватывается.
|
||||
|
||||
```c
|
||||
// tick.c (уже реализовано):
|
||||
__attribute__((weak)) void bsp_systick_hook(void) { /* no-op по умолчанию */ }
|
||||
|
||||
void SysTick_Handler(void)
|
||||
{
|
||||
bsp_tick_inc();
|
||||
bsp_systick_hook();
|
||||
}
|
||||
```
|
||||
|
||||
### **Пример: watchdog из board.c**
|
||||
|
||||
```c
|
||||
// board.c
|
||||
#include "bsp/tick.h"
|
||||
#include "bsp/wdog.h"
|
||||
|
||||
// Переопределяем слабый хук — линкер возьмёт эту версию
|
||||
void bsp_systick_hook(void)
|
||||
{
|
||||
bsp_wdog_feed();
|
||||
}
|
||||
```
|
||||
|
||||
### **Пример: два действия в хуке**
|
||||
|
||||
```c
|
||||
// board.c
|
||||
void bsp_systick_hook(void)
|
||||
{
|
||||
bsp_wdog_feed();
|
||||
bsp_some_other_periodic_task();
|
||||
}
|
||||
```
|
||||
|
||||
**Важно:** хук вызывается из ISR-контекста. Никаких блокирующих
|
||||
операций, мьютексов или `bsp_delay()` внутри.
|
||||
|
||||
---
|
||||
|
||||
## Использование в BSP-модулях и приложении
|
||||
|
||||
```c
|
||||
#include "bsp/tick.h"
|
||||
|
||||
// Таймаут (wraparound-safe):
|
||||
uint32_t start = bsp_tick_get_ms();
|
||||
while (!done) {
|
||||
if ((bsp_tick_get_ms() - start) >= TIMEOUT_MS) { break; }
|
||||
}
|
||||
|
||||
// Блокирующая пауза (инициализация, datasheet-задержки):
|
||||
bsp_delay(10);
|
||||
|
||||
// Периодическое действие без блокировки основного цикла:
|
||||
static uint32_t s_last_ms = 0U;
|
||||
if ((bsp_tick_get_ms() - s_last_ms) >= 500U) {
|
||||
s_last_ms = bsp_tick_get_ms();
|
||||
// ... действие ...
|
||||
}
|
||||
```
|
||||
103
bsp/tick/include/bsp/tick.h
Normal file
103
bsp/tick/include/bsp/tick.h
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* @file bsp/tick.h
|
||||
* @brief Монотонный миллисекундный счётчик для BSP.
|
||||
*
|
||||
* Предоставляет единый API времени для всех BSP-модулей
|
||||
* (bsp_uart, bsp_delay, watchdog и др.) в двух режимах сборки:
|
||||
*
|
||||
* Bare-metal (по умолчанию):
|
||||
* - bsp_tick_init() конфигурирует SysTick на 1 мс.
|
||||
* - SysTick_Handler() в board.c должен вызывать bsp_tick_inc().
|
||||
* - bsp_tick_get_ms() читает внутренний volatile-счётчик.
|
||||
*
|
||||
* FreeRTOS (#define BSP_TICK_FREERTOS_MODE):
|
||||
* - bsp_tick_init() — no-op (SysTick принадлежит FreeRTOS).
|
||||
* - vApplicationTickHook() в board.c должен вызывать bsp_tick_inc().
|
||||
* - bsp_tick_get_ms() возвращает xTaskGetTickCount().
|
||||
* - Предполагает configTICK_RATE_HZ = 1000 (1 тик = 1 мс).
|
||||
*
|
||||
* Использование в firmware/CMakeLists.txt:
|
||||
* target_compile_definitions(firmware_test PRIVATE) // bare-metal
|
||||
* target_compile_definitions(tft_app PRIVATE BSP_TICK_FREERTOS_MODE)
|
||||
*/
|
||||
|
||||
#ifndef BSP_TICK_H
|
||||
#define BSP_TICK_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* ── Инициализация ───────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* @brief Инициализировать источник тиков.
|
||||
*
|
||||
* Bare-metal: настраивает SysTick на прерывание каждые 1 мс.
|
||||
* FreeRTOS: no-op — SysTick уже настроен планировщиком.
|
||||
*
|
||||
* Вызывать после board_hw_init(), до любого использования
|
||||
* bsp_tick_get_ms() или bsp_delay().
|
||||
*/
|
||||
void bsp_tick_init(void);
|
||||
|
||||
/* ── Инкремент (из обработчика прерывания) ───────────────────────────── */
|
||||
|
||||
/**
|
||||
* @brief Увеличить счётчик на 1 мс.
|
||||
*
|
||||
* Bare-metal: вызывать из SysTick_Handler().
|
||||
* FreeRTOS: вызывать из vApplicationTickHook().
|
||||
*
|
||||
* @note В FreeRTOS-режиме функция — no-op (счётчик не используется),
|
||||
* но вызов из hook-а оставляем для единообразия кода board.c.
|
||||
*/
|
||||
void bsp_tick_inc(void);
|
||||
|
||||
/* ── Чтение времени ──────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* @brief Вернуть текущее время в миллисекундах.
|
||||
*
|
||||
* Bare-metal: монотонный счётчик, инкрементируемый из SysTick_Handler.
|
||||
* Переполняется через ~49.7 дней (UINT32_MAX мс).
|
||||
* FreeRTOS: возвращает xTaskGetTickCount() (при configTICK_RATE_HZ=1000
|
||||
* значение в мс совпадает с тиками).
|
||||
*
|
||||
* Для измерения интервалов используй разность:
|
||||
* uint32_t start = bsp_tick_get_ms();
|
||||
* // ... работа ...
|
||||
* uint32_t elapsed = bsp_tick_get_ms() - start; // корректно при wraparound
|
||||
*/
|
||||
uint32_t bsp_tick_get_ms(void);
|
||||
|
||||
/* ── Задержка ────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* @brief Блокирующая задержка.
|
||||
*
|
||||
* Реализована через polling bsp_tick_get_ms() — не использует
|
||||
* RTOS-примитивов, работает в любом контексте включая ISR
|
||||
* (при условии что SysTick прерывания не заблокированы).
|
||||
*
|
||||
* @warning В FreeRTOS-контексте блокирует задачу без освобождения
|
||||
* процессора. Для FreeRTOS предпочитай vTaskDelay().
|
||||
* bsp_delay() здесь — для BSP-инициализации до старта
|
||||
* планировщика и для bare-metal firmware.
|
||||
*
|
||||
* @param millis Время задержки в миллисекундах.
|
||||
*/
|
||||
void bsp_delay(uint32_t millis);
|
||||
|
||||
#endif /* BSP_TICK_H */
|
||||
|
||||
/* ── Хук для внешней логики ──────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* @brief Вызывается из SysTick_Handler (bare-metal) или
|
||||
* vApplicationTickHook (FreeRTOS) после bsp_tick_inc().
|
||||
*
|
||||
* Слабое определение по умолчанию — no-op.
|
||||
* Переопредели в своём .c файле для watchdog, программных таймеров и т.п.
|
||||
*
|
||||
* @warning ISR-контекст. Никаких блокировок и задержек.
|
||||
*/
|
||||
void bsp_systick_hook(void);
|
||||
101
bsp/tick/src/tick.c
Normal file
101
bsp/tick/src/tick.c
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* @file tick.c
|
||||
* @brief Реализация BSP tick — SysTick (bare-metal) или FreeRTOS.
|
||||
*
|
||||
* SysTick_Handler определён здесь и принадлежит этому модулю.
|
||||
* Для добавления внешней логики в обработчик используй bsp_systick_hook().
|
||||
*/
|
||||
|
||||
#include "bsp/tick.h"
|
||||
|
||||
/* ── Слабый хук для внешней логики в SysTick ─────────────────────────── */
|
||||
|
||||
/**
|
||||
* @brief Хук, вызываемый из SysTick_Handler после bsp_tick_inc().
|
||||
*
|
||||
* По умолчанию — no-op. Переопредяется в своём .c файле (без weak)
|
||||
* чтобы добавить периодическую работу: watchdog, программный таймер и т.п.
|
||||
*
|
||||
* @warning Вызывается из ISR-контекста. Никаких блокировок и задержек.
|
||||
*/
|
||||
__attribute__((weak)) void bsp_systick_hook(void)
|
||||
{ /* no-op */
|
||||
}
|
||||
|
||||
/* ── Bare-metal реализация ───────────────────────────────────────────── */
|
||||
|
||||
#ifndef BSP_TICK_FREERTOS_MODE
|
||||
|
||||
#include "fsl_common.h" /* SystemCoreClock, SysTick_Config */
|
||||
|
||||
static volatile uint32_t s_tick_ms = 0U;
|
||||
|
||||
void bsp_tick_init(void)
|
||||
{
|
||||
SysTick_Config(SystemCoreClock / 1000U);
|
||||
}
|
||||
|
||||
void bsp_tick_inc(void)
|
||||
{
|
||||
s_tick_ms++;
|
||||
}
|
||||
|
||||
uint32_t bsp_tick_get_ms(void)
|
||||
{
|
||||
return s_tick_ms;
|
||||
}
|
||||
|
||||
void SysTick_Handler(void)
|
||||
{
|
||||
bsp_tick_inc();
|
||||
bsp_systick_hook();
|
||||
}
|
||||
|
||||
/* ── FreeRTOS реализация ─────────────────────────────────────────────── */
|
||||
|
||||
#else /* BSP_TICK_FREERTOS_MODE */
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
#if configTICK_RATE_HZ != 1000
|
||||
#warning "BSP_TICK_FREERTOS_MODE: configTICK_RATE_HZ != 1000. \
|
||||
bsp_tick_get_ms() returns ticks, not milliseconds."
|
||||
#endif
|
||||
|
||||
void bsp_tick_init(void)
|
||||
{ /* no-op */
|
||||
}
|
||||
void bsp_tick_inc(void)
|
||||
{ /* no-op */
|
||||
}
|
||||
|
||||
uint32_t bsp_tick_get_ms(void)
|
||||
{
|
||||
return (uint32_t) xTaskGetTickCount();
|
||||
}
|
||||
|
||||
/*
|
||||
* В FreeRTOS-режиме SysTick_Handler принадлежит планировщику.
|
||||
* Периодическая логика идёт через vApplicationTickHook в board.c,
|
||||
* который вызывает bsp_tick_inc() (no-op) и bsp_systick_hook().
|
||||
*
|
||||
* board.c:
|
||||
* void vApplicationTickHook(void) {
|
||||
* bsp_tick_inc();
|
||||
* bsp_systick_hook();
|
||||
* }
|
||||
*/
|
||||
|
||||
#endif /* BSP_TICK_FREERTOS_MODE */
|
||||
|
||||
/* ── bsp_delay — общий для обоих режимов ────────────────────────────── */
|
||||
|
||||
void bsp_delay(uint32_t millis)
|
||||
{
|
||||
uint32_t start = bsp_tick_get_ms();
|
||||
while ((bsp_tick_get_ms() - start) < millis)
|
||||
{
|
||||
/* busy-wait */
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ target_sources(firmware_test
|
|||
# -----------------------------------------------------------------------------
|
||||
target_link_libraries(
|
||||
firmware_test
|
||||
PRIVATE bsp_board bsp_led
|
||||
PRIVATE bsp_board bsp_led bsp_tick
|
||||
# Опциональные библиотеки
|
||||
lib_external # SEGGER RTT если включён через SEGGER_RTT_ENABLED
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
#include "board.h"
|
||||
#include "bsp/led.h"
|
||||
#include "bsp/tick.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
board_hw_init();
|
||||
led_init();
|
||||
|
||||
led_on(LED_HEARTBEAT);
|
||||
led_off(LED_APP);
|
||||
board_hw_init();
|
||||
bsp_led_init();
|
||||
bsp_tick_init();
|
||||
const uint16_t DELAY_MS = 1000;
|
||||
while (1)
|
||||
{
|
||||
bsp_led_on(LED_HEARTBEAT);
|
||||
bsp_delay(DELAY_MS);
|
||||
bsp_led_off(LED_HEARTBEAT);
|
||||
bsp_delay(DELAY_MS);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,28 +3,34 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Вспомогательная функция добавления host-теста.
|
||||
#
|
||||
# Использование: add_host_test( NAME <имя теста> SOURCES <тест.c>
|
||||
# <тестируемый_модуль.c> ... MOCKS <путь к папке с stub-хедерами> #
|
||||
# опционально )
|
||||
# Использование: add_host_test( NAME <имя теста> SOURCES <тест.c> [<модуль.c>
|
||||
# ...] # исходники теста + тестируемый код INCLUDES <dir> [<dir> ...] #
|
||||
# дополнительные include-пути (опц.) MOCKS <dir> [<dir> ...] # папки
|
||||
# со stub-хедерами (опц.) )
|
||||
#
|
||||
# Каждый тест — отдельный исполняемый файл. CTest запускает их все.
|
||||
#
|
||||
# Соглашения: - lib_external подключается всегда (Unity + fff). - INCLUDES и
|
||||
# MOCKS — явные: каждый тест сам декларирует свои зависимости. Нет скрытых
|
||||
# глобальных include-путей, специфичных для одного модуля.
|
||||
# -----------------------------------------------------------------------------
|
||||
function(add_host_test)
|
||||
cmake_parse_arguments(ARG "" "NAME" "SOURCES;MOCKS" ${ARGN})
|
||||
cmake_parse_arguments(ARG "" "NAME" "SOURCES;INCLUDES;MOCKS" ${ARGN})
|
||||
|
||||
add_executable(${ARG_NAME} ${ARG_SOURCES})
|
||||
|
||||
target_link_libraries(${ARG_NAME} PRIVATE lib_external)
|
||||
|
||||
# stub-хедеры подключаются как PRIVATE — не торчат в других таргетах
|
||||
# Явные include-пути, специфичные для теста
|
||||
foreach(inc_dir IN LISTS ARG_INCLUDES)
|
||||
target_include_directories(${ARG_NAME} PRIVATE ${inc_dir})
|
||||
endforeach()
|
||||
|
||||
# Stub-хедеры для моков (PRIVATE — не торчат в других таргетах)
|
||||
foreach(mock_dir IN LISTS ARG_MOCKS)
|
||||
target_include_directories(${ARG_NAME} PRIVATE ${mock_dir})
|
||||
endforeach()
|
||||
|
||||
# публичные хедеры BSP нужны для #include "bsp/led.h"
|
||||
target_include_directories(${ARG_NAME}
|
||||
PRIVATE ${PROJECT_SOURCE_DIR}/bsp/led/include)
|
||||
|
||||
add_test(
|
||||
NAME ${ARG_NAME}
|
||||
COMMAND ${ARG_NAME}
|
||||
|
|
@ -44,8 +50,31 @@ add_host_test(
|
|||
NAME
|
||||
test_bsp_led
|
||||
SOURCES
|
||||
test_bsp_led.c
|
||||
${PROJECT_SOURCE_DIR}/bsp/led/src/led.c # тестируемый модуль напрямую
|
||||
led/test_bsp_led.c
|
||||
${PROJECT_SOURCE_DIR}/bsp/led/src/led.c
|
||||
INCLUDES
|
||||
${PROJECT_SOURCE_DIR}/bsp/led/include # для #include "bsp/led.h"
|
||||
MOCKS
|
||||
${BSP_MOCKS_DIR} # fsl_gpio.h, pin_mux.h, board.h
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# utils/ring_buffer
|
||||
# -----------------------------------------------------------------------------
|
||||
add_host_test(
|
||||
NAME
|
||||
test_ring_buffer
|
||||
SOURCES
|
||||
ring_buffer/test_ring_buffer.c
|
||||
${PROJECT_SOURCE_DIR}/utils/ring_buffer/ring_buffer.c
|
||||
INCLUDES
|
||||
${PROJECT_SOURCE_DIR}/utils # для #include "ring_buffer/ring_buffer.h"
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Тест таймаут-паттерна, используемого в bsp/tick
|
||||
# -----------------------------------------------------------------------------
|
||||
add_host_test(
|
||||
NAME test_timeout_pattern SOURCES timeout/test_timeout_pattern.c
|
||||
# нет INCLUDES и MOCKS — чистая математика, нет зависимостей
|
||||
)
|
||||
|
|
|
|||
|
|
@ -80,9 +80,9 @@ tests/host/
|
|||
│ ├── fsl_lpuart.h
|
||||
│ ├── pin_mux.h # зеркало макросов пинов из generated/
|
||||
│ └── board.h
|
||||
├── test_led.c # категория B — BSP-модуль
|
||||
├── test_protocol.c # категория A — платформонезависимый
|
||||
└── test_runner_logic.c # категория A
|
||||
├── led/test_led.c # категория B — BSP-модуль
|
||||
├── protocol/test_protocol.c # категория A — платформонезависимый
|
||||
└── runner/test_runner_logic.c # категория A
|
||||
```
|
||||
|
||||
`mocks/` подключается как include path с более высоким приоритетом чем `sdk/`. Компилятор найдёт `fsl_gpio.h` из `mocks/` раньше чем из SDK — `led.c` компилируется на хосте без единого изменения в BSP-коде.
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ FAKE_VOID_FUNC(GPIO_PinWrite, GPIO_Type *, uint32_t, uint8_t);
|
|||
|
||||
/* ── Capture для gpio_pin_config_t ──────────────────────────────────
|
||||
* fff хранит arg2_history как указатель — но cfg живёт на стеке
|
||||
* led_init() и становится dangling после возврата.
|
||||
* bsp_led_init() и становится dangling после возврата.
|
||||
* (ASAN правильно детектирует это как stack-use-after-return)
|
||||
*
|
||||
* Решение: custom_fake копирует структуру по значению в момент вызова,
|
||||
* пока стек led_init() ещё жив.
|
||||
* пока стек bsp_led_init() ещё жив.
|
||||
* ─────────────────────────────────────────────────────────────────── */
|
||||
static gpio_pin_config_t s_captured_cfg[2];
|
||||
static int s_capture_idx = 0;
|
||||
|
|
@ -43,11 +43,11 @@ void setUp(void)
|
|||
RESET_FAKE(GPIO_PinWrite);
|
||||
FFF_RESET_HISTORY();
|
||||
|
||||
/* подключаем capture до вызова led_init */
|
||||
/* подключаем capture до вызова bsp_led_init */
|
||||
s_capture_idx = 0;
|
||||
GPIO_PinInit_fake.custom_fake = GPIO_PinInit_capture;
|
||||
|
||||
led_init();
|
||||
bsp_led_init();
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
|
|
@ -56,110 +56,110 @@ void tearDown(void)
|
|||
|
||||
/* ── Тесты инициализации ─────────────────────────────────────────────── */
|
||||
|
||||
void test_led_init_calls_gpio_init_for_each_led(void)
|
||||
void test_bsp_led_init_calls_gpio_init_for_each_led(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL(2, GPIO_PinInit_fake.call_count);
|
||||
}
|
||||
|
||||
void test_led_init_configures_as_output(void)
|
||||
void test_bsp_led_init_configures_as_output(void)
|
||||
{
|
||||
/* s_captured_cfg — копия по значению, стек led_init() не нужен */
|
||||
/* s_captured_cfg — копия по значению, стек bsp_led_init() не нужен */
|
||||
TEST_ASSERT_EQUAL(kGPIO_DigitalOutput, s_captured_cfg[0].direction);
|
||||
TEST_ASSERT_EQUAL(kGPIO_DigitalOutput, s_captured_cfg[1].direction);
|
||||
}
|
||||
|
||||
void test_led_init_output_logic_is_high(void)
|
||||
void test_bsp_led_init_output_logic_is_high(void)
|
||||
{
|
||||
/* active LOW — начальное состояние GPIO = 1 (LED выключен) */
|
||||
TEST_ASSERT_EQUAL_UINT8(1U, s_captured_cfg[0].outputLogic);
|
||||
TEST_ASSERT_EQUAL_UINT8(1U, s_captured_cfg[1].outputLogic);
|
||||
}
|
||||
|
||||
void test_led_init_both_leds_off(void)
|
||||
void test_bsp_led_init_both_leds_off(void)
|
||||
{
|
||||
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
|
||||
TEST_ASSERT_FALSE(led_get(LED_APP));
|
||||
TEST_ASSERT_FALSE(bsp_led_get(LED_HEARTBEAT));
|
||||
TEST_ASSERT_FALSE(bsp_led_get(LED_APP));
|
||||
}
|
||||
|
||||
/* ── Тесты led_on / led_off ──────────────────────────────────────────── */
|
||||
/* ── Тесты bsp_led_on / bsp_led_off ──────────────────────────────────────────── */
|
||||
|
||||
void test_led_on_sets_state_true(void)
|
||||
void test_bsp_led_on_sets_state_true(void)
|
||||
{
|
||||
led_on(LED_HEARTBEAT);
|
||||
TEST_ASSERT_TRUE(led_get(LED_HEARTBEAT));
|
||||
bsp_led_on(LED_HEARTBEAT);
|
||||
TEST_ASSERT_TRUE(bsp_led_get(LED_HEARTBEAT));
|
||||
}
|
||||
|
||||
void test_led_off_sets_state_false(void)
|
||||
void test_bsp_led_off_sets_state_false(void)
|
||||
{
|
||||
led_on(LED_HEARTBEAT);
|
||||
led_off(LED_HEARTBEAT);
|
||||
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
|
||||
bsp_led_on(LED_HEARTBEAT);
|
||||
bsp_led_off(LED_HEARTBEAT);
|
||||
TEST_ASSERT_FALSE(bsp_led_get(LED_HEARTBEAT));
|
||||
}
|
||||
|
||||
void test_led_on_writes_gpio_low(void)
|
||||
void test_bsp_led_on_writes_gpio_low(void)
|
||||
{
|
||||
/* active LOW: включить LED = записать 0 в GPIO */
|
||||
led_on(LED_HEARTBEAT);
|
||||
bsp_led_on(LED_HEARTBEAT);
|
||||
TEST_ASSERT_EQUAL_UINT8(0U, GPIO_PinWrite_fake.arg2_val);
|
||||
}
|
||||
|
||||
void test_led_off_writes_gpio_high(void)
|
||||
void test_bsp_led_off_writes_gpio_high(void)
|
||||
{
|
||||
/* active LOW: выключить LED = записать 1 в GPIO */
|
||||
led_off(LED_HEARTBEAT);
|
||||
bsp_led_off(LED_HEARTBEAT);
|
||||
TEST_ASSERT_EQUAL_UINT8(1U, GPIO_PinWrite_fake.arg2_val);
|
||||
}
|
||||
|
||||
/* ── Тесты led_toggle ────────────────────────────────────────────────── */
|
||||
/* ── Тесты bsp_led_toggle ────────────────────────────────────────────────── */
|
||||
|
||||
void test_led_toggle_off_to_on(void)
|
||||
void test_bsp_led_toggle_off_to_on(void)
|
||||
{
|
||||
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
|
||||
led_toggle(LED_HEARTBEAT);
|
||||
TEST_ASSERT_TRUE(led_get(LED_HEARTBEAT));
|
||||
TEST_ASSERT_FALSE(bsp_led_get(LED_HEARTBEAT));
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
TEST_ASSERT_TRUE(bsp_led_get(LED_HEARTBEAT));
|
||||
}
|
||||
|
||||
void test_led_toggle_on_to_off(void)
|
||||
void test_bsp_led_toggle_on_to_off(void)
|
||||
{
|
||||
led_on(LED_HEARTBEAT);
|
||||
led_toggle(LED_HEARTBEAT);
|
||||
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
|
||||
bsp_led_on(LED_HEARTBEAT);
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
TEST_ASSERT_FALSE(bsp_led_get(LED_HEARTBEAT));
|
||||
}
|
||||
|
||||
void test_led_toggle_twice_returns_to_initial(void)
|
||||
void test_bsp_led_toggle_twice_returns_to_initial(void)
|
||||
{
|
||||
led_toggle(LED_HEARTBEAT);
|
||||
led_toggle(LED_HEARTBEAT);
|
||||
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
TEST_ASSERT_FALSE(bsp_led_get(LED_HEARTBEAT));
|
||||
}
|
||||
|
||||
/* ── Тесты led_set ───────────────────────────────────────────────────── */
|
||||
/* ── Тесты bsp_led_set ───────────────────────────────────────────────────── */
|
||||
|
||||
void test_led_set_true_turns_on(void)
|
||||
void test_bsp_led_set_true_turns_on(void)
|
||||
{
|
||||
led_set(LED_APP, true);
|
||||
TEST_ASSERT_TRUE(led_get(LED_APP));
|
||||
bsp_led_set(LED_APP, true);
|
||||
TEST_ASSERT_TRUE(bsp_led_get(LED_APP));
|
||||
}
|
||||
|
||||
void test_led_set_false_turns_off(void)
|
||||
void test_bsp_led_set_false_turns_off(void)
|
||||
{
|
||||
led_set(LED_APP, true);
|
||||
led_set(LED_APP, false);
|
||||
TEST_ASSERT_FALSE(led_get(LED_APP));
|
||||
bsp_led_set(LED_APP, true);
|
||||
bsp_led_set(LED_APP, false);
|
||||
TEST_ASSERT_FALSE(bsp_led_get(LED_APP));
|
||||
}
|
||||
|
||||
/* ── Тест независимости LED друг от друга ───────────────────────────── */
|
||||
|
||||
void test_leds_are_independent(void)
|
||||
{
|
||||
led_on(LED_HEARTBEAT);
|
||||
TEST_ASSERT_TRUE(led_get(LED_HEARTBEAT));
|
||||
TEST_ASSERT_FALSE(led_get(LED_APP));
|
||||
bsp_led_on(LED_HEARTBEAT);
|
||||
TEST_ASSERT_TRUE(bsp_led_get(LED_HEARTBEAT));
|
||||
TEST_ASSERT_FALSE(bsp_led_get(LED_APP));
|
||||
|
||||
led_on(LED_APP);
|
||||
led_off(LED_HEARTBEAT);
|
||||
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
|
||||
TEST_ASSERT_TRUE(led_get(LED_APP));
|
||||
bsp_led_on(LED_APP);
|
||||
bsp_led_off(LED_HEARTBEAT);
|
||||
TEST_ASSERT_FALSE(bsp_led_get(LED_HEARTBEAT));
|
||||
TEST_ASSERT_TRUE(bsp_led_get(LED_APP));
|
||||
}
|
||||
|
||||
/* ── main ────────────────────────────────────────────────────────────── */
|
||||
|
|
@ -168,22 +168,22 @@ int main(void)
|
|||
{
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_led_init_calls_gpio_init_for_each_led);
|
||||
RUN_TEST(test_led_init_configures_as_output);
|
||||
RUN_TEST(test_led_init_output_logic_is_high);
|
||||
RUN_TEST(test_led_init_both_leds_off);
|
||||
RUN_TEST(test_bsp_led_init_calls_gpio_init_for_each_led);
|
||||
RUN_TEST(test_bsp_led_init_configures_as_output);
|
||||
RUN_TEST(test_bsp_led_init_output_logic_is_high);
|
||||
RUN_TEST(test_bsp_led_init_both_leds_off);
|
||||
|
||||
RUN_TEST(test_led_on_sets_state_true);
|
||||
RUN_TEST(test_led_off_sets_state_false);
|
||||
RUN_TEST(test_led_on_writes_gpio_low);
|
||||
RUN_TEST(test_led_off_writes_gpio_high);
|
||||
RUN_TEST(test_bsp_led_on_sets_state_true);
|
||||
RUN_TEST(test_bsp_led_off_sets_state_false);
|
||||
RUN_TEST(test_bsp_led_on_writes_gpio_low);
|
||||
RUN_TEST(test_bsp_led_off_writes_gpio_high);
|
||||
|
||||
RUN_TEST(test_led_toggle_off_to_on);
|
||||
RUN_TEST(test_led_toggle_on_to_off);
|
||||
RUN_TEST(test_led_toggle_twice_returns_to_initial);
|
||||
RUN_TEST(test_bsp_led_toggle_off_to_on);
|
||||
RUN_TEST(test_bsp_led_toggle_on_to_off);
|
||||
RUN_TEST(test_bsp_led_toggle_twice_returns_to_initial);
|
||||
|
||||
RUN_TEST(test_led_set_true_turns_on);
|
||||
RUN_TEST(test_led_set_false_turns_off);
|
||||
RUN_TEST(test_bsp_led_set_true_turns_on);
|
||||
RUN_TEST(test_bsp_led_set_false_turns_off);
|
||||
|
||||
RUN_TEST(test_leds_are_independent);
|
||||
|
||||
459
tests/host/ring_buffer/test_ring_buffer.c
Normal file
459
tests/host/ring_buffer/test_ring_buffer.c
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
/**
|
||||
* @file test_ring_buffer.c
|
||||
* @brief Unit-тесты для ring_buffer (Unity framework).
|
||||
*
|
||||
* Группы тестов:
|
||||
* 1. Init — корректная и некорректная инициализация
|
||||
* 2. Put/Get — базовые операции с одним байтом
|
||||
* 3. Write/Read — блочные операции
|
||||
* 4. Full/Empty — граничные состояния
|
||||
* 5. Wraparound — корректность при переполнении индексов
|
||||
* 6. SPSC sim — имитация одновременной записи ISR и чтения consumer
|
||||
* 7. Reset — сброс состояния
|
||||
*/
|
||||
|
||||
#include "ring_buffer/ring_buffer.h"
|
||||
#include "unity.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* ── Вспомогательные объекты ─────────────────────────────────────────── */
|
||||
|
||||
#define BUF_SIZE 8U /* Степень двойки, удобна для wraparound-тестов. */
|
||||
|
||||
static ring_buffer_desc_t rb;
|
||||
static uint8_t storage[BUF_SIZE];
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
/* Вызывается перед каждым тестом — чистое состояние. */
|
||||
bool ok = ring_buffer_init(&rb, storage, BUF_SIZE);
|
||||
TEST_ASSERT_TRUE(ok);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{ /* ничего */
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
* 1. Init
|
||||
* ═══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_init_valid(void)
|
||||
{
|
||||
ring_buffer_desc_t tmp;
|
||||
uint8_t mem[16];
|
||||
TEST_ASSERT_TRUE(ring_buffer_init(&tmp, mem, 16U));
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_empty(&tmp));
|
||||
TEST_ASSERT_EQUAL_size_t(0U, ring_buffer_count(&tmp));
|
||||
TEST_ASSERT_EQUAL_size_t(16U, ring_buffer_free(&tmp));
|
||||
}
|
||||
|
||||
void test_init_rejects_null_rb(void)
|
||||
{
|
||||
uint8_t mem[8];
|
||||
TEST_ASSERT_FALSE(ring_buffer_init(NULL, mem, 8U));
|
||||
}
|
||||
|
||||
void test_init_rejects_null_buf(void)
|
||||
{
|
||||
ring_buffer_desc_t tmp;
|
||||
TEST_ASSERT_FALSE(ring_buffer_init(&tmp, NULL, 8U));
|
||||
}
|
||||
|
||||
void test_init_rejects_zero_size(void)
|
||||
{
|
||||
ring_buffer_desc_t tmp;
|
||||
uint8_t mem[8];
|
||||
TEST_ASSERT_FALSE(ring_buffer_init(&tmp, mem, 0U));
|
||||
}
|
||||
|
||||
void test_init_rejects_non_power_of_two(void)
|
||||
{
|
||||
ring_buffer_desc_t tmp;
|
||||
uint8_t mem[10];
|
||||
|
||||
TEST_ASSERT_FALSE(ring_buffer_init(&tmp, mem, 3U));
|
||||
TEST_ASSERT_FALSE(ring_buffer_init(&tmp, mem, 5U));
|
||||
TEST_ASSERT_FALSE(ring_buffer_init(&tmp, mem, 6U));
|
||||
TEST_ASSERT_FALSE(ring_buffer_init(&tmp, mem, 7U));
|
||||
}
|
||||
|
||||
void test_init_accepts_all_valid_powers_of_two(void)
|
||||
{
|
||||
ring_buffer_desc_t tmp;
|
||||
uint8_t mem[256];
|
||||
|
||||
for (size_t s = 1U; s <= 256U; s <<= 1U)
|
||||
{
|
||||
TEST_ASSERT_TRUE(ring_buffer_init(&tmp, mem, s));
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
* 2. Put / Get — одиночные байты
|
||||
* ═══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_put_get_single_byte(void)
|
||||
{
|
||||
uint8_t out = 0xFF;
|
||||
|
||||
TEST_ASSERT_TRUE(ring_buffer_put(&rb, 0xAB));
|
||||
TEST_ASSERT_EQUAL_size_t(1U, ring_buffer_count(&rb));
|
||||
TEST_ASSERT_FALSE(ring_buffer_is_empty(&rb));
|
||||
|
||||
TEST_ASSERT_TRUE(ring_buffer_get(&rb, &out));
|
||||
TEST_ASSERT_EQUAL_UINT8(0xAB, out);
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_empty(&rb));
|
||||
}
|
||||
|
||||
void test_get_from_empty_returns_false(void)
|
||||
{
|
||||
uint8_t out = 0xAA;
|
||||
TEST_ASSERT_FALSE(ring_buffer_get(&rb, &out));
|
||||
/* out не должен быть изменён */
|
||||
TEST_ASSERT_EQUAL_UINT8(0xAA, out);
|
||||
}
|
||||
|
||||
void test_put_returns_false_when_full(void)
|
||||
{
|
||||
/* Заполнить буфер до краёв */
|
||||
for (uint8_t i = 0; i < BUF_SIZE; i++)
|
||||
{
|
||||
TEST_ASSERT_TRUE(ring_buffer_put(&rb, i));
|
||||
}
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_full(&rb));
|
||||
|
||||
/* Следующий put должен провалиться */
|
||||
TEST_ASSERT_FALSE(ring_buffer_put(&rb, 0xFF));
|
||||
/* Данные при этом не повреждены */
|
||||
TEST_ASSERT_EQUAL_size_t(BUF_SIZE, ring_buffer_count(&rb));
|
||||
}
|
||||
|
||||
void test_fifo_ordering(void)
|
||||
{
|
||||
/* Проверяем порядок FIFO: первым вошёл — первым вышел. */
|
||||
for (uint8_t i = 1U; i <= 4U; i++)
|
||||
{
|
||||
ring_buffer_put(&rb, i);
|
||||
}
|
||||
|
||||
uint8_t out;
|
||||
for (uint8_t i = 1U; i <= 4U; i++)
|
||||
{
|
||||
TEST_ASSERT_TRUE(ring_buffer_get(&rb, &out));
|
||||
TEST_ASSERT_EQUAL_UINT8(i, out);
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
* 3. Write / Read — блочные операции
|
||||
* ═══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_write_read_full_block(void)
|
||||
{
|
||||
const uint8_t src[] = { 0x01, 0x02, 0x03, 0x04 };
|
||||
uint8_t dst[4] = { 0 };
|
||||
|
||||
TEST_ASSERT_EQUAL_size_t(4U, ring_buffer_write(&rb, src, 4U));
|
||||
TEST_ASSERT_EQUAL_size_t(4U, ring_buffer_count(&rb));
|
||||
|
||||
TEST_ASSERT_EQUAL_size_t(4U, ring_buffer_read(&rb, dst, 4U));
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(src, dst, 4U);
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_empty(&rb));
|
||||
}
|
||||
|
||||
void test_write_partial_when_nearly_full(void)
|
||||
{
|
||||
/* Заполнить 6 из 8 байт */
|
||||
uint8_t fill[6] = { 0 };
|
||||
ring_buffer_write(&rb, fill, 6U);
|
||||
TEST_ASSERT_EQUAL_size_t(2U, ring_buffer_free(&rb));
|
||||
|
||||
/* Попытка записать 5, должно записаться 2 */
|
||||
const uint8_t src[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE };
|
||||
size_t written = ring_buffer_write(&rb, src, 5U);
|
||||
|
||||
TEST_ASSERT_EQUAL_size_t(2U, written);
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_full(&rb));
|
||||
}
|
||||
|
||||
void test_read_partial_when_not_enough_data(void)
|
||||
{
|
||||
ring_buffer_put(&rb, 0x11);
|
||||
ring_buffer_put(&rb, 0x22);
|
||||
|
||||
uint8_t dst[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
size_t n = ring_buffer_read(&rb, dst, 5U);
|
||||
|
||||
/* Прочитано только то, что было */
|
||||
TEST_ASSERT_EQUAL_size_t(2U, n);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x11, dst[0]);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x22, dst[1]);
|
||||
/* Остаток dst не тронут */
|
||||
TEST_ASSERT_EQUAL_UINT8(0xFF, dst[2]);
|
||||
}
|
||||
|
||||
void test_read_from_empty_returns_zero(void)
|
||||
{
|
||||
uint8_t dst[4];
|
||||
TEST_ASSERT_EQUAL_size_t(0U, ring_buffer_read(&rb, dst, 4U));
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
* 4. Full / Empty — граничные состояния
|
||||
* ═══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_empty_after_init(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_empty(&rb));
|
||||
TEST_ASSERT_FALSE(ring_buffer_is_full(&rb));
|
||||
TEST_ASSERT_EQUAL_size_t(0U, ring_buffer_count(&rb));
|
||||
TEST_ASSERT_EQUAL_size_t(BUF_SIZE, ring_buffer_free(&rb));
|
||||
}
|
||||
|
||||
void test_full_after_filling(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < BUF_SIZE; i++)
|
||||
{
|
||||
ring_buffer_put(&rb, i);
|
||||
}
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_full(&rb));
|
||||
TEST_ASSERT_FALSE(ring_buffer_is_empty(&rb));
|
||||
TEST_ASSERT_EQUAL_size_t(BUF_SIZE, ring_buffer_count(&rb));
|
||||
TEST_ASSERT_EQUAL_size_t(0U, ring_buffer_free(&rb));
|
||||
}
|
||||
|
||||
void test_not_full_after_one_read_from_full(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < BUF_SIZE; i++)
|
||||
{
|
||||
ring_buffer_put(&rb, i);
|
||||
}
|
||||
uint8_t out;
|
||||
ring_buffer_get(&rb, &out);
|
||||
|
||||
TEST_ASSERT_FALSE(ring_buffer_is_full(&rb));
|
||||
TEST_ASSERT_EQUAL_size_t(BUF_SIZE - 1U, ring_buffer_count(&rb));
|
||||
TEST_ASSERT_EQUAL_size_t(1U, ring_buffer_free(&rb));
|
||||
}
|
||||
|
||||
void test_empty_after_draining_full_buffer(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < BUF_SIZE; i++)
|
||||
{
|
||||
ring_buffer_put(&rb, i);
|
||||
}
|
||||
uint8_t out;
|
||||
for (size_t i = 0; i < BUF_SIZE; i++)
|
||||
{
|
||||
ring_buffer_get(&rb, &out);
|
||||
}
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_empty(&rb));
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
* 5. Wraparound — корректность при переполнении индексов
|
||||
*
|
||||
* Это самые важные тесты: именно здесь обычно живут баги.
|
||||
* Цикл: заполнить → слить → заполнить снова — индексы пройдут через
|
||||
* границу массива, маска & должна корректно их завернуть.
|
||||
* ═══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_wraparound_single_bytes(void)
|
||||
{
|
||||
uint8_t out;
|
||||
|
||||
/* Несколько полных циклов fill→drain, чтобы гарантированно
|
||||
* пройти wraparound несколько раз. */
|
||||
for (int cycle = 0; cycle < 4; cycle++)
|
||||
{
|
||||
for (uint8_t i = 0; i < BUF_SIZE; i++)
|
||||
{
|
||||
TEST_ASSERT_TRUE(ring_buffer_put(&rb, i));
|
||||
}
|
||||
for (uint8_t i = 0; i < BUF_SIZE; i++)
|
||||
{
|
||||
TEST_ASSERT_TRUE(ring_buffer_get(&rb, &out));
|
||||
TEST_ASSERT_EQUAL_UINT8(i, out);
|
||||
}
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_empty(&rb));
|
||||
}
|
||||
}
|
||||
|
||||
void test_wraparound_partial_overlap(void)
|
||||
{
|
||||
uint8_t out;
|
||||
|
||||
/* Сдвинуть индексы к краю массива: записать 6, прочитать 6. */
|
||||
for (uint8_t i = 0; i < 6U; i++)
|
||||
ring_buffer_put(&rb, i);
|
||||
for (uint8_t i = 0; i < 6U; i++)
|
||||
ring_buffer_get(&rb, &out);
|
||||
|
||||
/* Теперь tail≈6, head≈6. Записать 5 — запись перейдёт через край. */
|
||||
const uint8_t src[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE };
|
||||
TEST_ASSERT_EQUAL_size_t(5U, ring_buffer_write(&rb, src, 5U));
|
||||
|
||||
/* Прочитать обратно — порядок должен сохраниться. */
|
||||
uint8_t dst[5];
|
||||
TEST_ASSERT_EQUAL_size_t(5U, ring_buffer_read(&rb, dst, 5U));
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(src, dst, 5U);
|
||||
}
|
||||
|
||||
void test_wraparound_preserves_data_integrity(void)
|
||||
{
|
||||
/* Заполнить буфер контрольными значениями, слить наполовину,
|
||||
* дозаписать новые данные — проверить весь порядок. */
|
||||
for (uint8_t i = 0; i < BUF_SIZE; i++)
|
||||
{
|
||||
ring_buffer_put(&rb, i * 10U); /* 0, 10, 20, 30, 40, 50, 60, 70 */
|
||||
}
|
||||
|
||||
uint8_t out;
|
||||
/* Слить первые 4 */
|
||||
for (uint8_t i = 0; i < 4U; i++)
|
||||
{
|
||||
ring_buffer_get(&rb, &out);
|
||||
TEST_ASSERT_EQUAL_UINT8(i * 10U, out);
|
||||
}
|
||||
|
||||
/* Дозаписать 4 новых значения */
|
||||
for (uint8_t i = 0; i < 4U; i++)
|
||||
{
|
||||
ring_buffer_put(&rb, 0xA0U + i);
|
||||
}
|
||||
|
||||
/* Прочитать оставшиеся старые 4 */
|
||||
for (uint8_t i = 4U; i < BUF_SIZE; i++)
|
||||
{
|
||||
ring_buffer_get(&rb, &out);
|
||||
TEST_ASSERT_EQUAL_UINT8(i * 10U, out);
|
||||
}
|
||||
|
||||
/* Прочитать новые 4 */
|
||||
for (uint8_t i = 0; i < 4U; i++)
|
||||
{
|
||||
ring_buffer_get(&rb, &out);
|
||||
TEST_ASSERT_EQUAL_UINT8(0xA0U + i, out);
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_empty(&rb));
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
* 6. SPSC simulation — имитация ISR-producer / task-consumer
|
||||
*
|
||||
* В реальном коде ISR пишет байты, задача читает.
|
||||
* Здесь симулируем это в один поток, но чередуя операции —
|
||||
* проверяем, что счётчики не расходятся при долгой работе.
|
||||
* ═══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_spsc_interleaved_puts_and_gets(void)
|
||||
{
|
||||
uint8_t out;
|
||||
uint8_t expected = 0U;
|
||||
uint8_t next_in = 0U;
|
||||
|
||||
/* 200 итераций: ISR кладёт 3, задача забирает 2.
|
||||
* Буфер постепенно заполняется, потом стабилизируется у верхней границы.
|
||||
* Главное — порядок данных сохранён. */
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
/* "ISR": положить до 3 байт если есть место */
|
||||
for (int p = 0; p < 3; p++)
|
||||
{
|
||||
if (!ring_buffer_is_full(&rb))
|
||||
{
|
||||
ring_buffer_put(&rb, next_in++);
|
||||
}
|
||||
}
|
||||
/* "Task": забрать до 2 байт */
|
||||
for (int c = 0; c < 2; c++)
|
||||
{
|
||||
if (!ring_buffer_is_empty(&rb))
|
||||
{
|
||||
ring_buffer_get(&rb, &out);
|
||||
TEST_ASSERT_EQUAL_UINT8(expected++, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
* 7. Reset
|
||||
* ═══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_reset_clears_data(void)
|
||||
{
|
||||
ring_buffer_put(&rb, 0xDE);
|
||||
ring_buffer_put(&rb, 0xAD);
|
||||
TEST_ASSERT_EQUAL_size_t(2U, ring_buffer_count(&rb));
|
||||
|
||||
ring_buffer_reset(&rb);
|
||||
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_empty(&rb));
|
||||
TEST_ASSERT_EQUAL_size_t(0U, ring_buffer_count(&rb));
|
||||
TEST_ASSERT_EQUAL_size_t(BUF_SIZE, ring_buffer_free(&rb));
|
||||
}
|
||||
|
||||
void test_reset_allows_reuse(void)
|
||||
{
|
||||
/* Заполнить, сбросить, снова использовать — не должно быть артефактов. */
|
||||
for (uint8_t i = 0; i < BUF_SIZE; i++)
|
||||
ring_buffer_put(&rb, i);
|
||||
ring_buffer_reset(&rb);
|
||||
|
||||
ring_buffer_put(&rb, 0x42);
|
||||
uint8_t out = 0;
|
||||
ring_buffer_get(&rb, &out);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x42, out);
|
||||
TEST_ASSERT_TRUE(ring_buffer_is_empty(&rb));
|
||||
}
|
||||
|
||||
/* ── Runner ──────────────────────────────────────────────────────────── */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
||||
/* Init */
|
||||
RUN_TEST(test_init_valid);
|
||||
RUN_TEST(test_init_rejects_null_rb);
|
||||
RUN_TEST(test_init_rejects_null_buf);
|
||||
RUN_TEST(test_init_rejects_zero_size);
|
||||
RUN_TEST(test_init_rejects_non_power_of_two);
|
||||
RUN_TEST(test_init_accepts_all_valid_powers_of_two);
|
||||
|
||||
/* Put / Get */
|
||||
RUN_TEST(test_put_get_single_byte);
|
||||
RUN_TEST(test_get_from_empty_returns_false);
|
||||
RUN_TEST(test_put_returns_false_when_full);
|
||||
RUN_TEST(test_fifo_ordering);
|
||||
|
||||
/* Write / Read */
|
||||
RUN_TEST(test_write_read_full_block);
|
||||
RUN_TEST(test_write_partial_when_nearly_full);
|
||||
RUN_TEST(test_read_partial_when_not_enough_data);
|
||||
RUN_TEST(test_read_from_empty_returns_zero);
|
||||
|
||||
/* Full / Empty */
|
||||
RUN_TEST(test_empty_after_init);
|
||||
RUN_TEST(test_full_after_filling);
|
||||
RUN_TEST(test_not_full_after_one_read_from_full);
|
||||
RUN_TEST(test_empty_after_draining_full_buffer);
|
||||
|
||||
/* Wraparound */
|
||||
RUN_TEST(test_wraparound_single_bytes);
|
||||
RUN_TEST(test_wraparound_partial_overlap);
|
||||
RUN_TEST(test_wraparound_preserves_data_integrity);
|
||||
|
||||
/* SPSC simulation */
|
||||
RUN_TEST(test_spsc_interleaved_puts_and_gets);
|
||||
|
||||
/* Reset */
|
||||
RUN_TEST(test_reset_clears_data);
|
||||
RUN_TEST(test_reset_allows_reuse);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
222
tests/host/timeout/test_timeout_pattern.c
Normal file
222
tests/host/timeout/test_timeout_pattern.c
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/**
|
||||
* @file test_timeout_pattern.c
|
||||
* @brief Тесты паттерна таймаута (bsp_tick_get_ms() - start) < timeout.
|
||||
*
|
||||
* Паттерн используется в bsp_uart_read(), bsp_delay() и других
|
||||
* BSP-модулях. Тестируем математику изолированно — без железа и моков.
|
||||
*
|
||||
* Ключевое свойство: беззнаковая арифметика uint32_t корректна
|
||||
* при wraparound счётчика (переполнение через UINT32_MAX).
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
}
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* ── Вспомогательный макрос — сам паттерн ───────────────────────────── */
|
||||
|
||||
/**
|
||||
* Возвращает 1 если таймаут истёк.
|
||||
* now и start — uint32_t (как bsp_tick_get_ms()).
|
||||
*/
|
||||
#define TIMEOUT_EXPIRED(now, start, timeout_ms) \
|
||||
(((uint32_t) (now) - (uint32_t) (start)) >= (uint32_t) (timeout_ms))
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
* 1. Нормальные случаи (без wraparound)
|
||||
* ═══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_not_expired_elapsed_less_than_timeout(void)
|
||||
{
|
||||
uint32_t start = 1000U;
|
||||
uint32_t now = 1099U;
|
||||
uint32_t timeout = 100U;
|
||||
|
||||
TEST_ASSERT_FALSE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
}
|
||||
|
||||
void test_expired_elapsed_equals_timeout(void)
|
||||
{
|
||||
/* Граничное условие: elapsed == timeout → истёк. */
|
||||
uint32_t start = 1000U;
|
||||
uint32_t now = 1100U;
|
||||
uint32_t timeout = 100U;
|
||||
|
||||
TEST_ASSERT_TRUE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
}
|
||||
|
||||
void test_expired_elapsed_greater_than_timeout(void)
|
||||
{
|
||||
uint32_t start = 1000U;
|
||||
uint32_t now = 1200U;
|
||||
uint32_t timeout = 100U;
|
||||
|
||||
TEST_ASSERT_TRUE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
}
|
||||
|
||||
void test_not_expired_at_zero_elapsed(void)
|
||||
{
|
||||
/* now == start: ничего не прошло. */
|
||||
uint32_t start = 5000U;
|
||||
uint32_t now = 5000U;
|
||||
uint32_t timeout = 1U;
|
||||
|
||||
TEST_ASSERT_FALSE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
}
|
||||
|
||||
void test_zero_timeout_always_expired(void)
|
||||
{
|
||||
/*
|
||||
* timeout == 0: 0 >= 0 всегда true.
|
||||
* Это ожидаемое поведение — вызывающий код трактует 0
|
||||
* как «не ждать вообще».
|
||||
*/
|
||||
uint32_t start = 1000U;
|
||||
uint32_t now = 1000U;
|
||||
uint32_t timeout = 0U;
|
||||
|
||||
TEST_ASSERT_TRUE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
* 2. Wraparound — счётчик переполнился через UINT32_MAX
|
||||
* ═══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_wraparound_not_expired(void)
|
||||
{
|
||||
/*
|
||||
* start близко к UINT32_MAX, now уже за нулём.
|
||||
* elapsed = (50 - (UINT32_MAX - 49)) = 100 в беззнаковой арифметике.
|
||||
* timeout = 150 → ещё не истёк.
|
||||
*/
|
||||
uint32_t start = UINT32_MAX - 49U; /* 4294967246 */
|
||||
uint32_t now = 50U;
|
||||
uint32_t timeout = 150U;
|
||||
|
||||
/* elapsed = now - start = 50 - 4294967246 = 100 (mod 2^32) */
|
||||
TEST_ASSERT_EQUAL_UINT32(100U, now - start);
|
||||
TEST_ASSERT_FALSE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
}
|
||||
|
||||
void test_wraparound_exactly_at_timeout(void)
|
||||
{
|
||||
uint32_t start = UINT32_MAX - 49U;
|
||||
uint32_t now = 50U;
|
||||
uint32_t timeout = 100U; /* elapsed == timeout → истёк */
|
||||
|
||||
TEST_ASSERT_TRUE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
}
|
||||
|
||||
void test_wraparound_expired(void)
|
||||
{
|
||||
uint32_t start = UINT32_MAX - 49U;
|
||||
uint32_t now = 100U;
|
||||
uint32_t timeout = 100U;
|
||||
|
||||
/* elapsed = 150 >= 100 → истёк */
|
||||
TEST_ASSERT_TRUE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
}
|
||||
|
||||
void test_wraparound_start_at_max(void)
|
||||
{
|
||||
/* start = UINT32_MAX, now = 0: elapsed = 1 */
|
||||
uint32_t start = UINT32_MAX;
|
||||
uint32_t now = 0U;
|
||||
uint32_t timeout = 1U;
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32(1U, now - start);
|
||||
TEST_ASSERT_TRUE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
}
|
||||
|
||||
void test_wraparound_start_at_max_not_expired(void)
|
||||
{
|
||||
/* start = UINT32_MAX, now = 0: elapsed = 1, timeout = 2 → не истёк */
|
||||
uint32_t start = UINT32_MAX;
|
||||
uint32_t now = 0U;
|
||||
uint32_t timeout = 2U;
|
||||
|
||||
TEST_ASSERT_FALSE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
* 3. Почему альтернативный паттерн НЕВЕРЕН
|
||||
*
|
||||
* Документируем антипаттерн: (now >= start + timeout).
|
||||
* При wraparound start + timeout переполняется и сравнение ломается.
|
||||
* Эти тесты показывают конкретные случаи поломки.
|
||||
* ═══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_antipattern_breaks_at_wraparound(void)
|
||||
{
|
||||
/*
|
||||
* Антипаттерн: now >= (start + timeout)
|
||||
* start = UINT32_MAX - 49, timeout = 100
|
||||
* start + timeout = UINT32_MAX - 49 + 100 = 50 (переполнение!)
|
||||
*
|
||||
* now = 80: реальный elapsed = 130 >= 100 → должно быть expired.
|
||||
* Но антипаттерн: 80 >= 50 → true (случайно верно здесь)
|
||||
*
|
||||
* now = 30: реальный elapsed = 80 < 100 → не истёк.
|
||||
* Антипаттерн: 30 >= 50 → false (верно, но по случайности)
|
||||
*
|
||||
* now = 40: реальный elapsed = 90 < 100 → не истёк.
|
||||
* Антипаттерн: 40 >= 50 → false (верно)
|
||||
*
|
||||
* now = 60: реальный elapsed = 110 >= 100 → истёк.
|
||||
* Антипаттерн: 60 >= 50 → true (верно)
|
||||
*
|
||||
* Сложный случай — start близко к MAX, timeout большой:
|
||||
* start = UINT32_MAX - 10, timeout = UINT32_MAX - 100
|
||||
* start + timeout переполняется в очень маленькое число →
|
||||
* антипаттерн скажет "истёк" почти сразу.
|
||||
*/
|
||||
|
||||
uint32_t start = UINT32_MAX - 10U;
|
||||
uint32_t timeout = 1000U;
|
||||
uint32_t now = start + 5U; /* elapsed = 5, далеко до timeout */
|
||||
|
||||
/* Правильный паттерн: не истёк (5 < 1000) */
|
||||
TEST_ASSERT_FALSE(TIMEOUT_EXPIRED(now, start, timeout));
|
||||
|
||||
/* Антипаттерн: start + timeout переполнился → (start+timeout) маленькое,
|
||||
* now > (start+timeout) → антипаттерн скажет "истёк" — НЕВЕРНО. */
|
||||
uint32_t wrong_deadline = start + timeout; /* wraparound! */
|
||||
int antipattern_result = (now >= wrong_deadline);
|
||||
|
||||
/* Демонстрируем что антипаттерн даёт неверный результат: */
|
||||
TEST_ASSERT_TRUE(antipattern_result); /* антипаттерн говорит "истёк" */
|
||||
TEST_ASSERT_FALSE(TIMEOUT_EXPIRED(now, start, timeout)); /* правильно: нет */
|
||||
}
|
||||
|
||||
/* ── Runner ──────────────────────────────────────────────────────────── */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
||||
/* Нормальные случаи */
|
||||
RUN_TEST(test_not_expired_elapsed_less_than_timeout);
|
||||
RUN_TEST(test_expired_elapsed_equals_timeout);
|
||||
RUN_TEST(test_expired_elapsed_greater_than_timeout);
|
||||
RUN_TEST(test_not_expired_at_zero_elapsed);
|
||||
RUN_TEST(test_zero_timeout_always_expired);
|
||||
|
||||
/* Wraparound */
|
||||
RUN_TEST(test_wraparound_not_expired);
|
||||
RUN_TEST(test_wraparound_exactly_at_timeout);
|
||||
RUN_TEST(test_wraparound_expired);
|
||||
RUN_TEST(test_wraparound_start_at_max);
|
||||
RUN_TEST(test_wraparound_start_at_max_not_expired);
|
||||
|
||||
/* Антипаттерн */
|
||||
RUN_TEST(test_antipattern_breaks_at_wraparound);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
14
utils/CMakeLists.txt
Normal file
14
utils/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# utils/CMakeLists.txt
|
||||
#
|
||||
# Платформо-независимые утилиты. Не зависят от SDK, BSP или железа —
|
||||
# компилируются на хосте и таргете.
|
||||
#
|
||||
# Правило включения: код попадает сюда только если выполняются ОБА условия: 1.
|
||||
# Не зависит от конкретного железа (нет fsl_*, CMSIS, FreeRTOS). 2. Используется
|
||||
# более чем в одном месте проекта.
|
||||
|
||||
add_library(utils STATIC ring_buffer/ring_buffer.c)
|
||||
|
||||
# PUBLIC: любой таргет, слинкованный с utils, автоматически получает корень
|
||||
# utils/ в include path → #include "ring_buffer/ring_buffer.h"
|
||||
target_include_directories(utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
48
utils/README.md
Normal file
48
utils/README.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# utils
|
||||
|
||||
Платформо-независимые утилиты проекта.
|
||||
|
||||
**Правило включения** — код попадает сюда только если выполняются оба условия:
|
||||
|
||||
- не зависит от железа (нет `fsl_*`, CMSIS, FreeRTOS, BSP);
|
||||
- используется более чем в одном месте проекта.
|
||||
|
||||
Все модули компилируются на хосте (host-тесты) и на таргете без изменений.
|
||||
|
||||
---
|
||||
|
||||
## Модули
|
||||
|
||||
### `ring_buffer`
|
||||
|
||||
Кольцевой буфер байт — SPSC (single-producer / single-consumer), lock-free.
|
||||
|
||||
**Типичное использование:** ISR пишет принятые байты, задача или основной цикл
|
||||
читает. Не требует отключения прерываний при условии единственного producer и
|
||||
единственного consumer.
|
||||
|
||||
| Параметр | Значение |
|
||||
|---|---|
|
||||
| Элемент | 1 байт (`uint8_t`) |
|
||||
| Ёмкость | любая степень двойки, задаётся при `ring_buffer_init` |
|
||||
| Thread-safety | SPSC без блокировок; multi-producer/consumer — только с внешней синхронизацией |
|
||||
| Зависимости | `<stdint.h>`, `<stddef.h>`, `<stdbool.h>` |
|
||||
|
||||
```c
|
||||
#include "ring_buffer/ring_buffer.h"
|
||||
|
||||
static uint8_t storage[256];
|
||||
static ring_buffer_t rb;
|
||||
|
||||
// Инициализация (размер — степень двойки)
|
||||
ring_buffer_init(&rb, storage, sizeof(storage));
|
||||
|
||||
// Запись (например, из ISR)
|
||||
ring_buffer_put(&rb, byte);
|
||||
|
||||
// Чтение (например, из задачи)
|
||||
uint8_t b;
|
||||
if (ring_buffer_get(&rb, &b)) { /* обработать b */ }
|
||||
```
|
||||
|
||||
Тесты: `tests/host/test_ring_buffer.c` (24 теста, включая wraparound и SPSC-симуляцию).
|
||||
143
utils/ring_buffer/ring_buffer.c
Normal file
143
utils/ring_buffer/ring_buffer.c
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/**
|
||||
* @file ring_buffer.c
|
||||
* @brief Реализация кольцевого буфера (SPSC, lock-free).
|
||||
*
|
||||
* Ключевой инвариант SPSC-безопасности:
|
||||
* - producer читает head (чужой индекс) только для проверки is_full.
|
||||
* - consumer читает tail (чужой индекс) только для проверки is_empty/count.
|
||||
* - Каждый индекс пишет только его владелец.
|
||||
* - volatile гарантирует, что компилятор не кэширует чужой индекс.
|
||||
* - Беззнаковый wraparound size_t корректен: (0 - 1) == SIZE_MAX,
|
||||
* арифметика modulo 2^N работает без явной обёртки.
|
||||
*/
|
||||
|
||||
#include "ring_buffer.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* ── Вспомогательная функция ─────────────────────────────────────────── */
|
||||
|
||||
/** Проверить, является ли n степенью двойки (и n > 0). */
|
||||
static inline bool is_power_of_two(size_t n)
|
||||
{
|
||||
return (n > 0U) && ((n & (n - 1U)) == 0U);
|
||||
}
|
||||
|
||||
/* ── Инициализация ───────────────────────────────────────────────────── */
|
||||
|
||||
bool ring_buffer_init(ring_buffer_desc_t *p_desc, uint8_t *p_buf, size_t size)
|
||||
{
|
||||
if (p_desc == NULL || p_buf == NULL || !is_power_of_two(size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
p_desc->buf = p_buf;
|
||||
p_desc->size = size;
|
||||
p_desc->mask = size - 1U;
|
||||
p_desc->tail = 0U;
|
||||
p_desc->head = 0U;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ring_buffer_reset(ring_buffer_desc_t *p_desc)
|
||||
{
|
||||
p_desc->tail = 0U;
|
||||
p_desc->head = 0U;
|
||||
}
|
||||
|
||||
/* ── Состояние ───────────────────────────────────────────────────────── */
|
||||
|
||||
bool ring_buffer_is_empty(const ring_buffer_desc_t *p_desc)
|
||||
{
|
||||
return p_desc->tail == p_desc->head;
|
||||
}
|
||||
|
||||
bool ring_buffer_is_full(const ring_buffer_desc_t *p_desc)
|
||||
{
|
||||
return (p_desc->tail - p_desc->head) == p_desc->size;
|
||||
}
|
||||
|
||||
size_t ring_buffer_count(const ring_buffer_desc_t *p_desc)
|
||||
{
|
||||
/* Беззнаковое вычитание: корректно при любом wraparound. */
|
||||
return p_desc->tail - p_desc->head;
|
||||
}
|
||||
|
||||
size_t ring_buffer_free(const ring_buffer_desc_t *p_desc)
|
||||
{
|
||||
return p_desc->size - (p_desc->tail - p_desc->head);
|
||||
}
|
||||
|
||||
/* ── Запись (producer) ───────────────────────────────────────────────── */
|
||||
|
||||
bool ring_buffer_put(ring_buffer_desc_t *p_desc, uint8_t byte)
|
||||
{
|
||||
if (ring_buffer_is_full(p_desc))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Порядок важен для SPSC:
|
||||
* 1. Записать данные в ячейку.
|
||||
* 2. Только потом продвинуть tail — это делает байт видимым consumer-у.
|
||||
*
|
||||
* На Cortex-M7 (strongly-ordered memory model) барьер не нужен.
|
||||
* На weakly-ordered архитектурах (ARM64, RISC-V) нужен store-release.
|
||||
* Если понадобится портируемость — добавить __atomic_store_n.
|
||||
*/
|
||||
p_desc->buf[p_desc->tail & p_desc->mask] = byte;
|
||||
p_desc->tail++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t ring_buffer_write(ring_buffer_desc_t *p_desc, const uint8_t *p_data, size_t len)
|
||||
{
|
||||
size_t written = 0U;
|
||||
|
||||
while (written < len && !ring_buffer_is_full(p_desc))
|
||||
{
|
||||
p_desc->buf[p_desc->tail & p_desc->mask] = p_data[written];
|
||||
p_desc->tail++;
|
||||
written++;
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
/* ── Чтение (consumer) ───────────────────────────────────────────────── */
|
||||
|
||||
bool ring_buffer_get(ring_buffer_desc_t *p_desc, uint8_t *p_byte)
|
||||
{
|
||||
if (ring_buffer_is_empty(p_desc))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Симметрично put():
|
||||
* 1. Прочитать данные из ячейки.
|
||||
* 2. Продвинуть head — освобождает место для producer-а.
|
||||
*/
|
||||
*p_byte = p_desc->buf[p_desc->head & p_desc->mask];
|
||||
p_desc->head++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t ring_buffer_read(ring_buffer_desc_t *p_desc, uint8_t *p_data, size_t len)
|
||||
{
|
||||
size_t read = 0U;
|
||||
|
||||
while (read < len && !ring_buffer_is_empty(p_desc))
|
||||
{
|
||||
p_data[read] = p_desc->buf[p_desc->head & p_desc->mask];
|
||||
p_desc->head++;
|
||||
read++;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
105
utils/ring_buffer/ring_buffer.h
Normal file
105
utils/ring_buffer/ring_buffer.h
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* @file ring_buffer.h
|
||||
* @brief Кольцевой буфер — SPSC, lock-free для Cortex-M и host.
|
||||
*
|
||||
* Конвенция индексов:
|
||||
* tail — индекс следующей ЗАПИСИ (двигает producer / ISR)
|
||||
* head — индекс следующего ЧТЕНИЯ (двигает consumer / задача)
|
||||
*
|
||||
* Запись: buf[tail & mask] = byte; tail++;
|
||||
* Чтение: byte = buf[head & mask]; head++;
|
||||
* Счётчик: tail - head (беззнаковая арифметика, wraparound корректен)
|
||||
*
|
||||
* запись чтение
|
||||
* ↓ ↓
|
||||
* ... [ ][ ][tail][D][D][D][D][D][head][ ][ ] ...
|
||||
* ←───── данные ──────→
|
||||
*
|
||||
* Гарантии потокобезопасности:
|
||||
* - Ровно один writer и один reader (SPSC).
|
||||
* - Запись и чтение не требуют отключения прерываний.
|
||||
* - head и tail объявлены volatile — компилятор не кэширует в регистрах.
|
||||
* - На Cortex-M запись/чтение size_t (32 бит) атомарны по архитектуре.
|
||||
*
|
||||
* Ограничения:
|
||||
* - size ОБЯЗАНА быть степенью двойки (проверяется в ring_buffer_init).
|
||||
* - Элемент — ровно 1 байт. Для 9-bit UART используй uint8_t[2] снаружи.
|
||||
* - Multi-producer/consumer — только с внешней синхронизацией.
|
||||
*
|
||||
* Платформо-независим: зависит только от <stdint.h>, <stddef.h>, <stdbool.h>.
|
||||
*/
|
||||
#ifndef RING_BUFFER_H_
|
||||
#define RING_BUFFER_H_
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t *buf; /**< Указатель на внешний массив данных. */
|
||||
size_t size; /**< Размер массива (степень двойки). */
|
||||
size_t mask; /**< size - 1, для быстрого & вместо %. */
|
||||
volatile size_t head; /**< Индекс следующего чтения (consumer). */
|
||||
volatile size_t tail; /**< Индекс следующей записи (producer). */
|
||||
} ring_buffer_desc_t;
|
||||
|
||||
/**
|
||||
* @brief Инициализировать дескриптор.
|
||||
*
|
||||
* @param p_desc Дескриптор (не NULL).
|
||||
* @param p_buf Внешний массив (не NULL, размер = @p size байт).
|
||||
* @param size Размер массива — ОБЯЗАТЕЛЬНО степень двойки.
|
||||
* @return true при успехе, false если size не степень двойки.
|
||||
*/
|
||||
bool ring_buffer_init(ring_buffer_desc_t *p_desc, uint8_t *p_buf, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Сбросить буфер в пустое состояние.
|
||||
*
|
||||
* @warning Не thread-safe. Вызов только когда ни producer,
|
||||
* ни consumer не активны (например, при переинициализации).
|
||||
*/
|
||||
void ring_buffer_reset(ring_buffer_desc_t *p_desc);
|
||||
|
||||
/**
|
||||
* @brief Положить один байт.
|
||||
* @return true если байт записан, false если буфер полон.
|
||||
*/
|
||||
bool ring_buffer_put(ring_buffer_desc_t *p_desc, uint8_t byte);
|
||||
|
||||
/* ── Запись (producer side) ──────────────────────────────────────────── */
|
||||
/**
|
||||
* @brief Положить блок байт.
|
||||
* @return Количество реально записанных байт (< len если буфер заполнился).
|
||||
*/
|
||||
size_t ring_buffer_write(ring_buffer_desc_t *p_desc, const uint8_t *p_data, size_t len);
|
||||
|
||||
/* ── Чтение (consumer side) ──────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* @brief Прочитать один байт.
|
||||
* @return true если байт получен, false если буфер пуст.
|
||||
*/
|
||||
bool ring_buffer_get(ring_buffer_desc_t *p_desc, uint8_t *p_byte);
|
||||
|
||||
/**
|
||||
* @brief Прочитать блок байт.
|
||||
* @return Количество реально прочитанных байт.
|
||||
*/
|
||||
size_t ring_buffer_read(ring_buffer_desc_t *p_desc, uint8_t *p_data, size_t len);
|
||||
|
||||
/* ── Состояние ───────────────────────────────────────────────────────── */
|
||||
|
||||
/** @return true если буфер пуст (head == tail). */
|
||||
bool ring_buffer_is_empty(const ring_buffer_desc_t *p_desc);
|
||||
|
||||
/** @return true если буфер полон (tail - head == size). */
|
||||
bool ring_buffer_is_full(const ring_buffer_desc_t *p_desc);
|
||||
|
||||
/** @return Количество байт, доступных для чтения. */
|
||||
size_t ring_buffer_count(const ring_buffer_desc_t *p_desc);
|
||||
|
||||
/** @return Количество байт, доступных для записи. */
|
||||
size_t ring_buffer_free(const ring_buffer_desc_t *p_desc);
|
||||
|
||||
#endif //RING_BUFFER_H_
|
||||
Loading…
Reference in a new issue