ARTICLE DETAIL

资讯详情

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

STM32F103与HMI串口屏双向通信完整实现方案

STM32F103与HMI串口屏双向通信完整实现方案 简介面向嵌入式开发者的STM32F103与HMI串口屏双向通信工程包适用于工业控制、智能设备、数据采集等需要人机交互的项目能有效解决串口屏与单片机之间数据收发、指令响应和界面控制等常见问题。资源共三百零九个文件主要包含C语言源码、头文件、工程配置、编译中间文件、HMI屏配置文件以及说明文档整体约十一兆字节便于直接导入常用集成开发环境查看与移植。已有三千八百一十九人学习下载适合二次开发和快速参考。包内代码基于标准外设库编写完整覆盖串口初始化、中断收发、数据帧格式定义、LCD显示驱动与错误处理等模块并附有工程清理脚本和烧写文件结合实例可深入理解双向通信协议、波特率配置和实时响应机制也可根据实际需求修改界面交互逻辑帮助开发者缩短调试与开发周期。 作为一个常年跟嵌入式设备和上位机打交道的老开发我对于“界面”这件事的感情一直很复杂。早年做项目最头疼的就是给STM32配显示屏要么是8080并口屏光接线就占掉十几个IOPCB走线走到怀疑人生要么用SPI屏刷新一慢菜单切换卡成PPT客户当场脸色就变了。后来接触了HMI串口屏算是把这块的体验彻底翻了篇。这篇文章想跟你仔细聊聊HMI串口屏和STM32F103之间双向通信的完整实现方案。无论你是刚接触串口屏的新手还是想把手头的老项目改成屏机分离架构这篇文章都会从硬件选型、串口组网、帧格式设计、双向数据流解析、代码实现到实战排错给你捋出一套可直接抄作业的路径。整套方案我已经在多个工业控制项目里实际跑过稳定性经历了现场验证不是纸上谈兵。1. 方案为什么这么定HMI串口屏与STM32F103的分工逻辑1.1 串口屏解决的核心痛点很多人在做设备端界面时会陷入一个误区界面逻辑非要跟MCU业务逻辑塞在同一个芯片里。这在产品原型阶段问题不大但一旦进入量产或者迭代期麻烦就来了——改一个菜单结构可能就要动界面代码界面代码跟业务代码混在一起回归测试的成本高得吓人。HMI串口屏把这些事彻底拆开了。它的内部本身就是一个完整的图形处理系统自带Flash存储界面资源运行着独立的界面引擎。你在电脑上用上位机软件比如淘晶驰的USART HMI、迪文的DGUS、大彩的VisualTFT画好界面下载到屏幕里屏幕自己就能跑起来。而STM32F103作为主控只需要通过UART发送报文控制屏幕显示内容同时接收屏幕上传的触控事件然后再执行对应的业务逻辑。屏幕管好看主控管干活各司其职整个项目的分工一下就清楚多了。1.2 STM32F103在这套系统里的不可替代性不少新人有个疑问STM32F103都十多年前的芯片了现在随便一个国产MCU资源都比它强为什么还在大量项目里用答案其实很朴实供应链成熟、资料丰富、开发工具链稳定、成本压得极低。在工控和消费类设备里稳定性和可采购性往往比极限性能更重要。STM32F103系列最常用的几个型号比如STM32F103C8T664KB Flash20KB RAM和STM32F103RCT6256KB Flash48KB RAM跑一个串口屏双向通信的协议栈绰绰有余。一个基础的双向通信链路软件层面只需要三个USART外设一个跟屏通信一个做调试日志一个给外部模块加上几个定时器芯片的资源利用率通常连一半都到不了。这也是它作为“屏幕下位机”的优势——你不需要一颗高配芯片去处理界面渲染省下来的成本就是实打实的竞争力。2. 双向通信的底层设计帧格式、变量地址与协议规则2.1 通信链路怎么搭3根线的事串口屏和MCU之间的物理链路本质上就是一组UART。接线只需要3根线屏幕的TX接STM32的RXPA3/USART2_RX或其他复用引脚屏幕的RX接STM32的TXPA2/USART2_TX或其他复用引脚两端共地GND必须连这是很多人第一次调试死活不通的原因这里有一个非常关键的细节如果STM32的供电系统和屏幕的供电系统不是同一个电源通信前务必确认GND已经连到一起。UART的电平参考地是同一个地如果地没共两边的电压参考不一致轻则数据乱码重则烧毁芯片IO口。我亲眼见过一个工程师拿两个独立电源给开发板和串口屏供电忘了共地出来的数据全是一堆FF排查了整整一个下午。如果你的产品里STM32的逻辑电平和屏幕的逻辑电平不一致比如STM32跑3.3V屏幕是5V TTL电平必须加电平转换电路。多数串口屏标称是TTL电平很多型号兼容3.3V但不要想当然——上电前翻一下屏幕的硬件手册确认逻辑电平范围实在是心里没底就用一个MAX3232做了隔离转换稳妥第一。2.2 上行和下行数据流怎么区分双向通信意味着两条数据链路并存下行链路MCU - 屏幕STM32把传感器数据、设备状态、运行进度等发送给屏幕屏幕更新对应控件。上行链路屏幕 - MCU用户在屏幕上触摸按钮、滑动滑块、输入参数屏幕把这些事件封装成帧回传给STM32STM32解析后执行对应逻辑。这两条链路在协议设计上必须明确区分。我见过不少项目初期图省事上行和下行共用一个帧类型代码结果后续扩展时解析逻辑越写越乱最后只能推倒重来。正确的做法是在帧结构里包含“帧类型”字段比如0x01代表下行数据请求0x02代表上行事件上报0x03代表应答这样两边解析时逻辑单一扩展性也好得多。2.3 帧格式设计一个实践过的协议模板串口屏厂商通常有自己的私有协议但如果你用的是开放协议的串口屏如淘晶驰的T5L系列、迪文的DGUS或者你想在屏幕的协议之上再做一层应用层封装我建议自己定义一套轻量级帧结构。经过多个项目的验证下面这套帧格式既简单又可靠字段名字节数说明帧头2固定为0xAA 0x55用于同步设备地址1组网时区分多个设备单机时填0x01帧类型10x01下行控制0x02上行事件0x03应答数据长度2数据域字节数小端模式数据域N具体业务数据校验1异或校验或CRC8校验位这里我强烈建议大家不要图省事用简单的累加和。串口屏工作环境往往是有电机、继电器、变频器的工业现场干扰源多累积和碰撞概率不低。我通常用异或校验BCC一字节的异或校验代码量很小解析效率也高。如果数据安全性要求极高可以用CRC16代价是代码和计算量上去了但对STM32F103来说依然毫无压力。uint8_t calc_bcc(const uint8_t *data, uint16_t len) { uint8_t bcc 0; for (uint16_t i 0; i len; i) { bcc ^ data[i]; } return bcc; }2.4 串口屏变量的地址映射机制理解串口屏数据交互的关键在于理解“变量”的概念。在PC画界面时你会给每个控件关联一个变量比如设定温度关联到地址0x1000、当前转速关联到地址0x1001。设备和屏之间通信操作的本质其实就是对这些变量地址进行“读”和“写”。这个机制跟单片机里的寄存器映射很像。屏幕端维护一块“变量表”MCU发指令给某个地址写入数值屏幕上的文本框就会实时刷新用户在屏幕上拖动滑块屏幕把滑块的新数值以地址数据的形式发回给MCUMCU解析后就知道滑块当前处于什么位置。理解了这一点整套双向通信的代码逻辑就有了很清晰的轮廓要显示数据就往地址里写要读取用户操作就监听地址的变更事件。3. 核心代码与实操把双向链路跑起来3.1 硬件连接与CubeMX初始化以STM32F103C8T6和淘晶驰USART HMI串口屏为例接线方案如下屏幕TX - STM32 PA10USART1_RX屏幕RX - STM32 PA9USART1_TXGND - GND使用STM32CubeMX做初始化是最省事的路子几分钟就能把时钟和串口配置好。配置要点时钟树使用外部8MHz晶振PLL倍频到72MHz主频。USART1模式设为“Asynchronous”波特率9600屏幕端也要设成一致数据位8停止位1无校验。开启USART1全局中断用于接收数据处理。如果代码里需要轮询等待屏幕应答可以再加一个定时器做超时管理。这里有个实操建议如果项目对通信速率有要求可以直接上115200但前提是屏幕端的“串口波特率”参数也相应改成115200这个参数在屏幕的工程配置里不是每次开机后临时能改的。波特率不一致是通不出数据的头号原因务必两边核对。3.2 发送数据到屏幕实测稳定的发送函数串口屏厂商的协议各有不同以淘晶驰USART HMI为例MCU给屏幕发送数据的指令格式是命令头 指令 参数 结束符0xFF 0xFF 0xFF比如要往地址为0x1000的变量写入数值123指令串是// 核心指令写数值到变量地址 // 格式: 0x01 地址高字节 地址低字节 数据高字节 数据低字节 0xFF 0xFF 0xFF // 发送示例 uint8_t cmd[8] {0x01, 0x10, 0x00, 0x00, 0x7B, 0xFF, 0xFF, 0xFF}; HAL_UART_Transmit(huart1, cmd, sizeof(cmd), 100);如果你用的是迪文DGUS屏指令会有细微出入比如迪文的0x82指令用于写变量但整体思路一致操作的都是“变量地址数据”。写一个通用的“向串口屏变量写整数”的函数不难但真正要留心的细节在于发送节奏。串口屏的协议解析处理虽然快但在高频率刷新数据时仍然可能出现解析不过来或者显示卡顿的情况。我实测下来向屏幕连续发送时间间隔保持在10ms以上稳定性最好。这个时间间隔在逻辑上可以通过简单的状态机或者定时器控制不要用HAL_Delay死等那会把整个MCU的实时性拖垮。void hmi_set_value(uint16_t addr, uint16_t value) { uint8_t cmd[7] {0x01, (uint8_t)(addr 8), (uint8_t)(addr 0xFF), (uint8_t)(value 8), (uint8_t)(value 0xFF), 0xFF, 0xFF}; // 发送7字节最后补一个结束符0xFF共8字节 uint8_t buf[8]; memcpy(buf, cmd, 7); buf[7] 0xFF; HAL_UART_Transmit(huart1, buf, 8, 100); }注意这里我把结束符分开写了因为不同型号对结束符的个数要求不一样。淘晶驰的屏有些固件配置了2字节结束符有些要3字节具体看你屏的出厂配置。最稳妥的做法是拿到屏之后先查固件版本和协议配置再写发送函数别一上来就按老经验硬套。3.3 接收屏幕事件中断接收加状态机解析屏幕向MCU发送事件通常发生在用户触控的时候。比如用户点击了“启动/停止”按钮屏幕会把按钮的触控事件封装成指令发给MCU以淘晶驰为例指令格式为按键触控返回的指令码控件编号0xFF 0xFF 0xFF。接收侧的处理直接决定整个通信的稳定性。我用的是“串口空闲中断 DMA接收”和“逐字节中断接收 状态机解析”两种方案前者适合数据包固定大小或者你能预估最大包长的场景后者灵活且适合包长变化大的场景。我平时用得更多的是逐字节中断加状态机解析原因很直接简单可控调试方便。串口屏的事件回报包长短不一状态机天然适合处理这种“边收边判”的场景。基本逻辑如下typedef enum { ST_IDLE, ST_CMD, ST_DATA, ST_END1, ST_END2, ST_END3 } parse_state_t; parse_state_t state ST_IDLE; uint8_t rx_buf[64]; uint8_t rx_idx 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { uint8_t byte rx_tmp; rx_process(byte); HAL_UART_Receive_IT(huart1, rx_tmp, 1); } } void rx_process(uint8_t byte) { switch (state) { case ST_IDLE: rx_idx 0; rx_buf[rx_idx] byte; state ST_CMD; break; case ST_CMD: rx_buf[rx_idx] byte; state ST_DATA; break; case ST_DATA: rx_buf[rx_idx] byte; if (byte 0xFF) state ST_END1; else state ST_DATA; break; case ST_END1: rx_buf[rx_idx] byte; if (byte 0xFF) state ST_END2; else state ST_DATA; break; case ST_END2: rx_buf[rx_idx] byte; if (byte 0xFF) { // 完整的帧已收到开始解析 parse_frame(rx_buf, rx_idx); state ST_IDLE; } else { // 说明不是结束符回退到数据状态继续接收 state ST_DATA; } break; default: state ST_IDLE; break; } }这段状态机逻辑比较朴素但胜在直观。有一点要注意状态机里的数组rx_buf要留足长度。我一开始图省事定义16字节后来一次屏幕连发多个控件的事件直接把缓冲区写溢出系统跑几小时就崩一次排查了很久才定位。改成64字节之后就再没出现过这类问题。3.4 屏上报数据的解析与业务联动当MCU收到屏幕发来的完整帧后需要根据不同的控件ID和值执行对应的业务逻辑。以“启动”按钮为例假设按钮控件编号是btn_start对应的指令格式是屏幕返回的“触控指令控件ID”实操中我们会在上位机软件里给这个按钮的“按下事件”绑定一个值变更为1的变量然后把变量地址改到一个约定好的地址比如0x2000。这样MCU收到0x2000的写入事件就知道用户按了启动。一份典型的事件映射表大概长这样事件源变量地址数据含义MCU动作启动按钮0x20001按下置位启动标志停止按钮0x20011按下清除启动标志安全停车设定温度0x2002数值更新PID目标值运行模式0x20030手动1自动切换控制模式故障复位0x20041按下清除故障状态在MCU代码里我一般把解析出来的变量地址和值扔进一个轻量级的“事件表”里然后由主循环统一消费这样不会因为处理屏幕事件卡住定时器中断。这个思路对新手尤其重要——不要在中断回调里执行复杂的时间敏感操作UART中断里只做数据搬移和状态更新真正的业务计算放到主循环。4. 调试与避坑双向通信中我踩过的那些坑4.1 通信完全不通时的排查顺序如果你按上面的步骤做完发现屏幕收不到MCU的数据或者MCU收不到屏幕的事件最先该做的不是改代码而是按一个固定顺序排查。第一确认波特率。90%第一次通信失败的人问题都出在这里。打开屏幕的工程项目配置看屏幕设置的波特率再跟MCU的USART配置比对。两边不一致屏幕上会偶尔出现一个“握手失败”或者“通信超时”的提示但部分屏不提示只是静默无响应。第二确认接线。串口屏的TX、RX别接反了TXTX互接是新手最容易犯的错误。这里建议用串口助手调试工具直接跟屏幕单独通信如果串口助手能控制屏幕显示说明屏幕侧没问题问题在主控侧如果串口助手也控制不了那故障大概率在屏幕的配置或者线序上。第三确认共地。前面说了不共地必出乱码甚至通信完全失败。这条如此简单却因为惯性思维被大部分人忽略。第四用逻辑分析仪看波形。如果前三步都通过了还是不通信用逻辑分析仪抓一下TX脚和RX脚的波形不用分析细节只要看通信时有没有电平翻转就能迅速定位是不是芯片根本没把数据发出去。4.2 数据乱码和偶发丢包的处理思路串口屏通信中乱码和丢包是高频问题。乱码的原因除了波特率不匹配还有可能是两边UART初始化时校验位配置不一致。比如STM32配置了无校验8N1屏幕那边却是奇校验8O1出来的帧就全是废数据。这种问题在博文里看起来很低级但真到了现场尤其是项目从别人手上交接过来时坑得非常深。偶发丢包则通常和电源质量有关。串口屏的背光、屏幕刷新都是瞬时大电流器件如果供电电路设计不合理通信瞬间地线上会出现较大的压摆干扰导致UART采样错位。处理方法屏幕和MCU的电源尽量分开走线在屏幕电源输入处加一颗100uF电解电容和0.1uF陶瓷电容退耦别舍不得这两颗电容钱。在软件层面丢包往往发生在MCU忙着做耗时操作而无法及时响应串口中断时。虽然中断是抢占式的但如果你在某个关中断的临界区里待太久数据照样丢。一般我建议把UART中断优先级调成高于或等于定时器中断除非某些硬件定时器对响应有极端要求否则没必要为了省一点CPU时间丢数据。4.3 不同品牌串口屏的协议差异与兼容处理市面上的串口屏品牌很多协议习惯各不相同。比如淘晶驰直接用文本格式指令简单直观适合快速开发迪文走的是二进制协议指令效率高适合数据量较大的项目大彩科技则有自己的协议栈同时支持Lua脚本在屏端跑逻辑。没有绝对的好坏关键是做项目之前把协议吃透不要混用。我个人的处理方法是把“屏幕交互层”封装成一组统一的APIvoid hmi_display_temp(float temp); // 显示温度 void hmi_display_rpm(uint16_t rpm); // 显示转速 void hmi_set_status(uint8_t status); // 设置运行状态 void hmi_event_handler(uint16_t addr, uint16_t value); // 事件入口这样即便中途换了一个品牌的屏幕底层发送函数重写一套上层业务逻辑完全不用动。对于产品可能多型号共存的场景这层抽象能省下大量重复工作。4.4 “双向”联调的额外经验让STM32F103停机模式下依然保持通信一个我做过好几次的高级需求是设备平时处于低功耗模式但屏幕上有“唤醒”按钮用户一触摸屏幕要把事件发给MCU把主控唤醒。STM32F103的停机模式Stop Mode可以通过UART外部中断唤醒。但有一个坑进入停机模式前USART的时钟还在一旦进入停机时钟会停掉UART外设也就不工作了。这时候直接靠UART中断唤醒是做不到的。常规做法是屏幕发送唤醒事件的同时额外通过一个IO口产生一个下降沿接到STM32的EXTI引脚上用外部中断唤醒MCU。这个需求在工业面板和便携设备上很常见本身并不复杂但很多人第一次做低功耗联调时容易卡在这。如果你有类似需求记得在原理图设计阶段就留一个“屏唤醒”引脚到MCU的EXTI输入省得后期飞线。5. 一个参考实现完整的数据上报与屏幕显示波形说到整机功能做过的项目里最有代表性的一个是“串口屏显示波形”的需求。这在很多仪器仪表类设备上非常常见而且能直观检验双向通信的实时性和稳定性。实现思路其实不难MCU把ADC采集到的数据比如电流采样值实时发送给串口屏的一个“波形显示”控件屏幕内部把数据点连成曲线。STM32F103的ADC采样率最高可以做到几万次每秒但串口通信的带宽是瓶颈。9600波特率下1秒最多传大概960字节一个点占2字节最多也就每秒钟刷400多个点刷新率明显不够115200波特率下则可以到每秒4000多点刷新率基本满足实时显示需求。这里分享一个经验不要每采一个点就发一个独立帧那会把带宽几乎全部浪费在帧头和校验上。正确做法是——MCU内部先攒齐一包数据比如64个字节再一次性发出去屏幕端每收到一包就刷新一次波形。这样的“批量传输”策略能把有效数据占比提升一倍以上。实测在115200波特率下用这个方案把1024点波形刷到屏幕上肉眼看不到任何卡顿。屏幕端的控件变量地址和数据格式不同品牌的波形控件定义不同淘晶驰的波形控件一般需要配置数据变量名迪文的DGUS支持类似功能。到这一步整个系统的数据链路就是完全打通的MCU采集 - 串口帧封装 - 屏幕解析 - 波形显示反过来用户在屏幕上点击“暂停采集”事件上行到MCUMCU停止传输数据。一整套双向闭环就成立了。6. 最后的实践心得串口屏加STM32F103这套组合最大的魅力在于它把“界面开发”和“逻辑开发”彻底解耦了。界面改版完全不碰主控代码主控升级也不影响屏的资源项目管理上甚至可以让两个人并行开发一个调UI一个写逻辑互不干扰。在我经手的项目里这一套几乎是中小型设备人机交互的黄金搭档。几个关键心得再强调一遍不要低估帧格式设计的重要性前期花半小时定好协议后期能省好几个晚上排查BUG不要忽略共地再简单的接线上电前也要确认这是所有人踩过的最基础的坑不要嫌麻烦跳过上位机模拟调试先用PC端的串口助手把屏幕的收发协议调通再接到单片机上联调能让你把问题范围缩小一半。如果你正在纠结选什么屏幕、怎么做通信、或者通信调不通希望这篇文章能把你的思路理清楚。照着上面的框架走一遍大部分双向通信的坑你都能提前避开。项目做完之后那份“屏幕归屏幕主控归主控”的清爽感是值得的。本文还有配套的精品资源点击获取
返回列表