From 899016795c389151e6b486ec470653f5688e5c5f Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 20 May 2026 02:14:45 +0530 Subject: feat: merge display-fix — touch driver, keyboard, WiFi setup UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- components/axs15231b/axs15231b.c | 237 ++++++++++++++++++++----------- components/axs15231b/include/axs15231b.h | 5 +- 2 files changed, 159 insertions(+), 83 deletions(-) (limited to 'components') 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"; #define MADCTL_MV 0x20 #define MADCTL_RGB 0x00 +#define QSPI_CMD_REG_WRITE 0x02 +#define QSPI_CMD_DATA_WRITE 0x32 +#define QSPI_DATA_ADDR 0x003C00 + static spi_device_handle_t s_spi = NULL; static uint16_t *s_fb = NULL; static int s_width = AXS15231B_WIDTH; static int s_height = AXS15231B_HEIGHT; +static int s_stride = AXS15231B_WIDTH; +static uint8_t *s_swap_buf = NULL; +#define SWAP_BUF_PIXELS 2048 typedef struct { uint8_t cmd; @@ -41,28 +48,92 @@ typedef struct { uint16_t delay_ms; } init_cmd_t; -static esp_err_t send_cmd(uint8_t cmd) { - spi_transaction_t t = {0}; - t.length = 8; - t.tx_data[0] = cmd; - t.flags = SPI_TRANS_USE_TXDATA; - return spi_device_polling_transmit(s_spi, &t); +static inline void cs_low(void) { + gpio_set_level(AXS15231B_PIN_CS, 0); +} + +static inline void cs_high(void) { + gpio_set_level(AXS15231B_PIN_CS, 1); +} + +static void cs_init(void) { + gpio_config_t cfg = { + .pin_bit_mask = (1ULL << AXS15231B_PIN_CS), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&cfg); + gpio_set_level(AXS15231B_PIN_CS, 1); +} + +static void qspi_write_command(uint8_t lcd_cmd) { + spi_transaction_ext_t t = {0}; + t.base.flags = SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR; + t.base.cmd = QSPI_CMD_REG_WRITE; + t.base.addr = ((uint32_t)lcd_cmd) << 8; + t.base.tx_buffer = NULL; + t.base.length = 0; + cs_low(); + spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t); + cs_high(); +} + +static void qspi_write_cmd_data8(uint8_t lcd_cmd, uint8_t d) { + spi_transaction_ext_t t = {0}; + t.base.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR; + t.base.cmd = QSPI_CMD_REG_WRITE; + t.base.addr = ((uint32_t)lcd_cmd) << 8; + t.base.tx_data[0] = d; + t.base.length = 8; + cs_low(); + spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t); + cs_high(); +} + +static void qspi_write_cmd_data16(uint8_t lcd_cmd, uint16_t d) { + spi_transaction_ext_t t = {0}; + t.base.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR; + t.base.cmd = QSPI_CMD_REG_WRITE; + t.base.addr = ((uint32_t)lcd_cmd) << 8; + t.base.tx_data[0] = d >> 8; + t.base.tx_data[1] = d & 0xFF; + t.base.length = 16; + cs_low(); + spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t); + cs_high(); } -static esp_err_t send_data(const uint8_t *data, int len) { - if (len == 0) return ESP_OK; - spi_transaction_t t = {0}; - t.length = len * 8; - t.tx_buffer = data; - t.flags = 0; - return spi_device_polling_transmit(s_spi, &t); +static void qspi_write_cmd_bytes(uint8_t lcd_cmd, const uint8_t *data, int len) { + if (len == 0) { + qspi_write_command(lcd_cmd); + return; + } + spi_transaction_ext_t t = {0}; + t.base.flags = SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR; + t.base.cmd = QSPI_CMD_REG_WRITE; + t.base.addr = ((uint32_t)lcd_cmd) << 8; + t.base.tx_buffer = data; + t.base.length = len * 8; + cs_low(); + spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t); + cs_high(); } -static esp_err_t send_cmd_data(uint8_t cmd, const uint8_t *data, int len) { - esp_err_t ret = send_cmd(cmd); - if (ret != ESP_OK) return ret; - if (len > 0) ret = send_data(data, len); - return ret; +static void qspi_write_cmd_d16d16(uint8_t lcd_cmd, uint16_t d1, uint16_t d2) { + spi_transaction_ext_t t = {0}; + t.base.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR; + t.base.cmd = QSPI_CMD_REG_WRITE; + t.base.addr = ((uint32_t)lcd_cmd) << 8; + t.base.tx_data[0] = d1 >> 8; + t.base.tx_data[1] = d1 & 0xFF; + t.base.tx_data[2] = d2 >> 8; + t.base.tx_data[3] = d2 & 0xFF; + t.base.length = 32; + cs_low(); + spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t); + cs_high(); } static const uint8_t init_bb[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x5A,0xA5}; @@ -136,20 +207,23 @@ esp_err_t axs15231b_init(void) { esp_err_t ret; spi_bus_config_t buscfg = { - .mosi_io_num = AXS15231B_PIN_D0, + .data0_io_num = AXS15231B_PIN_D0, + .data1_io_num = AXS15231B_PIN_D1, .sclk_io_num = AXS15231B_PIN_CLK, - .miso_io_num = -1, - .quadwp_io_num = -1, - .quadhd_io_num = -1, + .data2_io_num = AXS15231B_PIN_D2, + .data3_io_num = AXS15231B_PIN_D3, .max_transfer_sz = 32768, }; spi_device_interface_config_t devcfg = { + .command_bits = 8, + .address_bits = 24, + .dummy_bits = 0, .clock_speed_hz = 40 * 1000 * 1000, .mode = 0, - .spics_io_num = AXS15231B_PIN_CS, + .spics_io_num = -1, .queue_size = 7, - .flags = 0, + .flags = SPI_DEVICE_HALFDUPLEX, }; ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); @@ -164,6 +238,10 @@ esp_err_t axs15231b_init(void) { return ret; } + spi_device_acquire_bus(s_spi, portMAX_DELAY); + + cs_init(); + size_t fb_size = (size_t)s_width * s_height * 2; s_fb = heap_caps_malloc(fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); if (!s_fb) { @@ -173,6 +251,13 @@ esp_err_t axs15231b_init(void) { memset(s_fb, 0, fb_size); ESP_LOGI(TAG, "Framebuffer allocated: %zu bytes in PSRAM", fb_size); + s_swap_buf = heap_caps_aligned_alloc(16, SWAP_BUF_PIXELS * 2, MALLOC_CAP_DMA); + if (!s_swap_buf) { + ESP_LOGE(TAG, "Failed to allocate DMA swap buffer (%d bytes)", SWAP_BUF_PIXELS * 2); + return ESP_ERR_NO_MEM; + } + ESP_LOGI(TAG, "DMA swap buffer: %d bytes in internal RAM", SWAP_BUF_PIXELS * 2); + gpio_config_t bl_cfg = { .pin_bit_mask = (1ULL << AXS15231B_PIN_BL), .mode = GPIO_MODE_OUTPUT, @@ -182,40 +267,28 @@ esp_err_t axs15231b_init(void) { }; gpio_config(&bl_cfg); - send_cmd(SWRESET); + qspi_write_command(SWRESET); vTaskDelay(pdMS_TO_TICKS(200)); for (int i = 0; i < INIT_CMD_COUNT; i++) { - ret = send_cmd_data(s_init_cmds[i].cmd, s_init_cmds[i].data, s_init_cmds[i].data_len); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Init cmd 0x%02X failed: %s", s_init_cmds[i].cmd, esp_err_to_name(ret)); - return ret; - } + qspi_write_cmd_bytes(s_init_cmds[i].cmd, s_init_cmds[i].data, s_init_cmds[i].data_len); if (s_init_cmds[i].delay_ms > 0) { vTaskDelay(pdMS_TO_TICKS(s_init_cmds[i].delay_ms)); } } - uint8_t madctl_val = MADCTL_MX | MADCTL_MV | MADCTL_RGB; - ret = send_cmd_data(MADCTL, &madctl_val, 1); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to set rotation: %s", esp_err_to_name(ret)); - return ret; - } + uint8_t madctl_val = MADCTL_RGB; + qspi_write_cmd_data8(MADCTL, madctl_val); uint8_t colmod_val = 0x55; - ret = send_cmd_data(COLMOD, &colmod_val, 1); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to set pixel format: %s", esp_err_to_name(ret)); - return ret; - } + qspi_write_cmd_data8(COLMOD, colmod_val); axs15231b_fill_screen(0x0000); axs15231b_flush(); axs15231b_set_backlight(true); - ESP_LOGI(TAG, "AXS15231B initialized: %dx%d landscape", s_width, s_height); + ESP_LOGI(TAG, "AXS15231B initialized: %dx%d portrait", s_width, s_height); return ESP_OK; } @@ -224,9 +297,10 @@ void axs15231b_set_backlight(bool on) { } void axs15231b_fill_screen(uint16_t color) { - uint32_t pixels = (uint32_t)s_width * s_height; - for (uint32_t i = 0; i < pixels; i++) { - s_fb[i] = color; + for (int row = 0; row < s_height; row++) { + for (int col = 0; col < s_width; col++) { + s_fb[row * s_stride + col] = color; + } } } @@ -234,48 +308,51 @@ void axs15231b_fill_rect(int x, int y, int w, int h, uint16_t color) { if (x < 0 || y < 0 || x + w > s_width || y + h > s_height) return; for (int row = y; row < y + h; row++) { for (int col = x; col < x + w; col++) { - s_fb[row * s_width + col] = color; + s_fb[row * s_stride + col] = color; } } } void axs15231b_flush(void) { - if (!s_spi || !s_fb) return; - - uint8_t buf[4]; - buf[0] = 0; - buf[1] = 0; - buf[2] = (s_width - 1) >> 8; - buf[3] = (s_width - 1) & 0xFF; - send_cmd_data(CASET, buf, 4); - - buf[0] = 0; - buf[1] = 0; - buf[2] = (s_height - 1) >> 8; - buf[3] = (s_height - 1) & 0xFF; - send_cmd_data(RASET, buf, 4); - - send_cmd(RAMWR); - - int total_bytes = s_width * s_height * 2; - int chunk_size = 32768; - int offset = 0; - uint8_t *fb_bytes = (uint8_t *)s_fb; - - while (offset < total_bytes) { - int remaining = total_bytes - offset; - int this_chunk = remaining < chunk_size ? remaining : chunk_size; - - spi_transaction_t t = {0}; - t.length = this_chunk * 8; - t.tx_buffer = fb_bytes + offset; - esp_err_t ret = spi_device_polling_transmit(s_spi, &t); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Flush transfer failed at offset %d: %s", offset, esp_err_to_name(ret)); - return; + if (!s_spi || !s_fb || !s_swap_buf) return; + + qspi_write_cmd_d16d16(CASET, 0, s_width - 1); + qspi_write_cmd_d16d16(RASET, 0, s_height - 1); + qspi_write_command(RAMWR); + + bool first = true; + cs_low(); + for (int row = 0; row < s_height; row++) { + int chunk_remaining = s_width; + int col_offset = 0; + while (chunk_remaining > 0) { + int chunk_pixels = chunk_remaining < SWAP_BUF_PIXELS ? chunk_remaining : SWAP_BUF_PIXELS; + int chunk_bytes = chunk_pixels * 2; + + uint8_t *src = (uint8_t *)(s_fb + row * s_stride + col_offset); + for (int i = 0; i < chunk_bytes; i += 2) { + s_swap_buf[i] = src[i + 1]; + s_swap_buf[i + 1] = src[i]; + } + + spi_transaction_ext_t t = {0}; + if (first) { + t.base.flags = SPI_TRANS_MODE_QIO; + t.base.cmd = QSPI_CMD_DATA_WRITE; + t.base.addr = QSPI_DATA_ADDR; + first = false; + } else { + t.base.flags = SPI_TRANS_MODE_QIO | SPI_TRANS_VARIABLE_CMD | + SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY; + } + t.base.tx_buffer = s_swap_buf; + t.base.length = chunk_pixels * 16; + spi_device_polling_transmit(s_spi, (spi_transaction_t *)&t); + col_offset += chunk_pixels; + chunk_remaining -= chunk_pixels; } - offset += this_chunk; } + cs_high(); } int 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 @@ #include #include -#define AXS15231B_WIDTH 480 -#define AXS15231B_HEIGHT 320 +#define AXS15231B_WIDTH 320 +#define AXS15231B_HEIGHT 480 #define AXS15231B_PIN_CS 45 #define AXS15231B_PIN_CLK 47 @@ -23,5 +23,4 @@ void axs15231b_fill_rect(int x, int y, int w, int h, uint16_t color); void axs15231b_flush(void); int axs15231b_get_width(void); int axs15231b_get_height(void); - #endif -- cgit v1.2.3