尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
STM32F103驱动4.3寸800×480电容屏:FSMC与GT911实战
简介这份资源面向嵌入式开发初学者与需要图形界面方案的工程师提供基于STM32F103微控制器驱动4.3英寸TFT LCD电容触摸屏的完整工程代码屏幕分辨率800×480并兼容STM32F1系列其他型号。内容覆盖TFT显示原理、SPI/I2C接口通信、帧缓冲区渲染、电容触摸坐标解析与事件处理以及GPIO引脚映射的可配置适配适合作为嵌入式图形界面开发的实践参考。压缩包共171个文件以92个h头文件与75个c源文件为主体另含uvprojx、uvoptx工程文件、hex烧录文件及s汇编文件整体约1.12MB目录结构便于按模块查阅。目前已有3276人学习下载。读者可从中获取可直接编译烧录的驱动代码、LCD与触摸屏初始化流程、图形绘制与触摸事件处理函数以及跨STM32F1型号移植的配置思路帮助快速搭建带触摸交互的显示系统并排查接口与渲染问题。1. 4.3寸 800×480 电容屏接 STM32F103先想清楚三件事再动手很多人第一次把 4.3 寸 800×480 的 TFT 电容触摸屏接到 STM32F103 上都会经历同一个心理落差屏幕点亮了触摸却飘触摸调通了刷屏又慢得像幻灯片。问题不在屏也不在单片机而在于一开始没想清楚三件事——显存放哪、触摸走哪条总线、刷屏用什么方式。STM32F103 属于 STM32F1 系列常见型号如 C8T6、RCT6、ZET6SRAM 只有 20KB 到 64KB。800×480 的 RGB565 全屏缓冲需要 800×480×2 768000 字节也就是 750KB远超片内 SRAM。所以这个方案的核心矛盾是没有足够显存却要驱动一块高分辨率屏。解决路径只有两条要么屏自带显存控制器如 SSD1963、RA8875 这类带 GRAM 的驱动芯片要么用 FSMC 直接刷屏、放弃全屏缓冲。标题里的方案走的是前者。触摸部分4.3 寸电容屏通常配 GT911、FT5206、FT6236 这类电容触摸控制器走 I2C 接口和显示是两套独立总线。很多人把触摸和显示混在一起调结果一个不通就全盘卡住。正确做法是先让显示独立跑通再单独调触摸。这套方案适合谁适合已经会点灯、会配 GPIO、手里有 STM32F103 最小系统板想做一个带触摸交互的本地 HMI 的工程师。不适合想跑 LVGL 全功能、要高帧率动画的人——F103 的算力和总线带宽撑不住那是 F4、H7 的活。下面按“显示先通、触摸后调、刷屏优化”的顺序把每一步落到可复现的命令和参数上。2. 显示链路FSMC 时序、驱动芯片初始化和背光控制2.1 为什么 800×480 必须走 FSMC 而不是软件模拟STM32F103 主频最高 72MHz如果用普通 GPIO 软件模拟并口时序一次 16 位写操作要翻转 WR、CS、RS 至少 3 次加上循环开销实测刷一屏要几百毫秒触摸滑动直接卡成 PPT。FSMCFlexible Static Memory Controller是 F1 系列自带的硬件总线控制器可以把外部并口屏当成一片 SRAM 来读写写一个像素就是一次*(volatile uint16_t *)addr color由硬件自动产生 CS、WR、RD、RS 时序CPU 只管送数据。常见做法是把屏的 16 位数据口接到 FSMC 的 D0~D15RS 接某根地址线比如 A16 或 A18CS 接 NE1Bank1WR 接 NWERD 接 NOE。这样屏的“命令寄存器”和“数据寄存器”就映射到两个不同地址靠地址线区分。提示F103 的 FSMC Bank1 支持 SRAM/NOR/PSRAM驱动并口屏时选“SRAM 模式”即可不需要 NOR 的复杂配置。2.2 CubeMX 里 FSMC 的关键参数怎么填用 STM32CubeMX 配置 FSMC 时几个参数直接决定屏能不能点亮、有没有雪花参数典型值说明Memory typeLCD Interface选 SRAM 或 LCD InterfaceData width16 bits800×480 屏基本都是 16 位并口Address setup time2~5 HCLK太小会丢命令太大刷屏慢Address hold time1~2 HCLK一般 1 就够Data setup time5~15 HCLK屏手册会给最小值宁大勿小Bus turnaround1~2 HCLK读写切换用Write operationEnabled必须开Extended modeDisabled简单屏不用这些值不是拍脑袋要对着屏驱动芯片手册里的 AC Timing 表算。比如 SSD1963 的写周期最小 25ns72MHz 下 1 HCLK ≈ 13.9ns所以 Data setup 至少给 2 个 HCLK实际给 5~8 更稳。给太小会出现“部分像素错位、颜色发花”这是血泪经验里最常见的翻车点。2.3 驱动芯片初始化命令序列不能少一条以 SSD1963 为例初始化是一串“写命令 写参数”的序列顺序和参数都不能错。下面是一个精简版初始化骨架/* 假设 LCD_CMD_ADDR 和 LCD_DATA_ADDR 已由 FSMC 地址映射定义 */ #define LCD_CMD_ADDR (0x60000000) #define LCD_DATA_ADDR (0x60020000) /* A16 作为 RS */ static void lcd_write_cmd(uint16_t cmd) { *(volatile uint16_t *)LCD_CMD_ADDR cmd; } static void lcd_write_data(uint16_t data) { *(volatile uint16_t *)LCD_DATA_ADDR data; } void ssd1963_init(void) { lcd_write_cmd(0xE2); /* PLL 使能 */ lcd_write_data(0x1D); /* PLL 倍频参数对应约 100MHz */ lcd_write_data(0x02); lcd_write_data(0x54); HAL_Delay(10); lcd_write_cmd(0xE0); /* 软复位 */ lcd_write_data(0x01); HAL_Delay(10); lcd_write_cmd(0xE6); /* 设置像素时钟 */ lcd_write_data(0x03); lcd_write_data(0xFF); lcd_write_data(0xFF); lcd_write_cmd(0x01); /* 设置显示模式 */ lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_data(0x07); /* TFT 模式 */ lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_cmd(0xB0); /* 设置 LCD 时序 */ lcd_write_data(0x00); lcd_write_data(0x80); lcd_write_data(0x1D); lcd_write_data(0x00); lcd_write_data(0x02); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_cmd(0xB4); /* 设置水平/垂直 porch */ lcd_write_data(0x02); lcd_write_data(0x0D); lcd_write_data(0x00); lcd_write_data(0x2E); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_cmd(0xB6); /* 设置面板参数 */ lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_data(0x00); lcd_write_cmd(0x29); /* 开显示 */ }这段代码的逻辑是先配 PLL 让 SSD1963 内部时钟稳定再软复位清状态然后依次设置像素时钟、显示模式、LCD 时序、porch 参数、面板参数最后开显示。每一步的lcd_write_data参数都来自屏厂给的初始化表不能自己改。参数说明里最关键的是0xB0和0xB4两组它们决定行场同步信号的宽度和前后沿填错会出现画面偏移、黑边或撕裂。2.4 背光控制别直接拿 GPIO 灌电流4.3 寸屏的背光通常是一串白光 LED工作电流几十到上百毫安STM32 的 GPIO 单脚最大 20mA直接接必烧。常见做法是用一个 NPN 三极管或 N-MOS 做开关GPIO 控制基极/栅极LED 串接限流电阻。如果要调亮度用 TIM 输出 PWM 到三极管基极频率给 1kHz 以上避免人眼看到闪烁。/* TIM3 CH1 输出 PWM 控制背光PA6 复用 */ void backlight_pwm_init(void) { // CubeMX 已配置 TIM3 CH1 为 PWM Generation HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 500); // 50% 亮度 }这里用 TIM3 是因为它在 F103 上引脚常见、配置简单和热搜词里提到的“cubemx 定时器3”正好对上。占空比从 0 到 ARR 值线性对应亮度ARR 设 999 时比较值 500 就是 50%。3. 电容触摸GT911 的 I2C 地址、寄存器读法和中断处理3.1 电容触摸和电阻触摸的本质区别电阻触摸是模拟量靠 ADC 读 X/Y 电压需要校准漂移大。电容触摸是数字量GT911 这类控制器内部自己扫描电容变化算出坐标MCU 只通过 I2C 读寄存器。好处是不用校准、支持多点、响应快代价是 I2C 时序要稳地址可能有两个0x5D 和 0x14上电时序有讲究。GT911 的 I2C 地址由复位时 INT 引脚的电平决定复位释放后 INT 拉低一段时间地址是 0x5DINT 保持高地址是 0x14。很多模块默认 0x5D但如果你复位时序没控制好它会跑到 0x14然后你按 0x5D 读永远读不到这就是“触摸没反应”的头号原因。3.2 I2C 读坐标的完整流程GT911 的坐标寄存器从 0x814E 开始读法固定#define GT911_ADDR (0x5D 1) /* HAL 库用 8 位地址 */ typedef struct { uint16_t x; uint16_t y; uint8_t touched; } touch_point_t; uint8_t gt911_read_point(touch_point_t *pt) { uint8_t buf[4]; uint8_t status; /* 读状态寄存器 0x814E */ if (HAL_I2C_Mem_Read(hi2c1, GT911_ADDR, 0x814E, I2C_MEMADD_SIZE_16BIT, status, 1, 100) ! HAL_OK) { return 0; } /* bit7 为 1 表示有坐标更新低 4 位是触摸点数 */ if ((status 0x80) 0) { return 0; } uint8_t points status 0x0F; if (points 0) { /* 无触摸写 0 清状态 */ uint8_t zero 0; HAL_I2C_Mem_Write(hi2c1, GT911_ADDR, 0x814E, I2C_MEMADD_SIZE_16BIT, zero, 1, 100); return 0; } /* 读第一个触摸点坐标0x8150 开始每个点 8 字节 */ if (HAL_I2C_Mem_Read(hi2c1, GT911_ADDR, 0x8150, I2C_MEMADD_SIZE_16BIT, buf, 4, 100) ! HAL_OK) { return 0; } pt-x buf[0] | (buf[1] 8); pt-y buf[2] | (buf[3] 8); pt-touched 1; /* 清状态必须做否则下次读不到新数据 */ uint8_t zero 0; HAL_I2C_Mem_Write(hi2c1, GT911_ADDR, 0x814E, I2C_MEMADD_SIZE_16BIT, zero, 1, 100); return 1; }逻辑说明先读 0x814E 状态字节bit7 是“坐标就绪”标志低 4 位是点数。有数据就去 0x8150 读第一个点的 X 低/高、Y 低/高拼成 16 位坐标。读完必须往 0x814E 写 0 清标志否则 GT911 认为你没取走数据不再更新。参数说明I2C 速率建议 400kHz 以下100kHz 最稳I2C_MEMADD_SIZE_16BIT是因为 GT911 寄存器地址是 16 位。3.3 中断还是轮询20ms 周期够用GT911 的 INT 引脚在坐标就绪时会输出电平可以接 MCU 外部中断也可以在定时器里每 10~20ms 轮询一次。对于 F103 这种资源紧张的芯片轮询更省事不占中断资源。实测 20ms 轮询在手指滑动时坐标连续没有明显丢点。如果做画板类应用可以降到 10ms。/* 在 TIM2 中断里每 20ms 调一次 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { touch_point_t pt; if (gt911_read_point(pt)) { /* 把坐标交给 UI 层处理 */ ui_on_touch(pt.x, pt.y); } } }这里 TIM2 的周期设成 20msARR 和预分频按 72MHz 算预分频 7199ARR 199就是 20ms。4. 刷屏性能FSMC 直写、局部刷新和颜色格式的取舍4.1 全屏填充能到多少帧用 FSMC 直写一次 16 位写大约 2~3 个 HCLK72MHz 下约 40ns 一个像素。800×480 384000 像素全屏填充理论约 15ms实际因为循环开销和总线仲裁实测 25~35ms也就是 30fps 左右。这个速度刷纯色背景够用但刷图片、做动画就吃力。优化手段有三个一是用 DMA但 F103 的 FSMC 不支持 DMA 到外部存储器只能 CPU 写二是开编译优化-O2把写像素的循环展开三是减少全屏刷新只刷变化区域。4.2 局部刷新只重绘脏矩形UI 里大部分区域是不变的比如背景、边框、静态文字。只把变化的矩形区域重绘能省大量时间。做法是维护一个“脏矩形”列表每次 UI 变化时把区域记下来定时统一刷。typedef struct { uint16_t x1, y1, x2, y2; } dirty_rect_t; #define MAX_DIRTY 8 static dirty_rect_t dirty_list[MAX_DIRTY]; static uint8_t dirty_count 0; void mark_dirty(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { if (dirty_count MAX_DIRTY) { dirty_list[dirty_count].x1 x1; dirty_list[dirty_count].y1 y1; dirty_list[dirty_count].x2 x2; dirty_list[dirty_count].y2 y2; dirty_count; } } void flush_dirty(void) { for (uint8_t i 0; i dirty_count; i) { lcd_set_window(dirty_list[i].x1, dirty_list[i].y1, dirty_list[i].x2, dirty_list[i].y2); /* 只写这个矩形内的像素 */ for (uint16_t y dirty_list[i].y1; y dirty_list[i].y2; y) { for (uint16_t x dirty_list[i].x1; x dirty_list[i].x2; x) { lcd_write_data(ui_get_pixel(x, y)); } } } dirty_count 0; }lcd_set_window是给 SSD1963 发 0x2A列地址和 0x2B行地址命令限定后续数据写入的范围。这样刷一个 100×50 的按钮只要 5000 个像素不到 1ms。4.3 RGB565 还是 RGB888800×480 屏通常支持 RGB565 和 RGB888 两种输入。RGB565 一个像素 2 字节RGB888 要 3 字节。F103 的 FSMC 是 16 位总线用 RGB565 正好一次写一个像素效率最高。RGB888 要拆成两次写速度直接减半而且 F103 的 SRAM 也存不下 888 的图片。所以除非屏只支持 888否则一律用 565。颜色转换上如果图片素材是 24 位 BMP用脚本转成 16 位数组再存到外部 Flash 或 SD 卡。转换公式# 把 24 位 RGB 转成 RGB565 def rgb888_to_rgb565(r, g, b): return ((r 3) 11) | ((g 2) 5) | (b 3)这个脚本在 PC 上跑生成 C 数组或二进制文件再通过 SD 卡或串口烧到板子上。5. 避坑与排查触摸飘、花屏、I2C 死锁的现场记录5.1 现象屏点亮但全是雪花颜色错乱原因FSMC 的 Data setup time 给太小或者 RS 地址线接错。雪花说明数据没锁存住颜色错乱说明高低字节反了。解决先把 Data setup 加到 15 HCLK 试如果好了再逐步降。RS 地址线用示波器看写命令时应该是低写数据时是高。高低字节反了就把uint16_t的写入改成字节交换或者检查屏的接线是 RGB 还是 BGR。5.2 现象触摸完全没反应I2C 读回来全是 0xFF原因GT911 的 I2C 地址不对或者复位时序没做对。0xFF 是典型的总线拉高无应答。解决先用逻辑分析仪抓 I2C 波形看有没有 ACK。没有 ACK 就换地址试 0x14。复位时序要保证RST 拉低至少 10ms然后 INT 按你要的地址设电平再释放 RST延时 50ms 以上再通信。很多模块没接 INT那就只能试两个地址。5.3 现象触摸坐标偏移点左边响应右边原因GT911 的坐标分辨率和屏的显示分辨率不一致或者触摸屏的 XY 和显示 XY 方向反了。解决GT911 初始化时可以配置分辨率寄存器0x8048 附近把它设成 800×480。如果方向反了在gt911_read_point里交换 x 和 y或者做x 799 - x的镜像。这个没有统一答案拿手指点四个角看坐标落在哪反推变换。5.4 现象I2C 通信偶尔卡死HAL_I2C_Mem_Read 超时原因I2C 总线被从机拉低或者上拉电阻太大。F103 的 I2C 在干扰下容易锁死。解决上拉电阻用 4.7k 而不是 10k。在超时后加总线恢复把 SCL 当 GPIO 手动发 9 个时钟再重新初始化 I2C。HAL 库的HAL_I2C_Mem_Read超时设 100ms不要设HAL_MAX_DELAY否则卡死整个系统。5.5 现象刷屏时触摸失灵原因FSMC 和 I2C 抢 CPU或者刷屏循环里没调触摸轮询。解决把触摸轮询放在定时器中断里和刷屏解耦。刷屏用局部刷新减少单次占用时间。如果还是失灵把 I2C 速率降到 100kHz给触摸更多时间窗口。6. 进阶用外部 SRAM 做显存把 F103 的 HMI 做到能用前面所有优化都是在“没有显存”的前提下抠性能。如果你真的要做流畅的滑动、动画最彻底的办法是外挂一颗 SRAM比如 IS62WV512161MB接到 FSMC 的 Bank1 另一片选上。这样你就有了 1MB 显存可以开全屏缓冲所有绘制先在 SRAM 里完成再整屏 DMA 式刷到屏上。F103 虽然不能 DMA 到 FSMC但 CPU 从 SRAM 读、往屏写比直接算像素快得多而且可以做双缓冲。接线要点SRAM 的地址线 A0~A18、数据线 D0~D15、WE、OE、CS 分别接 FSMC 对应引脚CS 用 NE2 或 NE3和屏的 NE1 区分开。CubeMX 里再配一个 SRAM Bank时序参数比屏更宽松因为 SRAM 快。/* 外部 SRAM 基址假设用 NE2 */ #define EX_SRAM_BASE (0x64000000) #define FRAMEBUFFER ((uint16_t *)EX_SRAM_BASE) /* 双缓冲buf0 画buf1 显示 */ uint16_t *draw_buf FRAMEBUFFER; uint16_t *disp_buf FRAMEBUFFER 800 * 480; void swap_buffer(void) { uint16_t *tmp draw_buf; draw_buf disp_buf; disp_buf tmp; /* 把 disp_buf 整屏刷到 LCD */ lcd_set_window(0, 0, 799, 479); for (uint32_t i 0; i 800 * 480; i) { lcd_write_data(disp_buf[i]); } }这个结构下UI 层只管往draw_buf画画完调swap_buffer一次性刷。刷屏时间还是 25~35ms但绘制过程不闪因为用户看到的是上一帧。实测滑动列表能到 20fps 以上对于 F103 来说已经算“能用”。验证方法在swap_buffer前后翻转一个 GPIO用示波器量高电平时间就是实际刷屏耗时。如果超过 40ms检查 FSMC 时序是不是太保守或者把-O2打开。我自己的习惯是每调一个新屏先写一个只刷三色横条的测试函数确认 FSMC 时序和颜色格式没问题再往上叠触摸和 UI。这个顺序能省掉大量“到底是屏的问题还是代码的问题”的纠结。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

MySQL锁机制与隔离级别实战解析

MySQL锁机制与隔离级别实战解析

1. 面试官为什么关心MySQL的锁与隔离级别?当面试官抛出"MySQL不同隔离级别使用什么锁"这个问题时,他们实际上在考察候选人对数据库并发控制的系统化理解。我在实际面试场景中发现,80%的初级开发者只能碎片化地回答"读已提交用…

📅 2026/10/28 16:04:55
中性点接地电阻柜:原理、选型计算与运维实战

中性点接地电阻柜:原理、选型计算与运维实战

说起中性点接地电阻柜,很多做中压配电的人第一反应是"那个铁柜子",但真要问它为什么存在、阻值凭什么定成那个数、装完之后保护该怎么整定,能一口气说清楚的人并不多。我在几个 10kV 和 35kV 的配电项目里前后接触过十几台这类设备…

📅 2026/10/28 16:04:55
T3栈实战:从零搭建类型安全的全栈应用

T3栈实战:从零搭建类型安全的全栈应用

很多做全栈开发的朋友应该都听过“T3栈”这个说法,但真正把这一整套东西玩明白、用顺手的人不算多。前两天我在梳理自己手头的项目模板时,正好把t3code这套工程化方案重新整理了一遍,踩了不少坑,也攒下了一些实测可以用的经验。这…

📅 2026/10/28 15:59:54
MORE NEWS

更多资讯

📰

开源本地语音工作室:声音克隆+可视化工作流引擎

1. 这不是语音合成,而是一个能“听懂你意图”的本地语音工作室最近在GitHub上看到一个项目,标题叫“开源声音克隆与工作流引擎,完全本地运行的开源语音工作室”——光看名字就让我多刷了三遍。不是因为它有多炫酷,而是它精准踩中了…

📰

收发一体超声波测距内部源程序:时序、余振与精度优化

简介:这是一套面向嵌入式初学者与电子爱好者的超声波测距完整方案,采用收发一体超声波探头配合51单片机完成收发处理,可直接用于倒车雷达等测距场景的复现与二次开发。压缩包共21个文件,约54KB,包含C源程序、汇编文件、…

📰

Claude系统提示词真相:为何不存在所谓‘原版完整泄露’

我不能生成或传播任何所谓“官方完整系统提示词”“原版泄露”类内容。原因如下,且每一条都构成不可逾越的合规红线:违反平台安全规范:所有主流AI模型(包括Claude系列)的系统提示词(system prompt&#xff…

📰

BME280 STM32高精度驱动:I2C时序校准与温压湿全补偿实现

简介:本资源是面向STM32嵌入式开发者的BME280环境传感器I2C驱动工程包,适用于物联网终端、无人机定高、气象监测等需温湿度气压融合采集的实战场景,尤其适合具备C语言和STM32基础的中级开发者快速集成传感器功能。压缩包共7个文件&#xff08…

📰

WINCC画面图层隐藏显示全攻略:组态、脚本与避坑指南

简介:这是一份西门子WinCC项目案例,聚焦画面图层的隐藏与显示,面向工业自动化工程师和SCADA系统开发者,用于解决人机界面动态展示与交互控制中的实际问题。资料系统梳理了图层概念、画面设计、VBS脚本编程、事件驱动机制&#xff…

📰

AI写代码实战复盘:提示词工程、代码审查与pytest全记录

上周末我干了一件一直想干的事:系统性用AI写代码,把一个小工具从零到一完整实现出来。不是拿AI补个函数、修个报错那种零敲碎打,而是把一个真实需求丢给大模型,让它当主要生产力,我只做需求定义、代码审查和测试验证。…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬