采用VIVADO开发环境,频率50MHz,波特率 256000,8位数据位,1位停止位。
串口接收程序源自正点原子的例程。
带仿真工程,数据帧格式如下图:
发送数据为:aa ff 03 00 0E 03 B1 86 10 00 40 01 11 00 00 00 00 00 00 00 11 00 00 00 00 00 11 11 55 CC
效果如图:
仿真效果图:
参考以下文章和视频:
FPGA串口多字节收发_哔哩哔哩_bilibili
FPGA串口多字节接收、解码和仿真_浅塘.小鲤鱼的博客-CSDN博客
完整工程代码:
链接:https://pan.baidu.com/s/1M_E8hh8MNzZKfbq3mVitig?pwd=8888
提取码:8888
顶层模块代码如下:
`timescale 1ns / 1ps
//不同数据格式需要改波特率、发送模块中的cnt阈值和环回模块中的cnt阈值、接收模块环回模块中的帧头帧尾判断及DATA_NUM
module uart_loopback_top(
input sys_clk, //外部50M时钟
input sys_rst_n, //外部复位信号,低有效
//(*mark_debug = "true"*)
input uart_rxd, //UART接收端口
output uart_txd //UART发送端口
// output [63:0] dataA, //数据包1
// output [63:0] dataB, //数据包2
// output [63:0] dataC //数据包3
);
//parameter define
parameter CLK_FREQ = 50000000; //定义系统时钟频率
parameter UART_BPS = 256000; //定义串口波特率
//wire define
//(*mark_debug = "true"*)
wire uart_done; //UART单字节接收完成
wire uart_recv_done; //UART一帧接收完成
wire [7:0] uart_data; //UART单字节接收数据
wire uart_send_en; //UART发送使能
wire [7:0] uart_send_data; //UART发送数据
wire uart_tx_busy; //UART发送忙状态标志
wire [7:0] cnt;
//*****************************************************
//** main code
//*****************************************************
//串口接收模块
//uart_recv #(
// .CLK_FREQ (CLK_FREQ), //设置系统时钟频率
// .UART_BPS (UART_BPS)) //设置串口接收波特率
//u_uart_recv(
// .sys_clk (sys_clk),
// .sys_rst_n (sys_rst_n),
// .uart_rxd (uart_rxd),
// .uart_done (uart_recv_done),
// .uart_data (uart_recv_data)
// );
//串口环回模块
//uart_loop u_uart_loop(
// .sys_clk (sys_clk),
// .sys_rst_n (sys_rst_n),
// .recv_done (uart_recv_done), //接收一帧数据(30字节数据)完成标志信号,4字节帧头,3*8=24字节数据,2字节帧尾
// .recv_data (uart_recv_data), //接收的数据
// .tx_busy (uart_tx_busy), //发送忙状态标志
// .send_en (uart_send_en), //发送使能信号
// .send_data (uart_send_data) //待发送数据
// );
//串口接收模块
uart_recv #(
.CLK_FREQ (CLK_FREQ), //设置系统时钟频率
.UART_BPS (UART_BPS)) //设置串口接收波特率
u_uart_recv(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_done (uart_done),
.uart_data (uart_data),
.recv_done (uart_recv_done),
.dataA (dataA),
.dataB (dataB),
.dataC (dataC)
);
//串口发送模块
uart_send #(
.CLK_FREQ (CLK_FREQ), //设置系统时钟频率
.UART_BPS (UART_BPS)) //设置串口发送波特率
u_uart_send(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.cnt (cnt),
.uart_en (uart_send_en),
.uart_din (uart_send_data),
.uart_tx_busy (uart_tx_busy),
.uart_txd (uart_txd)
);
//串口环回模块
uart_loop u_uart_loop(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_done (uart_done), //接收单字节数据接收一帧数据(30字节数据)完成标志信号,4字节帧头,3*8=24字节数据,2字节帧尾
.uart_data (uart_data), //接收的数据
.tx_busy (uart_tx_busy), //发送忙状态标志
.cnt (cnt),
.send_en (uart_send_en), //发送使能信号
.send_data (uart_send_data) //待发送数据
);
endmodule
分模块代码:文章来源:https://uudwc.com/A/59jWJ
`timescale 1ns / 1ps
module uart_recv(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位,低电平有效
input uart_rxd, //UART接收端口
output reg [ 7:0] uart_data, //接收的数据
output reg uart_done, //接收一字节数据完成标志
output reg uart_get, //单字节采样点
output reg [7:0] pack_cnt, //字节计数
output reg pack_ing, //接收过程标志位
output reg pack_done, //帧接收完成标志位
output reg [7:0] pack_num, //接收到的字节数
output reg recv_done, //接收完一帧数据的接收和解码
output reg [63:0] dataA, //解码后数据,8*8=64bit
output reg [63:0] dataB, //解码后数据,8*8=64bit
output reg [63:0] dataC //解码后数据,8*8=64bit
);
localparam DATA_NUM = 30;
integer j;
reg [7:0] pack_data [DATA_NUM-1:0]; //接收的数据
//parameter define
parameter CLK_FREQ = 50_000_000; //系统时钟频率
parameter UART_BPS = 115200; //串口波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS; //为得到指定波特率
//localparam TimeOut = BPS_CNT*DATA_NUM*10*2;//超时时间
//起始信号下降沿捕捉
wire start_flag;
reg uart_rxd_d0;
reg uart_rxd_d1;
reg [15:0] clk_cnt; //系统时钟计数器
reg [ 3:0] rx_cnt; //接收数据计数器
reg rx_flag; //接收过程标志信号
reg [ 7:0] rxdata; //接收数据寄存器
//接收信号完成标志位上升沿
wire rxdone_flag;
reg uart_done_d0;
reg uart_done_d1;
//包数据接收完成
wire packdone_flag;
reg pack_done_d0;
reg pack_done_d1;
//*****************************************************
//** main code
//*****************************************************
//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
assign start_flag = uart_rxd_d1 & (~uart_rxd_d0);
//对UART接收端口的数据延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_rxd_d0 <= 1'b0;
uart_rxd_d1 <= 1'b0;
end
else begin
uart_rxd_d0 <= uart_rxd;
uart_rxd_d1 <= uart_rxd_d0;
end
end
//当脉冲信号start_flag到达时,进入接收过程
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
rx_flag <= 1'b0;
else begin
if(start_flag) //检测到起始位
rx_flag <= 1'b1; //进入接收过程,标志位rx_flag拉高
else if((rx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
rx_flag <= 1'b0; //计数到停止位中间时,停止接收过程
else
rx_flag <= rx_flag;
end
end
//进入接收过程后,启动系统时钟计数器与接收数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
clk_cnt <= 16'd0;
rx_cnt <= 4'd0;
end
else if ( rx_flag ) begin //处于接收过程
if (clk_cnt < BPS_CNT - 1) begin
clk_cnt <= clk_cnt + 1'b1;
rx_cnt <= rx_cnt;
end
else begin
clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
rx_cnt <= rx_cnt + 1'b1; //此时接收数据计数器加1
end
end
else begin //接收过程结束,计数器清零
clk_cnt <= 16'd0;
rx_cnt <= 4'd0;
end
end
//根据接收数据计数器来寄存uart接收端口数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
rxdata <= 8'd0;
uart_get<=1'b0;
end
else if(rx_flag) //系统处于接收过程
if (clk_cnt == BPS_CNT/2) begin //判断系统时钟计数器计数到数据位中间
case ( rx_cnt )
4'd1 : rxdata[0] <= uart_rxd_d1; //寄存数据位最低位
4'd2 : rxdata[1] <= uart_rxd_d1;
4'd3 : rxdata[2] <= uart_rxd_d1;
4'd4 : rxdata[3] <= uart_rxd_d1;
4'd5 : rxdata[4] <= uart_rxd_d1;
4'd6 : rxdata[5] <= uart_rxd_d1;
4'd7 : rxdata[6] <= uart_rxd_d1;
4'd8 : rxdata[7] <= uart_rxd_d1; //寄存数据位最高位
default:;
endcase
uart_get<=1'b1;
end
else begin
rxdata <= rxdata;
uart_get<=1'b0;
end
else begin
rxdata <= 8'd0;
uart_get<=1'b0;
end
end
//数据接收完毕后给出标志信号并寄存输出接收到的数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_data <= 8'd0;
uart_done <= 1'b0;
end
else if(rx_cnt == 4'd9) begin //接收数据计数器计数到停止位时
uart_data <= rxdata; //寄存输出接收到的数据
uart_done <= 1'b1; //并将接收完成标志位拉高
end
else begin
uart_data <= 8'd0;
uart_done <= 1'b0;
end
end
//---单字节接收程序,uart_done接收完成标志位会持续半个波特率周期,捕捉上升沿可以计数,高电平状态,接收数据有效
//==============================================接收多个字节,添加的模块====================================================//
//捕获接收完成标志位的上升沿,得到一个时钟周期的脉冲信号
assign rxdone_flag = uart_done_d0 & (~uart_done_d1);
//对UART完成标志的数据延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_done_d0 <= 1'b0;
uart_done_d1 <= 1'b0;
end
else begin
uart_done_d0 <= uart_done;
uart_done_d1 <= uart_done_d0;
end
end
//接收到的数据存入数组中,并计数
always @(posedge sys_clk or negedge sys_rst_n) begin //接收到数据
if (!sys_rst_n) begin
pack_cnt <=8'd0;
pack_num <=8'd0;
pack_done<=1'b0;
pack_ing <=1'b0;
for (j=0;j<DATA_NUM;j=j+1)
pack_data[j] <= 8'd0;
end
else if(rxdone_flag) begin //接收完成标志位的上升沿,延迟了两个时钟周期
if (pack_cnt < DATA_NUM-1) begin //处于接收过程中
for (j=0;j<DATA_NUM;j=j+1) begin
if(j==pack_cnt)
pack_data[pack_cnt] <= uart_data;//寄存输出接收到的数据
else
pack_data[j] <= pack_data[j];
end
pack_cnt <= pack_cnt + 1'b1;
pack_num <= 8'd0;
pack_done<=1'b0;
pack_ing <=1'b1;
end
else begin //接收完成---最后一个字节的接收
for (j=0;j<DATA_NUM;j=j+1) begin
if(j==pack_cnt)
pack_data[pack_cnt] <= uart_data;//寄存输出接收到的数据
else
pack_data[j] <= pack_data[j];
end
pack_num <= pack_cnt + 1'b1; //加上最后一个字节
pack_cnt <= 8'd0; //此时接收数据计数器归零,只有接收完成时才清零
pack_done<= 1'b1; //输出帧接收完成标志位,只存在一个周期
pack_ing <= 1'b0;
end
end
else begin
pack_cnt <=pack_cnt;
pack_ing <=pack_ing;//保持
pack_num <=pack_num;
pack_done<=1'b0;
for (j=0;j<DATA_NUM;j=j+1)
pack_data[j] <= pack_data[j];
end
end
//------------解码-------------------------//
//捕获接收完成标志位的上升沿,得到一个时钟周期的脉冲信号
assign packdone_flag = pack_done_d0 & (~pack_done_d1);
//对UART完成标志的数据延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
pack_done_d0 <= 1'b0;
pack_done_d1 <= 1'b0;
end
else begin
pack_done_d0 <= pack_done;
pack_done_d1 <= pack_done_d0;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
dataA <= 8'd0;
dataB <= 16'd0;
dataC <=16'd0;
recv_done <=1'b0;
end
else if(packdone_flag) begin //数据接收完成,进行解码
if(pack_num==DATA_NUM && pack_data[0]==8'haa && pack_data[1]==8'hff && pack_data[2]==8'h03 && pack_data[3]==8'h00 && pack_data[28]==8'h55 && pack_data[29]==8'hcc ) begin //判断数据正误
dataA <= {pack_data[4],pack_data[5],pack_data[6],pack_data[7],pack_data[8],pack_data[9],pack_data[10],pack_data[11]};
dataB <= {pack_data[12],pack_data[13],pack_data[14],pack_data[15],pack_data[16],pack_data[17],pack_data[18],pack_data[19]};
dataC <= {pack_data[20],pack_data[21],pack_data[22],pack_data[23],pack_data[24],pack_data[25],pack_data[26],pack_data[27]};
recv_done <=1'b1;
end
else begin //数据错误
dataA <= 64'd0;
dataB <= 64'd0;
dataC <= 64'd0;
recv_done <=1'b0;
end
end
else begin //数据保持到下一个周期,标志位保持一个周期
dataA <= dataA;
dataB <= dataB;
dataC <= dataC;
recv_done <=1'b0;
end
end
endmodule
`timescale 1ns / 1ps
module uart_send(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位,低电平有效
input [7:0] cnt,
input uart_en, //发送使能信号
input [7:0] uart_din, //待发送数据
output uart_tx_busy, //发送忙状态标志
output reg uart_txd //UART发送端口
);
//parameter define
parameter CLK_FREQ = 50000000; //系统时钟频率
parameter UART_BPS = 115200; //串口波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次
//reg define
reg uart_en_d0;
reg uart_en_d1;
reg [15:0] clk_cnt; //系统时钟计数器
reg [ 3:0] tx_cnt; //发送数据计数器
reg tx_flag; //发送过程标志信号
reg [ 7:0] tx_data; //寄存发送数据
//wire define
wire en_flag;
//*****************************************************
//** main code
//*****************************************************
//在串口发送过程中给出忙状态标志
assign uart_tx_busy = tx_flag;
//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
assign en_flag = (~uart_en_d1) & uart_en_d0;
//对发送使能信号uart_en延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_en_d0 <= 1'b0;
uart_en_d1 <= 1'b0;
end
else begin
uart_en_d0 <= uart_en;
uart_en_d1 <= uart_en_d0;
end
end
//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
end
else if (en_flag && (cnt>=8'd2)) begin //检测到发送使能上升沿
tx_flag <= 1'b1; //进入发送过程,标志位tx_flag拉高
tx_data <= uart_din; //寄存待发送的数据
end
//计数到停止位结束时,停止发送过程
else if ((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT -(BPS_CNT/16))) begin
tx_flag <= 1'b0; //发送过程结束,标志位tx_flag拉低
tx_data <= 8'd0;
end
else begin
tx_flag <= tx_flag;
tx_data <= tx_data;
end
end
//进入发送过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_cnt <= 16'd0;
else if (tx_flag) begin //处于发送过程
if (clk_cnt < BPS_CNT - 1)
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
end
else
clk_cnt <= 16'd0; //发送过程结束
end
//进入发送过程后,启动发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
tx_cnt <= 4'd0;
else if (tx_flag) begin //处于发送过程
if (clk_cnt == BPS_CNT - 1) //对系统时钟计数达一个波特率周期
tx_cnt <= tx_cnt + 1'b1; //此时发送数据计数器加1
else
tx_cnt <= tx_cnt;
end
else
tx_cnt <= 4'd0; //发送过程结束
end
//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
uart_txd <= 1'b1;
else if (tx_flag)
case(tx_cnt)
4'd0: uart_txd <= 1'b0; //起始位
4'd1: uart_txd <= tx_data[0]; //数据位最低位
4'd2: uart_txd <= tx_data[1];
4'd3: uart_txd <= tx_data[2];
4'd4: uart_txd <= tx_data[3];
4'd5: uart_txd <= tx_data[4];
4'd6: uart_txd <= tx_data[5];
4'd7: uart_txd <= tx_data[6];
4'd8: uart_txd <= tx_data[7]; //数据位最高位
4'd9: uart_txd <= 1'b1; //停止位
default: ;
endcase
else
uart_txd <= 1'b1; //空闲时发送端口为高电平
end
endmodule
`timescale 1ns / 1ps
module uart_loop(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位,低电平有效
input uart_done, //接收一帧数据完成标志
input [7:0] uart_data, //接收的数据
input tx_busy, //发送忙状态标志
output reg send_en, //发送使能信号
output reg[7:0] cnt,
// output wire[7:0] cnt0,
output reg [7:0] send_data //待发送数据
);
//reg define
reg uart_done_d0;
reg uart_done_d1;
reg tx_ready;
reg [4:0]state;
reg [7:0]cnt;
reg [7:0]post_data;
//wire define
wire uart_done_flag;
//wire [7:0]cnt0;
parameter IDLE=5'b00001,AASTATE=5'b00010,FFSTATE=5'b00100,STATE03=5'b01000,STATE00=5'b10000;
//*****************************************************
//** main code
//*****************************************************
//捕获uart_done上升沿,得到一个时钟周期的脉冲信号
assign uart_done_flag = (~uart_done_d1) & uart_done_d0;
//assign cnt0 = cnt;
//对发送使能信号uart_done延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_done_d0 <= 1'b0;
uart_done_d1 <= 1'b0;
end
else begin
uart_done_d0 <= uart_done;
uart_done_d1 <= uart_done_d0;
end
end
//判断帧头部分,不判断帧尾,因为数据长度固定
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
post_data<=8'd0;state<=IDLE;
end
else case(state)
IDLE:begin
post_data<=8'b0;
cnt<=8'd0;
if(uart_done_flag)begin
if(uart_data==8'hAA)state<=AASTATE;
else state<=IDLE;
end
end
AASTATE:if(uart_done_flag)begin
if(uart_data==8'hFF)state<=FFSTATE;
else state<=IDLE;
end
FFSTATE:if(uart_done_flag)begin
if(uart_data==8'h03)state<=STATE03;
else state<=IDLE;
end
STATE03:if(uart_done_flag)begin
if(uart_data==8'h00)state<=STATE00;
else state<=IDLE;
end
STATE00:if(uart_done_flag)begin
post_data<=uart_data;
cnt<=cnt+1'b1;//24个字节
if(cnt==8'd25)begin //相对uart_data延迟了一个时钟,所以cnt要多一位,防止丢失最后一个数据
state<=IDLE;
end
end
endcase
end
//判断接收完成信号,并在串口发送模块空闲时给出发送使能信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
tx_ready <= 1'b0;
send_en <= 1'b0;
send_data <= 8'd0;
end
else begin
if(uart_done_flag)begin //检测串口接收到数据
tx_ready <= 1'b1; //准备启动发送过程
send_en <= 1'b0;
send_data <= post_data; //寄存串口接收的数据,相对uart_data延迟了一个时钟
end
else if(tx_ready && (~tx_busy)) begin //检测串口发送模块空闲并且在有效数据区才发送数据使能信号
tx_ready <= 1'b0; //准备过程结束
send_en <= 1'b1; //拉高发送使能信号
end
end
end
endmodule
文章来源地址https://uudwc.com/A/59jWJ