项目名称
UART接收模块设计与验证
项目要求
利用串口助手向fpga发送数据用来点亮led
项目说明
rs232协议示意图如下,当PC未给FPGA通过串口发送数据的时候,串口处于空闲状态(高电平),当PC端需要开始发送数据时,需要给一个起始位,然后就是发送一个字节的数据,从低位到高位依次发送,数据线从高电平转为低电平代表一个起始位。发送一帧数据之后校验位,在本项目中未使用。
这个项目比较简单,就直接开始进行设计,串口的波特率为9600bit/s,就是每秒传送9600位,每一位需要传送的时 为1/9600 s=1_000_000_000/9600ns=104167ns ,那么传送一位数据需要计数器计数104167/20=5208(取整)次。
好了开始进行设计,下图是设计的时序图,rx_data3,是同步之后的rs232数据线上的数据,bpa_cnt是波特率计数器,data_flag是数据有效标志,data_sample是计数一半的时候开始采样数据,比较稳定,data_cnt是接收数据计数,flag表示接收数据完成标志,data为接收的8位数据。
每个人的设计思路不一样,笔者就从自己的设计思路出发进行说明,虽然看起来比较简单,但是也有许多小细节要注意,否则可能会出问题,当检测到rx_data3下降沿之后就开始 进行发送数据,先是发送一个低电平的起始位,当数据有效的时候,波特率小于5207,就一直+1,每次计数一半的时候进行采样数据,将data_sample置一拍高电平。当data_sample为高电平的时候,下一个时钟沿到来的时候,接收数据计数器开始计数。
这里需要注意的是data_cnt在复位的时候为0,当计数到10的时候,下一个采样时间到来的时候就从1开始计数,为什么要从1开始计数,因为下一个采样时间到来的时候对应的是上图中的第一个起始位采样点,上图中的采样点到来data_cnt为1,如果从0开始计数那么就会出错,笔者也是在这里出错了,所以也花费了一些时间。
第二个需要注意的是当data_cnt为1的时候data_sample为高电平的时候开始接收数据。
第三个要注意的是接收数据完成标志信号flag是在接收完8位数据之后开始置高,当data_cnt为9的时候,bps_cnt为计数中间值加3的时候置高,因为我想要一个时钟周期的高电平,如果只是当data_cnt为9的时候置高,将有5208个时钟周期的高电平,加3是因为,在data_cnt为8的最后一个时钟周期为计数中间值,随便加上一个1-5207都可以,这里没有用到标志信号,笔者直接将接收的数据给led显示。最后进行代码设计。
代码设计
顶层模块设计
module uart_rx_top(
input clk,
input rst_n,
input rs232_data,
output led595_dout,
output led595_clk,
output led595_latch
);
wire [7:0] data;
wire flag;
uart_rx
#(.bps_cnt_end(5207))
uart_rx
(
.clk(clk),
.rst_n(rst_n),
.rs232_data(rs232_data),
.data(data),
.flag(flag)
);
led_74595_driver led_74595_driver
(
.clk(clk),
.rst_n(rst_n),
.led595_dout(led595_dout),
.led595_clk(led595_clk),
.led595_latch(led595_latch),
.led_data(data)
);
endmodule
接收模块设计
module uart_rx
#(parameter bps_cnt_end=5207)
(
input clk,
input rst_n,
input rs232_data,
output reg [7:0]data,
output reg flag
);
//串口数据同步处理
reg rx_data1;
reg rx_data2;
reg rx_data3;
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
rx_data1<=0;
rx_data2<=0;
rx_data3<=0;
end
else begin
rx_data1<=rs232_data;
rx_data2<=rx_data1;
rx_data3<=rx_data2;
end
//下降沿起始位检测
wire nedge= !rx_data2 & rx_data3;
reg data_flag;
reg [12:0]bps_cnt;
always@(posedge clk or negedge rst_n)
if(!rst_n)
bps_cnt<=0;
else if(data_flag)begin
if(bps_cnt<5207)
bps_cnt<=bps_cnt+1;
else
bps_cnt<=0;
end
else
bps_cnt<=0;
reg [3:0] data_cnt;
always@(posedge clk or negedge rst_n)
if(!rst_n)
data_flag<=0;
else if(nedge)
data_flag<=1;
else if(bps_cnt==5205 && data_cnt==10)
data_flag<=0;
reg data_sample;
reg [12:0] bps_cnt_mid=bps_cnt_end/2+1;
always@(posedge clk or negedge rst_n)
if(!rst_n)
data_sample<=0;
else if(data_flag && bps_cnt==bps_cnt_mid)
data_sample<=1;
else
data_sample<=0;
always@(posedge clk or negedge rst_n)
if(!rst_n)
data_cnt<=0;
else if(data_sample)begin
if(data_cnt<10)
data_cnt<=data_cnt+1;
else
data_cnt<=1;
end
else
data_cnt<=data_cnt;
always@(posedge clk or negedge rst_n)
if(!rst_n)
flag<=0;
else if(data_cnt==9 && bps_cnt==bps_cnt_mid+3)
flag<=1;
else
flag<=0;
//接收数据
always@(posedge clk or negedge rst_n)
if(!rst_n)
data<=0;
else if(data_sample && data_cnt>=1 && data_cnt<=8)
data<={rx_data3,data[7:1]};
else
data<=data;
endmodule
74hc595驱动
module led_74595_driver
(
input clk, //50MHz
input rst_n, //global reset
output led595_dout, //74hc595 serial data input
output led595_clk, //74hc595 shift clock (rising edge)
output led595_latch, //74hc595 latch clock (rising edge)
input [7:0] led_data //led data input
);
reg [7:0] led_data_r = 8'h00;
reg update_flag;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
led_data_r <= 0;
update_flag <= 1;
end
else
begin
led_data_r <= led_data;
update_flag <= (led_data_r != led_data) ? 1'b1 : 1'b0;
end
end
localparam DELAY_CNT = 3'd7;
reg [2:0] delay_cnt;
reg shift_state;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
delay_cnt <= 0;
else if(shift_state == 1'b1)
delay_cnt <= (delay_cnt < DELAY_CNT) ? delay_cnt + 1'b1 : 3'd0;
else
delay_cnt <= 0;
end
wire shift_flag = (delay_cnt == DELAY_CNT) ? 1'b1 : 1'b0;
wire shift_clk = (delay_cnt > DELAY_CNT/2) ? 1'b1 : 1'b0;
reg [3:0] led_cnt;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
shift_state <= 0;
led_cnt <= 0;
end
else
begin
case(shift_state)
0: begin
led_cnt <= 0;
if(update_flag)
shift_state <= 1;
else
shift_state <= 0;
end
1: begin
if(shift_flag)
begin
if(led_cnt < 4'd8)
begin
led_cnt <= led_cnt + 1'b1;
shift_state <= 1'd1;
end
else
begin
led_cnt <= 0;
shift_state <= 0;
end
end
else
begin
led_cnt <= led_cnt;
shift_state <= shift_state;
end
end
endcase
end
end
assign led595_dout = (shift_state == 1'b1 && led_cnt < 4'd8) ? led_data[3'd7 - led_cnt] : 1'b0;
assign led595_clk = (shift_state == 1'b1 && led_cnt < 4'd8) ? shift_clk : 1'b0;
assign led595_latch = (shift_state == 1'b1 && led_cnt == 4'd8) ? 1'b1 : 1'b0;
endmodule
代码仿真
就只是对串口接收模块进行了仿真,对顶层模块仿真方法也是一样的,就不在赘述了。模拟串口发送11,55,22,以16进制发送。
`timescale 1ns/1ns
module uart_rx_tb;
reg clk;
reg rst_n;
reg rs232_data_tx;
wire [7:0] data;
wire flag;
uart_rx
#(.bps_cnt_end(5207))
uart_rx
(
.clk(clk),
.rst_n(rst_n),
.rs232_data(rs232_data_tx),
.data(data),
.flag(flag)
);
initial clk=0;
always #10 clk=~clk;
reg [7:0] temp_data;
initial begin
rst_n=0;
temp_data=8'h0;
rs232_data_tx=0;
#200;
rst_n=1;
temp_data=8'h11;
rs232_data_tx=1;
#200;
rs232_data_tx=0;
#104160;
rs232_data_tx=temp_data[0];
#104160;
rs232_data_tx=temp_data[1];
#104160;
rs232_data_tx=temp_data[2];
#104160;
rs232_data_tx=temp_data[3];
#104160;
rs232_data_tx=temp_data[4];
#104160;
rs232_data_tx=temp_data[5];
#104160;
rs232_data_tx=temp_data[6];
#104160;
rs232_data_tx=temp_data[7];
#104160;
rs232_data_tx=1;
#104160;
rs232_data_tx=0;
#104160;
temp_data=8'h55;
rs232_data_tx=1;
#200;
rs232_data_tx=0;
#104160;
rs232_data_tx=temp_data[0];
#104160;
rs232_data_tx=temp_data[1];
#104160;
rs232_data_tx=temp_data[2];
#104160;
rs232_data_tx=temp_data[3];
#104160;
rs232_data_tx=temp_data[4];
#104160;
rs232_data_tx=temp_data[5];
#104160;
rs232_data_tx=temp_data[6];
#104160;
rs232_data_tx=temp_data[7];
#104160;
rs232_data_tx=1;
#104160;
temp_data=8'h22;
rs232_data_tx=1;
#200;
rs232_data_tx=0;
#104160;
rs232_data_tx=temp_data[0];
#104160;
rs232_data_tx=temp_data[1];
#104160;
rs232_data_tx=temp_data[2];
#104160;
rs232_data_tx=temp_data[3];
#104160;
rs232_data_tx=temp_data[4];
#104160;
rs232_data_tx=temp_data[5];
#104160;
rs232_data_tx=temp_data[6];
#104160;
rs232_data_tx=temp_data[7];
#104160;
rs232_data_tx=1;
#104160;
rs232_data_tx=0;
$stop;
end
endmodule
仿真结果
可以看到能正确接收到数据11,55和22。
项目验证
以16进制发送22点亮led。