一、Uart串口通信
uart串口通信是一种异步串行全双工通信方式,tx端用于数据发送;rx端用于数据接收。信号线在空闲时为高电平。
异步通信是按字符传输的。每传输一个字符就用起始位来收、发双方的同步。不会因收发双方的时钟频率的小的偏差导致错误。这种传输方式利用每一帧的起、止信号来建立发送与接收之间的同步。特点是:每帧内部各位均采用固定的时间间隔,而帧与帧之间的间隔时随即的。接收机完全靠每一帧的起始位和停止位来识别字符时正在进行传输还是传输结束。uart也是异步通信方式,数据发送会包装成数据帧的形式发送,数据帧的格式为:
空闲时间为高电平,故rx端接收到低电平时,表示有数据开始发送,再根据波特率对数据进行接收。波特率:每秒传输二进制数据的位数,单位bps。
若波特率为115200bps,即代表一秒钟需要传输115200个bit数据。
1s=10e9ns,50MHz时钟周期为20ns,则传输一个bit所需的时间为
10e9/115200=8681ns
则传输一个bit需要的时钟周期为8681/20=434个时钟周期。
数据位的传输是串行从低位到高位传输,接收到的数据暂时存储在寄存器中,待接收完1字节的数据,通过串并转换保存接收到的数据。发送时通过tx信号线按照设置好的比特率将数据发送出去,数据发送仍要按照数据帧发送,先发送起始位,再从低位到高位发送数据。
奇偶校验位:数据位加上校验位后,使得“1”的位数为偶数(偶校验)或者奇数(奇校验)。(一般都是无奇偶校验位的。)
停止位:数据位传输完成后都会发送停止位,标志一个传输已经完成。(默认1位,可选择1.5位、2位。)
二、Verilog 代码
1.发送模块
module uart_tx(
input clk, //系统时钟
input rst_n, //系统复位信号
input [2:0]baud_set, //波特率选择信号
input [7:0]data_byte, //并行数据
input send_en, //发送使能信号
output reg rs_Tx, //发送串行数据
output reg tx_state, //uart正在发送信号
output reg tx_done //发送完成信号
);
reg [15:0]baud_temp; //波特率
reg [15:0]baud_cnt; //波特率分频器
reg baud_clk; //波特率时钟
reg [3:0]baud_clk_cnt; //波特率时钟计数器
//波特率选择
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
baud_temp <= 3'd0;
else begin
case(baud_set)
3'd0:baud_temp <= 16'd5207; //波特率9600bps
3'd1:baud_temp <= 16'd2603; //波特率19200bps
3'd2:baud_temp <= 16'd1301; //波特率38400bps
3'd3:baud_temp <= 16'd867; //波特率57600bps
3'd4:baud_temp <= 16'd433; //波特率115200bps
default: baud_temp <= 16'd5207;
endcase
end
end
//波特率分频计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
baud_cnt <= 16'd0;
else if(tx_state)begin //开始发送后波特率分频计数器开始工作
if(baud_cnt == baud_temp)
baud_cnt <= 16'd0;
else
baud_cnt <= baud_cnt + 1'b1;
end
else
baud_cnt <= 16'd0;
end
//生成波特率时钟
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
baud_clk <= 1'b0;
else if(baud_cnt == 1'b1)
baud_clk <= 1'b1;
else
baud_clk <= 1'b0;
end
//对波特率时钟进行计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
baud_clk_cnt <= 4'd0;
else if(baud_clk_cnt == 4'd11)
baud_clk_cnt <= 4'd0;
else if(baud_clk)
baud_clk_cnt <= baud_clk_cnt + 1'b1;
else
baud_clk_cnt <= baud_clk_cnt;
end
//发送完成标志
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_done <= 1'b0;
else if(baud_clk_cnt == 4'd11) //波特率计数器记到4'd11时,发送完成
tx_done <= 1'd1;
else
tx_done <= 1'd0;
end
//正在发送标志
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_state <= 1'b0;
else if(send_en) //发送使能拉高时,发送开始
tx_state <= 1'b1;
else if(baud_clk_cnt == 4'd11)
tx_state <= 1'd0;
else
tx_state <= tx_state;
end
//数据寄存
reg [7:0]data_byte_r;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
data_byte_r <= 8'd0;
else if(send_en)
data_byte_r <= data_byte;
else
data_byte_r <= data_byte_r;
end
//并行数据转变成串行发送
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rs_Tx <= 1'b0;
else begin
case(baud_clk_cnt)
0:rs_Tx <= 1'b1;
1:rs_Tx <= 1'b0;
2:rs_Tx <= data_byte_r[0];
3:rs_Tx <= data_byte_r[1];
4:rs_Tx <= data_byte_r[2];
5:rs_Tx <= data_byte_r[3];
6:rs_Tx <= data_byte_r[4];
7:rs_Tx <= data_byte_r[5];
8:rs_Tx <= data_byte_r[6];
9:rs_Tx <= data_byte_r[7];
10:rs_Tx <= 1'b1;
endcase
end
end
endmodule
`timescale 1ns/1ps
`define clk_period 20
module uart_tx_tb();
reg clk;
reg rst_n;
reg [2:0]baud_set;
reg [7:0]data_byte;
reg send_en;
wire rs_Tx;
wire tx_state;
wire tx_done;
uart_tx u1(
.clk(clk),
.rst_n(rst_n),
.baud_set(baud_set),
.data_byte(data_byte),
.send_en(send_en),
.rs_Tx(rs_Tx),
.tx_state(tx_state),
.tx_done(tx_done)
);
initial clk = 0;
always #(`clk_period/2)clk = ~clk;
initial begin
rst_n = 1'b0;
data_byte = 8'd0;
send_en = 1'b0;
baud_set = 3'd4;
#(`clk_period*20+1)
rst_n = 1'b1;
#(`clk_period*50);
data_byte = 8'hae;
send_en = 1'b1;
#`clk_period;
send_en = 0;
@(posedge tx_done)
#(`clk_period*5000);
data_byte = 8'hbc;
send_en = 1'b1;
#`clk_period;
send_en = 1'b0;
@(posedge tx_done)
#(`clk_period*5000);
$stop;
end
endmodule
仿真图
2.接收模块
module uart_rx(
input clk, //系统时钟
input rst_n, //系统复位信号
input rs_rx, //输入接收到的串行数据
input reg[2:0]baud_set, //波特率选择信号
output reg[7:0]r_data_byte, //输出并行数据
output reg rx_done //接收完成信号
);
reg rs_rx_r1,rs_rx_r2; //同步寄存器
reg rs_rx_temp1,rs_rx_temp2; //数据寄存器
wire nedege; //判断起始信号
//此时输入信号相对与系统时钟是异步信号,需要对其进行同步处理
//同步寄存器,消除亚稳态
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rs_rx_r1 <= 1'b0;
rs_rx_r2 <= 1'b0;
end
else begin
rs_rx_r1 <= rs_rx;
rs_rx_r2 <= rs_rx_r1;
end
end
//数据寄存
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rs_rx_temp1 <= 1'b0;
rs_rx_temp2 <= 1'b0;
end
else begin
rs_rx_temp1 <= rs_rx_r2;
rs_rx_temp2 <= rs_rx_temp1;
end
end
assign nedege = (!rs_rx_temp1) && rs_rx_temp2; //若为1,则输入了起始位
/*
实际传输中,会有许多干扰,只采样一次的数据是很不可靠的。这里将每个数据平均分为16段,采样中间6段较为平稳的数据,进行累加,
1-3'b001,2-3'b010,3-3'b011,4-3'b100,5-3'b101,6-3'b110.
可见当采样数据有一半的状态为1时,最高位都为1,故以最高位来判断此时传输的数据
*/
reg [15:0]baud_temp; //波特率
reg [15:0]baud_cnt; //波特率分频计数器
reg baud_clk; //波特率时钟
reg [7:0]baud_clk_cnt; //波特率时钟计数器
//波特率选择
//相比较发送模式的采样频率,接收模式的采样频率是其的16倍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
baud_temp <= 16'd324;
else begin
case(baud_set)
0:baud_temp <= 16'd324;
1:baud_temp <= 16'd162;
2:baud_temp <= 16'd80;
3:baud_temp <= 16'd53;
4:baud_temp <= 16'd26;
default : baud_temp <= 16'd324;
endcase
end
end
reg rx_state; //正在传输信号
//正在传输数据时baud_cnt开始计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
baud_cnt <= 16'd0;
else if(rx_state)begin
if(baud_cnt == baud_temp)
baud_cnt <= 16'd0;
else
baud_cnt <= baud_cnt + 1'b1;
end
else
baud_cnt <= 16'd0;
end
//
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
baud_clk <= 1'b0;
else if(baud_cnt == 16'd1)
baud_clk <= 1'b1;
else
baud_clk <= 1'b0;
end
reg [2:0]data_byte_r [7:0];
reg [2:0]START_BIT,STOP_BIT;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
baud_clk_cnt <= 8'd0;
else if(baud_clk_cnt == 8'd159 || ((baud_clk_cnt == 8'd12) && (START_BIT > 2)))//baud_clk_cnt计满时、或者是起始信号不为1时清零;
baud_clk_cnt <= 8'd0;
else if(baud_clk)
baud_clk_cnt <= baud_clk_cnt + 1'b1;
else
baud_clk_cnt <= baud_clk_cnt;
end
//传输完成信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rx_done <= 1'b0;
else if(baud_clk_cnt == 8'd159)
rx_done <= 1'b1;
else
rx_done <= 1'b0;
end
//正在传输信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rx_state <= 1'b0;
else if(nedege)
rx_state <= 1'b1;
else if(rx_done || (baud_clk_cnt == 8'd12 &&(START_BIT>2)))
rx_state <= 1'b0;
else
rx_state <= rx_state;
end
//计数完成时,串行数据转成并行数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
r_data_byte <= 8'b0;
else if(baud_clk_cnt == 8'd159)begin
r_data_byte[0] <= data_byte_r[0][2];
r_data_byte[1] <= data_byte_r[1][2];
r_data_byte[2] <= data_byte_r[2][2];
r_data_byte[3] <= data_byte_r[3][2];
r_data_byte[4] <= data_byte_r[4][2];
r_data_byte[5] <= data_byte_r[5][2];
r_data_byte[6] <= data_byte_r[6][2];
r_data_byte[7] <= data_byte_r[7][2];
end
else begin
r_data_byte[0] <= data_byte_r[0];
r_data_byte[1] <= data_byte_r[1];
r_data_byte[2] <= data_byte_r[2];
r_data_byte[3] <= data_byte_r[3];
r_data_byte[4] <= data_byte_r[4];
r_data_byte[5] <= data_byte_r[5];
r_data_byte[6] <= data_byte_r[6];
r_data_byte[7] <= data_byte_r[7];
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
START_BIT <= 3'd0;
data_byte_r[0] <= 3'd0;
data_byte_r[1] <= 3'd0;
data_byte_r[2] <= 3'd0;
data_byte_r[3] <= 3'd0;
data_byte_r[4] <= 3'd0;
data_byte_r[5] <= 3'd0;
data_byte_r[6] <= 3'd0;
data_byte_r[7] <= 3'd0;
end
else if(baud_clk)begin
case(baud_clk_cnt)
0:begin
START_BIT <= 3'd0;
data_byte_r[0] <= 3'd0;
data_byte_r[1] <= 3'd0;
data_byte_r[2] <= 3'd0;
data_byte_r[3] <= 3'd0;
data_byte_r[4] <= 3'd0;
data_byte_r[5] <= 3'd0;
data_byte_r[6] <= 3'd0;
data_byte_r[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
6,7,8,9,10,11:START_BIT <= START_BIT + rs_rx_r2;
22,23,24,25,26,27:data_byte_r[0] <= data_byte_r[0] + rs_rx_r2;
38,39,40,41,42,43:data_byte_r[1] <= data_byte_r[1] + rs_rx_r2;
54,55,56,57,58,59:data_byte_r[2] <= data_byte_r[2] + rs_rx_r2;
70,71,72,73,74,75:data_byte_r[3] <= data_byte_r[3] + rs_rx_r2;
86,87,88,89,90,91:data_byte_r[4] <= data_byte_r[4] + rs_rx_r2;
102,103,104,105,106,107:data_byte_r[5] <= data_byte_r[5] + rs_rx_r2;
118,119,120,121,122,123:data_byte_r[6] <= data_byte_r[6] + rs_rx_r2;
134,135,136,137,138,139:data_byte_r[7] <= data_byte_r[7] + rs_rx_r2;
150,151,152,153,154,155:STOP_BIT <= STOP_BIT + rs_rx_r2;
default : begin
START_BIT <= START_BIT;
data_byte_r[0] <= data_byte_r[0];
data_byte_r[1] <= data_byte_r[1];
data_byte_r[2] <= data_byte_r[2];
data_byte_r[3] <= data_byte_r[3];
data_byte_r[4] <= data_byte_r[4];
data_byte_r[5] <= data_byte_r[5];
data_byte_r[6] <= data_byte_r[6];
data_byte_r[7] <= data_byte_r[7];
STOP_BIT <= STOP_BIT;
end
endcase
end
end
endmodule
`timescale 1ns/1ps
`define clk_period 20
module uart_rx_tb();
reg clk;
reg rst_n;
reg send_en;
reg [2:0]baud_set;
reg rs_rx;
wire rs_Tx;
wire tx_state;
wire tx_done;
wire rx_done;
reg [7:0]data_byte;
wire [7:0]r_data_byte;
uart_tx u1(
.clk(clk),
.rst_n(rst_n),
.baud_set(baud_set),
.data_byte(data_byte),
.send_en(send_en),
.rs_Tx(rs_Tx),
.tx_state(tx_state),
.tx_done(tx_done)
);
uart_rx u2(
.clk(clk),
.rst_n(rst_n),
.rs_rx(rs_Tx),
.baud_set(baud_set),
.r_data_byte(r_data_byte),
.rx_done(rx_done)
);
initial clk = 1;
always #(`clk_period/2)clk = ~clk;
initial begin
rst_n = 1'b0;
data_byte <= 8'd0;
send_en = 1'b0;
baud_set = 4'd4;
#(`clk_period*20+1);
rst_n = 1'b1;
#(`clk_period*500);
data_byte = 8'haa;
send_en = 1;
#(`clk_period);
send_en = 0;
@(posedge tx_done)
#(`clk_period*500);
data_byte = 8'h55;
send_en = 1;
#(`clk_period);
send_en = 0;
@(posedge tx_done)
#(`clk_period*5000);
$stop;
end
endmodule
仿真图