FPGA学习之串口发送模块设计与验证
1.实验目的:
实现一个串口输出,通过上位机PC查看接收到的是否是串口发送的数据。
2.实验介绍:
学习UART通信原理及其硬件电路设计,使用FPGA实现UART通信中的数据发送部分设计,并使用ISSP工具来进行板级验证。
3.实验原理
(1)异步串行通信原理
串行通信是指利用一条传输线将数据一位位的顺序传送,每位数据占据固定时间长度。
异步通信以一个字符为传输单位,通信中两个字符之间的时间间隔多少是不固定的,但同一个字符中的两个相邻位之间的时间间隔是固定的。
即下图中10位数据之间时间间隔是固定的,这个固定时间称为波特率,指每秒钟可以通信的数据比特个数,典型的波特率有1200bps,4800bps,9600bps,19200bps,115200bps等,一般通信两端波特率要一致。
(2)系统框架图
对于整个串口发送模块,基于上述原理,理解如下输入输出接口:
1.首先作为发送模块,必然有一个输出发送数据端口Rx232_Tx;
2.对于一个字符发送完成,必须要告诉上位机字符发送结束,启动下一次发送,即发送结束信号标志Tx_Done,当发送完成,输出一个时钟周期高电平。
3.当串口模块在发送数据期间,需要存在一个输出标志串口状态位uart_state,即处于发送状态时为1,告诉上位机目前模块正忙。
4.输入肯定有时钟Clk和复位输入Rst_n。
5.在上图中波特率固然有常用的几个,但是如何去选择对应上位机的波特率,来实现不同上位机通信,因此这里需要一个波特率选择输入端口baud_set[3:0]。
6.我们要发送一个数据,必须要控制模块在需要数据时刻发送,不能无休止的一直发送,因此必须有发送控制信号即发送使能信号send_en
7.待发送的数据输入data_byte[7:0];
那这些输入输出端口之间存在怎样的逻辑电路来将数据发送出去呢?
数据输出格式
对于要输出1字节10位数据,其中数据发送格式已知,即起始位+8位数据位+停止位(校验位默认无),那么这里需要10选一多路器,这样我们可以来选择究竟将哪一位数据发送出去。
由于RS232是异步收发器,因此为了保证发送的数据在时钟到来时处于稳定,需要对输入数据进行寄存。
8位有效数据经过寄存器发送到10选一多路中,再由sel选择信号选择输出哪位数据,选择好数据后,传输到data_reg寄存器中。
那么上图中sel选择信号怎么产生呢?
波特率时钟沿计数输出10选一多路器控制
首先sel选择信号选择的是哪位数据输出?由下图可知,我们可以将BPS_CLK的时钟沿进行计数,当每来一个上升沿就让计数器bps_cnt自加1,然后根据计数结果来选择数据位数输出,例如计数器计到7时,选择信号就会选择发送BIT[5]数据。
而且这个计数器最大只能计数到11(一个字节发送完毕),计数到11时,就需要产生清零clr信号。
这个清零clr信号根据上述输出端口Tx_Done而来,当结束信号标志Tx_Done输出一个时钟周期高电平,认定一个字节传输完毕。
当计数器bps_cnt_q等于11,结果为真,连接到clr清零信号线上,来实现清零动作。
同时比较结果又经过寄存器传输到Tx_Done信号上,告诉上位机字符发送结束。
查看上图可得,bps_cnt的输入BPS_Clk信号其实波特率对应的时钟信号。
分频计数器输出波特率时钟
因为一个字符相邻位的时间间隔对应的波特率,而每一位的传输是基于bps_clk的上升沿来的,因此我们需要不同的bps_clk来对应不同波特率(1200bps,4800bps,9600bps,115200bps)。
首先第一步是产生不同的波特率,因为系统时钟是50Mhz,系统时钟周期为20ns,这里采用依然采用分频计数器来实现不同波特率时钟,下表是常见波特率对应的计数器关系:
上表可知所谓波特率的生成,无非就是利用一个定时器来定时,产生频率与对应波特率时钟频率。
例如当我们使用9600bps时,则需要产生一个频率为9600Hz的时钟信号。那么如何去产生这个信号呢?
在这里,我们先将9600Hz的时钟信号周期计算出来,1秒钟为1000_000_000ns,因此9600bps波特率时钟的周期为1000_000_000/9600≈104166.6,即9600Hz时钟信号的一个周期为104166.6ns,每当定时时间当来,就产生一个系统时钟周期长度的高脉冲信号即可,由于系统时钟周期为20ns(50MHz),因此只需计数104166.6/20个系统时钟,即可获取到101166.6的定时。
分频计数器计数使能控制
对应一个分频计数器而言,在系统时钟运行过程中,什么时候开始计数即使能计数信号?计数什么时候开始工作呢?
本次设计采用两个二选一多路器来产生使能计数信号,首先这里有一个优先级的问题,因为当send_en发送使能信号为高电平时,那么我们的分频计数器也就要开始计数了。
然而send_en信号只是维持一个时钟周期的高电平,其余时间为低电平,这时就需要一个二选一多路器来实现当send_en信号为高电平时,输出“1”表示计数开始。
如何实现在send_en为低电平时,分频计数器还在计数呢?计数结束的标志是什么呢?
由上文可知在数据传输结束后会产生一个Tx_Done标志信号,这个信号是计数器bps_cnt等于11时产生的,在与11的比较结果会连接到计数器bps_cnt清零clr信号上,那么也可以将该信号作为另一个二选一多路器的选择端上。
因此send_en信号为低电平时会选择第二个二选一多路器MUX2_2作为输出,MUX2_2的选择端与clr同源,因此当比较结果为假时,则MUX2_2的输出即是原来的输入UART_state,即保持UART_state状态不变,依然表示分频计数器还在计数。
当比较结果为真时,即发送数据结束,MUX2_2会输出“0”,这时UART_state会输出低电平,使能计数en_cnt会控制分频计数器停止计数。
不同波特率查找计数值输出
如何产生选择不同波特率时钟信号对应的计数值bps_DR[15:0]呢?
这里利用查找表LUT的方式来实现baud_set信号选择不同的波特率,输出不同的系统时钟计数值。
系统框架图
因此可以得出串口发送模块基本逻辑的系统框架图:
4.代码实现
根据上文描述系统框架图来分别实现相应部分功能:
数据输出格式
波特率时钟沿计数输出10选一多路器控制
分频计数器输出波特率时钟
分频计数器计数使能控制
不同波特率查找计数值输出
总体代码:
//--------------------------------------------------------------------------------------------
// Component name : uart_Tx
// Author : 硬件嘟嘟嘟
// time : 2020.04.21
// Description : 串口发送模块
// src : FPGA系统设计与验证实战指南_V1.2
//--------------------------------------------------------------------------------------------
module uart_tx(Clk,Rst_n,send_en,baud_set,data_byte,Rx232_Tx,Tx_Done,uart_state);
input Clk,Rst_n;
input send_en; //发送使能信号输入
input [2:0] baud_set; //波特率选择输入
input [7:0] data_byte; //待发送数据
//
output Rx232_Tx; //发送数据输出端口
output Tx_Done; //一字节发送结束标志位
output uart_state; //发送数据状态
//数据输入寄存,输出格式及输出寄存模块
//8位有效数据寄存
reg [7:0] r_data_byte;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
r_data_byte <= 8'b0;
else if(send_en)
r_data_byte <= data_byte;
else
r_data_byte <= r_data_byte;
//10选1多路器模块
reg [3:0] bps_cnt_q;
reg Rx232_Tx;
localparam START_BIT = 1'b0,
STOP_BIT = 1'b1;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
Rx232_Tx = 1'b1;
else begin
case(bps_cnt_q)
0 : Rx232_Tx <= 1'b1;
1 : Rx232_Tx <= START_BIT;
2 : Rx232_Tx <= r_data_byte[0];
3 : Rx232_Tx <= r_data_byte[1];
4 : Rx232_Tx <= r_data_byte[2];
5 : Rx232_Tx <= r_data_byte[3];
6 : Rx232_Tx <= r_data_byte[4];
7 : Rx232_Tx <= r_data_byte[5];
8 : Rx232_Tx <= r_data_byte[6];
9 : Rx232_Tx <= r_data_byte[7];
10 : Rx232_Tx <= STOP_BIT;
default : Rx232_Tx = 1'b1;
endcase
end
//波特率时钟沿计数输出10选一多路器控制
//10选1多路器控制信号产生
reg bps_clk;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
bps_cnt_q <= 4'd0;
else if(bps_clk)
bps_cnt_q <= bps_cnt_q + 1'b1;
else if(bps_cnt_q == 4'd11)
bps_cnt_q <= 4'd0;
else
bps_cnt_q <= bps_cnt_q;
//发送数据完成标志位
reg Tx_Done;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
Tx_Done <= 1'b0;
else if(bps_cnt_q == 4'd11)
Tx_Done <= 1'b0;
else
Tx_Done <= 1'b1;
//分频计数器输出波特率时钟
//分频计数
reg [15:0] bps_DR;
reg [15:0] div_cnt;
reg uart_state ;//en_cnt信号由uart_cnt寄存器寄存
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
div_cnt <= 16'd0;
else if(uart_state)begin
if(div_cnt == bps_DR)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 16'd0;
//输出波特率时钟信号模块
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1)
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
//分频计数器计数使能控制
always@(posedge Clk ,negedge Rst_n)
if(!Rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(bps_cnt_q == 4'd11)
uart_state <= 1'b0;
else
uart_state <= uart_state;
//不同波特率查找计数值输出
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
bps_DR <= 16'd5207;
else begin
case (baud_set)
0:bps_DR <= 16'd10416;
1:bps_DR <= 16'd5207;
2:bps_DR <= 16'd2603;
3:bps_DR <= 16'd1301;
4:bps_DR <= 16'd433;
default : bps_DR <= 16'd5207;
endcase
end
endmodule