Files
templates/c/led-indicator/README.md

7.4 KiB
Raw Blame History

LED Indicator

Неблокирующая C99-библиотека для индикации состояний STARTUP, WORK, ACTIVITY, WARNING, ERROR и CRITICAL с разными рисунками и частотами. Один экземпляр обслуживает несколько светодиодов.

Ядро не знает о GPIO, PWM, сдвиговом регистре, RTOS и модели таймера. Порт передаёт логическое состояние канала наружу и читает монотонное время в миллисекундах. В библиотеке нет задержек, динамической памяти, прерываний и изменяемого глобального состояния.

приложение -> 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 один цикл: 100 мс включён, 900 мс выключен, затем остаётся выключенным
WARNING 250/250 мс
ERROR два импульса и пауза
CRITICAL три импульса и пауза

Частоты не зашиты в алгоритм. Скопируйте встроенную таблицу и измените duration_ms до инициализации:

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.

Контракт порта

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

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.

Для индикации приёма вызывайте LedIndicator_TriggerActivity либо LedIndicator_TriggerActivityAt на каждый принятый пакет. Свечение занимает 10% секундного цикла: 100 мс включён и 900 мс выключен. Новый приём немедленно перезапускает фазу свечения, даже внутри предыдущего импульса или паузы; очереди циклов нет. При интервалах между пакетами меньше 100 мс LED может гореть непрерывно до истечения 100 мс после последнего пакета. Без нового события ACTIVITY не повторяется. Настраиваемая таблица patterns по-прежнему позволяет изменить длительности и повторение для других применений.

LedIndicator_TriggerActivityAt(&led, 0U, received_ms); /* Каждый приём запускает цикл канала 0 от миллисекундной метки события. */
LedIndicator_ProcessAt(&led, now_ms); /* Главный цикл обслуживает окончание свечения без задержек и ожидания следующего пакета. */

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.

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. Такое разделение оставляет номер таймера и выводы в проекте конкретной платы.

Проверка

cmake -S c/led-indicator -B build/led-indicator
cmake --build build/led-indicator
ctest --test-dir build/led-indicator --output-on-failure