Add human IR (PIR) module test on GPIO22 with SoftSPI touch and LVGL.

Motion-start count, hold bar, auto polarity, and flashable firmware/cyd package.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-23 14:46:56 +08:00
commit bab93d0e36
23 changed files with 1982 additions and 0 deletions

154
src/ui.cpp Normal file
View File

@@ -0,0 +1,154 @@
#include "ui.h"
#include "pir.h"
#include "pins.h"
#include <Arduino.h>
#include <lvgl.h>
#include <stdio.h>
static constexpr uint32_t COL_BG = 0x0A1016;
static constexpr uint32_t COL_INK = 0xEAF0F6;
static constexpr uint32_t COL_MUTED = 0x7A8B9C;
static constexpr uint32_t COL_BTN = 0x243140;
static constexpr uint32_t COL_ACCENT = 0x4FC3F7;
static constexpr uint32_t COL_HOT = 0xFF7043;
static lv_obj_t *lbl_title;
static lv_obj_t *lbl_pin;
static lv_obj_t *lbl_count;
static lv_obj_t *lbl_hint;
static lv_obj_t *lbl_state;
static lv_obj_t *lbl_hold;
static lv_obj_t *lbl_raw;
static lv_obj_t *bar_hold;
static lv_obj_t *btn_reset;
static lv_obj_t *lbl_reset;
static void style_btn(lv_obj_t *btn, uint32_t bg) {
lv_obj_set_style_radius(btn, 10, 0);
lv_obj_set_style_bg_color(btn, lv_color_hex(bg), 0);
lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0);
lv_obj_set_style_shadow_width(btn, 0, 0);
lv_obj_set_style_border_width(btn, 0, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
}
void ui_refresh() {
if (lbl_count) {
lv_label_set_text_fmt(lbl_count, "%lu", (unsigned long)pir_count());
}
const bool active = pir_is_active();
const uint32_t hold = pir_hold_ms();
if (lbl_state) {
lv_label_set_text(lbl_state, active ? "MOTION" : "IDLE");
lv_obj_set_style_text_color(lbl_state,
lv_color_hex(active ? COL_HOT : COL_MUTED), 0);
}
if (lbl_hold) {
if (active) {
lv_label_set_text_fmt(lbl_hold, "hold %lu ms", (unsigned long)hold);
} else {
lv_label_set_text(lbl_hold, "hold --");
}
}
if (bar_hold) {
// 条:0~5s 映射到 0~100,便于看保持时间
uint32_t pct = hold / 50;
if (pct > 100) {
pct = 100;
}
lv_bar_set_value(bar_hold, (int32_t)pct, LV_ANIM_OFF);
lv_obj_set_style_bg_color(bar_hold,
lv_color_hex(active ? COL_HOT : 0x1A2430),
LV_PART_INDICATOR);
}
if (lbl_raw) {
const int lv = pir_raw_level();
lv_label_set_text_fmt(lbl_raw, "OUT %s act=%s", lv ? "HIGH" : "LOW",
pir_active_level() ? "HIGH" : "LOW");
lv_obj_set_style_text_color(lbl_raw,
lv_color_hex(lv ? 0x3DDC97 : COL_MUTED), 0);
}
}
static void on_reset(lv_event_t *e) {
if (lv_event_get_code(e) != LV_EVENT_CLICKED) {
return;
}
pir_reset_count();
ui_refresh();
}
void ui_create() {
lv_obj_t *scr = lv_screen_active();
lv_obj_set_style_bg_color(scr, lv_color_hex(COL_BG), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lbl_title = lv_label_create(scr);
lv_label_set_text(lbl_title, "Human IR");
lv_obj_set_style_text_color(lbl_title, lv_color_hex(COL_INK), 0);
lv_obj_set_style_text_font(lbl_title, &lv_font_montserrat_28, 0);
lv_obj_align(lbl_title, LV_ALIGN_TOP_MID, 0, 2);
lbl_pin = lv_label_create(scr);
lv_label_set_text_fmt(lbl_pin, "OUT->GPIO%d VCC 3.3V", PIR_PIN);
lv_obj_set_style_text_color(lbl_pin, lv_color_hex(COL_MUTED), 0);
lv_obj_set_style_text_font(lbl_pin, &lv_font_montserrat_14, 0);
lv_obj_align(lbl_pin, LV_ALIGN_TOP_MID, 0, 32);
lbl_count = lv_label_create(scr);
lv_label_set_text(lbl_count, "0");
lv_obj_set_style_text_color(lbl_count, lv_color_hex(COL_ACCENT), 0);
lv_obj_set_style_text_font(lbl_count, &lv_font_montserrat_28, 0);
lv_obj_align(lbl_count, LV_ALIGN_TOP_MID, 0, 54);
lbl_hint = lv_label_create(scr);
lv_label_set_text(lbl_hint, "detects");
lv_obj_set_style_text_color(lbl_hint, lv_color_hex(COL_MUTED), 0);
lv_obj_set_style_text_font(lbl_hint, &lv_font_montserrat_14, 0);
lv_obj_align(lbl_hint, LV_ALIGN_TOP_MID, 0, 88);
lbl_state = lv_label_create(scr);
lv_label_set_text(lbl_state, "IDLE");
lv_obj_set_style_text_color(lbl_state, lv_color_hex(COL_MUTED), 0);
lv_obj_set_style_text_font(lbl_state, &lv_font_montserrat_20, 0);
lv_obj_align(lbl_state, LV_ALIGN_TOP_MID, 0, 108);
lbl_hold = lv_label_create(scr);
lv_label_set_text(lbl_hold, "hold --");
lv_obj_set_style_text_color(lbl_hold, lv_color_hex(COL_MUTED), 0);
lv_obj_set_style_text_font(lbl_hold, &lv_font_montserrat_14, 0);
lv_obj_align(lbl_hold, LV_ALIGN_TOP_MID, 0, 132);
bar_hold = lv_bar_create(scr);
lv_obj_set_size(bar_hold, UI_HOR_RES - 48, 10);
lv_obj_align(bar_hold, LV_ALIGN_TOP_MID, 0, 152);
lv_bar_set_range(bar_hold, 0, 100);
lv_bar_set_value(bar_hold, 0, LV_ANIM_OFF);
lv_obj_set_style_bg_color(bar_hold, lv_color_hex(0x1A2430), LV_PART_MAIN);
lv_obj_set_style_bg_opa(bar_hold, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_radius(bar_hold, 4, LV_PART_MAIN);
lv_obj_set_style_bg_color(bar_hold, lv_color_hex(COL_HOT), LV_PART_INDICATOR);
lv_obj_set_style_radius(bar_hold, 4, LV_PART_INDICATOR);
lbl_raw = lv_label_create(scr);
lv_label_set_text(lbl_raw, "OUT --");
lv_obj_set_style_text_color(lbl_raw, lv_color_hex(COL_MUTED), 0);
lv_obj_set_style_text_font(lbl_raw, &lv_font_montserrat_14, 0);
lv_obj_align(lbl_raw, LV_ALIGN_TOP_MID, 0, 168);
btn_reset = lv_button_create(scr);
lv_obj_set_size(btn_reset, UI_HOR_RES - 48, 40);
lv_obj_align(btn_reset, LV_ALIGN_TOP_MID, 0, 192);
style_btn(btn_reset, COL_BTN);
lv_obj_add_event_cb(btn_reset, on_reset, LV_EVENT_CLICKED, nullptr);
lbl_reset = lv_label_create(btn_reset);
lv_label_set_text(lbl_reset, "Reset");
lv_obj_set_style_text_color(lbl_reset, lv_color_hex(COL_INK), 0);
lv_obj_set_style_text_font(lbl_reset, &lv_font_montserrat_20, 0);
lv_obj_center(lbl_reset);
ui_refresh();
}