Add button counter test on GPIO22 with SoftSPI touch and LVGL.
Each press increments on-screen count; touch Reset clears. Flashable firmware/cyd package. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
55
src/button.cpp
Normal file
55
src/button.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "button.h"
|
||||
#include "pins.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
static uint32_t g_count = 0;
|
||||
static bool g_stable_down = false;
|
||||
static bool g_last_raw = false;
|
||||
static uint32_t g_last_change_ms = 0;
|
||||
static constexpr uint32_t DEBOUNCE_MS = 40;
|
||||
|
||||
void button_init() {
|
||||
pinMode(BTN_PIN, INPUT_PULLUP);
|
||||
g_count = 0;
|
||||
g_stable_down = false;
|
||||
g_last_raw = (digitalRead(BTN_PIN) == LOW);
|
||||
g_last_change_ms = millis();
|
||||
Serial.printf("Button GPIO%d INPUT_PULLUP (press=LOW)\n", BTN_PIN);
|
||||
}
|
||||
|
||||
bool button_poll_press() {
|
||||
const bool raw_down = (digitalRead(BTN_PIN) == LOW);
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (raw_down != g_last_raw) {
|
||||
g_last_raw = raw_down;
|
||||
g_last_change_ms = now;
|
||||
}
|
||||
|
||||
if ((now - g_last_change_ms) < DEBOUNCE_MS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (raw_down == g_stable_down) {
|
||||
return false;
|
||||
}
|
||||
|
||||
g_stable_down = raw_down;
|
||||
if (!g_stable_down) {
|
||||
return false; // 只计按下边沿
|
||||
}
|
||||
|
||||
g_count++;
|
||||
Serial.printf("Button press count=%lu\n", (unsigned long)g_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t button_count() { return g_count; }
|
||||
|
||||
void button_reset_count() {
|
||||
g_count = 0;
|
||||
Serial.println("Button count reset");
|
||||
}
|
||||
|
||||
bool button_is_down() { return g_stable_down; }
|
||||
57
src/main.cpp
Normal file
57
src/main.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <Arduino.h>
|
||||
#include <lvgl.h>
|
||||
|
||||
#include "button.h"
|
||||
#include "pins.h"
|
||||
#include "touch.h"
|
||||
#include "ui.h"
|
||||
|
||||
static constexpr uint32_t DRAW_BUF_SIZE =
|
||||
(SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8));
|
||||
static uint32_t draw_buf[DRAW_BUF_SIZE / 4];
|
||||
|
||||
void setup() {
|
||||
Serial.begin(460800);
|
||||
delay(200);
|
||||
Serial.println();
|
||||
Serial.printf("LVGL %d.%d.%d | Button sensor GPIO%d\n", lv_version_major(),
|
||||
lv_version_minor(), lv_version_patch(), BTN_PIN);
|
||||
Serial.println("BOOT_OK");
|
||||
|
||||
pinMode(TFT_BL_PIN, OUTPUT);
|
||||
digitalWrite(TFT_BL_PIN, HIGH);
|
||||
|
||||
touch_init();
|
||||
button_init();
|
||||
|
||||
lv_init();
|
||||
|
||||
lv_display_t *disp =
|
||||
lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
|
||||
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);
|
||||
|
||||
lv_indev_t *indev = lv_indev_create();
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_read_cb(indev, touch_read_cb);
|
||||
lv_indev_set_display(indev, disp);
|
||||
|
||||
ui_create();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (button_poll_press()) {
|
||||
ui_refresh_count();
|
||||
} else {
|
||||
// 按下/松开时刷新状态字,但不重复计数
|
||||
static bool last_down = false;
|
||||
const bool down = button_is_down();
|
||||
if (down != last_down) {
|
||||
last_down = down;
|
||||
ui_refresh_count();
|
||||
}
|
||||
}
|
||||
|
||||
lv_timer_handler();
|
||||
lv_tick_inc(5);
|
||||
delay(5);
|
||||
}
|
||||
125
src/touch.cpp
Normal file
125
src/touch.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "touch.h"
|
||||
#include "pins.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
/**
|
||||
* SoftSPI XPT2046
|
||||
* BL↔TR 是矩形主对角线镜像(XY 对调),不是 180°(180° 会搞成 TL↔BR)。
|
||||
* 先按 rot2 得到旧 UI,再做对角修正,反变换喂给 LVGL 270。
|
||||
*/
|
||||
|
||||
int16_t g_dbg_phys_x = -1;
|
||||
int16_t g_dbg_phys_y = -1;
|
||||
int16_t g_dbg_logic_x = -1;
|
||||
int16_t g_dbg_logic_y = -1;
|
||||
bool g_dbg_touch_down = false;
|
||||
|
||||
static void soft_write(uint8_t data) {
|
||||
for (int i = 7; i >= 0; --i) {
|
||||
digitalWrite(CYD_TOUCH_MOSI, (data >> i) & 1);
|
||||
digitalWrite(CYD_TOUCH_CLK, HIGH);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(CYD_TOUCH_CLK, LOW);
|
||||
delayMicroseconds(2);
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t soft_read12() {
|
||||
uint16_t v = 0;
|
||||
digitalWrite(CYD_TOUCH_CLK, HIGH);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(CYD_TOUCH_CLK, LOW);
|
||||
delayMicroseconds(2);
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
digitalWrite(CYD_TOUCH_CLK, HIGH);
|
||||
delayMicroseconds(2);
|
||||
v = (uint16_t)((v << 1) | (digitalRead(CYD_TOUCH_MISO) ? 1u : 0u));
|
||||
digitalWrite(CYD_TOUCH_CLK, LOW);
|
||||
delayMicroseconds(2);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static uint16_t xpt_read(uint8_t cmd) {
|
||||
digitalWrite(CYD_TOUCH_CS, LOW);
|
||||
soft_write(cmd);
|
||||
uint16_t v = soft_read12();
|
||||
digitalWrite(CYD_TOUCH_CS, HIGH);
|
||||
return v;
|
||||
}
|
||||
|
||||
static int16_t clampi(int32_t v, int32_t lo, int32_t hi) {
|
||||
if (v < lo) {
|
||||
return (int16_t)lo;
|
||||
}
|
||||
if (v > hi) {
|
||||
return (int16_t)hi;
|
||||
}
|
||||
return (int16_t)v;
|
||||
}
|
||||
|
||||
void touch_init() {
|
||||
pinMode(CYD_TOUCH_CS, OUTPUT);
|
||||
pinMode(CYD_TOUCH_CLK, OUTPUT);
|
||||
pinMode(CYD_TOUCH_MOSI, OUTPUT);
|
||||
pinMode(CYD_TOUCH_MISO, INPUT);
|
||||
pinMode(CYD_TOUCH_IRQ, INPUT);
|
||||
digitalWrite(CYD_TOUCH_CS, HIGH);
|
||||
digitalWrite(CYD_TOUCH_CLK, LOW);
|
||||
Serial.println("Touch: SoftSPI + diagonal fix (BL/TR)");
|
||||
}
|
||||
|
||||
void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
|
||||
uint16_t adc_x = xpt_read(0x90);
|
||||
uint16_t adc_y = xpt_read(0xD0);
|
||||
adc_x = (uint16_t)((adc_x + xpt_read(0x90)) / 2);
|
||||
adc_y = (uint16_t)((adc_y + xpt_read(0xD0)) / 2);
|
||||
|
||||
const bool irq = (digitalRead(CYD_TOUCH_IRQ) == LOW);
|
||||
const bool in_range =
|
||||
(adc_x > 80 && adc_x < 4000 && adc_y > 80 && adc_y < 4000);
|
||||
|
||||
if (!in_range || (!irq && (adc_x < 200 || adc_y < 200))) {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
g_dbg_touch_down = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t p_x = (int16_t)adc_x;
|
||||
const int16_t p_y = (int16_t)(4095 - adc_y);
|
||||
|
||||
int16_t x = map(p_x, 200, 3700, 1, SCREEN_WIDTH);
|
||||
int16_t y = map(p_y, 240, 3800, 1, SCREEN_HEIGHT);
|
||||
x = clampi(x, 0, SCREEN_WIDTH - 1);
|
||||
y = clampi(y, 0, SCREEN_HEIGHT - 1);
|
||||
|
||||
// 旧 UI(LVGL270):(y, 239-x) — 该空间里 BL↔TR,做主对角线镜像
|
||||
// (ux,uy) -> (uy*(319/239), ux*(239/319))
|
||||
const int32_t old_ux = y;
|
||||
const int32_t old_uy = (SCREEN_WIDTH - 1) - x;
|
||||
int16_t ui_x = clampi((old_uy * (UI_HOR_RES - 1)) / (UI_VER_RES - 1), 0,
|
||||
UI_HOR_RES - 1);
|
||||
int16_t ui_y = clampi((old_ux * (UI_VER_RES - 1)) / (UI_HOR_RES - 1), 0,
|
||||
UI_VER_RES - 1);
|
||||
|
||||
// 反变换喂 LVGL,使最终落在 (ui_x, ui_y)
|
||||
const int16_t feed_x = (int16_t)((SCREEN_WIDTH - 1) - ui_y);
|
||||
const int16_t feed_y = ui_x;
|
||||
|
||||
g_dbg_phys_x = x;
|
||||
g_dbg_phys_y = y;
|
||||
g_dbg_logic_x = ui_x;
|
||||
g_dbg_logic_y = ui_y;
|
||||
g_dbg_touch_down = true;
|
||||
|
||||
static uint32_t last_log = 0;
|
||||
if (millis() - last_log > 80) {
|
||||
last_log = millis();
|
||||
Serial.printf("Touch UI(%d,%d)\n", ui_x, ui_y);
|
||||
}
|
||||
|
||||
data->point.x = feed_x;
|
||||
data->point.y = feed_y;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
}
|
||||
99
src/ui.cpp
Normal file
99
src/ui.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "ui.h"
|
||||
#include "button.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 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 *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_count() {
|
||||
if (lbl_count) {
|
||||
lv_label_set_text_fmt(lbl_count, "%lu", (unsigned long)button_count());
|
||||
}
|
||||
if (lbl_state) {
|
||||
const bool down = button_is_down();
|
||||
lv_label_set_text(lbl_state, down ? "PRESSED" : "IDLE");
|
||||
lv_obj_set_style_text_color(lbl_state,
|
||||
lv_color_hex(down ? 0x3DDC97 : COL_MUTED), 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_reset(lv_event_t *e) {
|
||||
if (lv_event_get_code(e) != LV_EVENT_CLICKED) {
|
||||
return;
|
||||
}
|
||||
button_reset_count();
|
||||
ui_refresh_count();
|
||||
}
|
||||
|
||||
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, "Button");
|
||||
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, 6);
|
||||
|
||||
lbl_pin = lv_label_create(scr);
|
||||
lv_label_set_text_fmt(lbl_pin, "SIG -> GPIO%d (press +1)", BTN_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, 40);
|
||||
|
||||
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, 78);
|
||||
|
||||
lbl_hint = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_hint, "count");
|
||||
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, 116);
|
||||
|
||||
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, 142);
|
||||
|
||||
btn_reset = lv_button_create(scr);
|
||||
lv_obj_set_size(btn_reset, UI_HOR_RES - 48, 48);
|
||||
lv_obj_align(btn_reset, LV_ALIGN_TOP_MID, 0, 180);
|
||||
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_count();
|
||||
}
|
||||
Reference in New Issue
Block a user