UART分析与设计

摘要:UART是广泛使用的串行数据通信电路,因其要求的传输线少,可靠性高,传输距离远, 所以系统间互联常采用RS—232接口方式,一般说来,该接口由硬件(UART专用芯片)实现。 文章基于VerilogHDL语言,结合有限状态机的设计方法来实现UART,将其核心功能集成到 FPGA上,使整体设计紧凑、小巧,实现的UART功能稳定、可靠,为RS—232接口提供了一种 新的解决方案;同时,与其他设计方法相比较,利用有限状态机的方法具有结构模式直观简单, 设计流程短,程序层次分明,易综合,可靠性高等优点,必将在EDA技术中发挥重要作用。 
关键词:VerilogHDL; UART; 帧格式; 状态机

引言

随着微机系统的广泛运用和微机网络的极大发展,UART(Universal Asynchronous Receive Transmitter)1在数据通信及控制系统中得到了广泛运用。8250、NS16450等芯片都是常见的UART器件[2],这类芯片已经相当复杂,有的含有许多辅助模块(如FIFO),但在实际中有时只需要使用UART的部分功能,因而会造成一定的资源浪费。FPGA在现代电子设计中的广泛运用,使我们可以充分利用其资源,在芯片上集成UART的功能模块,这样就无需外接专用UART芯片,从而简化了电路,缩小了体积,设计的灵活性更大。

文章通过分析UART的功能,利用有限状态机[4]来描述UART核心控制逻辑的方法,将其核心功能集成,从而使整个设计更加稳定、可靠。基本的UART通信只需要两条信号线就可以完成数据的相互通信。UART的结构如图1所示。 

 

 

1  UART的帧格式

    在UART中,数据位是以字符为传送单位,数据的前、后要有起始位、停止位,另外可以在停止位的前面加上一个比特(bit)的校验位。其帧格式如图2所示[4]

 

其基本特点是:

 ① 在信号线上共有两种状态,可分别用逻辑1和逻辑0来区分。在发送器空闲时,数据线应该保持在逻辑高电平状态。

 ② 起始位:该位是一个逻辑0,总是加在每一帧的头部,提示接受器数据传输即将开始,在接收数据位过程中又被分离出去。

 ③ 数据位:在起始位之后就是数据位,一般为8位一个字节的数据,低位在前,高位在后。如字母C在ASCII表中是十进制67,二进制01000011,那么传输的将是11000010。

 ④ 校验位:该位一般用来判断接收的数据位有无错误,常用的校验方法是奇偶校验法。

 ⑤ 停止位:停止位总在每一帧的末尾,为逻辑1,用于标志一个字符传送的结束。

 ⑥ 帧:从起始位到停止位结束的时间间隙称为一帧。

2  UART的设计

2.1  UART接收器

串行数据帧和接收时钟是异步的,由逻辑1跳变为逻辑0可视为一个数据帧的开始,所以接收器首先要判断起始位。常用的方法有三倍速采样法,起始位中断捕捉、定时采样法[5]。文章采用的方法是中间时刻采样法。由于内部采样时钟clk__rev是发送或接收时钟频率的16倍,所以起始位至少有8个连续的clk__rev周期的逻辑0被检测到才认为起始位接收到了,接着数据位和奇偶校验位将每隔 个clk__rev周期被采样一次。如果起始位的确是 个clk__rev周期长,那么接下来将在每个位的中点处被采样。

UART接收器的状态转移图如图3所示。

 

 图3  UART接收器的状态转移图

状态机一共有4个状态:state0(检测起始位),state1(对数据位进行采样,并串/并转换),state2(奇偶校验分析),state3(接收数据正确与否检测)。

① state0状态:当UART接收器复位以后,接收器将处于这一状态。在该状态,状态机一直等待rxd电平的跳变,即从逻辑1变为逻辑0,也就是等待起始位的到来。一旦检测到起始位,就对采样时钟clk__rev上跳沿计数,当计数为8时,也就是确保在起始位的中间点,然后转到state1状态。

② state1状态:该状态下,每间隔16位时钟采样一位串行数据,接收8位异步数据并进行串/并转换。即对clk__rev上跳沿计数,当为16时,就对数据采样,这样保证了数据位是在中点处被采样的,同时串/并转换,当探测已经收到8位数据以后,便进入了state2状态。

③ state2状态:该状态实现的功能是奇偶校验。本文采用的是偶校验。校验结束以后,转到state3状态。

④  state3状态:该状态是用来帧校验的,即在校验位以后,检测停止位是否为逻辑1。

如果是逻辑1,则输出state2中串/并转换后的8位数据,同时输出后级使能控制信号en__con为1,提醒后级可以接收该8位数据;如果是逻辑0,则说明帧错误,en__con输出为0,后级不能接收该8位数据。状态最终返回到state0。

接收器端口信号如图4所示。

 

图4  接收器端口信号图

clk__rev:内部采样时钟               parity_err:奇偶校验位

rst:复位信号                        fra_err:桢错误信号

rxd:串行输入数据                   en_con:后级使能控制

dout[7..0]:输出数据总线

UART接收器仿真结果如图5所示。

 

图5  UART接收器仿真结果

仿真结果说明:选择8位数据宽度,例如在rxd信号线上设置数据000001011,在输出数据总线dout[7..0]得到10100000,表明仿真结果正确。

2.2  UART发送器

   发送器实现的功能是将输入的 8位并行数据 变为串行数据,同时在数据头部加起始位,在数据位尾部加奇偶校验位和停止位。

   UART发送器的状态转移图如图6所示。

 

图6  UART发送器的状态转移图

 ①state0状态:当 UART被复位信号复位以后,状态机将立刻进入这一状态,在该状态下读8位并行数据,并转到state1。

 ② state1状态:输出起始位之前的一位逻辑1,然后转入到state2。

 ③ state2状态:输出起始位0。综合state1和state2可知,在第一位数据位之前,有一个从1到0的下跳变。

 ④ state3状态:根据8位数据,检验校验位为逻辑1还是逻辑0,校验位在state4状态输出。

 ⑤ state4状态:该状态机的功能在state3中已经讲过。

 ⑥ state5状态:输出停止位。

发送器端口信号如图7所示。

 

图7  发送器端口信号图

rst:复位信号                       w__en:写锁存

clk__send:发送时钟                 din[7..0]:输入数据

d__out:串行数据输出

UART发送器仿真结果如图8所示。

 

图8  UART发送器仿真结果

仿真结果说明:在写锁存w__en有效的情况下,当输入数据din[7..0]为11001100时,串行数据输出d__out为0011001101。倒数第2位是添加的奇偶校验位0,显然满足偶校验的方式;最后一位是停止位,为高电平1。

2.3  波特率发生器

波特率发生器实际上就是分频器,设计比较简单,文章用的是16分频。

3   结束语

利用VerilogHDL设计的灵活性,根据串行通信协议的要求,通过对波特率发生器、发送器和接收器的设计与仿真,能较容易地实现通用异步收发器总模块。对于收发的数据帧和波特率时钟频率能较灵活地改变,而且硬件实现不需要很多资源。

本文设计出的基于VerilogHDL异步串行通信电路,在实验室已经与计算机串口RS—232进行了通信实验,实验表明,0到255所有的数据都能被正确收、发。

 

参考文献:

[1]    Wilfried Elmenreich,Martin Delvai。“Time—Triggered Communication with UARTS” [J] 4th IEEE International Workshop on Factory Communication Systems,Vasteras,Sweden,August 28—30,2002 .

[2]    Martin S.Michael,“A Comparison of the INS8250,NS16450 and NS16550AF Series of UARTs” [J] National Seiniconductor Application Note 493,April 1989 .

[3]   潘松,王国栋.VHDL实用教程[M] .成都:电子科技大学出版社,2003 .

[4]   李群芳,黄建.单片微型计算机与接口技术[M].北京:电子工业出版社,2002 .

[5]   http://www.goldenchip.com.cn/gdbbs/dispbbs.asp?ID=200433110139608&boardid=4.

 

11.5.3  UART分频器

假设数据的波特率为p,则所需时钟的频率为16*p。以波特率p为115200为例,系统时钟为50MHz,则分频系数为50000000/(16*115200) = 27.127,取整为27。分频器Verilog HDL语言代码如下:

 
  
  1. module clkdiv(clk, clkout);  
  2.  
  3. input clk;          //系统时钟  
  4. output clkout;      //采样时钟输出  
  5.  
  6. reg clkout;  
  7. reg [15:0] cnt;  
  8.  
  9. always @(posedge clk)   //分频进程  
  10. begin  
  11. if(cnt == 16'd12)  
  12. begin  
  13. clkout <= 1'b1;  
  14. cnt <= cnt + 16'd1;  
  15. end  
  16. else if(cnt == 16'd26)  
  17. begin  
  18. clkout <= 1'b0;  
  19. cnt <= 16'd0;  
  20. end  
  21. else 
  22. begin  
  23. cnt <= cnt + 16'd1;  
  24. end  
  25. end  
  26.  
  27. endmodule 

保存文件为clkdiv.v,单击Files → Create/Update → Create Symbol Files for Current File命令,为clkdiv.v生成原理图模块。新建一个原理图文件,在原理图空白处双击,在弹出的Symbol对话框中选择Project → clkidv模块,单击OK按钮退出Symbol对话框。在原理图的适当位置放置clkdiv模块,并添加输入输出模块。保存原理图为uartrxtx.bdf。编译工程文件,编译无误后单击Processing → Generate Functional Simulation Netlist,产生功能仿真网表。新建波形仿真文件,加入输入输出信号,设置系统时钟信号clk的周期为20ns,保存波形文件为 uartrxtx.vwf,单击 按钮进行分频器的波形仿真,波形仿真报告如图11-41所示。

UART分析与设计_第1张图片  

 

 

 

11.5.4  UART发送模块

UART发送模块的功能:接收到发送指令后,把数据按UART协议输出,先输出一个低电平的起始位,然后从低到高输出8个数据位,接着是可选的奇偶校验位,最后是高电平的停止位。Verilog HDL语言代码如下:

 
  
  1. module uarttx(clk, datain, wrsig, idle, tx);  
  2.  
  3. input clk;          //UART时钟  
  4. input [7:0] datain; //需要发送的数据  
  5. input wrsig;        //发送命令,上升沿有效  
  6.  
  7. output idle;    //线路状态指示,高为线路忙,低为线路空闲  
  8. output tx;      //发送数据信号  
  9.  
  10. reg idle, tx;  
  11. reg send;  
  12. reg wrsigbuf, wrsigrise;  
  13. reg presult;  
  14.  
  15. reg[7:0] cnt;       //计数器  
  16.  
  17. parameter paritymode = 1'b0;  
  18.  
  19. //检测发送命令是否有效  
  20. always @(posedge clk)  
  21. begin  
  22. wrsigbuf <= wrsig;  
  23. wrsigrise <= (~wrsigbuf) & wrsig;  
  24. end  
  25.  
  26. always @(posedge clk)  
  27. begin  
  28. if (wrsigrise &&  (~idle))    
  29. //当发送命令有效且线路为空闲时,启动新的数据发送进程  
  30. begin  
  31. send <= 1'b1;  
  32. end  
  33. else if(cnt == 8'd176)      //一帧资料发送结束  
  34. begin  
  35. send <= 1'b0;  
  36. end  
  37. end  
  38.  
  39. always @(posedge clk)  
  40. begin  
  41. if(send == 1'b1)  
  42. begin  
  43. case(cnt)       //产生起始位  
  44. 8'd0:  
  45. begin  
  46. tx <= 1'b0;  
  47. idle <= 1'b1;  
  48. cnt <= cnt + 8'd1;  
  49. end  
  50. 8'd16:  
  51. begin  
  52. tx <= datain[0];    //发送数据0位  
  53. presult <= datain[0]^paritymode;  
  54. idle <= 1'b1;  
  55. cnt <= cnt + 8'd1;  
  56. end  
  57. 8'd32:  
  58. begin  
  59. tx <= datain[1];    //发送数据1位  
  60. presult <= datain[1]^presult;  
  61. idle <= 1'b1;  
  62. cnt <= cnt + 8'd1;  
  63. end  
  64. 8'd48:  
  65. begin  
  66. tx <= datain[2];    //发送数据2位  
  67. presult <= datain[2]^presult;  
  68. idle <= 1'b1;  
  69. cnt <= cnt + 8'd1;  
  70. end  
  71. 8'd64:  
  72. begin  
  73. tx <= datain[3];    //发送数据3位  
  74. presult <= datain[3]^presult;  
  75. idle <= 1'b1;  
  76. cnt <= cnt + 8'd1;  
  77. end  
  78. 8'd80:  
  79. begin  
  80. tx <= datain[4];    //发送数据4位  
  81. presult <= datain[4]^presult;  
  82. idle <= 1'b1;  
  83. cnt <= cnt + 8'd1;  
  84. end  
  85. 8'd96:  
  86. begin  
  87. tx <= datain[5];    //发送数据5位  
  88. presult <= datain[5]^presult;  
  89. idle <= 1'b1;  
  90. cnt <= cnt + 8'd1;  
  91. end  
  92. 8'd112:  
  93. begin  
  94. tx <= datain[6];    //发送数据6位  
  95. presult <= datain[6]^presult;  
  96. idle <= 1'b1;  
  97. cnt <= cnt + 8'd1;  
  98. end  
  99. 8'd128:  
  100. begin  
  101. tx <= datain[7];    //发送数据7位  
  102. presult <= datain[7]^presult;  
  103. idle <= 1'b1;  
  104. cnt <= cnt + 8'd1;  
  105. end  
  106. 8'd144:  
  107. begin  
  108. tx <= presult;      //发送奇偶校验位  
  109. presult <= datain[0]^paritymode;  
  110. idle <= 1'b1;  
  111. cnt <= cnt + 8'd1;  
  112. end  
  113. 8'd160:  
  114. begin  
  115. tx <= 1'b1;     //发送停止位               
  116. idle <= 1'b1;  
  117. cnt <= cnt + 8'd1;  
  118. end  
  119. 8'd176:  
  120. begin  
  121. tx <= 1'b1;               
  122. idle <= 1'b0;   //一帧资料发送结束  
  123. cnt <= cnt + 8'd1;  
  124. end  
  125. default:  
  126. begin  
  127. cnt <= cnt + 8'd1;  
  128. end  
  129. endcase  
  130. end  
  131. else 
  132. begin  
  133. tx <= 1'b1;  
  134. cnt <= 8'd0;  
  135. idle <= 1'b0;  
  136. end  
  137. end  
  138.  
  139. endmodule 

保存文件为uarttx.v,单击Files → Create/Update → Create Symbol Files for Current File命令,为uarttx.v生成原理图模块。为了测试UART发送模块的正确性,需要编写一个测试模块来测试UART发送模块,Verilog HDL语言代码如下:

 
  
  1. module testuart(clk, dataout, wrsig);  
  2.  
  3. input clk;  
  4.  
  5. output[7:0] dataout;  
  6. output wrsig;  
  7.  
  8. reg [7:0] dataout;  
  9. reg wrsig;  
  10.  
  11. reg [7:0] cnt;  
  12.  
  13. always @(posedge clk)  
  14. begin  
  15. if(cnt == 254)  
  16. begin  
  17. dataout <= dataout + 8'd1;  //每次数据加"1"  
  18. wrsig <= 1'b1;          //产生发送命令  
  19. cnt <= 8'd0;  
  20. end  
  21. else 
  22. begin  
  23. wrsig <= 1'b0;  
  24. cnt <= cnt + 8'd1;  
  25. end  
  26. end  
  27.  
  28. endmodule 

保存文件为testuart.v,单击Files → Create/Update → Create Symbol Files for Current File命令,为testuart.v生成原理图模块。新建一个原理图文件,在原理图空白处双击,在弹出的Symbol对话框中选择Project → testuart模块和uarttx模块,单击OK按钮退出Symbol对话框。在原理图的适当位置放置testuart模块和uarttx模块,并添加输入输出模块。为了仿真方便,把原来分频模块的分频系数更改为4,各个模块的连接如图11-42所示。

 

UART分析与设计_第2张图片  

保存原理图为uartrxtx.bdf。编译工程文件,编译无误后单击Processing → Generate Functional Simulation Netlist,产生功能仿真网表。新建波形仿真文件,加入输入输出信号,设置系统时钟信号clk的周期为20ns,保存波形文件为uartrxtx.vwf,单击 按钮进行UART数据发送的波形仿真,波形仿真报告如图11-43所示。

 

UART分析与设计_第3张图片 

 

11.5.5  UART接收模块

UART接收模块的功能:时时检测线路,当线路产生下降沿时,即认为线路有数据传输,启动接收数据进程进行接收,按从低位到高位接收数据。UART接收模块的Verilog HDL语言代码如下:

 
  
  1. module uartrx(clk, rx, dataout, rdsig, dataerror, frameerror);  
  2.  
  3. input clk;          //采样时钟  
  4. input rx;           //UART数据输入  
  5. output dataout;     //接收数据输出  
  6. output rdsig;  
  7. output dataerror;   //资料出错指示  
  8. output frameerror;  //帧出错指示  
  9.  
  10. reg[7:0] dataout;  
  11. reg rdsig, dataerror;  
  12. reg frameerror;  
  13.  
  14. reg [7:0] cnt;  
  15. reg rxbuf, rxfall, receive;  
  16.  
  17. parameter paritymode = 1'b0;  
  18. reg presult, idle;  
  19.  
  20. always @(posedge clk)   //检测线路的下降沿  
  21. begin  
  22. rxbuf <= rx;  
  23. rxfall <= rxbuf & (~rx);  
  24. end  
  25.  
  26. always @(posedge clk)  
  27. begin  
  28. if (rxfall && (~idle))    
  29. //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程  
  30. begin  
  31. receive <= 1'b1;  
  32. end  
  33. else if(cnt == 8'd175)  //接收数据完成  
  34. begin  
  35. receive <= 1'b0;  
  36. end  
  37. end  
  38.  
  39. always @(posedge clk)  
  40. begin  
  41. if(receive == 1'b1)  
  42. begin  
  43. case (cnt)  
  44. 8'd0:  
  45. begin  
  46. idle <= 1'b1;  
  47. cnt <= cnt + 8'd1;  
  48. rdsig <= 1'b0;  
  49. end  
  50. 8'd24:  //接收第0位数据  
  51. begin  
  52. idle <= 1'b1;  
  53. dataout[0] <= rx;  
  54. presult <= paritymode^rx;  
  55. cnt <= cnt + 8'd1;  
  56. rdsig <= 1'b0;  
  57. end  
  58. 8'd40:  //接收第1位数据  
  59. begin  
  60. idle <= 1'b1;  
  61. dataout[1] <= rx;  
  62. presult <= presult^rx;  
  63. cnt <= cnt + 8'd1;  
  64. rdsig <= 1'b0;  
  65. end  
  66. 8'd56:  //接收第2位数据  
  67. begin  
  68. idle <= 1'b1;  
  69. dataout[2] <= rx;  
  70. presult <= presult^rx;  
  71. cnt <= cnt + 8'd1;  
  72. rdsig <= 1'b0;  
  73. end  
  74. 8'd72:  //接收第3位数据  
  75. begin  
  76. idle <= 1'b1;  
  77. dataout[3] <= rx;  
  78. presult <= presult^rx;  
  79. cnt <= cnt + 8'd1;  
  80. rdsig <= 1'b0;  
  81. end  
  82. 8'd88:  //接收第4位数据  
  83. begin  
  84. idle <= 1'b1;  
  85. dataout[4] <= rx;  
  86. presult <= presult^rx;  
  87. cnt <= cnt + 8'd1;  
  88. rdsig <= 1'b0;  
  89. end  
  90. 8'd104: //接收第5位数据  
  91. begin  
  92. idle <= 1'b1;  
  93. dataout[5] <= rx;  
  94. presult <= presult^rx;  
  95. cnt <= cnt + 8'd1;  
  96. rdsig <= 1'b0;  
  97. end  
  98. 8'd120:     //接收第6位数据  
  99. begin  
  100. idle <= 1'b1;  
  101. dataout[6] <= rx;  
  102. presult <= presult^rx;  
  103. cnt <= cnt + 8'd1;  
  104. rdsig <= 1'b0;  
  105. end  
  106. 8'd136:     //接收第7位数据  
  107. begin  
  108. idle <= 1'b1;  
  109. dataout[7] <= rx;  
  110. presult <= presult^rx;  
  111. cnt <= cnt + 8'd1;  
  112. rdsig <= 1'b1;  
  113. end  
  114. 8'd152:     //接收奇偶校验位  
  115. begin  
  116. idle <= 1'b1;  
  117. if(presult == rx)  
  118. dataerror <= 1'b0;  
  119. else 
  120. dataerror <= 1'b1;  //如果奇偶校验位不对,表示数据出错    
  121. cnt <= cnt + 8'd1;  
  122. rdsig <= 1'b1;  
  123. end  
  124. 8'd168:  
  125. begin  
  126. idle <= 1'b1;  
  127. if(1'b1 == rx)  
  128. frameerror <= 1'b0;  
  129. else 
  130. frameerror <= 1'b1; //如果没有接收到停止位,表示帧出错  
  131. cnt <= cnt + 8'd1;  
  132. rdsig <= 1'b1;  
  133. end  
  134. default:  
  135. begin  
  136. cnt <= cnt + 8'd1;  
  137. end  
  138. endcase  
  139. end  
  140. else 
  141. begin  
  142. cnt <= 8'd0;  
  143. idle <= 1'b0;  
  144. rdsig <= 1'b0;  
  145. end  
  146. end  
  147.  
  148. endmodule 

保存文件为uartrx.v,单击Files → Create/Update → Create Symbol Files for Current File命令,为uartrx.v生成原理图模块。新建一个原理图文件,在原理图空白处双击,在弹出的Symbol对话框中选择Project → uartrx模块,单击OK按钮退出Symbol对话框。在原理图的适当位置放置uartrx模块,并添加输入输出模块,各个模块的连接如图11-44所示。

UART分析与设计_第4张图片  

保存原理图为uartrxtx.bdf。编译工程文件,编译无误后单击Processing → Generate Functional Simulation Netlist,产生功能仿真网表。新建波形仿真文件,加入输入输出信号,设置系统时钟信号clk的周期为20ns,保存波形文件为 uartrxtx.vwf,单击 按钮进行UART数据接收的波形仿真,波形仿真报告如图11-45所示。

UART分析与设计_第5张图片  

波形仿真报告说明:

对图11-45分析看出,UART接收模块接收到的数据与UART发送模块发送的数据相一至,每接收到一个数据都有一个读取数据指示rdisg,UART接收模块得到正确验证。

你可能感兴趣的:(FPGA)