ARTICLE DETAIL

资讯详情

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

FPGA驱动串口屏USART_HMI:从协议到调试全解析

FPGA驱动串口屏USART_HMI:从协议到调试全解析 写这个系列之前我一直觉得FPGA搞显示是很劝退的一件事。数字逻辑写到能跑UART、能收发字符串已经算入门了结果一看需求要把采集到的电压、温度、状态帧值显示在屏幕上还得有几个按钮能切页面——很多人在这一步直接转投ARM了。直到我把USART_HMI串口屏引入项目才发现FPGA做交互界面也可以这么省心。这篇是FPGA串口收发字符串系列的第四篇专门把串口屏USART_HMI这块讲透包括协议帧格式、FPGA侧指令发送模块怎么写、联调时会遇到哪些坑以及后续可以往双向交互延伸的方向。手里已经跑通UART收发模块的朋友这一章可以直接落地就算前面几篇没读只要懂串口字节怎么发也能跟上。1. 为什么FPGA项目里我会选串口屏来显示1.1 显示方案的选型对比很多FPGA工程师第一次面对要给项目加显示这个问题时列出的候选方案大概跟我当年差不多数码管、LCD1602/COG12864、SPI或者RGB接口的TFT屏最后才轮到串口屏。我把实际对比结果放在一张表里大家感受一下差异。方案显示能力人机交互开发工作量FPGA资源占用适合场景数码管数字和少量字母内容有限基本无低动态扫描即可低电压、计数、状态指示LCD1602/COG12864字符或简单图形需自己取模无或按键扩展中初始化时序和字库都要处理中简单文本显示SPI/RGB接口TFT色彩丰富像素级控制需要自己写触摸和GUI高显存、字库、GUI框架全自己做高RGB屏还得外挂SRAM/SDRAM对显示刷新速度要求极高的场合USART_HMI串口屏彩色显示文字、图形、曲线、图片均可自带触摸事件上报低上位机做界面FPGA只发指令极低占两个IO和一个UART设备状态显示、参数面板、人机交互这里不是说数码管和TFT不好而是工程上有性价比的考量。数码管适合那种看一眼就够的数据TFT屏适合需要高频刷新画面的场景但代价是FPGA里要维护一整套路显存和绘图库开发周期以周为单位。串口屏把这块全包了它内部有一颗主控芯片专门负责解析指令、刷新LCD、扫描触摸FPGA要做的只是按协议把字符串或者数值帧发过去。1.2 USART_HMI解决的核心问题用一句话概括串口屏的本质它不是一块普通显示屏而是一个带GUI引擎的UART从设备。你在上位机软件里画好界面拖好文本控件、数字控件、按钮分配好变量地址烧录到屏幕里然后FPGA这边只需要通过串口告诉它把变量0x0001的内容改成多少多少屏幕上对应的控件就会自动刷新。这和传统TFT方案完全是两种思维方式。以前是FPGA逐像素去画现在是FPGA只发命令画面怎么渲染、字体怎么显示、按钮怎么响应都是串口屏自己去处理。就像一个是自己调油漆刷墙另一个是打电话让装修队干活活一样能干完但工作量根本不是一个量级。我用串口屏之后最直观的感受是FPGA侧的逻辑可以专注在信号采集、算法处理、状态控制这些真正核心的事情上界面交互只是偶尔发一串字节的事。开发周期从几周缩短到一两天而且改界面布局不用重新综合FPGA工程只改串口屏上位机的工程再烧录就行这在项目调试阶段非常救命。1.3 什么场景不适合串口屏串口屏不是银弹它的短板也很明显我这里把丑话说在前面。首先是刷新速度以115200波特率为例理论上一秒只能传约11520字节一条简单的写变量指令大概10字节左右也就是说一秒最多发一千多次但实际考虑帧间隙和串口屏内部处理时间稳定跑到几十赫兹就到头了。所以它适合展示状态数据和参数不适合做高速波形输出之类需要像素级实时更新的界面。其次是成本串口屏的单价明显高于同等尺寸的裸屏加驱动方案如果产品已经确定要大规模量产而且显示内容极其固定那还是老老实实自己驱动屏幕更划算。最后还有一点容易忽略串口屏是带固件的黑盒一旦遇到官方固件不支持的协议细节你没法绕过它直接操作像素灵活度确实比不上自己写驱动。理解了这个边界你才知道什么时候该用它什么时候不该用。2. USART_HMI协议先看懂再动手2.1 指令帧的基本结构串口屏虽然界面华丽但通信协议其实相当简洁核心就是一帧一指令。USART_HMI的指令帧通常由帧头、数据长度、指令码、数据区和校验字段组成大致是这个样子字段长度说明帧头1字节固定值0xA5表示一帧的开始数据长度1字节从指令码到校验之前的字节数用于接收端分帧指令码1字节标识这条指令要做什么比如写变量、读变量、跳页面数据区N字节变量地址、数据类型、具体的数据内容校验1字节通常是对前面数据区做异或和用于验证帧没有传错做FPGA的人看到这种结构应该很亲切它跟我们的UART状态机是天然匹配的。接收端先等帧头0xA5收到之后再读数据长度然后按长度把剩下的字节收满最后做一遍校验帧就完整还原了。这也是为什么串口屏特别适合跟FPGA配合——协议本身就对硬件逻辑友好不需要PC上那种复杂的缓冲解析。不同品牌和固件版本在字段定义细节上有差异比如淘晶驰TJC系列、Nextion系列和迪文DGUS系列就是三套不同的体系甚至同一品牌不同批次固件对长度字段的计数方式都可能不一样。我在项目里固定用淘晶驰的USART_HMI协议本文也以它为例。动手之前一定要把自己手上屏的协议手册打印出来对照别拿网上例程的帧直接抄。2.2 最常用的几条指令用串口屏控制界面绝大多数时间就三条指令写变量、读变量、跳页面。所谓变量就是你在上位机里给每个控件绑定的数据通道比如文本控件t0绑定了变量v0数字控件n0绑定了变量n0这些变量在屏的内部存储里都有固定地址FPGA只要往对应地址写数据控件就会刷新。写变量指令的语义是往地址X写入数据Y。数据Y可以是字符串也可以是二进制数值具体是哪一种由数据类型字段区分。举个例子如果要在文本控件里显示OK两个字指令帧的数据区就包含控件地址、字符串类型标识、以及字符O和K的ASCII码。FPGA要做的事情并不复杂地址、类型、ASCII码都是确定的算好长度和校验把整帧发出去即可。读变量指令是反过来FPGA向屏请求读取某地址的数据屏收到后会把当前值回传。这条指令在做双向交互时很有用比如FPGA上电后要读取屏上保存的参数。跳页面指令就更直白了告诉屏切换到某个页面ID适合做多菜单、多界面切换。在FPGA里设计发送模块的时候我建议把这些指令统一封装成一个指令序列器每条指令是一个固定模板需要变化的只是地址和数据区。模板加上动态参数拼成一帧再送到UART模块发出去。这样代码结构清晰后面要加新指令只是加一个模板的事。2.3 控件变量地址与数据类型的坑用串口屏上位机拖控件的时候很多人会忽略地址分配这件事。上位机软件的变量控件列表里每个变量都有一个地址一般是按创建顺序从0x0000开始编号。FPGA侧写指令时用的就是这些地址。如果你只记得控件名字不记得地址那写指令时就会对不上。我一般会在上位机里把变量名和地址做成一张对照表和FPGA代码放同一个工程目录下改一次同步一次不然调试时极容易晕。数据类型字段是另一个容易出岔子的地方。字符串和数值的编码方式不一样字符串直接用ASCII码字节流数值可以用二进制直接传输也可以转成ASCII形式的数字字符。串口屏接收不同数据类型时解析逻辑完全不同一旦类型标识传错屏上显示的就是乱码或者0。这个字段在上位机协议文档里有明确的取值表写代码前要确认好。还有一个细节容易被新手忽略字符串长度和控件显示区域的关系。文本控件如果设置了最多显示4个字符你一帧发8个字符进去要么被截断要么把旁边内容挤乱。所以FPGA侧发送字符串之前要么自己做好截断要么在上位机里把控件长度放宽。我自己写模块时会在指令序列器里加一个最大长度寄存器配合控件属性一起设置避免指令本身没问题但显示效果稀碎。3. FPGA端串口指令发送模块怎么设计3.1 模块划分思路FPGA端驱动串口屏不需要一个万能协议栈那反而把事情搞复杂了。我习惯把逻辑拆成三块指令序列器、字节发送器、参数生成逻辑。字节发送器说白了就是系列前几章写的UART TX模块负责把一个字节变成串行波形发出去带busy或者done信号。参数生成逻辑负责算地址、算长度、做校验比如数值转ASCII、字符串拼接。指令序列器是大脑负责按顺序把一帧里的字节逐个送给字节发送器中间控制节奏保证帧内字节连续、帧间留出间隔。这个划分的好处是每一块都能单独测试。UART TX单独连电脑验证过参数生成逻辑可以用Modelsim仿真对比输出字节流指令序列器只要状态机不跑飞整帧顺序就不会乱。等到最后联调时问题范围能快速锁定到某一小块不会满工程找bug。3.2 状态机加FIFO的设计方式指令序列器我一律采用先组装到发送FIFO再自动发送的方式而不是一字节一状态地硬憋。为什么因为指令帧拼装过程中可能要插入动态参数如果整个帧的字节都是一个时钟一个时钟地直接塞给UART中间一旦被更高优先级的逻辑打断帧就残了。用FIFO缓冲一层组装过程可以分步完成组装完毕后UART模块从FIFO里连续取字节发送帧的原子性就有保证。核心状态机示意代码大致是这样// 指令序列器核心状态机节选 localparam IDLE 3d0; localparam ASSEMBLE 3d1; localparam WAIT_EMPTY 3d2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; end else begin case (state) IDLE: if (tx_start) begin byte_cnt 0; state ASSEMBLE; end ASSEMBLE: begin // 每个时钟写一个字节到FIFO // 先写帧头0xA5再写长度、指令码、数据区、校验 fifo_wr_en 1b1; fifo_wr_data frame_byte_gen(byte_cnt); if (byte_cnt frame_total_len - 1) begin fifo_wr_en 1b0; state WAIT_EMPTY; end else begin byte_cnt byte_cnt 1b1; end end WAIT_EMPTY: if (fifo_empty) begin // 整帧发送完成 tx_done 1b1; state IDLE; end endcase end end实际工程里frame_byte_gen不会是一个简单函数而是根据byte_cnt查表0号字节固定是0xA51号字节是长度2号字节是指令码后面的字节可能是变量地址、数据类型、数据内容最后是校验字节。动态参数部分在上层逻辑触发tx_start之前就预先算好放在参数寄存器里组装阶段只是把它们按顺序搬进FIFO。UART模块那边从FIFO读字节、逐字节发送发送完成信号把FIFO读指针推进。这个方案的妙处在于FIFO天然隔离了两个时钟域如果有的话和两级逻辑组装过程不占用UART发送时间帧与帧之间还能方便地插入任意延时。3.3 长度和校验的计算时机长度和校验是FPGA工程师最容易写错的地方。长度字段指的是从这个字段自身后面开始、直到数据区结束的字节数。校验字段通常是对长度、指令码和数据区做异或帧头0xA5不参与校验。这两者的计算必须放在组装阶段的开头算出结果存进寄存器随帧一起写入FIFO而不是在UART发送过程中临时去算。为什么强调时机因为很多新手会在发送每一字节的组合逻辑里去动态计算校验结果综合出来的电路出现奇怪的时序问题帧内容对了但校验总不对。我现在的做法是先算后发。帧长如果固定长度字段就直接是一个常量校验则在PREPARE阶段用组合逻辑异或树算好或者更省事——因为一帧字节数不多完全可以在写入FIFO的时候同步计算校验最后一个字节写进去时校验值也已经算出来直接跟着写进去。这里还有个工程上的建议第一版尽量把所有指令设计成定长帧。定长帧的长度字段是常量地址偏移是常量校验树也可以预先确定结构调试难度会低很多。等定长帧跑通了再改成变长帧处理动态字符串就会从容很多。4. FPGA变量如何变成屏幕上的数字和字符串4.1 数值转ASCII的处理FPGA内部处理的数据是二进制数但串口屏要显示的是人类能读的十进制数字。所以中间必须有一个数值转ASCII的过程。最基础的方法是把二进制数转成BCD码然后把每一位BCD码加上0x30就得到对应数字的ASCII码。比如二进制数27转成BCD码是0010 0111即十位2、个位7加上0x30后得到0x32和0x37对应字符2和7。二进制转BCD常用的是加3移位算法也叫移位加3法一段循环逻辑就能实现不需要除法器非常适合FPGA。对于0到9999范围的数值移位16次就可以完成转换占用资源很少。如果你要显示的是有符号数还得先处理符号位在前面加一个负号0x2D。我看到有些人在FPGA里直接用取模和除法运算符去转ASCII仿真没问题但综合出的除法器吃资源比较多而且路径延迟大。在小规模显示应用里加3移位法既省资源又容易控制时序是我比较推荐的方案。4.2 字符串指令帧的拼接实例举个实际例子假设要把温度值27度显示在屏上的文本控件控件地址是0x0001。FPGA侧要发出的指令帧大致是这样的先帧头0xA5然后是长度、写变量指令码、地址高字节、地址低字节、数据类型标识接着是2和7两个ASCII字节最后是校验。整个过程在指令序列器里就是按byte_cnt依次输出这些字节。数值转ASCII的结果怎么和固定帧头拼接呢我一般用一个可变长度移位寄存器组方式把固定部分、动态部分、校验部分分别存到三个小寄存器区组装时按顺序选通。如果帧长固定动态部分也只是长度固定那整个帧甚至可以定义成一个寄存器数组ASSEMBLE状态里按地址查表输出即可。调试的时候有一个很实用的技巧在UART TX的输出引脚上接逻辑分析仪或者直接在FPGA里把那路信号引到一个空闲引脚用示波器看波形。整帧字节的时序波形一目了然哪里的字节不对对照协议文档一比对就能定位到是拼接逻辑还是数值转换的问题。4.3 显示刷新节奏与带宽估算串口屏能接受的显示刷新速度没有你想象的那么高。我实测下来状态类数据10到20Hz刷新就已经非常平滑了没必要每毫秒无脑刷。原因有两个一是串口带宽有限115200波特率一秒钟才能传一万多字节一条指令就是十几字节刷新频率越高留给其他指令的带宽就越少二是串口屏内部解析帧、刷新LCD也需要时间指令过来太密会出现排队甚至丢帧。所以我一般在FPGA里给串口屏发送加一个节流器比如用一个计数器产生10ms或50ms的周期脉冲每个脉冲最多触发一帧显示指令。这样既保证了人眼看起来连续又不会把串口屏和串口带宽压到极限。我在做多通道数据采集显示时用5ms周期更新一个页面同时还要处理触摸事件上报没有出现过串口拥堵的问题。记住一个原则显示类的数据宁可少刷不要暴刷。5. 联调实录这几个坑我替你们踩过了5.1 波特率误差带来的隐性故障FPGA和串口屏之间的波特率如果对不上表现不是完全不通而是时好时坏尤其当误差积累到采样的临界点时会随机出乱码。这个坑隐蔽性很高因为它不稳定复现很容易让人怀疑是屏坏了或者代码逻辑错了。正确的做法是先用频率计确认FPGA系统的基准时钟频率再算波特率分频值。比如50MHz系统时钟跑115200波特率分频值是50_000_000/115200约等于434取整后误差不到0.01%基本可以忽略。但如果你的板子用的是24MHz或者12MHz晶振分频后的误差就可能超过1%这时候就要考虑换一个更合适的波特率或者用小数分频器来处理。我的习惯是FPGA侧和串口屏都固定用115200这几乎是两者之间最稳的一个平衡点。9600虽然更抗干扰但显示刷新有明显的打字机感不太适合做界面。另外要注意串口屏模块的默认波特率可能和它实际跑的波特率不一致收到屏之后第一次通电就要用串口助手确认屏的配置。5.2 帧被切断和指令被吞的问题联调时最常遇到的现象是屏上偶尔显示一次正确内容然后连续发好几条指令都没反应或者显示出来的是残缺数据。排查下来的典型原因是FPGA侧的指令序列器在多帧连续发送时帧间隙控制不好或者处理器逻辑在发送过程中抢占了串口发送的优先级导致帧头后面的数据没跟上。解决思路就是我前面说的FIFO缓冲方案让整帧组装成一个不可分割的整体。发送期间绝不允许其他逻辑往UART模块里插入字节必须等当前帧的最后一个字节发完、FIFO清空之后才允许下一帧开始组装。这个原子性设计不是可选项而是必须项。另外帧与帧之间要留出足够的间隔让串口屏来得及解析上一帧。我实测淘晶驰系列的屏处理一帧普通指令大概需要几毫秒到十几毫秒不等取决于指令类型。所以我在指令序列器里配置了一个帧间隔计数器一般取5ms到10ms如果连续快速切换页面时发现偶发不响应就把这个间隔再拉大一些。5.3 上电时序和初始化等待FPGA和串口屏有一个很容易被忽略的时序关系上电瞬间谁先就绪。串口屏内部有操作系统和GUI引擎上电后需要几百毫秒甚至更长时间初始化如果FPGA在这个窗口期就往外发指令屏根本收不到。我见过最头疼的问题就是FPGA启动后立刻发初始化指令配置界面结果指令被吞界面停留在默认状态但单独用串口助手手动发指令又一切正常。解决方案很简单也很粗暴FPGA的串口屏初始化流程必须在一个延时后才开始。我在顶层状态机里加了一个上电等待计数器一般取500ms到1秒。先让串口屏初始化完再发第一条写变量或跳页面指令。如果有条件更稳妥的做法是先用串口助手上电观察屏的返回信息确认屏完全就绪后再让FPGA发指令。如果做出来的产品还有休眠唤醒这类需求那上电时序就更要谨慎了。串口屏进入休眠后不代表串口还能照常收数据唤醒时序也得按手册来不能默认它一直在线。5.4 先用串口助手验证再上FPGA这个经验我每次带人都要强调一遍不要一上来就拿FPGA和串口屏对调先用USB转串口模块接电脑用串口调试助手手工发指令确认屏的界面和协议没问题。这个操作能帮你把问题分成两个阶段非常省时间。具体操作流程是第一步用串口助手连接屏幕把上位机生成的指令示例逐条发过去观察屏幕反应确认地址、长度、校验都对第二步把电脑断开把FPGA的UART TX引脚接到串口屏的RX引脚先用FPGA发一条最简单的指令用逻辑分析仪抓UART TX的波形和串口助手里发过的指令字节流做对比第三步确认波形一致后再让FPGA跑完整逻辑。这三步看起来多花了一点时间但实际上能省掉大量无头绪的排查。我遇到过很多人把问题定位在FPGA侧结果是串口屏上位机工程里控件地址没分配对也有人怀疑屏坏了结果根本原因是FPGA的引脚电平不匹配。先验证屏自身没问题是从源头排除故障面的好习惯。6. 从这一章还能往哪走双向交互与进阶玩法6.1 触摸事件上报与FPGA接收解析串口屏不光是显示设备它还是一个完整的人机交互终端。当你用手指按下屏上的按钮时串口屏会主动向主机发送一条事件上报帧里面通常包含页面ID、控件ID和事件类型。这就意味着FPGA不光要发还要收。接收侧的核心也是那个熟悉的套路状态机检测帧头0xA5然后读长度字段按长度收完整帧做校验。解析到是一个触摸事件后根据控件ID跳转到对应的处理逻辑。比如一个启动/停止按钮FPGA接收到按下事件后置位或清除一个控制寄存器进而控制外设状态。我建议接收和发送共用同一个FIFO缓冲思路只不过方向反过来。串口屏发来的字节流被UART RX模块接收后先写进接收FIFOFPGA主逻辑空闲时再去解析。这样即使一帧事件在接收过程中被其他逻辑打断也不会丢字节。等这一块跑通你的FPGA系统就真正有了面板操作的能力而不只是单方向显示。6.2 页面切换、曲线显示与后续扩展USART_HMI协议里还有很多值得玩的功能。页面切换指令可以让你的FPGA设备实现菜单层级跳转按下设置进入设置页按下返回回到主页面。曲线控件可以把采集到的连续数据流动态画出来虽然刷新率不如直接驱动屏幕高但做温湿度历史曲线这类低频波形完全够用。图片推送功能甚至可以把FPGA采集的图像数据一帧一帧推到串口屏上显示配合SD卡存储可以做一个很轻量的数据记录仪。如果项目到了一定规模我建议把串口屏通信封装成一个独立模块对外只留写字符串变量写数值变量注册触摸回调这几个接口。上层逻辑永远不直接拼帧所有协议细节都隔离在模块内部。这样以后换屏幕品牌、升级协议版本只需要替换这一个模块顶层控制逻辑一行都不用改。我现在项目里的分工基本是FPGA专心做信号采集和算法处理串口屏专心做人机界面两者之间只有一组UART连接。这种把“界面”从“逻辑”里剥离开的思路其实是FPGA工程成熟之后很自然的演进方向。串口收发字符串本身只是基本功接上一个像USART_HMI串口屏这样真正能出活的终端它才变成生产力。这大概是我写这个系列到现在最想留给你的东西。
返回列表