资讯动态

STM32F429移植STemWin与FreeRTOS:GUI按钮控制LED

发布时间:2026/10/26 4:57:47 来源:尧图企业网站定制
简介面向嵌入式GUI进阶学习者的STM32F429 DISC1实战工程将STemwin图形库与FreeRTOS实时系统移植到Cortex-M4平台并用GUIBuilder设计两个按钮实现点击控制LED亮灭的交互闭环。工程覆盖LCD控制器与触摸屏驱动适配、内存分配策略、任务调度与中断管理以及竖屏模式下界面布局的坐标调整可作为理解事件驱动编程与GUI、RTOS协同工作的参考实例。压缩包共530个文件、约18.26MB以176个h头文件与102个c源文件为主体另含75个o目标文件、75个d依赖文件及74个crf浏览信息配合uvproj工程、sct分散加载脚本与hex烧录文件构成可直接编译烧录的Keil工程。现有966人学习下载适合希望从裸机界面过渡到RTOS多任务GUI的中高级开发者对照移植流程、排查显示与触摸问题。1. 从一块 DISC1 板子说起STemWin 和 FreeRTOS 的版本得先对齐手上这块 STM32F429I-DISC1 是最容易被低估的板子8MB SDRAM、2.4 寸 ILI9341 屏、256KB SRAM看着什么都有可一上手把 STemWin 和 FreeRTOS 往同一个工程里塞屏是黑的、按钮是不响的、HardFault 是随时来的。在 stm32f429 上移植 stemwin 加 freertos再用 guibuilder 生成两个按钮控制 LED难点从来不在写代码而在版本对齐——STemWin 的库分带 OS 和不带 OS 两套FreeRTOS 的 heap 和 STemWin 的 GUI_ALLOC 抢同一块 SRAMGUIbuilder 生成的资源表默认按单线程假设走。三个东西各自都没错凑一起就翻车。这篇记录写给正在 DISC1 上折腾这套组合的人先把版本和内存摆清再一步步点亮屏、跑起任务、把按钮事件接到 LED 上最后留一份装坏了能救回来的排查清单。2. 环境清单与版本选型stm32f429 disc1 这套组合该怎么配动手之前先把版本钉死。嵌入式这块最容易出的事就是拿着别人的工程直接编译报了几十个错才发现人家的 STemWin 库是 Keil 版、你用的是 GCC或者人家用的是 noOS 库、你套了 FreeRTOS 的任务锁。这一章把要装的东西列成一张表逐项说清楚为什么这么选。2.1 硬件、工具链和中间件的版本对照下表是我在这块板子上跑通的一套组合型号和版本都对得上照着抄不会出错。项目型号 / 版本说明开发板STM32F429I-DISC1主控 STM32F429ZIT62MB Flash / 256KB SRAM另有 64KB CCM板载 SDRAMIS42S16400J8MB挂在 FMC Bank2映射地址 0xD000000016 位总线板载 LCDILI9341240×320SPI 接口不是 LTDC 直驱这点决定了后面的驱动选型板载 LEDLD3 / LD4分别接 PG13、PG14高电平点亮不同批次建议对一遍原理图固件包STM32CubeF4 V1.27.x里面自带 STemWin 和 FreeRTOS 的源码与库STemWin5.44老一点的包是 5.32位置在 Middlewares/ST/STemWinFreeRTOSV10.xCube 包内版本位置在 Middlewares/Third_Party/FreeRTOSGUIbuilder随 STemWin 包附带Windows 下运行产物是一个 .c 文件不依赖编译器IDESTM32CubeIDE 1.x 或 MDK-ARM 5.x两者库文件不同下面单独说这张表里最容易被忽略的是 LCD 那一行。DISC1 板载屏走的是 SPI不是 LTDC 并口 RGB所以网上大量「LTDC STemWin」的教程不能直接套。搞清楚这一点后面 LCDConf 里的驱动选择才不会走弯路。工具链这边还有一个隐性差异CubeIDE 用的是 GCC链接的是libSTemWin*.a这种后缀的库MDK-ARM 链接的是*_Keil.a。两个库的 ABI 不兼容工程从 Keil 挪到 CubeIDE 的时候编译选项、链接脚本、库文件三样都得换只换一样必然报一堆未定义符号。我的习惯是新建工程时就确定好用哪个 IDE后面不来回切。2.2 STemWin 的库文件有两个版本选错等于白装这是整套环境里第一个真正的坑。STemWin 包里的 Library 目录下文件名带_OS_的和不带的是两套完全不同的东西不带_OS_的库内部所有 GUI 操作都是直接执行不提供任务锁接口GUI_X_Lock / GUI_X_Unlock 这种符号压根不存在。带_OS_的库留出了 GUI_X_InitOS、GUI_X_Lock、GUI_X_Unlock、GUI_X_GetTaskId 这几个钩子需要你自己用 FreeRTOS 的互斥量实现。如果工程里跑了 FreeRTOS却链接了 noOS 的库编译期通常不报错运行起来才会出问题两个任务同时调 GUI 函数显存里的绘制指令互相踩表现是界面偶尔花一下、偶尔卡死极难定位。反过来如果只跑裸机却链接了 OS 库链接阶段就会报一堆undefined reference to GUI_X_Lock之类的错——这种情况反而是好事至少当场就发现了。判断方法很直接工程里只要有xTaskCreate就必须选_OS_那一批库。文件具体叫什么名字随版本变但_OS_这三个字符是不会少的选库的时候眼睛盯住这一段就行。另外还有 CM4 和 CM7 的区分。F429 是 Cortex-M4F带单精度 FPU选 CM4 版本的库。CM7 的库虽然也能链上但内部用的是不同的指令集假设跑起来行为不可预期别图省事混用。FreeRTOS 这边版本差异相对小V10 系列的 API 从 V8 之后就基本没动过。需要注意的是 Cube 包默认带的是 CMSIS-RTOS V1 那层封装osThreadCreate这类函数是包了一层壳。用 V1 还是直接用原生xTaskCreate都行但整个工程只能选一种混着用会在优先级映射上出问题CMSIS 的osPriorityNormal和 FreeRTOS 的tskIDLE_PRIORITY n不是一回事GUI 任务的优先级算错界面就会响应迟钝。GUIbuilder 不用单独找版本STemWin 包里那个GUIBuilder.exe就是配套的双击就能跑Windows 环境不需要装运行时。它生成的是纯 C 代码跟编译器、跟 RTOS 都没关系只是生成的代码默认按「单线程、有一个默认窗口管理器」来写接到 FreeRTOS 任务里需要额外包一层。2.3 工程目录怎么摆把 STemWin、FreeRTOS 和 GUIbuilder 产物分开放目录结构这一步看着琐碎但后面排查问题时能不能一眼定位全看这里。我一般按下面这样分F429_DISC1_GUI/ ├── Core/ │ ├── Inc/ │ └── Src/ │ ├── main.c │ ├── freertos.c # 任务创建都放这里 │ ├── stm32f4xx_it.c │ └── led.c # LED 驱动别塞进 main.c ├── Drivers/ │ ├── CMSIS/ │ └── STM32F4xx_HAL_Driver/ ├── Middlewares/ │ ├── Third_Party/FreeRTOS/ # Cube 包原样拷过来 │ └── ST/STemWin/ │ ├── Config/ │ │ ├── GUIConf.h # GUI_OS、GUI_ALLOC_SIZE │ │ ├── LCDConf.c # 分辨率、颜色格式、显存地址 │ │ └── GUI_X.c # 时基与延时 │ ├── inc/ │ ├── Lib/ # 放 *._OS_.a │ └── GUI_X_OS.c # 自己补的 FreeRTOS 适配层 └── App/ ├── WindowDLG.c # GUIbuilder 生成的界面 └── WindowDLG.h配置步骤按顺序走用 CubeMX 生成基础工程只勾 FreeRTOSCMSIS_V1和外设时钟先不碰 GUI。把 Cube 包里的Middlewares/ST/STemWin整个目录拷进工程Config下那三个模板文件留着后面改。在 IDE 的 include path 里加三条Middlewares/ST/STemWin/inc、Middlewares/ST/STemWin/Config、App。这三条漏一条就是几十个头文件找不到。链接器里加上_OS_那个 .a 文件同时确认链接顺序在 C 库之后。GUIbuilder 生成的WindowDLG.c丢进App/不要跟 STemWin 的源码混在一起——混在一起以后升级 STemWin 会很难受。App这个目录是我自己加的CubeMX 重新生成代码时不会动它。凡是手写的、GUIbuilder 生成的全放这里CubeMX 管不到的地方就是安全区。这个习惯救过我几次改 CubeMX 配置重新生成Core/下的东西被覆盖App/下的原封不动。3. 先把屏点亮SDRAM 显存、LCDConf 与 SPI 刷屏的移植顺序STemWin 的移植顺序不能反。很多人一上来就调 GUI_Init屏是黑的然后开始怀疑库、怀疑版本、怀疑人生。正确的顺序是先让 SDRAM 能读写再让屏能整块刷色最后才轮到 STemWin 初始化。三个层次逐层验证出了问题时能立刻判断卡在哪一层。3.1 FMC 配 SDRAM让 0xD0000000 这块地能读写DISC1 上那颗 IS42S16400J 是 8MB组织方式是 4 个 bank × 12 位行 × 8 位列 × 16 位宽。CubeMX 里 FMC 的 SDRAM 参数照着这个填/* FMC SDRAM Bank2 初始化HCLK180MHzSDCLKHCLK/290MHz */ hsdram.Instance FMC_SDRAM_DEVICE; hsdram.Init.SDBank FMC_SDRAM_BANK2; /* 板载颗粒挂在 Bank2 */ hsdram.Init.ColumnBitsNumber FMC_SDRAM_COLUMN_BITS_NUM_8; /* 8 位列地址 */ hsdram.Init.RowBitsNumber FMC_SDRAM_ROW_BITS_NUM_12; /* 12 位行地址 */ hsdram.Init.MemoryDataWidth FMC_SDRAM_MEM_BUS_WIDTH_16; /* 16 位数据总线 */ hsdram.Init.InternalBankNumber FMC_SDRAM_INTERN_BANKS_NUM_4; /* 片内 4 个 bank */ hsdram.Init.CASLatency FMC_SDRAM_CAS_LATENCY_3; /* 90MHz 下取 3 拍 */ hsdram.Init.WriteProtection FMC_SDRAM_WRITE_PROTECTION_DISABLE; hsdram.Init.SDClockPeriod FMC_SDRAM_CLOCK_PERIOD_2; /* 2 分频 */ hsdram.Init.ReadBurst FMC_SDRAM_RBURST_ENABLE; /* 开突发读提升连续读效率 */ hsdram.Init.ReadPipeDelay FMC_SDRAM_RPIPE_DELAY_0;几个参数值得单独说。SDClockPeriod选 2 分频SDCLK 就是 90MHz这颗颗粒的 -7 速度等级标称 143MHz90MHz 有足够余量如果为了追帧率改成分频 1180MHz大概率跑不稳表现是随机某几个像素颜色不对或者整屏偶尔闪一下。CASLatency在 90MHz 下取 3取 2 会读错数据。ReadBurst一定要开STemWin 刷屏时是连续读显存突发模式对帧率影响很明显。写完初始化别急着往下走先做一次读写自检/* SDRAM 读写自检写 0x5A5A5A5A 再读回来比对 */ #define SDRAM_BASE 0xD0000000UL #define SDRAM_SIZE (8UL * 1024 * 1024) uint32_t sdram_test(void) { volatile uint32_t *p (volatile uint32_t *)SDRAM_BASE; for (uint32_t i 0; i SDRAM_SIZE / 4; i) { p[i] 0x5A5A5A5A ^ i; /* 用异或避免全同数据掩盖地址线问题 */ } for (uint32_t i 0; i SDRAM_SIZE / 4; i) { if (p[i] ! (0x5A5A5A5A ^ i)) return i; /* 返回出错位置方便定位是哪根地址线 */ } return 0xFFFFFFFF; }这段的作用不只是「能读能写」用异或而不是写全 0 或全 1是为了把地址线短路、虚焊这类问题暴露出来——全写同一个值地址线错了也读得回来。返回值设计成出错下标是因为烧到板子上跑的时候没法挂调试器看变量通过串口把这一个数字打出来就能算出是哪一段地址坏了比打「测试失败」有用得多。这步过不去就别往下走了显存放不住后面所有画面问题都会变成玄学。3.2 LCDConf.c 里四个必须改的参数Config/LCDConf.c是 STemWin 和你这块屏之间的合同。模板文件改四个地方就够用/* LCDConf.c 关键部分 */ #define LCD_XSIZE 240 /* 屏宽ILI9341 是 240 */ #define LCD_YSIZE 320 /* 屏高竖屏 320横屏反过来 */ #define COLOR_CONVERSION GUICC_M565 /* ILI9341 用 RGB565一个像素 2 字节 */ #define DISPLAY_DRIVER GUIDRV_LIN_16 /* 带帧缓冲的线性驱动 */ /* 显存放 SDRAM别放 SRAM更别放 CCM */ #define LCD_VRAM_ADDR 0xD0000000UL void LCD_X_Config(void) { CONFIGURATION Config {0}; Config.vxsize LCD_XSIZE; Config.vysize LCD_YSIZE; /* 用带帧缓冲的线性驱动绘制全部落在 SDRAM 里不直接操作 SPI */ GUIDEVICE_CREATE_DRIVER(0, GUIDRV_LIN_16, Config); /* 横竖屏切换只需交换这两个参数驱动层不用动 */ LCD_SetSizeEx (0, LCD_XSIZE, LCD_YSIZE); LCD_SetVSizeEx(0, LCD_XSIZE, LCD_YSIZE); /* 把显存基址告诉 STemWin这里必须是 SDRAM 的物理地址 */ LCD_SetVRAMAddrEx(0, (void *)LCD_VRAM_ADDR); }GUIDRV_LIN_16的选择是这套方案的核心。它表示「STemWin 认为自己在往一块连续显存里画画完由外部把这块内存送到屏上」。对 SPI 屏来说这是唯一现实的做法因为逐像素走 SPI 太慢画一条线就要几十毫秒界面根本动不了。用帧缓冲以后GUI 绘制是纯内存操作速度取决于 SDRAM 带宽跟 SPI 彻底解耦。COLOR_CONVERSION必须和屏一致。ILI9341 支持 RGB565也就是一个像素 16 位对应GUICC_M565。写成GUICC_M888会让每个像素占 3 字节显存占用从 150KB 涨到 225KB颜色还会错位——表现是所有颜色都偏红变蓝、黄变青看着像屏坏了其实是格式没对上。显存地址这一行有个细节0xD0000000是 SDRAM 的基址但整屏 240×320×2 153600 字节只占 8MB 里很小一段。后面 FreeRTOS 的堆、其他缓冲区都可以往后面的地址放只要不重叠。我一般把前 256KB 划给显存后面的留给别的用途画个内存分布图贴在工程说明里省得以后自己都忘了。注意CCM RAM 那 64KB 只能 CPU 访问DMA 拿不到。显存放进去画面会一直是黑的而且不会报错。3.3 帧缓冲怎么送到 ILI9341SPI 刷屏的节流帧缓冲建好以后STemWin 画完就不管了把数据推到屏上是你的活。典型实现是这样一个函数/* 把 SDRAM 里的整帧推到 ILI9341用 DMA 搬运避免长时间占 CPU */ static void LCD_Flush(const void *pSrc) { ILI9341_SetWindow(0, 0, LCD_XSIZE - 1, LCD_YSIZE - 1); /* 开一个全屏窗口 */ ILI9341_WriteCmd(0x2C); /* Memory Write 命令 */ /* 153600 字节DMA 里跑注意源地址必须在可被 DMA 访问的 SDRAM 上 */ BSP_SPI_TransmitDMA((uint8_t *)pSrc, LCD_XSIZE * LCD_YSIZE * 2); /* 必须等 DMA 完成再返回否则 GUI 下一帧会改到正在发送的缓冲 */ while (LCD_DMA_Busy()) { } }算一下时间240×320×2 153600 字节SPI 时钟 20MHz一帧要 153600×8 / 20e6 ≈ 61ms。也就是满打满算 16fps这是 SPI 屏的物理上限换什么驱动都突破不了。理解这个数字很重要它决定了后面按钮响应的设计事件处理必须在 WM_NOTIFY_PARENT 里做不能靠主循环轮询按钮状态因为主循环一圈就有 61ms 的延迟。节流也是必须的。如果不做任何判断每调一次GUI_Exec()就整屏刷一次哪怕画面根本没变化CPU 就全耗在 SPI 上了。常见做法是记一个「脏」标志只有窗口管理器报告有重绘时才刷或者干脆每 100ms 强制刷一次中间的变化合并掉。我一般用后者代码简单视觉上也看不出差别。3.4 GUI_Init 之前先做一次裸刷屏到这一步屏能刷色但 STemWin 还没初始化。这时候做一次最简单的验证不调任何 GUI 函数直接往0xD0000000里填纯色然后调LCD_Flush。红、绿、蓝各刷一遍颜色对了说明显存地址、颜色格式、SPI 时序三样全对。这一层验证的价值在于「隔离变量」。如果直接上GUI_Init屏花了以后你要同时怀疑 SDRAM、怀疑 LCDConf、怀疑 STemWin 库版本、怀疑 FreeRTOS 配置四个方向一起查一天就没了。分层验证以后每一层只有一两个变量出了问题立刻能定位。裸刷屏通过之后再调GUI_Init()。返回值是 0 表示成功非 0 就是失败。第一次成功初始化以后可以先不建窗口直接GUI_SetBkColorGUI_Clear刷一次背景色看看 STemWin 画出来的东西和裸刷屏颜色是否一致。一致就说明曲线通了可以往上叠 GUIbuilder 的界面了。4. FreeRTOS 与界面一起上GUI 任务、GUIbuilder 资源表和 LED 事件裸机跑通以后把 FreeRTOS 接进来事情会复杂一个量级。核心问题是「谁在什么时候能碰 GUI」。STemWin 内部有大量全局状态当前窗口、裁剪区、绘图上下文两个任务同时调就是灾难。所以这一章的重点不是任务怎么写而是锁怎么加、内存怎么分、事件从哪来。4.1 FreeRTOSConfig.h 里先改这七个宏CubeMX 生成的FreeRTOSConfig.h默认值偏保守跑 GUI 不够用。这几个是我每次必改的/* FreeRTOSConfig.h */ #define configUSE_PREEMPTION 1 /* GUI 任务需要被更高优先级任务抢占 */ #define configTICK_RATE_HZ 1000 /* 1ms 一个 tickGUI_X_GetTime 精度才够 */ #define configMAX_PRIORITIES 7 /* p a hrefhttps://download.csdn.net/download/weixin_42174355/10997572 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p

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

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

免费获取报价 →
↑