资讯动态

FPGA实战:UART串口通信协议解析与Verilog实现全攻略

发布时间:2026/9/8 7:15:49 来源:尧图企业网站定制
搞FPGA的同学十有八九第一个“能跟外部世界说话”的模块就是UART串口通信。这东西说简单也简单无非就是一条发送线一条接收线按约定好的速率把数据一位一位挪出去但说麻烦也真麻烦波特率算错一位就是满屏乱码电平不匹配直接烧引脚采样点没选好隔三差五丢字节。我当年第一次调UART板子上FPGA和PC死活不通折腾到半夜才发现是分频系数四舍五入的问题从那以后就养成了习惯——凡是串口相关代码先拿仿真波形确认时序再上板。这篇文章就把我这几年做UART串口通信FPGA实现的经验整理出来从协议细节到Verilog代码从仿真到上板调试再到常见问题排查一次性说透。无论你是刚开始学FPGA还是已经会点基础但被串口折磨过这篇都能让你少走不少弯路。1. 先理清协议再说代码UART一帧数据到底长什么样很多朋友一上来就找代码抄过来能用就完事但串口这种异步协议不理解底层时序出问题根本无从下手。所以我先花点篇幅把UART的“规矩”讲清楚。1.1 UART的本质异步、电平、时序UART全称是Universal Asynchronous Receiver/Transmitter通用异步收发器。注意“异步”两个字这是UART和SPI、I2C最大的区别——UART没有独立的时钟线收发双方各用各的时钟靠的是事先约定好速率然后在数据线上通过电平跳变来同步。从物理连接看UART至少需要两条数据线TX发送和RX接收。A设备的TX接B设备的RXA设备的RX接B设备的TX数据方向是交叉的。最容易被忽略的是地线串口通信必须共地否则电平参考点不一致数据根本没法正确解析。电平标准这里要特别强调一下。大家常说的“串口”可能指好几个东西电平标准逻辑1电平逻辑0电平常见场景TTL3.3V或5V0VFPGA、单片机的UART引脚直接输出RS-232-3V ~ -15V3V ~ 15V老式电脑DB9串口RS-485A-B差分正电压A-B差分负电压工业现场远距离通信FPGA引脚输出的是TTL电平而电脑的DB9串口是RS-232电平二者必须经过电平转换芯片比如MAX3232才能互通。现在主流做法是用USB转UART芯片比如热词里提到的FT231x、FT232R这类芯片在电脑端虚拟出一个串口物理端直接输出TTL电平接FPGA非常方便。如果电平不匹配直接硬接轻则数据错误重则烧毁引脚。1.2 波特率是个什么概念时钟分频的计算波特率Baud Rate表示每秒传输的码元数对UART来说就是每秒传输的位数单位bps。常见的有9600、115200、460800等。波特率越高单位时间传的数据越多但对时钟精度和线路质量要求也越高。FPGA内部是全局时钟驱动的要让UART按指定波特率工作需要把系统时钟分频到目标速率。分频公式很简单分频系数 系统时钟频率 / (波特率 × 采样倍数)这里“采样倍数”值得说道说道。接收端为了在噪声干扰下准确判断每个bit通常不希望只在理论中点采样一次而是用更高的频率去采样取多数判决或取中间点。最常见的是16倍过采样一个bit周期内采样16次然后在第8次左右采样点取值。16倍过采样有个额外好处就是用分频计数器可以比较精确地定位起始位的下降沿。实际工程中可以直接分频到波特率本身1倍采样也可以分频到16倍波特率16倍过采样。前者简单但抗干扰差后者逻辑稍复杂接收稳定度明显更好。我一般接收端用16倍过采样发送端直接用波特率分频这样资源占用和稳定性比较均衡。来看一个实际计算案例系统时钟50MHz目标波特率115200采用16倍过采样分频系数 50_000_000 / (115200 × 16) 27.1267计数器只能是整数取27那么实际采样频率 50_000_000 / (16 × 27) 115740.7 bps误差 (115740.7 - 115200) / 115200 ≈ 0.47%。这个误差小于1%在UART允许的容差范围内通常要求收发双方误差不超过2%通信没问题。但换个组合就可能出问题。比如热词里有人问“波特率9600能通信4800没有数据”这种情况十有八九就是分频误差过大。假设系统时钟是不太规整的频率9600算出来误差小4800算出来误差大超过容差就完全收不到。我遇到过20MHz时钟分频4800波特率取整后误差高达3.2%怎么调都收不到数据换到9600反而正常。所以设计时一定要把误差算清楚最好控制在1%以内。1.3 常见应用场景从FPGA到PC、STM32、串口屏UART在嵌入式系统里的存在感极强FPGA之间通信、FPGA和STM32交互、FPGA和串口屏对接、FPGA和PC机调试都是UART的典型场景。FPGA与PC通信主要用于调试和上位机交互通过USB转UART芯片连接配合串口助手或自定义上位机查看FPGA内部状态、下发控制指令。FPGA与STM32通信比如热词里提到的STM32H743与FPGA通过FMC通信这是一种并行高速接口但很多场合两者之间只需要传少量控制信息和状态带个UART就够用了简单可靠。FPGA与串口屏陶晶驰这类串口屏就是吃UART数据的FPGA按协议组织好帧格式发过去屏幕就能显示对应内容。FPGA与工业设备工业现场大量用RS-485总线本质是在UART数据帧基础上加了一层差分物理层和Modbus协议FPGA做Modbus从站或主站也很常见热词里就有“TTL UART Modbus 串口”。理解这些场景后你会发现UART就像实验室里的万用表哪里需要调试哪里就能用上值得花点时间彻底搞明白。2. 模块怎么拆RTL代码怎么设计协议搞清楚了接下来动手写Verilog。我习惯把UART拆成三个模块波特率生成模块、发送模块TX、接收模块RX。发送和接收相对独立各自有状态机放在一个文件里也行分开更清晰。2.1 模块划分与接口规划顶层模块的接口建议这样设计module uart_top #( parameter CLK_FREQ 50_000_000, // 系统时钟频率Hz parameter BAUD_RATE 115200 // 串口波特率 )( input wire clk, // 系统时钟 input wire rst_n, // 低电平复位 input wire rx, // UART接收 output wire tx, // UART发送 // 用户接口 input wire [7:0] tx_data, // 待发送数据 input wire tx_start, // 发送启动脉冲 output wire tx_busy, // 发送忙标志 output reg [7:0] rx_data, // 接收到的数据 output wire rx_done // 接收完成脉冲 );用户接口的意思是FPGA内部其他模块要发送数据就把数据放到tx_data上拉高tx_start一个时钟周期TX模块自动完成发送接收端则通过rx_done脉冲提示“有新数据到了”同时rx_data上就是刚收到的字节。这种接口风格对上层逻辑非常友好。2.2 波特率生成模块实现这个模块核心就是一个计数器。如果用16倍过采样计数器满值是CLK_FREQ / BAUD_RATE / 16如果直接用波特率分频发送端用计数器满值是CLK_FREQ / BAUD_RATE - 1。// 波特率分频模块 module baud_gen #( parameter CLK_FREQ 50_000_000, parameter BAUD_RATE 115200, parameter OVERSAMPLE 16 )( input wire clk, input wire rst_n, output reg baud_pulse // 一个时钟周期宽的脉冲 ); localparam DIV_CNT CLK_FREQ / (BAUD_RATE * OVERSAMPLE); localparam DIV_WIDTH clog2(DIV_CNT); // 位宽计算 reg [DIV_WIDTH-1:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 0; baud_pulse 0; end else if (cnt DIV_CNT - 1) begin cnt 0; baud_pulse 1b1; end else begin cnt cnt 1b1; baud_pulse 1b0; end end endmodule这里有个小细节DIV_CNT必须大于等于2否则计数器还没加就清零了分频完全失效。如果系统时钟太高、目标波特率太高算出来分频系数小于2就要考虑换更低的波特率或者用专门的UART硬核。2.3 发送器TX状态机实现发送端的核心是状态机包含四个状态空闲IDLE、起始位START、数据位DATA、停止位STOP。具体流程是空闲时TX线保持高电平这是UART协议要求的检测到tx_start后拉低TX线一个bit时间这是起始位然后从低位到高位依次发送8个数据位最后拉高TX线一个bit时间这是停止位。module uart_tx #( parameter CLK_FREQ 50_000_000, parameter BAUD_RATE 115200 )( input wire clk, input wire rst_n, input wire [7:0] tx_data, input wire tx_start, output reg tx, output reg tx_busy ); localparam IDLE 2d0; localparam START 2d1; localparam DATA 2d2; localparam STOP 2d3; localparam BIT_CNT_MAX CLK_FREQ / BAUD_RATE - 1; localparam CNT_WIDTH 16; // 按需调整位宽 reg [1:0] state, next_state; reg [CNT_WIDTH-1:0] bit_cnt; reg [2:0] bit_index; reg tx_data_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; bit_cnt 0; bit_index 0; tx 1b1; tx_busy 1b0; end else begin case (state) IDLE: begin tx 1b1; tx_busy 1b0; if (tx_start) begin state START; tx_busy 1b1; bit_cnt 0; end end START: begin tx 1b0; if (bit_cnt BIT_CNT_MAX) begin bit_cnt 0; state DATA; bit_index 0; end else begin bit_cnt bit_cnt 1b1; end end DATA: begin tx tx_data[bit_index]; if (bit_cnt BIT_CNT_MAX) begin bit_cnt 0; if (bit_index 3d7) begin state STOP; end else begin bit_index bit_index 1b1; end end else begin bit_cnt bit_cnt 1b1; end end STOP: begin tx 1b1; if (bit_cnt BIT_CNT_MAX) begin bit_cnt 0; state IDLE; tx_busy 1b0; end else begin bit_cnt bit_cnt 1b1; end end default: state IDLE; endcase end end endmodule这里有个经验之谈tx_busy信号很重要。上层逻辑发送前要先看tx_busy是不是低电平低电平才能触发新的发送。如果忽略busy直接发还没发完又给新数据状态机就会错乱。2.4 接收器RX采样点选择是关键接收比发送复杂难点在于如何确定每一位的采样时刻。发送端可以精准控制每个bit的持续时间但接收端不知道数据什么时候来只能通过检测起始位的下降沿来“对齐”。具体做法是用16倍过采样时钟持续检测RX引脚。检测到下降沿高电平变低电平认为可能来了起始位。等8个采样周期半个bit时间再采一次确认依然是低电平。这一步是为了排除毛刺干扰。如果此时是高电平说明刚才只是干扰脉冲不是真正的起始位。确认为起始位后每16个采样周期采样一次数据线依次采8个数据位再加一个停止位。采到的8个bit从低位到高位拼成一个字节输出rx_done脉冲。用代码实现module uart_rx #( parameter CLK_FREQ 50_000_000, parameter BAUD_RATE 115200 )( input wire clk, input wire rst_n, input wire rx, output reg [7:0] rx_data, output reg rx_done ); localparam OVERSAMPLE 16; localparam SMP_CNT_MAX CLK_FREQ / BAUD_RATE / OVERSAMPLE - 1; localparam CNT_WIDTH 16; reg rx_sync1, rx_sync2; // 打两拍同步消除亚稳态 reg [1:0] state; reg [CNT_WIDTH-1:0] smp_cnt; reg [3:0] bit_cnt; reg [7:0] shift_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin rx_sync1 1b1; rx_sync2 1b1; end else begin rx_sync1 rx; rx_sync2 rx_sync1; end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; smp_cnt 0; bit_cnt 0; shift_reg 8d0; rx_data 8d0; rx_done 1b0; end else begin rx_done 1b0; case (state) IDLE: begin smp_cnt 0; bit_cnt 0; // 检测到下降沿等8个采样周期后确认 if (rx_sync2 1b0) begin // 这里直接跳转到起始位确认状态 // 为了让代码紧凑这里用START状态统一处理 state START; end end START: begin // 等8个采样周期后在中间点确认 if (smp_cnt SMP_CNT_MAX/2 - 1) begin if (rx_sync2 1b0) begin state DATA; bit_cnt 0; smp_cnt 0; end else begin state IDLE; // 干扰回到空闲 end end else begin smp_cnt smp_cnt 1b1; end end DATA: begin if (smp_cnt SMP_CNT_MAX - 1) begin smp_cnt 0; shift_reg {rx_sync2, shift_reg[7:1]}; // 先收到的bit放高位 if (bit_cnt 4d7) begin state STOP; end else begin bit_cnt bit_cnt 1b1; end end else begin smp_cnt smp_cnt 1b1; end end STOP: begin if (smp_cnt SMP_CNT_MAX - 1) begin smp_cnt 0; state IDLE; rx_data shift_reg; rx_done 1b1; end else begin smp_cnt smp_cnt 1b1; end end default: state IDLE; endcase end end endmodule注意一个细节在DATA状态shift_reg的赋值方式是把新采到的bit拼到高位移位。这是因为UART协议先发低位后发高位第一次采到的是bit0。如果每次采到后往高位拼那么8次之后shift_reg[7]是最后采到的bit7也就是最高位在后。示例代码里用{rx_sync2, shift_reg[7:1]}第一次采到的bit0会被推到最高位这个顺序容易搞错建议仿真验证时仔细看波形。更稳妥的做法是维护一个bit_index按bit_index往对应位写// 更直观的写法 if (bit_cnt 7) begin shift_reg[bit_cnt] rx_sync2; end建议用这种直观写法避免位序混乱。2.5 流控和扩展RTS/CTS要不要加UART除了最基本的TX/RX两线还有硬件流控引脚RTS请求发送和CTS允许发送。低速场景、短距离通信不加流控完全没问题但高速率比如921600和高负载场景下接收方处理不过来可能丢数据这时候就需要流控协作。FPGA里做流控并不复杂接收模块收到数据后拉高RTS表示“缓冲区满了先别发”对方CTS检测到高电平就暂停发送。但大多数FPGA串口调试场景收发量不大直接用三线制TX、RX、GND就够了。我一般只在做数据采集、接收端需要大数据量缓存时才考虑流控和FIFO配合。3. 工程落地仿真与上板调试代码写完了第一件事不是上板而是仿真。仿真能快速验证逻辑正确性还能看到内部信号细节比上板用示波器慢慢抓高效得多。3.1 用Testbench验证收发逻辑写一个简单的Testbench模拟PC端往下发一个字节0xA5同时让FPGA发送一个字节0x5A看两端行为是否正常。timescale 1ns / 1ps module tb_uart_top; reg clk; reg rst_n; reg rx; wire tx; reg [7:0] tx_data; reg tx_start; wire tx_busy; wire [7:0] rx_data; wire rx_done; uart_top #( .CLK_FREQ(50_000_000), .BAUD_RATE(115200) ) dut ( .clk(clk), .rst_n(rst_n), .rx(rx), .tx(tx), .tx_data(tx_data), .tx_start(tx_start), .tx_busy(tx_busy), .rx_data(rx_data), .rx_done(rx_done) ); always #10 clk ~clk; // 50MHz initial begin clk 0; rst_n 0; rx 1; tx_data 8h00; tx_start 0; #100; rst_n 1; #100; // 测试发送 0xA5 tx_data 8hA5; tx_start 1; #20; tx_start 0; // 等待发送完成 #(50_000_000 / 115200 * 10 * 20); // 大约10个bit的时间乘以20ns周期 $display(TX done); // 模拟PC发送 0x5A手动拉低形成起始位 // 这个仿真要精确计算bit时间用task更好 #1000; rx 0; // 起始位 #(50_000_000 / 115200 * 20); // 一个bit的时间 // 发送 LSB first: 0x5A 01011010, LSB0, 然后1,0,1,1,0,1,0 rx 0; #8680; rx 1; #8680; rx 0; #8680; rx 1; #8680; rx 1; #8680; rx 0; #8680; rx 1; #8680; rx 0; #8680; rx 1; // 停止位 #8680; #10000; $display(RX done: data0x%02x, rx_data); #1000; $finish; end endmodule这里有个实用技巧测试发射端时不需要真的去抓TX线上的模拟波形只要看tx_busy从高到低的变化就知道发送开始了而测试接收端必须精确控制rX线上的时序delay时间一定要按仿真时钟算准差一点点都会导致采样点偏移。3.2 上板实测管脚约束与串口助手配置仿真通过后终于可以上板了。上板的第一步是把逻辑引脚绑定到FPGA的实际管脚。以Xilinx 7系列为例XDC约束文件长这样set_property PACKAGE_PIN M14 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property PACKAGE_PIN C4 [get_ports rx] set_property IOSTANDARD LVCMOS33 [get_ports rx] set_property PACKAGE_PIN D4 [get_ports tx] set_property IOSTANDARD LVCMOS33 [get_ports tx]管脚分配一定对照开发板的原理图确认USB转UART芯片接到FPGA的哪个引脚、电平域是多少伏。Xilinx开发板上USB转UART常用FT231x或FT232R输出是3.3V TTL电平FPGA的IO也配置为LVCMOS33就行。然后是电脑端的驱动。FT231x和FT232R这类芯片在Windows下通常会自动识别如果没装上到芯片官网下载对应版本的驱动注意看设备管理器里识别出的PID和VID确认驱动装的是不是对应的芯片型号。驱动装好后设备管理器里会出现一个“COM端口”记下端口号比如COM3。打开串口助手配置参数要跟FPGA代码里的设置完全一致波特率115200与代码中的BAUD_RATE一致数据位8停止位1校验位None流控None配对后先做一个最简单的回环测试在FPGA里把RX直接连到TX或者用代码把接收到的数据原样发送回去在串口助手里发一个字符如果能原样收到说明物理链路和基本通信都通了。这一步通过后再跑自己写的收发逻辑排查问题效率会高很多。3.3 上位机配合用Python做串口调试串口助手适合快速验证但真要做自动化测试或者处理大量数据写个Python脚本更方便。Python的pyserial库是标准选择。import serial import time ser serial.Serial( portCOM3, baudrate115200, bytesizeserial.EIGHTBITS, parityserial.PARITY_NONE, stopbitsserial.STOPBITS_ONE, timeout1 ) if ser.is_open: print(串口已打开) # 发送数据给FPGA ser.write(b\x5A) time.sleep(0.1) # 读取FPGA返回的数据 data ser.read(10) print(收到数据:, data.hex()) ser.close()用Python的好处是灵活可以定时发包、批量测试不同波特率下的误码率。我调试FPGA图像处理算法时经常用Python做数据分析和可视化串口收到的图像数据直接转成numpy数组处理完了再回传结果调试链路完整闭环。4. 常见问题与排查技巧实录这部分是我最想分享的因为串口通信的问题太典型了而且很多坑不看波形根本想象不到。4.1 问题速查表现象可能原因排查方法满屏乱码波特率不一致、时钟误差大确认两边波特率相同用示波器测实际bit宽度完全无数据RX/TX接反、电平不匹配、驱动未装好万用表量电平检查设备管理器COM口9600能通4800不通分频系数取整误差过大重新计算分频误差看是否超过1%偶尔丢字节接收端没加FIFO、采样点抖动加FIFO缓冲提高过采样倍数上电丢第一个字符FPGA复位时间不够长加长复位时间或加延时发送长时间运行死机没有处理接收错误、数据溢出加错误标志溢出时自动恢复4.2 “9600能通4800不通”的经典案例这个热词对应的场景我猜很多人都遇到过。我详细复盘一下当时的情况某块板子用的66MHz时钟代码里分频系数取整后9600波特率算出来误差是0.2%但4800波特率误差跑到了2.8%。9600勉强在容差内能通4800超过了容差接收端采样点偏移到bit边界附近时好时坏很多时候干脆收不到。解决办法有两个方向重新选取时钟频率让分频系数能整除或者误差很小。在接收端做“bit中心再校准”——每次检测到RX线上的跳变沿就重新对齐采样计数这样即使某个bit有偏差后续bit也能通过跳变沿纠回来。这个策略在低速和噪声场景下非常有效。4.3 接收端必须打拍同步这是个很多人忽略但极其重要的问题。FPGA的全局时钟和外部串口信号是异步的直接用外部信号做判断可能采到亚稳态。正确做法是先用两级触发器同步always (posedge clk or negedge rst_n) begin if (!rst_n) begin rx_sync1 1b1; rx_sync2 1b1; end else begin rx_sync1 rx; rx_sync2 rx_sync1; end end所有逻辑判断都用rx_sync2而不是直接用rx。打两拍之后亚稳态概率降到几乎可以忽略。这个习惯一定要养成不仅仅是UART任何跨时钟域信号进FPGA都要这么做。4.4 硬件层面的坑硬件问题往往比逻辑问题更隐蔽。常见的有共地问题FPGA板子和USB转UART板子各用各的电源地线没连信号根本没有共同参考点。接上地线问题立马消失。TX/RX接反很多人习惯“同名相连接”但UART必须交叉连接。FPGA的TX接外部芯片的RXFPGA的RX接外部芯片的TX。电平不匹配外部是5V TTLFPGA是3.3V IO直接接可能超过IO绝对最大额定值。要用电平转换电路或者选支持5V容忍的bank。FT231x/FT232R驱动版本问题Windows更新后驱动失效、串口号被占用这些都是常见问题。遇到设备管理器里看不到COM口先重装驱动再检查USB线是不是只供电不传数据的那种。4.5 实测心得让调试事半功倍的小技巧最后分享几个我的调试习惯。第一先做回环测试。不跑自己复杂的逻辑直接把FPGA的RX接到TX上PC发什么收什么。通了说明硬件链路、驱动、串口配置全都没问题不通优先查硬件。第二善用逻辑分析仪。现在很多逻辑分析仪很便宜接上TX/RX线能直接解码UART数据帧比示波器好用得多。看到波形里数据位、起始位、停止位清清楚楚比猜代码靠谱一万倍。第三波特率越界检查。很多芯片最高波特率有限制比如FT231x最高到3Mbps。代码里配了921600甚至更高可能上板后数据全是乱的。查阅芯片手册确认在支持范围内。第四给串口数据加帧格式。调试裸数据容易出问题正式项目建议在UART之上加一层帧协议比如帧头0xAA 0x55、长度、数据、校验CRC或累加和。FPGA收到帧后先校验再解析错误帧直接丢弃。这样即使链路有干扰上层逻辑也能保证数据可靠。5. 现有代码怎么改成其他场景很多人照着教程做完基础通信接下来就会遇到各种“变形”需求我统一讲一下。5.1 改成RS-485通信RS-485物理层是差分信号需要一颗RS-485收发器芯片连接FPGA的UART引脚同时还需要一个方向控制信号DE/RE。发送时拉高方向脚接收时拉低方向脚。FPGA内部UART逻辑不用改只是多了一个方向引脚的控制。半双工模式下要注意发完一帧后不能立刻切回接收要多等一小段时间让总线稳定。5.2 和STM32对接FPGA和STM32之间用UART对接时特别要注意电平域一致。STM32有的板子是5V供电但IO可以容忍3.3VFPGA这边如果bank电压是3.3V两者可以直接连。但保险起见还是查一下数据手册确认输入高电平阈值。FPGA发到STM32的数据STM32的串口配置要和FPGA一致尤其波特率误差要两边都计算确保都在容差范围内。5.3 多字节帧通信如果一次要传几百字节直接给每个字节都接一个tx_start驱动模块外更好的办法是在UART外面加一个发送FIFO和接收FIFO。发送时上位逻辑把数据写入FIFO发送模块自动从FIFO取数据依次发出接收时接收模块每收到一个字节就存入FIFO上位逻辑有空再来读取。Xilinx FPGA可以例化内置FIFO IP操作方便还省资源。有了FIFOUART模块和业务逻辑之间就解耦了数据吞吐量也上去了。5.4 用IP核还是自己写Xilinx和Intel都提供UART IP核配置界面里勾一勾就能生成。那为什么还要自己写我的看法是IP核适合产品开发省时省力参数经过验证但学习阶段一定要自己写一遍这样才能深刻理解协议和时序。自己写的UART模块还有一个好处就是可以根据需求随意裁剪比如只要发送不要接收或者自定义校验方式代码都在自己手里改起来方便。我现在项目里如果只是简单调试就直接用自己写的模块如果涉及复杂协议和大量数据考虑用IP核或硬核毕竟稳定性经过广泛验证。6. 写在最后调串口不要急UART这个东西看着简单但涉及时钟、电平、协议、上位机、驱动这么多层面任何一个环节出问题都会导致通信失败。我见过很多初学者卡在乱码上代码翻来覆去改都没用最后发现是串口助手里波特率选错了或者USB线没插紧。我的经验是先分层排查。软件配置对不对驱动装没装电平匹配不匹配波形对不对一层层排除基本都能找到问题。调试工具方面示波器、逻辑分析仪、串口助手、Python脚本各有各的用处多备几种排查效率翻倍。如果你也正在调UART建议把这篇里的代码照着写一遍跑通仿真再上板。等你自己能从无到有写出一套可用的UART收发模块再回头去看那些“听不懂”的时序图会突然觉得全通透了。串口这个技能以后做图像传输、做传感器采集、做指令控制都会反复用到值得花时间彻底掌握。

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

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

免费获取报价