分享

UART串口收发控制器设计详解

 surfboarder 2017-07-25

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

异步通信协议
      

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<SPAN style="FONT-SIZE: 9pt; COLOR: black; FONT-FAMILY: 宋体">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
           
           
    </SPAN>

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

给出我用的testbench:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
  
  
</SPAN>

仿真波形如下:红色圆圈内的数据是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次采样取中间值。附图。

 

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多