UART通信——字节的发送与接受
串行接口是一种可以将接受来自CPU的并行数据字符转换为连续的串行数据流发送出去,同时可将接受的串行数据流转换为并行的数据字符供给CPU的器件。
在FPGA入门阶段,UART通讯协议是必学的知识,因为很多模块都是使用UART协议进行传输的,详细的这里我就不再多做介绍了,先贴上我的代码模板再慢慢做解释。
协议发送原理:
在串口通信中波特率表示每秒能传输的数据位,9600就是每秒能传输9600位,1word=2byte、1byte=8bit,这里的9600位指的是9600bit。
每个开发板上都有它的系统时钟,初级阶段本人用的是basys2开发板,对于入门的种子选手是个不错的开端,此款开发板的系统时钟是50MHZ,我们假定波特率是9600bit/s传输,那么传输1bit就需要50M/9600个时钟周期。
依次推理,每传输1bit需要 **“系统时钟/波特率”**个时钟周期。
先来关于字节的发送,代码如下:
//-------1字节数据的发送-------
module uart_tx(
input clk,
input rst_n,
input cnt_start, //发送使能
input [7:0]tx_data,
output reg tx,
output reg tx_done //传输一个字节完成标志
);
reg [12:0]cnt_bps;
parameter bps_t = 13'd5207; //传输1bit所需计数值
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_bps <= 13'd0;
else if(cnt_bps == bps_t)
cnt_bps <= 13'd0;
else if(cnt_start)
cnt_bps <= cnt_bps + 1'b1;
else
cnt_bps <= 1'b0;
end
wire bps_sig;
assign bps_sig = (cnt_bps == 13'd2604) ? 1'b1 : 1'b0; //将采集数据的时刻放在波特率计数器每次循环计数的中间位置
reg [3:0]state;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
state <= 4'd0;
tx <= 1'b1;
tx_done <= 1'b0;
end
else begin
case(state)
0: if(cnt_start & bps_sig) begin
state <= state + 1'b1;
tx <= 1'b0;
end
else begin
state <= state;
tx <= 1'b1;
end
1,2,3,4,5,6,7,8: if(bps_sig) begin
tx <= tx_data[state - 1'b1]; //注意,从低位依次往高位发送
state <= state + 1'b1;
end
else begin
state <= state;
tx <= tx;
end
9,10: if(bps_sig) begin
state <= state + 1'b1;
tx <= 1'b1;
end
11: begin
state <= state + 1'b1;
tx_done <= 1'b1;
end
12: begin
state <= 1'b0;
tx_done <= 1'b0;
end
endcase
end
end
endmodule
这时候我们来看看字节发送的仿真图,更能清晰的理解整个过程:
从仿真图中可以看出,我们可以看出,输入端tx_data中数据11000101,输出tx为10100011,所以一定要明白,传输过程是从低字节开始传输!
【注:本博客中所有仿真图,紫线表示输出信号,黄线表示中间变量,绿线表示输入信号】
接下来就是数据的接收部分了,接收部分还是依照协议原理,字节传输形式:1-0-x-x-x-x-x-x-x-x-0-1,检测到下降沿即-1-0-(起始标志)后开始进行接收字节,将接收到的一一存入寄存器中,8个bit后结束接收,就是简单的1个字节的接收。
值得注意的是,虽然1字节=8bit,但是因为有起始标志(2bit:-1-0-)和结束标志(2bit:-0-1-),所以在发送和接收过程中,都需要考虑到这一点。
//-------1字节的接收-------
module uart_rx(
input clk,
input rst_n,
input data_rx,
output [7:0] data_tx,
output reg rx_int //接收字节时状态为1
);
reg [12:0]cnt_bps;
reg bps_start;
parameter bps_t = 13'd5207; //传输1bit所需计数值
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_bps <= 13'd0;
else if(cnt_bps == bps_t)
cnt_bps <= 13'd0;
else if(bps_start)
cnt_bps <= cnt_bps + 1'b1;
else
cnt_bps <= 1'b0;
end
wire bps_sig;
assign bps_sig = (cnt_bps == 13'd2604) ? 1'b1 : 1'b0; //将采集数据的时刻放在波特率计数器每次循环计数的中间位置
reg [1:0] rx;
always @(posedge clk or negedge rst_n)begin
if(!rst_n) rx <= 2'b11;
else begin
rx[0] <= data_rx;
rx[1] <= rx[0];
end
end
wire nege_edge;
assign nege_edge= rx[1] & ~rx[0]; //检测下降沿
reg [3:0]num;
always@(posedge clk or negedge rst_n)begin
if(!rst_n) begin
bps_start <= 1'b0;
rx_int <= 1'b0;
end
else if(nege_edge)begin
bps_start <= 1'b1;
rx_int <= 1'b1;
end
else if(num == 4'd10)begin
bps_start <= 1'b0;
rx_int <= 1'b0;
end
end
reg [7:0] rx_data_temp_r; //当前数据接收寄存器
reg [7:0] rx_data_r; //用来锁存数据
always@(posedge clk or negedge rst_n)begin
if(!rst_n) begin
rx_data_r <= 8'd0;
rx_data_temp_r <= 8'd0;
num <= 4'd0;
end
else if(rx_int) begin
if(bps_sig) begin
num <= num + 1'b1;
case(num)
4'd1: rx_data_temp_r[0] <= data_rx; //锁存第0bit
4'd2: rx_data_temp_r[1] <= data_rx; //锁存第1bit
4'd3: rx_data_temp_r[2] <= data_rx; //锁存第2bit
4'd4: rx_data_temp_r[3] <= data_rx; //锁存第3bit
4'd5: rx_data_temp_r[4] <= data_rx; //锁存第4bit
4'd6: rx_data_temp_r[5] <= data_rx; //锁存第5bit
4'd7: rx_data_temp_r[6] <= data_rx; //锁存第6bit
4'd8: rx_data_temp_r[7] <= data_rx; //锁存第7bit
default: ;
endcase
end
else if(num == 4'd10)begin
rx_data_r <= rx_data_temp_r;
num <= 4'd0;
end
end
end
assign data_tx = rx_data_r;
endmodule
下来就是我们的仿真测试部分了,在这里给大家一个方便省事的建议,先看我们的测试代码:
module tb_uart_rx;
// Inputs
reg clk;
reg rst_n;
reg data_rx;
// Outputs
wire [7:0] data_tx;
wire rx_int;
// Instantiate the Unit Under Test (UUT)
uart_rx uut (
.clk(clk),
.rst_n(rst_n),
.data_rx(data_rx),
.data_tx(data_tx),
.rx_int(rx_int)
);
always #10 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
data_rx = 0;
// Wait 20 ns for global reset to finish
#20
rst_n = 1;
data_rx = 1;
#104160
rst_n = 1;
data_rx = 0; //起始位:-1-0-
#104160 //传输11001011 (倒序)
rst_n = 1;
data_rx = 1;
#104160
rst_n = 1;
data_rx = 1;
#104160
rst_n = 1;
data_rx = 0;
#104160
rst_n = 1;
data_rx = 1;
#104160
rst_n = 1;
data_rx = 0;
#104160
rst_n = 1;
data_rx = 0;
#104160
rst_n = 1;
data_rx = 1;
#104160
rst_n = 1;
data_rx = 1;
#104160 //结束位 -0-1-
rst_n = 1;
data_rx = 0;
#104160
rst_n = 1;
data_rx = 1;
#104160 //复位
rst_n = 0;
data_rx = 0;
end
endmodule
注意到了吗,我给的1bit变化的持续时间是#104160ns,可大于但不能小于它,为什么?大家可以看到测试代码中我给的clk是20ns一个周期,那么此时1bit的传输就需要时间:5207/20 ns ,那么以后关于接收数据的仿真,套这个模板就对啦!
下面就是我们的仿真图:
依旧老原理啦,输入的是11010011,可以看到输出的是11001011,这个一定要记住噢。
好啦我的关于UART通讯协议就到这啦,这是简单的单字节的接收与发送,那关于多字节的也类似啦,以后有机会我会更新多字节的。
PS:我们都在各自生活的领域里,忙忙碌碌,如果有所选择,还是不顾一切向前跑吧。