/*******************************************************************************
* Part 4 - Example 5: Hardware IPC LED Control (CM55 Side)
*
* Learning: IPC LED commands, flag-based pattern, multi-LED control
* Platform: PSoC Edge E84 - CM55 Core + LVGL v9.2.0
*
* CRITICAL: LVGL is NOT thread-safe!
* - IPC callback sets flags only
* - LVGL timer reads flags and updates UI
******************************************************************************/
#include "lvgl.h"
#include "ipc/cm55_ipc_pipe.h"
#include "../../shared/ipc_shared.h"
/* ===== LED Definitions ===== */
#define NUM_LEDS 3
typedef struct {
uint8_t id;
const char *name;
lv_color_t color;
bool state;
uint8_t brightness;
} led_info_t;
static led_info_t leds[NUM_LEDS] = {
{ 0, "Red", {0}, false, 100 },
{ 1, "Green", {0}, false, 100 },
{ 2, "Blue", {0}, false, 100 },
};
/* ===== LVGL UI Objects ===== */
static lv_obj_t * led_widgets[NUM_LEDS]; /* LED indicator widgets */
static lv_obj_t * led_switches[NUM_LEDS]; /* ON/OFF switches */
static lv_obj_t * led_status[NUM_LEDS]; /* Status labels */
static lv_obj_t * brightness_slider;
static lv_obj_t * brightness_label;
static lv_obj_t * ipc_status_label;
/* ===== IPC Flag Variables (volatile for thread safety) ===== */
static volatile bool ipc_led_ack_flag = false;
static volatile uint8_t ipc_ack_led_id = 0;
static volatile bool ipc_ack_state = false;
static volatile uint8_t ipc_ack_brightness = 0;
static volatile uint32_t ipc_error_count = 0;
/*******************************************************************************
* IPC Receive Callback (ISR context - NEVER call LVGL here!)
******************************************************************************/
static void ex5_ipc_rx_callback(const ipc_msg_t *msg, void *user_data)
{
(void)user_data;
switch (msg->cmd) {
case IPC_CMD_ACK: {
/* CM33 confirmed LED state change */
ipc_led_data_t *led_data = (ipc_led_data_t *)msg->data;
ipc_ack_led_id = led_data->led_id;
ipc_ack_state = (led_data->state != 0);
ipc_ack_brightness = led_data->brightness;
ipc_led_ack_flag = true; /* Set flag - LVGL timer will handle */
break;
}
case IPC_CMD_NACK:
ipc_error_count++;
break;
default:
break;
}
}
/*******************************************************************************
* LVGL Timer: Check IPC flags and update UI (safe context)
******************************************************************************/
static void ex5_ipc_timer_cb(lv_timer_t *timer)
{
(void)timer;
if (ipc_led_ack_flag) {
ipc_led_ack_flag = false;
uint8_t id = ipc_ack_led_id;
if (id < NUM_LEDS) {
/* Update internal state */
leds[id].state = ipc_ack_state;
leds[id].brightness = ipc_ack_brightness;
/* Update LED widget */
if (leds[id].state) {
lv_led_on(led_widgets[id]);
lv_led_set_brightness(led_widgets[id],
(uint8_t)(leds[id].brightness * 255 / 100));
} else {
lv_led_off(led_widgets[id]);
}
/* Update status label */
if (leds[id].state) {
lv_label_set_text_fmt(led_status[id],
"%s: ON (%d%%)", leds[id].name, (int)leds[id].brightness);
lv_obj_set_style_text_color(led_status[id],
lv_palette_main(LV_PALETTE_GREEN), 0);
} else {
lv_label_set_text_fmt(led_status[id], "%s: OFF", leds[id].name);
lv_obj_set_style_text_color(led_status[id],
lv_palette_main(LV_PALETTE_RED), 0);
}
}
/* Update IPC status */
lv_label_set_text_fmt(ipc_status_label,
"IPC: ACK received (LED%d = %s) | Errors: %u",
(int)id, ipc_ack_state ? "ON" : "OFF",
(unsigned int)ipc_error_count);
}
}
/*******************************************************************************
* Switch Event Callback - ส่ง IPC LED ON/OFF
******************************************************************************/
static void ex5_switch_cb(lv_event_t *e)
{
lv_obj_t *sw = lv_event_get_target(e);
int *led_idx = (int *)lv_event_get_user_data(e);
int idx = *led_idx;
if (idx < 0 || idx >= NUM_LEDS) return;
bool is_on = lv_obj_has_state(sw, LV_STATE_CHECKED);
/* ส่ง IPC command ไป CM33-NS */
cy_en_ipc_pipe_status_t status;
status = cm55_ipc_set_led(leds[idx].id, is_on);
if (status == CY_IPC_PIPE_SUCCESS) {
lv_label_set_text_fmt(ipc_status_label,
"IPC: Sent LED_%s %s (waiting ACK...)",
leds[idx].name, is_on ? "ON" : "OFF");
CM55_LOGI("LED %s -> %s", leds[idx].name, is_on ? "ON" : "OFF");
} else {
lv_label_set_text_fmt(ipc_status_label,
"IPC: SEND FAILED (LED_%s) err=%d",
leds[idx].name, (int)status);
CM55_LOGE("IPC send failed for LED%d, err=%d", idx, (int)status);
ipc_error_count++;
}
}
/*******************************************************************************
* Slider Event Callback - ส่ง IPC LED Brightness
******************************************************************************/
static void ex5_brightness_cb(lv_event_t *e)
{
lv_obj_t *slider = lv_event_get_target(e);
int32_t value = lv_slider_get_value(slider);
/* Update label */
lv_label_set_text_fmt(brightness_label, "Brightness: %d%%", (int)value);
/* ส่ง brightness ให้ LED ทุกดวงที่เปิดอยู่ */
for (int i = 0; i < NUM_LEDS; i++) {
if (leds[i].state) {
cm55_ipc_set_led_brightness(leds[i].id, (uint8_t)value);
/* CRITICAL: 20ms delay ระหว่าง IPC sends
* ป้องกัน CM55 rx_buffer overwrite */
vTaskDelay(pdMS_TO_TICKS(20));
}
}
CM55_LOGD("Brightness set to %d%%", (int)value);
}
/*******************************************************************************
* Main Function: สร้าง LED Control Panel
******************************************************************************/
/* Static index storage for user_data (ต้องเป็น static เพื่อให้ pointer valid) */
static int led_indices[NUM_LEDS] = {0, 1, 2};
void part4_ex5_hw_ipc_led(void)
{
/* [1] Initialize LED colors */
leds[0].color = lv_palette_main(LV_PALETTE_RED);
leds[1].color = lv_palette_main(LV_PALETTE_GREEN);
leds[2].color = lv_palette_main(LV_PALETTE_BLUE);
/* [2] Register IPC callback */
cm55_ipc_register_callback(ex5_ipc_rx_callback, NULL);
/* [3] Background */
lv_obj_set_style_bg_color(lv_screen_active(),
lv_color_hex(0x0a0e27), LV_PART_MAIN);
lv_obj_clear_flag(lv_screen_active(), LV_OBJ_FLAG_SCROLLABLE);
/* [4] Title */
lv_obj_t *title = lv_label_create(lv_screen_active());
lv_label_set_text(title, "Part 4 - Ex5: IPC LED Control");
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(title, &lv_font_montserrat_18, 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);
lv_obj_t *subtitle = lv_label_create(lv_screen_active());
lv_label_set_text(subtitle, "CM55 UI --> IPC --> CM33 GPIO");
lv_obj_set_style_text_color(subtitle, lv_color_hex(0x888888), 0);
lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 30);
/* [5] LED Control Panel (Container) */
lv_obj_t *panel = lv_obj_create(lv_screen_active());
lv_obj_set_size(panel, 440, 160);
lv_obj_align(panel, LV_ALIGN_TOP_MID, 0, 50);
lv_obj_set_style_bg_color(panel, lv_color_hex(0x111633), 0);
lv_obj_set_style_border_color(panel, lv_color_hex(0x333366), 0);
lv_obj_set_style_border_width(panel, 1, 0);
lv_obj_set_style_radius(panel, 8, 0);
lv_obj_clear_flag(panel, LV_OBJ_FLAG_SCROLLABLE);
/* [6] สร้าง LED rows (LED widget + Switch + Status) */
for (int i = 0; i < NUM_LEDS; i++) {
int y_pos = 5 + i * 48;
/* LED indicator widget */
led_widgets[i] = lv_led_create(panel);
lv_obj_set_size(led_widgets[i], 30, 30);
lv_obj_set_pos(led_widgets[i], 10, y_pos);
lv_led_set_color(led_widgets[i], leds[i].color);
lv_led_off(led_widgets[i]);
/* LED name label */
lv_obj_t *name_lbl = lv_label_create(panel);
lv_label_set_text(name_lbl, leds[i].name);
lv_obj_set_style_text_color(name_lbl, lv_color_hex(0xCCCCCC), 0);
lv_obj_set_pos(name_lbl, 50, y_pos + 6);
/* ON/OFF Switch */
led_switches[i] = lv_switch_create(panel);
lv_obj_set_size(led_switches[i], 60, 28);
lv_obj_set_pos(led_switches[i], 130, y_pos + 2);
lv_obj_set_style_bg_color(led_switches[i],
leds[i].color, LV_PART_INDICATOR | LV_STATE_CHECKED);
lv_obj_add_event_cb(led_switches[i], ex5_switch_cb,
LV_EVENT_VALUE_CHANGED, &led_indices[i]);
/* Status label */
led_status[i] = lv_label_create(panel);
lv_label_set_text_fmt(led_status[i], "%s: OFF", leds[i].name);
lv_obj_set_style_text_color(led_status[i],
lv_palette_main(LV_PALETTE_RED), 0);
lv_obj_set_pos(led_status[i], 210, y_pos + 6);
}
/* [7] Brightness Control */
lv_obj_t *bright_panel = lv_obj_create(lv_screen_active());
lv_obj_set_size(bright_panel, 440, 50);
lv_obj_align(bright_panel, LV_ALIGN_TOP_MID, 0, 218);
lv_obj_set_style_bg_color(bright_panel, lv_color_hex(0x111633), 0);
lv_obj_set_style_border_color(bright_panel, lv_color_hex(0x333366), 0);
lv_obj_set_style_border_width(bright_panel, 1, 0);
lv_obj_set_style_radius(bright_panel, 8, 0);
lv_obj_clear_flag(bright_panel, LV_OBJ_FLAG_SCROLLABLE);
brightness_label = lv_label_create(bright_panel);
lv_label_set_text(brightness_label, "Brightness: 100%");
lv_obj_set_style_text_color(brightness_label, lv_color_hex(0xCCCCCC), 0);
lv_obj_set_pos(brightness_label, 10, 8);
brightness_slider = lv_slider_create(bright_panel);
lv_obj_set_size(brightness_slider, 200, 15);
lv_obj_set_pos(brightness_slider, 200, 12);
lv_slider_set_range(brightness_slider, 0, 100);
lv_slider_set_value(brightness_slider, 100, LV_ANIM_OFF);
lv_obj_set_style_bg_color(brightness_slider,
lv_palette_main(LV_PALETTE_AMBER),
LV_PART_INDICATOR);
lv_obj_set_style_bg_color(brightness_slider,
lv_palette_main(LV_PALETTE_AMBER),
LV_PART_KNOB);
lv_obj_add_event_cb(brightness_slider, ex5_brightness_cb,
LV_EVENT_VALUE_CHANGED, NULL);
/* [8] IPC Status bar */
ipc_status_label = lv_label_create(lv_screen_active());
lv_label_set_text(ipc_status_label, "IPC: Ready (waiting for command...)");
lv_obj_set_style_text_color(ipc_status_label,
lv_palette_main(LV_PALETTE_CYAN), 0);
lv_obj_set_style_text_font(ipc_status_label, &lv_font_montserrat_12, 0);
lv_obj_align(ipc_status_label, LV_ALIGN_BOTTOM_MID, 0, -8);
/* [9] Create LVGL Timer to check IPC flags (50ms interval) */
lv_timer_create(ex5_ipc_timer_cb, 50, NULL);
CM55_LOGI("Part4 Ex5: IPC LED Control initialized");
}