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