# 11
- написан и покрыт хост-тестами модуль bsp_opto (оптовоходы RS, IN1, IN2) - TODO: написал первый HIL тест с m5stamplc
This commit is contained in:
parent
c4feaa8f45
commit
6673bf5da6
17 changed files with 1049 additions and 29 deletions
|
|
@ -10,6 +10,7 @@ Checks: >
|
|||
hicpp-signed-bitwise,
|
||||
performance-*,
|
||||
portability-*,
|
||||
-performance-no-int-to-ptr,
|
||||
readability-braces-around-statements,
|
||||
readability-container-size-empty,
|
||||
readability-function-size,
|
||||
|
|
|
|||
1
.clangd
1
.clangd
|
|
@ -65,6 +65,7 @@ Diagnostics:
|
|||
- readability-*
|
||||
- portability-*
|
||||
Remove:
|
||||
- performance-no-int-to-ptr
|
||||
- readability-magic-numbers
|
||||
- readability-isolate-declaration
|
||||
- bugprone-easily-swappable-parameters
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@
|
|||
"targets": [
|
||||
"test_bsp_led",
|
||||
"test_log",
|
||||
"test_bsp_opto",
|
||||
"test_ring_buffer",
|
||||
"test_timeout_pattern",
|
||||
"uart_host_mock_example"
|
||||
|
|
@ -153,6 +154,7 @@
|
|||
"targets": [
|
||||
"test_bsp_led",
|
||||
"test_log",
|
||||
"test_bsp_opto",
|
||||
"test_ring_buffer",
|
||||
"test_timeout_pattern",
|
||||
"uart_host_mock_example"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ if(BUILD_TESTS_HOST)
|
|||
endif()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# bsp_board — стартап, сгенерированные файлы Config Tools, инициализация платы
|
||||
# bsp_board — генерированные файлы Config Tools, инициализация платы
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
add_library(bsp_board STATIC generated/board.c generated/pin_mux.c)
|
||||
|
|
@ -12,7 +12,7 @@ add_subdirectory(common)
|
|||
add_subdirectory(led)
|
||||
add_subdirectory(tick)
|
||||
add_subdirectory(uart_host)
|
||||
|
||||
add_subdirectory(opto)
|
||||
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
||||
target_compile_options(bsp_board PRIVATE -w)
|
||||
|
||||
|
|
|
|||
8
bsp/opto/CMakeLists.txt
Normal file
8
bsp/opto/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
add_library(bsp_opto STATIC src/opto.c)
|
||||
|
||||
target_include_directories(bsp_opto PUBLIC include)
|
||||
|
||||
target_link_libraries(
|
||||
bsp_opto
|
||||
PUBLIC bsp_status
|
||||
PRIVATE bsp_tick bsp_board sdk_gpio)
|
||||
69
bsp/opto/README.md
Normal file
69
bsp/opto/README.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# bsp_opto — оптоизолированные входы
|
||||
|
||||
## Аппаратура
|
||||
|
||||
| Канал | Пин MCU | GPIO | Схема |
|
||||
|--------------------|------------------|------------|--------------------------|
|
||||
| `BSP_OPTO_CH_IN1` | GPIO_AD_B1_06 | GPIO1[22] | PS2801-4 + 1K pull-up |
|
||||
| `BSP_OPTO_CH_IN2` | GPIO_AD_B1_05 | GPIO1[21] | PS2801-4 + 1K pull-up |
|
||||
| `BSP_OPTO_CH_RS` | GPIO_AD_B1_07 | GPIO1[23] | PS2801-4 + 1K pull-up |
|
||||
|
||||
**Логика:** active-low. Пин HIGH = нет тока = `INACTIVE`. Пин LOW = ток течёт = `ACTIVE`.
|
||||
|
||||
`BSP_OPTO_CH_RS` — опциональный. Активен только при `rs_as_gpio = true` в конфигурации.
|
||||
При `rs_as_gpio = false` пин остаётся под управлением `bsp_uart_rs` (LPUART3_RX).
|
||||
|
||||
Все три пина принадлежат GPIO1[16..31] → один IRQ: `GPIO1_Combined_16_31_IRQn`.
|
||||
|
||||
## Дебаунс
|
||||
|
||||
Аппаратного фильтра нет. Реализован программный дебаунс:
|
||||
|
||||
1. ISR фиксирует timestamp (`bsp_tick_get_ms()`) и raw состояние пина, выставляет `pending`.
|
||||
2. `bsp_opto_process()` вызывается из main loop. Если с момента последнего фронта прошло
|
||||
>= `debounce_ms` — перечитывает пин, сравнивает с `confirmed_state`, стреляет коллбэком.
|
||||
|
||||
Рекомендуемое значение `debounce_ms`: **10 мс** (PS2801-4 response ~50 мкс,
|
||||
основной источник шума — механические контакты на стенде).
|
||||
|
||||
## Использование
|
||||
|
||||
```c
|
||||
static void on_opto_change(bsp_opto_ch_t ch, bsp_opto_state_t state)
|
||||
{
|
||||
if (ch == BSP_OPTO_CH_IN1 && state == BSP_OPTO_STATE_ACTIVE) {
|
||||
/* IN1 активирован */
|
||||
}
|
||||
}
|
||||
|
||||
bsp_opto_config_t cfg = {
|
||||
.callbacks = { on_opto_change, on_opto_change, NULL },
|
||||
.rs_as_gpio = false,
|
||||
.debounce_ms = 10U,
|
||||
};
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
/* в main loop: */
|
||||
for (;;) {
|
||||
bsp_opto_process();
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
Полинг без коллбэков:
|
||||
|
||||
```c
|
||||
bsp_opto_state_t state = bsp_opto_read(BSP_OPTO_CH_IN1);
|
||||
```
|
||||
|
||||
## Совместное использование RS_RX
|
||||
|
||||
Пин GPIO_AD_B1_07 может работать в двух режимах:
|
||||
|
||||
| Режим | BSP-модуль | pin_mux функция |
|
||||
|----------------|-----------------|-------------------------|
|
||||
| LPUART3 RX | `bsp_uart_rs` | `BOARD_InitRS_UART()` |
|
||||
| GPIO input | `bsp_opto` | `BOARD_InitRS_GPIO()` |
|
||||
|
||||
Одновременно использовать оба нельзя. В `firmware_test` режим выбирается
|
||||
при инициализации в зависимости от конфигурации теста.
|
||||
145
bsp/opto/include/bsp/opto.h
Normal file
145
bsp/opto/include/bsp/opto.h
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* bsp_opto — оптоизолированные входы
|
||||
*
|
||||
* Аппаратура:
|
||||
* EXT_IN1 GPIO1[22] GPIO_AD_B1_06 active-low (PS2801-4, pull-up 1K к 3V3)
|
||||
* EXT_IN2 GPIO1[21] GPIO_AD_B1_05 active-low (PS2801-4, pull-up 1K к 3V3)
|
||||
* RS_RX GPIO1[23] GPIO_AD_B1_07 active-low (PS2801-4, pull-up 1K к 3V3)
|
||||
* опциональный канал — только при rs_as_gpio == true
|
||||
*
|
||||
* Дебаунс: программный, на базе bsp_tick.
|
||||
* ISR фиксирует timestamp + raw state → bsp_opto_process() из main loop
|
||||
* подтверждает стабильное состояние и стреляет коллбэком.
|
||||
*
|
||||
* Использование:
|
||||
* bsp_opto_config_t cfg = {
|
||||
* .callbacks = { my_cb, my_cb, NULL },
|
||||
* .rs_as_gpio = false,
|
||||
* .debounce_ms = 10U,
|
||||
* };
|
||||
* bsp_opto_init(&cfg);
|
||||
*
|
||||
* // в main loop:
|
||||
* bsp_opto_process();
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "bsp/status.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* ── Каналы ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
typedef enum
|
||||
{
|
||||
BSP_OPTO_CH_IN1 = 0U, /* EXT_IN1 — всегда доступен */
|
||||
BSP_OPTO_CH_IN2, /* EXT_IN2 — всегда доступен */
|
||||
BSP_OPTO_CH_RS, /* RS_RX — только при rs_as_gpio == true */
|
||||
BSP_OPTO_CH_COUNT,
|
||||
} bsp_opto_ch_t;
|
||||
|
||||
/* ── Состояние канала ────────────────────────────────────────────────────── */
|
||||
|
||||
typedef enum
|
||||
{
|
||||
BSP_OPTO_STATE_INACTIVE = 0U, /* цепь разомкнута, тока нет */
|
||||
BSP_OPTO_STATE_ACTIVE, /* цепь замкнута, ток течёт */
|
||||
} bsp_opto_state_t;
|
||||
|
||||
/* ── Фронт прерывания ────────────────────────────────────────────────────── */
|
||||
|
||||
typedef enum
|
||||
{
|
||||
BSP_OPTO_EDGE_RISING = 0U,
|
||||
BSP_OPTO_EDGE_FALLING,
|
||||
} bsp_opto_edge_t;
|
||||
|
||||
/* ── Коллбэк ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* @brief Коллбэк изменения состояния канала.
|
||||
*
|
||||
* Вызывается из bsp_opto_process() (контекст main loop, не ISR).
|
||||
* @param ch канал, изменивший состояние
|
||||
* @param state новое подтверждённое (дебаунсированное) состояние
|
||||
*/
|
||||
typedef void (*bsp_opto_callback_t)(bsp_opto_ch_t input_channel, bsp_opto_state_t state);
|
||||
|
||||
/* ── Конфигурация ────────────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* Массив коллбэков, индекс = bsp_opto_ch_t.
|
||||
* NULL — коллбэк для данного канала не используется.
|
||||
*/
|
||||
bsp_opto_callback_t callbacks[BSP_OPTO_CH_COUNT];
|
||||
|
||||
/**
|
||||
* Фронт срабатывания прерывания
|
||||
*
|
||||
*/
|
||||
bsp_opto_edge_t edges[BSP_OPTO_CH_COUNT];
|
||||
|
||||
/**
|
||||
* true — RS_RX сконфигурировать как GPIO (BSP_OPTO_CH_RS активен).
|
||||
* false — RS_RX остаётся в состоянии по умолчанию (LPUART3 / AIN).
|
||||
* BSP_OPTO_CH_RS недоступен, его коллбэк игнорируется.
|
||||
*/
|
||||
bool rs_as_gpio;
|
||||
|
||||
/**
|
||||
* Период дебаунса в миллисекундах. Рекомендуется 5–10 мс.
|
||||
* 0 — дебаунс отключён (для тестирования).
|
||||
*/
|
||||
uint32_t debounce_ms;
|
||||
} bsp_opto_config_t;
|
||||
|
||||
/* ── API ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Инициализировать модуль.
|
||||
*
|
||||
* Конфигурирует пины (pin mux), настраивает GPIO на вход,
|
||||
* включает прерывания по обоим фронтам. Для RS_RX вызывает
|
||||
* BOARD_InitRS_GPIO() если rs_as_gpio == true.
|
||||
*
|
||||
* @param p_config указатель на конфигурацию (не NULL)
|
||||
* @return BSP_OK или BSP_ERR_INVALID_ARG
|
||||
*/
|
||||
bsp_status_t bsp_opto_init(const bsp_opto_config_t *p_config);
|
||||
|
||||
/**
|
||||
* @brief Прочитать текущее подтверждённое состояние канала.
|
||||
*
|
||||
* Возвращает последнее состояние, зафиксированное после дебаунса.
|
||||
* Может вызываться из любого контекста.
|
||||
*
|
||||
* @param ch номер канала
|
||||
* @return BSP_OPTO_STATE_INACTIVE / BSP_OPTO_STATE_ACTIVE
|
||||
*/
|
||||
bsp_opto_state_t bsp_opto_read(bsp_opto_ch_t input_channel);
|
||||
|
||||
/**
|
||||
* @brief Обработать отложенные события дебаунса.
|
||||
*
|
||||
* Должна вызываться из main loop на каждой итерации.
|
||||
* НЕ вызывать из ISR.
|
||||
*
|
||||
* Для каждого канала с pending-флагом:
|
||||
* — если прошло >= debounce_ms с последнего фронта,
|
||||
* перечитывает пин, сравнивает с confirmed_state,
|
||||
* при изменении вызывает коллбэк и обновляет confirmed_state.
|
||||
*/
|
||||
void bsp_opto_process(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
214
bsp/opto/src/opto.c
Normal file
214
bsp/opto/src/opto.c
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* bsp_opto — реализация
|
||||
*/
|
||||
|
||||
#include "bsp/opto.h"
|
||||
|
||||
#include "board.h"
|
||||
#include "bsp/tick.h"
|
||||
#include "fsl_gpio.h"
|
||||
#include "fsl_iomuxc.h"
|
||||
#include "pin_mux.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* ── Константы пинов ─────────────────────────────────────────────────────── */
|
||||
|
||||
/* Все три пина — GPIO1, банк [16..31] → GPIO1_Combined_16_31_IRQn */
|
||||
|
||||
#define OPTO_GPIO_BASE GPIO1
|
||||
|
||||
#define OPTO_IN1_PIN 22U
|
||||
#define OPTO_IN2_PIN 21U
|
||||
#define OPTO_RS_PIN 23U
|
||||
|
||||
/* Active-low: оптопара тянет пин к GND → ACTIVE = LOW = 0 */
|
||||
#define OPTO_PIN_TO_STATE(raw) ((raw) == 0U ? BSP_OPTO_STATE_ACTIVE : BSP_OPTO_STATE_INACTIVE)
|
||||
|
||||
/* ── Состояние канала ────────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
volatile uint32_t last_edge_ms; /* bsp_tick_get_ms() в момент фронта */
|
||||
volatile bool pending; /* фронт зафиксирован, ждём дебаунс */
|
||||
volatile uint32_t raw_pin; /* raw состояние пина в момент фронта */
|
||||
bsp_opto_state_t confirmed_state; /* последнее подтверждённое состояние */
|
||||
uint8_t pin; /* номер пина в GPIO1 */
|
||||
bool enabled; /* канал активен */
|
||||
} opto_ch_state_t;
|
||||
|
||||
/* ── Модульное состояние ─────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
opto_ch_state_t channels[BSP_OPTO_CH_COUNT];
|
||||
bsp_opto_callback_t callbacks[BSP_OPTO_CH_COUNT];
|
||||
uint32_t debounce_ms;
|
||||
bool initialised;
|
||||
} opto_module_t;
|
||||
|
||||
static opto_module_t g_s_opto;
|
||||
|
||||
/* ── Вспомогательные функции ─────────────────────────────────────────────── */
|
||||
|
||||
static uint32_t read_pin(uint8_t pin)
|
||||
{
|
||||
return GPIO_PinRead(OPTO_GPIO_BASE, pin);
|
||||
}
|
||||
|
||||
static void enable_irq(uint8_t pin, bsp_opto_edge_t edge)
|
||||
{
|
||||
gpio_interrupt_mode_t mode =
|
||||
(edge == BSP_OPTO_EDGE_RISING) ? kGPIO_IntRisingEdge : kGPIO_IntFallingEdge;
|
||||
GPIO_SetPinInterruptConfig(OPTO_GPIO_BASE, pin, mode);
|
||||
GPIO_EnableInterrupts(OPTO_GPIO_BASE, 1UL << pin);
|
||||
}
|
||||
|
||||
/* ── ISR ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Общий обработчик для GPIO1[16..31].
|
||||
* Все три канала (пины 21, 22, 23) попадают сюда.
|
||||
*/
|
||||
void GPIO1_Combined_16_31_IRQHandler(void) // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
uint32_t flags = GPIO_GetPinsInterruptFlags(OPTO_GPIO_BASE);
|
||||
|
||||
for (bsp_opto_ch_t ch = 0U; ch < BSP_OPTO_CH_COUNT; ch++)
|
||||
{
|
||||
opto_ch_state_t *p_ch = &g_s_opto.channels[ch];
|
||||
|
||||
if (!p_ch->enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t mask = 1UL << p_ch->pin;
|
||||
if ((flags & mask) != 0U)
|
||||
{
|
||||
p_ch->raw_pin = read_pin(p_ch->pin);
|
||||
p_ch->last_edge_ms = bsp_tick_get_ms();
|
||||
p_ch->pending = true;
|
||||
|
||||
GPIO_ClearPinsInterruptFlags(OPTO_GPIO_BASE, mask);
|
||||
}
|
||||
}
|
||||
|
||||
SDK_ISR_EXIT_BARRIER;
|
||||
}
|
||||
|
||||
/* ── Инициализация ───────────────────────────────────────────────────────── */
|
||||
|
||||
bsp_status_t bsp_opto_init(const bsp_opto_config_t *p_config)
|
||||
{
|
||||
if (p_config == NULL)
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
|
||||
/* Сохранить конфигурацию */
|
||||
for (bsp_opto_ch_t ch = 0U; ch < BSP_OPTO_CH_COUNT; ch++)
|
||||
{
|
||||
g_s_opto.callbacks[ch] = p_config->callbacks[ch];
|
||||
}
|
||||
g_s_opto.debounce_ms = p_config->debounce_ms;
|
||||
|
||||
/* Настройка каналов IN1, IN2 — всегда активны */
|
||||
static const uint8_t K_PINS[BSP_OPTO_CH_COUNT] = {
|
||||
[BSP_OPTO_CH_IN1] = OPTO_IN1_PIN,
|
||||
[BSP_OPTO_CH_IN2] = OPTO_IN2_PIN,
|
||||
[BSP_OPTO_CH_RS] = OPTO_RS_PIN,
|
||||
};
|
||||
|
||||
gpio_pin_config_t gpio_in_cfg = {
|
||||
.direction = kGPIO_DigitalInput,
|
||||
.outputLogic = 0U,
|
||||
.interruptMode = kGPIO_NoIntmode,
|
||||
};
|
||||
|
||||
for (bsp_opto_ch_t ch = 0U; ch < BSP_OPTO_CH_COUNT; ch++)
|
||||
{
|
||||
opto_ch_state_t *p_ch = &g_s_opto.channels[ch];
|
||||
|
||||
if (ch == BSP_OPTO_CH_RS && !p_config->rs_as_gpio)
|
||||
{
|
||||
p_ch->enabled = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
p_ch->pin = K_PINS[ch];
|
||||
p_ch->enabled = true;
|
||||
p_ch->pending = false;
|
||||
|
||||
/* Начальное состояние — читаем пин до включения прерываний */
|
||||
if (ch == BSP_OPTO_CH_RS)
|
||||
{
|
||||
BOARD_InitRS_GPIO();
|
||||
}
|
||||
|
||||
GPIO_PinInit(OPTO_GPIO_BASE, p_ch->pin, &gpio_in_cfg);
|
||||
p_ch->confirmed_state = OPTO_PIN_TO_STATE(read_pin(p_ch->pin));
|
||||
|
||||
enable_irq(p_ch->pin, p_config->edges[ch]);
|
||||
}
|
||||
|
||||
/* Включить IRQ GPIO1_Combined_16_31 */
|
||||
EnableIRQ(GPIO1_Combined_16_31_IRQn);
|
||||
|
||||
g_s_opto.initialised = true;
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
/* ── Чтение состояния ────────────────────────────────────────────────────── */
|
||||
|
||||
bsp_opto_state_t bsp_opto_read(bsp_opto_ch_t input_channel)
|
||||
{
|
||||
if (input_channel >= BSP_OPTO_CH_COUNT || !g_s_opto.channels[input_channel].enabled)
|
||||
{
|
||||
return BSP_OPTO_STATE_INACTIVE;
|
||||
}
|
||||
return g_s_opto.channels[input_channel].confirmed_state;
|
||||
}
|
||||
|
||||
/* ── Обработка дебаунса ──────────────────────────────────────────────────── */
|
||||
|
||||
void bsp_opto_process(void)
|
||||
{
|
||||
if (!g_s_opto.initialised)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = bsp_tick_get_ms();
|
||||
|
||||
for (bsp_opto_ch_t ch = 0U; ch < BSP_OPTO_CH_COUNT; ch++)
|
||||
{
|
||||
opto_ch_state_t *p_ch = &g_s_opto.channels[ch];
|
||||
|
||||
if (!p_ch->enabled || !p_ch->pending)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t elapsed = now - p_ch->last_edge_ms;
|
||||
if (elapsed < g_s_opto.debounce_ms)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Дебаунс истёк — перечитать пин и подтвердить состояние */
|
||||
bsp_opto_state_t new_state = OPTO_PIN_TO_STATE(read_pin(p_ch->pin));
|
||||
p_ch->pending = false;
|
||||
|
||||
if (new_state != p_ch->confirmed_state)
|
||||
{
|
||||
p_ch->confirmed_state = new_state;
|
||||
|
||||
if (g_s_opto.callbacks[ch] != NULL)
|
||||
{
|
||||
g_s_opto.callbacks[ch](ch, new_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,26 +26,27 @@ __attribute__((weak)) void bsp_systick_hook(void)
|
|||
|
||||
#ifndef BSP_TICK_FREERTOS_MODE
|
||||
|
||||
#include "fsl_common.h" /* SystemCoreClock, SysTick_Config */
|
||||
#include "fsl_common.h"
|
||||
|
||||
static volatile uint32_t s_tick_ms = 0U;
|
||||
static volatile uint32_t g_s_tick_ms = 0U;
|
||||
|
||||
static const uint16_t MS_DIVIDER = 1000; // Переполнение раз в 1 мс
|
||||
void bsp_tick_init(void)
|
||||
{
|
||||
SysTick_Config(SystemCoreClock / 1000U);
|
||||
SysTick_Config(SystemCoreClock / MS_DIVIDER);
|
||||
}
|
||||
|
||||
void bsp_tick_inc(void)
|
||||
{
|
||||
s_tick_ms++;
|
||||
g_s_tick_ms++;
|
||||
}
|
||||
|
||||
uint32_t bsp_tick_get_ms(void)
|
||||
{
|
||||
return s_tick_ms;
|
||||
return g_s_tick_ms;
|
||||
}
|
||||
|
||||
void SysTick_Handler(void)
|
||||
void SysTick_Handler(void) // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
bsp_tick_inc();
|
||||
bsp_systick_hook();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Обзор стека
|
||||
|
||||
```
|
||||
```bash
|
||||
devcontainer
|
||||
─────────────────────────────────────────────────────────────────
|
||||
tests/host/<n>/test_<n>.c ← тест (Unity + опционально fff)
|
||||
|
|
@ -366,7 +366,7 @@ just build::test-host
|
|||
|
||||
## Чеклист
|
||||
|
||||
```
|
||||
```bash
|
||||
[ ] Определена категория (A или B)
|
||||
[ ] tests/host/<n>/test_<n>.c — тест с main(), setUp(), tearDown()
|
||||
[ ] tests/host/mocks/fsl_<driver>.h — stub (только категория B, если нет)
|
||||
|
|
|
|||
|
|
@ -94,8 +94,7 @@ add_host_test(
|
|||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Тест кода, использующего bsp_uart_host через мок (замените SOURCES на свой
|
||||
# реальный тест и код под тестом)
|
||||
# Тест кода, использующего bsp_uart_host через мок
|
||||
# -----------------------------------------------------------------------------
|
||||
add_host_test(
|
||||
NAME
|
||||
|
|
@ -112,3 +111,18 @@ add_host_test(
|
|||
# ${CMAKE_SOURCE_DIR}/bsp/protocol/include # protocol.h
|
||||
MOCKS
|
||||
${CMAKE_SOURCE_DIR}/bsp/uart_host/mocks/uart_host_mock.c)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Тест кода, использующего bsp_opto через мок
|
||||
# -----------------------------------------------------------------------------
|
||||
add_host_test(
|
||||
NAME
|
||||
test_bsp_opto
|
||||
SOURCES
|
||||
opto/test_bsp_opto.c
|
||||
${PROJECT_SOURCE_DIR}/bsp/opto/src/opto.c
|
||||
INCLUDES
|
||||
${PROJECT_SOURCE_DIR}/bsp/opto/include
|
||||
${PROJECT_SOURCE_DIR}/bsp/common/include
|
||||
MOCKS
|
||||
${BSP_MOCKS_DIR})
|
||||
|
|
|
|||
|
|
@ -1,2 +1,13 @@
|
|||
#pragma once
|
||||
/* Stub board.h для host-тестов — led.c включает его транзитивно через bsp_board */
|
||||
|
||||
/**
|
||||
* Stub board.h для host-тестов.
|
||||
* Содержит только сигнатуры используемые в bsp-модулях.
|
||||
* fff предоставляет реализации в тест-файлах где нужно.
|
||||
*/
|
||||
|
||||
void BOARD_InitBootPins(void);
|
||||
void BOARD_InitPins(void);
|
||||
void BOARD_InitRS_UART(void);
|
||||
void BOARD_InitRS_GPIO(void);
|
||||
void board_hw_init(void);
|
||||
11
tests/host/mocks/bsp/tick.h
Normal file
11
tests/host/mocks/bsp/tick.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
/**
|
||||
* Stub bsp/tick.h для host-тестов.
|
||||
* bsp_tick_get_ms() мокается через fff в тест-файлах где нужен контроль времени.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint32_t bsp_tick_get_ms(void);
|
||||
void bsp_delay(uint32_t ms);
|
||||
|
|
@ -2,32 +2,63 @@
|
|||
|
||||
/**
|
||||
* Stub fsl_gpio.h для host-тестов.
|
||||
* Содержит только типы и сигнатуры используемые в bsp/led/src/led.c.
|
||||
* fff предоставляет реализации через FAKE_VOID_FUNC.
|
||||
* Содержит типы и сигнатуры используемые в:
|
||||
* bsp/led/src/led.c
|
||||
* bsp/opto/src/opto.c
|
||||
*
|
||||
* fff предоставляет реализации через FAKE_VOID_FUNC / FAKE_VALUE_FUNC.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* GPIO peripheral base pointer type */
|
||||
/* ── GPIO peripheral ─────────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t reserved[64];
|
||||
} GPIO_Type;
|
||||
|
||||
/* Pin direction */
|
||||
/*
|
||||
* Stub-экземпляры и макросы GPIO1/2/3.
|
||||
* static — каждая translation unit получает свою копию (нормально для тестов).
|
||||
* #ifndef защита — на случай если pin_mux.h включён раньше.
|
||||
*/
|
||||
#ifndef GPIO1
|
||||
static GPIO_Type stub_GPIO1;
|
||||
static GPIO_Type stub_GPIO2;
|
||||
static GPIO_Type stub_GPIO3;
|
||||
|
||||
#define GPIO1 (&stub_GPIO1)
|
||||
#define GPIO2 (&stub_GPIO2)
|
||||
#define GPIO3 (&stub_GPIO3)
|
||||
#endif
|
||||
|
||||
/* ── Enums ───────────────────────────────────────────────────────────────── */
|
||||
|
||||
typedef enum
|
||||
{
|
||||
kGPIO_DigitalInput = 0U,
|
||||
kGPIO_DigitalOutput = 1U,
|
||||
} gpio_pin_direction_t;
|
||||
|
||||
/* Interrupt mode — не используется в LED, но нужен для компиляции */
|
||||
typedef enum
|
||||
{
|
||||
kGPIO_NoIntmode = 0U,
|
||||
kGPIO_NoIntmode = 0U,
|
||||
kGPIO_IntLowLevel = 1U,
|
||||
kGPIO_IntHighLevel = 2U,
|
||||
kGPIO_IntRisingEdge = 3U,
|
||||
kGPIO_IntFallingEdge = 4U,
|
||||
kGPIO_IntRisingOrFallingEdge = 5U,
|
||||
} gpio_interrupt_mode_t;
|
||||
|
||||
/* Pin config struct */
|
||||
/* IRQ-номера — только те что нужны для opto */
|
||||
typedef enum
|
||||
{
|
||||
GPIO1_Combined_16_31_IRQn = 100,
|
||||
} IRQn_Type;
|
||||
|
||||
/* ── Pin config ──────────────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gpio_pin_direction_t direction;
|
||||
|
|
@ -35,6 +66,21 @@ typedef struct
|
|||
gpio_interrupt_mode_t interruptMode;
|
||||
} gpio_pin_config_t;
|
||||
|
||||
/* Functions — реализуются через fff в тест-файле */
|
||||
/* ── CMSIS / SDK макросы ─────────────────────────────────────────────────── */
|
||||
|
||||
/* На хосте — no-op */
|
||||
#define SDK_ISR_EXIT_BARRIER ((void) 0)
|
||||
|
||||
/* ── Function signatures — реализуются через fff ─────────────────────────── */
|
||||
|
||||
void GPIO_PinInit(GPIO_Type *base, uint32_t pin, const gpio_pin_config_t *config);
|
||||
void GPIO_PinWrite(GPIO_Type *base, uint32_t pin, uint8_t output);
|
||||
uint32_t GPIO_PinRead(GPIO_Type *base, uint32_t pin);
|
||||
|
||||
void GPIO_SetPinInterruptConfig(GPIO_Type *base, uint32_t pin,
|
||||
gpio_interrupt_mode_t pinInterruptMode);
|
||||
void GPIO_EnableInterrupts(GPIO_Type *base, uint32_t mask);
|
||||
uint32_t GPIO_GetPinsInterruptFlags(GPIO_Type *base);
|
||||
void GPIO_ClearPinsInterruptFlags(GPIO_Type *base, uint32_t mask);
|
||||
|
||||
void EnableIRQ(IRQn_Type irq);
|
||||
8
tests/host/mocks/fsl_iomuxc.h
Normal file
8
tests/host/mocks/fsl_iomuxc.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
/**
|
||||
* Stub fsl_iomuxc.h для host-тестов.
|
||||
* В реальном SDK содержит только inline-функции для настройки pin mux.
|
||||
* В opto.c используется как транзитивный include — прямых вызовов нет,
|
||||
* поэтому stub пустой.
|
||||
*/
|
||||
|
|
@ -3,20 +3,17 @@
|
|||
/**
|
||||
* Stub pin_mux.h для host-тестов.
|
||||
* Макросы пинов зеркалят реальные значения из generated/pin_mux.h.
|
||||
* GPIO-инстансы — статические объекты-заглушки, адреса уникальны.
|
||||
* GPIO-инстансы приходят из fsl_gpio.h (stub_GPIO1/2/3).
|
||||
*/
|
||||
|
||||
#include "fsl_gpio.h"
|
||||
|
||||
/* Заглушки GPIO-периферии */
|
||||
static GPIO_Type stub_GPIO3;
|
||||
|
||||
/* UserLed1 → GPIO3 pin 3 (LED_HEARTBEAT) */
|
||||
#define BOARD_INITPINS_UserLed1_GPIO (&stub_GPIO3)
|
||||
#define BOARD_INITPINS_UserLed1_GPIO GPIO3
|
||||
#define BOARD_INITPINS_UserLed1_GPIO_PIN 3U
|
||||
#define BOARD_INITPINS_UserLed1_INIT_GPIO_VALUE 1U
|
||||
|
||||
/* UserLed2 → GPIO3 pin 4 (LED_APP) */
|
||||
#define BOARD_INITPINS_UserLed2_GPIO (&stub_GPIO3)
|
||||
#define BOARD_INITPINS_UserLed2_GPIO GPIO3
|
||||
#define BOARD_INITPINS_UserLed2_GPIO_PIN 4U
|
||||
#define BOARD_INITPINS_UserLed2_INIT_GPIO_VALUE 1U
|
||||
492
tests/host/opto/test_bsp_opto.c
Normal file
492
tests/host/opto/test_bsp_opto.c
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
/*
|
||||
* test_bsp_opto.c — host-тест bsp_opto
|
||||
*
|
||||
* Категория B: opto.c вызывает fsl_gpio.h и bsp_tick.
|
||||
* Все зависимости заменены fff-фейками.
|
||||
*
|
||||
* Топология пинов (из opto.c):
|
||||
* BSP_OPTO_CH_IN1 → GPIO1 pin 22
|
||||
* BSP_OPTO_CH_IN2 → GPIO1 pin 21
|
||||
* BSP_OPTO_CH_RS → GPIO1 pin 23
|
||||
*/
|
||||
|
||||
#include "fff.h"
|
||||
#include "unity.h"
|
||||
|
||||
DEFINE_FFF_GLOBALS;
|
||||
|
||||
/* ── 1. Стабы SDK (до тестируемого модуля) ───────────────────────────────── */
|
||||
|
||||
#include "fsl_gpio.h"
|
||||
|
||||
FAKE_VOID_FUNC(GPIO_PinInit, GPIO_Type *, uint32_t, const gpio_pin_config_t *);
|
||||
FAKE_VOID_FUNC(GPIO_PinWrite, GPIO_Type *, uint32_t, uint8_t);
|
||||
FAKE_VALUE_FUNC(uint32_t, GPIO_PinRead, GPIO_Type *, uint32_t);
|
||||
|
||||
FAKE_VOID_FUNC(GPIO_SetPinInterruptConfig, GPIO_Type *, uint32_t, gpio_interrupt_mode_t);
|
||||
FAKE_VOID_FUNC(GPIO_EnableInterrupts, GPIO_Type *, uint32_t);
|
||||
FAKE_VALUE_FUNC(uint32_t, GPIO_GetPinsInterruptFlags, GPIO_Type *);
|
||||
FAKE_VOID_FUNC(GPIO_ClearPinsInterruptFlags, GPIO_Type *, uint32_t);
|
||||
|
||||
FAKE_VOID_FUNC(EnableIRQ, IRQn_Type);
|
||||
|
||||
/* ── 2. Стабы BSP ─────────────────────────────────────────────────────────── */
|
||||
|
||||
#include "board.h"
|
||||
FAKE_VOID_FUNC(BOARD_InitRS_GPIO);
|
||||
|
||||
#include "bsp/tick.h"
|
||||
FAKE_VALUE_FUNC(uint32_t, bsp_tick_get_ms);
|
||||
|
||||
/* ── 3. Тестируемый модуль — всегда последним ────────────────────────────── */
|
||||
|
||||
#include "bsp/opto.h"
|
||||
|
||||
/* ── Вспомогательное ─────────────────────────────────────────────────────── */
|
||||
|
||||
#define OPTO_IN1_PIN 22U
|
||||
#define OPTO_IN2_PIN 21U
|
||||
#define OPTO_RS_PIN 23U
|
||||
|
||||
#define DEBOUNCE_MS 10U
|
||||
|
||||
/* Capture конфигурации GPIO_PinInit (та же техника что в test_bsp_led.c) */
|
||||
#define MAX_INIT_CALLS 3U
|
||||
static gpio_pin_config_t s_captured_cfg[MAX_INIT_CALLS];
|
||||
static uint32_t s_captured_pin[MAX_INIT_CALLS];
|
||||
static uint32_t s_capture_idx;
|
||||
|
||||
static void GPIO_PinInit_capture(GPIO_Type *base, uint32_t pin, const gpio_pin_config_t *p_cfg)
|
||||
{
|
||||
(void) base;
|
||||
if (s_capture_idx < MAX_INIT_CALLS)
|
||||
{
|
||||
s_captured_pin[s_capture_idx] = pin;
|
||||
s_captured_cfg[s_capture_idx] = *p_cfg;
|
||||
s_capture_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Capture SetPinInterruptConfig */
|
||||
#define MAX_IRQ_CONFIG_CALLS 3U
|
||||
static gpio_interrupt_mode_t s_captured_irq_mode[MAX_IRQ_CONFIG_CALLS];
|
||||
static uint32_t s_captured_irq_pin[MAX_IRQ_CONFIG_CALLS];
|
||||
static uint32_t s_irq_config_idx;
|
||||
|
||||
static void GPIO_SetPinInterruptConfig_capture(GPIO_Type *base, uint32_t pin,
|
||||
gpio_interrupt_mode_t mode)
|
||||
{
|
||||
(void) base;
|
||||
if (s_irq_config_idx < MAX_IRQ_CONFIG_CALLS)
|
||||
{
|
||||
s_captured_irq_pin[s_irq_config_idx] = pin;
|
||||
s_captured_irq_mode[s_irq_config_idx] = mode;
|
||||
s_irq_config_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Коллбэк-фиксатор */
|
||||
static bsp_opto_ch_t s_cb_ch;
|
||||
static bsp_opto_state_t s_cb_state;
|
||||
static uint32_t s_cb_count;
|
||||
|
||||
static void test_callback(bsp_opto_ch_t ch, bsp_opto_state_t state)
|
||||
{
|
||||
s_cb_ch = ch;
|
||||
s_cb_state = state;
|
||||
s_cb_count++;
|
||||
}
|
||||
|
||||
/* Конфигурация по умолчанию (rs_as_gpio = false, 2 канала) */
|
||||
static bsp_opto_config_t make_default_cfg(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = {
|
||||
.callbacks = { test_callback, test_callback, NULL },
|
||||
.edges = {
|
||||
[BSP_OPTO_CH_IN1] = BSP_OPTO_EDGE_RISING,
|
||||
[BSP_OPTO_CH_IN2] = BSP_OPTO_EDGE_RISING,
|
||||
[BSP_OPTO_CH_RS] = BSP_OPTO_EDGE_RISING,
|
||||
},
|
||||
.rs_as_gpio = false,
|
||||
.debounce_ms = DEBOUNCE_MS,
|
||||
};
|
||||
return cfg;
|
||||
}
|
||||
|
||||
/* Forward declaration ISR — определён в opto.c */
|
||||
void GPIO1_Combined_16_31_IRQHandler(void);
|
||||
|
||||
/* Симулировать ISR — дёргаем обработчик напрямую как из железа */
|
||||
static void simulate_isr(uint8_t pin, uint32_t pin_level)
|
||||
{
|
||||
GPIO_GetPinsInterruptFlags_fake.return_val = (1UL << pin);
|
||||
GPIO_PinRead_fake.return_val = pin_level;
|
||||
GPIO1_Combined_16_31_IRQHandler();
|
||||
}
|
||||
|
||||
/* ── setUp / tearDown ────────────────────────────────────────────────────── */
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
RESET_FAKE(GPIO_PinInit);
|
||||
RESET_FAKE(GPIO_PinWrite);
|
||||
RESET_FAKE(GPIO_PinRead);
|
||||
RESET_FAKE(GPIO_SetPinInterruptConfig);
|
||||
RESET_FAKE(GPIO_EnableInterrupts);
|
||||
RESET_FAKE(GPIO_GetPinsInterruptFlags);
|
||||
RESET_FAKE(GPIO_ClearPinsInterruptFlags);
|
||||
RESET_FAKE(EnableIRQ);
|
||||
RESET_FAKE(BOARD_InitRS_GPIO);
|
||||
RESET_FAKE(bsp_tick_get_ms);
|
||||
FFF_RESET_HISTORY();
|
||||
|
||||
s_capture_idx = 0U;
|
||||
s_irq_config_idx = 0U;
|
||||
s_cb_count = 0U;
|
||||
|
||||
GPIO_PinInit_fake.custom_fake = GPIO_PinInit_capture;
|
||||
GPIO_SetPinInterruptConfig_fake.custom_fake = GPIO_SetPinInterruptConfig_capture;
|
||||
|
||||
/* По умолчанию пины INACTIVE (active-low → raw = 1) */
|
||||
GPIO_PinRead_fake.return_val = 1U;
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* ── Тесты: bsp_opto_init ────────────────────────────────────────────────── */
|
||||
|
||||
void test_init_returns_ok(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
TEST_ASSERT_EQUAL(BSP_OK, bsp_opto_init(&cfg));
|
||||
}
|
||||
|
||||
void test_init_null_config_returns_err(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL(BSP_ERR_PARAM, bsp_opto_init(NULL));
|
||||
}
|
||||
|
||||
void test_init_two_channels_when_rs_as_gpio_false(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
/* IN1 + IN2, RS пропущен */
|
||||
TEST_ASSERT_EQUAL(2U, GPIO_PinInit_fake.call_count);
|
||||
}
|
||||
|
||||
void test_init_three_channels_when_rs_as_gpio_true(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
cfg.rs_as_gpio = true;
|
||||
cfg.callbacks[BSP_OPTO_CH_RS] = test_callback;
|
||||
bsp_opto_init(&cfg);
|
||||
TEST_ASSERT_EQUAL(3U, GPIO_PinInit_fake.call_count);
|
||||
}
|
||||
|
||||
void test_init_all_pins_configured_as_input(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
TEST_ASSERT_EQUAL(kGPIO_DigitalInput, s_captured_cfg[0].direction);
|
||||
TEST_ASSERT_EQUAL(kGPIO_DigitalInput, s_captured_cfg[1].direction);
|
||||
}
|
||||
|
||||
void test_init_correct_pins_configured(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
/* Порядок в реестре: IN1=22, IN2=21 */
|
||||
TEST_ASSERT_EQUAL(OPTO_IN1_PIN, s_captured_pin[0]);
|
||||
TEST_ASSERT_EQUAL(OPTO_IN2_PIN, s_captured_pin[1]);
|
||||
}
|
||||
|
||||
void test_init_irq_enabled_for_each_channel(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
TEST_ASSERT_EQUAL(2U, GPIO_EnableInterrupts_fake.call_count);
|
||||
}
|
||||
|
||||
void test_init_global_irq_enabled(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
TEST_ASSERT_EQUAL(1U, EnableIRQ_fake.call_count);
|
||||
TEST_ASSERT_EQUAL(GPIO1_Combined_16_31_IRQn, EnableIRQ_fake.arg0_val);
|
||||
}
|
||||
|
||||
void test_init_rs_as_gpio_calls_board_init(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
cfg.rs_as_gpio = true;
|
||||
bsp_opto_init(&cfg);
|
||||
TEST_ASSERT_EQUAL(1U, BOARD_InitRS_GPIO_fake.call_count);
|
||||
}
|
||||
|
||||
void test_init_rs_as_gpio_false_no_board_init(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
TEST_ASSERT_EQUAL(0U, BOARD_InitRS_GPIO_fake.call_count);
|
||||
}
|
||||
|
||||
/* ── Тесты: edge config ──────────────────────────────────────────────────── */
|
||||
|
||||
void test_init_rising_edge_sets_rising_irq_mode(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
cfg.edges[BSP_OPTO_CH_IN1] = BSP_OPTO_EDGE_RISING;
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
/* Найти запись для пина IN1 */
|
||||
uint32_t idx = 0U;
|
||||
for (uint32_t i = 0U; i < s_irq_config_idx; i++)
|
||||
{
|
||||
if (s_captured_irq_pin[i] == OPTO_IN1_PIN)
|
||||
{
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
TEST_ASSERT_EQUAL(kGPIO_IntRisingEdge, s_captured_irq_mode[idx]);
|
||||
}
|
||||
|
||||
void test_init_falling_edge_sets_falling_irq_mode(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
cfg.edges[BSP_OPTO_CH_IN1] = BSP_OPTO_EDGE_FALLING;
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
uint32_t idx = 0U;
|
||||
for (uint32_t i = 0U; i < s_irq_config_idx; i++)
|
||||
{
|
||||
if (s_captured_irq_pin[i] == OPTO_IN1_PIN)
|
||||
{
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
TEST_ASSERT_EQUAL(kGPIO_IntFallingEdge, s_captured_irq_mode[idx]);
|
||||
}
|
||||
|
||||
/* ── Тесты: bsp_opto_read — начальное состояние ─────────────────────────── */
|
||||
|
||||
void test_read_initial_inactive_when_pin_high(void)
|
||||
{
|
||||
/* active-low: raw=1 → INACTIVE */
|
||||
GPIO_PinRead_fake.return_val = 1U;
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
TEST_ASSERT_EQUAL(BSP_OPTO_STATE_INACTIVE, bsp_opto_read(BSP_OPTO_CH_IN1));
|
||||
TEST_ASSERT_EQUAL(BSP_OPTO_STATE_INACTIVE, bsp_opto_read(BSP_OPTO_CH_IN2));
|
||||
}
|
||||
|
||||
void test_read_initial_active_when_pin_low(void)
|
||||
{
|
||||
/* active-low: raw=0 → ACTIVE */
|
||||
GPIO_PinRead_fake.return_val = 0U;
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
TEST_ASSERT_EQUAL(BSP_OPTO_STATE_ACTIVE, bsp_opto_read(BSP_OPTO_CH_IN1));
|
||||
}
|
||||
|
||||
void test_read_disabled_rs_channel_returns_inactive(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg(); /* rs_as_gpio = false */
|
||||
bsp_opto_init(&cfg);
|
||||
TEST_ASSERT_EQUAL(BSP_OPTO_STATE_INACTIVE, bsp_opto_read(BSP_OPTO_CH_RS));
|
||||
}
|
||||
|
||||
void test_read_invalid_channel_returns_inactive(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
TEST_ASSERT_EQUAL(BSP_OPTO_STATE_INACTIVE, bsp_opto_read((bsp_opto_ch_t) BSP_OPTO_CH_COUNT));
|
||||
}
|
||||
|
||||
/* ── Тесты: bsp_opto_process — дебаунс ──────────────────────────────────── */
|
||||
|
||||
void test_process_before_debounce_no_callback(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
/* ISR фиксирует фронт в момент T=100 */
|
||||
bsp_tick_get_ms_fake.return_val = 100U;
|
||||
simulate_isr(OPTO_IN1_PIN, 0U); /* pin → ACTIVE */
|
||||
|
||||
/* process в T=105: прошло 5ms < debounce_ms(10) */
|
||||
bsp_tick_get_ms_fake.return_val = 105U;
|
||||
GPIO_PinRead_fake.return_val = 0U;
|
||||
bsp_opto_process();
|
||||
|
||||
TEST_ASSERT_EQUAL(0U, s_cb_count);
|
||||
}
|
||||
|
||||
void test_process_after_debounce_fires_callback(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
bsp_tick_get_ms_fake.return_val = 100U;
|
||||
simulate_isr(OPTO_IN1_PIN, 0U); /* pin → ACTIVE */
|
||||
|
||||
/* process в T=112: прошло 12ms >= debounce_ms(10) */
|
||||
bsp_tick_get_ms_fake.return_val = 112U;
|
||||
GPIO_PinRead_fake.return_val = 0U;
|
||||
bsp_opto_process();
|
||||
|
||||
TEST_ASSERT_EQUAL(1U, s_cb_count);
|
||||
}
|
||||
|
||||
void test_process_callback_receives_correct_channel(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
bsp_tick_get_ms_fake.return_val = 0U;
|
||||
simulate_isr(OPTO_IN1_PIN, 0U);
|
||||
|
||||
bsp_tick_get_ms_fake.return_val = DEBOUNCE_MS + 1U;
|
||||
GPIO_PinRead_fake.return_val = 0U;
|
||||
bsp_opto_process();
|
||||
|
||||
TEST_ASSERT_EQUAL(BSP_OPTO_CH_IN1, s_cb_ch);
|
||||
}
|
||||
|
||||
void test_process_callback_receives_active_state(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
bsp_tick_get_ms_fake.return_val = 0U;
|
||||
simulate_isr(OPTO_IN1_PIN, 0U); /* raw=0 → ACTIVE */
|
||||
|
||||
bsp_tick_get_ms_fake.return_val = DEBOUNCE_MS + 1U;
|
||||
GPIO_PinRead_fake.return_val = 0U;
|
||||
bsp_opto_process();
|
||||
|
||||
TEST_ASSERT_EQUAL(BSP_OPTO_STATE_ACTIVE, s_cb_state);
|
||||
}
|
||||
|
||||
void test_process_callback_receives_inactive_state(void)
|
||||
{
|
||||
/* Начинаем с ACTIVE — init с pin=0 */
|
||||
GPIO_PinRead_fake.return_val = 0U;
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
/* Фронт: pin → INACTIVE (raw=1) */
|
||||
bsp_tick_get_ms_fake.return_val = 0U;
|
||||
simulate_isr(OPTO_IN1_PIN, 1U);
|
||||
|
||||
bsp_tick_get_ms_fake.return_val = DEBOUNCE_MS + 1U;
|
||||
GPIO_PinRead_fake.return_val = 1U;
|
||||
bsp_opto_process();
|
||||
|
||||
TEST_ASSERT_EQUAL(BSP_OPTO_STATE_INACTIVE, s_cb_state);
|
||||
}
|
||||
|
||||
void test_process_no_callback_if_state_unchanged(void)
|
||||
{
|
||||
/* Init с pin=0 → ACTIVE */
|
||||
GPIO_PinRead_fake.return_val = 0U;
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
/* ISR срабатывает, но пин при перечитке всё ещё ACTIVE */
|
||||
bsp_tick_get_ms_fake.return_val = 0U;
|
||||
simulate_isr(OPTO_IN1_PIN, 0U);
|
||||
|
||||
bsp_tick_get_ms_fake.return_val = DEBOUNCE_MS + 1U;
|
||||
GPIO_PinRead_fake.return_val = 0U; /* состояние не изменилось */
|
||||
bsp_opto_process();
|
||||
|
||||
TEST_ASSERT_EQUAL(0U, s_cb_count);
|
||||
}
|
||||
|
||||
void test_process_pending_cleared_after_debounce(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
bsp_tick_get_ms_fake.return_val = 0U;
|
||||
simulate_isr(OPTO_IN1_PIN, 0U);
|
||||
|
||||
bsp_tick_get_ms_fake.return_val = DEBOUNCE_MS + 1U;
|
||||
GPIO_PinRead_fake.return_val = 0U;
|
||||
bsp_opto_process();
|
||||
|
||||
/* Второй вызов process без нового ISR — коллбэк не должен стрелять снова */
|
||||
bsp_opto_process();
|
||||
|
||||
TEST_ASSERT_EQUAL(1U, s_cb_count);
|
||||
}
|
||||
|
||||
/* ── Тесты: независимость каналов ───────────────────────────────────────── */
|
||||
|
||||
void test_channels_are_independent(void)
|
||||
{
|
||||
bsp_opto_config_t cfg = make_default_cfg();
|
||||
bsp_opto_init(&cfg);
|
||||
|
||||
/* ISR только для IN1 */
|
||||
bsp_tick_get_ms_fake.return_val = 0U;
|
||||
simulate_isr(OPTO_IN1_PIN, 0U);
|
||||
|
||||
bsp_tick_get_ms_fake.return_val = DEBOUNCE_MS + 1U;
|
||||
GPIO_PinRead_fake.return_val = 0U;
|
||||
bsp_opto_process();
|
||||
|
||||
/* IN1 изменился, IN2 — нет */
|
||||
TEST_ASSERT_EQUAL(BSP_OPTO_STATE_ACTIVE, bsp_opto_read(BSP_OPTO_CH_IN1));
|
||||
TEST_ASSERT_EQUAL(BSP_OPTO_STATE_INACTIVE, bsp_opto_read(BSP_OPTO_CH_IN2));
|
||||
TEST_ASSERT_EQUAL(1U, s_cb_count); /* ровно один коллбэк */
|
||||
}
|
||||
|
||||
/* ── main ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
||||
/* init */
|
||||
RUN_TEST(test_init_returns_ok);
|
||||
RUN_TEST(test_init_null_config_returns_err);
|
||||
RUN_TEST(test_init_two_channels_when_rs_as_gpio_false);
|
||||
RUN_TEST(test_init_three_channels_when_rs_as_gpio_true);
|
||||
RUN_TEST(test_init_all_pins_configured_as_input);
|
||||
RUN_TEST(test_init_correct_pins_configured);
|
||||
RUN_TEST(test_init_irq_enabled_for_each_channel);
|
||||
RUN_TEST(test_init_global_irq_enabled);
|
||||
RUN_TEST(test_init_rs_as_gpio_calls_board_init);
|
||||
RUN_TEST(test_init_rs_as_gpio_false_no_board_init);
|
||||
|
||||
/* edge config */
|
||||
RUN_TEST(test_init_rising_edge_sets_rising_irq_mode);
|
||||
RUN_TEST(test_init_falling_edge_sets_falling_irq_mode);
|
||||
|
||||
/* read — начальное состояние */
|
||||
RUN_TEST(test_read_initial_inactive_when_pin_high);
|
||||
RUN_TEST(test_read_initial_active_when_pin_low);
|
||||
RUN_TEST(test_read_disabled_rs_channel_returns_inactive);
|
||||
RUN_TEST(test_read_invalid_channel_returns_inactive);
|
||||
|
||||
/* process — дебаунс */
|
||||
RUN_TEST(test_process_before_debounce_no_callback);
|
||||
RUN_TEST(test_process_after_debounce_fires_callback);
|
||||
RUN_TEST(test_process_callback_receives_correct_channel);
|
||||
RUN_TEST(test_process_callback_receives_active_state);
|
||||
RUN_TEST(test_process_callback_receives_inactive_state);
|
||||
RUN_TEST(test_process_no_callback_if_state_unchanged);
|
||||
RUN_TEST(test_process_pending_cleared_after_debounce);
|
||||
|
||||
/* независимость каналов */
|
||||
RUN_TEST(test_channels_are_independent);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Reference in a new issue