如何修改ESP32-nesemu项目以通过SPI寄存器实现ILI9486的18位彩色显示?
解决ESP32-NESEmu适配ILI9486 18位色的像素错乱问题
首先得明确你遇到像素错乱的核心原因:原来的代码是为RGB565(16位,2字节/像素)设计的,它把两个16位像素打包成一个32位值批量发送;但ILI9486的18位色是RGB666(3字节/像素),像素的字节边界和旧逻辑完全不匹配,强行套用旧打包方式会直接破坏RGB字节顺序,自然就出现错位了。
下面是具体的修改方案,分步骤落地:
1. 先确认ILI9486的显示模式配置
在发送图像数据前,必须确保ILI9486已被设置为18位RGB666模式。你需要发送对应的屏幕指令(比如0x3A像素格式指令,设置参数为0x66,具体参考ILI9486 datasheet),这一步是基础,否则即使数据正确,屏幕也会解析错误。
2. 实现RGB565到RGB666的正确转换
先写一个简单的转换函数,把原来的16位RGB565值拆成18位RGB666的三个字节:
// 将RGB565(16位)转换为RGB666的R/G/B单字节分量 void rgb565_to_rgb666(uint16_t color, uint8_t *r_byte, uint8_t *g_byte, uint8_t *b_byte) { // RGB565结构:R(5位) G(6位) B(5位) // 转成RGB666:把5位分量左移1位补0,凑成6位 *r_byte = (color >> 11) << 1; // 提取5位R,左移1位补0 *g_byte = (color >> 5) & 0x3F; // 直接提取6位G *b_byte = (color & 0x1F) << 1; // 提取5位B,左移1位补0 }
3. 重构SPI数据打包逻辑
ESP32的SPI W寄存器是32位(4字节)的,我们需要把3字节的像素按顺序填充到这些寄存器里,保证每个像素的R/G/B字节连续发送,不跨像素打乱顺序。
修改原来的循环代码,改成按8个像素为一组(8×3=24字节,刚好填满6个32位SPI寄存器)处理:
#include <stdint.h> #include <string.h> #include "driver/spi_common.h" // 假设SPI_NUM是你使用的SPI端口(比如SPI2_HOST) #define SPI_NUM SPI2_HOST #define MIN(a,b) ((a)<(b)?(a):(b)) // ... 其他原有代码 ... while (x < width) { // 每次最多处理8个像素(凑24字节,适配6个32位寄存器) int pixels_to_process = MIN(8, width - x); uint32_t spi_reg_data[6] = {0}; // 存储要写入SPI寄存器的32位数据 int byte_pos = 0; // 当前填充的字节位置(0~23) for (int i = 0; i < pixels_to_process; i++) { if (data == NULL) { // 空数据时填充黑色(0x000000) uint8_t empty_bytes[3] = {0, 0, 0}; // 把三个空字节填充到spi_reg_data中 for (int j = 0; j < 3; j++) { int reg_idx = byte_pos / 4; int bit_shift = (3 - (byte_pos % 4)) * 8; // SPI高位字节先发送 spi_reg_data[reg_idx] |= (uint32_t)empty_bytes[j] << bit_shift; byte_pos++; } x++; continue; } // 从调色板获取当前像素的RGB565值 uint16_t rgb565_color = myPalette[(unsigned char)(data[y][x])]; x++; // 转换为RGB666的三个字节 uint8_t r, g, b; rgb565_to_rgb666(rgb565_color, &r, &g, &b); // 按顺序把R/G/B字节填充到SPI寄存器数组中 uint8_t rgb_bytes[3] = {r, g, b}; for (int j = 0; j < 3; j++) { int reg_idx = byte_pos / 4; int bit_shift = (3 - (byte_pos % 4)) * 8; // 高位字节优先,匹配ILI9486接收顺序 spi_reg_data[reg_idx] |= (uint32_t)rgb_bytes[j] << bit_shift; byte_pos++; } } // 等待SPI传输完成,确保寄存器空闲 while (READ_PERI_REG(SPI_CMD_REG(SPI_NUM)) & SPI_USR); // 把填充好的32位数据写入SPI的W0~W5寄存器 for (int i = 0; i < 6; i++) { // 处理行尾剩余像素时,跳过未使用的寄存器 if (i * 4 >= byte_pos) break; WRITE_PERI_REG(SPI_W0_REG(SPI_NUM) + (i << 2), spi_reg_data[i]); } // 启动SPI传输 SET_PERI_REG_MASK(SPI_CMD_REG(SPI_NUM), SPI_USR); }
关键细节说明
- 字节顺序:代码中用
(3 - (byte_pos % 4)) * 8的移位方式,是因为SPI默认高位字节先发送,这样能保证每个像素的R→G→B字节按顺序被ILI9486接收;如果你的屏幕要求低位字节先,把移位逻辑改成(byte_pos % 4) * 8即可。 - 寄存器填充:按8个像素一组处理,刚好凑齐24字节(6×4),完美适配ESP32的32位SPI寄存器,避免了跨寄存器的像素拆分,这是解决错位的核心。
- 边界处理:用
MIN(8, width - x)处理行尾剩余像素,避免越界访问。
这样修改后,就能正确把RGB565的NES画面转换成18位色,通过SPI发送给ILI9486,不会再出现像素错乱的问题了。
内容的提问来源于stack exchange,提问作者Ilya
相关产品推荐
相关产品推荐

