基于STM32H743的RGB屏驱动:LTDC+DMA2D点亮800x480电容触摸屏
发布时间:2026/9/16 14:26:51 锦皓数字建站

简介针对意法半导体高性能单片机STM32H743及H7系列开发者解决四点三英寸、八百乘四百八十分辨率的薄膜晶体管液晶显示屏与电容触摸屏的驱动问题是一套完整基于硬件抽象层库的工程方案覆盖显示控制器的时序初始化、图层配置、背光调节及触摸中断处理与坐标解析。压缩包共两百三十一个文件核心为一百一十九个C语言头文件和一百零七个C源文件提供底层外设驱动及显示触摸相关接口另含工程配置文件、可烧录的十六进制固件与汇编启动文件方便直接编译运行或二次开发。目前已有约两百人学习下载。驱动代码之外资料还附带官方参考手册与寄存器说明便于理解像素时钟、行场同步、触摸控制器读取流程等细节。无论是快速移植到项目还是学习H7系列图形显示与人机交互设计这一工程都能有效缩短开发周期是嵌入式工程师值得收藏的实用资源。1. 一块800x480的4.3寸电容屏为什么要用STM32H743的HAL库来点把一块4.3寸、800x480的RGB接口TFT LCD点亮第一道坎不是背光不是触摸而是像素数据怎么从单片机里持续不断送到屏上。800x48060Hz要求像素时钟跑到30MHz级别按RGB565算每秒钟要搬66MB显存这已经不是GPIO模拟或者串行屏能轻松应付的工程量。STM32H743这类H7芯片把LTDC和DMA2D直接做成片内外设LTDC负责输出面板需要的行同步、场同步和像素数据DMA2D负责成块刷显存CPU只需要改显存内容整套方案天然配套HAL库。加上电容触摸IC基本都走I2C接口H7的HAL驱动库把I2C、DMA、EXTI这些外设全部包好了后面要上LVGL也顺理成章。下面按我实际调屏的顺序把H743点这块屏整条链路讲完时序参数、CubeMX配置、HAL驱动代码、触摸坐标映射最后是花屏和触摸漂移的排错手段。2. 拆开H743的点屏链路LTDC时序、DMA2D加速与I2C触摸点屏和点LED纯GPIO完全不同RGB接口屏没有控制器自己管理刷新单片机必须按行场时序连续把像素喂给面板。H743里负责这件活的是LTDC外设它把CPU从逐像素输出里解放出来只从显存里取数按固定节奏发到屏幕。2.1 LTDC从引脚到像素时钟RGB屏为什么不吃“SPI屏”那一套SPI或8080并口屏自带GRAMMCU只管写指令和数据刷屏速度受总线限制而RGB屏是直驱面板LTDC要连续输出数据、行同步和场同步信号没有“写寄存器的操作”这一步。H743的LTDC引脚包括CLK、HSYNC、VSYNC、DE以及R/G/B三组数据线RGB888时是24根只用RGB565时可只接其中的高16位。屏幕能不能稳定显示取决于LTDC的时序参数和像素时钟是否匹配面板规格。LTDC参数单位典型值4.3寸800x480说明Hsync像素时钟48水平同步脉宽HBP像素时钟88行同步结束到有效数据开始HFP像素时钟40有效数据结束到下一个行同步Vsync行3垂直同步脉宽VBP行32场同步结束到有效数据开始VFP行13有效数据结束到下一个场同步Pixel ClockMHz33.3LTDC输出像素时钟这些值是常见4.3寸800x480模块的参数不同模组厂商会有差异拿到屏第一件事是找规格书核对。纯有效像素算下来800x480x6023.04MHz加上两个消隐区后整体需要30MHz以上所以典型Pixel Clock落在33.3MHz附近。H7的时钟树里专门有一个PLL3用于给LTDC供时钟后文CubeMX配置就是在处理这件事。2.2 DMA2D的三个常用模式以及为什么刷屏不能靠CPU memsetLTDC从显存取数、CPU写显存这两条路径在H7里都会占用总线带宽。如果刷一帧800x480的数据用CPU逐点循环写DMA2D用R2M模式只需要一条HAL调用差距不是百分比是数量级。DMA2D是2D DMA控制器支持填充、拷贝、格式转换和混合模式枚举值典型用途内存到内存DMA2D_M2M整块显存拷贝不做格式转换带格式转换的拷贝DMA2D_M2M_PFCJPEG/摄像头输出ARGB8888转换成RGB565再贴图双图混合DMA2D_M2M_BLEND两层窗口叠加、半透明图标寄存器到内存DMA2D_R2M整屏或矩形区域填充纯色最常用的清屏方式HAL库把DMA2D封装成HAL_DMA2D_Start和HAL_DMA2D_PollingTransfer两个核心函数传参时指定源地址、目标地址、宽高即可。刷屏代码里最忌讳的是用CPU循环memset一个颜色值特别是在开了D-Cache之后memset写进去的数据未必立刻到达内存LTDC取到旧数据的概率很高。后面排错章节会回来讲Cache这里先记住一句话凡是整块刷屏操作走DMA2D不要走CPU。2.3 电容触摸不是ADC扫描GT911这类IC的I2C寄存器方案电阻屏才需要MCU自己去采集ADC电压推算位置电容屏的感应电极和算法全部集成在触摸IC内部IC把坐标算好放在寄存器里MCU通过I2C读出来即可。4.3寸800x480这个尺寸最常遇到的是GT911和FT5x06两者都是I2C接口带INT和RST两根控制脚。GT911的7位I2C地址由RST与INT的上电时序决定常见取值是0x5D或0x14所以初始化代码里要先读芯片ID确认地址不能写死。GT911内部寄存器分三大块配置区、坐标区、控制区。坐标区起始地址通常是0x814E第一个触摸点的X坐标在0x8150-0x8151Y坐标在0x8152-0x8153缓冲区状态在第0x814E字节的bit7。读坐标之前先读状态位读完要写0清标志这套流程在HAL库下用两个I2C收发函数就能实现。INT脚接STM32的EXTI触摸按下时触发中断主循环里也可以在中断标志置位后再去读I2C节省不少总线时间。3. 用CubeMX配好时钟、LTDC引脚和GT911触摸再让HAL生成工程H7的HAL库和标准库最大的区别是外设初始化被CubeMX接管LTDC这种寄存器成片的外设手写初始化容易漏掉时钟使能或极性配置。用CubeMX生成工程后HAL库已经帮你把外设句柄、GPIO复用和中断向量都铺好这也是标题里“HAL库驱动”最实际的落地方式。3.1 时钟树从PLL3Q算出33.3MHz像素时钟在CubeMX里选择STM32H743芯片后先配置RCC的HSE外部晶振再把SYS的Debug选为Serial Wire方便后面用ST-LINK调试。图形外设的时钟路径是HSE进PLL3PLL3分频得到PLL3QPLL3Q直接作为LTDC的像素时钟。操作路径是Clock Configuration页面找到LTDC的Clock Mux把时钟源选成PLL3Q然后调整PLL3的倍频和分频系数让LTDC时钟值显示为33.3MHz左右。这一步最常见的错误是偷懒保留CubeMX默认的PLL3配置默认值通常只有几十MHz或者某个分频系数凑不出目标值。像素时钟和屏规格偏差过大的时候屏不会完全黑掉而是出现整屏斜条纹或者右边明显发暗看起来像信号线接触不良。我的习惯是把CubeMX生成的SystemClock_Config()单独抽出来用串口打印RCC-PLL3DIVR寄存器的实际分频结果先确认PLL3Q真的落在目标频率附近再继续调显示部分。3.2 LTDC时序参数和图层配置800x480必须能整屏显示在Connectivity分类下使能LTDC外设把前面表格里的Hsync、HBP、HFP、Vsync、VBP、VFP以及Pixel Clock逐一填入LTDC配置面板宽和高分别填800和480。Layer窗口配置里启用Layer0窗口位置X0、Y0宽800、高480像素格式选RGB565。CubeMX会把LTDC的引脚自动分配到对应的复用功能上分配不了时会在引脚冲突提示里列出来常见原因是芯片封装不够大引脚被其他外设占用。RGB565和RGB888的引脚接法要提前确定800x480分辨率用RGB565时帧缓冲区是800x480x2字节768KB内部内存连续放不下用RGB888时翻倍到1.5MB对显存压力更大。如果屏幕模组本身是24位接口H743只接16根数据线屏上剩余的RGB低bit位建议直接接地不要悬空否则部分屏会出现整体偏色或者色彩断层。3.3 触摸接口的引脚规划INT要接到EXTI、RST要有上电时序GT911触摸部分的CubeMX配置不像LTDC那样一键完成需要单独规划几根引脚功能引脚类型CubeMX配置T_SCLI2C时钟I2C1 SCL开漏上拉T_SDAI2C数据I2C1 SDA开漏上拉T_INT触摸中断GPIO外部中断下降沿触发T_RST触摸复位GPIO推挽输出I2C1在CubeMX里选Fast Mode速率设400kHz。触摸IC的I2C引脚要求外部上拉电阻很多屏幕模组已经把上拉电阻集成在排线上直接接MCU就能用如果自己在面包板上飞线需要加2.2k-4.7k上拉。GT911上电时要求RST先拉低再拉高INT的电平决定I2C从机地址屏幕模组的PCB通常已经固定好这个时序。MCU这边要做的只是在初始化GT911之前给出一个不小于10ms的复位脉冲再延时100ms等IC内部固件启动完成之后才能正常读到寄存器值。3.4 生成代码后要手工调整的三处CubeMX生成工程后LTDC和I2C的初始化代码已经自动加入但有几处必须手动处理。第一处在main.h或lcd驱动头文件里定义屏幕和显存相关的宏避免在初始化代码里到处写魔法数字#define LCD_WIDTH 800 #define LCD_HEIGHT 480 #define LCD_PIX_FMT LTDC_PIXEL_FORMAT_RGB565 #define LCD_FB_ADDR 0xD0000000 /* FMC SDRAM地址视核心板内存映射调整 */ #define LCD_FB_SIZE (LCD_WIDTH * LCD_HEIGHT * 2) #define TOUCH_RES_X 800 #define TOUCH_RES_Y 480第二处是启动顺序如果板子上用FMC外挂了SDRAM并且要把显存放在SDRAM里必须先调用FMC的初始化CubeMX会生成MX_FMC_Init再调用MX_LTDC_Init最后调用触摸初始化。顺序反了的话LTDC配置Layer时会直接访问一个还没准备好的SDRAM地址HAL_LTDC_ConfigLayer返回HAL_ERROR屏就是黑的。第三处是DMA2D时钟使能。CubeMX生成的外设初始化代码不一定帮你打开DMA2D的时钟需要在stm32h7xx_hal_msp.c里找到对应的HAL_DMA2D_MspInit确认这一行存在__HAL_RCC_DMA2D_CLK_ENABLE();缺少这行的话所有HAL_DMA2D_Start调用都会卡在超时上并且不会报编译错误是最容易忽略的一个坑。4. 点亮屏幕并读回坐标LTDC图层、DMA2D清屏与GT911驱动的落地代码CubeMX生成的是外设初始化骨架真正能跑起来的显示驱动还要补上图层配置、清屏函数、触摸读取和坐标转换。这一章给的代码顺序就是调试顺序先把屏点亮再验证清屏最后读触摸。4.1 初始化顺序先背光、再LTDC、最后触摸背光引脚通常是一根GPIO控制有的模块还支持PWM调光。先把背光点亮再初始化LTDC不然屏会从开屏瞬间就进入“黑屏但有数据”的状态很难判断是时序问题还是背光问题。void lcd_init(void) { HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET); /* 背光先开 */ MX_LTDC_Init(); /* CubeMX生成的LTDC初始化 */ lcd_layer_config(); /* 配置Layer0窗口和颜色格式 */ gt911_init(); /* 触摸IC复位与ID校验 */ }lcd_layer_config里面调用的是HAL_LTDC_ConfigLayerLayerIndex传0窗口起点是0宽高填LCD_WIDTH和LCD_HEIGHT像素格式和LTDC初始化时保持一致。Layer配置完成后显存地址就开始被LTDC周期读取这时候哪怕不画任何内容屏上也会显示显存里的随机值或者上一帧残留。4.2 DMA2D清屏和区域填充R2M模式一条命令整屏变色清屏是验证LTDC链路是否正常的第一条测试命令。DMA2D的R2M模式下源地址被忽略第一个参数直接传颜色值。void lcd_fill_color(uint16_t color) { HAL_DMA2D_Start(hdma2d, color, LCD_FB_ADDR, LCD_WIDTH, LCD_HEIGHT); HAL_DMA2D_PollingTransfer(hdma2d, 10); }这段代码里HAL_DMA2D_Start的第一个参数在R2M模式下不是地址而是RGB565格式的颜色值第二个参数才是目标显存地址。宽高传800和480DMA2D会按顺序生成一整帧数据填满显存。PollingTransfer后面的10是超时毫秒数RGB565整屏填充在H743 480MHz下一般几毫秒内完成超时10ms足够。如果传地址而不是颜色值DMA2D会把那块地址里的内容当作填充色结果大概率是花屏。矩形区域填充比整屏稍微复杂难点在行偏移。显存里800x480是一行1600字节连续排列的画一个宽200高100的矩形时每画完一行要跳过600个像素这个值通过DMA2D的OutputOffset设置void lcd_fill_rect(uint16_t color, uint16_t x, uint16_t y, uint16_t w, uint16_t h) { hdma2d.Init.Mode DMA2D_R2M; hdma2d.Init.ColorMode DMA2D_RGB565; hdma2d.Init.OutputOffset LCD_WIDTH - w; /* 行末要跳过的像素数 */ HAL_DMA2D_Init(hdma2d); uint32_t dst LCD_FB_ADDR 2 * (y * LCD_WIDTH x); HAL_DMA2D_Start(hdma2d, color, dst, w, h); HAL_DMA2D_PollingTransfer(hdma2d, 10); }输出偏移的单位是像素RGB565模式下DMA2D硬件会自己乘2换算成字节。目标地址必须按像素偏移后乘2写漏这个乘2的话矩形矩形会显示成斜向条纹。这个函数可以直接当作后面LVGL刷新回调的底层实现等显示和触摸都调通后再考虑移植LVGL9.x系列就会顺手很多。4.3 GT911驱动读坐标前的三个寄存器GT911的硬件复位完成后先用I2C读取芯片ID寄存器0x9280和0x9281确认值是0x91和0x11。如果读不到ID先尝试换I2C地址0x14再读一次两个地址都试过仍失败回到硬件复位时序查RST和INT的先后关系。static uint8_t gt911_i2c_addr 0x5D; /* 7位地址测试时可能改为0x14 */ uint8_t gt911_read_touch(uint16_t *x, uint16_t *y) { uint8_t reg[2] {0x81, 0x4E}; /* 要读的寄存器地址0x814E */ uint8_t data[5] {0}; HAL_I2C_Master_Transmit(hi2c1, gt911_i2c_addr 1, reg, 2, 10); HAL_I2C_Master_Receive(hi2c1, gt911_i2c_addr 1, data, 5, 10); if ((data[0] 0x80) 0) /* bit7为1才表示坐标数据有效 */ return 0; if ((data[0] 0x0F) 0) /* 低4位是触摸点数0则抬起 */ return 0; *x (uint16_t)((data[2] 8) | data[1]); *y (uint16_t)((data[4] 8) | data[3]); reg[1] 0x4E; /* 清buffer状态标志 */ uint8_t clear 0; HAL_I2C_Master_Transmit(hi2c1, gt911_i2c_addr 1, reg, 2, 10); HAL_I2C_Master_Transmit(hi2c1, gt911_i2c_addr 1, clear, 1, 10); return 1; }从0x814E开始一次性读5个字节第0字节是高字节在前实际GT911坐标寄存器是小端排列0x8150是X低字节0x8151是X高字节所以组坐标时要把读到的高字节左移8位再或上低字节。这个顺序如果写反表现为触摸点跟随手指移动但坐标值乱跳特别是越过屏幕中线时数值突变。清除状态标志那一步很容易漏漏了之后下一次读取还是同一包旧坐标触摸会表现成“粘住不动”。4.4 触摸坐标转屏幕坐标把x/y映射到800x480GT911返回的坐标是相对触摸传感面的原始坐标触摸屏厂商要么返回0-1023这类固定范围要么把坐标范围配置成和屏对齐。最稳妥的做法是先按照触摸IC的满量程归一化到LCD像素再根据屏幕安装方向做0/90/180/270度的映射。void touch_map(uint16_t *px, uint16_t *py, uint8_t rotate) { uint32_t tx *px; uint32_t ty *py; uint32_t x tx * LCD_WIDTH / TOUCH_RES_X; uint32_t y ty * LCD_HEIGHT / TOUCH_RES_Y; switch (rotate) { case 0: *px x; *py y; break; case 1: /* 触摸面逆时针旋转90度 */ *px y; *py LCD_WIDTH - 1 - x; break; case 2: *px LCD_WIDTH - 1 - x; *py LCD_HEIGHT - 1 - y; break; case 3: *px LCD_HEIGHT - 1 - y; *py x; break; } }注意case 1和case 3里旋转后目标的宽度和高度是交换的所以一个case里同时出现LCD_WIDTH和LCD_HEIGHT。映射函数和rotate具体对应哪个物理方向跟屏幕模组的触摸排线贴装方向有关没有统一标准。调试时在屏幕四个角和中心画十字用一根手指点上去观察映射结果如果某个case下点左上角却响应在右下角换成相邻case再试。这一步骤最好放在驱动验证阶段完成不要拖到UI层否则LVGL里触摸方向错乱会非常难排查。4.5 帧缓冲放哪里内部SRAM不够768KB时的做法RGB565格式下800x480一帧需要768KB连续内存。H743内部SRAM总量约1MB但被分成多块不连续的物理区域AXI SRAM 512KB是最大的一块SRAM1和SRAM2加起来256KB中间有地址空洞拼不出768KB的连续区域。这就是为什么市面上绝大多数带RGB屏的H743开发板都要在FMC总线上挂一片SDRAM。显存地址放在SDRAM基址附近初始化顺序保证FMC先于LTDC后面CPU和DMA2D都直接操作这片区域。如果手上的板子没有外扩SDRAM也有一个可以点亮屏幕的偏门做法把Layer0窗口拆成左右两半左半屏用AXI SRAM右半屏用SRAM12区域的连续地址。LTDC支持多个图层每个图层可以设置独立的窗口位置和显存地址。左半图层窗口设为X0、Y0、宽度400、高度480显存地址指向0x24000000右半图层窗口设为X400、宽度400显存地址指向0x30000000两个图层同时叠加显示视觉上就是一幅完整的800x480图像。DMA2D填充时也要按两半分别操作左边填左半屏矩形右边填右半屏矩形。这个方案的缺点是左右半屏的绘制边界要自己维护跑简单刷屏测试没问题但跑LVGL动画和复杂控件时会遇到跨半屏绘图的不同步问题。我的建议是能上SDRAM就上SDRAM双图层拆分只能作为验证屏幕参数时的应急手段不适合作为产品方案长期使用。5. H743花屏与触摸漂移排错显存、Cache与测试工具收尾5.1 点屏后先用三色和十字线做功能验证初始化完成后不要急着烧UI代码先用三色整屏测试确认LTDC链路完整lcd_fill_color(0xF800); /* 纯红 */ HAL_Delay(300); lcd_fill_color(0x07E0); /* 纯绿 */ HAL_Delay(300); lcd_fill_color(0x001F); /* 纯蓝 */三色都能正常显示说明像素时钟、行场时序、引脚映射和数据位宽都没问题。然后写一个极简触摸画点函数把GT911读到的坐标映射到屏幕上按下手指时画白点观察轨迹是否跟随手指移动。这套验证流程跑通后续应用层开发基本不会再碰到硬件级的怪异现象。5.2 显存与CacheH743上花屏率最高的源头H7内核带D-Cache显存所在的SDRAM区域默认可能被配置成Cacheable。此时CPU往显存写像素数据先留在Cache里LTDC直接从DRAM读到的却是旧数据表现成“图像部分区域不刷新”或“边界有残留拖影”。GPIO模拟和低性能MCU上不会遇到这类问题但H743几乎必踩。处理办法有两种把显存区域配置成Non-cacheable或者每帧画完后手动执行SCB_CleanDCache_by_Addr。对800x480这种大尺寸显存手动Clean需要遍历整块区域耗时明显一般推荐把显存所在的MPU区域直接配置为Non-cacheable牺牲一点CPU画点速度换来LTDC读数据的确定性。触摸的寄存器变量放在普通SRAM保持默认Cacheable即可。症状常见原因先查什么白屏无显示背光未开、时序参数错背光引脚电平、PLL3Q输出是否33.3MHz画面出现斜向条纹像素时钟偏差大重新核对屏规格书的Pixel Clock颜色偏色或通道错位数据位接错或悬空RGB565接16位时低bit是否接地部分区域刷新滞后D-Cache缓存了显存MPU把显存配成Non-cacheable触摸点跳跃或反向坐标未归一化、映射case不对用十字线确认映射关系5.3 用DWT测一帧填充时间判断DMA2D是否跑满不用外接逻辑分析仪H743内核自带的DWT计数器和CPU主频同频一条指令就能读出精确周期数DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; DWT-CYCCNT 0; lcd_fill_color(0x07E0); uint32_t cost DWT-CYCCNT; float ms (float)cost / 480000000.0f * 1000.0f;800x480 RGB565整屏填充在DMA2D加速下通常能控制在2-4ms。如果测得超过10ms先确认DMA2D时钟已经打开再确认目标地址在DMA2D可访问的AXI总线上。用这个数字配合触摸画点的响应延迟就能量化整条驱动链路还剩多少余量再决定UI层要不要上双缓冲。把这段测量代码留在工程里每次改动LTDC或者SDRAM初始化参数后跑一遍30秒内就能判断性能有没有回退。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。