upleb.uk

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

summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-18 18:34:33 +0530
committerYour Name <you@example.com>2026-05-18 18:34:33 +0530
commit067a5700e26518d9a3f8bd92966c8957d17f0817 (patch)
tree1379c544731f0edd9c52016c117221085713887d /components
parent8f00db9d57e9e06df7fbf3e7285ae9256d21d716 (diff)
Document QSPI protocol findings and display fix plan
- DISPLAY_FIX_PLAN.md: root cause analysis, byte-order mismatch, PSRAM cache coherency issue, reference implementations studied - axs15231b.c: QSPI protocol rewrite with correct ArduinoGFX framing (cmd=0x02 for regs, cmd=0x32/addr=0x003C00 for pixels) - display.c: portrait centering for 320x480, render-on-change logic - Makefile: Board C support (flash-c, lock-c) - axs15231b.h: QSPI command/address constants Display shows recognizable text, colors wrong due to byte-swap
Diffstat (limited to 'components')
-rw-r--r--components/axs15231b/axs15231b.c174
-rw-r--r--components/axs15231b/include/axs15231b.h4
2 files changed, 119 insertions, 59 deletions
diff --git a/components/axs15231b/axs15231b.c b/components/axs15231b/axs15231b.c
index 50be305..d64c9bc 100644
--- a/components/axs15231b/axs15231b.c
+++ b/components/axs15231b/axs15231b.c
@@ -29,6 +29,10 @@ 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;
@@ -41,28 +45,92 @@ typedef struct {
41 uint16_t delay_ms; 45 uint16_t delay_ms;
42} init_cmd_t; 46} init_cmd_t;
43 47
44static esp_err_t send_cmd(uint8_t cmd) { 48static inline void cs_low(void) {
45 spi_transaction_t t = {0}; 49 gpio_set_level(AXS15231B_PIN_CS, 0);
46 t.length = 8; 50}
47 t.tx_data[0] = cmd; 51
48 t.flags = SPI_TRANS_USE_TXDATA; 52static inline void cs_high(void) {
49 return spi_device_polling_transmit(s_spi, &t); 53 gpio_set_level(AXS15231B_PIN_CS, 1);
54}
55
56static void cs_init(void) {
57 gpio_config_t cfg = {
58 .pin_bit_mask = (1ULL << AXS15231B_PIN_CS),
59 .mode = GPIO_MODE_OUTPUT,
60 .pull_up_en = GPIO_PULLUP_DISABLE,
61 .pull_down_en = GPIO_PULLDOWN_DISABLE,
62 .intr_type = GPIO_INTR_DISABLE,
63 };
64 gpio_config(&cfg);
65 gpio_set_level(AXS15231B_PIN_CS, 1);
66}
67
68static void qspi_write_command(uint8_t lcd_cmd) {
69 spi_transaction_ext_t t = {0};
70 t.base.flags = SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR;
71 t.base.cmd = QSPI_CMD_REG_WRITE;
72 t.base.addr = ((uint32_t)lcd_cmd) << 8;
73 t.base.tx_buffer = NULL;
74 t.base.length = 0;
75 cs_low();
76 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
77 cs_high();
50} 78}
51 79
52static esp_err_t send_data(const uint8_t *data, int len) { 80static void qspi_write_cmd_data8(uint8_t lcd_cmd, uint8_t d) {
53 if (len == 0) return ESP_OK; 81 spi_transaction_ext_t t = {0};
54 spi_transaction_t t = {0}; 82 t.base.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR;
55 t.length = len * 8; 83 t.base.cmd = QSPI_CMD_REG_WRITE;
56 t.tx_buffer = data; 84 t.base.addr = ((uint32_t)lcd_cmd) << 8;
57 t.flags = 0; 85 t.base.tx_data[0] = d;
58 return spi_device_polling_transmit(s_spi, &t); 86 t.base.length = 8;
87 cs_low();
88 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
89 cs_high();
59} 90}
60 91
61static esp_err_t send_cmd_data(uint8_t cmd, const uint8_t *data, int len) { 92static void qspi_write_cmd_data16(uint8_t lcd_cmd, uint16_t d) {
62 esp_err_t ret = send_cmd(cmd); 93 spi_transaction_ext_t t = {0};
63 if (ret != ESP_OK) return ret; 94 t.base.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR;
64 if (len > 0) ret = send_data(data, len); 95 t.base.cmd = QSPI_CMD_REG_WRITE;
65 return ret; 96 t.base.addr = ((uint32_t)lcd_cmd) << 8;
97 t.base.tx_data[0] = d >> 8;
98 t.base.tx_data[1] = d & 0xFF;
99 t.base.length = 16;
100 cs_low();
101 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
102 cs_high();
103}
104
105static void qspi_write_cmd_bytes(uint8_t lcd_cmd, const uint8_t *data, int len) {
106 if (len == 0) {
107 qspi_write_command(lcd_cmd);
108 return;
109 }
110 spi_transaction_ext_t t = {0};
111 t.base.flags = SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR;
112 t.base.cmd = QSPI_CMD_REG_WRITE;
113 t.base.addr = ((uint32_t)lcd_cmd) << 8;
114 t.base.tx_buffer = data;
115 t.base.length = len * 8;
116 cs_low();
117 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
118 cs_high();
119}
120
121static void qspi_write_cmd_d16d16(uint8_t lcd_cmd, uint16_t d1, uint16_t d2) {
122 spi_transaction_ext_t t = {0};
123 t.base.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR;
124 t.base.cmd = QSPI_CMD_REG_WRITE;
125 t.base.addr = ((uint32_t)lcd_cmd) << 8;
126 t.base.tx_data[0] = d1 >> 8;
127 t.base.tx_data[1] = d1 & 0xFF;
128 t.base.tx_data[2] = d2 >> 8;
129 t.base.tx_data[3] = d2 & 0xFF;
130 t.base.length = 32;
131 cs_low();
132 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
133 cs_high();
66} 134}
67 135
68static const uint8_t init_bb[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x5A,0xA5}; 136static const uint8_t init_bb[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x5A,0xA5};
@@ -145,9 +213,12 @@ esp_err_t axs15231b_init(void) {
145 }; 213 };
146 214
147 spi_device_interface_config_t devcfg = { 215 spi_device_interface_config_t devcfg = {
216 .command_bits = 8,
217 .address_bits = 24,
218 .dummy_bits = 0,
148 .clock_speed_hz = 40 * 1000 * 1000, 219 .clock_speed_hz = 40 * 1000 * 1000,
149 .mode = 0, 220 .mode = 0,
150 .spics_io_num = AXS15231B_PIN_CS, 221 .spics_io_num = -1,
151 .queue_size = 7, 222 .queue_size = 7,
152 .flags = SPI_DEVICE_HALFDUPLEX, 223 .flags = SPI_DEVICE_HALFDUPLEX,
153 }; 224 };
@@ -164,6 +235,10 @@ esp_err_t axs15231b_init(void) {
164 return ret; 235 return ret;
165 } 236 }
166 237
238 spi_device_acquire_bus(s_spi, portMAX_DELAY);
239
240 cs_init();
241
167 size_t fb_size = (size_t)s_width * s_height * 2; 242 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); 243 s_fb = heap_caps_malloc(fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
169 if (!s_fb) { 244 if (!s_fb) {
@@ -182,40 +257,28 @@ esp_err_t axs15231b_init(void) {
182 }; 257 };
183 gpio_config(&bl_cfg); 258 gpio_config(&bl_cfg);
184 259
185 send_cmd(SWRESET); 260 qspi_write_command(SWRESET);
186 vTaskDelay(pdMS_TO_TICKS(200)); 261 vTaskDelay(pdMS_TO_TICKS(200));
187 262
188 for (int i = 0; i < INIT_CMD_COUNT; i++) { 263 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); 264 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) { 265 if (s_init_cmds[i].delay_ms > 0) {
195 vTaskDelay(pdMS_TO_TICKS(s_init_cmds[i].delay_ms)); 266 vTaskDelay(pdMS_TO_TICKS(s_init_cmds[i].delay_ms));
196 } 267 }
197 } 268 }
198 269
199 uint8_t madctl_val = MADCTL_MX | MADCTL_MV | MADCTL_RGB; 270 uint8_t madctl_val = MADCTL_RGB;
200 ret = send_cmd_data(MADCTL, &madctl_val, 1); 271 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 272
206 uint8_t colmod_val = 0x55; 273 uint8_t colmod_val = 0x55;
207 ret = send_cmd_data(COLMOD, &colmod_val, 1); 274 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 275
213 axs15231b_fill_screen(0x0000); 276 axs15231b_fill_screen(0x0000);
214 axs15231b_flush(); 277 axs15231b_flush();
215 278
216 axs15231b_set_backlight(true); 279 axs15231b_set_backlight(true);
217 280
218 ESP_LOGI(TAG, "AXS15231B initialized: %dx%d landscape", s_width, s_height); 281 ESP_LOGI(TAG, "AXS15231B initialized: %dx%d portrait", s_width, s_height);
219 return ESP_OK; 282 return ESP_OK;
220} 283}
221 284
@@ -242,41 +305,38 @@ void axs15231b_fill_rect(int x, int y, int w, int h, uint16_t color) {
242void axs15231b_flush(void) { 305void axs15231b_flush(void) {
243 if (!s_spi || !s_fb) return; 306 if (!s_spi || !s_fb) return;
244 307
245 uint8_t buf[4]; 308 ESP_LOGI(TAG, "Flush %dx%d", s_width, s_height);
246 buf[0] = 0;
247 buf[1] = 0;
248 buf[2] = (s_width - 1) >> 8;
249 buf[3] = (s_width - 1) & 0xFF;
250 send_cmd_data(CASET, buf, 4);
251 309
252 buf[0] = 0; 310 qspi_write_cmd_d16d16(CASET, 0, s_width - 1);
253 buf[1] = 0; 311 qspi_write_cmd_d16d16(RASET, 0, s_height - 1);
254 buf[2] = (s_height - 1) >> 8;
255 buf[3] = (s_height - 1) & 0xFF;
256 send_cmd_data(RASET, buf, 4);
257
258 send_cmd(RAMWR);
259 312
260 int total_bytes = s_width * s_height * 2; 313 int total_bytes = s_width * s_height * 2;
261 int chunk_size = 32768; 314 int chunk_size = 32768;
262 int offset = 0; 315 int offset = 0;
263 uint8_t *fb_bytes = (uint8_t *)s_fb; 316 uint8_t *fb_bytes = (uint8_t *)s_fb;
317 bool first = true;
264 318
319 cs_low();
265 while (offset < total_bytes) { 320 while (offset < total_bytes) {
266 int remaining = total_bytes - offset; 321 int remaining = total_bytes - offset;
267 int this_chunk = remaining < chunk_size ? remaining : chunk_size; 322 int this_chunk = remaining < chunk_size ? remaining : chunk_size;
268 323
269 spi_transaction_ext_t t = {0}; 324 spi_transaction_ext_t t = {0};
270 t.base.length = this_chunk * 8; 325 if (first) {
271 t.base.tx_buffer = fb_bytes + offset; 326 t.base.flags = SPI_TRANS_MODE_QIO;
272 t.base.flags = SPI_TRANS_MODE_QIO | SPI_TRANS_MULTILINE_CMD; 327 t.base.cmd = QSPI_CMD_DATA_WRITE;
273 esp_err_t ret = spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t); 328 t.base.addr = QSPI_DATA_ADDR;
274 if (ret != ESP_OK) { 329 first = false;
275 ESP_LOGE(TAG, "Flush transfer failed at offset %d: %s", offset, esp_err_to_name(ret)); 330 } else {
276 return; 331 t.base.flags = SPI_TRANS_MODE_QIO | SPI_TRANS_VARIABLE_CMD |
332 SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY;
277 } 333 }
334 t.base.tx_buffer = fb_bytes + offset;
335 t.base.length = this_chunk * 8;
336 spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t);
278 offset += this_chunk; 337 offset += this_chunk;
279 } 338 }
339 cs_high();
280} 340}
281 341
282int axs15231b_get_width(void) { return s_width; } 342int axs15231b_get_width(void) { return s_width; }
diff --git a/components/axs15231b/include/axs15231b.h b/components/axs15231b/include/axs15231b.h
index 5ec017c..cddea98 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