资讯动态

STM32驱动ST7789V 8080并口屏:从GPIO模拟到FSMC硬件加速实战

发布时间:2026/9/28 21:52:06 来源:尧图企业网站定制
1. 项目缘起与整体设计思路1.1 为什么还要折腾8080并口屏现在做嵌入式显示SPI接口的屏几乎占了半壁江山接线少、驱动简单、Arduino库一抓一大把。但真到了量产项目或者对刷屏速度有要求的场景SPI那点带宽就开始捉襟见肘了。我去年做一个工业手持终端界面刷新率要求稳定在30fps以上SPI屏跑起来明显掉帧尤其是整屏刷新的时候肉眼可见地撕裂。后来换成8080并口方案同样的ST7789V控制器刷屏速度直接翻了五六倍这才把体验拉回来。8080并口全称是Intel 8080并行总线接口也有叫MCU接口的。它的核心思路是把显示数据通过一组并行的数据线一次性传给屏幕控制器而不是像SPI那样一位一位地挪。你可以把它理解成高速公路和乡间小路的区别——SPI是单车道8080并口是八车道甚至十六车道。代价就是占用的IO口多布线麻烦但对于STM32F103、F407这类IO资源还算充裕的芯片来说完全扛得住。ST7789V这颗驱动IC本身是支持多种接口的SPI、8080并口、RGB接口都能配。市面上常见的1.3寸、1.54寸、2.0寸小屏大部分默认走SPI但同款屏也有并口版本或者通过背面的电阻跳线切换模式。我手上这块是2.0寸240x320的IPS屏背面BS0、BS1、BS2三个跳线电阻决定了接口模式出厂默认是SPI需要手动改成8080并口。这一步后面会详细说很多人卡在这里以为屏坏了其实是模式没切对。1.2 整体方案选型与硬件框架这次我用的主控是STM32F103RCT664脚封装IO口够用。8080并口需要的数据线是8位或16位ST7789V支持8位、9位、16位、18位并口模式但最常用的是8位和16位。8位模式下一个像素的RGB565数据要分两次传16位模式一次搞定。我选的是16位模式数据线D0到D15全接上加上片选CS、写使能WR、读使能RD、数据命令切换DC、复位RESET一共21根线。听起来多但STM32的IO都是成组出现的PB0到PB15刚好一组接起来还算整齐。这里有个关键点8080并口的写时序分两种一种是8080-I型WR和RD分开另一种是8080-II型只有WR和RD合一的使能信号。ST7789V默认是8080-I型WR低电平有效RD低电平有效。我一开始没注意把WR和RD接反了结果屏幕死活不亮查了半天时序图才发现问题。所以接线之前一定要把数据手册的时序图翻出来确认清楚每个信号的有效电平。电源部分也不能马虎。ST7789V的逻辑电压是1.65V到3.3VIO口耐压也是3.3V所以直接跟STM32的3.3V IO对接没问题。但背光LED的驱动电压通常在2.8V到3.2V之间电流20mA左右最好用一个三极管或者MOS管做开关控制不要直接从IO口拉电流否则IO口容易烧。我用的是一颗AO3400 NMOS栅极接STM32的PB1源极接地漏极接背光负极背光正极串一个10欧姆限流电阻到3.3V。这样PWM调光也能做写个定时器输出PWM就行。1.3 软件架构的分层设计驱动代码我分成三层来写最底层是硬件抽象层负责GPIO初始化和8080并口的读写时序中间层是ST7789V的寄存器操作层封装了写命令、写数据、读数据这些基本动作最上层是图形库提供画点、画线、填充矩形、显示字符这些接口。这样分层的好处是换主控或者换屏幕尺寸的时候只需要改底层和中间层的少量参数上层图形库基本不用动。底层时序的实现有两种方式一种是纯GPIO模拟用宏定义直接操作寄存器优点是移植性强任何单片机都能用另一种是用FSMC或者FMC硬件外设把8080并口映射到外部存储器地址写数据就像写内存一样快。我两种都试过GPIO模拟在STM32F103上跑72MHz主频刷一屏240x320大概要80毫秒左右换成FSMC之后同样的屏刷一屏只要12毫秒差距非常明显。所以如果你的主控支持FSMC强烈建议用FSMC方案后面会详细讲配置方法。不过FSMC方案有个坑STM32F103的FSMC只支持NOR/PSRAM模式用来驱动8080并口屏的时候需要把屏幕当成一个16位的SRAM来操作。地址线A0用来区分命令和数据A0为0时写命令A0为1时写数据。这样写命令就是往基地址写写数据就是往基地址加2写。听起来简单但FSMC的时序参数配置很讲究后面会专门讲怎么算这些参数。2. 核心细节解析与实操要点2.1 ST7789V的8080并口时序详解ST7789V的8080并口写时序核心就三个信号CS片选、WR写使能、DC数据命令选择。写一个字节的过程是这样的先把数据放到D0到D15上然后拉低CS拉低WR保持一段时间再拉高WR最后拉高CS。DC信号在WR下降沿之前就要准备好DC为低表示接下来传的是命令DC为高表示传的是数据。具体的时间参数数据手册里给了几个关键值WR低电平最小脉宽是15纳秒WR高电平最小脉宽是15纳秒数据建立时间最小是10纳秒数据保持时间最小是10纳秒。这些参数在72MHz的STM32上用GPIO模拟的时候一个指令周期大约是14纳秒所以只要在WR拉低和拉高之间插入一两个空指令就能满足时序要求。我实测下来不加延时也能跑但为了稳定还是在WR操作前后各加了一个__NOP()。读时序稍微复杂一点因为ST7789V的读操作需要先发一个读命令然后才能读数据。读命令的时序跟写命令类似但读数据的时候RD信号起作用WR保持高电平。读数据的过程是拉低CS拉低RD等待一段时间从数据线上读取数据然后拉高RD拉高CS。ST7789V的读时序里RD低电平最小脉宽是40纳秒比写时序慢不少所以读操作要加更长的延时。这里有个容易忽略的点ST7789V的读操作返回的数据里第一个字节通常是无效的第二个字节才是真正的数据。这是很多驱动IC的通病手册里叫“dummy read”。我一开始读ID的时候读回来全是0后来加了两次读操作取第二次的结果才正确。所以读寄存器的时候一定要读两次丢掉第一次的结果。2.2 硬件接线与跳线配置接线之前先把屏幕背面的跳线电阻确认清楚。我手上这块2.0寸屏背面有三个0欧姆电阻标着BS0、BS1、BS2。根据ST7789V的数据手册BS0、BS1、BS2的组合决定了接口模式三个都接地是8080并口16位模式BS0接VDD、BS1和BS2接地是8080并口8位模式三个都接VDD是SPI模式。出厂默认是SPI所以要把BS0、BS1、BS2对应的电阻重新焊接改成8080并口16位模式。具体操作是用热风枪把原来的0欧姆电阻吹下来然后在BS0、BS1、BS2的位置分别焊上0欧姆电阻到地。如果没有0欧姆电阻用焊锡直接短接也行但要注意不要短路到旁边的引脚。我一开始用烙铁直接拖锡结果把旁边的电容烫掉了后来改用热风枪温度调到300度风速调到最低慢慢吹就安全多了。接线的时候数据线D0到D15接STM32的PB0到PB15顺序不能乱。CS接PB12WR接PB13RD接PB14DC接PB15RESET接PB11。背光控制接PB1。这里要注意PB3和PB4是JTAG引脚默认功能是JTDO和NJTRST如果要用作普通IO需要先关闭JTAG功能。我一开始没关结果PB3和PB4死活输出不了电平后来在代码里加了GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)才搞定。电源方面屏幕的VCC接3.3VGND接GND背光正极串10欧姆电阻接3.3V背光负极接AO3400的漏极。AO3400的栅极接PB1源极接GND。这里有个细节AO3400的栅极阈值电压是1.4V左右STM32的3.3V IO可以直接驱动但为了加快开关速度最好在栅极和源极之间并一个10k欧姆的下拉电阻防止浮空时误导通。2.3 初始化代码的关键寄存器配置ST7789V的初始化序列比较长但核心的寄存器就那么几个。首先是0x11命令退出睡眠模式发完这个命令之后要延时120毫秒等内部电源稳定。然后是0x3A命令设置像素格式参数是0x55表示RGB5650x66表示RGB666。我用的是RGB565所以写0x55。接下来是0xB2命令设置 porch 参数这个跟刷新率有关。参数是四个字节前两个是垂直方向的前后 porch后两个是水平方向的前后 porch。我用的值是0x0C、0x0C、0x00、0x33、0x33。这个值不是随便写的是根据屏幕的时序要求算出来的。具体算法是垂直方向的总行数是320加上前后porch再乘以刷新率要小于控制器的最大时钟频率。我算下来用这个参数刷新率大概在60Hz左右够用了。0xB7命令是设置 gate 控制参数0x35是默认值不用改。0xBB命令是设置VCOM参数0x19是默认值。0xC0到0xC4是电源控制相关的命令这些值跟屏幕的亮度、对比度有关一般用默认值就行但如果你觉得屏幕偏暗或者偏白可以微调这几个值。我试过把0xC0从0x2C改成0x3C屏幕亮度明显提升但功耗也上去了最后折中用了0x35。0xD0命令是设置电源控制参数0xA4、0xA1是默认值。0x21命令是打开反显0x20是关闭反显这个根据屏幕的实际显示效果来选。我手上这块屏不开反显的时候颜色是反的开了之后就正常了。0x13命令是设置显示模式参数0x00是正常模式0x60是部分模式一般用0x00。最后是0x29命令打开显示。发完这个命令屏幕就应该亮了。如果没亮先检查背光有没有开再检查复位时序对不对。ST7789V的复位要求是RESET拉低至少10微秒然后拉高再延时120毫秒。我一开始复位延时只给了10毫秒结果屏幕偶尔不亮后来改成120毫秒就稳定了。3. 实操过程与核心环节实现3.1 GPIO模拟8080并口的完整代码先贴底层时序的代码这是整个驱动的基础。我用的是宏定义直接操作寄存器这样速度最快。// 数据线宏定义 #define DATA_OUT(x) GPIOB-ODR (GPIOB-ODR 0x0000) | (x) #define DATA_IN() (GPIOB-IDR 0xFFFF) // 控制线宏定义 #define CS_LOW() GPIOB-BRR GPIO_Pin_12 #define CS_HIGH() GPIOB-BSRR GPIO_Pin_12 #define WR_LOW() GPIOB-BRR GPIO_Pin_13 #define WR_HIGH() GPIOB-BSRR GPIO_Pin_13 #define RD_LOW() GPIOB-BRR GPIO_Pin_14 #define RD_HIGH() GPIOB-BSRR GPIO_Pin_14 #define DC_LOW() GPIOB-BRR GPIO_Pin_15 #define DC_HIGH() GPIOB-BSRR GPIO_Pin_15 #define RESET_LOW() GPIOB-BRR GPIO_Pin_11 #define RESET_HIGH() GPIOB-BSRR GPIO_Pin_11 // 写命令 void LCD_WriteCmd(uint16_t cmd) { DC_LOW(); CS_LOW(); DATA_OUT(cmd); WR_LOW(); __NOP(); __NOP(); WR_HIGH(); CS_HIGH(); } // 写数据 void LCD_WriteData(uint16_t data) { DC_HIGH(); CS_LOW(); DATA_OUT(data); WR_LOW(); __NOP(); __NOP(); WR_HIGH(); CS_HIGH(); } // 读数据 uint16_t LCD_ReadData(void) { uint16_t data; DC_HIGH(); CS_LOW(); RD_LOW(); __NOP(); __NOP(); __NOP(); __NOP(); data DATA_IN(); RD_HIGH(); CS_HIGH(); return data; }这段代码里DATA_OUT宏先把PB0到PB15清零然后再或上要写的数据。这样做的目的是保证只影响低16位不影响PB端口的高16位如果有的话。STM32F103RCT6的PB端口只有低16位可用所以直接赋值也行但为了兼容其他芯片还是用掩码操作更安全。WR_LOW和WR_HIGH之间的两个__NOP()是为了满足WR低电平最小脉宽15纳秒的要求。72MHz下一个__NOP()是14纳秒两个就是28纳秒足够了。读数据的时候RD_LOW之后加了四个__NOP()因为RD低电平最小脉宽是40纳秒四个__NOP()是56纳秒也够了。3.2 FSMC硬件加速方案配置如果你的主控是STM32F103ZE或者F407这类带FSMC的芯片强烈建议用FSMC方案。配置步骤如下第一步打开FSMC时钟和GPIO时钟。FSMC的时钟在RCC_APB3ENR寄存器里GPIO的时钟在RCC_APB2ENR里。第二步配置GPIO为复用推挽输出。数据线D0到D15配置为复用推挽速度50MHz。控制线CS、WR、RD、DC也配置为复用推挽。这里要注意FSMC的CS信号对应FSMC_NE1到NE4WR对应FSMC_NWERD对应FSMC_NOEDC对应FSMC_A0到A25中的任意一根。我用的CS是FSMC_NE1对应PD7WR是FSMC_NWE对应PD5RD是FSMC_NOE对应PD4DC是FSMC_A0对应PD11。第三步配置FSMC的NOR/PSRAM控制器。在FSMC_BCR1寄存器里设置MBKEN为1MTYP为0SRAM模式MWID为116位宽度WREN为1EXTMOD为0。在FSMC_BTR1寄存器里设置ADDSET、ADDHLD、DATAST、CLKDIV等时序参数。时序参数的计算方法ADDSET是地址建立时间单位是HCLK周期。HCLK是72MHz一个周期是13.9纳秒。ST7789V的地址建立时间最小是0纳秒所以ADDSET可以设为0。DATAST是数据保持时间ST7789V要求最小10纳秒所以DATAST设为113.9纳秒就够了。ADDHLD是地址保持时间一般设为0。CLKDIV是时钟分频设为0表示不分频。配置好之后写命令就是往基地址写写数据就是往基地址加2写。基地址是0x60000000因为CS用的是NE1。代码大概长这样#define LCD_BASE ((uint32_t)0x60000000) #define LCD_CMD (*(volatile uint16_t *)LCD_BASE) #define LCD_DATA (*(volatile uint16_t *)(LCD_BASE 2)) void LCD_WriteCmd(uint16_t cmd) { LCD_CMD cmd; } void LCD_WriteData(uint16_t data) { LCD_DATA data; }用FSMC之后刷一屏240x320的时间从80毫秒降到了12毫秒提升非常明显。而且CPU占用率也低了因为FSMC是硬件自动完成时序的CPU只需要写数据就行。3.3 初始化序列的完整实现初始化序列我整理成一个数组每一项包含命令和参数个数然后循环发送。这样代码看起来清爽也方便修改。typedef struct { uint16_t cmd; uint8_t len; uint8_t para[16]; } LCD_InitSeq; const LCD_InitSeq st7789v_init_seq[] { {0x11, 0, {0}}, // 退出睡眠 {0x36, 1, {0x00}}, // 内存访问控制 {0x3A, 1, {0x55}}, // 像素格式RGB565 {0xB2, 5, {0x0C, 0x0C, 0x00, 0x33, 0x33}}, // porch设置 {0xB7, 1, {0x35}}, // gate控制 {0xBB, 1, {0x19}}, // VCOM设置 {0xC0, 1, {0x2C}}, // 电源控制1 {0xC2, 1, {0x01}}, // 电源控制2 {0xC3, 1, {0x12}}, // 电源控制3 {0xC4, 1, {0x20}}, // 电源控制4 {0xC6, 1, {0x0F}}, // VCOM偏移 {0xD0, 2, {0xA4, 0xA1}}, // 电源控制5 {0x21, 0, {0}}, // 反显开 {0x13, 0, {0}}, // 正常显示模式 {0x29, 0, {0}}, // 打开显示 }; void LCD_Init(void) { RESET_LOW(); delay_ms(10); RESET_HIGH(); delay_ms(120); for (int i 0; i sizeof(st7789v_init_seq)/sizeof(LCD_InitSeq); i) { LCD_WriteCmd(st7789v_init_seq[i].cmd); for (int j 0; j st7789v_init_seq[i].len; j) { LCD_WriteData(st7789v_init_seq[i].para[j]); } if (st7789v_init_seq[i].cmd 0x11) { delay_ms(120); } } }这里有个细节0x36命令是内存访问控制决定了屏幕的扫描方向和RGB顺序。参数0x00表示从左到右、从上到下扫描RGB顺序是正常的。如果你发现屏幕显示是镜像的或者颜色不对改这个参数就行。我试过0x60、0xA0、0xC0几个值分别对应不同的旋转方向最后根据实际安装方向选了0x00。0x21命令是反显开这个跟屏幕的物理特性有关。有些屏不开反显颜色是反的有些屏开了反而反。我手上这块屏不开反显的时候白色显示成黑色开了之后就正常了。所以这个命令要根据实际效果来定不能照搬。4. 常见问题与排查技巧实录4.1 屏幕不亮或白屏的排查思路屏幕不亮是最常见的问题排查顺序应该是先查电源再查复位再查背光最后查时序。电源方面用万用表量一下屏幕的VCC和GND之间是不是3.3V背光正极是不是有2.8V以上。如果VCC正常但背光不亮检查背光控制三极管的栅极有没有电平变化。我遇到过背光三极管焊反的情况源极和漏极搞反了结果背光一直不亮换了方向就好了。复位方面用示波器看一下RESET引脚上电后应该是低电平持续至少10微秒然后拉高。如果RESET一直低检查STM32的复位代码有没有执行。我一开始把RESET接在了PB11但PB11默认是普通IO输出低电平没问题输出高电平也没问题但复位延时不够后来改成120毫秒就好了。背光方面如果背光亮了但屏幕是白的说明背光正常但显示数据没写进去。这时候要查CS、WR、DC三个信号。用示波器看WR引脚写命令的时候应该有一个下降沿。如果没有检查GPIO初始化对不对时钟有没有开。我遇到过GPIO时钟没开的情况代码里写了GPIO_Init但忘了开RCC_APB2ENR的时钟结果引脚一直是高阻态示波器上看不到任何波形。时序方面如果WR有波形但屏幕还是白的检查数据线上的数据对不对。用逻辑分析仪抓一下D0到D15写0x11命令的时候数据线上应该是0x0011。如果数据不对检查DATA_OUT宏有没有写错或者数据线接错了顺序。我一开始把D0和D15接反了结果写进去的数据全是乱的屏幕显示花屏后来重新接线就好了。4.2 显示花屏或颜色异常的解决方法花屏通常有两个原因一是数据线接触不良二是时序参数不对。数据线接触不良的话用万用表量一下每根线的通断特别是杜邦线连接的时候容易松动。我建议用排线焊接比杜邦线可靠得多。时序参数不对的话重点查WR的低电平脉宽。如果WR低电平时间太短数据还没稳定就被锁存了就会花屏。用示波器量一下WR的低电平时间至少要15纳秒。72MHz下两个__NOP()是28纳秒够了。但如果你的主频更高比如168MHz一个__NOP()只有6纳秒两个才12纳秒就不够了需要加更多__NOP()。颜色异常的话先查0x3A命令的参数。0x55是RGB5650x66是RGB666。如果你写的是0x66但数据只给了16位颜色就会错乱。再查0x36命令的参数RGB顺序对不对。我遇到过红色和蓝色反了的情况把0x36的参数从0x00改成0x08就好了因为0x08表示BGR顺序。还有一个容易忽略的点ST7789V的显存是240x320但有些屏的实际显示区域不是从(0,0)开始的而是有一个偏移。比如1.3寸的240x240屏偏移是(0,80)1.54寸的240x240屏偏移是(0,80)或(80,0)。如果初始化之后显示内容偏了查一下数据手册里的“Display Data RAM”章节找到正确的偏移值然后在设置行列地址的时候加上偏移。4.3 刷屏速度慢的优化技巧刷屏速度慢首先要确认用的是GPIO模拟还是FSMC。如果是GPIO模拟刷一屏80毫秒是正常的想提速就换FSMC。如果是FSMC刷一屏12毫秒还嫌慢的话可以试试以下几个优化第一用DMA传输。FSMC支持DMA可以把显存数据直接搬到FSMC的数据寄存器CPU不用参与。这样刷屏速度能再提升30%左右。配置方法是打开DMA2的通道5源地址是显存数组目标地址是LCD_DATA的地址传输长度是240x320传输宽度是16位。第二减少不必要的数据传输。比如只刷新变化区域而不是整屏刷新。这个需要在上层图形库做脏矩形管理实现起来稍微复杂但效果很明显。我做一个仪表盘界面只刷新指针区域刷屏时间从12毫秒降到了2毫秒。第三提高FSMC的时钟频率。STM32F103的FSMC时钟最高是72MHz但可以通过设置CLKDIV来分频。如果CLKDIV设为0就是不分频72MHz。如果设为1就是36MHz。我实测下来72MHz下FSMC写一个16位数据需要2个HCLK周期也就是27.8纳秒。如果屏幕的时序允许可以把DATAST设小一点比如设为0这样写一个数据只要1个周期13.9纳秒。但ST7789V要求数据保持时间最小10纳秒13.9纳秒刚好满足再小就不行了。4.4 常见问题速查表问题现象可能原因排查方法解决方法屏幕完全不亮电源没接好万用表量VCC和GND重新接线确保3.3V背光不亮背光三极管焊反量三极管各脚电压调换源极和漏极白屏无显示复位延时不够示波器看RESET波形延时改成120毫秒白屏无显示WR无波形示波器看WR引脚检查GPIO时钟和初始化花屏数据线接触不良万用表量通断改用排线焊接花屏WR脉宽太短示波器量低电平时间增加__NOP()数量颜色反了0x21命令不对对比开和关的效果切换0x21和0x20红蓝反了0x36参数不对试0x00和0x08改成BGR顺序显示偏移显存偏移没设查数据手册偏移值设置行列地址时加偏移刷屏慢用GPIO模拟看代码用的是哪种改用FSMC方案这个表是我踩坑之后整理的基本上覆盖了80%的常见问题。剩下的20%可能是屏幕本身坏了或者主控的FSMC配置有误那就需要更深入的排查了。4.5 实操心得与避坑建议第一个心得接线之前一定要把屏幕背面的跳线电阻改对。我见过太多人拿着SPI模式的屏死活调不通8080并口最后发现是跳线没改。改跳线的时候用热风枪比烙铁安全温度300度风速最低慢慢吹不要急。第二个心得初始化序列里的延时不能省。0x11命令之后的120毫秒延时是等内部电源稳定的省了的话屏幕可能偶尔不亮。复位之后的120毫秒延时也一样省了的话屏幕可能初始化失败。我一开始为了加快启动速度把延时改成了10毫秒结果10块屏里有2块不亮后来改回120毫秒就全好了。第三个心得FSMC的时序参数要留余量。虽然理论上DATAST设为1就够了但实际布线有寄生电容信号有上升下降时间所以最好设为2或者3。我一开始设为1刷屏偶尔有噪点改成3之后就干净了。ADDSET也一样设为0理论上够但实际用1更稳。第四个心得读操作一定要读两次。ST7789V的读时序里第一次读返回的是无效数据第二次才是真数据。这个在数据手册里写得很清楚但很多人不看读回来全是0还以为屏幕坏了。我一开始也踩了这个坑后来加了两次读操作取第二次的结果就对了。第五个心得背光控制最好用PWM。虽然直接给高电平也能亮但亮度不可调而且长时间满亮度背光LED容易老化。用PWM调光频率设在1kHz以上人眼就看不到闪烁了。我用的TIM3的通道3频率2kHz占空比从0到100可调效果很好。5. 图形库的移植与显示效果优化5.1 画点函数的实现与优化画点是图形库的基础所有图形都是画点堆出来的。画点的核心是设置行列地址然后写颜色数据。ST7789V的0x2A命令设置列地址0x2B命令设置行地址0x2C命令开始写显存。void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color) { LCD_WriteCmd(0x2A); LCD_WriteData(x 8); LCD_WriteData(x 0xFF); LCD_WriteCmd(0x2B); LCD_WriteData(y 8); LCD_WriteData(y 0xFF); LCD_WriteCmd(0x2C); LCD_WriteData(color); }这个函数每次画点都要发三次命令效率很低。优化方法是把行列地址设置和写数据分开批量画点的时候只设置一次地址然后连续写数据。比如填充矩形的时候设置好起始地址然后循环写颜色数据不用每次都设地址。void LCD_Fill(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) { LCD_WriteCmd(0x2A); LCD_WriteData(x1 8); LCD_WriteData(x1 0xFF); LCD_WriteData(x2 8); LCD_WriteData(x2 0xFF); LCD_WriteCmd(0x2B); LCD_WriteData(y1 8); LCD_WriteData(y1 0xFF); LCD_WriteData(y2 8); LCD_WriteData(y2 0xFF); LCD_WriteCmd(0x2C); for (uint32_t i 0; i (x2 - x1 1) * (y2 - y1 1); i) { LCD_WriteData(color); } }这样填充一个矩形只需要设置一次地址然后连续写数据速度比逐个画点快好几倍。我实测填充整屏240x320用这个函数只要12毫秒跟FSMC的极限速度差不多。5.2 字符显示与字库选择字符显示有两种方案一种是内置ASCII字库一种是外挂字库芯片。内置ASCII字库简单把8x16或者16x16的点阵数据放在代码里显示的时候查表就行。外挂字库芯片支持中文和多种字体但需要额外的SPI接口和字库芯片成本高一些。我用的是内置ASCII字库8x16的点阵每个字符16个字节。显示的时候先设置字符的起始地址然后逐行写数据每个字节的每一位对应一个像素。代码大概长这样void LCD_ShowChar(uint16_t x, uint16_t y, char ch, uint16_t color, uint16_t bg) { uint8_t *p ascii_8x16[(ch - ) * 16]; LCD_WriteCmd(0x2A); LCD_WriteData(x 8); LCD_WriteData(x 0xFF); LCD_WriteData((x 7) 8); LCD_WriteData((x 7) 0xFF); LCD_WriteCmd(0x2B); LCD_WriteData(y 8); LCD_WriteData(y 0xFF); LCD_WriteData((y 15) 8); LCD_WriteData((y 15) 0xFF); LCD_WriteCmd(0x2C); for (int i 0; i 16; i) { for (int j 0; j 8; j) { if (p[i] (0x80 j)) { LCD_WriteData(color); } else { LCD_WriteData(bg); } } } }这个函数显示一个8x16的字符需要写128个像素数据。如果字符很多比如显示一整屏文字速度会慢一些。优化方法是把字符点阵先转换成RGB565的数组然后用DMA传输这样CPU不用参与速度能快不少。5.3 显示效果调优与实测数据显示效果调优主要从三个方面入手亮度、对比度、刷新率。亮度方面背光PWM的占空比决定了亮度。我实测下来占空比50%的时候屏幕亮度大概是200cd/m²室内用足够了。占空比100%的时候亮度能到400cd/m²室外阳光下也能看清。但亮度越高功耗越大背光LED的寿命也越短。所以一般室内用50%就够了室外用80%到100%。对比度方面ST7789V的0xC0命令控制VCOM0xC4命令控制VCOM偏移。我试过把0xC0从0x2C改成0x35对比度明显提升黑色更黑白色更白。但改得太大颜色会失真所以要在对比度和色彩准确性之间折中。我最后用了0x30效果比较均衡。刷新率方面0xB2命令的porch参数决定了刷新率。我算过用0x0C、0x0C、0x00、0x33、0x33这组参数刷新率大概是62Hz。如果把垂直porch改小比如改成0x08、0x08刷新率能到70Hz但屏幕边缘可能会有闪烁。所以刷新率不是越高越好要跟屏幕的物理特性匹配。实测数据用FSMC方案刷一屏240x320的纯色耗时12毫秒刷新率83fps。刷一屏彩色渐变耗时14毫秒刷新率71fps。显示一个8x16的字符耗时0.2毫秒。填充一个100x100的矩形耗时1.5毫秒。这些数据是在STM32F103RCT672MHz主频FSMC不分频的条件下测的。6. 项目总结与扩展思路6.1 关键经验回顾这个项目从选型到调通前前后后花了大概两周时间踩了不少坑也积累了一些经验。最关键的经验有三条第一条硬件跳线一定要先确认。SPI模式和8080并口模式的跳线不一样不改跳线代码写得再对也没用。我一开始就是没改跳线调了三天没调通后来才发现是模式不对。第二条时序参数要留余量。数据手册给的是最小值实际用的时候要留20%到30%的余量特别是WR的低电平脉宽和FSMC的DATAST参数。留余量可能会牺牲一点速度但稳定性会好很多。第三条读操作要读两次。ST7789V的读时序里第一次读是dummy read第二次才是真数据。这个坑我踩过读ID读回来全是0查了半天手册才发现。6.2 后续可以扩展的方向这个驱动调通之后可以往几个方向扩展第一个方向是加触摸功能。很多8080并口屏都带触摸面板通常是电阻屏或者电容屏。电阻屏需要额外的ADC通道电容屏需要I2C接口。加了触摸之后就可以做交互界面了。第二个方向是加DMA传输。现在刷屏还是CPU参与写数据如果用DMACPU可以腾出来做其他事情比如跑UI逻辑或者通信协议。DMA配置不难关键是源地址和目标地址要对传输长度要算准。第三个方向是加硬件旋转。ST7789V支持0度、90度、180度、270度四种旋转方向通过0x36命令的bit5到bit7控制。加了旋转之后屏幕可以横屏也可以竖屏适配不同的安装方向。第四个方向是加低功耗管理。ST7789V支持睡眠模式0x10命令进入睡眠0x11命令退出睡眠。在电池供电的设备上不用屏幕的时候进入睡眠模式能省不少电。我实测下来睡眠模式下电流从20毫安降到了1毫安以下。6.3 给后来者的建议如果你也在调ST7789V的8080并口屏我的建议是先别急着写代码先把数据手册的时序图看三遍把硬件接线图看三遍把跳线配置看三遍。这三样确认无误了再开始写代码。写代码的时候先写最底层的写命令和写数据函数用示波器确认波形对了再往上写初始化序列。初始化序列调通了屏幕能亮了再写图形库。调试的时候示波器和逻辑分析仪是必备的。没有示波器你只能猜有了示波器你能看到实际的波形一眼就能看出问题在哪。我用的是一台二手的DS1054Z四通道100MHz带宽调这个屏足够了。逻辑分析仪用的是Saleae的8通道版本抓时序很方便。最后不要怕慢不要怕麻烦。8080并口屏的接线确实比SPI多但调通之后那个刷屏速度是SPI比不了的。我做完这个项目之后再回头看SPI屏感觉就像从高速公路回到了乡间小路回不去了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑