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次采样取中间值。附图。