注:通过学习小梅哥的笔记代码,以下是我做的仿真及总结。不当处请指点。
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter)是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。在嵌入式设计中,UART用于主机与辅助设备通信,如汽车音响与外接AP之间的通信,与PC机通信包括与监控调试器和其它器件,如EEPROM通信。
值得注意的是,必须要分清UART,串口,RS232等概念:
UART是异步串行通信总称。
RSXXX(X代表数字)是一种标准,对应各种异步串行通信的接口标准和总线标准。
首先串口的概念一张图介绍
下面是具体实现过程
串口发送代码思路:就是一个简单的并转串的过程,值得注意的是波特率的设置
串口接收代码思路:由于传输可能引起信号失真,所以在接收端并不是一个简单的并转串的过程。
难点:1、分频+波特率设置,接收的信号通过把1bit数据分为多份,容易想混。
2、把1bit数据分为多份,取中间部分,1位数据累加(即5个数中3个为1则判定为1)。
3、判定是否为起始信号时,采用3位开始数据的累加,当大于2则判定非起始信号。
注意:1、接收的数据要打两拍,消除亚稳态的影响。
2、uart接收的标志信号(在接收这字节信号时拉高),设下降沿来时拉高,接收完毕和开始信号检测错误拉低。
3、设一个缓存器,缓存接收到的串行数据,当接收完一字节数据后,再把这个缓存器的值给出去。
一、uart_tx(uart的串口发送)
上面是uart发送一个字节的时序图,下面来介绍波特率的概念。波特率是传输数据的速度,若波特率为9600bps(9600bit per second)就是每秒传送9600bit的数据,可以得到传送1bit数据需要1/9600=104167ns,这时若用50M时钟来计数,50M时钟的时钟周期为1/50M=20ns。那么计104167/20-1=5207个数,则产生1bit数据。用查找变来设置可以复用的波特率000:9600bps; 001:19200bps; 010:8400bps; 01157600bps; 100:15200bps;
首先产生波特率时钟(即每bit产生一个计数时钟)
reg [15:0] bps_DR;//分频计数最大值
always@(posedge clk or negedge rst_n)
if(!rst_n)
bps_DR <= 16'd5207;
else begin
case(baud_set)
0:bps_DR <= 16'd5207; //9600bps
1:bps_DR <= 16'd2603; //19200bps
2:bps_DR <= 16'd1301; //38400bps
3:bps_DR <= 16'd867; //57600bps
4:bps_DR <= 16'd433; //115200bps
default:bps_DR <= 16'd5207;
endcase
end
reg bps_clk; //波特率时钟
reg [15:0]div_cnt;//分频计数器
always@(posedge clk or negedge rst_n)
if(!rst_n)
div_cnt <= 16'd0;
else if(uart_state_tx)begin //处于发送状态时,uart_state为1
if(div_cnt == bps_DR) //当传输完一个数据时,分频计数器置0重新计数
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 16'd0;
always@(posedge clk or negedge rst_n)
if(!rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1)
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
然后再用产生的波特率时钟来计数,计满11个数,开始清零(同时产生一个传输结束信号Tx_Done)。由此来传输一个字节的数据
接着在使能信号send_en有效的时候发送状态uart_state_tx为1,当波特率时钟计数达到11时发送状态uart_state_tx为0,其余时间uart_state_tx保持。
最后用查找表实现串口数据(Rs232_Tx )的发送
localparam START_BIT = 1'b0;
localparam STOP_BIT = 1'b1;
always@(posedge clk or negedge rst_n)
if(!rst_n)
Rs232_Tx <= 1'b1;
else begin
case(bps_cnt)
0:Rs232_Tx <= 1'b1;
1:Rs232_Tx <= START_BIT;
2:Rs232_Tx <= r_data_byte[0];
3:Rs232_Tx <= r_data_byte[1];
4:Rs232_Tx <= r_data_byte[2];
5:Rs232_Tx <= r_data_byte[3];
6:Rs232_Tx <= r_data_byte[4];
7:Rs232_Tx <= r_data_byte[5];
8:Rs232_Tx <= r_data_byte[6];
9:Rs232_Tx <= r_data_byte[7];
10:Rs232_Tx <= STOP_BIT;
default:Rs232_Tx <= 1'b1;
endcase
end
附上串口发送数据仿真图:
二、uart_rx(uart的串口接收)
由于传送数据的时候容易受到干扰导致数据出现误差,想要避免此误差,采用把1bit的数据分为16个部分,取中间的6个部分为有效,若这6个部分为1大于3个则这bit数据判断为1,若6个部分为0大于3个则这bit数据判断为0,若6个部分为1的个数等于为0的个数则判断传输环境恶劣数据不处理。uart串口发送时序图以及下面的每bit量化16部分时序图对比理解。
首先把接收到的串口数据(Rs232_Tx )打两拍实现同步时钟域,消除亚稳态,同时产生一个下降沿信号nedege。剩下的同上,产生一个采样时钟(对比串口发送的波特率时钟),这个采样时钟是波特率时钟的16倍(把每bit数据分成16部分,则实现一个部分的接收就需要快16倍)。举9600bps的波特率例,1/9600=104167ns,104167/20/16-1=325。同上用查找表表示波特率时钟,并在数据传输状态(uart_state)有效时,开始计数产生一个比波特率时钟大16倍的采样时钟(用于采集1bit中16部分中每个部分数据)。
//波特率选择器
reg [15:0]bps_DR;
always @(posedge clk or negedge rst_n)
if(!rst_n)
bps_DR <= 16'd325;
else begin
case(baud_set)
0:bps_DR <= 16'd325;
1:bps_DR <= 16'd162;
2:bps_DR <= 16'd80;
3:bps_DR <= 16'd53;
4:bps_DR <= 16'd26;
default:bps_DR <= 16'd325;
endcase
end
//产生采样时钟bps_clk,即波特率时钟的16倍
reg uart_state;
reg [15:0]div_cnt;
reg bps_clk;
always @(posedge clk or negedge rst_n)
if(!rst_n)
div_cnt <= 16'd0;
else if(uart_state)begin
if(div_cnt == bps_DR)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 16'd0;
always @(posedge clk or negedge rst_n)
if(!rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1)
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
然后对采样时钟进行计数,计满159个数即10bit数据(每个数据16个部分)||(计到12个数时&&START_BIT > 2),计数清零重新计数。tip:计12个并且起始位大于2个是检测起始位是否出错。
再当计满159个数即一个字节的数据后,实现Rx_Done信号拉高。
reg [7:0]bps_cnt;
always @(posedge clk or negedge rst_n)
if(!rst_n)
bps_cnt <= 8'd0;
else if(bps_cnt == 8'd159 || (bps_cnt == 8'd12 && (START_BIT > 2)))
bps_cnt <= 8'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
接着我们回到串口发送部分,当发送完一个字节的数据后,最后的1bit数据为stopt_bit=1’b1,并且数据保持。那么我们在串口接收到的数据的开始数据start_bit=0则必定产生一个下降沿,在这个下降沿产生的时候,接收数据的使能(uart_state_rx)有效,当接收完一个完整的字节数据(Rx_Done)||(计到12个数时&&START_BIT > 2),(tip:计12个并且起始位大于2个是检测起始位是否出错)接收数据的使能(uart_state_rx)无效,其余时间使能保持。
再接着采集数据接收模块的设计。由于中间的采样时间段对应的采样时钟值为6、7、8、9、10、11,通过规律可得下一组为22~27,同理每隔16个为下一组采样有效值(6+16=22)
//采样数据接收模块设计
reg [2:0]r_data_byte [7:0];
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
START_BIT <= 3'd0;
r_data_byte[0] <= 3'd0;
r_data_byte[1] <= 3'd0;
r_data_byte[2] <= 3'd0;
r_data_byte[3] <= 3'd0;
r_data_byte[4] <= 3'd0;
r_data_byte[5] <= 3'd0;
r_data_byte[6] <= 3'd0;
r_data_byte[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
else if(bps_clk)begin
case(bps_cnt)
0:begin
START_BIT <= 3'd0;
r_data_byte[0] <= 3'd0;
r_data_byte[1] <= 3'd0;
r_data_byte[2] <= 3'd0;
r_data_byte[3] <= 3'd0;
r_data_byte[4] <= 3'd0;
r_data_byte[5] <= 3'd0;
r_data_byte[6] <= 3'd0;
r_data_byte[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
6,7,8,9,10,11: START_BIT <= START_BIT + s1_Rs232_Rx;
22,23,24,25,26,27: r_data_byte[0] <= r_data_byte[0] + s1_Rs232_Rx;
38,39,40,41,42,43: r_data_byte[1] <= r_data_byte[1] + s1_Rs232_Rx;
54,55,56,57,58,59: r_data_byte[2] <= r_data_byte[2] + s1_Rs232_Rx;
70,71,72,73,74,75: r_data_byte[3] <= r_data_byte[3] + s1_Rs232_Rx;
86,87,88,89,90,91: r_data_byte[4] <= r_data_byte[4] + s1_Rs232_Rx;
102,103,104,105,106,107: r_data_byte[5] <= r_data_byte[5] + s1_Rs232_Rx;
118,119,120,121,122,123: r_data_byte[6] <= r_data_byte[6] + s1_Rs232_Rx;
134,135,136,137,138,139: r_data_byte[7] <= r_data_byte[7] + s1_Rs232_Rx;
150,151,152,153,154,155: STOP_BIT <= STOP_BIT + s1_Rs232_Rx;
default: //保持
begin
START_BIT <= START_BIT;
r_data_byte[0] <= r_data_byte[0];
r_data_byte[1] <= r_data_byte[1];
r_data_byte[2] <= r_data_byte[2];
r_data_byte[3] <= r_data_byte[3];
r_data_byte[4] <= r_data_byte[4];
r_data_byte[5] <= r_data_byte[5];
r_data_byte[6] <= r_data_byte[6];
r_data_byte[7] <= r_data_byte[7];
STOP_BIT <= STOP_BIT;
end
endcase
end
tip:在这里引入一个fpga中二维数组的定义:其实它并不是真正的二维数组,而是有多个寄存器组成的RAM或ROM。下面举一个例子:
reg [7:0] data [0:99] //定义一个二维数组(100个8位的寄存器)
assign dout = data [88][7:5] //取其中第88个寄存器的高3位为输出
//注意个数的书写问题,定义的时候reg [7:0]data [99:0]与上述是等价的,调用的时候必须个数在前,位数在后。
//其实在这里个数的思想就相当于在fifo中深度的思想一个道理。
最后,状态模块的判断设计,由于我们在1bit中划分16个部分并取中间6个部分为有效,判断6个部分有三个以上的1为1,有三个以下的0为0。其实在这里就是用计数(cnt)来数这6个部分的个数,当个数大于3d(011b)则判断为1,即判断二进制数的最高位是否为1,为1则这bit数据判断为1,为0则这bit数据判断为0。
//数据状态判定模块设计
always @(posedge clk or negedge rst_n)
if(!rst_n)
data_byte <= 8'd0;
else if(bps_cnt == 8'd159)begin
data_byte[0] <= r_data_byte[0][2];
data_byte[1] <= r_data_byte[1][2];
data_byte[2] <= r_data_byte[2][2];
data_byte[3] <= r_data_byte[3][2];
data_byte[4] <= r_data_byte[4][2];
data_byte[5] <= r_data_byte[5][2];
data_byte[6] <= r_data_byte[6][2];
data_byte[7] <= r_data_byte[7][2];
end
//这里把这八个3位寄存器的最高位赋值过去,因为若最高位为1,那么6个部分的计数大于3,若最高位为0,那么这6个部分计数小于3
附上串口接收数据仿真图