文章目录
一、UART基础知识
1.串口通信重要参数
UART串口的重要参数包括波特率、停止位、数据位、奇偶校验位,这些参数通讯双方需保持一致。
波特率:每秒通信数据的比特个数,单位bit/s,一般常见的波特率为2400、4800、9600、19200、38400、57600、115200、256000等,串口通讯时要求通信双方波特率一致。
停止位:在每个字节的数据发送完成之后,发送停止位,表示本次数据传输完成,一般停止位选择:1位(默认)、1.5位 、 2 位,停止位为逻辑高电平。
数据位:数据位紧跟在起始位之后,是通信中发送的有效信息。一般数据位可以为5位、7位、8位(默认)。串口通信先发送数据低位,后发送高位。
奇偶校验位:校验位用于验证数据的正确性,一般不对数据进行校验,即校验位选择None。
除此之外,串口通讯还应具有1位起始位,起始位为逻辑低电平,标志传输一个字符的开始,接收方可用起始位使自己的接收时钟与发送方的数据同步。
2.串口发送波特率分频计数值计算
波特率是指每秒通信数据的比特个数,则每1/bps秒发送1bit数据,则分频计数时间为
1
∗
1
0
9
/
b
p
s
1*10^9 /bps
1∗109/bps ns,系统时钟为50Mhz,得出分频计数值计算公式:
波特率分频计数值
=
1
∗
1
0
9
/
b
p
s
/
20
−
1
波特率分频计数值=1*10^9/bps/20-1
波特率分频计数值=1∗109/bps/20−1
二、UART串口发送模块设计
1.模块端口说明
端口名称 | 方向 | 说明 |
---|---|---|
Clk | input | 系统时钟 |
Rst_n | input | 系统复位 |
send_en | input | 串口发送使能信号 |
data[7:0] | input | 8位并行待发送数据 |
baud_set[2:0] | input | 波特率设置 |
tx | output | 串口发送端口 |
tx_done | output | 串口发送完成标志 |
uart_state | output | 串口发送状态标志 |
模块接收到send_en使能信号时,开始发送数据,tx_state置1。baud_set是数值为0-7的3位二进制数,分别对应波特率2400、4800、9600、19200、38400、57600、115200、256000;当串口发送完成时,tx_done置1,tx_state置0。
2.波特率设置表格
波特率分频计数值
=
1
∗
1
0
9
/
b
p
s
/
20
−
1
波特率分频计数值=1*10^9/bps/20-1
波特率分频计数值=1∗109/bps/20−1
根据波特率分频计数值计算公式计算得出如下表格:
baud_set | 波特率 | 波特率分频计数值 |
---|---|---|
0 | 2400 | 20832 |
1 | 4800 | 10415 |
2 | 9600 | 5207 |
3 | 19200 | 2603 |
4 | 38400 | 1301 |
5 | 57600 | 867 |
6 | 115200 | 433 |
7 | 384000 | 194 |
3.代码实现
代码中用了7个always块,分别实现串口发送状态标志uart_state赋值、数据缓存、波特率分频计数最大值cnt_bpsmax赋值、波特率时钟分频计数器、波特率时钟计数器、数据发送、串口发送完成标志tx_done赋值。
(1)串口发送状态标志uart_state赋值
通过对uart_state赋值,可实现将脉冲的send_en信号转化为uart_state的持续信号。当捕捉到send_en为高电平时,uart_state赋值为1;当tx_done为高电平时,串口发送完成,uart_state赋值为0。
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(tx_done)
uart_state <= 1'b0;
(2)数据缓存
接收到send_en发送信号时,数据开始发送,为避免在发送过程中由于干扰导致输入的数据变化,在send_en为高电平时,将输入数据data缓存到tx_data中。
reg [7:0] tx_data; //数据缓存,防止数据丢失
always@(posedge Clk or negedge Rst_n)//数据缓存
if(!Rst_n)
tx_data <= 8'd0;
else if(send_en)
tx_data <= data;
else
tx_data <= tx_data;
(3)波特率分频计数最大值cnt_bpsmax赋值
根据上面计算出的波特率设置表格对cnt_bpsmax进行赋值。复位时,cnt_bpsmax为5207,表示默认波特率为9600。
always@(posedge Clk or negedge Rst_n)//波特率选择
if(!Rst_n)
cnt_bpsmax <= 16'd5207;//默认波特率9600
else
case(baud_set)
0:cnt_bpsmax <= 16'd20832; //波特率2400
1:cnt_bpsmax <= 16'd10415; //波特率4800
2:cnt_bpsmax <= 16'd5207; //波特率9600
3:cnt_bpsmax <= 16'd2603; //波特率19200
4:cnt_bpsmax <= 16'd1301; //波特率38400
5:cnt_bpsmax <= 16'd867; //波特率57600
6:cnt_bpsmax <= 16'd433; //波特率115200
7:cnt_bpsmax <= 16'd194; //波特率256000
default:cnt_bpsmax <= 16'd5207;
endcase
(4)波特率时钟分频计数器
得到波特率分频计数最大值后,编写波特率时钟分频计数器。
always@(posedge Clk or negedge Rst_n)//分频计数
if(!Rst_n)
cnt <= 16'd0;
else if(uart_state) begin
if(cnt == cnt_bpsmax)
cnt <= 16'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 16'b0;
(5)波特率时钟计数器
当串口处于发送状态(uart_state=1)时,对波特率时钟bps_cnt进行计数。
当cnt = cnt_bpsmax时对bps_cnt计数,需等待一个波特率时钟才能够开始发送数据,故将cnt == 1设置为bps_cnt的计数条件。
串口需发送起始位、8位data、停止位,共10位数据,当uart_state=0时,bps_cnt为0,应避免在bps_cnt=0时发送起始位,则bps_cnt=1时发送起始位,bps_cnt=10时发送停止位,停止位应发送一个波特率时钟的时长,停止位发送完毕时bps_cnt=11,此时可对bps_cnt清零,对tx_done置位。
always@(posedge Clk or negedge Rst_n)//波特率时钟计数
if(!Rst_n)
bps_cnt <= 4'd0;
else if(uart_state) begin
if(bps_cnt == 4'd11)
bps_cnt <= 4'd0;
else if(cnt == 16'b1)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
else
bps_cnt <= 4'd0;
(6)数据发送
串口处于空闲状态时,tx为高电平,故复位和bps_cnt=0时tx为高电平。bps_cnt=1时发送起始位,bps_cnt为2-9时,发送缓存后的tx_data,先发送低位,后发送高位,bps_cnt=10时发送停止位。
always@(posedge Clk or negedge Rst_n)//数据发送
if(!Rst_n)
tx <= 1'b1;
else
case(bps_cnt)
0:tx <= 1'b1;
1:tx <= START_Bit;
2:tx <= tx_data[0];
3:tx <= tx_data[1];
4:tx <= tx_data[2];
5:tx <= tx_data[3];
6:tx <= tx_data[4];
7:tx <= tx_data[5];
8:tx <= tx_data[6];
9:tx <= tx_data[7];
10:tx <= STOP_Bit;
default:tx <= 1'b1;
endcase
(7)串口发送完成标志tx_done赋值
停止位发送完毕时bps_cnt=11,对tx_done置位。
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
tx_done <= 1'b0;
else if(bps_cnt == 4'd11)
tx_done <= 1'b1;
else
tx_done <= 1'b0;
模块源代码
//本模块用于实现串口数据发送,系统频率为50Mhz,时钟周期为20ns
module uart_data_tx(
input Clk, //系统时钟
input Rst_n, //系统复位
input send_en, //串口发送使能信号
input [7:0] data, //8位并行待发送数据
input [2:0] baud_set, //波特率设置
output reg tx, //串口发送端口
output reg tx_done, //串口发送完成标志
output reg uart_state //串口发送状态标志
);
reg [7:0] tx_data; //数据缓存,防止数据丢失
reg [15:0] cnt; //分频计数器
reg [15:0] cnt_bpsmax; //分频计数最大值
reg [3:0] bps_cnt; //波特率时钟计数器
localparam
START_Bit = 1'b0, //起始位
STOP_Bit = 1'b1; //停止位
always@(posedge Clk or negedge Rst_n)//数据缓存
if(!Rst_n)
tx_data <= 8'd0;
else if(send_en)
tx_data <= data;
else
tx_data <= tx_data;
always@(posedge Clk or negedge Rst_n)//波特率选择
if(!Rst_n)
cnt_bpsmax <= 16'd5207;//默认波特率9600
else
case(baud_set)
0:cnt_bpsmax <= 16'd20832; //波特率2400
1:cnt_bpsmax <= 16'd10415; //波特率4800
2:cnt_bpsmax <= 16'd5207; //波特率9600
3:cnt_bpsmax <= 16'd2603; //波特率19200
4:cnt_bpsmax <= 16'd1301; //波特率38400
5:cnt_bpsmax <= 16'd867; //波特率57600
6:cnt_bpsmax <= 16'd433; //波特率115200
7:cnt_bpsmax <= 16'd194; //波特率256000
default:cnt_bpsmax <= 16'd5207;
endcase
always@(posedge Clk or negedge Rst_n)//分频计数
if(!Rst_n)
cnt <= 16'd0;
else if(uart_state) begin
if(cnt == cnt_bpsmax)
cnt <= 16'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 16'b0;
always@(posedge Clk or negedge Rst_n)//波特率时钟计数
if(!Rst_n)
bps_cnt <= 4'd0;
else if(uart_state) begin
if(bps_cnt == 4'd11)
bps_cnt <= 4'd0;
else if(cnt == 16'b1)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
else
bps_cnt <= 4'd0;
always@(posedge Clk or negedge Rst_n)//数据发送
if(!Rst_n)
tx <= 1'b1;
else
case(bps_cnt)
0:tx <= 1'b1;
1:tx <= START_Bit;
2:tx <= tx_data[0];
3:tx <= tx_data[1];
4:tx <= tx_data[2];
5:tx <= tx_data[3];
6:tx <= tx_data[4];
7:tx <= tx_data[5];
8:tx <= tx_data[6];
9:tx <= tx_data[7];
10:tx <= STOP_Bit;
default:tx <= 1'b1;
endcase
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
tx_done <= 1'b0;
else if(bps_cnt == 4'd11)
tx_done <= 1'b1;
else
tx_done <= 1'b0;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(bps_cnt == 4'd11)
uart_state <= 1'b0;
endmodule
三、仿真
1.仿真代码
在代码中发送两次数据,分别发送8’hab和8’h23。
`timescale 1ns/1ns
module uart_data_tx_tb();
reg Clk,Rst_n,send_en;
reg [7:0]data;
reg [2:0]baud_set;
wire tx,tx_done,uart_state;
uart_data_tx uart_data_tx(
.Clk(Clk),
.Rst_n(Rst_n),
.send_en(send_en),
.data(data),
.baud_set(baud_set),
.tx(tx),
.tx_done(tx_done),
.uart_state(uart_state)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Rst_n = 1'b0;
send_en = 1'b0;
data = 8'h0;
baud_set = 3'd2;
#201;//20+1为了和系统时钟错开,便于观察结果
Rst_n = 1'b1;
#200;
data = 8'hab;
send_en = 1'b1;
#20;
send_en = 1'b0;
@(posedge tx_done);//等待发送完成
#50000;
data = 8'h23;
send_en = 1'b1;
#20;
send_en = 1'b0;
@(posedge tx_done);
#50000;
$stop;
end
endmodule
2.仿真结果及代码更正
波形整体上没有问题,实现了串口发送。但将波形放大后发现tx_done和uart_state不是同时变化的,当tx_done被拉高时,uart_state依然为高电平,如下图所示:
这是因为uart_state清零的条件是tx_done=1,当tx_done被拉高时,在下一个时钟周期才会对uart_state清零,因此应该将uart_state的清零条件和tx_done被拉高的条件保持一致,将代码更改如下:
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(bps_cnt == 4'd11)
uart_state <= 1'b0;
更改后再进行仿真,tx_done和uart_state同时变化。