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

141 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` | один цикл: 100 мс включён, 900 мс выключен, затем остаётся выключенным |
| `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`.
Для индикации приёма вызывайте `LedIndicator_TriggerActivity` либо
`LedIndicator_TriggerActivityAt` на каждый принятый пакет. Свечение занимает
10% секундного цикла: 100 мс включён и 900 мс выключен. Новый приём немедленно
перезапускает фазу свечения, даже внутри предыдущего импульса или паузы;
очереди циклов нет. При интервалах между пакетами меньше 100 мс LED может
гореть непрерывно до истечения 100 мс после последнего пакета. Без нового
события `ACTIVITY` не повторяется. Настраиваемая таблица `patterns` по-прежнему
позволяет изменить длительности и повторение для других применений.
```c
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`.
```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
```