[原创] 凔海笔记之FPGA(八):Verilog描述RS232 UART

凔海   2016-5-17 00:03 楼主
借做项目催着急的由头给了自己一个多月不发帖的借口,现在我又来冒泡了。 在我看来,有些代码会用,但未必理解,有些代码理解,但未必会写,有些代码会写,但未必能用自己的话说出来。当能够以自己的想法深入浅出的讲解所学知识,那也就可以说自己掌握了,所以,我还是来发帖吧。
1.png
记得刚用单片机学习串口通讯的时候,我以为串口通讯=uart=RS232。o(╯□╰)o 直到最近我才明白 串口通讯(Serial Communication):是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式,就是一类通讯方式。 UART(Universal Asynchronous Receiver and Transmitter):通用异步收发器(异步串行通信口),是一种通用的数据通信协议,它包括了RS232、RS485等接口标准规范和总线标准规范,即UART是异步串行通信口的总称。 RS232:COM口是PC(个人计算机)上,异步串行通信口的简写。由于历史原因,IBM的PC外部接口配置为RS232,成为实际上的PC界默认标准。所以,现在PC机的COM口均为RS232。 这里所说的串口通讯,准确的说,应该是RS232 UART串口通讯,说白了,它就是一种数据传输的方式,一次能发送或接收一串数据。 首先,我们要了解下这个协议。 RS232 UART串口通讯就是将传输数据的每个字符一位接一位地按照一定时间间隔传输,这个数据是被包装起来的,首先,由起始位(0)+八位数据+奇偶校验+停止位构成。 那个一定的时间间隔称之为波特率,波特率就是发送一位所用的时间,在发送的时候,每一位的发送都是在这个时间段内的。 如果波特率为9600,也就是每秒发送9600个数据,这样的话发送一个数据的时间就是1÷9600=0.0001041667s=104166ns我们晶振是50MHZ,则始终周期是20ns,所以奇数个数为104166÷20=5208.所以,当我们在计数5208过程中要完成一位数据的传输,但为了保险起见,我们在这个数的中间进行传输,也就是2604时一位发送数据。 起始位:顾名思义,就是开始发送数据的标志位。当我们检测到X电平时,则是起始位,然后就可以发送八位数据了。 奇偶校验:如果发生的数据为计数,则置1,否则为0,但这并不能精准的检测是否发送正确,所以,既不能精准,便是无用,置一置零看你心情了, 停止位:如果为高电平,则发送数据完成且正确,反之,你懂得。
2.png
通过Verilog描述RS232的串口通讯,那怎么描述嘞,这就需要我们写写画画了。首先,要产生波特率,如果我们用9600bps,也就是以每104166ns发送一个字节的方式把11个数据发送给PC机,然后通过串口助手接收发来的数据。其实就是来个分频模块,其次要实现数据的发送,就是发送数据,那发送的数据从哪来呢,所以再来个数据源模块,它来控制数据的发送。综上,我们要建立三个模块分别完成波特率的产生,数据的发送,还有控制它们的工作。 波特率的产生模块命名为pbs_module.v 数据发送命名为tx_module 控制数据的发送命名为ctrl_module 首先,先描述三个模块的沟通吧
3.png
pbs_module.v“生产”9600波特率,每计数5 208完成一个数据的发送,但是,为了保险起见,我们再计数到2604就发送一个数据。所以assign pbs_ctrl_sig = (pbs_cnt == pbs_half) ? 1'b1 : 1'b0;当pbs_ctrl_sig=1则开始数据的发送 例如: 发送起始位: 4'd0: if(pbs_ctrl_sig) begin tx <= 1'b0; i <= i + 1'b1; end 但是,不能说pbs_ctrl_sig=1我们就发送数据,那样的话容易乱套,所以我们还需要start_sig信号tx_module.v模块和pbs_module.v模块同时干活,注意是同时干活,这样保证了不会出错。 当数据发送完后,再来个完成信号告诉ctrl_module活干完了,这样,该模块就会不让波特率计数,更不让数据发送 else if(ctrl_sig) begin start <= 1'b0; t_data <= 8'h32; end 歇息1s再干活。 else if(cnt == T1S) start <= 1'b1; 4.png 上面所说的是数据的发送,那么接收呢?也是类似的。先发送起始位,也就是0,接着接收八位数据,最后是奇偶校验位和停止位。所以了,先检测到低电平是否产生,然后在接收数据。 对于起始位的检测采用边沿检测法,在闲暇时,它一直都是高电平,当检测到低电平后又检测到高电平,这说明起始位来了,所以,要检测到低电平而后又转为高电平,就是起始位。 always @ (posedge clk or negedge rst_n) if(!rst_n) begin begin_sig_1 <= 1'b1; begin_sig_2 <= 1'b1; end else begin begin_sig_1 <= rx_pin; begin_sig_2 <= begin_sig_1; end assign rx_begin = begin_sig_2 & ~begin_sig_1; 起始位的检测,使产生bps的模块开始工作,当 assign pbs_ctrl_sig = (pbs_cnt == pbs_half) ? 1'b1 : 1'b0; 为1的时候,采集一位数据, 4'd2,4'd3,4'd4,4'd5,4'd6,4'd7,4'd8,4'd9: if(pbs_ctrl_sig) begin rx_data[i-2] <= rx_pin; i <= i + 1'b1; end 同样,低位在前,高位在后。 至于奇偶校验位和停止位,咱就这样对待吧,没啥用呀。 4'd10: if(pbs_ctrl_sig) i <= i + 1'b1; 4'd11: if(pbs_ctrl_sig) begin i <= 4'b0; ctrl <= 1'b0; end
5.png
最后,有没有发现,我们发送方式是 起始位(0)+八位数据+奇偶校验+停止位 接收方式还是 起始位(0)+八位数据+奇偶校验+停止位 先发送给PC机的,PC机就先发送给下位机,也就是先进来的现出去。这个就是FIFO。做项目的时候总是用到这玩意。 6.png
7.png 学识浅薄出拙文,如察错误望赐教,小弟在此感涕零。
本帖最后由 凔海 于 2016-5-17 00:10 编辑

    TX_RX.rar (2016-5-17 00:10 上传)

    5.55 MB, 下载次数: 101

回复评论 (7)

傻傻的分不清串口通讯  uart  RS232,这回明白了,哈哈
点赞  2016-5-17 08:29
!!!
点赞  2016-5-17 08:32
楼主辛苦。能说能写。
小建议,供参考:
1。如下,因为rx_pin是异步输入,所以 begin_sig_1不推荐生产内部信号,
begin_sig_1 <= rx_pin
assign rx_begin = begin_sig_2 & ~begin_sig_1;

2。至于奇偶校验位和停止位, 这是数据完整性的最少限度的保证, 他们要来判断,并生成error输出,供上层判断

3。这个代码你能过关的话,楼主距离无敌不远了

MicroPython中文社区https://micropython.org.cn/forum/  
点赞  2016-5-17 11:37
引用: 5525 发表于 2016-5-17 11:37
楼主辛苦。能说能写。
小建议,供参考:
1。如下,因为rx_pin是异步输入,所以 begin_ ...

谢谢您的建议,受教了
点赞  2016-5-21 06:37
好好好
点赞  2016-5-21 08:00
受益匪浅!!!!!!!!!!!!!!
点赞  2016-6-3 10:07
点赞  2018-12-7 10:16
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复