对FPGA串口接收程序用自己的理解做一个总结,有问题还请指正。UART即通用异步收发器,俗称串口。总结几个要点。1、信号线个数,从电路层面来看总共有两根线,分别是接收数据线rx和发送数据线tx。2、数据格式,如果不算上奇偶校验位等等,串口数据常用格式为1位起始位(为0)+8位数据位+1位停止位(为1)。rx和tx空闲态为1,当从1拉低到0时就认为数据传输开始了。3、串口波特率,简单来说就是传输一个bit所需要的时间,比如波特率为9600,那么传输一个bit的时间就是1秒钟/9600。4、串口先传输低位,再传输高位。
知道了串口协议的构成,就可以开始写FPGA串口接收程序了。整体思路如下:1、在检测到接收数据线rx产生了一个下降沿,即从空闲态1拉低成了0,就认为有数据传输过来了。2、在检测到下降沿后就开始10个bit的接收,每个bit的时间根据系统时钟频率和串口波特率即可算出一个bit需要多少个系统时钟周斯,定义一个计数器对一个bit的传输时间计时,并选择在最大计数值的中间值进行采样(中间的话数据相对稳定)。3、再定义一个计数器对传输的位数进行计数,每当一个bit时间计数器达到最大计数值就加1,当加到9(0-9共10个bit)说明一个字节接收完毕。在传输位数计数器的值为1-8且bit传输时间计数器为中间值时,把采样值赋给一个8位reg的对应位即可。
以下是自己手敲的程序,写了些注释,程序功能已验证。
module uart_rx
#(
parameter CLK_FRE = 50_000_000,//系统时钟频率为50M
parameter BPS = 115200//串口波特率
)
(
input wire sys_clk,//系统时钟
input wire sys_rst_n,//系统复位
input wire rx,//串口接收数据线
output reg [7:0] rx_byte,//缓存接收到的一个字节数据
output reg rx_done//串口接收完成信号
);
reg [3:0] bit_cnt;//用于记录接收的bit数
reg [19:0] clk_cnt;//用于记录传输一个bit所需的系统时钟数
reg start_rx_flag;//开始串口接收标志信号
reg rx_r0;
reg rx_r1;
reg rx_r2;//对rx打拍,防止亚稳态问题,以及捕捉下降沿
wire down_edge;//用于表示rx有下降沿产生
localparam CLK_CNT = CLK_FRE/BPS; //传输一个bit所需要的系统时钟个数
//1/BPS:传输一个bit所需要的时间
//1/CLK_FRE:一个系统时钟周期所占的时间,1/50M=20ns
//传输一个bit所需要的系统时钟个数CLK_CNT=(1/BPS)/(1/CLK_FRE)=(CLK_FRE/BPS)
assign down_edge = (rx_r2)&(!rx_r1);//在下降沿的时候,rx_r2寄存的是rx上一个状态为1
//rx_r1寄存的是当前状态为0
//这样有下降沿的时候,down_edge就为1
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
rx_r0 <= 1'b1;
rx_r1 <= 1'b1;
rx_r2 <= 1'b1;//初值赋值为1,因为rx空闲态为1
end
else begin
rx_r0 <= rx;
rx_r1 <= rx_r0;
rx_r2 <= rx_r1;//对rx打了三拍,前两拍是为了防止亚稳态,多加了一拍是为了捕捉下降沿
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
start_rx_flag <= 1'b0;
end
else if(down_edge)begin//捕捉到下降沿,开始串口接收标志信号为1
start_rx_flag <= 1'b1;
end
else if(rx_done)begin//一个字节接收完成才拉低串口接收标志信号start_rx_flag
start_rx_flag <= 1'b0;
end
else begin
start_rx_flag <= start_rx_flag;
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
clk_cnt <= 20'd0;
end
//clk_cnt清零的条件要放在start_rx_flag的条件下判断,否则clk_cnt不会清零
//当start_rx_flag拉高时,系统时钟计数器开始计数,每个系统时钟周期加1,并且在达到计数最大值减1时清零
else if(start_rx_flag)begin
if(clk_cnt == (CLK_CNT - 1'b1))
clk_cnt <= 20'd0;
else
clk_cnt <= clk_cnt + 1'b1;
end
else begin
clk_cnt <= 20'd0;//不在接收状态,系统时钟计数器就始终为0
end
end
//接收bit数计数器
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
bit_cnt <= 4'd0;
end
else if(clk_cnt == (CLK_CNT - 1'b1))begin//每当系统时钟计数器达到计数最大值减1时就认为接收完了一个bit,接收bit数计数器就加1
bit_cnt <= bit_cnt +1'b1;
end
else if(bit_cnt == 4'd9)begin//接收bit数计数器在等于9时清零,一个字节10bit接收完毕
bit_cnt <= 4'd0;
end
else begin
bit_cnt <= bit_cnt;
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_byte <= 8'h0;
else begin
case(bit_cnt)//根据接收bit数计数器bit_cnt的值,在达到CLK_CNT的中间值时就采样rx并赋给rx_byte的对应位
1:begin //比如bit_cnt=0时,是一个字节的起始位,我们不需要,bit_cnt=1时就是该字节的最低位,所以赋值给rx_byte的最低位
if(clk_cnt == (CLK_CNT>>1))
rx_byte[0] <= rx_r2;
end
2:
begin
if(clk_cnt == (CLK_CNT>>1))
rx_byte[1] <= rx_r2;
end
3:begin
if(clk_cnt == (CLK_CNT>>1))
rx_byte[2] <= rx_r2;
end
4:begin
if(clk_cnt == (CLK_CNT>>1))
rx_byte[3] <= rx_r2;
end
5:begin
if(clk_cnt == (CLK_CNT>>1))
rx_byte[4] <= rx_r2;
end
6:begin
if(clk_cnt == (CLK_CNT>>1))
rx_byte[5] <= rx_r2;
end
7:begin
if(clk_cnt == (CLK_CNT>>1))
rx_byte[6] <= rx_r2;
end
8:begin
if(clk_cnt == (CLK_CNT>>1))
rx_byte[7] <= rx_r2;
end
default:;
endcase
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
rx_done <= 1'b0;
end
else if(bit_cnt == 4'd9)begin//当bit_cnt == 9时,认为一个字节的数据接收完成,拉高接收完成标志信号
rx_done <= 1'b1;
end
else begin
rx_done <= 1'b0;
end
end
endmodule