【FPGA学习笔记】--串口发送模块的设计与验证

所谓串口,顾名思义就是串行接口。串口的设计,实际上就是设计串口的通信协议,而常用的串口通信协议有RS-232、RS-422、RS-485。在这里,笔者以RS-232为例,设计实现一个串口发送模块。至于其他两种协议,感兴趣的同学可以自行学习一下,网上能查到很多资料。好了,废话不多说,我们直奔主题,下面直接给出串口通信的协议:
【FPGA学习笔记】--串口发送模块的设计与验证_第1张图片
可以看到,在空闲状态下,发送总线为高电平1,而当有数据需要发送时,发送总线首先拉低,产生一个起始位,紧接着,依次发送数据的第0位到第7位,当8位数据发送结束,如果需要进行数据校验,则产生校验位,若不需要,则发送总线直接拉高,产生停止位,8位数据发送结束。
整个过程很简单,我们只需要在特定的时间让总线产生特定的信号便可。好了,串口发送的时序就介绍到这里,下面说说具体怎么实现。
首先,我们要发送数据,就一定要考虑到数据发送的速度,总不能一会快一会慢对不对,当然也不能连你这个设计者都不知道发送完8位数据需要多少时间。说到速度问题,这里引入一个波特率的概念,常用的波特率有:9600、19200、38400、115200等。在此文中,笔者将使用9600的波特率,来设计一个串口发送模块。
9600的波特率,说白了就是9600Hz/S,是数据发送的速度。举个栗子,比如这里采用9600的波特率,那么发送一位数据需要的时间便是9600分之1秒,这么说大家明白了吗?简单理解为波特率就是发送数据的速度就可以了。那么这个时候有人问了,你说了半天波特率,我还是不知道波特率是什么怎么办?对于这个,我只能说非常抱歉,同学你还是去查查百度吧,笔者没有说清楚的,百度搜索一定能为你解答。
好了,波特率已经交代了,我们知道它实际上就是一个频率,一个发送数据的频率,也就是发送数据的速度。那么,如何来产生这个频率呢?要知道,这个频率可不是凭空就有的。
想必说到频率,一定有同学已经想到了,没错,就是分频计数而已。不就是要得到9600Hz的时钟频率么,一个简单的计数器就能搞定!
既然要用到计数器,那么这个计数器,究竟要计数多少次呢?这里,给出一个简单的计算方法。
由于笔者手中开发板的时钟频率是50MHz,这里就以50MHz的时钟频率为栗:
在这里插入图片描述
解释一下,就是用系统时钟的频率,除以我们需要的频率(这里我们需要9600Hz),得到的结果,就是我们分频计数器需要计数的次数,每计满5208次,产生一个高电平,就能最终产生9600Hz的频率。这里,由于系统时钟频率的单位是MHz,而9600波特率的单位是Hz,所以需要先统一单位,因为应该是50_000_000/9600。自己算过的同学肯定发现了,50_000_000/9600的结果分明是5208.333333…无数个3啊,你这么写行吗?对于这种能自己动手计算的同学,笔者首先恭喜你,在学习FPGA的路上,你已经超过了很多人。不要看这个简单,我们学习技术,就是要多动手自己做,才能发现问题,解决问题,在解决问题中成长,才能进步的更快…咳咳好像偏题了,好了我告诉你同学,小数点后面这些3,你尽管让他们见鬼去吧!不会有问题的!
好了,波特率我们也有了,现在,我们来具体分析一下串口发送模块,究竟该怎么实现。这里,笔者先上一张模块信号图如下:

【FPGA学习笔记】--串口发送模块的设计与验证_第2张图片
从上图我们可以看到,串口发送模块一共被定义了5个信号,其中4个输入,1个输出,下面,对这5个信号进行一一阐述。
Clk:我想不用多说,哪怕是小白也知道,这是系统时钟,自然是输入信号。
Rst_n:系统复位信号,自然也是输入。另外,n表示低电平有效。
Tx_trig:发送数据开始信号,只有当这个信号出现一个高电平,整个模块才开始工作。
Tx_data:需要发送的数据。
RS232_tx:模块唯一的输出信号,起始位、要发送的数据、停止位都包含在这里。通过仿真可以看到,该信号是发送模块完整时序。我们发送模块最终,也是为了产生这个信号。
以上,就是对串口发送模块5个信号的解释。下面,我们要做的就是分析一下该模块代码究竟如何去写。
显然,除了以上5个信号,在模块中,我们还需要一些寄存器,才能完整代码功能的实现,这里,笔者先贴出写好的代码,后面再做详细解释。具体代码如下:
module Uart_tx(
//----system signal------
Clk,
Rst_n,
//-----------------------
Tx_trig,
Tx_data,
Rs232_tx
);
input Clk,Rst_n;
input Tx_trig;
input [7:0] Tx_data;

output reg Rs232_tx;

reg [ 7:0] tx_data_reg;
reg tx_flag;
reg [12:0] baud_cnt;
reg bit_flag;
reg [ 3:0] bit_cnt;

//tx_data_reg

always @ (posedge Clk or negedge Rst_n)
if(!Rst_n)
tx_data_reg <= 8’d0;
else if(Tx_trig == 1’b1 && tx_flag == 1’b0)
tx_data_reg <= Tx_data;
else
tx_data_reg <= tx_data_reg;

//tx_flag

always @ (posedge Clk or negedge Rst_n)
if(!Rst_n)
tx_flag <= 1’b0;
else if(Tx_trig == 1’b1)
tx_flag <= 1’b1;
else if(bit_flag == 1’b1 && bit_cnt == 4’d8)
tx_flag <= 1’b0;

//baud_cnt

always @ (posedge Clk or negedge Rst_n)
if(!Rst_n)
baud_cnt <= 13’d0;
else if(baud_cnt == 13’d5207)
baud_cnt <= 13’d0;
else if(tx_flag == 1’b1)
baud_cnt <= baud_cnt + 1’d1;
else
baud_cnt <= 13’d0;

 //bit_flag

always @ (posedge Clk or negedge Rst_n)
if(!Rst_n)
bit_flag <= 1’b0;
else if(baud_cnt == 13’d5207)
bit_flag <= 1’b1;
else
bit_flag <= 1’b0;

//bit_cnt

always @ (posedge Clk or negedge Rst_n)
if(!Rst_n)
bit_cnt <= 4’d0;
else if(bit_cnt == 4’d8 && bit_flag == 1’b1)
bit_cnt <= 4’d0;
else if(bit_flag == 1’b1)
bit_cnt <= bit_cnt + 1’b1;
// else
// bit_cnt <= bit_cnt;

//Rs232_tx 

always @ (posedge Clk or negedge Rst_n)
if(!Rst_n)
Rs232_tx <= 1;
else if(tx_flag == 1)
case(bit_cnt)
0: Rs232_tx <= 1’b0;
1: Rs232_tx <= tx_data_reg[0];
2: Rs232_tx <= tx_data_reg[1];
3: Rs232_tx <= tx_data_reg[2];
4: Rs232_tx <= tx_data_reg[3];
5: Rs232_tx <= tx_data_reg[4];
6: Rs232_tx <= tx_data_reg[5];
7: Rs232_tx <= tx_data_reg[6];
8: Rs232_tx <= tx_data_reg[7];
default:Rs232_tx <= 1’b1;
endcase
else
Rs232_tx <= 1’b1;
endmodule
以上就是串口发送模块的代码,大家可以参考一下。下面,笔者对代码做一个简单的解释。
首先,我们看到以下几个寄存器:
在这里插入图片描述
tx_data_reg:数据寄存器,当需要发送的数据送到时,先将其存储在此寄存器中,再进行按位发送。至于为什么要先做一个存储,而不是直接发送,大家可以自己考虑一下。
tx_flag:处于发送状态的标志信号,当tx_flag为高电平,则表示总线上正在发送数据。
baud_cnt:波特率计数寄存器,前面我们说过,我们需要产生一个9600的波特率,此寄存器,就是为了存储波特率计数器计数的值。至于为什么是13位,大家可以把5208化成二进制,看看这个二进制数有多少位,就知道了。
bit_flag:发送完一位数据标志信号,每当一位数据发送完毕,该信号变为高电平。
bit_cnt:发送数据位数寄存器,当8位数据发送完毕,寄存器清零。很容易想到,该寄存器最大计数值为8,所以该寄存器的位宽为4。
几个主要的寄存器就介绍到这里,下面详细介绍代码。
【FPGA学习笔记】--串口发送模块的设计与验证_第3张图片
给发送数据寄存器赋值,根据分析不难想到,该寄存器只有当模块开始工作信号来临,且还未开始发送数据时,才会将需要发送的数据存储进来,其余状态,该寄存器内的值保持不变。
【FPGA学习笔记】--串口发送模块的设计与验证_第4张图片
给模块正在工作的信号赋值,当Tx_trig信号有效时,模块正在工作信号被拉高,表示整个模块正在进行数据发送;当bit_cnt计数到最大值,且bit_flag信号为高时,该信号拉低,表示模块未在工作。
【FPGA学习笔记】--串口发送模块的设计与验证_第5张图片
这部分代码就是我们9600波特率的产生了,要注意,默认是从0开始计数,所以我们计数器应该是计数到5207就清零,而不是5208。
【FPGA学习笔记】--串口发送模块的设计与验证_第6张图片
bit_flag寄存器,当波特率计数器计满时,一位数据也就发送完毕,因此此时bit_flag为高电平,其余为低电平。
【FPGA学习笔记】--串口发送模块的设计与验证_第7张图片
当bit_flag寄存器为高电平,bit_cnt便计数一次,当计数满8次(即已发送完8位数据),且最后一位数据发送完成信号有效时,该寄存器清零。
【FPGA学习笔记】--串口发送模块的设计与验证_第8张图片
产生RS232_tx信号,总线空闲时,该信号为高电平,在bit_cnt为0时,产生一个低电平起始位,紧接着,当bit_cnt分别为1-8时,发送数据的0-7位。其余情况,RS232_tx信号均为高电平。
以上就是笔者对串口发送模块代码的解析,下面,我们来对这个模块进行仿真验证。
直接上modelsim仿真波形图,如下:
【FPGA学习笔记】--串口发送模块的设计与验证_第9张图片
可以看到,我们串口发送模块的功能已经实现。至于仿真代码,笔者这里就不提供了,大家自己写一下,很简单的。有兴趣的同学,还可以设计一个顶层模块,下载到自己的开发板上,用串口调试助手测试一下,看自己的设计究竟对不对。反正笔者已经在自己的开发板上测试过了,大家也可以尝试一下。
以上,就是串口发送模块的完整设计。当然笔者只介绍了9600波特率这一种,同学们若是有兴趣,可以把其他几种波特率都实现出来,当然,也可以设计一个查找表,实现几种波特率之间的切换。笔者水平有限,文中可能有错误的地方,也有很多考虑不周的地方,还望大家批评指正。笔者坚信相互交流进步才更快!
最后再强调一下,我们做技术的,一定要自己多动手,在实践中才能发现问题,不要觉得简单,一定要自己动手。自己动手,发现错误解决错误,才能真正理解这个问题!
最后最后,欢迎大家添加笔者微信、QQ相互交流!此文章也在【FPGA学习笔记】公众号同步更新,欢迎大家关注!
【FPGA学习笔记】--串口发送模块的设计与验证_第10张图片
【FPGA学习笔记】--串口发送模块的设计与验证_第11张图片

你可能感兴趣的:(FPGA)