upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/components/axs15231b
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-20 02:14:45 +0530
committerYour Name <you@example.com>2026-05-20 02:14:45 +0530
commit899016795c389151e6b486ec470653f5688e5c5f (patch)
tree64b4c732d6f585552fd14df5f47890764c66ac1a /components/axs15231b
parent4f713120dbedd37a3512c2ec1af0766025a59d57 (diff)
feat: merge display-fix — touch driver, keyboard, WiFi setup UI
Squash-merge of feature/display-fix (27 commits): - AXS15231B touch driver with coordinate parsing (touch.c/h) - On-screen keyboard with layout/hit detection (keyboard.c/h) - WiFi setup state machine + config_add_wifi (wifi_setup.c/h) - Web-based WiFi setup via captive portal - Display rotation fix (stride=480), color fixes, DMA byte-swap - WiFi QR code on BOOT and ERROR screens - ERROR state transition after WiFi retries exhausted - Unit tests: test_touch, test_keyboard, test_wifi_setup - Integration test: wifi_setup.mjs - E2E test: wifi-setup.spec.mjs - Per-board hardware locks for flash targets Conflicts resolved: took master for config/cvm/api/main (more current), took display-fix for display/axs15231b (newer fixes).
Diffstat (limited to 'components/axs15231b')
-rw-r--r--components/axs15231b/axs15231b.c237
-rw-r--r--components/axs15231b/include/axs15231b.h5
2 files changed, 159 insertions, 83 deletions
diff --git a/components/axs15231b/axs15231b.c b/components/axs15231b/axs15231b.c
index dd7145a..00e8467 100644
--- a/components/axs15231b/axs15231b.c
+++ b/components/axs15231b/axs15231b.c
@@ -29,10 +29,17 @@ static const char *TAG = "axs15231b";
29#define MADCTL_MV 0x20 29#define MADCTL_MV 0x20
30#define MADCTL_RGB 0x00 30#define MADCTL_RGB 0x00
31 31
32#define QSPI_CMD_REG_WRITE 0x02
33#define QSPI_CMD_DATA_WRITE 0x32
34#define QSPI_DATA_ADDR 0x003C00
35
32static spi_device_handle_t s_spi = NULL; 36static spi_device_handle_t s_spi = NULL;
33static uint16_t *s_fb = NULL; 37static uint16_t *s_fb = NULL;
34static int s_width = AXS15231B_WIDTH; 38static int s_width = AXS15231B_WIDTH;
35static int s_height = AXS15231B_HEIGHT; 39static int s_height = AXS15231B_HEIGHT;
40static int s_stride = AXS15231B_WIDTH;
41static uint8_t *s_swap_buf = NULL;
42#define SWAP_BUF_PIXELS 2048
36 43
37typedef struct { 44typedef struct {
38 uint8_t cmd; 45 uint8_t cmd;
@@ -41,28 +48,92 @@ typedef struct {
41 uint16_t delay_ms; 48 uint16_t delay_ms;
42} init_cmd_t; 49} init_cmd_t;
43 50
44static esp_err_t send_cmd(uint8_t cmd) { 51static inline void cs_low(void) {
45 spi_transaction_t t = {0}; 52 gpio_set_level(AXS15231B_PIN_CS, 0);
46 t.length = 8; 53}
47 t.tx_data[0] = cmd; 54
48 t.flags = SPI_TRANS_USE_TXDATA; 55static inline void cs_high(void) {
49 return spi_device_polling_transmit(s_spi, &t); 56 gpio_set_level(AXS15231B_PIN_CS, 1);
57}
58
59static void cs_init(void) {
60 gpio_config_t cfg = {
61 .pin_bit_mask = (1ULL << AXS15231B_PIN_CS),
62 .mode = GPIO_MODE_OUTPUT,
63 .pull_up_en = GPIO_PULLUP_DISABLE,
64 .pull_down_en = GPIO_PULLDOWN_DISABLE,
65 .intr_type = GPIO_INTR_DISABLE,
66 };
67 gpio_config(&cfg);
68 gpio_set_level(AXS15231B_PIN_CS, 1);
69}
70
71static void qspi_write_command(uint8_t lcd_cmd) {
72 spi_transaction_ext_t t = {0};
73 t.base.flags = SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR;
74 t.base.cmd = QSPI_CMD_REG_WRITE;
75 t.base.addr = ((uint32_t)lcd_cmd) << 8;
76 t.base.tx_buffer = NULL;
77 t.base.length = 0;
78 cs_low();
79 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
80 cs_high();
81}
82
83static void qspi_write_cmd_data8(uint8_t lcd_cmd, uint8_t d) {
84 spi_transaction_ext_t t = {0};
85 t.base.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR;
86 t.base.cmd = QSPI_CMD_REG_WRITE;
87 t.base.addr = ((uint32_t)lcd_cmd) << 8;
88 t.base.tx_data[0] = d;
89 t.base.length = 8;
90 cs_low();
91 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
92 cs_high();
93}
94
95static void qspi_write_cmd_data16(uint8_t lcd_cmd, uint16_t d) {
96 spi_transaction_ext_t t = {0};
97 t.base.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR;
98 t.base.cmd = QSPI_CMD_REG_WRITE;
99 t.base.addr = ((uint32_t)lcd_cmd) << 8;
100 t.base.tx_data[0] = d >> 8;
101 t.base.tx_data[1] = d & 0xFF;
102 t.base.length = 16;
103 cs_low();
104 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
105 cs_high();
50} 106}
51 107
52static esp_err_t send_data(const uint8_t *data, int len) { 108static void qspi_write_cmd_bytes(uint8_t lcd_cmd, const uint8_t *data, int len) {
53 if (len == 0) return ESP_OK; 109 if (len == 0) {
54 spi_transaction_t t = {0}; 110 qspi_write_command(lcd_cmd);
55 t.length = len * 8; 111 return;
56 t.tx_buffer = data; 112 }
57 t.flags = 0; 113 spi_transaction_ext_t t = {0};
58 return spi_device_polling_transmit(s_spi, &t); 114 t.base.flags = SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR;
115 t.base.cmd = QSPI_CMD_REG_WRITE;
116 t.base.addr = ((uint32_t)lcd_cmd) << 8;
117 t.base.tx_buffer = data;
118 t.base.length = len * 8;
119 cs_low();
120 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
121 cs_high();
59} 122}
60 123
61static esp_err_t send_cmd_data(uint8_t cmd, const uint8_t *data, int len) { 124static void qspi_write_cmd_d16d16(uint8_t lcd_cmd, uint16_t d1, uint16_t d2) {
62 esp_err_t ret = send_cmd(cmd); 125 spi_transaction_ext_t t = {0};
63 if (ret != ESP_OK) return ret; 126 t.base.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR;
64 if (len > 0) ret = send_data(data, len); 127 t.base.cmd = QSPI_CMD_REG_WRITE;
65 return ret; 128 t.base.addr = ((uint32_t)lcd_cmd) << 8;
129 t.base.tx_data[0] = d1 >> 8;
130 t.base.tx_data[1] = d1 & 0xFF;
131 t.base.tx_data[2] = d2 >> 8;
132 t.base.tx_data[3] = d2 & 0xFF;
133 t.base.length = 32;
134 cs_low();
135 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
136 cs_high();
66} 137}
67 138
68static const uint8_t init_bb[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x5A,0xA5}; 139static const uint8_t init_bb[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x5A,0xA5};
@@ -136,20 +207,23 @@ esp_err_t axs15231b_init(void) {
136 esp_err_t ret; 207 esp_err_t ret;
137 208
138 spi_bus_config_t buscfg = { 209 spi_bus_config_t buscfg = {
139 .mosi_io_num = AXS15231B_PIN_D0, 210 .data0_io_num = AXS15231B_PIN_D0,
211 .data1_io_num = AXS15231B_PIN_D1,
140 .sclk_io_num = AXS15231B_PIN_CLK, 212 .sclk_io_num = AXS15231B_PIN_CLK,
141 .miso_io_num = -1, 213 .data2_io_num = AXS15231B_PIN_D2,
142 .quadwp_io_num = -1, 214 .data3_io_num = AXS15231B_PIN_D3,
143 .quadhd_io_num = -1,
144 .max_transfer_sz = 32768, 215 .max_transfer_sz = 32768,
145 }; 216 };
146 217
147 spi_device_interface_config_t devcfg = { 218 spi_device_interface_config_t devcfg = {
219 .command_bits = 8,
220 .address_bits = 24,
221 .dummy_bits = 0,
148 .clock_speed_hz = 40 * 1000 * 1000, 222 .clock_speed_hz = 40 * 1000 * 1000,
149 .mode = 0, 223 .mode = 0,
150 .spics_io_num = AXS15231B_PIN_CS, 224 .spics_io_num = -1,
151 .queue_size = 7, 225 .queue_size = 7,
152 .flags = 0, 226 .flags = SPI_DEVICE_HALFDUPLEX,
153 }; 227 };
154 228
155 ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); 229 ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
@@ -164,6 +238,10 @@ esp_err_t axs15231b_init(void) {
164 return ret; 238 return ret;
165 } 239 }
166 240
241 spi_device_acquire_bus(s_spi, portMAX_DELAY);
242
243 cs_init();
244
167 size_t fb_size = (size_t)s_width * s_height * 2; 245 size_t fb_size = (size_t)s_width * s_height * 2;
168 s_fb = heap_caps_malloc(fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); 246 s_fb = heap_caps_malloc(fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
169 if (!s_fb) { 247 if (!s_fb) {
@@ -173,6 +251,13 @@ esp_err_t axs15231b_init(void) {
173 memset(s_fb, 0, fb_size); 251 memset(s_fb, 0, fb_size);
174 ESP_LOGI(TAG, "Framebuffer allocated: %zu bytes in PSRAM", fb_size); 252 ESP_LOGI(TAG, "Framebuffer allocated: %zu bytes in PSRAM", fb_size);
175 253
254 s_swap_buf = heap_caps_aligned_alloc(16, SWAP_BUF_PIXELS * 2, MALLOC_CAP_DMA);
255 if (!s_swap_buf) {
256 ESP_LOGE(TAG, "Failed to allocate DMA swap buffer (%d bytes)", SWAP_BUF_PIXELS * 2);
257 return ESP_ERR_NO_MEM;
258 }
259 ESP_LOGI(TAG, "DMA swap buffer: %d bytes in internal RAM", SWAP_BUF_PIXELS * 2);
260
176 gpio_config_t bl_cfg = { 261 gpio_config_t bl_cfg = {
177 .pin_bit_mask = (1ULL << AXS15231B_PIN_BL), 262 .pin_bit_mask = (1ULL << AXS15231B_PIN_BL),
178 .mode = GPIO_MODE_OUTPUT, 263 .mode = GPIO_MODE_OUTPUT,
@@ -182,40 +267,28 @@ esp_err_t axs15231b_init(void) {
182 }; 267 };
183 gpio_config(&bl_cfg); 268 gpio_config(&bl_cfg);
184 269
185 send_cmd(SWRESET); 270 qspi_write_command(SWRESET);
186 vTaskDelay(pdMS_TO_TICKS(200)); 271 vTaskDelay(pdMS_TO_TICKS(200));
187 272
188 for (int i = 0; i < INIT_CMD_COUNT; i++) { 273 for (int i = 0; i < INIT_CMD_COUNT; i++) {
189 ret = send_cmd_data(s_init_cmds[i].cmd, s_init_cmds[i].data, s_init_cmds[i].data_len); 274 qspi_write_cmd_bytes(s_init_cmds[i].cmd, s_init_cmds[i].data, s_init_cmds[i].data_len);
190 if (ret != ESP_OK) {
191 ESP_LOGE(TAG, "Init cmd 0x%02X failed: %s", s_init_cmds[i].cmd, esp_err_to_name(ret));
192 return ret;
193 }
194 if (s_init_cmds[i].delay_ms > 0) { 275 if (s_init_cmds[i].delay_ms > 0) {
195 vTaskDelay(pdMS_TO_TICKS(s_init_cmds[i].delay_ms)); 276 vTaskDelay(pdMS_TO_TICKS(s_init_cmds[i].delay_ms));
196 } 277 }
197 } 278 }
198 279
199 uint8_t madctl_val = MADCTL_MX | MADCTL_MV | MADCTL_RGB; 280 uint8_t madctl_val = MADCTL_RGB;
200 ret = send_cmd_data(MADCTL, &madctl_val, 1); 281 qspi_write_cmd_data8(MADCTL, madctl_val);
201 if (ret != ESP_OK) {
202 ESP_LOGE(TAG, "Failed to set rotation: %s", esp_err_to_name(ret));
203 return ret;
204 }
205 282
206 uint8_t colmod_val = 0x55; 283 uint8_t colmod_val = 0x55;
207 ret = send_cmd_data(COLMOD, &colmod_val, 1); 284 qspi_write_cmd_data8(COLMOD, colmod_val);
208 if (ret != ESP_OK) {
209 ESP_LOGE(TAG, "Failed to set pixel format: %s", esp_err_to_name(ret));
210 return ret;
211 }
212 285
213 axs15231b_fill_screen(0x0000); 286 axs15231b_fill_screen(0x0000);
214 axs15231b_flush(); 287 axs15231b_flush();
215 288
216 axs15231b_set_backlight(true); 289 axs15231b_set_backlight(true);
217 290
218 ESP_LOGI(TAG, "AXS15231B initialized: %dx%d landscape", s_width, s_height); 291 ESP_LOGI(TAG, "AXS15231B initialized: %dx%d portrait", s_width, s_height);
219 return ESP_OK; 292 return ESP_OK;
220} 293}
221 294
@@ -224,9 +297,10 @@ void axs15231b_set_backlight(bool on) {
224} 297}
225 298
226void axs15231b_fill_screen(uint16_t color) { 299void axs15231b_fill_screen(uint16_t color) {
227 uint32_t pixels = (uint32_t)s_width * s_height; 300 for (int row = 0; row < s_height; row++) {
228 for (uint32_t i = 0; i < pixels; i++) { 301 for (int col = 0; col < s_width; col++) {
229 s_fb[i] = color; 302 s_fb[row * s_stride + col] = color;
303 }
230 } 304 }
231} 305}
232 306
@@ -234,48 +308,51 @@ void axs15231b_fill_rect(int x, int y, int w, int h, uint16_t color) {
234 if (x < 0 || y < 0 || x + w > s_width || y + h > s_height) return; 308 if (x < 0 || y < 0 || x + w > s_width || y + h > s_height) return;
235 for (int row = y; row < y + h; row++) { 309 for (int row = y; row < y + h; row++) {
236 for (int col = x; col < x + w; col++) { 310 for (int col = x; col < x + w; col++) {
237 s_fb[row * s_width + col] = color; 311 s_fb[row * s_stride + col] = color;
238 } 312 }
239 } 313 }
240} 314}
241 315
242void axs15231b_flush(void) { 316void axs15231b_flush(void) {
243 if (!s_spi || !s_fb) return; 317 if (!s_spi || !s_fb || !s_swap_buf) return;
244 318
245 uint8_t buf[4]; 319 qspi_write_cmd_d16d16(CASET, 0, s_width - 1);
246 buf[0] = 0; 320 qspi_write_cmd_d16d16(RASET, 0, s_height - 1);
247 buf[1] = 0; 321 qspi_write_command(RAMWR);
248 buf[2] = (s_width - 1) >> 8; 322
249 buf[3] = (s_width - 1) & 0xFF; 323 bool first = true;
250 send_cmd_data(CASET, buf, 4); 324 cs_low();
251 325 for (int row = 0; row < s_height; row++) {
252 buf[0] = 0; 326 int chunk_remaining = s_width;
253 buf[1] = 0; 327 int col_offset = 0;
254 buf[2] = (s_height - 1) >> 8; 328 while (chunk_remaining > 0) {
255 buf[3] = (s_height - 1) & 0xFF; 329 int chunk_pixels = chunk_remaining < SWAP_BUF_PIXELS ? chunk_remaining : SWAP_BUF_PIXELS;
256 send_cmd_data(RASET, buf, 4); 330 int chunk_bytes = chunk_pixels * 2;
257 331
258 send_cmd(RAMWR); 332 uint8_t *src = (uint8_t *)(s_fb + row * s_stride + col_offset);
259 333 for (int i = 0; i < chunk_bytes; i += 2) {
260 int total_bytes = s_width * s_height * 2; 334 s_swap_buf[i] = src[i + 1];
261 int chunk_size = 32768; 335 s_swap_buf[i + 1] = src[i];
262 int offset = 0; 336 }
263 uint8_t *fb_bytes = (uint8_t *)s_fb; 337
264 338 spi_transaction_ext_t t = {0};
265 while (offset < total_bytes) { 339 if (first) {
266 int remaining = total_bytes - offset; 340 t.base.flags = SPI_TRANS_MODE_QIO;
267 int this_chunk = remaining < chunk_size ? remaining : chunk_size; 341 t.base.cmd = QSPI_CMD_DATA_WRITE;
268 342 t.base.addr = QSPI_DATA_ADDR;
269 spi_transaction_t t = {0}; 343 first = false;
270 t.length = this_chunk * 8; 344 } else {
271 t.tx_buffer = fb_bytes + offset; 345 t.base.flags = SPI_TRANS_MODE_QIO | SPI_TRANS_VARIABLE_CMD |
272 esp_err_t ret = spi_device_polling_transmit(s_spi, &t); 346 SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY;
273 if (ret != ESP_OK) { 347 }
274 ESP_LOGE(TAG, "Flush transfer failed at offset %d: %s", offset, esp_err_to_name(ret)); 348 t.base.tx_buffer = s_swap_buf;
275 return; 349 t.base.length = chunk_pixels * 16;
350 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
351 col_offset += chunk_pixels;
352 chunk_remaining -= chunk_pixels;
276 } 353 }
277 offset += this_chunk;
278 } 354 }
355 cs_high();
279} 356}
280 357
281int axs15231b_get_width(void) { return s_width; } 358int axs15231b_get_width(void) { return s_width; }
diff --git a/components/axs15231b/include/axs15231b.h b/components/axs15231b/include/axs15231b.h
index 5ec017c..32c489f 100644
--- a/components/axs15231b/include/axs15231b.h
+++ b/components/axs15231b/include/axs15231b.h
@@ -5,8 +5,8 @@
5#include <stdint.h> 5#include <stdint.h>
6#include <stdbool.h> 6#include <stdbool.h>
7 7
8#define AXS15231B_WIDTH 480 8#define AXS15231B_WIDTH 320
9#define AXS15231B_HEIGHT 320 9#define AXS15231B_HEIGHT 480
10 10
11#define AXS15231B_PIN_CS 45 11#define AXS15231B_PIN_CS 45
12#define AXS15231B_PIN_CLK 47 12#define AXS15231B_PIN_CLK 47
@@ -23,5 +23,4 @@ void axs15231b_fill_rect(int x, int y, int w, int h, uint16_t color);
23void axs15231b_flush(void); 23void axs15231b_flush(void);
24int axs15231b_get_width(void); 24int axs15231b_get_width(void);
25int axs15231b_get_height(void); 25int axs15231b_get_height(void);
26
27#endif 26#endif