当前位置:文档之家› UART串口通信设计实例

UART串口通信设计实例

2.5 UART串口通信设计实例(1)接下来用刚才采用的方法设计一个典型实例。

在一般的嵌入式开发和FPGA设计中,串口UART是使用非常频繁的一种调试手段。

下面我们将使用Verilog RTL编程设计一个串口收发模块。

这个实例虽然简单,但是在后续的调试开发中,串口使用的次数比较多,这里阐明它的设计方案,不仅仅是为了讲解RTL编程,而且为了后续使用兼容ARM9内核实现嵌入式开发。

串口在一般的台式机上都会有。

随着笔记本电脑的使用,一般会采用USB转串口的方案虚拟一个串口供笔记本使用。

图2-7为UART串口的结构图。

串口具有9个引脚,但是真正连接入FPGA开发板的一般只有两个引脚。

这两个引脚是:发送引脚TxD和接收引脚RxD。

由于是串行发送数据,因此如果开发板发送数据的话,则要通过TxD线1 bit接着1 bit 发送。

在接收时,同样通过RxD引脚1 bit接着1 bit接收。

再看看串口发送/接收的数据格式(见图2-8)。

在TxD或RxD这样的单线上,是从一个周期的低电平开始,以一个周期的高电平结束的。

它中间包含8个周期的数据位和一个周期针对8位数据的奇偶校验位。

每次传送一字节数据,它包含的8位是由低位开始传送,最后一位传送的是第7位。

这个设计有两个目的:一是从串口中接收数据,发送到输出端口。

接收的时候是串行的,也就是一个接一个的;但是发送到输出端口时,我们希望是8位放在一起,成为并行状态(见图2-10)。

我们知道,串口中出现信号,是没有先兆的。

如果出现了串行数据,则如何通知到输出端口呢?我们引入“接收有效”端口。

“接收有效”端口在一般情况下都是低电平,一旦有数据到来时,它就变成高电平。

下一个模块在得知“接收有效”信号为高电平时,它就明白:新到了一个字节的数据,放在“接收字节”端口里面。

二是发送数据到串口。

发送数据的时候,我们也希望输入端口能够给出一个简单的形式。

我们引入“发送有效”信号,它为高电平,表示我们希望把“发送字节”送入TxD发送出去。

但是“发送有效”信号是否生效是有限制的,也就是正在发送的时候,是不能接收新的数据并发送的。

所以,我们引入一个“发送状态”信号,它标识当前的“发报机”是否处于忙碌状态。

如果“发报机”处于忙碌状态,则它拒绝“发送有效”信号,不予执行。

根据上面的分析,可以确定端口信号如下:1.module rxtx (2. clk,3. rst,4. rx,5. tx_vld,6. tx_data,7.8. rx_vld,9. rx_data,10. tx,11. txrdy12. );13.input clk;14.input rst;15.input rx;16.input tx_vld;17.input [7:0] tx_data;18.19.output rx_vld;20.output [7:0] rx_data;21.output tx;22.output txrdy;rx对应RxD,tx对应TxD。

rx_vld就是“接收有效”信号,rx_data则是“接收字节”信号。

tx_vld是“发送有效”信号,tx_data是“发送字节”信号。

txrdy是“发送状态”信号,它是低电平表示正处于发送状态,不接收新的字节而进行发送。

我们知道,串行数据的频率是9600Hz,而FPGA开发板的频率却是非常高的。

这里,我们假定FPGA的工作频率是25MHz,则串口发送1位信息,则FPGA上的clk需要计数:25 000 000/9600=2604次。

我们知道rx一旦变化,不论是从0到1,还是从1到0,都表示1位信息的传递开始。

因此,我们在设计一个最大计数值为2604的计数器的时候,rx的变化都将导致这个计数器重新开始计数。

如果计数到2604附近,rx发生变化,那么又将导致计数器清零;如果rx没有发生变化,没关系,计数器在计数到2604时,自动清零。

因此,rx的变化会不断调整计数器的计数。

在这个计数器计算到中间,也就是1302时,是最佳采样时刻,这个时候,rx的电平是我们需要知道的位信息。

对于rx的接收,需要2~3个寄存器同步,来消除异步传送的不确定性。

下面描述的rx1、rx2、rx3、rxx只是对rx进行延时,消除异步效果。

1.reg rx1,rx2,rx3,rxx;2.always @ ( posedge clk ) begin3. rx1 <= rx;4. rx2 <= rx1;5. rx3 <= rx2;6. rxx <= rx3;7. end对于rxx,我们将检测它的变化,这个变化将作为置位计数器的标志。

rx_change表示rxx 发生了改变,它比较了rx_dly和rxx的差别。

1.reg rx_dly;2.always @ ( posedge clk )3. rx_dly <= rxx;4.5.wire rx_change;6.assign rx_change = (rxx != rx_dly );下面将实现一个计数器,它将以2604为周期进行计数。

如果rx保持长时间不变,比如传送多个1或多个0,则计数器以2604为周期计数,可以计算到底有多少个1或0传送—因为在传送多个1或0时,rx是不会发生变化的,但是计数器会从2604恢复到0,可以计算传递了多少位。

rx_en是我们提取rx的标志时刻,这时候计数器位于串行数据的中间—计数到1302时。

1.reg [13:0] rx_cnt;2.always @ ( posedge clk or posedge rst )3.if ( rst )4. rx_cnt <= 0;5.else if ( rx_change | ( rx_cnt==14'd2603 ) )6. rx_cnt <= 0;7.else8. rx_cnt <= rx_cnt + 1'b1;9.10.wire rx_en;11.assign rx_en = ( rx_cnt==14'd1301 );如果在RxD检测到0,即在rx_en等于1时,检测到rxx等于1'b0,我们知道探测到一个字节的传送开始。

这标志着后续将传送8位数据、1个奇偶校验位和1个停止位。

因此,在rx_en==1'b1,rxx==1'b0时,启动一个以10为周期的计数器。

以10为周期的计数器递进的标志是rx_en==1'b1—这是传送1个位的标志。

当计数到9时,计数终止,计数器清0,此时一个字节的数据接收完毕。

在第二次探测到rxx在rx_en有效时等于0,又将重复第二次计数,如此周而复始。

1.reg data_vld;2.always @ ( posedge clk or posedge rst )3.if ( rst )4. data_vld <= 1'b0;5.else if ( rx_en & ~rxx & ~data_vld )6. data_vld <= 1'b1;7.else if ( data_vld & ( data_cnt==4'h9 ) & rx_en )8. data_vld <= 1'b0;9.else;10.11.reg [3:0] data_cnt;12.always @ ( posedge clk or posedge rst )13.if ( rst )14. data_cnt <= 4'b0;15.else if ( data_vld )16. if ( rx_en )17. data_cnt <= data_cnt + 1'b1;18. else;19.else20.data_cnt <= 4'b0;我们在前面已经用到了这个计数器形式。

在这里,使用这种类型计数器,就是通过探测到传送开始的0位,启动一个以10为周期的计数器进行对后续位的接收,并在接收完毕后,自动恢复到初态。

在data_vld为高电平时,对应8位数据、1个奇偶校验位以及1个停止位的接收。

所以data_cnt从0到7计数时,我们可以依次接收rxx的数据。

因为rxx是从低位到高位传递,所以向右移位。

1.reg [7:0] rx_data;2.always @ ( posedge clk or posedge rst )3.if ( rst )4. rx_data <= 7'b0;5.else if ( data_vld & rx_en & ~data_cnt[3] )6. rx_data <= {rxx,rx_data[7:1]};7.else;同理,在data_vld计数到停止位时,我们认为该字节接收完毕,发送一个周期的高电平信号,通知给其他模块:表示已经接收到1字节,位于rx_data内。

其他模块在探测到rx_vld 为高电平时,取出rx_data。

1.always @ ( posedge clk or posedge rst )2.if ( rst )3. rx_vld <= 1'b0;4.else5.rx_vld <= data_vld & rx_en & ( data_cnt==4'h9);以上就是串口接收数据的设计代码。

在发送时,我们也希望能够利用rx_en这个定时信息,使用它来发送数据。

首先,我们在tx_vld==1'b1时,保存tx_data,用来发送。

tx_rdy_data 就是用来暂存tx_data的。

只有在txrdy等于1的情况下,也就是发送单元处于空闲状态时,tx_data才能保存入tx_rdy_data。

1.reg [7:0] tx_rdy_data;2.always @ ( posedge clk or posedge rst )3.if ( rst )4. tx_rdy_data <= 8'b0;5.else if ( tx_vld & txrdy )6. tx_rdy_data <= tx_data;7.else;当tx_vld有效时,会触发一个发送过程。

在发送时,tx会发送起始0位、8位数据、1个奇偶校验位和1个停止位,总共是11位。

因此,tx_vld触发了一个以11为周期的计数器,在每计数到一个数以后,会发送相应的位信息。

在发送完毕后,计数器清0。

1.reg tran_vld;2.always @ ( posedge clk or posedge rst )3.if ( rst )4. tran_vld <= 1'b0;5.else if ( tx_vld )6. tran_vld <= 1'b1;7.else if ( tran_vld & rx_en & ( tran_cnt== 4'd10 ) )8. tran_vld <= 1'b0;9.else;10.11.reg [3:0] tran_cnt;12.always @ ( posedge clk or posedge rst )13.if ( rst )14. tran_cnt <= 4'b0;15.else if ( tran_vld )16. if( rx_en )17. tran_cnt <= tran_cnt + 1'b1;18. else;19.else20.tran_cnt <= 4'b0;在上面,我们用到了同类的计数器。

相关主题