八.基于FPGA的串口(UART)发送
1.简介
- 串口通信模块设计的目的是用来发送数据的,因此需要一个数据输入端口。
- 串口通信,支持不同的波特率,所以需要一个波特兰设置端口。
- 串口通信的本质是将8位并行的数据通过一根信号线。在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据全部传出。
- 串口通信以1位的低电平标志串行传输的开始,待8位数据传输完成之后,再以1位的高电平标志传输结束。
2.代码
`timescale 1ns / 1ps
module uart_byte_tx(
sys_clk ,sys_rst , date , date_tx ,baud_set ,tx_done ,send_en
);
input sys_clk ;
input sys_rst ;
input [2:0]baud_set ;
input [7:0] date ;
output reg date_tx;
output reg tx_done ;
input send_en ;
// 1.波特率的设置
reg [12:0] bps_DR ;
always@(*) begin
case(baud_set)
0 : bps_DR <= 5208; // 1_000_000_000 / 9600 /20 ;
1 : bps_DR <=2604 ; // 1_000_000_000 / 19200/20 ;
2 : bps_DR <= 1302; //1_000_000_000 / 38600/20 ;
3 : bps_DR <= 868 ; //1_000_000_000 / 57600/20 ;
4 : bps_DR <= 434 ; // 1_000_000_000 / 115200/20 ;
default : bps_DR = 5208; // 1_000_000_000 / 9600/20 ;
endcase
end
//2 波特率计数时钟 div_cnt
reg [17:0] div_cnt ; //一位数据位发送时间计数器,记录发送数据的时间,用于更新下一段数据
always@(posedge sys_clk or negedge sys_rst )
if(!sys_rst)
div_cnt <= 0 ;
else if (send_en == 1 ) //如果有使能才可以进行自加。
begin
if (div_cnt >= bps_DR - 1 )
div_cnt <= 0 ;
else
div_cnt <= div_cnt + 1'd1 ;
end
else
div_cnt <= 0 ; //没有使能信号,使计数器归0
// 3.对波特率时钟 进行计数
wire bps_clk ;//每一位发送数据前后的标志位
assign bps_clk = (div_cnt == 1) ; //当每次计数器记到 1 的时候就让数据开始发送
reg [3:0] bps_cnt ;
always@(posedge sys_clk or negedge sys_rst )
if(!sys_rst)
bps_cnt <= 0 ;
else if (send_en) begin
if(bps_clk == 1 ) //当有发送的标志时,状态才自增一次。
begin
if (bps_cnt == 11) //当状态计数到第11个状态,状态归0。
bps_cnt <= 0 ;
else
bps_cnt<= bps_cnt + 1'd1 ; end
end
else
bps_cnt <= 0 ;
// 5.数据的发送环节
always@(posedge sys_clk or negedge sys_rst )
if(!sys_rst)
date_tx <= 1 ;
else
begin
case (bps_cnt) //开始发送数据,以数据状态来决定发送的是哪个数据。
// 0 : date_tx <= 0 ;
1 : date_tx <= 0; //发送起始位,为什么不是0状态时刻发送0,因为在复位或者空闲时状态为0,
//而此时需要·uart_tx为高电平,所以在0状态时刻不能为0。
2 : date_tx <= date[0]; //发送数据低位,数据从低位发送到高位,此下依次发送数据。
3 : date_tx <= date[1];
4 : date_tx <= date[2];
5 : date_tx <= date[3];
6 : date_tx <= date[4];
7 : date_tx <= date[5];
8 : date_tx <= date[6];
9 : date_tx <= date[7];
default date_tx <= 1 ; //数据位发送完成之后,需要发送停止位,我们可以发现,空闲和停止位都是高电平,所以用default来将这些状态综合。
//细心的可以发现,我们状态定义了11个,这儿9个就结束了,为什么需要定义11个呢?因为在最后一个停止位发送为第10个状态,而停止位需要保留一个数据周期,
// 所以在第10个状态发送停止位后,需要再延后一个状态,来保证停止位的时间,所以是11个状态。
endcase
end
// 6. 专门处理tx_done
always@(posedge sys_clk or negedge sys_rst )
if(!sys_rst)
tx_done <= 0 ;
else if ((bps_clk ) && ( 10 <= bps_cnt)) //当状态为第10或者11个状态时且发送一个数据的标志位置位时才使总发送标志位置1。
tx_done <= 1 ;
else
tx_done <= 0 ; //其余时刻都将状态标志位置0。
endmodule
2.测试代码
`timescale 1ns / 1ps
module uart_byte_tx_tb( );
reg sys_clk ;
reg sys_rst ;
reg [7:0] date ;
reg send_en ;
reg [2:0] baud_set ;
wire tx_done ;
wire date_tx;
uart_byte_tx uart_byte_tx(
.sys_clk (sys_clk ) ,
.sys_rst (sys_rst) ,
.date (date) ,
.send_en (send_en) ,
.baud_set (baud_set) ,
.tx_done (tx_done) ,
.date_tx (date_tx)
);
initial sys_clk = 1 ;
always#10 sys_clk = !sys_clk ;
initial
begin
sys_rst = 0 ;
date = 0 ;
send_en = 0 ;
baud_set = 4;
#201
sys_rst = 1 ;
#20
date = 8'b1001_1000 ;
send_en = 1 ;
#20 ;
@(posedge tx_done) // 在这里一直等待 Tx_Done =1;
send_en = 0;
#6000
date = 8'b1001_1111 ;
send_en = 1 ;
#20 ;
@(posedge tx_done) // 在这里一直等待 Tx_Done =1;
send_en = 0;
$stop;
end
endmodule
4.仿真结果