> 技术文档 > FPGA设计-多字节UART收发-零基础学FPGA_fpga 实现uart接收任意字节数据

FPGA设计-多字节UART收发-零基础学FPGA_fpga 实现uart接收任意字节数据


欢迎留言,每条留言都会精选、本人当天回复,文章错误内容也会在回复中更新。

verilog代码在文章最下方,包括《设计文件》:顶层模块 uart_loopback+实例模块uart_data_rx+实例模块uart_data_tx


设计在vivado2018.3环境中进行,结尾有具体代码和详细注释,可直接综合成硬件电路

一、顶层模块 uart_loopback 逻辑梳理

1.1 功能

实现多字节数据的 UART 自发自收环回测试,将接收的数据通过串口回传。

1.2 核心逻辑

1.2.1 实例化模块:

    • uart_data_rx:接收串口数据,拼接为多字节后输出 rx_data
    • uart_data_tx:读取 rx_data,拆分为单字节通过串口发送。

1.2.2 环回链路:

    • uart_rx 接入接收模块,uart_tx 接出发送模块,形成物理环回。
    • rx_data 直接连接至 uart_data_tx 的 data 端口,实现 “接收→发送” 闭环。

1.2.3 参数传递:

    • DATA_WIDTH 和 MSB_FIRST 透传至子模块,统一多字节数据宽度和字节顺序。

1.2.4 状态指示:

    • led[0]:接收超时标志(uart_data_rx 的 timeout_flag)。
    • led[1]:发送完成标志(uart_data_tx 的 Tx_Done)。
    • led[2]:发送状态(uart_data_tx 的 uart_state)。

二、发送模块 uart_data_tx 逻辑梳理

2.1 功能

将多字节数据(8~256 位)拆分为单字节,按指定顺序通过 UART 发送。

2.2 核心逻辑

1.2.1 实例化模块:

核心逻辑

2.2.1 状态机控制:

    • S0(空闲):等待 send_en 触发,加载数据到缓存 data_r
    • S1(准备发送):根据 MSB_FIRST 提取当前字节(高位或低位),触发底层模块发送。
    • S2(等待完成):等待单字节发送完成标志 byte_tx_done
    • S3(进度检查):累计已发送位宽 cnt,达 DATA_WIDTH 则完成,否则循环发送。

2.2.2 数据处理:

    • 高位优先(MSB_FIRST=1):提取 data_r 最高 8 位,data_r 左移 8 位(注:逻辑上应为右移,代码存在潜在问题,但按原逻辑描述)。
    • 低位优先(MSB_FIRST=0):提取 data_r 最低 8 位,data_r 右移 8 位。

2.2.3 底层交互:

    • 通过 byte_send_en 脉冲触发 uart_byte_tx 发送单字节,同步 byte_tx_done 标志。

三、接收模块 uart_data_rx 逻辑梳理

3.1 功能

接收 UART 单字节数据,拼接为多字节输出,支持超时检测。

3.2 核心逻辑

3.2.1 状态机控制:

    • S0(等待接收):单字节接收完成后,若为 8 位数据直接输出;多字节则进入 S1
    • S1(超时判断):检测是否在 TIMEOUT 内接收到数据,超时则结束接收。
    • S2(完成检查):累计已接收位宽 cnt,达 DATA_WIDTH 则输出完整数据。

3.2.2 数据处理:

    • 高位优先MSB_FIRST=1):新字节左移拼接(如 {data_byte, data_r[低8位]})。
    • 低位优先MSB_FIRST=0):新字节右移拼接(如 {data_r[高8位], data_byte})。

3.2.3 超时机制:

    • 检测到起始位(uart_rx=0)后启动计数器,未在 TIMEOUT 内完成接收则置位 timeout_flag

四、vivado仿真

uart_data_rx_tb的仿真图

仿真中共发送三次,每次发送四个字节,比如16进制的19991201分为四个字节发,分别是16进制的:01、12、99、19。每发送完成1个字节Tx Done都会跳动一次。

五、总结:低速接口UART实现多字节收发

  • 顶层模块:通过实例化收发模块实现环回,参数统一控制数据格式,状态指示监控收发状态。
  • 发送模块:状态机驱动多字节拆分,按顺序逐字节发送,依赖底层模块实现串口物理层传输。
  • 接收模块:状态机管理多字节拼接,支持超时检测,按顺序组合单字节为完整数据。
  • 关键参数:DATA_WIDTH 决定数据位宽,MSB_FIRST 控制字节顺序,影响数据拼接 / 拆分逻辑。

六、代码及详细解析

6.1 顶层设计模块uart_loopback

//顶层设计uart_loopback,实例uart_data_tx、uart_data_rx两个模块,实现FPGA芯片接收多字节数据,并且发送多字节数据,数据收发中穿插了状态机功能,底层是低速接口UART。module uart_loopback#( parameter DATA_WIDTH = 32,parameter MSB_FIRST = 0)( input Clk, input Rst_n, input uart_rx, output [2:0]led, output uart_tx ); wire [DATA_WIDTH-1:0]rx_data; wire Rx_Done; wire [7:0]data_byte; uart_data_tx #(.DATA_WIDTH(DATA_WIDTH),.MSB_FIRST(MSB_FIRST))uart_data_tx_inst( .Clk(Clk), .Rst_n(Rst_n), .Baud_Set(3\'d4), .data(rx_data), .send_en(Rx_Done),  .uart_tx(uart_tx), .Tx_Done(led[1]), //1byte数据发送完成标志 .uart_state(led[2]) //是否正在发送1byte数据 ); uart_data_rx #(.DATA_WIDTH(DATA_WIDTH),//参数重定义.MSB_FIRST(MSB_FIRST)//参数重定义)uart_data_rx_inst( .Clk(Clk), .Rst_n(Rst_n), .uart_rx(uart_rx), .Baud_Set(3\'d4), .data(rx_data), //指定位宽的待发送数据  .Rx_Done(Rx_Done), //待发送数据data的数据位宽到达指定位宽或者超时未接收到有效数据,都代表接收完成 .timeout_flag(led[0]) //是否超时,==1为超时  ); endmodule

6.2 uart_loopback的实例模块uart_data_tx

/*本模块实现1~16字节(8~256)位数据的发送,必须为8的整数倍。实现将多个字节的数据发送,并拼接为指定的位宽数据后输出,接收输出的数据位宽,可以在例化模块时使用DATA_WIDTH来修改*/module uart_data_tx#(parameter DATA_WIDTH = 8,parameter MSB_FIRST = 1)(input Clk,input Rst_n,input [DATA_WIDTH - 1 : 0]data,input send_en,input [2:0]Baud_Set,output uart_tx,output reg Tx_Done,output uart_state);reg [DATA_WIDTH - 1 : 0]data_r;reg [7:0] data_byte;reg byte_send_en;wire byte_tx_done;uart_byte_tx uart_byte_tx_inst(.Clk(Clk),.Rst_n(Rst_n),.data_byte(data_byte), //待传输8bit数据.send_en(byte_send_en), //发送使能 .Baud_Set(Baud_Set), //波特率设置 .uart_tx(uart_tx), //out,bit级别串口输出信号 .Tx_Done(byte_tx_done), //out,1byte数据发送完成标志 .uart_state(uart_state) //out,是否正在发送1byte数据 );reg [8:0]cnt;//cnt跟踪已发送位宽,阈值为DATA_WIDTH,判断其是否已经达到指定位宽reg [1:0]state;localparam S0 = 0;//等待发送请求localparam S1 = 1;//等待发起单字节数据发送localparam S2 = 2;//等待单字节数据发送完成localparam S3 = 3;//检查所有数据是否发送完成always@(posedge Clk or negedge Rst_n)if(!Rst_n)begindata_byte <= 0;byte_send_en <= 0;state <= S0;cnt <= 0;endelse begincase(state)S0: begindata_byte <= 0;cnt <= 0;Tx_Done <= 0;if(send_en)begin//数据发送请求send_enstate <= S1;//转移到状态1data_r <= data;//端口输入的data赋值给data_rendelse beginstate <= S0;//如果没收到端口的数据发送请求,就保持在S0状态data_r <= data_r;//data_r不变end end S1:beginbyte_send_en <= 1;//uart_byte_tx中的byte数据发送开始if(MSB_FIRST == 1)begin//先发高字节data_byte <= data_r[DATA_WIDTH-1:DATA_WIDTH - 8];//取高位数据给data_bytedata_r <= data_r << 8;//将缓存数据左移,舍去高位数据endelse begindata_byte <= data_r[7:0];//取低位数据给data_bytedata_r > 8;//将缓存数据右移,舍去低位数据endstate <= S2;//转移到状态2end S2:beginbyte_send_en <= 0;//暂停uart_byte_tx中的byte数据发送if(byte_tx_done)begin//单字节发送完成state <= S3;//转移到状态3cnt <= cnt + 9\'d8;//cnt跟踪已发送位宽end else state = DATA_WIDTH)begin//cnt跟踪的已发送位宽已经达到指定位宽state <= S0;//返回状态0cnt <= 0;//cnt归0Tx_Done <= 1;//所有数据发送完成endelse begin //已发送数据未到达指定位宽state <= S1;//回到状态1,继续发送byte数据,并进行数据缓存Tx_Done <= 0;//未发送完成endend default:state <= S0;//默认回到状态0,等待数据发送请求endcaseendendmodule

6.2.1 uart_data_tx的实例模块uart_byte_tx

module uart_byte_tx(input Clk , //模块全局时钟输入,50Minput Rst_n, //复位信号输入,低有效input [7:0]data_byte, //待传输8bit数据input send_en, //发送使能input [2:0]Baud_Set, //波特率设置output reg uart_tx, //串口输出信号output reg Tx_Done, //1byte数据发送完成标志output reg uart_state //发送数据状态);localparam START_BIT = 1\'b0;localparam STOP_BIT = 1\'b1; reg bps_clk; //波特率时钟reg [15:0]div_cnt; //分频计数器reg [15:0]bps_DR; //分频计数最大值reg [3:0]bps_cnt; //波特率时钟计数器reg [7:0]data_byte_reg;//data_byte寄存后数据always@(posedge Clk or negedge Rst_n)//发送状态if(!Rst_n)uart_state <= 1\'b0;else if(send_en)uart_state <= 1\'b1;else if(bps_cnt == 4\'d11)uart_state <= 1\'b0;elseuart_state <= uart_state;always@(posedge Clk or negedge Rst_n)//数据寄存器if(!Rst_n)data_byte_reg <= 8\'d0;else if(send_en)data_byte_reg <= data_byte;elsedata_byte_reg <= data_byte_reg;always@(posedge Clk or negedge Rst_n)//波特率选择器if(!Rst_n)bps_DR <= 16\'d5207;else begincase(Baud_Set)0:bps_DR <= 16\'d5207;1:bps_DR <= 16\'d2603;2:bps_DR <= 16\'d1301;3:bps_DR <= 16\'d867;4:bps_DR <= 16\'d433;default:bps_DR <= 16\'d5207;endcaseend//counter,波特率分频计数器always@(posedge Clk or negedge Rst_n)if(!Rst_n)div_cnt <= 16\'d0;else if(uart_state)beginif(div_cnt == bps_DR)div_cnt <= 16\'d0;elsediv_cnt <= div_cnt + 1\'b1;endelsediv_cnt <= 16\'d0;// bps_clk gen,bps和div_cnt连接的媒介always@(posedge Clk or negedge Rst_n)if(!Rst_n)bps_clk <= 1\'b0;else if(div_cnt == 16\'d1)bps_clk <= 1\'b1;elsebps_clk <= 1\'b0;//bps counteralways@(posedge Clk or negedge Rst_n)if(!Rst_n)bps_cnt <= 4\'d0;else if(bps_cnt == 4\'d11)bps_cnt <= 4\'d0;else if(bps_clk)bps_cnt <= bps_cnt + 1\'b1;elsebps_cnt <= bps_cnt;always@(posedge Clk or negedge Rst_n)if(!Rst_n)Tx_Done <= 1\'b0;else if(bps_cnt == 4\'d11)Tx_Done <= 1\'b1;elseTx_Done <= 1\'b0;always@(posedge Clk or negedge Rst_n)if(!Rst_n)uart_tx <= 1\'b1;else begincase(bps_cnt)0:uart_tx <= 1\'b1;1:uart_tx <= START_BIT;2:uart_tx <= data_byte_reg[0];3:uart_tx <= data_byte_reg[1];4:uart_tx <= data_byte_reg[2];5:uart_tx <= data_byte_reg[3];6:uart_tx <= data_byte_reg[4];7:uart_tx <= data_byte_reg[5];8:uart_tx <= data_byte_reg[6];9:uart_tx <= data_byte_reg[7];10:uart_tx <= STOP_BIT;default:uart_tx <= 1\'b1;endcaseendendmodule

6.3 uart_loopback的实例模块uart_data_rx

/*本模块实现1~16字节(8~256)位数据的接收,必须为8的整数倍。实现将多个字节的数据接收,并拼接为指定的位宽数据后输出,接收输出的数据位宽,可以在例化模块时使用DATA_WIDTH来修改**/module uart_data_rx#(parameter DATA_WIDTH = 16,//待发送数据data的数据位宽,适用希望通过串口发送出去的位宽超过8位数据的场景parameter MSB_FIRST = 1//先发高字节还是先发低字节。=1则先发高字节,=0则先发低字节)(input Clk,input Rst_n,input uart_rx,//串口输入的bit级别信号input [2:0]Baud_Set,output reg [DATA_WIDTH - 1 : 0]data,//指定位宽的待发送数据output reg Rx_Done,  //待发送数据data的数据位宽到达指定位宽或者超时未接收到有效数据,都代表接收完成output reg timeout_flag //是否超时,=1为超时);reg [DATA_WIDTH - 1 : 0]data_r;//此时此刻已经接收的数据wire [7:0] data_byte;//串口接收的1byte数据wire byte_rx_done;//1byte数据接收完成标志wire [19:0] TIMEOUT; reg [8:0]cnt;//cnt跟踪已发送位宽,阈值为DATA_WIDTH,判断其是否已经达到指定位宽 reg [1:0]state;uart_byte_rx uart_byte_rx_inst(.Clk(Clk),//全局时钟.Rst_n(Rst_n),//复位信号,低电平有效.baud_set(Baud_Set),//波特率选择器.uart_rx(uart_rx),//串口输入的bit级别信号.data_byte(data_byte), //out,串口接收的1byte数据.Rx_Done(byte_rx_done) //out,1byte数据接收完成标志);//根据波特率自动设置超时时间,表示“在没有接收到有效数据时,最多等待多少个时钟周期后判定超时”assign TIMEOUT = (Baud_Set == 3\'d0) ? 20\'d182291:  (Baud_Set == 3\'d1) ? 20\'d91145:  (Baud_Set == 3\'d2) ? 20\'d45572:  (Baud_Set == 3\'d3) ? 20\'d30381: 20\'d15190;localparam S0 = 0;//等待单字节接收完成信号localparam S1 = 1;//判断接收是否超时localparam S2 = 2;//检查所有数据是否接收完成reg [31:0]timeout_cnt;//超时计数器always@(posedge Clk or negedge Rst_n)if(!Rst_n)//复位信号触发,不超时timeout_flag = TIMEOUT)//超时计数器计满,超时timeout_flag <= 1\'d1;else if(state == S0)//超时计数器没计满且无复位信号,且状态机处于 timeout_flag <= 1\'d0;reg to_state;always@(posedge Clk or negedge Rst_n)if(!Rst_n) to_state <= 0;else if(!uart_rx)//uart_rx==0(起始位)时触发 to_state <= 1;else if(byte_rx_done)//1byte数据接收完成 to_state <= 0; always@(posedge Clk or negedge Rst_n)if(!Rst_n)timeout_cnt <= 32\'d0;else if(to_state)begin//uart_rx为低电平 if(byte_rx_done)//1byte数据接收完成 timeout_cnt = TIMEOUT)//超过一定时长,没有接收到有效数据 timeout_cnt <= TIMEOUT; else timeout_cnt <= timeout_cnt + 1\'d1;endalways@(posedge Clk or negedge Rst_n)if(!Rst_n)begindata_r <= 0;state <= S0;cnt <= 0; data <= 0; endelse begincase(state)S0: begin//等待单字节接收完成信号 Rx_Done <= 0;data_r <= 0;if(DATA_WIDTH == 8)begin //待发送数据data的数据位宽==8data <= data_byte;//data_byte为串口接收的1byte数据,为uart_byte_rx的输出端口Rx_Done 8(16、24、32等),且单字节接收完成state <= S1; //状态:判断接收是否超时cnt <= cnt + 9\'d8;if(MSB_FIRST == 1)//先发高字节data_r <= {data_r[DATA_WIDTH - 1 - 8 : 0], data_byte};else//先发低字节data_r <= {data_byte, data_r[DATA_WIDTH - 1 : 8]};endend S1:begin//判断接收是否超时if(timeout_flag)begin //超时state <= S0;//超时,算作接收完成,回到状态0Rx_Done <= 1;//超时,Rx_Done置为1endelse if(byte_rx_done)begin //没超时,且单字节接收完成state <= S2;cnt <= cnt + 9\'d8;//累计位宽计数器加1个字节if(MSB_FIRST == 1) //先发高字节data_r <= {data_r[DATA_WIDTH - 1 - 8 : 0], data_byte};else //MSB_FIRST == 0,先发低字节data_r = DATA_WIDTH)begin //已接收的数据到达指定位宽,完成接收state <= S0;//回到状态0cnt <= 0;//累计位宽计数器清0data <= data_r;//下一个时钟上沿,data_r赋值给dataRx_Done <= 1; //待发送数据data的数据位宽到达指定位宽或者超时未接收到有效数据,都代表接收完成endelse begin//已接收的数据未到达指定位宽,未完成接收state <= S1;//回到状态1,继续发送字节Rx_Done <= 0;//接收完成标志置0endend default:state <= S0;endcaseendendmodule

6.3.1 uart_data_rx的实例模块uart_byte_rx

module uart_byte_rx(input Clk, //模块全局时钟输入,50Minput Rst_n, //复位信号输入,低有效input [2:0]baud_set, //波特率设置input uart_rx, //串口输入的bit级别信号output reg[7:0]data_byte, //串口接收的1byte数据output reg Rx_Done //1byte数据接收完成标志);reg uart_rx_sync1; //同步寄存器reg uart_rx_sync2; //同步寄存器reg uart_rx_reg1; //数据寄存器reg uart_rx_reg2; //数据寄存器reg [15:0]bps_DR; //分频计数最大值reg [15:0]div_cnt; //分频计数器reg bps_clk; //波特率时钟reg [7:0] bps_cnt; //波特率时钟计数器reg uart_state;//接收数据状态wire uart_rx_nedge; reg [2:0]START_BIT;reg [2:0]STOP_BIT;reg [2:0]data_byte_pre [7:0];//同步串行输入信号,消除亚稳态always@(posedge Clk or negedge Rst_n)if(!Rst_n)beginuart_rx_sync1 <= 1\'b0;uart_rx_sync2 <= 1\'b0;endelse beginuart_rx_sync1 <= uart_rx;uart_rx_sync2 <= uart_rx_sync1;end//数据寄存器always@(posedge Clk or negedge Rst_n)if(!Rst_n)beginuart_rx_reg1 <= 1\'b0;uart_rx_reg2 <= 1\'b0;endelse beginuart_rx_reg1 <= uart_rx_sync2;uart_rx_reg2 <= uart_rx_reg1;end //下降沿检测assign uart_rx_nedge = !uart_rx_reg1 & uart_rx_reg2;always@(posedge Clk or negedge Rst_n)if(!Rst_n)bps_DR <= 16\'d324;else begincase(baud_set)0:bps_DR <= 16\'d324;1:bps_DR <= 16\'d162;2:bps_DR <= 16\'d80;3:bps_DR <= 16\'d53;4:bps_DR <= 16\'d26;default:bps_DR <= 16\'d324;endcaseend//counteralways@(posedge Clk or negedge Rst_n)if(!Rst_n)div_cnt <= 16\'d0;else if(uart_state)beginif(div_cnt == bps_DR)div_cnt <= 16\'d0;elsediv_cnt <= div_cnt + 1\'b1;endelsediv_cnt <= 16\'d0;// bps_clk genalways@(posedge Clk or negedge Rst_n)if(!Rst_n)bps_clk <= 1\'b0;else if(div_cnt == 16\'d1)bps_clk <= 1\'b1;elsebps_clk <= 1\'b0;//bps counteralways@(posedge Clk or negedge Rst_n)if(!Rst_n)bps_cnt  2)))bps_cnt <= 8\'d0;else if(bps_clk)bps_cnt <= bps_cnt + 1\'b1;elsebps_cnt <= bps_cnt;always@(posedge Clk or negedge Rst_n)if(!Rst_n)Rx_Done <= 1\'b0;else if(bps_cnt == 8\'d159)Rx_Done <= 1\'b1;elseRx_Done <= 1\'b0;always@(posedge Clk or negedge Rst_n)if(!Rst_n)beginSTART_BIT <= 3\'d0;data_byte_pre[0] <= 3\'d0;data_byte_pre[1] <= 3\'d0;data_byte_pre[2] <= 3\'d0;data_byte_pre[3] <= 3\'d0;data_byte_pre[4] <= 3\'d0;data_byte_pre[5] <= 3\'d0;data_byte_pre[6] <= 3\'d0;data_byte_pre[7] <= 3\'d0;STOP_BIT <= 3\'d0;endelse if(bps_clk)begincase(bps_cnt)0:begin START_BIT <= 3\'d0; data_byte_pre[0] <= 3\'d0; data_byte_pre[1] <= 3\'d0; data_byte_pre[2] <= 3\'d0; data_byte_pre[3] <= 3\'d0; data_byte_pre[4] <= 3\'d0; data_byte_pre[5] <= 3\'d0; data_byte_pre[6] <= 3\'d0; data_byte_pre[7] <= 3\'d0; STOP_BIT <= 3\'d0; end6 ,7 ,8 ,9 ,10,11:START_BIT <= START_BIT + uart_rx_sync2;22,23,24,25,26,27:data_byte_pre[0] <= data_byte_pre[0] + uart_rx_sync2;38,39,40,41,42,43:data_byte_pre[1] <= data_byte_pre[1] + uart_rx_sync2;54,55,56,57,58,59:data_byte_pre[2] <= data_byte_pre[2] + uart_rx_sync2;70,71,72,73,74,75:data_byte_pre[3] <= data_byte_pre[3] + uart_rx_sync2;86,87,88,89,90,91:data_byte_pre[4] <= data_byte_pre[4] + uart_rx_sync2;102,103,104,105,106,107:data_byte_pre[5] <= data_byte_pre[5] + uart_rx_sync2;118,119,120,121,122,123:data_byte_pre[6] <= data_byte_pre[6] + uart_rx_sync2;134,135,136,137,138,139:data_byte_pre[7] <= data_byte_pre[7] + uart_rx_sync2;150,151,152,153,154,155:STOP_BIT <= STOP_BIT + uart_rx_sync2;default: begin START_BIT <= START_BIT; data_byte_pre[0] <= data_byte_pre[0]; data_byte_pre[1] <= data_byte_pre[1]; data_byte_pre[2] <= data_byte_pre[2]; data_byte_pre[3] <= data_byte_pre[3]; data_byte_pre[4] <= data_byte_pre[4]; data_byte_pre[5] <= data_byte_pre[5]; data_byte_pre[6] <= data_byte_pre[6]; data_byte_pre[7] <= data_byte_pre[7]; STOP_BIT <= STOP_BIT; endendcaseendalways@(posedge Clk or negedge Rst_n)if(!Rst_n)data_byte <= 8\'d0;else if(bps_cnt == 8\'d159)begindata_byte[0] <= data_byte_pre[0][2];data_byte[1] <= data_byte_pre[1][2];data_byte[2] <= data_byte_pre[2][2];data_byte[3] <= data_byte_pre[3][2];data_byte[4] <= data_byte_pre[4][2];data_byte[5] <= data_byte_pre[5][2];data_byte[6] <= data_byte_pre[6][2];data_byte[7] <= data_byte_pre[7][2];endalways@(posedge Clk or negedge Rst_n)if(!Rst_n)uart_state <= 1\'b0;else if(uart_rx_nedge)uart_state  2)) || (bps_cnt == 8\'d155 && (STOP_BIT < 3)))uart_state <= 1\'b0;elseuart_state <= uart_state;endmodule