一水寒

UART串口收发控制器

0
阅读(2264)

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

Baidu
map