Сохранить текущие изменения NAND и LED перед объединением веток
This commit is contained in:
30
c/led-indicator/CMakeLists.txt
Normal file
30
c/led-indicator/CMakeLists.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(led_indicator C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(led_indicator STATIC led_indicator.c)
|
||||
target_include_directories(led_indicator PUBLIC .)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(led_indicator PRIVATE /W4)
|
||||
else()
|
||||
target_compile_options(led_indicator PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
option(LED_INDICATOR_BUILD_TESTS "Собирать тесты LED indicator" ON)
|
||||
if(LED_INDICATOR_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_executable(test_led_indicator tests/test_led_indicator.c)
|
||||
target_link_libraries(test_led_indicator PRIVATE led_indicator)
|
||||
add_test(NAME led_indicator COMMAND test_led_indicator)
|
||||
|
||||
add_executable(test_led_indicator_stm32_port
|
||||
tests/test_stm32_port.c
|
||||
ports/stm32-hal/led_indicator_stm32_hal.c)
|
||||
target_include_directories(test_led_indicator_stm32_port PRIVATE
|
||||
. ports/stm32-hal tests/fakes)
|
||||
target_link_libraries(test_led_indicator_stm32_port PRIVATE led_indicator)
|
||||
add_test(NAME led_indicator_stm32_port COMMAND test_led_indicator_stm32_port)
|
||||
endif()
|
||||
126
c/led-indicator/README.md
Normal file
126
c/led-indicator/README.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# LED Indicator
|
||||
|
||||
Неблокирующая C99-библиотека для индикации состояний `STARTUP`, `WORK`,
|
||||
`ACTIVITY`, `WARNING`, `ERROR` и `CRITICAL` с разными рисунками и частотами.
|
||||
Один экземпляр обслуживает несколько светодиодов.
|
||||
|
||||
Ядро не знает о GPIO, PWM, сдвиговом регистре, RTOS и модели таймера. Порт
|
||||
передаёт логическое состояние канала наружу и читает монотонное время в
|
||||
миллисекундах. В библиотеке нет задержек, динамической памяти, прерываний и
|
||||
изменяемого глобального состояния.
|
||||
|
||||
```text
|
||||
приложение -> LedIndicator_SetMode/Process -> ядро -> write/now_ms -> GPIO/PWM/expander + TIM
|
||||
```
|
||||
|
||||
## Файлы
|
||||
|
||||
| Файл | Назначение | Зависимости |
|
||||
|---|---|---|
|
||||
| `led_indicator.h/.c` | режимы, шаблоны и планировщик | C99, `stdint.h` |
|
||||
| `ports/stm32-hal` | GPIO и выбранный аппаратный TIM | STM32 HAL |
|
||||
| `tests/test_led_indicator.c` | host-тесты, включая переполнение `uint32_t` | libc |
|
||||
|
||||
## Встроенные режимы
|
||||
|
||||
| Режим | Сигнал |
|
||||
|---|---|
|
||||
| `OFF` / `ON` | постоянно выключен / включён |
|
||||
| `STARTUP` | три коротких импульса, затем постоянно включён |
|
||||
| `WORK` | 100 мс включён, 900 мс выключен |
|
||||
| `ACTIVITY` | 50/50 мс |
|
||||
| `WARNING` | 250/250 мс |
|
||||
| `ERROR` | два импульса и пауза |
|
||||
| `CRITICAL` | три импульса и пауза |
|
||||
|
||||
Частоты не зашиты в алгоритм. Скопируйте встроенную таблицу и измените
|
||||
`duration_ms` до инициализации:
|
||||
|
||||
```c
|
||||
static LedIndicator_Pattern patterns[LED_INDICATOR_MODE_COUNT];
|
||||
|
||||
LedIndicator_CopyDefaultPatterns(patterns, LED_INDICATOR_MODE_COUNT);
|
||||
patterns[LED_INDICATOR_MODE_WORK].duration_ms[0] = 50U;
|
||||
patterns[LED_INDICATOR_MODE_WORK].duration_ms[1] = 1950U;
|
||||
config.patterns = patterns;
|
||||
config.pattern_count = LED_INDICATOR_MODE_COUNT;
|
||||
```
|
||||
|
||||
Таблица должна существовать всё время работы экземпляра. До восьми шагов
|
||||
описываются длительностями и битовой маской `levels`; шаблон может повторяться
|
||||
или после одного прохода перейти в `final_level`.
|
||||
|
||||
## Контракт порта
|
||||
|
||||
```c
|
||||
void write(void *context, uint8_t channel, uint8_t logical_on);
|
||||
uint32_t now_ms(void *context);
|
||||
```
|
||||
|
||||
`write` получает именно логический уровень. Инверсию active-low, управление
|
||||
PWM или запись общего регистра расширителя выполняет порт. Переполнение
|
||||
32-битной миллисекундной метки обработано разностью беззнаковых чисел.
|
||||
|
||||
Если время уже есть в планировщике приложения, `now_ms` можно оставить `NULL`
|
||||
и вызывать варианты `LedIndicator_SetModeAt`/`LedIndicator_ProcessAt`.
|
||||
|
||||
## Быстрый старт без привязки к HAL
|
||||
|
||||
```c
|
||||
static LedIndicator led;
|
||||
static LedIndicator_Channel state[2];
|
||||
LedIndicator_Config cfg;
|
||||
LedIndicator_Port port = {Board_LedWrite, Board_TimerMs, &board};
|
||||
|
||||
LedIndicator_ConfigDefault(&cfg);
|
||||
LedIndicator_Init(&led, state, 2U, &port, &cfg);
|
||||
LedIndicator_SetMode(&led, 0U, LED_INDICATOR_MODE_WORK);
|
||||
LedIndicator_SetMode(&led, 1U, LED_INDICATOR_MODE_ERROR);
|
||||
|
||||
for (;;) {
|
||||
LedIndicator_Process(&led);
|
||||
}
|
||||
```
|
||||
|
||||
`SetMode` идемпотентен: повторный вызов того же режима в каждом проходе цикла
|
||||
не начинает рисунок заново. Для нового импульса события служит
|
||||
`LedIndicator_RestartMode`.
|
||||
|
||||
## STM32F103, STM32F4, STM32G431 и STM32G474
|
||||
|
||||
Порт `ports/stm32-hal` принимает конкретный `TIM_HandleTypeDef *`; библиотека
|
||||
не использует `HAL_GetTick()` и не занимает SysTick. Настройте TIM как
|
||||
free-running, запустите его и передайте частоту счётчика после prescaler.
|
||||
|
||||
Скопируйте подходящий `led_indicator_stm32_hal_config.*.template.h` в каталог
|
||||
платы под именем `led_indicator_stm32_hal_config.h`.
|
||||
|
||||
```c
|
||||
static const LedIndicator_Stm32HalOutput outputs[] = {
|
||||
{STATUS_GPIO_Port, STATUS_Pin, 0U},
|
||||
{ERROR_GPIO_Port, ERROR_Pin, 1U}
|
||||
};
|
||||
static LedIndicator_Stm32HalPort hw;
|
||||
static LedIndicator_Port port;
|
||||
|
||||
HAL_TIM_Base_Start(&htim6); /* CNT = 1 кГц в данном примере. */
|
||||
LedIndicator_Stm32HalPortInit(&hw, &htim6, 1000U,
|
||||
outputs, 2U, &port);
|
||||
```
|
||||
|
||||
`LedIndicator_Process()` должен вызываться чаще, чем переполняется выбранный
|
||||
TIM. Для 16-битного CNT на 1 кГц это не реже одного раза за 65 секунд. Сам
|
||||
таймер и его prescaler/period задаются в CubeMX или board-порте, а не в ядре.
|
||||
|
||||
Для К1921ВК028 и C28x используется тот же основной порт: `now_ms` возвращает
|
||||
счётчик, увеличиваемый обработчиком выбранного TIMER/CPU Timer, а `write`
|
||||
обращается к GPIO SDK. Такое разделение оставляет номер таймера и выводы в
|
||||
проекте конкретной платы.
|
||||
|
||||
## Проверка
|
||||
|
||||
```sh
|
||||
cmake -S c/led-indicator -B build/led-indicator
|
||||
cmake --build build/led-indicator
|
||||
ctest --test-dir build/led-indicator --output-on-failure
|
||||
```
|
||||
246
c/led-indicator/led_indicator.c
Normal file
246
c/led-indicator/led_indicator.c
Normal file
@@ -0,0 +1,246 @@
|
||||
#include "led_indicator.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#define LEVELS_1 0x01U
|
||||
#define LEVELS_10 0x01U
|
||||
#define LEVELS_1010 0x05U
|
||||
#define LEVELS_101010 0x15U
|
||||
|
||||
static const LedIndicator_Pattern g_default_patterns[LED_INDICATOR_MODE_COUNT] = {
|
||||
{{0U}, 0U, 1U, 1U, 0U},
|
||||
{{0U}, LEVELS_1, 1U, 1U, 1U},
|
||||
{{100U, 100U, 100U, 100U, 100U, 100U}, LEVELS_101010, 6U, 0U, 1U},
|
||||
{{100U, 900U}, LEVELS_10, 2U, 1U, 0U},
|
||||
{{50U, 50U}, LEVELS_10, 2U, 1U, 0U},
|
||||
{{250U, 250U}, LEVELS_10, 2U, 1U, 0U},
|
||||
{{100U, 100U, 100U, 700U}, LEVELS_1010, 4U, 1U, 0U},
|
||||
{{100U, 100U, 100U, 100U, 100U, 700U}, LEVELS_101010, 6U, 1U, 0U}
|
||||
};
|
||||
|
||||
static uint8_t pattern_valid(const LedIndicator_Pattern *pattern)
|
||||
{
|
||||
size_t i;
|
||||
uint32_t total = 0U;
|
||||
|
||||
if ((pattern == NULL) || (pattern->step_count == 0U) ||
|
||||
(pattern->step_count > LED_INDICATOR_MAX_STEPS)) {
|
||||
return 0U;
|
||||
}
|
||||
if (pattern->step_count == 1U) {
|
||||
return 1U;
|
||||
}
|
||||
for (i = 0U; i < pattern->step_count; ++i) {
|
||||
if ((pattern->duration_ms[i] == 0U) ||
|
||||
(UINT32_MAX - total < pattern->duration_ms[i])) {
|
||||
return 0U;
|
||||
}
|
||||
total += pattern->duration_ms[i];
|
||||
}
|
||||
return (uint8_t)(total != 0U);
|
||||
}
|
||||
|
||||
static uint32_t pattern_period(const LedIndicator_Pattern *pattern)
|
||||
{
|
||||
size_t i;
|
||||
uint32_t total = 0U;
|
||||
|
||||
for (i = 0U; i < pattern->step_count; ++i) {
|
||||
total += pattern->duration_ms[i];
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static uint8_t pattern_level(const LedIndicator_Pattern *pattern, uint32_t elapsed)
|
||||
{
|
||||
uint32_t position = elapsed;
|
||||
uint32_t period;
|
||||
size_t i;
|
||||
|
||||
if (pattern->step_count == 1U) {
|
||||
return (uint8_t)(pattern->levels & 1U);
|
||||
}
|
||||
|
||||
period = pattern_period(pattern);
|
||||
if (pattern->repeat != 0U) {
|
||||
position %= period;
|
||||
} else if (position >= period) {
|
||||
return (uint8_t)(pattern->final_level != 0U);
|
||||
}
|
||||
|
||||
for (i = 0U; i < pattern->step_count; ++i) {
|
||||
if (position < pattern->duration_ms[i]) {
|
||||
return (uint8_t)((pattern->levels >> i) & 1U);
|
||||
}
|
||||
position -= pattern->duration_ms[i];
|
||||
}
|
||||
return (uint8_t)(pattern->final_level != 0U);
|
||||
}
|
||||
|
||||
static void write_if_changed(LedIndicator *instance, size_t channel, uint8_t level,
|
||||
uint8_t force)
|
||||
{
|
||||
LedIndicator_Channel *state = &instance->channels[channel];
|
||||
|
||||
level = (uint8_t)(level != 0U);
|
||||
if ((force != 0U) || (state->output_level != level)) {
|
||||
state->output_level = level;
|
||||
instance->port.write(instance->port.context, (uint8_t)channel, level);
|
||||
}
|
||||
}
|
||||
|
||||
void LedIndicator_ConfigDefault(LedIndicator_Config *config)
|
||||
{
|
||||
if (config != NULL) {
|
||||
config->patterns = g_default_patterns;
|
||||
config->pattern_count = LED_INDICATOR_MODE_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
size_t LedIndicator_CopyDefaultPatterns(LedIndicator_Pattern *patterns, size_t capacity)
|
||||
{
|
||||
size_t i;
|
||||
size_t count = (size_t)LED_INDICATOR_MODE_COUNT;
|
||||
|
||||
if (patterns == NULL) {
|
||||
return 0U;
|
||||
}
|
||||
if (capacity < count) {
|
||||
count = capacity;
|
||||
}
|
||||
for (i = 0U; i < count; ++i) {
|
||||
patterns[i] = g_default_patterns[i];
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
uint8_t LedIndicator_ValidateConfig(const LedIndicator_Config *config)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if ((config == NULL) || (config->patterns == NULL) ||
|
||||
(config->pattern_count == 0U) ||
|
||||
(config->pattern_count > (size_t)LED_INDICATOR_MODE_COUNT)) {
|
||||
return 0U;
|
||||
}
|
||||
for (i = 0U; i < config->pattern_count; ++i) {
|
||||
if (pattern_valid(&config->patterns[i]) == 0U) {
|
||||
return 0U;
|
||||
}
|
||||
}
|
||||
return 1U;
|
||||
}
|
||||
|
||||
uint8_t LedIndicator_Init(LedIndicator *instance,
|
||||
LedIndicator_Channel *channels,
|
||||
size_t channel_count,
|
||||
const LedIndicator_Port *port,
|
||||
const LedIndicator_Config *config)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if ((instance == NULL) || (channels == NULL) || (channel_count == 0U) ||
|
||||
(channel_count > 256U) || (port == NULL) || (port->write == NULL) ||
|
||||
(LedIndicator_ValidateConfig(config) == 0U)) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
instance->port = *port;
|
||||
instance->patterns = config->patterns;
|
||||
instance->pattern_count = config->pattern_count;
|
||||
instance->channels = channels;
|
||||
instance->channel_count = channel_count;
|
||||
instance->initialized = 1U;
|
||||
|
||||
for (i = 0U; i < channel_count; ++i) {
|
||||
channels[i].mode = LED_INDICATOR_MODE_OFF;
|
||||
channels[i].started_ms = 0U;
|
||||
channels[i].output_level = 0U;
|
||||
channels[i].initialized = 1U;
|
||||
write_if_changed(instance, i, 0U, 1U);
|
||||
}
|
||||
return 1U;
|
||||
}
|
||||
|
||||
uint8_t LedIndicator_SetModeAt(LedIndicator *instance, size_t channel,
|
||||
LedIndicator_Mode mode, uint32_t now_ms)
|
||||
{
|
||||
if ((instance == NULL) || (instance->initialized == 0U) ||
|
||||
(channel >= instance->channel_count) || ((size_t)mode >= instance->pattern_count)) {
|
||||
return 0U;
|
||||
}
|
||||
if (instance->channels[channel].mode == mode) {
|
||||
return 1U;
|
||||
}
|
||||
return LedIndicator_RestartModeAt(instance, channel, mode, now_ms);
|
||||
}
|
||||
|
||||
uint8_t LedIndicator_RestartModeAt(LedIndicator *instance, size_t channel,
|
||||
LedIndicator_Mode mode, uint32_t now_ms)
|
||||
{
|
||||
LedIndicator_Channel *state;
|
||||
const LedIndicator_Pattern *pattern;
|
||||
|
||||
if ((instance == NULL) || (instance->initialized == 0U) ||
|
||||
(channel >= instance->channel_count) || ((size_t)mode >= instance->pattern_count)) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
state = &instance->channels[channel];
|
||||
state->mode = mode;
|
||||
state->started_ms = now_ms;
|
||||
pattern = &instance->patterns[(size_t)mode];
|
||||
write_if_changed(instance, channel, pattern_level(pattern, 0U), 0U);
|
||||
return 1U;
|
||||
}
|
||||
|
||||
uint8_t LedIndicator_SetMode(LedIndicator *instance, size_t channel,
|
||||
LedIndicator_Mode mode)
|
||||
{
|
||||
if ((instance == NULL) || (instance->port.now_ms == NULL)) {
|
||||
return 0U;
|
||||
}
|
||||
return LedIndicator_SetModeAt(instance, channel, mode,
|
||||
instance->port.now_ms(instance->port.context));
|
||||
}
|
||||
|
||||
uint8_t LedIndicator_RestartMode(LedIndicator *instance, size_t channel,
|
||||
LedIndicator_Mode mode)
|
||||
{
|
||||
if ((instance == NULL) || (instance->port.now_ms == NULL)) {
|
||||
return 0U;
|
||||
}
|
||||
return LedIndicator_RestartModeAt(instance, channel, mode,
|
||||
instance->port.now_ms(instance->port.context));
|
||||
}
|
||||
|
||||
void LedIndicator_ProcessAt(LedIndicator *instance, uint32_t now_ms)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if ((instance == NULL) || (instance->initialized == 0U)) {
|
||||
return;
|
||||
}
|
||||
for (i = 0U; i < instance->channel_count; ++i) {
|
||||
const LedIndicator_Channel *state = &instance->channels[i];
|
||||
const LedIndicator_Pattern *pattern = &instance->patterns[(size_t)state->mode];
|
||||
const uint32_t elapsed = now_ms - state->started_ms;
|
||||
write_if_changed(instance, i, pattern_level(pattern, elapsed), 0U);
|
||||
}
|
||||
}
|
||||
|
||||
void LedIndicator_Process(LedIndicator *instance)
|
||||
{
|
||||
if ((instance != NULL) && (instance->port.now_ms != NULL)) {
|
||||
LedIndicator_ProcessAt(instance, instance->port.now_ms(instance->port.context));
|
||||
}
|
||||
}
|
||||
|
||||
LedIndicator_Mode LedIndicator_GetMode(const LedIndicator *instance, size_t channel)
|
||||
{
|
||||
if ((instance == NULL) || (instance->initialized == 0U) ||
|
||||
(channel >= instance->channel_count)) {
|
||||
return LED_INDICATOR_MODE_OFF;
|
||||
}
|
||||
return instance->channels[channel].mode;
|
||||
}
|
||||
117
c/led-indicator/led_indicator.h
Normal file
117
c/led-indicator/led_indicator.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @file led_indicator.h
|
||||
* @brief Неблокирующая индикация состояния устройства на одном или нескольких LED.
|
||||
*/
|
||||
|
||||
#ifndef LED_INDICATOR_H
|
||||
#define LED_INDICATOR_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define LED_INDICATOR_MAX_STEPS 8U
|
||||
|
||||
typedef enum {
|
||||
LED_INDICATOR_MODE_OFF = 0,
|
||||
LED_INDICATOR_MODE_ON,
|
||||
LED_INDICATOR_MODE_STARTUP,
|
||||
LED_INDICATOR_MODE_WORK,
|
||||
LED_INDICATOR_MODE_ACTIVITY,
|
||||
LED_INDICATOR_MODE_WARNING,
|
||||
LED_INDICATOR_MODE_ERROR,
|
||||
LED_INDICATOR_MODE_CRITICAL,
|
||||
LED_INDICATOR_MODE_COUNT
|
||||
} LedIndicator_Mode;
|
||||
|
||||
/** Один период сигнала. Бит N в levels задаёт уровень шага N. */
|
||||
typedef struct {
|
||||
uint32_t duration_ms[LED_INDICATOR_MAX_STEPS];
|
||||
uint8_t levels;
|
||||
uint8_t step_count;
|
||||
uint8_t repeat;
|
||||
uint8_t final_level;
|
||||
} LedIndicator_Pattern;
|
||||
|
||||
/**
|
||||
* Аппаратный порт. write получает логический уровень, поэтому active-low,
|
||||
* GPIO, PWM и регистры расширителя обрабатываются за границей ядра.
|
||||
*/
|
||||
typedef struct {
|
||||
void (*write)(void *context, uint8_t channel, uint8_t on);
|
||||
uint32_t (*now_ms)(void *context);
|
||||
void *context;
|
||||
} LedIndicator_Port;
|
||||
|
||||
typedef struct {
|
||||
LedIndicator_Mode mode;
|
||||
uint32_t started_ms;
|
||||
uint8_t output_level;
|
||||
uint8_t initialized;
|
||||
} LedIndicator_Channel;
|
||||
|
||||
typedef struct {
|
||||
const LedIndicator_Pattern *patterns;
|
||||
size_t pattern_count;
|
||||
} LedIndicator_Config;
|
||||
|
||||
typedef struct {
|
||||
LedIndicator_Port port;
|
||||
const LedIndicator_Pattern *patterns;
|
||||
LedIndicator_Channel *channels;
|
||||
size_t pattern_count;
|
||||
size_t channel_count;
|
||||
uint8_t initialized;
|
||||
} LedIndicator;
|
||||
|
||||
/** Заполняет конфигурацию встроенными шаблонами режимов. */
|
||||
void LedIndicator_ConfigDefault(LedIndicator_Config *config);
|
||||
|
||||
/** Копирует встроенные шаблоны в изменяемую таблицу; возвращает число записей. */
|
||||
size_t LedIndicator_CopyDefaultPatterns(LedIndicator_Pattern *patterns, size_t capacity);
|
||||
|
||||
/** Проверяет таблицу шаблонов. */
|
||||
uint8_t LedIndicator_ValidateConfig(const LedIndicator_Config *config);
|
||||
|
||||
/**
|
||||
* Инициализирует экземпляр и немедленно выключает все его каналы.
|
||||
* Массив channels принадлежит приложению и должен жить столько же, сколько instance.
|
||||
*/
|
||||
uint8_t LedIndicator_Init(LedIndicator *instance,
|
||||
LedIndicator_Channel *channels,
|
||||
size_t channel_count,
|
||||
const LedIndicator_Port *port,
|
||||
const LedIndicator_Config *config);
|
||||
|
||||
/** Назначает режим, используя время порта. Повтор того же режима не сбрасывает фазу. */
|
||||
uint8_t LedIndicator_SetMode(LedIndicator *instance, size_t channel,
|
||||
LedIndicator_Mode mode);
|
||||
|
||||
/** Назначает режим с явно переданной меткой времени. Повтор не сбрасывает фазу. */
|
||||
uint8_t LedIndicator_SetModeAt(LedIndicator *instance, size_t channel,
|
||||
LedIndicator_Mode mode, uint32_t now_ms);
|
||||
|
||||
/** Принудительно запускает режим с первого шага, используя время порта. */
|
||||
uint8_t LedIndicator_RestartMode(LedIndicator *instance, size_t channel,
|
||||
LedIndicator_Mode mode);
|
||||
|
||||
/** Принудительно запускает режим с первого шага в указанное время. */
|
||||
uint8_t LedIndicator_RestartModeAt(LedIndicator *instance, size_t channel,
|
||||
LedIndicator_Mode mode, uint32_t now_ms);
|
||||
|
||||
/** Обновляет выходы, используя время порта. Вызывать в главном цикле. */
|
||||
void LedIndicator_Process(LedIndicator *instance);
|
||||
|
||||
/** Обновляет выходы с явно переданной меткой времени. */
|
||||
void LedIndicator_ProcessAt(LedIndicator *instance, uint32_t now_ms);
|
||||
|
||||
LedIndicator_Mode LedIndicator_GetMode(const LedIndicator *instance, size_t channel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* LED_INDICATOR_H */
|
||||
65
c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.c
Normal file
65
c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "led_indicator_stm32_hal.h"
|
||||
|
||||
static void stm32_write(void *context, uint8_t channel, uint8_t on)
|
||||
{
|
||||
LedIndicator_Stm32HalPort *adapter = (LedIndicator_Stm32HalPort *)context;
|
||||
const LedIndicator_Stm32HalOutput *output;
|
||||
GPIO_PinState state;
|
||||
|
||||
if ((adapter == NULL) || ((size_t)channel >= adapter->output_count)) {
|
||||
return;
|
||||
}
|
||||
output = &adapter->outputs[channel];
|
||||
state = ((on != 0U) ^ (output->active_low != 0U)) ? GPIO_PIN_SET : GPIO_PIN_RESET;
|
||||
HAL_GPIO_WritePin(output->gpio, output->pin, state);
|
||||
}
|
||||
|
||||
static uint32_t stm32_now_ms(void *context)
|
||||
{
|
||||
LedIndicator_Stm32HalPort *adapter = (LedIndicator_Stm32HalPort *)context;
|
||||
const uint32_t current = __HAL_TIM_GET_COUNTER(adapter->timer);
|
||||
const uint32_t reload = __HAL_TIM_GET_AUTORELOAD(adapter->timer);
|
||||
uint32_t delta;
|
||||
uint64_t scaled;
|
||||
|
||||
if (current >= adapter->last_counter) {
|
||||
delta = current - adapter->last_counter;
|
||||
} else if (reload == UINT32_MAX) {
|
||||
delta = current - adapter->last_counter;
|
||||
} else {
|
||||
delta = (reload - adapter->last_counter) + 1U + current;
|
||||
}
|
||||
adapter->last_counter = current;
|
||||
|
||||
scaled = (uint64_t)adapter->remainder + ((uint64_t)delta * 1000ULL);
|
||||
adapter->accumulated_ms += (uint32_t)(scaled / adapter->timer_tick_hz);
|
||||
adapter->remainder = (uint32_t)(scaled % adapter->timer_tick_hz);
|
||||
return adapter->accumulated_ms;
|
||||
}
|
||||
|
||||
uint8_t LedIndicator_Stm32HalPortInit(LedIndicator_Stm32HalPort *adapter,
|
||||
TIM_HandleTypeDef *timer,
|
||||
uint32_t timer_tick_hz,
|
||||
const LedIndicator_Stm32HalOutput *outputs,
|
||||
size_t output_count,
|
||||
LedIndicator_Port *port)
|
||||
{
|
||||
if ((adapter == NULL) || (timer == NULL) || (timer_tick_hz == 0U) ||
|
||||
(outputs == NULL) || (output_count == 0U) || (output_count > 256U) ||
|
||||
(port == NULL)) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
adapter->timer = timer;
|
||||
adapter->outputs = outputs;
|
||||
adapter->output_count = output_count;
|
||||
adapter->timer_tick_hz = timer_tick_hz;
|
||||
adapter->last_counter = __HAL_TIM_GET_COUNTER(timer);
|
||||
adapter->accumulated_ms = 0U;
|
||||
adapter->remainder = 0U;
|
||||
|
||||
port->write = stm32_write;
|
||||
port->now_ms = stm32_now_ms;
|
||||
port->context = adapter;
|
||||
return 1U;
|
||||
}
|
||||
39
c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.h
Normal file
39
c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file led_indicator_stm32_hal.h
|
||||
* @brief Порт LED Indicator для STM32F1/F4/G4 HAL и выбранного TIM.
|
||||
*/
|
||||
|
||||
#ifndef LED_INDICATOR_STM32_HAL_H
|
||||
#define LED_INDICATOR_STM32_HAL_H
|
||||
|
||||
#include "led_indicator.h"
|
||||
#include "led_indicator_stm32_hal_config.h"
|
||||
|
||||
typedef struct {
|
||||
GPIO_TypeDef *gpio;
|
||||
uint16_t pin;
|
||||
uint8_t active_low;
|
||||
} LedIndicator_Stm32HalOutput;
|
||||
|
||||
typedef struct {
|
||||
TIM_HandleTypeDef *timer;
|
||||
const LedIndicator_Stm32HalOutput *outputs;
|
||||
size_t output_count;
|
||||
uint32_t timer_tick_hz;
|
||||
uint32_t last_counter;
|
||||
uint32_t accumulated_ms;
|
||||
uint32_t remainder;
|
||||
} LedIndicator_Stm32HalPort;
|
||||
|
||||
/**
|
||||
* Создаёт порт на уже настроенном и запущенном таймере.
|
||||
* timer_tick_hz — частота изменения CNT после prescaler, например 1000 Гц.
|
||||
*/
|
||||
uint8_t LedIndicator_Stm32HalPortInit(LedIndicator_Stm32HalPort *adapter,
|
||||
TIM_HandleTypeDef *timer,
|
||||
uint32_t timer_tick_hz,
|
||||
const LedIndicator_Stm32HalOutput *outputs,
|
||||
size_t output_count,
|
||||
LedIndicator_Port *port);
|
||||
|
||||
#endif /* LED_INDICATOR_STM32_HAL_H */
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef LED_INDICATOR_STM32_HAL_CONFIG_H
|
||||
#define LED_INDICATOR_STM32_HAL_CONFIG_H
|
||||
|
||||
#include "stm32f1xx_hal.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef LED_INDICATOR_STM32_HAL_CONFIG_H
|
||||
#define LED_INDICATOR_STM32_HAL_CONFIG_H
|
||||
|
||||
#include "stm32f4xx_hal.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef LED_INDICATOR_STM32_HAL_CONFIG_H
|
||||
#define LED_INDICATOR_STM32_HAL_CONFIG_H
|
||||
|
||||
#include "stm32g4xx_hal.h"
|
||||
|
||||
#endif
|
||||
27
c/led-indicator/tests/fakes/led_indicator_stm32_hal_config.h
Normal file
27
c/led-indicator/tests/fakes/led_indicator_stm32_hal_config.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef LED_INDICATOR_STM32_HAL_CONFIG_H
|
||||
#define LED_INDICATOR_STM32_HAL_CONFIG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
GPIO_PIN_RESET = 0,
|
||||
GPIO_PIN_SET
|
||||
} GPIO_PinState;
|
||||
|
||||
typedef struct {
|
||||
uint16_t last_pin;
|
||||
GPIO_PinState last_state;
|
||||
uint32_t writes;
|
||||
} GPIO_TypeDef;
|
||||
|
||||
typedef struct {
|
||||
uint32_t counter;
|
||||
uint32_t autoreload;
|
||||
} TIM_HandleTypeDef;
|
||||
|
||||
#define __HAL_TIM_GET_COUNTER(handle) ((handle)->counter)
|
||||
#define __HAL_TIM_GET_AUTORELOAD(handle) ((handle)->autoreload)
|
||||
|
||||
void HAL_GPIO_WritePin(GPIO_TypeDef *gpio, uint16_t pin, GPIO_PinState state);
|
||||
|
||||
#endif
|
||||
84
c/led-indicator/tests/test_led_indicator.c
Normal file
84
c/led-indicator/tests/test_led_indicator.c
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "led_indicator.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t now;
|
||||
uint8_t outputs[3];
|
||||
uint32_t writes[3];
|
||||
} FakePort;
|
||||
|
||||
static void fake_write(void *context, uint8_t channel, uint8_t on)
|
||||
{
|
||||
FakePort *fake = (FakePort *)context;
|
||||
fake->outputs[channel] = on;
|
||||
fake->writes[channel]++;
|
||||
}
|
||||
|
||||
static uint32_t fake_now(void *context)
|
||||
{
|
||||
return ((FakePort *)context)->now;
|
||||
}
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
fprintf(stderr, "check failed at line %d: %s\n", __LINE__, \
|
||||
#condition); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
LedIndicator indicator;
|
||||
LedIndicator_Channel channels[3];
|
||||
LedIndicator_Config config;
|
||||
LedIndicator_Pattern patterns[LED_INDICATOR_MODE_COUNT];
|
||||
FakePort fake = {0};
|
||||
LedIndicator_Port port = {fake_write, fake_now, &fake};
|
||||
|
||||
LedIndicator_ConfigDefault(&config);
|
||||
CHECK(LedIndicator_ValidateConfig(&config) != 0U);
|
||||
CHECK(LedIndicator_CopyDefaultPatterns(patterns, LED_INDICATOR_MODE_COUNT) ==
|
||||
LED_INDICATOR_MODE_COUNT);
|
||||
patterns[LED_INDICATOR_MODE_WORK].duration_ms[0] = 25U;
|
||||
CHECK(patterns[LED_INDICATOR_MODE_WORK].duration_ms[0] == 25U);
|
||||
CHECK(LedIndicator_Init(&indicator, channels, 3U, &port, &config) != 0U);
|
||||
CHECK(fake.writes[0] == 1U && fake.writes[1] == 1U && fake.writes[2] == 1U);
|
||||
|
||||
CHECK(LedIndicator_SetMode(&indicator, 0U, LED_INDICATOR_MODE_WORK) != 0U);
|
||||
CHECK(fake.outputs[0] == 1U);
|
||||
fake.now = 99U;
|
||||
CHECK(LedIndicator_SetMode(&indicator, 0U, LED_INDICATOR_MODE_WORK) != 0U);
|
||||
LedIndicator_Process(&indicator);
|
||||
CHECK(fake.outputs[0] == 1U);
|
||||
fake.now = 100U;
|
||||
LedIndicator_Process(&indicator);
|
||||
CHECK(fake.outputs[0] == 0U);
|
||||
fake.now = 1000U;
|
||||
LedIndicator_Process(&indicator);
|
||||
CHECK(fake.outputs[0] == 1U);
|
||||
fake.now = 1050U;
|
||||
CHECK(LedIndicator_RestartMode(&indicator, 0U, LED_INDICATOR_MODE_WORK) != 0U);
|
||||
fake.now = 1150U;
|
||||
LedIndicator_Process(&indicator);
|
||||
CHECK(fake.outputs[0] == 0U);
|
||||
|
||||
CHECK(LedIndicator_SetModeAt(&indicator, 1U, LED_INDICATOR_MODE_ERROR,
|
||||
0xFFFFFFF0UL) != 0U);
|
||||
LedIndicator_ProcessAt(&indicator, 0x00000054UL);
|
||||
CHECK(fake.outputs[1] == 0U); /* 100 ms после старта, переход во второй шаг. */
|
||||
LedIndicator_ProcessAt(&indicator, 0x000000B8UL);
|
||||
CHECK(fake.outputs[1] == 1U); /* 200 ms после старта, второй импульс. */
|
||||
|
||||
CHECK(LedIndicator_SetModeAt(&indicator, 2U, LED_INDICATOR_MODE_STARTUP, 10U) != 0U);
|
||||
LedIndicator_ProcessAt(&indicator, 610U);
|
||||
CHECK(fake.outputs[2] == 1U); /* Однократный startup завершён постоянным ON. */
|
||||
|
||||
CHECK(LedIndicator_SetMode(&indicator, 3U, LED_INDICATOR_MODE_ON) == 0U);
|
||||
CHECK(LedIndicator_GetMode(&indicator, 0U) == LED_INDICATOR_MODE_WORK);
|
||||
|
||||
puts("led indicator tests passed");
|
||||
return 0;
|
||||
}
|
||||
52
c/led-indicator/tests/test_stm32_port.c
Normal file
52
c/led-indicator/tests/test_stm32_port.c
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "led_indicator_stm32_hal.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void HAL_GPIO_WritePin(GPIO_TypeDef *gpio, uint16_t pin, GPIO_PinState state)
|
||||
{
|
||||
gpio->last_pin = pin;
|
||||
gpio->last_state = state;
|
||||
gpio->writes++;
|
||||
}
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
fprintf(stderr, "check failed at line %d: %s\n", __LINE__, \
|
||||
#condition); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
GPIO_TypeDef gpio_a = {0};
|
||||
GPIO_TypeDef gpio_b = {0};
|
||||
TIM_HandleTypeDef timer = {100U, 65535U};
|
||||
const LedIndicator_Stm32HalOutput outputs[] = {
|
||||
{&gpio_a, 0x0001U, 0U},
|
||||
{&gpio_b, 0x0080U, 1U}
|
||||
};
|
||||
LedIndicator_Stm32HalPort adapter;
|
||||
LedIndicator_Port port;
|
||||
|
||||
CHECK(LedIndicator_Stm32HalPortInit(&adapter, &timer, 10000U,
|
||||
outputs, 2U, &port) != 0U);
|
||||
port.write(port.context, 0U, 1U);
|
||||
CHECK(gpio_a.last_pin == 0x0001U && gpio_a.last_state == GPIO_PIN_SET);
|
||||
port.write(port.context, 1U, 1U);
|
||||
CHECK(gpio_b.last_pin == 0x0080U && gpio_b.last_state == GPIO_PIN_RESET);
|
||||
|
||||
timer.counter = 109U;
|
||||
CHECK(port.now_ms(port.context) == 0U);
|
||||
timer.counter = 110U;
|
||||
CHECK(port.now_ms(port.context) == 1U);
|
||||
|
||||
timer.counter = 65530U;
|
||||
(void)port.now_ms(port.context);
|
||||
timer.counter = 4U;
|
||||
CHECK(port.now_ms(port.context) == 6544U);
|
||||
|
||||
puts("led indicator STM32 port tests passed");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user