UART——串口发送模块
导读
- 在当今的电子系统中,经常需要板内、板间或者下位机与上位机之间进行数据的发送与接收,这就需要双方共同遵循一定的通信协议来保证数据传输的正确性。
- 常见的协议有 UART(通用异步收发传输器)、IIC(双向两线总线)、SPI(串行外围总线)、USB2.0/3.0(通用串行总线)以及 Ethernet(以太网)等。
- 在这些协议当中,最为基础的就是 UART,因其电路结构简单、成本较低,所以在注重性价比的情况下,使用非常广泛。
顶层模块
具体代码
`timescale 1ns / 1ps
//顶层模块,发送数据包
module Send_data(
clk,
reset_n,
Data,
baud_set,
uart_tx
);
input clk;
input reset_n;
input [8-1:0] Data;
input [3-1:0] baud_set;
output uart_tx;
//这里的Data_next并非发挥寄存器的作用
//主要是为了标志发送数据下一位
reg [8-1:0] Data_next;
wire Tx_Done;
reg Send_go;
uart_byte_tx uart_byte_tx(
.clk(clk),
.reset_n(reset_n),
.Data(Data_next),
.baud_set(baud_set),
.Send_go(Send_go),
.Tx_Done(Tx_Done),
.uart_tx(uart_tx)
);
//计数1us
reg [19-1:0] counter;
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
counter <= 1'b0;
else if(counter==500000-1)
counter <= 1'b0;
else
counter <= counter + 1;
end
//每隔1us,发送一次数据
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
Send_go <= 1'b0;
else if(counter==500000-1)
Send_go <= 1'b1;
else if(Tx_Done==1)
Send_go <= 1'b0;
end
//当接收当发送成功标识符时,数据加1,从而开始下一次发送
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
Data_next <= Data;
else if(Tx_Done)
Data_next <= Data_next + 1'b1;
else
Data_next <= Data_next;
end
endmodule
代码解析
- 每隔10ms进行一次数据发送,所以使用counter进行10ms的计数,在设计数值时,需要关注
timescale
中的时间单位和精度 - 通过输入
Send_go
来控制Send_en
,一般而言,go代表着一个单脉冲,en代表着使能电平,两者区别较大 - 每次接收到一个Tx_Done信号后,就让Data加一
底层模块1
具体代码
`timescale 1ns/1ps
module uart_byte_tx(
clk,
reset_n,
Data,
baud_set,
Send_go,
Tx_Done,
uart_tx
);
input clk;
input reset_n;
input Send_go;
input [3-1:0] baud_set;
input [8-1:0] Data;
//发送成功(结束)标志符
//重要,在作为模块调用时,可用于激励外部信号
output reg Tx_Done;
//主要输出信号,将Data的依次通过uart_tx传输,并转串?
output reg uart_tx;
//波特率设置
//波特率单位,每秒传输的bit数量,()bit/s
//baud_DR计算为每一个bit所需要的时钟周期,这里的单位是ns
//这里时钟周期以50MHz为例,也就是一个周期有20ns,因此是/20
reg [12-1:0] baud_DR;
always@(*)begin
if(reset_n == 0)
baud_DR <= 1_000_000_000/9600/20;
else case (baud_set)
0: baud_DR <= 1_000_000_000/9600/20;
1: baud_DR <= 1_000_000_000/19200/20;
2: baud_DR <= 1_000_000_000/38400/20;
3: baud_DR <= 1_000_000_000/57600/20;
4: baud_DR <= 1_000_000_000/115200/20;
default: baud_DR <= 1_000_000_000/9600/20;
endcase
end
reg [20-1:0] div_cnt;
reg [4-1:0] bps_cnt;
wire bps_clk;
//数据传输标识符,在分频计数为1时拉高
//标志开始新的bit传输
assign bps_clk = (div_cnt == 1);
reg Send_en;
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
Send_en <= 1'b1;
//当出现Send_go信号时,使能激发
else if(Send_go==1)
Send_en <= 1'b1;
//当发送成功(结束)后,使能归0
else if(Tx_Done==1)
Send_en <= 1'b0;
end
//r_Data = register for data
//用于数据保持,避免数据变更时出现意外
//当出现Send_go信号时,将Data存储在寄存器中
//避免在发送过程中,Data发生变化,导致发送出去的数据失真
//一般在信号前端或者后端加上“r”表示寄存器
reg [8-1:0] r_Data;
always@(posedge clk or negedge reset_n)begin
if(Send_go==1)
r_Data <= Data;
else
r_Data <= r_Data;
end
//分频计数 div_cnt = divider counter
//分频是以系统时钟为基础的,系统时钟有timescale和testbench设置
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
div_cnt <= 1'b0;
//Send_en使能控制(总体发送模块控制)
else if(Send_en)begin
if (div_cnt == baud_DR - 1 )
div_cnt <= 1'b0;
else
div_cnt <= div_cnt + 1'b1;
end
//只有使能过后,才会开启计数,否则一直置0
else
div_cnt <= 1'b0;
end
//数据传输计数
//bps_cnt标记着数据传输的数量
//每次bps_clk出现时,bps_cnt才会加1或者清零
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
bps_cnt <= 1'b0;
//Send_en使能控制(总体发送模块控制)
else if(Send_en) begin
if (bps_clk)begin
//1位起始+8位数据+1位结束
if(bps_cnt == 11)
bps_cnt <= 1'b0;
else
bps_cnt <= bps_cnt + 1'b1;
end
end
//只有使能过后,才会开启计数,否则一直置0
else
bps_cnt <= 1'b0;
end
//串口协议规定,传输的数据位宽要求为6、7、8位数据
//不论位宽大小,每一组数据传输开始都要求有一个低电平的起始位
//结束要求有一个高电平的结束位
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
uart_tx <= 1'b1;
else if (Send_en) begin
case(bps_cnt)//总计10位,从1~10,第11位用于收尾
1: uart_tx <= 1'b0;//起始位
2: uart_tx <= r_Data[0];
3: uart_tx <= r_Data[1];
4: uart_tx <= r_Data[2];
5: uart_tx <= r_Data[3];
6: uart_tx <= r_Data[4];
7: uart_tx <= r_Data[5];
8: uart_tx <= r_Data[6];
9: uart_tx <= r_Data[7];
10: uart_tx <= 1'b1;//结束位
//为了保证结束位持续1个周期,所以要计数到11
11: uart_tx <= 1'b1;
//其他时候都保持高电平,从而使得开始数据发送时
//在起始位有电平变化,观察明显
default: uart_tx <= 1'b1;
endcase
end
end
//发送成功(结束)标志符
always@(posedge clk or negedge reset_n)begin
if(reset_n==0)
Tx_Done <= 1'b0;
//当bps_cnt计数到结束位,
//并且再保持计数了一个周期的时间(此时bps_clk=1)
//视作发送成功
else if(bps_clk==1 && bps_cnt==10)
Tx_Done <= 1'b1;
else
Tx_Done <= 1'b0;
end
endmodule
代码解析
baud_DR
用于配置不同的波特率,通过输入baud_set
可以选择不同配置,常见有9600、19200、38400、57600、115200等div_clk
分频计数,计数周期和波特率有关bps_clk
数据传输时钟,标志着新bit的传输bps_cnt
数据传输计数,对于传输起始位、数据位、结束位很关键r_Data
用于数据保持,避免数据变更时出现意外uart_tx
传输数据,在bps_cnt
计数为1时传输起始位,计数为2~9时传输数据位,计数为10时传输结束位,其他的都是为了保证uart_tx
维持高电平- 需要注意的是,在进行设计时必须要遵守UART协议,例如,协议规定传输数据位的大小只能位6,7,8,其余大小不能满足要求!
Tx_Done
发送成功标识符,当bps_clk
为1,且bps_cnt
计数到10时,发送即为成功,标志位拉高
测试平台
具体代码
`timescale 1ns/1ps
module uart_byte_tx_tb();
reg clk;
reg reset_n;
reg [3-1:0] baud_set;
reg [8-1:0] Data;
wire uart_tx;
Send_data Send_data(
.clk(clk),
.reset_n(reset_n),
.Data(Data),
.baud_set(baud_set),
.uart_tx(uart_tx)
);
initial begin
clk = 1'b1;
forever #10 clk = ~clk;
end
initial begin
reset_n = 0;
Data = 8'b1111_1100;
baud_set = 0;
#201
reset_n = 1;
#100
baud_set = 4;
#50_000_000
$stop;
end
endmodule