/*******************************************************************************
* Part 4 - Example 9: CAPSENSE via IPC (CM55 Side)
*
* CM33-NS reads PSoC 4000T CAPSENSE via I2C (slave 0x08) and sends
* state changes to CM55 via IPC_CMD_CAPSENSE_DATA.
*
* Advantages over Part 1 Ex11 (direct I2C on CM55):
* - Touch screen remains functional (no lv_port_indev_disable_touch)
* - Follows CM33=HW service, CM55=UI pattern (IPC architecture)
* - Edge detection on CM33 reduces IPC traffic
*
* Data format (ipc_msg_t.data[0..3]):
* [0] btn0: 0=released, 1=pressed
* [1] btn1: 0=released, 1=pressed
* [2] slider_pos: 0-100 (0=no touch)
* [3] slider_active: 0=not touching, 1=touching
*
* CRITICAL: LVGL is NOT thread-safe!
* IPC callback -> volatile flags -> LVGL timer -> UI update
******************************************************************************/
#include "lvgl.h"
#include "ipc/cm55_ipc_pipe.h"
#include "../../shared/ipc_shared.h"
#define EX9_NUM_BUTTONS 2
/* ===== UI Elements ===== */
static lv_obj_t *ex9_btn_panels[EX9_NUM_BUTTONS];
static lv_obj_t *ex9_btn_leds[EX9_NUM_BUTTONS];
static lv_obj_t *ex9_btn_status[EX9_NUM_BUTTONS];
static lv_obj_t *ex9_slider;
static lv_obj_t *ex9_slider_value;
static lv_obj_t *ex9_output_led;
static lv_obj_t *ex9_ipc_count_label;
static lv_timer_t *ex9_timer;
/* ===== IPC Flag-Based Pattern (volatile for thread safety) ===== */
static volatile bool ex9_capsense_updated = false;
static volatile uint8_t ex9_rx_btn0 = 0;
static volatile uint8_t ex9_rx_btn1 = 0;
static volatile uint8_t ex9_rx_slider = 0;
static volatile uint8_t ex9_rx_slider_active = 0;
static uint32_t ex9_ipc_event_count = 0;
/* Previous state for UI edge detection (reduces unnecessary redraws) */
static uint8_t ex9_prev_btn0 = 0xFF;
static uint8_t ex9_prev_btn1 = 0xFF;
static uint8_t ex9_prev_slider = 0xFF;
/*******************************************************************************
* IPC Receive Callback (NOT LVGL-safe context!)
*
* CRITICAL: This function runs in IPC task context.
* NEVER call any lv_* function here!
* Only copy data to volatile variables and set the flag.
******************************************************************************/
static void ex9_ipc_callback(const ipc_msg_t *msg, void *user_data)
{
(void)user_data;
if (msg->cmd == IPC_CMD_CAPSENSE_DATA) {
/* Copy payload to volatile variables */
ex9_rx_btn0 = (uint8_t)msg->data[0];
ex9_rx_btn1 = (uint8_t)msg->data[1];
ex9_rx_slider = (uint8_t)msg->data[2];
ex9_rx_slider_active = (uint8_t)msg->data[3];
/* Set flag -- LVGL timer will read and update UI */
ex9_capsense_updated = true;
}
}
/*******************************************************************************
* Update UI with CAPSENSE data (called from LVGL timer - SAFE context)
*
* Uses edge detection: only update widgets when state actually changes.
* This reduces unnecessary LVGL redraws and improves performance.
******************************************************************************/
static void ex9_update_ui(uint8_t btn0, uint8_t btn1, uint8_t slider_pos)
{
/* ===== Button 0 (CSB1) ===== */
if (btn0 != ex9_prev_btn0) {
if (btn0) {
lv_obj_set_style_bg_color(ex9_btn_panels[0],
lv_color_hex(0x00AA00), 0);
lv_led_on(ex9_btn_leds[0]);
lv_label_set_text(ex9_btn_status[0], "TOUCHED");
lv_obj_set_style_text_color(ex9_btn_status[0],
AIC_COLOR_SUCCESS, 0);
} else {
lv_obj_set_style_bg_color(ex9_btn_panels[0],
lv_color_hex(0x333355), 0);
lv_led_off(ex9_btn_leds[0]);
lv_label_set_text(ex9_btn_status[0], "Ready");
lv_obj_set_style_text_color(ex9_btn_status[0],
AIC_COLOR_TEXT_DIM, 0);
}
ex9_prev_btn0 = btn0;
}
/* ===== Button 1 (CSB2) ===== */
if (btn1 != ex9_prev_btn1) {
if (btn1) {
lv_obj_set_style_bg_color(ex9_btn_panels[1],
lv_color_hex(0x00AA00), 0);
lv_led_on(ex9_btn_leds[1]);
lv_label_set_text(ex9_btn_status[1], "TOUCHED");
lv_obj_set_style_text_color(ex9_btn_status[1],
AIC_COLOR_SUCCESS, 0);
} else {
lv_obj_set_style_bg_color(ex9_btn_panels[1],
lv_color_hex(0x333355), 0);
lv_led_off(ex9_btn_leds[1]);
lv_label_set_text(ex9_btn_status[1], "Ready");
lv_obj_set_style_text_color(ex9_btn_status[1],
AIC_COLOR_TEXT_DIM, 0);
}
ex9_prev_btn1 = btn1;
}
/* ===== Slider (CSS1) ===== */
if (slider_pos != ex9_prev_slider) {
lv_slider_set_value(ex9_slider, slider_pos, LV_ANIM_ON);
lv_label_set_text_fmt(ex9_slider_value, "%d%%", slider_pos);
/* LED brightness follows slider position */
if (slider_pos > 0) {
uint8_t brightness = (uint8_t)(slider_pos * 255 / 100);
lv_led_on(ex9_output_led);
lv_led_set_brightness(ex9_output_led, brightness);
} else {
lv_led_off(ex9_output_led);
}
ex9_prev_slider = slider_pos;
}
}
/*******************************************************************************
* LVGL Timer Callback (50ms) - Check IPC flag and update UI
*
* This runs in LVGL context - SAFE to call lv_* functions here.
******************************************************************************/
static void ex9_timer_cb(lv_timer_t *timer)
{
(void)timer;
if (ex9_capsense_updated) {
ex9_capsense_updated = false; /* Clear flag FIRST */
ex9_ipc_event_count++;
/* Update UI widgets (with edge detection) */
ex9_update_ui(ex9_rx_btn0, ex9_rx_btn1, ex9_rx_slider);
/* Update IPC event counter in footer */
lv_label_set_text_fmt(ex9_ipc_count_label,
"Ex9: CAPSENSE (IPC) IPC events: %u",
(unsigned)ex9_ipc_event_count);
}
}
/*******************************************************************************
* Main Function: Build CAPSENSE IPC UI
******************************************************************************/
void part4_ex9_capsense_ipc(void)
{
/* Register IPC callback for CAPSENSE_DATA */
cm55_ipc_register_callback(ex9_ipc_callback, NULL);
/* Reset UI state */
ex9_prev_btn0 = 0xFF;
ex9_prev_btn1 = 0xFF;
ex9_prev_slider = 0xFF;
ex9_ipc_event_count = 0;
ex9_capsense_updated = false;
lv_obj_t *scr = lv_screen_active();
lv_obj_set_style_bg_color(scr, lv_color_hex(0x1a1a2e), LV_PART_MAIN);
/* ===== TITLE ===== */
lv_obj_t *title = lv_label_create(scr);
lv_label_set_text(title, "Part 4 Ex9: CAPSENSE (IPC)");
lv_obj_set_style_text_color(title, AIC_COLOR_TEXT, 0);
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);
/* ===== MODE LABEL ===== */
lv_obj_t *mode_label = lv_label_create(scr);
if (cm55_ipc_is_init()) {
lv_label_set_text(mode_label,
"Mode: IPC (CM33 reads I2C -> CM55 display)");
lv_obj_set_style_text_color(mode_label, AIC_COLOR_SUCCESS, 0);
} else {
lv_label_set_text(mode_label, "Mode: IPC NOT initialized");
lv_obj_set_style_text_color(mode_label, AIC_COLOR_ERROR, 0);
}
lv_obj_align(mode_label, LV_ALIGN_TOP_MID, 0, 32);
/* ===== CAPSENSE SLIDER PANEL (420x80) ===== */
lv_obj_t *slider_panel = lv_obj_create(scr);
lv_obj_set_size(slider_panel, 420, 80);
lv_obj_align(slider_panel, LV_ALIGN_TOP_MID, 0, 55);
lv_obj_set_style_bg_color(slider_panel, lv_color_hex(0x0f0f23), 0);
lv_obj_set_style_pad_all(slider_panel, 8, 0);
lv_obj_clear_flag(slider_panel, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *slider_title = lv_label_create(slider_panel);
lv_label_set_text(slider_title, "SLIDER (CSS1)");
lv_obj_set_style_text_color(slider_title, AIC_COLOR_TEXT, 0);
lv_obj_align(slider_title, LV_ALIGN_TOP_LEFT, 10, 0);
ex9_slider_value = lv_label_create(slider_panel);
lv_label_set_text(ex9_slider_value, "0%");
lv_obj_set_style_text_color(ex9_slider_value, AIC_COLOR_PRIMARY, 0);
lv_obj_set_style_text_font(ex9_slider_value, &lv_font_montserrat_16, 0);
lv_obj_align(ex9_slider_value, LV_ALIGN_TOP_RIGHT, -10, 0);
/* Slider widget (read-only: controlled by IPC data, not touch) */
ex9_slider = lv_slider_create(slider_panel);
lv_obj_set_width(ex9_slider, 340);
lv_obj_set_height(ex9_slider, 25);
lv_obj_align(ex9_slider, LV_ALIGN_BOTTOM_MID, 0, -8);
lv_slider_set_range(ex9_slider, 0, 100);
lv_slider_set_value(ex9_slider, 0, LV_ANIM_OFF);
lv_obj_set_style_bg_color(ex9_slider, lv_color_hex(0x333355),
LV_PART_MAIN);
lv_obj_set_style_bg_color(ex9_slider, AIC_COLOR_PRIMARY,
LV_PART_INDICATOR);
lv_obj_remove_flag(ex9_slider, LV_OBJ_FLAG_CLICKABLE);
/* Output LED next to slider */
ex9_output_led = lv_led_create(slider_panel);
lv_obj_set_size(ex9_output_led, 25, 25);
lv_obj_align(ex9_output_led, LV_ALIGN_BOTTOM_RIGHT, -5, -8);
lv_led_set_color(ex9_output_led,
lv_palette_main(LV_PALETTE_LIGHT_BLUE));
lv_led_off(ex9_output_led);
/* ===== CAPSENSE BUTTON PANELS (140x150 each) ===== */
const char *btn_names[] = {"BTN0", "BTN1"};
const char *btn_ids[] = {"(CSB1)", "(CSB2)"};
uint32_t led_colors[] = {0xff0000, 0x00ff00};
int btn_x_pos[] = {-110, 110};
for (int i = 0; i < EX9_NUM_BUTTONS; i++) {
ex9_btn_panels[i] = lv_obj_create(scr);
lv_obj_set_size(ex9_btn_panels[i], 140, 150);
lv_obj_align(ex9_btn_panels[i], LV_ALIGN_BOTTOM_MID,
btn_x_pos[i], -55);
lv_obj_set_style_bg_color(ex9_btn_panels[i],
lv_color_hex(0x333355), 0);
lv_obj_set_style_border_width(ex9_btn_panels[i], 3, 0);
lv_obj_set_style_border_color(ex9_btn_panels[i],
lv_color_hex(0x666699), 0);
lv_obj_set_style_radius(ex9_btn_panels[i], 10, 0);
lv_obj_set_style_pad_all(ex9_btn_panels[i], 5, 0);
lv_obj_clear_flag(ex9_btn_panels[i], LV_OBJ_FLAG_SCROLLABLE);
/* Button name */
lv_obj_t *name = lv_label_create(ex9_btn_panels[i]);
lv_label_set_text(name, btn_names[i]);
lv_obj_set_style_text_color(name, AIC_COLOR_TEXT, 0);
lv_obj_set_style_text_font(name, &lv_font_montserrat_16, 0);
lv_obj_align(name, LV_ALIGN_TOP_MID, 0, 2);
/* Hardware ID */
lv_obj_t *id_lbl = lv_label_create(ex9_btn_panels[i]);
lv_label_set_text(id_lbl, btn_ids[i]);
lv_obj_set_style_text_color(id_lbl, AIC_COLOR_TEXT_DIM, 0);
lv_obj_align(id_lbl, LV_ALIGN_TOP_MID, 0, 22);
/* LED indicator */
ex9_btn_leds[i] = lv_led_create(ex9_btn_panels[i]);
lv_obj_set_size(ex9_btn_leds[i], 50, 50);
lv_obj_align(ex9_btn_leds[i], LV_ALIGN_CENTER, 0, 8);
lv_led_set_color(ex9_btn_leds[i], lv_color_hex(led_colors[i]));
lv_led_off(ex9_btn_leds[i]);
/* Status label */
ex9_btn_status[i] = lv_label_create(ex9_btn_panels[i]);
lv_label_set_text(ex9_btn_status[i], "Ready");
lv_obj_set_style_text_color(ex9_btn_status[i],
AIC_COLOR_TEXT_DIM, 0);
lv_obj_align(ex9_btn_status[i], LV_ALIGN_BOTTOM_MID, 0, -2);
}
/* ===== FOOTER STATUS ===== */
ex9_ipc_count_label = lv_label_create(scr);
lv_label_set_text(ex9_ipc_count_label,
"Ex9: CAPSENSE (IPC) IPC events: 0");
lv_obj_set_style_text_color(ex9_ipc_count_label,
AIC_COLOR_TEXT_DIM, 0);
lv_obj_set_style_text_align(ex9_ipc_count_label,
LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(ex9_ipc_count_label, LV_ALIGN_BOTTOM_MID, 0, -25);
/* Copyright footer */
aic_create_footer(scr);
/* Timer to check IPC flags and update UI (50ms interval) */
ex9_timer = lv_timer_create(ex9_timer_cb, 50, NULL);
/* Request initial CAPSENSE state from CM33 */
if (cm55_ipc_is_init()) {
cm55_ipc_send_cmd(IPC_CMD_CAPSENSE_REQ, 0);
}
printf("[Part4] Ex9: CAPSENSE via IPC started\n");
printf(" - CM33-NS reads PSoC 4000T (I2C 0x08)\n");
printf(" - State changes sent via IPC_CMD_CAPSENSE_DATA\n");
printf(" - Touch screen remains functional!\n");
}