恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

STM32驱动0.99寸圆屏:硬件SPI+DMA显示完整实现

  • 首页
  • 资讯中心
  • /
  • STM32驱动0.99寸圆屏:硬件SPI+DMA显示完整实现

相关资讯

KUKA码垛机器人程序包解析:从归档导入到码垛循环调试 2026/10/14 18:04:18
RTD2660HD显示主控固件开发实战:从屏参修改到EDID与ISP烧录 2026/10/14 18:04:18
STM32驱动ST7789V:从寄存器初始化到DMA刷屏全解析 2026/10/14 17:59:18

最新资讯

MTK平台关机充电卡第一帧:充电链路与显示初始化的联合排查
apksigner签名全解:v1/v2/v3三重签名原理与实战
西安80到CGCS2000转换:七参数求解与ArcGIS实操指南
thClaws如何压缩超长对话?Compaction上下文管理机制全解析
用Math.NET做连续系统仿真:替代手写循环的数值库实践
kubectl 命令实战指南:从查询到排障的完整运维手册

今日推荐

IBM Verify Identity Access 修复 22 个漏洞:两个 9.8 严重 RCE 无需认证
小区物业管理系统
小型超市仓库管理系统

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

STM32驱动0.99寸圆屏:硬件SPI+DMA显示完整实现

发布时间:2026/10/14 18:04:18
STM32驱动0.99寸圆屏:硬件SPI+DMA显示完整实现 简介一套面向STM32中高级开发者的圆形TFT屏显示工程示例聚焦0.99寸小屏在硬件SPI与DMA方式下的高效刷新实现。资源对应STM32F103RCT6主控利用硬件SPIDMA传输显著提升图片显示速度动态效果流畅适合对刷屏性能有要求的嵌入式显示场景由于主控RAM有限工程内图片数量不多可帮助读者理解DMA显控架构与资源取舍。压缩包共218个文件包含41个.h头文件、40个.c源文件及编译链接产生的.o/.crf/.axf等中间文件另有工程配置与烧录文件整体约5.66MB属于精简可跑的Keil项目。已有547人学习下载适合想直接在F103平台验证SPIDMA刷屏思路的开发者。通过阅读驱动代码与工程配置可快速掌握DMA传输时序、圆屏初始化及动态图片显示方法同时也能看到小型MCU在图像存储上的局限性为后续扩展外部存储提供参考。1. STM32F103RCT6配0.99寸TFT圆屏硬件SPI加DMA显示这件事比想象中复杂STM32F103RCT6驱动0.99寸TFT圆屏用硬件SPI加DMA显示听上去只是普通方屏方案改个尺寸实际动手才发现完全不是一回事。圆屏的驱动IC天生只认识方形GRAM玻璃盖板却挖成了圆形直接灌数据会出现四角溢出、鬼影和边缘漏色如果沿用软件模拟SPI的写法80x160分辨率一帧RGB565要刷上百毫秒转个圈都是撕裂。这篇文章把硬件SPIDMA的完整路径拆开讲为什么选这颗屏配这条路、引线和DMA通道怎么选、圆域裁剪怎么做、初始化序列和偏移量怎么调以及最常见的翻车点和对应排查办法。适合已经点亮过普通方屏、想真正跑顺圆屏显示并榨干F103带宽的工程师纯入门读者看完前半部分也能跟得上。2. 为什么是硬件SPIDMA圆屏显示的核心矛盾与选型理由2.1 圆屏不是“方屏改个形状”驱动IC与显示区域的真相0.99寸圆屏的驱动IC我默认按ST7735S来讨论这颗芯片在低价圆屏里出现率很高。它的GRAM是标准矩形常见分辨率有80x160和128x128两种物理像素排列跟方屏没有任何区别。所谓“圆屏”只是玻璃盖板做成了圆形驱动IC自己完全没有“圆”的概念。这意味着三件事第一显示区坐标从(0,0)到(width-1,height-1)全部有效圆外区域一样会被刷新只是物理上被黑色边框盖住看不到第二如果你直接把圆外区域的像素也写入非黑非白的颜色屏幕扫描刷新时这些颜色会从圆形边缘漏出来形成一圈彩色的鬼影第三厂家为了让圆形画面居中通常会在驱动IC的OTP里写入坐标偏移量不处理这个偏移画面整体是歪的。所以拿到一块圆屏的第一件事不是写代码是先确认驱动IC型号。同一家店铺不同批次的屏可能是ST7735S也可能是GC9107两者的初始化序列差异很大直接套方屏代码的结果就是花屏或白屏。我一般会先看PCB丝印确认型号后再决定初始化表。如果丝印模糊用逻辑分析仪抓上电时序不现实更快的办法是写一个最小初始化序列把Sleep Out、Display On两个命令跑通然后逐帧刷纯色能出颜色基本就能确认驱动IC的工作方式。另一个容易忽略的点是颜色格式。0.99寸圆屏大多配置成RGB565也就是每像素2字节高字节在前。但部分GC9107的变种默认是RGB666或BGR顺序直接刷RGB565颜色会偏色甚至紫色。所以初始化里COLMOD命令的值要跟你后续填充的像素格式严格对应我自己吃过大亏第一版代码用RGB565刷新屏幕显示纯红色却是蓝紫色排查了整整一下午才想起去看COLMOD寄存器的设置。2.2 硬件SPI vs 软件模拟常见做法与带宽账常见做法是用GPIO手动翻转SCK和MOSI来模拟SPI时序。这个方案在1寸以下、分辨率64x64的屏上还勉强能用到了0.99寸圆屏这种80x160分辨率的场景就不行了。全屏RGB565一帧的总字节数是80160225600字节。如果SPI时钟能跑到18MHz理论刷新时间是25600*8/18MHz≈11.4ms。但软件模拟SPI的每条SCK翻转周期在F103跑72MHz主频时最少也要十几个CPU周期实际SPI时钟频率能到2-3MHz已经算优化得很好了对应的刷新时间接近100ms一帧。做动画或者表盘时这个速度肉眼可见地卡而且CPU全程被卡死在翻转循环里主循环里的按键扫描、传感器读取全部被拖慢。换到硬件SPI1情况完全不同。SPI1挂在APB2总线上时钟源72MHz用4分频就是18MHz跟ST7735S数据手册标称的最大SCLK频率基本匹配。更重要的是SPI1的发送可以触发DMA1的通道3数据从内存直接搬到SPI1的数据寄存器CPU只在启动传输时介入一次之后可以继续做别的逻辑。这才是“硬件SPIDMA显示”的真正价值不是单纯刷快一点而是把刷屏从CPU的串行任务里摘出去。选型时的另一个核心矛盾是帧缓冲放哪。STM32F103RCT6有48KB SRAM一个80x160x2的RGB565帧缓冲需要25.6KB看起来放得下但如果你同时开RTOS、跑日志缓冲区、放几个协议栈的接收缓冲内存就可能捉襟见肘。128x128分辨率的圆屏更紧张128128232KB再加上动态分配碎片很容易超。所以我一般建议优先考虑80x160分辨率的圆屏帧缓冲占一半内存另一半跑逻辑还能接受。如果一定要上128x128就得接受“不做双缓冲、脏矩形局部刷新”的折中方案。2.3 DMA搬运模式与SPI时钟分频的搭配DMA搬运的一点关键认知SPI1的发送对应DMA1的通道3外设地址固定是SPI1-DR的地址内存地址指向帧缓冲的首地址方向是内存到外设。模式必须选Normal而不是Circular除非你确实需要无限循环刷同一帧静态画面。传输宽度方面DMA和SPI都要配成匹配的格式。我习惯用8位DMA加上SPI的8位数据帧一帧25600字节正好搬运完毕RBG565拆成高字节、低字节依次写入帧缓冲的连续两个字节DMA按字节搬运后由SPI逐字节发出全程不存在字节序问题。16位DMA每次搬2字节理论效率更高但必须同时把SPI配成16位数据帧而且要注意SPI-DR是16位寄存器写入高字节还是低字节在前由代码决定一旦搞错整个画面花掉。SPI时钟分频我默认用4分频也就是18MHz。ST7735S在3.3V供电时跑18MHz实测可靠超过20MHz偶发花屏的案例不少尤其是在飞线连接、没有做阻抗匹配的开发板上。分频比跟APB2时钟源有关F103的SPI1时钟源是PCLK272MHz所以4分频18MHz8分频9MHz2分频36MHz但不可靠。初始调试阶段我通常会先用8分频跑通逻辑确认颜色和偏移正确之后再把分频切到4这样可以隔离掉“刷太快导致的花屏”跟“代码逻辑错误导致的花屏”两个不同的问题。DMA优先级这块容易被忽略。DMA1通道3如果不给高优先级当系统里同时有别的DMA请求比如ADC扫描或UART接收SPI1_TX的搬运请求可能被延后。被延后的直接表现不是丢数据而是帧内填充出现间隙视觉效果就是画面撕裂。我一般把DMA1_Channel3配成High优先级同时启用SPI1的TXE中断作为备用如果DMA因为某种原因没来得及喂数据TXE中断能兜底虽然速度会降但至少画面不裂。2.4 初始化序列为什么不能直接套方屏的圆屏与方屏的初始化命令主体相同差别集中在三个位置。第一是Sleep Out到Display On之间的延时。方屏常见延时120ms圆屏某些批次对延时更敏感延时不够会出现唤醒后第一帧颜色偏淡或闪烁。我习惯把这个延时加到200ms以上反正只影响上电那一下不影响运行。第二是伽马校正曲线寄存器序列的值同一颗ST7735S在不同批次的圆屏上伽马值可能完全不同不匹配的画面表现是红色偏紫或蓝色发闷。第三是坐标偏移这是圆屏区别于方屏最重要的点。方屏的偏移通常为0圆屏为了对齐圆形盖板厂商会在初始化时写入列偏移和行偏移典型值是列偏移2、行偏移1但不同批次差异很大。处理偏移的标准做法是写一个遍历工具。把x_off从0到5、y_off从0到5两重循环每次刷纯色全屏间隔1秒人眼观察哪一组组合让画面最居中。不要问为什么厂家不在手册里写清楚实际情况就是不同屏批次有不同值与其看手册猜不如实测。确定偏移后把它写死到代码里后面所有窗口设置都要把这个偏移加进去这一点在局部刷新时会反复用到。3. 引脚分配与驱动框架从GPIO到DMA的完整打通3.1 引脚分配与硬件连接我选用SPI1驱动这块圆屏核心原因是PA5和PA7是SPI1的默认SCK和MOSI引脚并且SPI1的DMA请求挂到DMA1上在F103里配置路径最短。DC、RES、CS这三个控制信号用普通GPIODC用于区分命令和数据RES用于硬件复位CS用于片选。CS不直接接地而是用GPIO控制方便以后SPI1总线上挂第二个外设时不用改板子。下面是实测稳定可用的接线表直接照着接即可屏幕引脚STM32F103RCT6引脚说明SCL/SCKPA5SPI1_SCK复用推挽输出SDA/MOSIPA7SPI1_MOSI复用推挽输出DCPA1普通GPIO高电平数据、低电平命令RESPA2普通GPIO低电平复位CSPA3普通GPIO低电平选中VCC3.3V屏幕逻辑供电背光LED单独处理GNDGND共地需要特别留意背光LED的供电。有些屏幕把背光LED正极单独引出来如果直接接到3.3V电流可能达到20-30mA这会超过F103单个GPIO引脚的驱动能力也会让3.3V轨的电压在LED开启瞬间产生毛刺进而影响SPI通信的可靠性。我的做法是背光LED用一个NPN三极管开关MCU的GPIO控制基极LED正极单独接3.3V或5V限流电阻负极接三极管集电极。这样既保护了MCU引脚也隔离了LED对主供电的干扰。另一个硬接线坑是RES引脚。很多调试场景下人们把RES直接拉到高电平省略复位电路这在第一次上电时可以但如果你需要软件复位屏幕没有RES的控制就会很被动。我习惯给RES一个R-C复位电路同时用GPIO控制上电时由RC自动拉低复位运行中需要复位时由GPIO手动拉低再拉高。3.2 SPI、GPIO与DMA初始化代码下面这段初始化代码基于STM32标准外设库如果你换到HAL库思路一致把寄存器操作换成对应的HAL函数即可。整个初始化分成GPIO配置、SPI配置、DMA配置三步顺序不能乱先配置GPIO再配置SPI最后配置DMA因为DMA配置需要引用SPI外设地址。#include stm32f10x.h #define LCD_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_3) #define LCD_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_3) #define LCD_DC_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_1) #define LCD_DC_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_1) #define LCD_RES_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_2) #define LCD_RES_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_2) void lcd_gpio_init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // PA5SCK, PA7MOSI复用推挽输出50MHz GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PA1DC, PA2RES, PA3CS普通推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); LCD_CS_HIGH(); LCD_RES_HIGH(); LCD_DC_HIGH(); } void lcd_spi_dma_init(void) { SPI_InitTypeDef SPI_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // SPI1主机模式8位数据帧CPOL0, CPHA0模式0 SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; // 72/4 18MHz SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); // DMA1通道3SPI1_TX内存到外设 DMA_DeInit(DMA1_Channel3); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)g_frame_buffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize LCD_WIDTH * LCD_HEIGHT * 2; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel3, DMA_InitStructure); DMA_Cmd(DMA1_Channel3, ENABLE); }逻辑说明GPIO初始化阶段PA5和PA7配置成复用推挽这样硬件SPI外设才能获得这两个引脚的控制权PA1、PA2、PA3配成普通推挽输出用于DC、RES和CS。SPI配置成模式0CPOL0、CPHA1Edge这是ST7735S数据手册推荐的时序如果你的屏花屏先试试模式1CPOL1、CPHA1Edge部分GC9107变种更吃模式1。参数说明SPI_BaudRatePrescaler_4在72MHz主频下对应18MHz。DMA方向为DMA_DIR_PeripheralDST含义是内存为源、外设为目标。PeripheralDataSize和MemoryDataSize都配成Byte匹配SPI的8位数据帧。DMA_BufferSize要等于整帧数据的字节数这里的g_frame_buffer定义见3.3节。DMA_Priority_High给SPI1的TX请求足够高的优先权避免被其他DMA挤掉。一个容易产生误解的地方DMA_Cmd(DMA1_Channel3, ENABLE)之后DMA并不会立刻开始搬运。触发条件是SPI1的TXE事件只有写了一次SPI1-DR之后SPI外设才产生TXE请求DMA才会真正搬第一字节。所以初始化结束后先不要着急你后面调用发送函数时DMA才会有动作。3.3 帧缓冲设计与圆屏裁剪实现帧缓冲我用全局数组放在BSS段。局部数组放在函数栈里80x160x225600字节的数组直接爆栈这一点务必注意。#define LCD_WIDTH 80 #define LCD_HEIGHT 160 #define LCD_X_OFF 0 // 具体值由偏移测试确定 #define LCD_Y_OFF 0 // 具体值由偏移测试确定 static uint8_t g_frame_buffer[LCD_WIDTH * LCD_HEIGHT * 2]; void lcd_dma_send_all(void) { DMA_Cmd(DMA1_Channel3, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel3, sizeof(g_frame_buffer)); DMA_Cmd(DMA1_Channel3, ENABLE); } void lcd_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { lcd_write_cmd(0x2A); // CASET列地址设置 lcd_write_data((x0 LCD_X_OFF) 8); lcd_write_data((x0 LCD_X_OFF) 0xFF); lcd_write_data((x1 LCD_X_OFF) 8); lcd_write_data((x1 LCD_X_OFF) 0xFF); lcd_write_cmd(0x2B); // RASET行地址设置 lcd_write_data((y0 LCD_Y_OFF) 8); lcd_write_data((y0 LCD_Y_OFF) 0xFF); lcd_write_data((y1 LCD_Y_OFF) 8); lcd_write_data((y1 LCD_Y_OFF) 0xFF); lcd_write_cmd(0x2C); // RAMWR开始写显存 }逻辑说明lcd_dma_send_all里先禁用DMA重设计数器再使能这个顺序不能随意颠倒。如果连续两次调用中间没有禁用第二次设置的计数器可能覆盖在一个未完成传输的DMA通道上轻则丢数据重则DMA卡死。lcd_set_window是局部刷新的基础任何局部刷新前都要先调用它让驱动IC只更新指定矩形区域。圆屏裁剪的落地方式是做一个圆形掩码判断。绘制像素时先算这个点是否落在圆内圆外直接丢弃不写入帧缓冲void lcd_draw_pixel(uint16_t x, uint16_t y, uint16_t color) { int16_t cx LCD_WIDTH / 2; int16_t cy LCD_HEIGHT / 2; int16_t r (LCD_WIDTH LCD_HEIGHT ? LCD_WIDTH : LCD_HEIGHT) / 2; int16_t dx x - cx; int16_t dy y - cy; // 圆外像素不写入避免边缘漏色 if (dx * dx dy * dy r * r) { return; } uint32_t idx (y * LCD_WIDTH x) * 2; g_frame_buffer[idx] color 8; // 高字节在前 g_frame_buffer[idx 1] color 0xFF; // 低字节在后 }参数说明这个裁剪是纯软件判断每画一个像素要做两次乘法和一次加法。如果只是全屏刷一张静态图建议先把图片预处理成圆形裁剪后的数据再直接拷入帧缓冲运行时不要走逐像素判断。如果分辨率是128x128圆屏圆心坐标和半径的计算方法一样把LCD_WIDTH和LCD_HEIGHT换成128即可。圆外不写入是刻意为之因为圆外区域本来就被黑色盖板遮挡写入纯属浪费DMA带宽还可能在边缘形成一圈彩带。3.4 ST7735S初始化序列与偏移量调整初始化序列建议用表驱动方式组织把命令、数据、延时放在结构体里换屏只改表不动代码逻辑typedef struct { uint8_t cmd; uint8_t len; uint8_t *data; uint16_t delay_ms; } lcd_init_cmd_t; static uint8_t gamma_seq[] {0x02, 0x1C, 0x2B, 0x2D, 0x0B, 0x0C}; static uint8_t power_seq[] {0xA2, 0x02, 0x84}; static const lcd_init_cmd_t lcd_st7735s_init[] { {0x01, 0, NULL, 120}, // SWRESET {0x11, 0, NULL, 120}, // SLPOUT {0x36, 1, (uint8_t[]){0xC0}, 0}, // MADCTLRGB顺序上下翻转 {0x3A, 1, (uint8_t[]){0x05}, 0}, // COLMOD16位/像素 {0xB1, 3, (uint8_t[]){0x05, 0x3A, 0x3A}, 0}, // FRMCTR1 {0xB4, 1, (uint8_t[]){0x03}, 0}, // INVCTR {0xC0, 3, power_seq, 0}, // PWCTR1 {0xC5, 1, (uint8_t[]){0xBE}, 0}, // VCOM {0xE0, 6, gamma_seq, 0}, // GMCTRP1 {0x29, 0, NULL, 20}, // DISPON };逻辑说明整个表里最容易被忽略的是MADCTL寄存器0x36。0xC0的意义是RGB顺序、行方向自下而上、列方向自右向左。如果你的屏排线方向不同这个值会表现为画面镜像或颜色通道互换需要改成0x00或0xE0实测。COLMOD配0x05表示RGB565如果配成0x06就是RGB666颜色会莫名偏白偏淡。Gamma序列直接影响白平衡换屏时如果颜色不准优先怀疑这个序列。偏移量调整做成独立函数不在初始化表写死方便批量测试void lcd_set_offset(uint8_t x_off, uint8_t y_off) { LCD_X_OFF x_off; LCD_Y_OFF y_off; }实际调试时在主循环里让x_off从0到5、y_off从0到5遍历每次刷纯色全屏间隔1秒仔细观察哪一组画面最居中。注意偏移量加在窗口地址上不是加在帧缓冲索引上这个区别在局部刷新时尤其重要。4. 避坑排查圆屏DMA最常见的五个翻车现场这一章把我在若干个模拟项目里踩过的坑整理成五条每条严格按现象、原因、解决三步写。你照着前文做完如果出现花屏、歪屏、撕裂、残影优先对照本段落排查。4.1 画面整体偏右下圆框边出现月牙漏白现象上电后图像内容整体向右下方偏移2到3个像素圆形边框左上角出现一条明显的白色或黑色月牙右下角画面被边框吃掉一块。原因驱动IC出厂时写入了坐标偏移量目的是让有效画面正好对着圆形盖板中心。你没有设置偏移寄存器窗口从逻辑(0,0)开始GRAM物理地址与逻辑地址错位。解决用3.4节的lcd_set_offset函数遍历偏移值从(0,0)开始逐步增加到(5,5)刷纯色观察哪一组让月牙消失、画面平稳居中。注意偏移量是加在lcd_set_window函数里CASET和RASET的地址上的不是加在帧缓冲索引上。改完窗口函数后重新刷全屏验证如果偏移值已经试完仍不居中检查MADCTL的镜像位有些屏在镜像生效后偏移方向会反转。4.2 快速刷新时画面水平撕裂中间一条分割线现象播放动画或快速切换界面时屏幕中间有一条水平分割线线的上下两部分内容错位一部分是新帧另一部分还是旧帧。原因DMA搬数据的同时屏幕扫描刷新也在进行GRAM写入和屏幕扫描竞争同一个显存区域。没有等待TE信号或固定延时下一帧的写入落在了上一帧的扫描过程中间扫描线两侧自然展示两帧内容。解决在全屏DMA发送前至少等待2ms让上一帧扫描周期结束再写入。实测80x160在18MHz下DMA搬运约1.4ms加2ms延时后撕裂基本消失。如果你的屏幕有TE引脚把它接到EXTI外部中断在TE中断里触发DMA发送更精准。没有TE就用固定延时这个方案最简单也最实用。4.3 第一帧花屏或尾部残影之后画面正常现象上电后第一帧左上角出现杂色条纹或者画面末尾带了一小截上一帧的数据之后刷新就正常了。反过来也有第一帧正常刷新多帧以后开始花屏。原因SPI刚使能时SCK线上有残余电平驱动IC把第一个字节误当成命令。另一个原因是DMA传输完成后SPI-DR寄存器里还残留一个没完全移出的字节下一帧首字节被这个残字节顶掉造成整体位移。解决在每次DMA发送前先等待SPI总线空闲然后清OVR标志再重新配置DMA计数器。标准库里的写法如下void lcd_dma_send_safe(void) { // 等待SPI空闲 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET); // 清溢出标志 SPI_I2S_ClearFlag(SPI1, SPI_I2S_FLAG_OVR); // 重新配置DMA计数并启动 DMA_Cmd(DMA1_Channel3, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel3, sizeof(g_frame_buffer)); DMA_Cmd(DMA1_Channel3, ENABLE); }注意SPI_I2S_ClearFlag这个函数在标准外设库里的参数是SPI_I2S_FLAG_OVR如果你换到HAL库没有等价的封装函数需要直接操作SPI的SR寄存器清除OVR位。上电时序问题则要查RES引脚复位脉冲宽度不要低于10微秒并且复位结束到Sleep Out命令之间至少要留120ms。4.4 屏幕亮度不均靠近排线侧偏亮对侧偏暗现象屏幕点亮后亮度从排线侧到对侧有一个明显渐变的趋势或者屏幕底部颜色比顶部淡全屏刷纯色时尤其明显。原因这是供电问题不是驱动问题。圆屏的VCC和背光LED共用3.3V通路如果3.3V是从开发板的USB取电导线压降在背光电流的影响下屏幕内部的供电电压会随刷新过程波动GRAM写入电压一漂移亮度就不均匀。解决背光LED和显示逻辑供电分开。常见做法是背光LED由5V经限流电阻供电GND与MCU共地屏幕的VCC用一颗低压差LDO独立供电不要跟MCU的3.3V混接。判断方法很直观用万用表量屏VCC引脚对地电压如果刷新时电压波动幅度超过50mV基本就是供电问题。4.5 文字和图标边缘毛刺透明像素变成脏色现象画字符或小图标时笔画边缘发虚白字周围有一圈灰边黑字旁边出现彩色杂点。原因字模和位图数据按8位宽度存储直接写入时没有做位掩码相邻的8个像素被一次性写入透明位置的像素被覆盖成了上一帧的残留色或默认的背景色。圆屏驱动IC的窗口写入会把整个地址段刷新不做掩码就会把相邻像素也一并涂掉。解决绘制位图前先按mask_bit判断为1画前景色为0画背景色void lcd_draw_char_pixel(uint16_t x, uint16_t y, uint16_t fg, uint16_t bg, uint8_t mask_bit) { uint32_t idx (y * LCD_WIDTH x) * 2; uint16_t color mask_bit ? fg : bg; g_frame_buffer[idx] color 8; g_frame_buffer[idx 1] color 0xFF; }mask_bit来自字模数组的当前位比如字模数据字节0x81bit7对应左起第一个像素bit0对应第八个像素逐个判断再写帧缓冲。如果你的背景是复杂图片更稳妥的做法是先把要绘制区域的原始像素保存下来画完文字后再恢复这就是下一章讲的局部缓存技巧。5. 进阶局部刷新与帧率验证把0.99寸圆屏的潜力挖干净5.1 局部刷新只用DMA搬脏矩形全屏刷新的搬运量是25600字节对需要实时刷新表盘或进度条的场景来说不划算。局部刷新的思路是维护一个脏矩形每次只搬运内容变化的外接矩形区域。绘制函数在修改像素的同时更新脏矩形的边界刷新时设置窗口并DMA搬矩形区域的数据。我习惯设置一个阈值脏矩形面积小于帧缓冲面积1/8时走局部刷新否则直接全屏刷新因为极小尺寸的DMA搬运反而受SPI启动停止开销拖累性价比很低。5.2 帧率验证用GPIO翻转和逻辑分析仪测真实刷新周期不要凭感觉判断显示快慢用工具说话。最轻量的办法是在DMA传输完成中断里翻转一个GPIO用逻辑分析仪测量两个上升沿的间隔这个间隔就是一帧的刷新周期。实测一块80x160圆屏在18MHz SPI下全屏刷新周期约1.6ms去掉撕裂控制延时后实际帧率能到50fps以上。如果帧率低于30fps优先查DMA优先级和SPI分频这两处是最常见的瓶颈。中断处理函数内不要塞耗时操作翻转GPIO两句即可多余代码都会直接影响帧率测量结果。5.3 圆环进度条与指针表盘的绘制技巧圆屏最常见的应用是表盘和仪表绘制时不要全屏重绘只更新角度变化的那一段弧。我的做法是把整个圆环的背景预先画好维护起始角和结束角每次更新把新角度区间写入脏矩形。指针表盘则需要“先擦旧后画新”保存指针路径覆盖的原始像素到一个小数组下次画之前先恢复背景再计算新指针位置保存新背景最后标记脏矩形。80x160分辨率下这个小数组开256字节足够。这块0.99寸圆屏看着小坑一点都不少。但把裁剪和DMA理顺之后它的小尺寸圆表质感是方屏替代不了的。我自己的习惯是每拿到一块新圆屏先不写业务代码通电量电压、抓时序、遍历偏移确认基础参数后再往下走这个流程帮我省掉了很多后续调试时间。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号