Skip to content

File syn_led.c

File List > output > syn_led.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_LED) || SYN_USE_LED

#include "../drivers/syn_gpio.h"
#include "../util/syn_assert.h"
#include "syn_led.h"

#include <string.h>

/* ── Internal helpers ───────────────────────────────────────────────────── */

static void led_set_output(SYN_LED *led, bool on)
{
    if (led == NULL)
        return;
    led->lit = on;
    SYN_GPIO_State level;
    if (led->polarity == (uint8_t)SYN_LED_ACTIVE_LOW) {
        level = on ? SYN_GPIO_LOW : SYN_GPIO_HIGH;
    } else {
        level = on ? SYN_GPIO_HIGH : SYN_GPIO_LOW;
    }
    syn_gpio_write(led->pin, level);
}

/* ── API ────────────────────────────────────────────────────────────────── */

void syn_led_init(SYN_LED *led, SYN_GPIO_Pin pin, SYN_LEDPolarity polarity)
{
    SYN_ASSERT(led != NULL);

    memset(led, 0, sizeof(*led));
    led->pin = pin;
    led->polarity = (uint8_t)polarity;
    led->mode = (uint8_t)SYN_LED_MODE_OFF;
    led->lit = false;
    syn_gpio_init(pin, SYN_GPIO_OUTPUT);
    led_set_output(led, false);
}

void syn_led_on(SYN_LED *led)
{
    SYN_ASSERT(led != NULL);
    led->mode = (uint8_t)SYN_LED_MODE_ON;
    led_set_output(led, true);
}

void syn_led_off(SYN_LED *led)
{
    SYN_ASSERT(led != NULL);
    led->mode = (uint8_t)SYN_LED_MODE_OFF;
    led_set_output(led, false);
}

void syn_led_toggle(SYN_LED *led)
{
    SYN_ASSERT(led != NULL);
    if (led->lit) {
        syn_led_off(led);
    } else {
        syn_led_on(led);
    }
}

void syn_led_blink(SYN_LED *led, uint16_t on_ms, uint16_t off_ms)
{
    SYN_ASSERT(led != NULL);

    led->mode = (uint8_t)SYN_LED_MODE_BLINK;
    led->on_ms = on_ms;
    led->off_ms = off_ms;
    led->tick = syn_port_get_tick_ms();
    led_set_output(led, true);
}

void syn_led_flash(SYN_LED *led, uint16_t on_ms, uint16_t off_ms, uint8_t count)
{
    SYN_ASSERT(led != NULL);
    SYN_ASSERT(count > 0);

    led->mode = (uint8_t)SYN_LED_MODE_FLASH;
    led->on_ms = on_ms;
    led->off_ms = off_ms;
    led->flash_remain = count;
    led->tick = syn_port_get_tick_ms();
    led_set_output(led, true);
}

void syn_led_pattern(SYN_LED *led, const char *pattern, uint16_t unit_ms)
{
    SYN_ASSERT(led != NULL);

    if (pattern == NULL || pattern[0] == '\0') {
        syn_led_off(led);
        return;
    }

    led->mode = (uint8_t)SYN_LED_MODE_PATTERN;
    led->pattern = pattern;
    led->pattern_idx = 0;
    led->pattern_unit = unit_ms;
    led->tick = syn_port_get_tick_ms();
    led_set_output(led, false);
}

/* ── Update ─────────────────────────────────────────────────────────────── */

void syn_led_update(SYN_LED *led)
{
    SYN_ASSERT(led != NULL);

    uint32_t now = syn_port_get_tick_ms();
    uint32_t elapsed = now - led->tick;

    switch ((SYN_LEDMode)led->mode) {
    case SYN_LED_MODE_OFF:
    case SYN_LED_MODE_ON:
        /* Static — nothing to do */
        break;

    case SYN_LED_MODE_BLINK:
        if (led->lit && elapsed >= led->on_ms) {
            led_set_output(led, false);
            led->tick = now;
        } else if (!led->lit && elapsed >= led->off_ms) {
            led_set_output(led, true);
            led->tick = now;
        }
        break;

    case SYN_LED_MODE_FLASH:
        if (led->lit && elapsed >= led->on_ms) {
            led_set_output(led, false);
            led->tick = now;
            led->flash_remain--;
            if (led->flash_remain == 0) {
                led->mode = (uint8_t)SYN_LED_MODE_OFF;
            }
        } else if (!led->lit && elapsed >= led->off_ms) {
            if (led->flash_remain > 0) {
                led_set_output(led, true);
                led->tick = now;
            }
        }
        break;

    case SYN_LED_MODE_PATTERN: {
        char ch = led->pattern[led->pattern_idx];
        uint16_t dur;

        switch (ch) {
        case '.': /* short on */
            if (!led->lit) {
                led_set_output(led, true);
                led->tick = now;
            } else if (elapsed >= led->pattern_unit) {
                led_set_output(led, false);
                led->tick = now;
                led->pattern_idx++;
            }
            break;
        case '-': /* long on */
            if (!led->lit) {
                led_set_output(led, true);
                led->tick = now;
            } else if (elapsed >= (uint32_t)(led->pattern_unit * 3u)) {
                led_set_output(led, false);
                led->tick = now;
                led->pattern_idx++;
            }
            break;
        case ' ': /* short pause */
            dur = led->pattern_unit;
            if (elapsed >= dur) {
                led->pattern_idx++;
                led->tick = now;
            }
            break;
        case '|': /* long pause */
            dur = (uint16_t)(led->pattern_unit * 3u);
            if (elapsed >= dur) {
                led->pattern_idx++;
                led->tick = now;
            }
            break;
        case '\0': /* end of pattern — loop */
            led->pattern_idx = 0;
            led->tick = now;
            break;
        default: /* unknown char — skip */
            led->pattern_idx++;
            break;
        }
        break;
    }
    }
}

void syn_led_service(SYN_LED *leds, size_t count)
{
    SYN_ASSERT(leds != NULL || count == 0);

    for (size_t i = 0; i < count; i++) {
        syn_led_update(&leds[i]);
    }
}

uint32_t syn_led_next_ms(const SYN_LED *led)
{
    SYN_ASSERT(led != NULL);

    uint32_t now = syn_port_get_tick_ms();
    uint32_t elapsed = now - led->tick;
    uint32_t duration;

    switch ((SYN_LEDMode)led->mode) {
    case SYN_LED_MODE_OFF:
    case SYN_LED_MODE_ON:
        return UINT32_MAX; /* Static — no upcoming transition */

    case SYN_LED_MODE_BLINK:
    case SYN_LED_MODE_FLASH:
        duration = led->lit ? led->on_ms : led->off_ms;
        return (elapsed >= duration) ? 0 : (duration - elapsed);

    case SYN_LED_MODE_PATTERN: {
        char ch = led->pattern[led->pattern_idx];
        switch (ch) {
        case '.':
            duration = led->pattern_unit;
            break;
        case '-':
            duration = led->pattern_unit * 3u;
            break;
        case ' ':
            duration = led->pattern_unit;
            break;
        case '|':
            duration = led->pattern_unit * 3u;
            break;
        default:
            return 0; /* '\0' or unknown — immediate */
        }
        return (elapsed >= duration) ? 0 : (duration - elapsed);
    }
    }

    return 0;
}

#endif /* SYN_USE_LED */