一水寒

UART串口收发控制器

0
阅读(2938)

UART调试记录:UART协议看起来很简单,但是里面涉及好几个小细节,处理起来也不是那么轻松顺畅的,代码写完,调试过了再想起来来也确实就那么回事。重要的是掌握解决问题的方法,代码贴在这里,备忘备参考备修改。

异步通信协议
      

这是发送模块与接收快联调的一种方式,进来的的八位数据经过控制器的串行发送和串行接收,再并行送出去,以验证各个模块的功能正确与否。各个模块的代码如下:


module bps_gen(clk,                 
	rst_n, 
	bps_clk);

   input            clk;
   input            rst_n;
   output reg       bps_clk;

reg [8:0]cnt;
 
always@(posedge clk or negedge rst_n)   //波特率时钟产生,采用9600bps
   if(~rst_n)
		  begin
			cnt<=0;
			bps_clk<=0;
			end                   //16倍波特率时钟,bps=9600bit/s;
	 else                       		//1bit=>1000000/9.6=10416ns
		    if(cnt==162)         	//104166ns/period20ns=5208个clk
		       begin               	//uart时钟采用16倍波特率时钟
			 cnt<=0;            //5208/16=325   
			 bps_clk<=~bps_clk; //每162个clk计数bps_clk取反
		        end
	             else cnt<=cnt+1;
			
endmodule


module uart_tx( rst_n,            
					 bps_clk_tx,       
					 txd,              
					 enable,           
					 tx_din);          

input           rst_n;    
input           bps_clk_tx;
input           enable;
input  [7:0]    tx_din;
output reg      txd;

reg    [3:0]	 tx_cnt;
reg    [3:0]	 bps_cnt;

always @(posedge bps_clk_tx)                                 
   if(~rst_n||(~enable))   bps_cnt<=0;                    
   else 
	    if(bps_cnt<4'd15)  
                       bps_cnt<=bps_cnt+1;    //每16个波特率时钟记一次数,到15归零 
               else    bps_cnt<=0;     //在下面的接收模块没有这样处理,直接每次加16

always@(posedge bps_clk_tx or negedge rst_n)       //异步复位	
  if(~rst_n)
    begin
      tx_cnt<=0;
      txd<=1'b1;                          //复位的时候TXD给1
    end                                   //
 else  if(~enable)                        //在没有发送信号时回到复位状态
    begin
      tx_cnt<=0;
      txd<=1'b1;
    end
  else 
     begin                               //下面用7看下面所记述的第二种联方式会理解
	    if(bps_cnt==4'd7)         //这个数字开始写的15 为了同步接收模块改成7                         
		   if(tx_cnt<4'd10)
		        tx_cnt<=tx_cnt+1;//每16个bps_clk加一                   
		   else tx_cnt<=0;
     case(tx_cnt)                                 
		    1:txd<=1'b0;             //发送起始位
			 2:txd<=tx_din[0];//先发送第0位
			 3:txd<=tx_din[1];
			 4:txd<=tx_din[2];
			 5:txd<=tx_din[3];
			 6:txd<=tx_din[4];
			 7:txd<=tx_din[5];
			 8:txd<=tx_din[6];
			 9:txd<=tx_din[7];
			 10:txd<=1'b1;     //发送停止位,无效验位 	
	 default: ;
		  endcase
		end

endmodule

module uart_rx( rst_n,                 		 
	 bps_clk_rx,           
	 rxd,                   		 
	 rx_dout,               				 
	 rx_start);//接收开始信号          

input           rst_n;  
input           rxd;  
input           bps_clk_rx;
output  [7:0]   rx_dout;
output  reg     rx_start;

reg             rxd_reg;
reg     [7:0]   cnt;
reg     [7:0]   data_int;
reg             start_flag;
wire            start;

assign start=~rxd&rxd_reg;         //下降沿信号              
always@(posedge bps_clk_rx)        //D触发器检测检测下降沿         
	if(~rst_n)  rxd_reg<=0; //为了更稳定的信号,可以用两级或三级触发器
	else       rxd_reg<=rxd;          
always@(*)
   if(~rst_n||(cnt==8'd143)) start_flag<=1'b0; //cnt=143数据已经发送完毕,拉低开始信号
	else if(start)     start_flag<=1'b1;//第一个下降沿信号是起始信号
                                               //这里不用else用以记忆开始信号标志
	 
always@(posedge bps_clk_rx)
   if(~rst_n||~start_flag)     //复位,如果用异步复位这里要分开写 
	  begin             
	    data_int<=8'hzz;
               cnt<=0;
	    rx_start<=0;
	  end
	else                                               
	  begin
	    rx_start<=1;      //开始接收                  
	    if(cnt==8'd143)
		 cnt<=0;
		 else cnt<=cnt+1;
			 case(cnt)
				 'd23:data_int[0]<=rxd;     
				 'd39:data_int[1]<=rxd;     
				 'd55:data_int[2]<=rxd;
				 'd71:data_int[3]<=rxd;
				 'd87:data_int[4]<=rxd;
				'd103:data_int[5]<=rxd;
				'd119:data_int[6]<=rxd;
				'd135:data_int[7]<=rxd; 
			 default:;
			 endcase
	  end
	  
	assign rx_dout=data_int;                   //把这个改成reg型写到data_int[7]收完之后
	
endmodule
		 
		 
    

顶层原理图如下,各模块的连接关系在原理图里非常清楚,故省略顶层例化模块:

给出我用的testbench:


`timescale 1 ns/ 1 ps
module uart_tb();
reg         clk;
reg         enable;
reg         rst_n;

reg  [7:0]  tx_din;  
wire        rx_start;                                           
wire [7:0]  rx_dout;

parameter period=104320;
parameter bpsclk=6520;
                          
uart i1 (
	.clk(clk),
	.enable(enable),
	.rst_n(rst_n),
	.rx_dout(rx_dout),
	.rx_start(rx_start),
	.tx_din(tx_din));
initial                                                
   begin                                                  
	clk=0;
	enable=0;
	rst_n=0;
	#bpsclk       rst_n=1;
	#bpsclk       enable=1;	
	#bpsclk       tx_din=8'b10110001;
	#period       tx_din=8'b10010011;
	#(8*period)   tx_din=8'b10111001;
	#(8*period)   tx_din=8'b10101011;
	#(14*period) $stop;
	end                                                    
always   #10 clk=~clk;        
endmodule


仿真波形如下:红色圆圈内的数据是0010011;

可以_dout内的数据使一位一位进来的,其实在没有接收完成时,rx_dout是不应该放出去的,所以上面接收模块的assign rx_dout=data_int;是没有意义的,还是改为REG型在接收一帧数据完成的时候再给他赋值为好。改了以后应该是这样样子:

下面是另外一种方式的联调模式,数据串行从接收端进来,传递到发送模块再串行发送出去,原理图如下:
下面接收模块的rx_complete是上面的rx_start信号,没有改过来记得就行,上面的原理图改了

代码是一样的,只是顶层接线方式有所不同,这很简单。

下面是加了奇校验位的,在发送模块加几行代码即可,接收也在case里面再添一行就行。。

assign check=tx_din[0]+tx_din[1]+tx_din[2]+tx_din[3]+tx_din[4]+tx_din[5]+tx_din[6]+tx_din[7];

if(check%2==0)     check_bit<=1;
                   else  check_bit<=0; 

8:txd<=tx_din[6];
9:txd<=tx_din[7];
10:txd<=check_bit;
11:txd<=1'b1; 

需要注意的几个问题


1、发送起始位,复位的时候把TXD拉高,发送数据的第一位给0;
2、波特率时钟的计算,知道为什么要16倍波特率。
3、接收端怎么检测开始信号,涉及到下降沿的检测。
4、怎么确认开始信号。
5、理解接收数据时的操作方式,16次采样取中间值。附图。