new module
This commit is contained in:
6
components/led/CMakeLists.txt
Executable file
6
components/led/CMakeLists.txt
Executable file
@@ -0,0 +1,6 @@
|
||||
set(srcs "src/led.c" "src/ledc_driver.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES driver esp_timer
|
||||
REQUIRES config evse)
|
||||
78
components/led/include/led.h
Executable file
78
components/led/include/led.h
Executable file
@@ -0,0 +1,78 @@
|
||||
#ifndef LED_H_
|
||||
#define LED_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief Identificadores dos LEDs disponíveis no hardware (por cor)
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
LED_ID_GREEN = 0,
|
||||
LED_ID_BLUE,
|
||||
LED_ID_RED,
|
||||
LED_ID_MAX
|
||||
} led_id_t;
|
||||
|
||||
/**
|
||||
* @brief Padrões de comportamento possíveis para os LEDs.
|
||||
*
|
||||
* Estes padrões são usados tanto pela lógica interna (estado EVSE, efeitos
|
||||
* de sessão, etc.) como por código externo que queira forçar um determinado
|
||||
* comportamento num LED.
|
||||
*
|
||||
* Nota:
|
||||
* - LED_PATTERN_BREATHING é implementado por uma task interna (HSV).
|
||||
* - LED_PATTERN_CHARGING_EFFECT é legado/experimental e pode não ser usado
|
||||
* diretamente na implementação atual.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
LED_PATTERN_OFF, ///< LED sempre desligado
|
||||
LED_PATTERN_ON, ///< LED sempre ligado
|
||||
LED_PATTERN_BLINK, ///< Pisca com ciclo padrão (500ms on / 500ms off)
|
||||
LED_PATTERN_BLINK_FAST, ///< Pisca rápido (250ms on / 250ms off)
|
||||
LED_PATTERN_BLINK_SLOW, ///< Pisca lento (500ms on / 1500ms off)
|
||||
LED_PATTERN_CHARGING_EFFECT, ///< Efeito de carregamento (2s on / 1s off) - legado/opcional
|
||||
LED_PATTERN_BREATHING ///< Efeito "breathing" (brilho suave via HSV)
|
||||
} led_pattern_t;
|
||||
|
||||
/**
|
||||
* @brief Inicializa o subsistema de LEDs com base na configuração da placa.
|
||||
*
|
||||
* - Configura o driver LEDC com os GPIOs definidos em board_config.
|
||||
* - Cria a task de efeitos (para padrões como BREATHING).
|
||||
* - Regista handlers dos eventos EVSE (estado e sessão) para aplicar
|
||||
* padrões automáticos em função do estado do carregador.
|
||||
*
|
||||
* Deve ser chamada uma única vez na inicialização do sistema.
|
||||
*/
|
||||
void led_init(void);
|
||||
|
||||
/**
|
||||
* @brief Define diretamente o tempo ligado/desligado de um LED.
|
||||
*
|
||||
* Esta função permite criar padrões personalizados à parte da tabela
|
||||
* led_pattern_t. Normalmente, código de alto nível deve preferir
|
||||
* led_apply_pattern(), deixando a lógica de tempos a cargo do módulo.
|
||||
*
|
||||
* @param led_id Identificador do LED (ver led_id_t)
|
||||
* @param ontime Tempo ligado em milissegundos (0 = sempre off)
|
||||
* @param offtime Tempo desligado em milissegundos (0 = sempre on)
|
||||
*/
|
||||
void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime);
|
||||
|
||||
/**
|
||||
* @brief Aplica um padrão de piscar/efeito definido ao LED.
|
||||
*
|
||||
* Esta é a API recomendada para controlar LEDs externamente. A implementação
|
||||
* interna do módulo também a usa para reagir a eventos EVSE (estado de
|
||||
* carregamento, início/fim de sessão, etc.).
|
||||
*
|
||||
* @param led_id Identificador do LED (ver led_id_t)
|
||||
* @param pattern Padrão desejado (ver led_pattern_t)
|
||||
*/
|
||||
void led_apply_pattern(led_id_t led_id, led_pattern_t pattern);
|
||||
|
||||
#endif /* LED_H_ */
|
||||
34
components/led/include/ledc_driver.h
Executable file
34
components/led/include/ledc_driver.h
Executable file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
/**
|
||||
* @brief Inicializa o controlador LEDC para os 3 LEDs (R,G,B)
|
||||
*
|
||||
* @param gpio_red GPIO ligado ao LED vermelho (via ULN2003)
|
||||
* @param gpio_green GPIO ligado ao LED verde (via ULN2003)
|
||||
* @param gpio_blue GPIO ligado ao LED azul (via ULN2003)
|
||||
*
|
||||
* @return ESP_OK em sucesso, erro caso contrário
|
||||
*/
|
||||
esp_err_t ledc_init(gpio_num_t gpio_red,
|
||||
gpio_num_t gpio_green,
|
||||
gpio_num_t gpio_blue);
|
||||
|
||||
/**
|
||||
* @brief Define a intensidade RGB (0–255 por cor)
|
||||
*
|
||||
* @param red Intensidade do vermelho (0–255)
|
||||
* @param green Intensidade do verde (0–255)
|
||||
* @param blue Intensidade do azul (0–255)
|
||||
*
|
||||
* @return ESP_OK em sucesso, erro caso contrário
|
||||
*/
|
||||
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue);
|
||||
|
||||
/**
|
||||
* @brief Desliga todos os LEDs (R,G,B)
|
||||
*/
|
||||
esp_err_t ledc_clear(void);
|
||||
598
components/led/src/led.c
Executable file
598
components/led/src/led.c
Executable file
@@ -0,0 +1,598 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#include <inttypes.h> // para PRIu32
|
||||
|
||||
#include "led.h"
|
||||
#include "board_config.h"
|
||||
#include "evse_events.h"
|
||||
#include "evse_state.h"
|
||||
#include "ledc_driver.h"
|
||||
|
||||
#define BLOCK_TIME pdMS_TO_TICKS(10)
|
||||
|
||||
static const char *TAG = "led";
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool present; ///< LED existe nesta placa
|
||||
bool on : 1; ///< estado lógico atual (ligado/desligado)
|
||||
uint16_t ontime; ///< ms ligado
|
||||
uint16_t offtime; ///< ms desligado
|
||||
TimerHandle_t timer; ///< timer de piscar (para padrões que usam on/off)
|
||||
led_pattern_t pattern; ///< padrão atual
|
||||
uint8_t blink_count; ///< reservado para padrões mais complexos
|
||||
} led_t;
|
||||
|
||||
static led_t leds[LED_ID_MAX] = {0};
|
||||
|
||||
// ----------------------------
|
||||
// Tabela de padrões (tempo on/off)
|
||||
// ----------------------------
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t on_ms;
|
||||
uint16_t off_ms;
|
||||
} led_timing_t;
|
||||
|
||||
// índice = led_pattern_t
|
||||
static const led_timing_t led_pattern_table[] = {
|
||||
[LED_PATTERN_OFF] = {0, 0},
|
||||
[LED_PATTERN_ON] = {1, 0}, // 1ms só para cair na lógica "sempre ligado"
|
||||
[LED_PATTERN_BLINK] = {500, 500},
|
||||
[LED_PATTERN_BLINK_FAST] = {250, 250},
|
||||
[LED_PATTERN_BLINK_SLOW] = {500, 1500},
|
||||
[LED_PATTERN_CHARGING_EFFECT] = {2000, 1000},
|
||||
// Para BREATHING, o temporizador FreeRTOS NÃO é usado; tratamos via task de efeitos.
|
||||
// Mesmo assim, usamos (1,0) para marcar LED como "on" logicamente.
|
||||
[LED_PATTERN_BREATHING] = {1, 0},
|
||||
};
|
||||
|
||||
#define LED_PATTERN_COUNT (sizeof(led_pattern_table) / sizeof(led_pattern_table[0]))
|
||||
|
||||
// ----------------------------
|
||||
// Estado base + efeitos de sessão
|
||||
// ----------------------------
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SESSION_EFFECT_NONE = 0,
|
||||
SESSION_EFFECT_START,
|
||||
SESSION_EFFECT_FINISH,
|
||||
} led_session_effect_type_t;
|
||||
|
||||
static evse_state_event_t current_state_mode = EVSE_STATE_EVENT_IDLE;
|
||||
static bool session_effect_active = false;
|
||||
static TimerHandle_t session_effect_timer = NULL;
|
||||
static led_session_effect_type_t session_effect_type = SESSION_EFFECT_NONE;
|
||||
static uint8_t session_effect_phase = 0;
|
||||
|
||||
// Forwards
|
||||
static void led_update_rgb_from_state(void);
|
||||
static void led_effect_task(void *arg);
|
||||
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data);
|
||||
static void evse_session_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data);
|
||||
static void session_effect_timer_cb(TimerHandle_t xTimer);
|
||||
static void led_apply_state_mode(evse_state_event_t state);
|
||||
|
||||
// ----------------------------
|
||||
// Timer de piscar (para padrões on/off)
|
||||
// ----------------------------
|
||||
static void led_timer_callback(TimerHandle_t xTimer)
|
||||
{
|
||||
led_t *led = (led_t *)pvTimerGetTimerID(xTimer);
|
||||
led->on = !led->on;
|
||||
|
||||
uint32_t next_time = led->on ? led->ontime : led->offtime;
|
||||
xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME);
|
||||
|
||||
// Atualiza hardware (via LEDC).
|
||||
led_update_rgb_from_state();
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Atualiza hardware a partir do estado lógico dos LEDs
|
||||
// (para padrões "normais": ON/OFF/BLINK/...)
|
||||
// ----------------------------
|
||||
static void led_update_rgb_from_state(void)
|
||||
{
|
||||
uint32_t red = 0;
|
||||
uint32_t green = 0;
|
||||
uint32_t blue = 0;
|
||||
|
||||
if (LED_ID_RED < LED_ID_MAX && leds[LED_ID_RED].present && leds[LED_ID_RED].on)
|
||||
red = 255;
|
||||
if (LED_ID_GREEN < LED_ID_MAX && leds[LED_ID_GREEN].present && leds[LED_ID_GREEN].on)
|
||||
green = 255;
|
||||
if (LED_ID_BLUE < LED_ID_MAX && leds[LED_ID_BLUE].present && leds[LED_ID_BLUE].on)
|
||||
blue = 255;
|
||||
|
||||
ledc_set_rgb(red, green, blue);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Task de efeitos (BREATHING)
|
||||
// ----------------------------
|
||||
static void led_effect_task(void *arg)
|
||||
{
|
||||
// v = "intensidade" lógica 0–100
|
||||
uint32_t v = 0;
|
||||
bool up = true;
|
||||
|
||||
const uint32_t v_min = 0;
|
||||
const uint32_t v_max = 100;
|
||||
const uint32_t step = 3;
|
||||
const TickType_t delay_breathe = pdMS_TO_TICKS(50); // velocidade da respiração
|
||||
const TickType_t delay_idle = pdMS_TO_TICKS(1000); // quando não há LED em BREATHING
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// Verifica se algum LED está em BREATHING
|
||||
bool has_breath = false;
|
||||
led_id_t breath_id = LED_ID_MAX;
|
||||
|
||||
for (int i = 0; i < LED_ID_MAX; ++i)
|
||||
{
|
||||
if (leds[i].present && leds[i].pattern == LED_PATTERN_BREATHING)
|
||||
{
|
||||
has_breath = true;
|
||||
breath_id = (led_id_t)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_breath)
|
||||
{
|
||||
// Usa o valor atual de v para calcular o brilho (0–255)
|
||||
uint32_t brightness = (v * 255U) / 100U;
|
||||
uint32_t r = 0, g = 0, b = 0;
|
||||
|
||||
switch (breath_id)
|
||||
{
|
||||
case LED_ID_RED:
|
||||
r = brightness;
|
||||
break;
|
||||
case LED_ID_GREEN:
|
||||
g = brightness;
|
||||
break;
|
||||
case LED_ID_BLUE:
|
||||
b = brightness;
|
||||
break;
|
||||
default:
|
||||
// fallback: usa azul se algo estranho acontecer
|
||||
b = brightness;
|
||||
break;
|
||||
}
|
||||
|
||||
// Aplica só um canal, sem misturar cores
|
||||
ledc_set_rgb(r, g, b);
|
||||
|
||||
// Se estiver completamente apagado (v == 0),
|
||||
// queremos que fique 500 ms off antes de voltar a subir
|
||||
TickType_t delay = (v == v_min) ? pdMS_TO_TICKS(500) : delay_breathe;
|
||||
|
||||
// Atualiza v para a próxima iteração (triângulo 0→100→0)
|
||||
if (up)
|
||||
{
|
||||
if (v + step >= v_max)
|
||||
{
|
||||
v = v_max;
|
||||
up = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
v += step;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v <= v_min + step)
|
||||
{
|
||||
v = v_min;
|
||||
up = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
v -= step;
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ninguém em BREATHING: deixa padrões normais controlarem o LED
|
||||
led_update_rgb_from_state();
|
||||
vTaskDelay(delay_idle);
|
||||
|
||||
// Opcional: quando sair de BREATHING e voltar mais tarde,
|
||||
// começa de novo a partir de apagado
|
||||
v = v_min;
|
||||
up = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Aplica padrão em função do estado EVSE (base)
|
||||
// GARANTINDO apenas 1 LED por estado
|
||||
// ----------------------------
|
||||
static void led_apply_state_mode(evse_state_event_t state)
|
||||
{
|
||||
// Desliga padrões anteriores para todos os canais
|
||||
for (int i = 0; i < LED_ID_MAX; ++i)
|
||||
{
|
||||
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case EVSE_STATE_EVENT_IDLE:
|
||||
// IDLE → verde fixo (claro e visível)
|
||||
led_apply_pattern(LED_ID_GREEN, LED_PATTERN_ON);
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_WAITING:
|
||||
// WAITING → azul a piscar lento
|
||||
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_SLOW);
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_CHARGING:
|
||||
// CHARGING → azul "breathing"
|
||||
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BREATHING);
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_FAULT:
|
||||
// FAULT → vermelho a piscar rápido
|
||||
led_apply_pattern(LED_ID_RED, LED_PATTERN_BLINK_FAST);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
led_update_rgb_from_state();
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Timer callback do efeito de sessão
|
||||
// Efeitos usam sempre UM LED forte
|
||||
// ----------------------------
|
||||
static void session_effect_timer_cb(TimerHandle_t xTimer)
|
||||
{
|
||||
switch (session_effect_type)
|
||||
{
|
||||
case SESSION_EFFECT_START:
|
||||
session_effect_phase++;
|
||||
switch (session_effect_phase)
|
||||
{
|
||||
case 1:
|
||||
// Fase 1: depois de flash sólido, passa a azul a piscar rápido
|
||||
for (int i = 0; i < LED_ID_MAX; ++i)
|
||||
{
|
||||
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
|
||||
}
|
||||
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
|
||||
led_update_rgb_from_state();
|
||||
|
||||
// Mantém piscar rápido mais um bocado
|
||||
xTimerChangePeriod(session_effect_timer,
|
||||
pdMS_TO_TICKS(5000),
|
||||
BLOCK_TIME);
|
||||
xTimerStart(session_effect_timer, BLOCK_TIME);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
default:
|
||||
// Fim do efeito de START → volta ao estado base (tipicamente CHARGING)
|
||||
session_effect_active = false;
|
||||
session_effect_type = SESSION_EFFECT_NONE;
|
||||
session_effect_phase = 0;
|
||||
led_apply_state_mode(current_state_mode);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SESSION_EFFECT_FINISH:
|
||||
session_effect_phase++;
|
||||
switch (session_effect_phase)
|
||||
{
|
||||
case 1:
|
||||
// Fase 1: depois de flash sólido, passa a azul a piscar rápido
|
||||
for (int i = 0; i < LED_ID_MAX; ++i)
|
||||
{
|
||||
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
|
||||
}
|
||||
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
|
||||
led_update_rgb_from_state();
|
||||
|
||||
// Mantém piscar rápido mais tempo (destaque de fim de sessão)
|
||||
xTimerChangePeriod(session_effect_timer,
|
||||
pdMS_TO_TICKS(5000),
|
||||
BLOCK_TIME);
|
||||
xTimerStart(session_effect_timer, BLOCK_TIME);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
default:
|
||||
// Fim do efeito de FINISH → volta ao estado base (IDLE/WAITING)
|
||||
session_effect_active = false;
|
||||
session_effect_type = SESSION_EFFECT_NONE;
|
||||
session_effect_phase = 0;
|
||||
led_apply_state_mode(current_state_mode);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SESSION_EFFECT_NONE:
|
||||
default:
|
||||
session_effect_active = false;
|
||||
session_effect_phase = 0;
|
||||
led_apply_state_mode(current_state_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Event Handler: EVSE State
|
||||
// ----------------------------
|
||||
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
|
||||
|
||||
ESP_LOGI(TAG, "EVSE State Changed: state=%d", evt->state);
|
||||
|
||||
// Atualiza o estado base
|
||||
current_state_mode = evt->state;
|
||||
|
||||
// Se estiver a decorrer um efeito de sessão, não mexe agora nos LEDs.
|
||||
if (session_effect_active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
led_apply_state_mode(current_state_mode);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Event Handler: EVSE Session
|
||||
// (efeitos de início/fim de sessão, 1 LED de cada vez)
|
||||
// ----------------------------
|
||||
static void evse_session_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
if (base != EVSE_EVENTS || id != EVSE_EVENT_SESSION || data == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const evse_session_event_data_t *evt =
|
||||
(const evse_session_event_data_t *)data;
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"EVSE Session Event: type=%d, id=%" PRIu32
|
||||
", duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg=%" PRIu32 " W, current=%d",
|
||||
(int)evt->type,
|
||||
evt->session_id,
|
||||
evt->duration_s,
|
||||
evt->energy_wh,
|
||||
evt->avg_power_w,
|
||||
evt->is_current);
|
||||
|
||||
// Marca que um efeito de sessão está ativo
|
||||
session_effect_active = true;
|
||||
session_effect_phase = 0;
|
||||
|
||||
if (session_effect_timer)
|
||||
{
|
||||
xTimerStop(session_effect_timer, BLOCK_TIME);
|
||||
}
|
||||
|
||||
// Apaga tudo antes de iniciar o efeito
|
||||
for (int i = 0; i < LED_ID_MAX; ++i)
|
||||
{
|
||||
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
|
||||
}
|
||||
|
||||
switch (evt->type)
|
||||
{
|
||||
case EVSE_SESSION_EVENT_STARTED:
|
||||
// Efeito de início:
|
||||
// Fase 0: azul sólido curto
|
||||
session_effect_type = SESSION_EFFECT_START;
|
||||
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON);
|
||||
led_update_rgb_from_state();
|
||||
|
||||
xTimerChangePeriod(session_effect_timer,
|
||||
pdMS_TO_TICKS(300), // 0.3 s flash
|
||||
BLOCK_TIME);
|
||||
xTimerStart(session_effect_timer, BLOCK_TIME);
|
||||
break;
|
||||
|
||||
case EVSE_SESSION_EVENT_FINISHED:
|
||||
// Efeito de fim:
|
||||
// Fase 0: azul sólido curto
|
||||
session_effect_type = SESSION_EFFECT_FINISH;
|
||||
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON);
|
||||
led_update_rgb_from_state();
|
||||
|
||||
xTimerChangePeriod(session_effect_timer,
|
||||
pdMS_TO_TICKS(300), // 0.3 s flash
|
||||
BLOCK_TIME);
|
||||
xTimerStart(session_effect_timer, BLOCK_TIME);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Se for tipo desconhecido, desiste do efeito e volta ao estado base
|
||||
session_effect_active = false;
|
||||
session_effect_type = SESSION_EFFECT_NONE;
|
||||
session_effect_phase = 0;
|
||||
led_apply_state_mode(current_state_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Inicialização
|
||||
// ----------------------------
|
||||
|
||||
void led_init(void)
|
||||
{
|
||||
// Marca quais LEDs existem de acordo com o board_config
|
||||
leds[LED_ID_GREEN].present = board_config.led_green;
|
||||
leds[LED_ID_BLUE].present = board_config.led_blue;
|
||||
leds[LED_ID_RED].present = board_config.led_red;
|
||||
|
||||
// Inicializa LEDC com os GPIOs definidos na board
|
||||
esp_err_t err = ledc_init(board_config.led_red_gpio,
|
||||
board_config.led_green_gpio,
|
||||
board_config.led_blue_gpio);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to init LEDC: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Regista handler de evento EVSE - STATE
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(
|
||||
EVSE_EVENTS,
|
||||
EVSE_EVENT_STATE_CHANGED,
|
||||
evse_led_event_handler,
|
||||
NULL));
|
||||
|
||||
// Regista handler de evento EVSE - SESSION
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(
|
||||
EVSE_EVENTS,
|
||||
EVSE_EVENT_SESSION,
|
||||
evse_session_led_event_handler,
|
||||
NULL));
|
||||
|
||||
// Cria task de efeitos (breathing)
|
||||
xTaskCreate(led_effect_task, "led_effect_task", 2048, NULL, 1, NULL);
|
||||
|
||||
// Cria timer one-shot para efeitos de sessão
|
||||
session_effect_timer = xTimerCreate(
|
||||
"session_eff",
|
||||
pdMS_TO_TICKS(1000), // valor default; ajustado com xTimerChangePeriod
|
||||
pdFALSE, // one-shot
|
||||
NULL,
|
||||
session_effect_timer_cb);
|
||||
|
||||
ESP_LOGI(TAG, "LED system initialized");
|
||||
|
||||
// Estado inicial: IDLE
|
||||
evse_state_event_data_t evt = {
|
||||
.state = EVSE_STATE_EVENT_IDLE};
|
||||
evse_led_event_handler(NULL, EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evt);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// API Pública
|
||||
// ----------------------------
|
||||
|
||||
void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime)
|
||||
{
|
||||
if (led_id >= LED_ID_MAX)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
led_t *led = &leds[led_id];
|
||||
if (!led->present)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (led->ontime == ontime && led->offtime == offtime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (led->timer)
|
||||
{
|
||||
xTimerStop(led->timer, BLOCK_TIME);
|
||||
}
|
||||
|
||||
led->ontime = ontime;
|
||||
led->offtime = offtime;
|
||||
|
||||
if (ontime == 0)
|
||||
{
|
||||
// sempre desligado
|
||||
led->on = false;
|
||||
}
|
||||
else if (offtime == 0)
|
||||
{
|
||||
// sempre ligado
|
||||
led->on = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// pisca
|
||||
led->on = true;
|
||||
|
||||
if (!led->timer)
|
||||
{
|
||||
// nome só para debug; opcional
|
||||
led->timer = xTimerCreate("led_timer",
|
||||
pdMS_TO_TICKS(ontime),
|
||||
pdFALSE,
|
||||
(void *)led,
|
||||
led_timer_callback);
|
||||
}
|
||||
|
||||
if (led->timer)
|
||||
{
|
||||
xTimerStart(led->timer, BLOCK_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
// Atualiza hardware (para estados sem BREATHING)
|
||||
led_update_rgb_from_state();
|
||||
}
|
||||
|
||||
void led_apply_pattern(led_id_t id, led_pattern_t pattern)
|
||||
{
|
||||
if (id >= LED_ID_MAX)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
led_t *led = &leds[id];
|
||||
if (!led->present)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((unsigned)pattern >= LED_PATTERN_COUNT)
|
||||
{
|
||||
ESP_LOGW(TAG, "Invalid LED pattern %d", pattern);
|
||||
return;
|
||||
}
|
||||
|
||||
if (led->pattern == pattern)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (led->timer)
|
||||
{
|
||||
xTimerStop(led->timer, BLOCK_TIME);
|
||||
}
|
||||
|
||||
led->pattern = pattern;
|
||||
led->blink_count = 0;
|
||||
|
||||
const led_timing_t *cfg = &led_pattern_table[pattern];
|
||||
led_set_state(id, cfg->on_ms, cfg->off_ms);
|
||||
|
||||
// led_set_state já chama led_update_rgb_from_state()
|
||||
}
|
||||
129
components/led/src/ledc_driver.c
Executable file
129
components/led/src/ledc_driver.c
Executable file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* LEDC driver para 3 LEDs (R,G,B) controlados via ULN2003
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#include "ledc_driver.h"
|
||||
|
||||
// Pode ser futuramente ligado a uma opção de Kconfig.
|
||||
// Para o teu hardware (comum a 12 V via ULN2003), visto do ESP é ativo-alto.
|
||||
#define IS_ACTIVE_HIGH 1
|
||||
|
||||
#define LEDC_LS_TIMER LEDC_TIMER_2
|
||||
#define LEDC_LS_MODE LEDC_LOW_SPEED_MODE
|
||||
|
||||
// Canais usados: 2, 3, 4
|
||||
#define LEDC_CH_RED LEDC_CHANNEL_2
|
||||
#define LEDC_CH_GREEN LEDC_CHANNEL_3
|
||||
#define LEDC_CH_BLUE LEDC_CHANNEL_4
|
||||
|
||||
#define LEDC_NUM_CHANNELS (3)
|
||||
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT
|
||||
#define LEDC_DUTY_MAX (8192 - 1)
|
||||
#define LEDC_FREQUENCY (5000) // 5 kHz
|
||||
|
||||
static ledc_channel_config_t ledc_channel[LEDC_NUM_CHANNELS] = {
|
||||
{
|
||||
.channel = LEDC_CH_RED,
|
||||
.duty = 0,
|
||||
.gpio_num = -1, // preenchido em runtime
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = LEDC_LS_TIMER,
|
||||
},
|
||||
{
|
||||
.channel = LEDC_CH_GREEN,
|
||||
.duty = 0,
|
||||
.gpio_num = -1,
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = LEDC_LS_TIMER,
|
||||
},
|
||||
{
|
||||
.channel = LEDC_CH_BLUE,
|
||||
.duty = 0,
|
||||
.gpio_num = -1,
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = LEDC_LS_TIMER,
|
||||
},
|
||||
};
|
||||
|
||||
esp_err_t ledc_init(gpio_num_t gpio_red,
|
||||
gpio_num_t gpio_green,
|
||||
gpio_num_t gpio_blue)
|
||||
{
|
||||
// Configuração do timer
|
||||
ledc_timer_config_t ledc_timer = {
|
||||
.duty_resolution = LEDC_DUTY_RES,
|
||||
.freq_hz = LEDC_FREQUENCY,
|
||||
.speed_mode = LEDC_LS_MODE,
|
||||
.timer_num = LEDC_LS_TIMER,
|
||||
.clk_cfg = LEDC_AUTO_CLK,
|
||||
};
|
||||
|
||||
esp_err_t err = ledc_timer_config(&ledc_timer);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
// Atribuir GPIOs aos canais
|
||||
ledc_channel[0].gpio_num = gpio_red;
|
||||
ledc_channel[1].gpio_num = gpio_green;
|
||||
ledc_channel[2].gpio_num = gpio_blue;
|
||||
|
||||
// Configurar canais
|
||||
for (int ch = 0; ch < LEDC_NUM_CHANNELS; ch++)
|
||||
{
|
||||
err = ledc_channel_config(&ledc_channel[ch]);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue)
|
||||
{
|
||||
if (red > 255)
|
||||
red = 255;
|
||||
if (green > 255)
|
||||
green = 255;
|
||||
if (blue > 255)
|
||||
blue = 255;
|
||||
|
||||
red = red * LEDC_DUTY_MAX / 255;
|
||||
green = green * LEDC_DUTY_MAX / 255;
|
||||
blue = blue * LEDC_DUTY_MAX / 255;
|
||||
|
||||
if (!IS_ACTIVE_HIGH)
|
||||
{
|
||||
red = LEDC_DUTY_MAX - red;
|
||||
green = LEDC_DUTY_MAX - green;
|
||||
blue = LEDC_DUTY_MAX - blue;
|
||||
}
|
||||
|
||||
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_RED, red);
|
||||
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_RED);
|
||||
|
||||
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_GREEN, green);
|
||||
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_GREEN);
|
||||
|
||||
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_BLUE, blue);
|
||||
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_BLUE);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ledc_clear(void)
|
||||
{
|
||||
return ledc_set_rgb(0, 0, 0);
|
||||
}
|
||||
Reference in New Issue
Block a user