- 串口通信
串口(UART)全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
串口通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),用于发送数据一根是接收数据端口线叫 rx(Receiver),用于接收数据,如图所示。当主机向从机发送数据时,从机也可向主机发送数据。
- 串口通信时序图
串口通信时序图如下图所示。
0作为起始位,发送开始时第一个bit为低电平,然后发送8bit数据,8bit数据发送完成后检测到1,即停止位时,进入空闲状态,等待下一次数据发送。需要注意的是,UART是串行发送数据,并且先发送8bit数据的低位。
串口通信的传输速率由波特率来确定,常用的波特率有9600、19200、38400、57600以及115200等。波特率的单位为bit/s,即一秒可以传输多少bit的数据,因此在代码设计时需要考虑波特率选择。例如时钟频率为100MHz,波特率选择为9600,则传输一bit数据需要的时钟周期为1000000000/9600/10=10416。
- 串口发送模块
串口发送模块设计框图如下所示。Clk为系统时钟,Rst_n为系统复位信号,Send_en为发送使能信号,当Send_en拉高时发送模块开始检测起始位,Data为待发送的8位数据,
Baud_set为波特率设置信号,Send_end为发送结束信号,Tx为输入发送数据线的数据。
每次数据发生包含10bit数据(1位起始位,8位数据,1位终止位),可以设计两个计数器,其中一个计数器在计数到预设时钟数时清零,此时另一个计数器加一,知道计数为9时表示一次数据发生完成进入空闲状态,同时还需要设置波特率,以确定所需时钟数。利用case语句将8位数据进行发送。
具体代码如下所示:
`timescale 1ns / 1ps
module uart_tx(
input Clk, //系统时钟
input Rst_n, //系统复位
input [7:0] Data, //发送8位数据
input Send_start, //发送使能信号
input [2:0] Baud_set, //波特率设置
output reg Tx, //输入发送数据线数据
output reg Send_end //8位数据发生完成信号
);
reg [9:0] Baud_num; //一位数据发送所需时钟数
reg [9:0] Baud_cnt; //对一位数据发送所需时钟数进行计数
reg [3:0] bit_cnt; //Baud_cnt计满一次则加一,发送一位数据,计数到九
reg [7:0] Data_r; //用于存放待发送的8位数据
reg Send_en; //发送使能信号
always@(posedge Clk)begin
case (Baud_set)
0 : Baud_num <= 1_000_000_000/9600/10; //计算所需时钟数
1 : Baud_num <= 1_000_000_000/19200/10;
2 : Baud_num <= 1_000_000_000/38400/10;
3 : Baud_num <= 1_000_000_000/57600/10;
4 : Baud_num <= 1_000_000_000/115200/10;
default: Baud_num <= 1_000_000_000/115200/10;
endcase
end
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
Baud_cnt <= 10'b0;
else if(Send_en) //使能信号拉高才开始计数
if(Baud_cnt == Baud_num - 1'b1) //计满清零,进行下一次计数
Baud_cnt <= 10'b0;
else
Baud_cnt <= Baud_cnt + 1'b1;
else
Baud_cnt <= 10'b0;
end
always @(posedge Clk or negedge Rst_n)begin //对发送数据位数进行计数
if(!Rst_n)
bit_cnt <= 4'd0;
else if(Baud_cnt == Baud_num - 1'b1)
if(bit_cnt == 4'd9)
bit_cnt <= 4'd0; //10位数据发送完成,计数器清零
else
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
always @(posedge Clk or negedge Rst_n)begin //使能信号拉高将待发送数据写入寄存器
if(!Rst_n)
Data_r <= 'd0;
else if(Send_en)
Data_r <= Data;
end
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
Tx <= 1'b1;
else if(Send_en)begin
case (bit_cnt)
0 : Tx <= 1'b0; //起始位
1 : Tx <= Data_r[0]; //低位先开始发送
2 : Tx <= Data_r[1];
3 : Tx <= Data_r[2];
4 : Tx <= Data_r[3];
5 : Tx <= Data_r[4];
6 : Tx <= Data_r[5];
7 : Tx <= Data_r[6];
8 : Tx <= Data_r[7];
9 : Tx <= 1'b1; //终止位
default: Tx <= 1'b1;
endcase
end
else
Tx <= 1'b1;
end
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
Send_end <= 1'b0;
else if(bit_cnt == 'd9 && Tx == 1'b1 && Baud_cnt == Baud_num - 1'b1) //一次发送完成Send_end信号拉高
Send_end <= 1'b1;
else
Send_end <= 1'b0;
end
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
Send_en <= 1'b0;
else if(Send_start) //发送开始信号到达,拉高发送使能信号
Send_en <= 1'b1;
else if(bit_cnt == 'd9 && Tx == 1'b1 && Baud_cnt == Baud_num - 1'b1) //停止位发送完成,拉低发送使能信号
Send_en <= 1'b0;
else
Send_en <= Send_en;
end
endmodule
-
`timescale 1ns / 1ps module uart_tx_tb(); reg Clk; reg Rst_n; reg [2:0] Baud_set; reg Send_start; reg [7:0] Data; wire Send_end; wire Tx; uart_tx inst1( .Clk (Clk ), //系统时钟 .Rst_n (Rst_n ), //系统复位 .Data (Data ), //发送8位数据 .Send_start (Send_start ), //发送使能信号 .Baud_set (Baud_set ), //波特率设置 .Tx (Tx ), //输入发送数据线数据 .Send_end (Send_end ) //8位数据发生完成信号 ); initial Clk = 1'b1; always#5 Clk = ~Clk; initial begin Rst_n = 1'b0; Baud_set = 'd0; Data = 'd0; Send_start = 1'b0; #201; Rst_n = 1'b1; #100; Baud_set = 'd4; Send_start = 1'b1; #10; Send_start = 1'b0; Data = ({$random} % 1024); #200000; Baud_set = 'd3; Send_start = 1'b1; #10; Send_start = 1'b0; Data = ({$random} % 1024); #200000; $stop; end endmodule
- 串口接收模块@
串口接收模块如图所示,
Clk为系统时钟,Rst_n为系统复位信号,Rx为接收数据线输入信号,Data为接收到的8位数据,Rx_done为接收终止信号。
串口接收模块与串口发送模块的设计思路类似,主要是计数器的设计。具体代码如下:
`timescale 1ns / 1ps
module uart_rx(
input Clk,
input Rst_n,
input Rx,
input [2:0] Baud_set,
output reg[7:0] Data,
output reg Rx_done
);
reg [9:0] Baud_num; //一位数据发送所需时钟数
reg [9:0] Baud_cnt; //对一位数据发送所需时钟数进行计数
reg [3:0] bit_cnt; //Baud_cnt计满一次则加一,发送一位数据,计数到九
reg Rx_en; //接收使能信号,检测到下降沿时拉高
reg [1:0] Rx_r; //两位寄存器存放Rx的数据,防止产生亚稳态,同时方便检测下降沿
wire n_edge; //下降沿
reg [7:0] Data_r; //
always@(posedge Clk)begin
case (Baud_set)
0 : Baud_num <= 1_000_000_000/9600/10; //计算所需时钟数
1 : Baud_num <= 1_000_000_000/19200/10;
2 : Baud_num <= 1_000_000_000/38400/10;
3 : Baud_num <= 1_000_000_000/57600/10;
4 : Baud_num <= 1_000_000_000/115200/10;
default: Baud_num <= 1_000_000_000/115200/10;
endcase
end
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
Baud_cnt <= 10'b0;
else if(Rx_en) //使能信号拉高才开始计数
if(Baud_cnt == Baud_num - 1'b1) //计满清零,进行下一次计数
Baud_cnt <= 10'b0;
else
Baud_cnt <= Baud_cnt + 1'b1;
else
Baud_cnt <= 10'b0;
end
always @(posedge Clk or negedge Rst_n)begin //对接收数据位数进行计数
if(!Rst_n)
bit_cnt <= 4'd0;
else if(Rx_en) //使能信号拉高时才开始计数
if(Baud_cnt == Baud_num - 1'b1)
if(bit_cnt == 4'd9)
bit_cnt <= 4'd0; //10位数据接收完成,计数器清零
else
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
else
bit_cnt <= 4'd0; //使能信号没有拉高时计数器保持清零
end
always @(posedge Clk)begin //将数据存入二维寄存器
Rx_r[0] <= Rx;
Rx_r[1] <= Rx_r[0];
end
assign n_edge = (Rx_r == 2'b10); //检测是否产生下降沿
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
Rx_en <= 1'b0;
else if(n_edge) //检测到下降沿时拉高使能信号,进入接收状态
Rx_en <= 1'b1;
else if((bit_cnt == 'd9) && (Rx_r[1] == 1'b1) && (Baud_cnt == Baud_num >> 1'b1)) //拉低使能信号
Rx_en <= 1'b0;
else
Rx_en <= Rx_en;
end
always @(posedge Clk or negedge Rst_n)begin //
if(!Rst_n)begin
Data <= 8'b0; //复位清零
end
else if(Rx_en)begin //进入接收状态
case(bit_cnt)
1 : Data_r[0] <= Rx_r[1];
2 : Data_r[1] <= Rx_r[1];
3 : Data_r[2] <= Rx_r[1];
4 : Data_r[3] <= Rx_r[1];
5 : Data_r[4] <= Rx_r[1];
6 : Data_r[5] <= Rx_r[1];
7 : Data_r[6] <= Rx_r[1];
8 : Data_r[7] <= Rx_r[1];
default:; //起始位和终止位不用接收
endcase
end
else begin
Data <= 8'b0; //使能信号没有拉高时保持清零
end
end
always @(posedge Clk or negedge Rst_n) begin
if(!Rst_n)begin
Rx_done <= 1'b0; //复位清零
Data <= 8'd0;
end
else if((bit_cnt == 'd9) && (Rx_r[1] == 1'b1) && (Baud_cnt == Baud_num >> 1'b1))begin
Rx_done <= 1'b1; //一次发送结束
Data <= Data_r;
end
else begin
Rx_done <= 1'b0;
Data <= Data;
end
end
endmodule
`timescale 1ns / 1ps
module uart_rx_tb();
reg Clk;
reg Rst_n;
reg [2:0] Baud_set;
reg Rx;
wire [7:0] Data;
wire Rx_done;
parameter counter = 1_000_000_000/115200;
uart_rx inst1(
.Clk (Clk ), //系统时钟
.Rst_n (Rst_n ), //系统复位
.Data (Data ), //接收8位数据
.Baud_set (Baud_set ), //波特率设置
.Rx (Rx ), //输入接收数据线数据
.Rx_done (Rx_done ) //8位数据接收完成信号
);
initial Clk = 1'b1; //产生时钟
always#5 Clk = ~Clk;
task uart_rx_byte( //对数据进行发送
input [7:0] data
);
integer i;
for (i = 0;i < 10; i = i + 1) begin
case(i)
0 : Rx <= 1'b0;
1 : Rx <= data[0];
2 : Rx <= data[1];
3 : Rx <= data[2];
4 : Rx <= data[3];
5 : Rx <= data[4];
6 : Rx <= data[5];
7 : Rx <= data[6];
8 : Rx <= data[7];
9 : Rx <= 1'b1;
endcase
#counter;
end
endtask
initial begin
Rst_n = 1'b0;
Rx = 1'b1;
Baud_set = 3'd4;
#201;
Rst_n = 1'b1;
#20;
uart_rx_byte({$random} % 256); //随机发送8位数据
uart_rx_byte({$random} % 256);
uart_rx_byte({$random} % 256);
uart_rx_byte({$random} % 256);
uart_rx_byte({$random} % 256);
#100000;
$stop;
end
endmodule
本文主要是记录之前的学过的东西,如有侵权,请联系博主。