# 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": [
|
"extensions": [
|
||||||
"actboy168.tasks",
|
"actboy168.tasks",
|
||||||
"bierner.markdown-preview-github-styles",
|
"bierner.markdown-preview-github-styles",
|
||||||
"brobeson.ctest-lab",
|
|
||||||
"cheshirekow.cmake-format",
|
"cheshirekow.cmake-format",
|
||||||
"codeium.codeium",
|
|
||||||
"cschlosser.doxdocgen",
|
"cschlosser.doxdocgen",
|
||||||
"dan-c-underwood.arm",
|
"dan-c-underwood.arm",
|
||||||
"davidanson.vscode-markdownlint",
|
"davidanson.vscode-markdownlint",
|
||||||
"gruntfuggly.todo-tree",
|
"gruntfuggly.todo-tree",
|
||||||
"kylinideteam.cmake-intellisence",
|
"ms-vscode.cmake-tools",
|
||||||
"llvm-vs-code-extensions.vscode-clangd",
|
"llvm-vs-code-extensions.vscode-clangd",
|
||||||
"mcu-debug.debug-tracker-vscode",
|
"mcu-debug.debug-tracker-vscode",
|
||||||
"mcu-debug.memory-view",
|
"mcu-debug.memory-view",
|
||||||
"mcu-debug.peripheral-viewer",
|
"mcu-debug.peripheral-viewer",
|
||||||
|
"brobeson.ctest-lab",
|
||||||
"mcu-debug.rtos-views",
|
"mcu-debug.rtos-views",
|
||||||
"ms-vscode.hexeditor",
|
"ms-vscode.hexeditor",
|
||||||
"ms-vscode.makefile-tools",
|
"nefrob.vscode-just-syntax",
|
||||||
"probe-rs.probe-rs-debugger",
|
|
||||||
"rioj7.command-variable",
|
"rioj7.command-variable",
|
||||||
"trond-snekvik.gnu-mapfiles",
|
"trond-snekvik.gnu-mapfiles",
|
||||||
"twxs.cmake",
|
|
||||||
"usernamehw.errorlens",
|
"usernamehw.errorlens",
|
||||||
"wayou.vscode-todo-highlight",
|
"wayou.vscode-todo-highlight",
|
||||||
"xaver.clang-format",
|
"xaver.clang-format",
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ endif()
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
add_subdirectory(sdk)
|
add_subdirectory(sdk)
|
||||||
add_subdirectory(bsp)
|
add_subdirectory(bsp)
|
||||||
|
add_subdirectory(utils)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Сторонние и внутренние библиотеки
|
# Сторонние и внутренние библиотеки
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,9 @@
|
||||||
"displayName": "Host Tests — Debug",
|
"displayName": "Host Tests — Debug",
|
||||||
"configurePreset": "host-debug",
|
"configurePreset": "host-debug",
|
||||||
"targets": [
|
"targets": [
|
||||||
"test_bsp_led"
|
"test_bsp_led",
|
||||||
|
"test_ring_buffer",
|
||||||
|
"test_timeout_pattern"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -136,7 +138,9 @@
|
||||||
"displayName": "Host Tests — Release",
|
"displayName": "Host Tests — Release",
|
||||||
"configurePreset": "host-release",
|
"configurePreset": "host-release",
|
||||||
"targets": [
|
"targets": [
|
||||||
"test_bsp_led"
|
"test_bsp_led",
|
||||||
|
"test_ring_buffer",
|
||||||
|
"test_timeout_pattern"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ add_library(
|
||||||
generated/startup/startup_MIMXRT1052.S)
|
generated/startup/startup_MIMXRT1052.S)
|
||||||
|
|
||||||
add_subdirectory(led)
|
add_subdirectory(led)
|
||||||
|
add_subdirectory(tick)
|
||||||
|
|
||||||
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
||||||
target_compile_options(bsp_board PRIVATE -w)
|
target_compile_options(bsp_board PRIVATE -w)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
/*! @brief The board name */
|
/*! @brief The board name */
|
||||||
#define BOARD_NAME "TFT4.3_UB_BOARD"
|
#define BOARD_NAME "TFT4.3_UB_BOARD"
|
||||||
|
|
||||||
/*! //TODO: @brief The board name */
|
/*! @brief The board name */
|
||||||
void board_hw_init(void);
|
void board_hw_init(void);
|
||||||
|
|
||||||
#endif /* _BOARD_H_ */
|
#endif /* _BOARD_H_ */
|
||||||
|
|
@ -19,13 +19,13 @@
|
||||||
## API
|
## API
|
||||||
|
|
||||||
```c
|
```c
|
||||||
void led_init(void); // вызвать один раз после BOARD_InitPins()
|
void bsp_led_init(void); // вызвать один раз после board_hw_init()
|
||||||
|
|
||||||
void led_on(led_id_t id);
|
void bsp_led_on(led_id_t id);
|
||||||
void led_off(led_id_t id);
|
void bsp_led_off(led_id_t id);
|
||||||
void led_toggle(led_id_t id);
|
void bsp_led_toggle(led_id_t id);
|
||||||
void led_set(led_id_t id, bool on);
|
void bsp_led_set(led_id_t id, bool on);
|
||||||
bool led_get(led_id_t id);
|
bool bsp_led_get(led_id_t id);
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -36,14 +36,14 @@ bool led_get(led_id_t id);
|
||||||
#include "bsp/led.h"
|
#include "bsp/led.h"
|
||||||
|
|
||||||
// инициализация
|
// инициализация
|
||||||
led_init();
|
bsp_led_init();
|
||||||
|
|
||||||
// heartbeat
|
// heartbeat
|
||||||
led_toggle(LED_HEARTBEAT);
|
bsp_led_toggle(LED_HEARTBEAT);
|
||||||
|
|
||||||
// прикладная индикация
|
// прикладная индикация
|
||||||
led_on(LED_APP); // пакет принят / тест запущен
|
bsp_led_on(LED_APP); // пакет принят / тест запущен
|
||||||
led_off(LED_APP); // сброс
|
bsp_led_off(LED_APP); // сброс
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#pragma once
|
#ifndef BSP_LED_H_
|
||||||
|
#define BSP_LED_H_
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file led.h
|
* @file led.h
|
||||||
|
|
@ -7,7 +8,7 @@
|
||||||
* LED_HEARTBEAT — системный, мигает как признак жизни прошивки
|
* LED_HEARTBEAT — системный, мигает как признак жизни прошивки
|
||||||
* LED_APP — прикладной, управляется из firmware по ситуации
|
* LED_APP — прикладной, управляется из firmware по ситуации
|
||||||
*
|
*
|
||||||
* Пины сконфигурированы в generated/pin_mux. Этот хедер не знает
|
* Пины сконфигурированы в generated/pin_mux.h. Этот хедер не знает
|
||||||
* ни про GPIO-порты, ни про NXP SDK.
|
* ни про GPIO-порты, ни про NXP SDK.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -25,25 +26,27 @@ typedef enum
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Инициализация обоих светодиодов.
|
* @brief Инициализация обоих светодиодов.
|
||||||
* Вызвать один раз после BOARD_InitPins().
|
* Вызвать один раз после board_init_hw().
|
||||||
* После вызова оба LED выключены.
|
* После вызова оба LED выключены.
|
||||||
*/
|
*/
|
||||||
void led_init(void);
|
void bsp_led_init(void);
|
||||||
|
|
||||||
/** @brief Включить светодиод. */
|
/** @brief Включить светодиод. */
|
||||||
void led_on(led_id_t led_id);
|
void bsp_led_on(led_id_t led_id);
|
||||||
|
|
||||||
/** @brief Выключить светодиод. */
|
/** @brief Выключить светодиод. */
|
||||||
void led_off(led_id_t led_id);
|
void bsp_led_off(led_id_t led_id);
|
||||||
|
|
||||||
/** @brief Переключить состояние светодиода. */
|
/** @brief Переключить состояние светодиода. */
|
||||||
void led_toggle(led_id_t led_id);
|
void bsp_led_toggle(led_id_t led_id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Установить состояние светодиода явно.
|
* @brief Установить состояние светодиода явно.
|
||||||
* @param on true — включить, false — выключить
|
* @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 — горит). */
|
/** @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). */
|
/** Перевести логическое состояние в физический уровень 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;
|
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 ───────────────────────────────────────────────────── */
|
/* ── Публичный API ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
void led_init(void)
|
void bsp_led_init(void)
|
||||||
{
|
{
|
||||||
gpio_pin_config_t cfg = {
|
gpio_pin_config_t cfg = {
|
||||||
.direction = kGPIO_DigitalOutput,
|
.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;
|
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;
|
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(
|
target_link_libraries(
|
||||||
firmware_test
|
firmware_test
|
||||||
PRIVATE bsp_board bsp_led
|
PRIVATE bsp_board bsp_led bsp_tick
|
||||||
# Опциональные библиотеки
|
# Опциональные библиотеки
|
||||||
lib_external # SEGGER RTT если включён через SEGGER_RTT_ENABLED
|
lib_external # SEGGER RTT если включён через SEGGER_RTT_ENABLED
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,19 @@
|
||||||
#include "board.h"
|
#include "board.h"
|
||||||
#include "bsp/led.h"
|
#include "bsp/led.h"
|
||||||
|
#include "bsp/tick.h"
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
board_hw_init();
|
|
||||||
led_init();
|
|
||||||
|
|
||||||
led_on(LED_HEARTBEAT);
|
board_hw_init();
|
||||||
led_off(LED_APP);
|
bsp_led_init();
|
||||||
|
bsp_tick_init();
|
||||||
|
const uint16_t DELAY_MS = 1000;
|
||||||
while (1)
|
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-теста.
|
# Вспомогательная функция добавления host-теста.
|
||||||
#
|
#
|
||||||
# Использование: add_host_test( NAME <имя теста> SOURCES <тест.c>
|
# Использование: add_host_test( NAME <имя теста> SOURCES <тест.c> [<модуль.c>
|
||||||
# <тестируемый_модуль.c> ... MOCKS <путь к папке с stub-хедерами> #
|
# ...] # исходники теста + тестируемый код INCLUDES <dir> [<dir> ...] #
|
||||||
# опционально )
|
# дополнительные include-пути (опц.) MOCKS <dir> [<dir> ...] # папки
|
||||||
|
# со stub-хедерами (опц.) )
|
||||||
#
|
#
|
||||||
# Каждый тест — отдельный исполняемый файл. CTest запускает их все.
|
# Каждый тест — отдельный исполняемый файл. CTest запускает их все.
|
||||||
|
#
|
||||||
|
# Соглашения: - lib_external подключается всегда (Unity + fff). - INCLUDES и
|
||||||
|
# MOCKS — явные: каждый тест сам декларирует свои зависимости. Нет скрытых
|
||||||
|
# глобальных include-путей, специфичных для одного модуля.
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
function(add_host_test)
|
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})
|
add_executable(${ARG_NAME} ${ARG_SOURCES})
|
||||||
|
|
||||||
target_link_libraries(${ARG_NAME} PRIVATE lib_external)
|
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)
|
foreach(mock_dir IN LISTS ARG_MOCKS)
|
||||||
target_include_directories(${ARG_NAME} PRIVATE ${mock_dir})
|
target_include_directories(${ARG_NAME} PRIVATE ${mock_dir})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# публичные хедеры BSP нужны для #include "bsp/led.h"
|
|
||||||
target_include_directories(${ARG_NAME}
|
|
||||||
PRIVATE ${PROJECT_SOURCE_DIR}/bsp/led/include)
|
|
||||||
|
|
||||||
add_test(
|
add_test(
|
||||||
NAME ${ARG_NAME}
|
NAME ${ARG_NAME}
|
||||||
COMMAND ${ARG_NAME}
|
COMMAND ${ARG_NAME}
|
||||||
|
|
@ -44,8 +50,31 @@ add_host_test(
|
||||||
NAME
|
NAME
|
||||||
test_bsp_led
|
test_bsp_led
|
||||||
SOURCES
|
SOURCES
|
||||||
test_bsp_led.c
|
led/test_bsp_led.c
|
||||||
${PROJECT_SOURCE_DIR}/bsp/led/src/led.c # тестируемый модуль напрямую
|
${PROJECT_SOURCE_DIR}/bsp/led/src/led.c
|
||||||
|
INCLUDES
|
||||||
|
${PROJECT_SOURCE_DIR}/bsp/led/include # для #include "bsp/led.h"
|
||||||
MOCKS
|
MOCKS
|
||||||
${BSP_MOCKS_DIR} # fsl_gpio.h, pin_mux.h, board.h
|
${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
|
│ ├── fsl_lpuart.h
|
||||||
│ ├── pin_mux.h # зеркало макросов пинов из generated/
|
│ ├── pin_mux.h # зеркало макросов пинов из generated/
|
||||||
│ └── board.h
|
│ └── board.h
|
||||||
├── test_led.c # категория B — BSP-модуль
|
├── led/test_led.c # категория B — BSP-модуль
|
||||||
├── test_protocol.c # категория A — платформонезависимый
|
├── protocol/test_protocol.c # категория A — платформонезависимый
|
||||||
└── test_runner_logic.c # категория A
|
└── runner/test_runner_logic.c # категория A
|
||||||
```
|
```
|
||||||
|
|
||||||
`mocks/` подключается как include path с более высоким приоритетом чем `sdk/`. Компилятор найдёт `fsl_gpio.h` из `mocks/` раньше чем из SDK — `led.c` компилируется на хосте без единого изменения в BSP-коде.
|
`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 ──────────────────────────────────
|
/* ── Capture для gpio_pin_config_t ──────────────────────────────────
|
||||||
* fff хранит arg2_history как указатель — но cfg живёт на стеке
|
* fff хранит arg2_history как указатель — но cfg живёт на стеке
|
||||||
* led_init() и становится dangling после возврата.
|
* bsp_led_init() и становится dangling после возврата.
|
||||||
* (ASAN правильно детектирует это как stack-use-after-return)
|
* (ASAN правильно детектирует это как stack-use-after-return)
|
||||||
*
|
*
|
||||||
* Решение: custom_fake копирует структуру по значению в момент вызова,
|
* Решение: custom_fake копирует структуру по значению в момент вызова,
|
||||||
* пока стек led_init() ещё жив.
|
* пока стек bsp_led_init() ещё жив.
|
||||||
* ─────────────────────────────────────────────────────────────────── */
|
* ─────────────────────────────────────────────────────────────────── */
|
||||||
static gpio_pin_config_t s_captured_cfg[2];
|
static gpio_pin_config_t s_captured_cfg[2];
|
||||||
static int s_capture_idx = 0;
|
static int s_capture_idx = 0;
|
||||||
|
|
@ -43,11 +43,11 @@ void setUp(void)
|
||||||
RESET_FAKE(GPIO_PinWrite);
|
RESET_FAKE(GPIO_PinWrite);
|
||||||
FFF_RESET_HISTORY();
|
FFF_RESET_HISTORY();
|
||||||
|
|
||||||
/* подключаем capture до вызова led_init */
|
/* подключаем capture до вызова bsp_led_init */
|
||||||
s_capture_idx = 0;
|
s_capture_idx = 0;
|
||||||
GPIO_PinInit_fake.custom_fake = GPIO_PinInit_capture;
|
GPIO_PinInit_fake.custom_fake = GPIO_PinInit_capture;
|
||||||
|
|
||||||
led_init();
|
bsp_led_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void tearDown(void)
|
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);
|
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[0].direction);
|
||||||
TEST_ASSERT_EQUAL(kGPIO_DigitalOutput, s_captured_cfg[1].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 выключен) */
|
/* active LOW — начальное состояние GPIO = 1 (LED выключен) */
|
||||||
TEST_ASSERT_EQUAL_UINT8(1U, s_captured_cfg[0].outputLogic);
|
TEST_ASSERT_EQUAL_UINT8(1U, s_captured_cfg[0].outputLogic);
|
||||||
TEST_ASSERT_EQUAL_UINT8(1U, s_captured_cfg[1].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(bsp_led_get(LED_HEARTBEAT));
|
||||||
TEST_ASSERT_FALSE(led_get(LED_APP));
|
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);
|
bsp_led_on(LED_HEARTBEAT);
|
||||||
TEST_ASSERT_TRUE(led_get(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);
|
bsp_led_on(LED_HEARTBEAT);
|
||||||
led_off(LED_HEARTBEAT);
|
bsp_led_off(LED_HEARTBEAT);
|
||||||
TEST_ASSERT_FALSE(led_get(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 */
|
/* active LOW: включить LED = записать 0 в GPIO */
|
||||||
led_on(LED_HEARTBEAT);
|
bsp_led_on(LED_HEARTBEAT);
|
||||||
TEST_ASSERT_EQUAL_UINT8(0U, GPIO_PinWrite_fake.arg2_val);
|
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 */
|
/* active LOW: выключить LED = записать 1 в GPIO */
|
||||||
led_off(LED_HEARTBEAT);
|
bsp_led_off(LED_HEARTBEAT);
|
||||||
TEST_ASSERT_EQUAL_UINT8(1U, GPIO_PinWrite_fake.arg2_val);
|
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));
|
TEST_ASSERT_FALSE(bsp_led_get(LED_HEARTBEAT));
|
||||||
led_toggle(LED_HEARTBEAT);
|
bsp_led_toggle(LED_HEARTBEAT);
|
||||||
TEST_ASSERT_TRUE(led_get(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);
|
bsp_led_on(LED_HEARTBEAT);
|
||||||
led_toggle(LED_HEARTBEAT);
|
bsp_led_toggle(LED_HEARTBEAT);
|
||||||
TEST_ASSERT_FALSE(led_get(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);
|
bsp_led_toggle(LED_HEARTBEAT);
|
||||||
led_toggle(LED_HEARTBEAT);
|
bsp_led_toggle(LED_HEARTBEAT);
|
||||||
TEST_ASSERT_FALSE(led_get(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);
|
bsp_led_set(LED_APP, true);
|
||||||
TEST_ASSERT_TRUE(led_get(LED_APP));
|
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);
|
bsp_led_set(LED_APP, true);
|
||||||
led_set(LED_APP, false);
|
bsp_led_set(LED_APP, false);
|
||||||
TEST_ASSERT_FALSE(led_get(LED_APP));
|
TEST_ASSERT_FALSE(bsp_led_get(LED_APP));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Тест независимости LED друг от друга ───────────────────────────── */
|
/* ── Тест независимости LED друг от друга ───────────────────────────── */
|
||||||
|
|
||||||
void test_leds_are_independent(void)
|
void test_leds_are_independent(void)
|
||||||
{
|
{
|
||||||
led_on(LED_HEARTBEAT);
|
bsp_led_on(LED_HEARTBEAT);
|
||||||
TEST_ASSERT_TRUE(led_get(LED_HEARTBEAT));
|
TEST_ASSERT_TRUE(bsp_led_get(LED_HEARTBEAT));
|
||||||
TEST_ASSERT_FALSE(led_get(LED_APP));
|
TEST_ASSERT_FALSE(bsp_led_get(LED_APP));
|
||||||
|
|
||||||
led_on(LED_APP);
|
bsp_led_on(LED_APP);
|
||||||
led_off(LED_HEARTBEAT);
|
bsp_led_off(LED_HEARTBEAT);
|
||||||
TEST_ASSERT_FALSE(led_get(LED_HEARTBEAT));
|
TEST_ASSERT_FALSE(bsp_led_get(LED_HEARTBEAT));
|
||||||
TEST_ASSERT_TRUE(led_get(LED_APP));
|
TEST_ASSERT_TRUE(bsp_led_get(LED_APP));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── main ────────────────────────────────────────────────────────────── */
|
/* ── main ────────────────────────────────────────────────────────────── */
|
||||||
|
|
@ -168,22 +168,22 @@ int main(void)
|
||||||
{
|
{
|
||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
|
|
||||||
RUN_TEST(test_led_init_calls_gpio_init_for_each_led);
|
RUN_TEST(test_bsp_led_init_calls_gpio_init_for_each_led);
|
||||||
RUN_TEST(test_led_init_configures_as_output);
|
RUN_TEST(test_bsp_led_init_configures_as_output);
|
||||||
RUN_TEST(test_led_init_output_logic_is_high);
|
RUN_TEST(test_bsp_led_init_output_logic_is_high);
|
||||||
RUN_TEST(test_led_init_both_leds_off);
|
RUN_TEST(test_bsp_led_init_both_leds_off);
|
||||||
|
|
||||||
RUN_TEST(test_led_on_sets_state_true);
|
RUN_TEST(test_bsp_led_on_sets_state_true);
|
||||||
RUN_TEST(test_led_off_sets_state_false);
|
RUN_TEST(test_bsp_led_off_sets_state_false);
|
||||||
RUN_TEST(test_led_on_writes_gpio_low);
|
RUN_TEST(test_bsp_led_on_writes_gpio_low);
|
||||||
RUN_TEST(test_led_off_writes_gpio_high);
|
RUN_TEST(test_bsp_led_off_writes_gpio_high);
|
||||||
|
|
||||||
RUN_TEST(test_led_toggle_off_to_on);
|
RUN_TEST(test_bsp_led_toggle_off_to_on);
|
||||||
RUN_TEST(test_led_toggle_on_to_off);
|
RUN_TEST(test_bsp_led_toggle_on_to_off);
|
||||||
RUN_TEST(test_led_toggle_twice_returns_to_initial);
|
RUN_TEST(test_bsp_led_toggle_twice_returns_to_initial);
|
||||||
|
|
||||||
RUN_TEST(test_led_set_true_turns_on);
|
RUN_TEST(test_bsp_led_set_true_turns_on);
|
||||||
RUN_TEST(test_led_set_false_turns_off);
|
RUN_TEST(test_bsp_led_set_false_turns_off);
|
||||||
|
|
||||||
RUN_TEST(test_leds_are_independent);
|
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