ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F103ZET6跑STemWin:DropDown下拉控件开发详解

STM32F103ZET6跑STemWin:DropDown下拉控件开发详解 简介面向STM32F103ZET6单片机开发者的STemWin图形库实验资源聚焦DropDown下拉选择控件的嵌入式应用开发。压缩包内共536个文件总体约12.15MB以C源码、头文件、启动汇编与Keil工程文件uvprojx为主同时含有编译生成的o目标文件、hex烧录文件及map映射文件便于直接编译运行或烧录验证。目前已有44位开发者学习下载。例程基于ARM Cortex-M3平台展示了STemWin库的完整集成流程与DropDown控件的创建、配置和事件响应方式并附带必要的配置文件帮助理解工程结构。这些代码经过实际测试可以直接套用到工业控制、仪表显示等需要图形交互的项目中缩短界面开发周期是入门到进阶的实用参考资料。1. STM32F103ZET6 上跑 STemWinDropDown 控件到底难在哪STM32F103ZET6 上跑 STemWin把 DropDown 控件加到界面里并不难创建语句只有几行。难的是展开、选中、回传这一串交互总在关键时刻失灵。这份压缩包名字里的 DropDwon 是拼写笔误实际对应的是 STemWin 官方文档里的 DROPDOWN 下拉列表控件。很多人第一次把下拉框加进界面发现要么弹不出列表要么点完选项后自己的按键逻辑完全没触发。这个问题的根子不在控件本身而在对 STemWin 的窗口消息机制理解不够。这类例程源码非常适合课程设计、毕业设计以及工业屏和智能小车这种人机交互不算复杂的项目先跑通一个 DropDown再去看按钮和编辑框其实就是同一套窗口管理逻辑。2. STemWin 初始化与 DropDown 控件的窗口句柄机制2.1 F103ZET6 跑 STemWin 前的底层依赖FSMC、LCD 与 GUI_Init 的顺序先理清一个原则GUI_Init不是上电就能调用的。STM32F103ZET6 驱动 TFT 彩屏最常见方案是 FSMC 并口。ZET6 有 144 个引脚其中 PD、PE 口承担 FSMC 的数据总线和控制线PB 口也可用于部分控制信号。这类例程源码的 HARDWARE 目录里一般放着 LCD 驱动和触摸驱动USER 目录里是 main.cSTemWin 目录里才是图形库和配置。拿到压缩包后不要急着看控件代码先确认 main 函数里的初始化顺序int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); Delay_Init(); USART1_Config(115200); LCD_FSMC_Config(); // 1. FSMC 时序与 GPIO 复用 LCD_Init(); // 2. LCD 控制器寄存器初始化 TOUCH_Init(); // 3. 触摸控制器可选 GUI_Init(); // 4. STemWin 初始化 Menu_Create(); // 5. 创建界面与 DropDown while (1) { GUI_Delay(20); } }这段顺序如果被打乱表现很典型屏能亮但 GUI 画不出内容或者卡在GUI_Init里出不来。原因在于 STemWin 的底层接口LCD_X_Config和LCD_X_DisplayDriver依赖 FSMC 提供的总线读写能力。FSMC 没配置好GUI 写像素时往寄存器写的地址全部无效。FSMC 的初始化重点是读时序与写时序的FSMC_AddressSetupTime和FSMC_DataSetupTime。常见的 LCD 控制器如 ILI9341 在 ZET6 上地址建立时间设 0x01、数据建立时间设 0x02 基本可用如果屏分辨率更高或控制器偏慢要把数据建立时间加到 0x04 以上。这个参数在源码里通常写在LCD_FSMC_Config或lcd_fsmc.c中是板级移植时最先要调的东西。2.2 DropDown 本质是一个由表头和列表框组成的复合窗口DropDown 控件的运行机制可以从 STemWin 的窗口管理器角度来理解。STemWin 里几乎每个控件都是一个窗口DropDown 也不例外。折叠状态时它只显示一个类似编辑框的表头和一个右侧箭头点击箭头后控件自动把自身拉高在下方显示一个真正的列表窗口。这个列表窗口也是窗口管理器的一部分只是它的创建和销毁由控件内部完成。这就解释了两个常见现象。第一为什么GUI_DROPDOWN_CreateEx里的 ySize 参数看起来特别大这个高度不是折叠时表头的高度而是展开后整个控件的总高度。第二为什么点击列表项之后事件不是直接发给列表窗口而是发给父窗口因为列表项的选中行为由控件内部处理处理后通过消息机制通知父窗口。这套“父窗口接收通知、控件内部管理状态”的模型和 STM32 裸机编程里把外设中断直接写在 main 里是完全不同的思路需要切换过来。DropDown 的窗口句柄类型是WM_HWIN本质上是一个指针值但不要把它当普通整数去算术运算。它用于父窗口向控件发消息也用于业务逻辑读取控件状态。句柄为 0 意味着创建失败常见原因是 GUI 内存池耗尽或者父窗口句柄不合法。例程源码里如果创建控件后直接GUI_DROPDOWN_AddString没有判空句柄内存不足时就会在GUI_ALLOC相关调用上死掉这是新手最容易撞上的第一个坑。2.3 GUI_X_Config 里的内存池大小直接决定 DropDown 能不能建出来STemWin 需要一块内部内存池来管理窗口、控件和字符串资源这块内存在GUI_X_Config里分配。ZET6 有 64KB SRAM并不是全部都能给 GUI 用因为还要留出业务变量、串口缓冲和 LCD 的显存如果不使用外扩 SRAM。常见例程会这样分配void GUI_X_Config(void) { static U32 aMemory[4096]; // 4096 * 4 16KB GUI_ALLOC_AssignMemory(aMemory, sizeof(aMemory)); GUI_ALLOC_SetMemoryThreshold(1024 * 8); }GUI_ALLOC_AssignMemory把静态数组交给 STemWin 内部堆管理器GUI_ALLOC_SetMemoryThreshold设置一个内存阈值。当剩余内存低于这个阈值时调试接口会给出提示。我这里给 4096 个字16KB是针对 320x240 分辨率、界面控件不超过十几个的场景。如果屏是 800x480需要把数组加到 8192 个字32KB否则 DropDown 展开时的列表框缓冲可能申请失败。关于内存还有一点值得注意GUI_X_Config的调用时机比用户写的GUI_Init更早它是 STemWin 内部在初始化阶段触发的底层回调。不要在GUI_X_Config里调用任何 GUI API这个阶段 GUI 核心还没就绪。正确做法是只在这里分配内存和设置参数。3. 在 STM32F103ZET6 上创建 DropDown最小代码与参数表3.1 确认引脚定义F103ZET6 的 FSMC接口映射在写任何控件代码之前需要先花五分钟确认板子上的引脚定义。ZET6 的 FSMC 功能引脚是硬件固定的数据线 D0-D15 分布在 PD0-PD15 和 PE7-PE15地址线 A0-A10 在 PF0-PF5控制线 NOE、NWE、NE1 等有固定映射。但不同开发板对 LCD 的复位脚、背光脚定义不同有的用 PB0有的用 PC10。因此例程源码里的LCD_FSMC_Config如果直接照搬很可能出现“屏亮一半”或者完全黑屏。我拿到这类例程时一般先查三个地方GPIO 时钟使能是否覆盖了全部 FSMC 相关端口、片选信号用的是 NE1 还是 NE2、LCD 的寄存器地址和写数据地址是否匹配。片选不同LCD 基地址就不同。例程里常见的写法是#define LCD_BASE ((uint32_t)(0x60000000 | 0x00FFFFFE))这里的0x60000000对应 bank1 NE1。如果用 NE2要改成0x64000000。引脚定义和基地址这两件事确认完再去碰 GUI 层的东西能省掉大量排错时间。3.2 创建菜单窗口与 DropDown 的完整代码下面的代码可以直接替换例程里的Menu_Create部分。它创建了一个 480x272 的父窗口然后在上面放一个 DropDown 控件用来选择档位#include GUI.h #include WM.h static WM_HWIN hDropSpeed; static void _cbMenu(WM_MESSAGE * pMsg) { // 事件处理在下一章展开这里先保留回调框架 } void Menu_Create(void) { WM_HWIN hScreen; hScreen WM_CreateWindowAsChild(0, 0, 480, 272, WM_HBKWIN, WM_CF_SHOW, _cbMenu, 0); hDropSpeed GUI_DROPDOWN_CreateEx(20, 24, 180, 132, hScreen, WM_CF_SHOW | WM_CF_MEMDEV, 0, GUI_ID_USER 1); if (hDropSpeed 0) { return; // 创建失败常见原因是 GUI 内存不足 } GUI_DROPDOWN_AddString(hDropSpeed, 低速 10%); GUI_DROPDOWN_AddString(hDropSpeed, 中速 50%); GUI_DROPDOWN_AddString(hDropSpeed, 高速 90%); GUI_DROPDOWN_SetSel(hDropSpeed, 0); GUI_DROPDOWN_Expand(hDropSpeed); // 上电后直接展开方便查看列表内容 }WM_CreateWindowAsChild创建了一个 480x272 的父窗口句柄是hScreen它挂在桌面窗口WM_HBKWIN上。GUI_DROPDOWN_CreateEx的参数里前四个是坐标和尺寸第五个是父窗口句柄第六个是窗口创建标志第七个在不同版本 STemWin 里有差异通常是特殊标志位或回调指针最后一个是控件 ID。如果编译报参数数量不匹配去 STemWin 的 Gui.h 里查GUI_DROPDOWN_CreateEx的原型STemWin 各版本函数签名确实改过按头文件为准修正即可。WM_CF_MEMDEV让控件在展开时先画到内存设备再整体刷新避免列表弹出瞬间出现闪烁代价是额外消耗约宽度 x 高度 x 4字节内存。显示一屏幕内容时如果发现展开时卡顿先检查这个标志位是否需要保留。GUI_DROPDOWN_AddString每调用一次就往列表尾部加一项重复调用即可。GUI_DROPDOWN_SetSel把默认选中项设为第 0 项。GUI_DROPDOWN_Expand在调试阶段很有用它直接把列表展开能立刻看到列表项数量以及文本框宽度是否够用。正式发布时一般不再调用它。3.3 CreateEx 参数表与显示效果GUI_DROPDOWN_CreateEx的参数含义需要记牢因为它们和直接画矩形框的直觉不一样参数含义建议值x0, y0控件左上角相对父窗口的坐标按界面排布来定xSize控件宽度即表头宽度大于最宽列表项文本宽度加 20px 箭头区域ySize控件总高度展开后整个控件的高度列表项数乘以行高再加表头高度hParent父窗口句柄自定义父窗口或WM_HBKWINWinFlags窗口创建标志WM_CF_SHOW必加WM_CF_MEMDEV防闪烁第 7 参数特殊标志或回调版本相关默认传 0Id控件标识使用GUI_ID_USER n避免与系统保留 ID 冲突有一种很常见的误用把 ySize 理解为折叠时的高度。折叠时 DropDown 只显示表头那一条高度大概在 21 到 25 像素之间展开后控件整体变高。如果 ySize 设成 30展开后整个控件只有 30 像素高列表框只能露出半行看起来就像是“展开失败”。想验证这一点可以把 ySize 设成 200展开后对比一下逻辑立刻清楚了。行高大约 20px 时三项列表需要 60px再加上表头 21pxySize 至少设 85 才够。如果列表项超过四五个建议不要硬拉高度而是把 ySize 控在一个范围内控件会在右侧自动出现滚动条不需要额外写滚动逻辑。3.4 两个容易踩的坑句柄判空与资源文件缺失创建控件后一定要判空。STM32F103ZET6 上的 STemWin 例程经常在调试中增加几个测试控件内存池被耗尽后续控件创建失败返回 0然后调用GUI_DROPDOWN_AddString(0, ...)直接死机。内存不足的问题在调试阶段不会马上暴露因为刚上电时内存池是充足的运行几分钟后由于某个窗口没被正确删除内存逐渐泄漏最终到某个界面才崩溃。把判空写进例程里至少能定位到是哪一个控件创建失败。另一个坑是字体资源缺失。默认字体GUI_Font6x8和GUI_Font8x16不带中文如果直接在例程里写入中文字符串界面上会显示为空心方块甚至乱码。STemWin 的字符编码默认按 UTF-8 或 ASCII 处理具体取决于库的配置。例程源码里如果已经带了中文字库文件通常是以gui_font_hz开头或名为FontHZ.c的文件需要在工程中包含它并用GUI_SetFont或对控件单独调用GUI_DROPDOWN_SetFont来指定中文字体。这个细节经常被忽略因为编译不报错运行也不崩溃只是界面显示是乱码。4. DropDown 事件通知与回读把选择变成业务数据4.1 WM_NOTIFY_PARENT 是控件与界面通信的主通道DropDown 被点击、展开、选中项改变时它不会直接调用用户的函数而是向父窗口发送一条WM_NOTIFY_PARENT消息。父窗口的回调函数里需要取出通知数据判断是哪个控件发出的、具体是什么事件。通知数据结构体定义如下typedef struct { int v; // 通知代码如 WM_NOTIFICATION_SEL_CHANGED WM_HWIN hWin; // 发出通知的控件句柄 } WM_NOTIFY_DATA;注意消息送达时pMsg-Data.p指向这个结构体而pMsg-Data.v单独存储通知代码。有些初学者在回调里直接读pMsg-Data.v当作控件 ID这是不准确的控件 ID 需要再从结构体里进一步获取比如通过WM_GetId(hWin)得到创建时传入的 ID。STemWin 源码里的官方例程更倾向于用WM_GetDialogItem配合对话框资源表但对裸窗口写法来说直接拿结构体里的hWin做比较是最直观的。4.2 父窗口回调里的处理代码下面是上一章回调函数_cbMenu的真正实现。它以接收WM_NOTIFY_PARENT为重点在下拉框选中项发生变化时读取新值并更新业务变量static void _cbMenu(WM_MESSAGE * pMsg) { WM_NOTIFY_DATA * pData; int Sel; char buf[24]; switch (pMsg-MsgId) { case WM_NOTIFY_PARENT: pData (WM_NOTIFY_DATA *)pMsg-Data.p; if (pData-hWin hDropSpeed) { if (pData-v WM_NOTIFICATION_SEL_CHANGED) { Sel GUI_DROPDOWN_GetSel(hDropSpeed); GUI_DROPDOWN_GetItemText(hDropSpeed, Sel, buf, sizeof(buf)); g_motor_speed Sel * 40; // 对应 0、40、80 三个档位 } } break; default: WM_DefaultProc(pMsg); break; } }WM_NOTIFY_PARENT分支里先取出pData然后用pData-hWin hDropSpeed判断是本控件的通知。这样做的原因是父窗口下往往有多个控件如果把所有控件通知混在一起处理界面逻辑很快就会乱掉。GUI_DROPDOWN_GetSel返回当前选中项的索引从 0 开始。GUI_DROPDOWN_GetItemText把该项的文本复制到 buf 中第三个参数是缓冲区大小。g_motor_speed是全局业务变量在远离 GUI 的其他模块中使用保证界面层与业务层的解耦。WM_NOTIFICATION_SEL_CHANGED只在下拉列表折叠后、选中项被确认时触发。如果用户点击箭头展开后在列表项上来回移动光标但没有确认这一通知不会发出。各通知代码的含义可对照下表通知代码触发时机WM_NOTIFICATION_CLICKED按下控件任意区域WM_NOTIFICATION_RELEASED释放控件WM_NOTIFICATION_SEL_CHANGED选中项改变且列表收起WM_NOTIFICATION_EXPANDED列表展开WM_NOTIFICATION_COLLAPSED列表收起WM_NOTIFICATION_ITEM_CLICKED点击列表中的项但还未确认选择4.3 把选择值转换成与业务无关的数据以小车内三个档位为例选中一个下拉项本身没有意义有意义的是它对应什么数据。常见的做法是在创建控件时给每个字符串前部固定编号或者在回调里用索引映射到具体参数。下面这个例子把档位选择转换为占空比并更新到全局变量uint16_t PWMDutyLUT[3] { 100, 500, 900 }; void SetMotorLevel(int index) { if (index 3) index 2; TIM_SetCompare1(TIM1, PWMDutyLUT[index]); }这里TIM_SetCompare1是标准库函数把主定时器 TIM1 的通道 1 比较值设为查表结果。下拉框只负责产生一个 0 到 2 的整数具体底层的 PWM 频率、周期、引脚初始化全部交给电机控制模块。界面代码里永远不要直接操作寄存器这是 STM32 工程里 GUI 与业务分离的关键。如果项目里还接入了 Modbus 协议栈这一选择值最终会落入从机寄存器。下拉框回调里做的是把选择写入协议层的数据缓冲区。Modbus 从机帧接收程序通常跑在串口中断里按定时器超时或者长度匹配来拼帧再用 CRC 校验。下拉框选中新档位后协议层在收到主机的读请求时返回新寄存器值整个过程 GUI 层与协议层完全独立回调里不应该出现阻塞型串口发送。4.4 调试 DropDown 交互时最容易卡住的三个点第一个是回调里忘记调用WM_DefaultProc(pMsg)。没有调用时窗口不会正确处理重绘和销毁消息表现为下拉框第一次能展开第二次点击没反应。第二个是在回调里做耗时操作比如用sprintf拼浮点数或调用HAL_Delay。STemWin 的窗口回调运行在 GUI 任务线程中如果在这里阻塞超过几百毫秒界面的触摸响应和刷新全部停住看起来就像死机。第三个是没有区分折叠与选中这两个时机。WM_NOTIFICATION_EXPANDED在点击箭头时立刻发出而WM_NOTIFICATION_SEL_CHANGED在用户点击列表项之后才发出。如果业务逻辑必须等用户完成选择后再动作不要挂在EXPANDED分支里否则展开时就会误触发。调试时还有一个实用技巧在_cbMenu里加上断点每次下拉框交互都会经过WM_NOTIFY_PARENT。断点停在pData-hWin hDropSpeed这一行时查看pMsg-MsgId和pData-v的数值与头文件里的定义值比对。这能快速确认消息路径是否通以及事件是在哪一个阶段丢失的。STemWin 的GUI_DEBUG模式会输出带行号的日志但不要一开始就依赖它先看消息是否到达回调再谈协议问题。5. 进阶让 DropDown 用起来更像商业产品5.1 中文显示的三件套字库、字体与编码默认字体渲染中文全是方块原因是 STemWin 内部没有中文字形数据。解决的办法是把字库 C 文件加入工程然后在创建控件前指定字体。常用做法是从 ST 官方手册或开发板例程中拷贝字体文件以GUI_FontHZ16这类名字存在GUI_SetFont(GUI_FontHZ16); GUI_DROPDOWN_SetFont(hDropSpeed, GUI_FontHZ16);GUI_DROPDOWN_SetFont只影响这一个下拉框比全局GUI_SetFont更可控。编码方面要保证源码文件里字符串的编码和字库表一致。如果字库按 GB2312 编码生成编译器却按 UTF-8 保存源码汉字就会无法匹配。在 Keil 里可以把源文件编码设为 GB2312或者把字符串常量转成宽字符数组。经验判断法是最直接的选择把原来的“低速档”改成D等英文如果英文能显示而中文是乱码问题就在字形数据或编码上。5.2 用皮肤和颜色 API 快速换风格DropDown 在 STemWin 5.x 之后支持皮肤机制换风格不需要自绘。先调用皮肤指定再设置具体颜色GUI_DROPDOWN_SetSkin(hDropSpeed, DROPDOWN_SKIN_default); GUI_DROPDOWN_SetBkColor(hDropSpeed, DROPDOWN_CI_UNSEL, 0x00FFFFFF); GUI_DROPDOWN_SetTextColor(hDropSpeed, DROPDOWN_CI_SEL, 0x00800000);颜色参数是 32 位的 0x00RRGGBB 格式高位 8 位是透明度STemWin 在 RGB565 屏上会自动降级处理。DROPDOWN_CI_UNSEL表示未选中项的底色DROPDOWN_CI_SEL表示当前高亮项的文本色。如果自己实现了皮肤回调要保留对WM_DRAW_ITEM消息的默认处理否则控件的箭头图标会丢失。5.3 失焦自动收起把 DropDown 调教成桌面级交互STemWin 默认行为是点击屏幕其他区域时下拉框仍然保持展开状态这在菜单页面里很碍眼。实现“点击别处自动收起”的核心技巧是在父窗口回调中拦截触摸事件获取 DropDown 展开后的窗口矩形然后判断触摸点是否落在矩形内。下面是可用的实现case WM_TOUCH: { WM_PID_STATE * pState (WM_PID_STATE *)pMsg-Data.p; GUI_RECT rDrop; if (pState-Pressed 0 hDropSpeed ! 0) { WM_GetWindowRect(hDropSpeed, rDrop); if (!GUI_RectContainsPoint(rDrop, pState-x, pState-y)) { GUI_DROPDOWN_Collapse(hDropSpeed); } } break; }WM_TOUCH消息在触摸事件发生时发给当前窗口。WM_PID_STATE的x和y是屏幕坐标Pressed 0表示这次是抬起事件而不是按下事件这样能避免手指还在控件上时就把列表收起。WM_GetWindowRect返回的是 DropDown 展开后的实际窗口矩形折叠状态下这个矩形高度很小但在展开状态下它正好覆盖整个列表区域。这个逻辑的核心就是“用窗口实际位置判断触摸归属”比尝试枚举所有子控件要简单得多。如果希望展开时列表不遮挡下方控件也可以直接把 ySize 调小并借助扩展创建函数里的自动滚动条标志位而不是在业务逻辑里反复移动控件。本文还有配套的精品资源点击获取
返回列表