
1. 串口屏到底解决了我什么问题做FPGA开发的人十有八九会撞上同一个尴尬板子调通了逻辑仿真波形也验证过了但数据看不着。早期靠LED灯拼二进制后来用数码管再后来学会了HDL的VGA驱动、写完字库模块——但真要显示完整一句话、一个动态数值、甚至一个简单菜单界面单靠FPGA直接撸VGA那工作量可一点都不小。如果项目又要求量产成本可控你可能还会发现光是给屏幕配一颗带显存和控制器的主控芯片整套方案已经变得很臃肿了。串口屏这个东西说白了就是把“显示”这件事从FPGA侧剥离出去。它内部有一颗专门处理用户界面的MCU芯片自带Flash存储字库、图片和控件配置通过TTL串口接收外部设备发过来的指令就能完成页面切换、显示字符串、更新数值、改变控件属性这些操作。你不需要自己在FPGA里写字符取模、做显存管理、处理像素同步时序只需要按协议帧格式发一串字节屏幕上就会乖乖出现你要的内容。市面上常见的USART_HMI串口屏比如淘晶驰、迪文、中显光电这些品牌基本都走“上位机配置界面 串口指令驱动”的思路。开发者先用厂家提供的上位机软件拖几个控件、放几张图片把界面布局和控件ID定义好下载到屏里FPGA端真正要写的只是一个串口发送模块和一套协议帧打包逻辑。所以这篇文章我打算直接以一个具体的交互场景为例FPGA作为主控通过USART_HMI串口屏显示一段字符串并解析按键返回给FPGA。把协议帧结构、FPGA状态机设计、代码实现和调试踩坑全走一遍。适合已经做过FPGA基础串口收发、想在项目里接块真屏幕的开发者参考。2. 动手前先摸清USART_HMI协议帧和串口屏工作机制2.1 USART_HMI的帧格式拆解FPGA和串口屏通信本质就是串口收发。难点不在于UART波特率时序而在于“怎么把一条完整的指令准确发到屏上、怎么从屏的响应里解析出有效数据”。USART_HMI的帧格式很有代表性搞清楚它其他品牌串口屏也就是换皮不换骨。以淘晶驰USART_HMI为例协议帧标准格式如下帧头0xAA 指令类型0x01/0x02/0x03... 数据块长度可变 结束符0xCC 0x33 0xC5 0x3C比如我要让屏幕上的文本控件t0显示字符串“FPGA OK”实际发送的完整字节序列是AA 01 74 30 07 46 50 47 41 20 4F 4B CC 33 C5 3C逐字节拆开看AA帧头告诉串口屏“新指令开始了”。01指令类型。0x01表示写文本控件内容。74 30控件名的ASCII码也就是“t0”两个字符。07后面待发送字符串的长度“FPGA OK”共7个字符。46 50 47 41 20 4F 4B字符串“FPGA OK”的ASCII码。CC 33 C5 3C固定结束符表示帧结束。这里值得多说一嘴的是07这个长度字节。很多新手第一次发指令不成功就是栽在长度上要么没把长度算对要么写的是十六进制0x07但发成了ASCII字符“07”。USART_HMI规定这一字节就是二进制长度值不是ASCII码。后面我会专门写一个在线计算长度的经验避免反复数错。2.2 页码、控件ID与工程配置之间的关系串口屏的操作对象不是“像素坐标”而是“控件”。你在上位机软件里设计的每一个文本框、进度条、按钮都会绑定一个唯一的控件名比如t0、n0、b0。屏幕的页面编号是page0、page1控件从属于某个页面。向FPGA端发来的控制指令本质上就是在说去page0页面把控件n0的数值改成123。还有一个容易忽视的点每个控件在系统内部对应一个变量地址。上位机软件里可以看到控件属性面板里有vscope、vaddr这些参数默认情况下系统自动分配也可以手动修改。FPGA侧其实不需要关心这个地址因为协议帧里传的是控件名ASCII码屏端会自己完成“控件名→内部变量地址”的映射。但如果你要写的是批量数据或者要在两个控件之间复制数值那理解vaddr能帮你少走弯路。特别是做曲线显示、历史数据回放这类场景有些指令格式是直接带地址的那时候再回头翻上位机手册不迟。2.3 全双工串口屏的交互模型不只是单向下发很多人的第一反应是串口屏不就是个显示器嘛我发给它帧它显示内容就完事了。一直到调试才发现屏还会往回发数据。USART_HMI支持双向通信这一点在这个系列里是核心进阶点。屏回发的数据主要分三类返回类型触发条件典型数据格式触摸按钮按下用户在屏上点了结束符为0x01的按钮控件AA 01 62 30 CC 33 C5 3C字符串数据回传用户用输入控件提交文字AA 02 74 31 05 41 42 43 44 45 FF FF FF系统/变量数值回传控件关联的变量发生变化且设置了回调AA 05 6E 30 31 32 33 CC 33 C5 3C这里有个很大的坑FPGA端的UART接收模块不能只要看到AA当成一帧开始就傻乎乎地一直接收。它必须同时处理“帧头识别”“帧长判断”“超时判断”三个问题。因为屏回传的帧不是固定长度如果中间某个字节丢了或者时序错乱接收端会卡死。我自己的做法是接收状态机里加一个超时计数器超过一定字节时间没收到新字节就强制回到IDLE状态丢弃半包数据等待下一个帧头。这套策略放到任何品牌串口屏上都适用算是通用保底方案。3. 串口屏与FPGA硬件连接别让电平标准毁了调试心情3.1 TTL电平接口是首选RS232是给老设备的备胎串口屏模块的接口一般有TTL、RS232、RS485三种版本。开发阶段默认买TTL就够了因为它直接就着FPGA开发板的UART引脚电平匹配不需要额外的电平转换芯片。这里特别强调一下TTL串口屏的逻辑1电平是3.3V部分货是5V而RS232串口屏的逻辑1是-12V、逻辑0是12V。如果你手里是RS232版本的屏幕直接怼到FPGA的引脚上轻则屏幕完全不响应重则烧毁FPGA引脚。实测经验淘晶驰和迪文的常见型号基本都是3.3V TTL和Xilinx Artix-7、Altera Cyclone IV的BANK电平能直接对接。如果是5V TTL的屏幕建议加一个电平转换芯片比如TXS0108E或SN74LVC4245别赌FPGA引脚耐压。3.2 交叉接线是基础中的基础但也是翻车高发区FPGA的UART_TX要接屏幕的RXFPGA的UART_RX接屏幕的TX。这个“交叉”原则看起来简单实际调试中我至少见身边三个人接反过。屏幕没有任何反应查了半小时才发现是TX接TX。另外还有几个隐藏细节共地。FPGA开发板和串口屏必须共地否则串口电平参考点不一致会出现偶发乱码。杜邦线尽量短。115200波特率下杜邦线超过20厘米就有风险。串口屏电流不大但信号线过长时寄生电容会拉缓边沿导致采样点错误。屏幕供电别从FPGA的3.3V引脚直接拉太大电流。USART_HMI大部分型号屏幕背光全开时电流能到200-300mA。很多FPGA核心板的3.3V是LDO输出的余量不大我建议用独立的5V给屏幕供电单独一个GPIO控制背光。3.3 用USB转串口工具先验证屏幕指令再动FPGA这个步骤省不得。拿到一块新串口屏第一步不要急着接FPGA先做一个“屏幕自主性测试”用一根USB转TTL的串口线接屏幕。电脑上打开串口调试助手波特率设成9600或115200与上位机配置一致。手动填入一帧完整的指令比如显示字符串的帧。点击发送观察屏幕是否响应。这样做的意义是先把FPGA和屏幕两侧的“嫌疑”分开。如果屏幕用电脑发指令能正常工作说明屏没问题剩余问题就在FPGA侧的UART模块或接线。否则问题大概率在屏幕配置或线序上。这个排障思路在后面会经常用到值得养成习惯。4. FPGA串口发送模块设计状态机拆分到帧级别4.1 顶层架构一个发送模块完成“数据加载字节发送”两个环节用FPGA驱动串口屏不需要什么复杂总线核心就是一个“按帧发送”的串口发送模块。整体架构我建议拆成两层底层一个最基础的UART TX字节发送模块8位数据、可配波特率、发送完成拉高一个周期的脉冲。上层一个帧级状态机负责把要发送的字符串逐字节送入底层UART TX模块。为什么要拆因为帧级状态机要处理的是“整个指令帧的字节流”而底层UART TX模块只管“一个字节怎么按位发出去”。职责分离之后调试定位问题会非常轻松。底层的字节发送模块是纯硬时序上层是逻辑状态控制两边独立仿真、独立测试。4.2 帧级发送状态机的经典结构以“发送一段字符串到串口屏”为例我贴上常用的状态机设计思路localparam IDLE 3d0; localparam START_TX 3d1; localparam WAIT_TXDN 3d2; localparam NEXT_BYTE 3d3; localparam GEN_CRC 3d4; // 如果协议需要校验字节再启用 localparam FINISH 3d5;处理流程是这样的IDLE等待外部发送使能信号比如一个周期的高脉冲。START_TX把要发送的当前字节送入UART TX模块同时拉高字节发送的触发信号。WAIT_TXDN等待UART TX模块返回发送完成信号tx_done。NEXT_BYTE字节计数加1判断是否已经发完整个帧的所有字节。FINISH发完整帧后拉高frame_done回到IDLE。这里面有个工程细节外部使能信号来的时候帧数据可能是一个长字符串比如20个字符那么状态机不能只在START_TX状态把字符串首字节送出去就完事。最可靠的做法是把整个字符串存放在一个寄存器数组里帧状态机用计数器循环取字节。如果以后字符串会变化就做一个FIFO把要显示的内容先写入FIFO状态机只要负责从FIFO读出再发。这种方法在动态显示数字、浮点结果时尤其好用。4.3 波特率的产生分频计数器的精度问题以50MHz系统时钟、115200波特率为例每个bit持续时间为周期数 50_000_000 / 115200 434.0277...取整为434。那么在发送一个字节时每位采样点就是系统时钟计数到434的倍数位置。误差约为0.006%完全可以忽略。但如果你的板子晶振是27MHz、或者波特率是250000这种非整分频的就要算一下误差是否超过2%。UART接收端取样的容错空间大约在48%52%位时间内误差太大就会在高速率传输时出现偶尔丢字节。一个稳妥的写法是parameter CLK_FREQ 50_000_000; parameter BAUD_RATE 115200; localparam BAUD_CNT CLK_FREQ / BAUD_RATE; localparam BAUD_CNT_HALF BAUD_CNT / 2;发送模块里用一个计数器做分频每当计数到BAUD_CNT就产生一个移位脉冲。接收模块则用BAUD_CNT_HALF做中点采样。这个设计在FPGA入门项目里非常常见不依赖厂商IP核移植性极强。4.4 发送一个字符串到串口屏的完整流程假设需求是FPGA上电后自动让串口屏page0的文本控件t0显示字符串“HELLO”显示完再发一帧让页面切到page1。上位机软件里我们已经配置好t0控件。那么第一帧为AA 01 74 30 05 48 45 4C 4C 4F CC 33 C5 3C其中74 30是t0的ASCII05是HELLO字符个数后面是HELLO的ASCII最后是结束符。在FPGA代码里我会把这些字节定义成一个参数数组parameter [7:0] FRAME_HELLO [0:14] {8hAA, 8h01, 8h74, 8h30, 8h05, 8h48, 8h45, 8h4C, 8h4C, 8h4F, 8hCC, 8h33, 8hC5, 8h3C};第二帧切页面的指令为AA 01 70 61 67 65 31 CC 33 C5 3C70 61 67 65 31是“page1”的ASCII码页面切换指令同样是0x01但数据内容是页面名字符串。这里特别提醒不同品牌串口屏的页面切换指令可能不同。淘晶驰是发一个独立的页名字符串帧迪文老型号则用专门的指令码。所以一定以所买屏幕的手册为准。5. 收到屏幕按键回传FPGA如何安全解析5.1 帧接收状态机不要见AA就以为是帧头FPGA接收串口屏的回传数据比发送更容易出岔子。因为FPGA并不知道屏幕什么时候会发数据只能被动接收。接收状态机最常见的错误是只判断数据是否为0xAA是就直接进入接收数据体状态。结果屏幕偶尔发来一个包含AA字节的数据帧状态机直接就错乱了。稳妥的接收状态机设计要考虑三件事帧头识别收到0xAA后继续等待指令类型字节。长度判断部分指令类型自带长度字段先收完长度字段再决定收多少数据字节。超时保护任何状态超过N个字节时间没有新数据强制复位到IDLE。拿按钮按下回传的帧举例AA 01 62 30 CC 33 C5 3C其中62 30是按钮控件名“b0”。“CC 33 C5 3C”是结束符。屏上按钮是“返回式”的按下时瞬间回传一个帧松开不回传。帧里没有长度字段解析方式就是靠结束符判断帧尾。有的屏幕型号支持设置回传格式为“按下和释放都回传”那就要会看到两帧几乎连续的数据。我给FPGA做解析时会额外用一个脉冲寄存器检测帧间隔避免两次回传被吞并成一帧。5.2 字符串回传的解析难点中英文长度不一样如果屏幕端用了输入控件比如用户要输入一个设备编号屏回传字符串的帧格式会带上数据内容并且字符串长度不是固定的。典型回传格式AA 02 74 31 05 41 42 43 44 45 FF FF FF02表示字符串回传指令74 31是输入控件名t105是数据长度后面是数据“ABCDE”FF FF FF是结束标志。注意这里结束符和前面的CC 33 C5 3C不一样是三个FF。很多FPGA初学者第一次写解析逻辑统一按五个结束符判断结果字符串数据里出现FF就解析失败。正确做法是把结束符也做成状态判断的一部分而且要为“一个FF出现后后面一个不是FF”也能兜底的情况设计状态回退逻辑。中文场景更麻烦。如果显示屏上默认用GBK编码一个中文汉字占两个字节FPGA端如果按一个字符一字节来解析长度字节数和实际内容对不上。比较推荐的做法是FPGA端只做透传不做编码转换。屏幕端配置输入格式时限制为ASCII或半角数字从根源上避开中文编码的长度计算问题。5.3 解析结果的脉冲化与跨时钟域处理FPGA主逻辑如果是50MHz串口波特率115200时每个字节要花近87微秒解析状态机每一步都远小于这个时间本身不存在跨时钟域问题。但是屏幕上按钮的按下回传是一个异步事件如果这帧数据要和内部某个功能模块联动比如按下按钮后启动DAC输出不要直接把解析完成信号当作长电平拉过去。正确做法是解析到结束符后拉一个单周期的脉冲信号触发后续逻辑。如果FPGA侧还有AXI总线或自定义总线要把这个帧解析结果作为写请求发出去最好在脉冲域加一个边沿检测或握手信号AXI的valid-ready机制。我用过最简单的方案是解析完成打一拍上升沿作为触发如果后续逻辑没来得及处理就置一个标志位主状态机轮询后再清除。6. 一个完整DemoFPGA控制串口屏显示温度并响应按键6.1 功能需求和模块划分这几天我手头正好有个小项目用FPGA接一个DS18B20温度传感器温度值通过USART_HMI串口屏显示出来同时在屏幕上放了两个触摸按钮一个切换摄氏度/华氏度显示另一个控制蜂鸣器开关。这个项目的核心链路很有代表性传感器模块DS18B20温度采集输出一个BCD码或二进制温度值。格式转换模块把温度值转换成ASCII字符串比如“25.5”或“25.5C”。串口发送模块驱动串口屏显示。串口接收模块解析屏幕按钮回传的指令转成按键脉冲。蜂鸣器控制按键脉冲控制输出到蜂鸣器引脚。这个划分逻辑很清晰数字处理和字符串生成是一层串口协议收发是一层外设控制是另一层。每一层单独仿真都能快速定位问题。6.2 数字转字符串模块的Verilog实现思路把数字转成字符串是FPGA开发里一个高频需求配合串口屏更有实际意义。温度值是16位有符号数单位是0.0625度要显示成度加小数点的形式。第一步先算出整数部分和小数部分integer int_part; integer frac_part; assign int_part temp_value / 16; // 整数部分 assign frac_part (temp_value % 16) * 100 / 16; // 小数部分保留两位第二步把整数部分逐位转换成ASCII码。常见的做法是// 假设int_part范围0~99取十位和个位 digit_tens int_part / 10; digit_ones int_part % 10; ascii_tens digit_tens 8h30; ascii_ones digit_ones 8h30;如果温度可能为负还要先用符号位判断在字符串前拼接一个-字符0x2D。最终组成“25.50”这样一帧字符串再通过帧状态机统一发送。这个模块的仿真非常推荐做一下边界条件比较多0度、负温度、最小值等。6.3 接收模块解析屏幕按键控制蜂鸣器开关屏幕端按钮b0配置成“按下回传”FPGA接收模块回传帧解析后判断字节序列是“b0”且帧完整就拉高一次按键触发信号。蜂鸣器的控制逻辑很简单always (posedge clk) begin if (key_pulse) buzzer_en ~buzzer_en; end这里我没做消抖处理因为USART_HMI屏幕硬件本身已经做了触摸消抖和按键去抖回传的帧只有一次。如果用的是没有去抖的廉价按键屏才需要在FPGA侧加20ms左右的消抖逻辑。6.4 实际调试中的常见问题现象可能原因解决思路屏幕完全无响应TX/RX接反、共地没接、波特率不一致先用USB转串口线直接验证屏幕自主性发字符串只显示第一个字符长度字节算错检查帧中的长度字段确认为二进制值乱码波特率误差偏大、接线过长降低波特率缩短杜邦线或换屏蔽线按钮回传时好时坏接收状态机没有超时保护增加超时强制复位逻辑屏幕显示正常但切页不生效页名写错或未下载到屏里上位机软件里确认页面名与指令一致并重新下载工程7. 几个项目级避坑经验能帮你少走三个月弯路第一所有协议帧里的十六进制字节一律用Verilog的8hXX格式书写。我碰过不止一次有人把十六进制手动转成十进制后再换算时出错。直接以参数数组的方式组织帧内容代码可读性和后续改动效率都高一大截。第二做FPGA和串口屏联调时一定要留一个调试端口把发送/接收的原始字节引到逻辑分析仪或串口调试助手上。我常用的一种做法是FPGA内部做一个环回把RX收到的字节直接转发到PC串口显示同时驱动屏幕。哪边出问题一眼就能在PC端看出来。这种“FPGA透传PC监控”的方式比只盯仿真波形高效得多。第三屏幕端控件属性里的“帧格式”“波特率”必须与实际代码一致。淘晶驰串口屏支持8-N-1波特率可选择9600到921600。我建议在项目初期统一规划调试阶段用115200量产阶段如果信号线较长再降到9600或19200。不要小看这个决定有的接插件质量差高波特率下容易受干扰丢字节降低波特率解决得干干净净。第四如果项目要量产务必在屏幕端做“上电默认页面超时返回”的逻辑。比如让屏在开机时自动显示page0FPGA检测到屏幕握手后切到主界面。很多商用屏幕支持“上电自动加载某个页属性”在上位机软件里配置好就省掉了FPGA发送开机页指令的环节。第五关于字符串的动态更新频率串口屏虽然能接收指令但它内部MCU处理每条指令也是要花时间的。实测下来同一控件连续刷新指令间隔最好在10ms以上。如果FPGA侧需要以很高频率更新数值比如100Hz刷新率显示电压波形可以开多个文本控件轮换显示或者改用指令中更高效的数值写入方式不要频繁发整段字符串。8. 我的建议先把单字节收发闭环再上串口屏我在实际带人做FPGA项目时一直坚持一个原则串口屏调试不能一上来就调屏幕。先把FPGA的UART收发模块做到能用串口调试助手在PC端自发自收再接入串口屏成功概率会高很多。具体来说在接屏幕之前至少先完成这几个实验FPGA发送一段固定字符串“Hello FPGA”PC串口助手能收到。PC串口助手发字符串“abc”FPGA收到后原样回传PC端能看到“abc”。FPGA解析PC发来的特定字符串比如“LED_ON”能控制LED引脚翻转。这三个实验做完说明UART收发模块稳定、时序没问题再接入串口屏。后面如果屏幕不工作问题基本就锁定在协议帧构造成屏端配置上了。串口屏本身是个“能把复杂显示问题简化成协议帧问题”的工具用好了它FPGA项目的人机交互会变得非常轻松。后续我还会继续写这个系列串口屏多页面交互、FPGA使用Flash存储字库、以及更高性能的双缓冲页面切换方案到时再和大家分享更多细节。