cryptnox-pos 1.0.0
Standalone USDC payment terminal firmware (ESP32 + Cryptnox smart card)
Loading...
Searching...
No Matches
ui_hal.cpp
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: LGPL-3.0-or-later
3 * Copyright (c) 2026 Cryptnox SA
4 */
5
11
12#include "ui_internal.h"
13
14TFT_eSPI tft;
15SPIClass touchSPI(VSPI);
16XPT2046_Touchscreen touch(T_CS, T_IRQ);
17/* Partial draw buffer — 40 lines (no PSRAM on the CYD, keep it small). */
18lv_color_t s_buf[SCR_W * 40];
19lv_disp_draw_buf_t s_draw_buf;
20lv_disp_drv_t s_disp_drv;
21lv_indev_drv_t s_indev_drv;
22void disp_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *px) {
23 uint32_t w = (area->x2 - area->x1 + 1);
24 uint32_t h = (area->y2 - area->y1 + 1);
25
26 tft.startWrite();
27 tft.setAddrWindow(area->x1, area->y1, w, h);
28 /* swap=true converts LVGL's native 16-bit order to the panel's. If colours
29 * come out byte-swapped/garbled, flip this bool or set CONFIG_LV_COLOR_16_SWAP. */
30 tft.pushColors(reinterpret_cast<uint16_t *>(&px->full), w * h, true);
31 tft.endWrite();
32
33 lv_disp_flush_ready(drv);
34}
35
36/* Raw XPT2046 counts at the panel's edges, cached from NVS (indev_read runs
37 * every few ms, no place to open flash). The 200/3800 defaults are close enough
38 * on most CYDs to reach the calibration screen on an uncalibrated panel. */
39uint16_t s_cal_xmin = 200U;
40uint16_t s_cal_xmax = 3800U;
41uint16_t s_cal_ymin = 200U;
42uint16_t s_cal_ymax = 3800U;
46
47/* Uncalibrated sample, for the calibration screen itself — the only caller that
48 * must not go through the mapping it is measuring. */
49bool touch_raw(int16_t *rx, int16_t *ry) {
50 if (!touch.tirqTouched() || !touch.touched()) { return false; }
51 TS_Point p = touch.getPoint();
52 *rx = p.x;
53 *ry = p.y;
54 return true;
55}
56
57/* @p sz is the raw pressure, for the second-contact guard — see
58 * touch_jump_filter(). Higher means a harder press or one more finger. */
59static bool touch_to_screen(int16_t *sx, int16_t *sy, int16_t *sz) {
60 if (!touch.tirqTouched() || !touch.touched()) {
61 return false;
62 }
63 TS_Point p = touch.getPoint();
64 *sz = (int16_t)p.z;
65 int16_t mx = map(p.x, s_cal_xmin, s_cal_xmax, 0, SCR_W);
66 int16_t my = map(p.y, s_cal_ymin, s_cal_ymax, 0, SCR_H);
67 if (mx < 0) { mx = 0; }
68 if (mx >= SCR_W) { mx = SCR_W - 1; }
69 if (my < 0) { my = 0; }
70 if (my >= SCR_H) { my = SCR_H - 1; }
71 *sx = mx;
72 *sy = my;
73 return true;
74}
75
76/* Swallow taps carried over from the screen we just left: a press is ignored
77 * until this tick (ms) AND until the finger has been released at least once.
78 * Set on every screen (re)build in render_requested_screen(). */
80bool s_wait_release = false;
81
82/* The white card the sale-flow screens draw into — see build_page(). NULL on
83 * every other screen, which is how the driver below tells the two apart. */
84lv_obj_t *s_page_card = NULL;
85
86/* Second-contact guard — see touch_jump_filter(). Sale screens only: every
87 * control there is a tap, so nothing legitimately jumps, while the admin panel's
88 * sliders and scrolling lists cross far more than 25px in a 30ms read period. */
89static touch_jump_t s_jump = { 0, 0, 0, false };
90static bool s_jump_logged = false; /* one log line per press */
91
92/* Swipe up from the bottom edge — the admin panel's door. Detected in the
93 * driver, not as an LVGL gesture: the screen is covered by a keypad and a
94 * Charge button, and a gesture delivered to a button never reaches the screen.
95 *
96 * The travel is deliberately longer than a fat-finger slip on the Charge button
97 * directly above the handle — that button commits a sale, so the two must not be
98 * confusable in either direction. */
99#define SWIPE_BAND_H 44 /* press must START in this bottom band */
100#define SWIPE_MIN_DY 58 /* ...and travel at least this far up */
101static bool s_swipe_armed = false;
102static int16_t s_swipe_y0 = 0;
103volatile bool s_swipe_admin = false; /* handed to the UI task loop */
104void indev_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
105 (void)drv;
106 int16_t x, y, z = 0;
107 bool pressed = touch_to_screen(&x, &y, &z);
108
109 /* Two fingers read as one point between them — hold the first one's. Ahead
110 * of the swipe so an artifact cannot arm it, and kept on for an armed press.
111 * The swipe's travel uses the raw point: that drag is the one big move on a
112 * sale screen, and a false swipe only opens a PIN-locked screen. */
113 const int16_t raw_x = x, raw_y = y;
114 if (!pressed || (s_page_card != NULL)) {
115 touch_jump_filter(&s_jump, pressed, z, &x, &y);
116 }
117
118 /* The numbers TOUCH_Z_STEP_PCT is tuned from. Once per press, at INFO
119 * (CONFIG_LOG_MAXIMUM_LEVEL=3 compiles ESP_LOGD/V away). Never fires with a
120 * thumb down: lower the percentage. Fires on one-finger taps: raise it. */
121 if (!pressed) {
122 s_jump_logged = false;
123 } else if (!s_jump_logged && ((x != raw_x) || (y != raw_y))) {
124 s_jump_logged = true;
125 ESP_LOGI(TAG, "second contact: %d,%d held at %d,%d (z=%d, z_min=%d)",
126 (int)raw_x, (int)raw_y, (int)x, (int)y, (int)z,
127 (int)s_jump.z_min);
128 }
129
130 /* Bottom-edge swipe up. Armed on a press that starts in the band, fired
131 * once it has travelled far enough; the rest of the drag is swallowed via
132 * s_wait_release so the button under the finger never sees a click. The UI
133 * task decides which screen it is allowed on. */
134 if (pressed) {
135 if (!s_swipe_armed && (y >= (SCR_H - SWIPE_BAND_H))) {
136 s_swipe_armed = true;
137 s_swipe_y0 = y;
138 } else if (s_swipe_armed && ((s_swipe_y0 - raw_y) >= SWIPE_MIN_DY)) {
139 s_swipe_armed = false;
140 s_swipe_admin = true;
141 s_wait_release = true;
142 data->state = LV_INDEV_STATE_REL;
143 return;
144 }
145 } else {
146 s_swipe_armed = false;
147 }
148
149 /* After a screen change, ignore a lingering or reflexive tap from the old
150 * screen (e.g. cancelling the tx right after validating the PIN). */
151 if (lv_tick_get() < s_input_block_until || s_wait_release) {
152 if (!pressed) { s_wait_release = false; } /* finger lifted — re-arm */
153 data->state = LV_INDEV_STATE_REL;
154 return;
155 }
156
157 if (pressed) {
158 data->state = LV_INDEV_STATE_PR;
159 data->point.x = x;
160 data->point.y = y;
161 } else {
162 data->state = LV_INDEV_STATE_REL;
163 }
164}
165void tick_cb(void *arg) {
166 (void)arg;
167 lv_tick_inc(LV_TICK_PERIOD_MS);
168}
169
170/******************************************************************
171 * 4b. Backlight — LEDC PWM dimming on the CYD's GPIO 21
172 ******************************************************************/
173#define BL_GPIO 21
174#define BL_LEDC_MODE LEDC_LOW_SPEED_MODE
175#define BL_LEDC_TIMER LEDC_TIMER_0
176#define BL_LEDC_CH LEDC_CHANNEL_0
177#define BL_LEDC_RES LEDC_TIMER_10_BIT /* duty 0..1023 */
178#define BL_PWM_HZ 5000 /* above the audible range */
179uint8_t s_brightness = 80; /* backlight %, restored from NVS */
180void backlight_set_pct(uint8_t pct) {
181 if (pct > 100U) { pct = 100U; }
182 uint32_t duty = (1023U * pct) / 100U;
183 (void)ledc_set_duty(BL_LEDC_MODE, BL_LEDC_CH, duty);
184 (void)ledc_update_duty(BL_LEDC_MODE, BL_LEDC_CH);
185}
186void backlight_init(uint8_t pct) {
187 ledc_timer_config_t t = {};
188 t.speed_mode = BL_LEDC_MODE;
189 t.duty_resolution = BL_LEDC_RES;
190 t.timer_num = BL_LEDC_TIMER;
191 t.freq_hz = BL_PWM_HZ;
192 t.clk_cfg = LEDC_AUTO_CLK;
193 (void)ledc_timer_config(&t);
194
195 ledc_channel_config_t c = {};
196 c.gpio_num = BL_GPIO;
197 c.speed_mode = BL_LEDC_MODE;
198 c.channel = BL_LEDC_CH;
199 c.timer_sel = BL_LEDC_TIMER;
200 c.duty = 0;
201 c.hpoint = 0;
202 (void)ledc_channel_config(&c);
203
205}
static const char *const TAG
Definition eth_rpc.cpp:33
void settings_get_touch_cal(uint16_t *x_min, uint16_t *x_max, uint16_t *y_min, uint16_t *y_max)
Raw XPT2046 range that maps to the panel's four edges.
Definition settings.cpp:260
static void touch_jump_filter(touch_jump_t *st, bool pressed, int16_t z, int16_t *x, int16_t *y)
Definition touch_cal.h:172
#define BL_GPIO
Definition ui_hal.cpp:173
#define BL_LEDC_TIMER
Definition ui_hal.cpp:175
volatile bool s_swipe_admin
Definition ui_hal.cpp:103
#define SWIPE_MIN_DY
Definition ui_hal.cpp:100
#define BL_LEDC_MODE
Definition ui_hal.cpp:174
lv_indev_drv_t s_indev_drv
Definition ui_hal.cpp:21
static bool touch_to_screen(int16_t *sx, int16_t *sy, int16_t *sz)
Definition ui_hal.cpp:59
bool touch_raw(int16_t *rx, int16_t *ry)
Definition ui_hal.cpp:49
lv_obj_t * s_page_card
Definition ui_hal.cpp:84
#define SWIPE_BAND_H
Definition ui_hal.cpp:99
static int16_t s_swipe_y0
Definition ui_hal.cpp:102
void tick_cb(void *arg)
Definition ui_hal.cpp:165
#define BL_LEDC_CH
Definition ui_hal.cpp:176
static bool s_swipe_armed
Definition ui_hal.cpp:101
bool s_wait_release
Definition ui_hal.cpp:80
void backlight_set_pct(uint8_t pct)
Definition ui_hal.cpp:180
void touch_cal_load(void)
Definition ui_hal.cpp:43
uint16_t s_cal_ymax
Definition ui_hal.cpp:42
lv_disp_draw_buf_t s_draw_buf
Definition ui_hal.cpp:19
static bool s_jump_logged
Definition ui_hal.cpp:90
static touch_jump_t s_jump
Definition ui_hal.cpp:89
void indev_read(lv_indev_drv_t *drv, lv_indev_data_t *data)
Definition ui_hal.cpp:104
uint8_t s_brightness
Definition ui_hal.cpp:179
TFT_eSPI tft
Definition ui_hal.cpp:14
lv_disp_drv_t s_disp_drv
Definition ui_hal.cpp:20
uint32_t s_input_block_until
Definition ui_hal.cpp:79
uint16_t s_cal_xmin
Definition ui_hal.cpp:39
uint16_t s_cal_ymin
Definition ui_hal.cpp:41
lv_color_t s_buf[SCR_W *40]
Definition ui_hal.cpp:18
uint16_t s_cal_xmax
Definition ui_hal.cpp:40
void disp_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *px)
Definition ui_hal.cpp:22
#define BL_LEDC_RES
Definition ui_hal.cpp:177
void backlight_init(uint8_t pct)
Definition ui_hal.cpp:186
#define BL_PWM_HZ
Definition ui_hal.cpp:178
Private to the ui*.cpp files: the state and helpers they share.
#define T_CS
#define LV_TICK_PERIOD_MS
XPT2046_Touchscreen touch
#define T_IRQ
#define SCR_H
#define SCR_W
SPIClass touchSPI