通用异步收发器(Universal Asynchronous Receiver/Transmitter,UART)可以和各种标准串行接口,如RS 232和RS 485等进行全双工异步通信,具有传输距离远、成本低、可靠性高等优点。一般UART由专用芯片如8250,16450来实现,但专用芯片引脚都较多,内含许多辅助功能,在实际使用时往往只需要用到UART的基本功能,使用专用芯片会造成资源浪费和成本提高。
一般而言UART和外界通信只需要两条信号线RXD和TXD,其中RXD是UART的接收端,TXD是UART的发送端,接收与发送是全双工形式。由于可编程逻辑器件技术的快速发展,FPGA的功能日益强大,其开发周期短、可重复编程的优点也越来越明显,在FPGA芯片上集成UART功能模块并和其他模块组合可以很方便地实现一个能与其他设备进行串行通信的片上系统。
FPGA(Field Programmable Gate Array)现场可编程门阵列在数字电路的设计中已经被广泛使用。这种设计方式可以将以前需要多块集成芯片的电路设计到一块大模块可编程逻辑器件中,大大减少了电路板的尺寸,增强了系统的可靠性和设计的灵活性。
1 UART 功能设计
1.1 UART的工作原理
异步通信时,UART发送/接收数据的传输格式表1所示,一个字符单位由开始位、数据位、停止位组成。
表1 UART发送/接收数据的传输格式
START |
D0 |
D1 |
D2 |
D3 |
D4 |
D5 |
D6 |
D7 |
P |
STOP |
起始位 |
|
较验位 |
停止位 |
异步通信的一帧传输经历以下步骤:
(1)无传输。发送方连续发送信号,处于信息“1”状态。
(2)起始传输。发送方在任何时刻将传号变成空号,即“1”跳变到“O”,并持续1位时间表明发送方开始传输数据。而同时,接收方收到空号后,开始与发送方同步,并期望收到随后的数据。
(3)奇偶传输。数据传输之后是可供选择的奇偶位发送或接收。
(4)停止传输。最后是发送或接收的停止位,其状态恒为“1”。
发送或接收一个完整的字节信息,首先是一个作为起始位的逻辑“0”位,接着是8个数据位,然后是停止位逻辑“1”位,数据线空闲时为高或“1”状态。起始位和停止位的作用是使接收器能把局部时钟与每个新开始接收的字符再同步。异步通信没有可参照的时钟信号,发送器可以随时发送数据,任何时刻串行数据到来时,接收器必须准确地发现起始位下降沿的出现时间,从而正确采样数据。
设计时可参考由专用芯片实现的UART的功能并进行一定精简,如可以用FPGA的片内RAM替代UART的FIFO,不用单独在UART模块中实现。设计的基本原则是保留最主要的功能,基于FPGA的UART系统波特率时钟发生器、接收器和发送器3个子模块组成,如图1所示。
图1 基于FPGA的UART组成模块
1.2 接收器设计
接收器的工作过程如下,如图2所示,在接收数据寄存器被读出一帧数据或系统开始工作以后,接收进程被启动。接收进程启动之后,检测起始位,检测到有效起始位后,以约定波特率的时钟开始接收数据,根据数据位数的约定,计数器统计接收位数。一帧数据接收完毕之后,如果使用了奇偶校验,则检测校验位,如无误则接收停止位。停止位接收完毕后,将接收数据转存到数据寄存器中。
图2 数据接收图
为确保接收器可靠工作,在接收端开始接收数据位之前,处于搜索状态,这时接收端以16倍波特率的速率读取线路状态,检测线路上出现低电平的时刻。因为异步传输的特点是以起始位为基准同步的。然而,通信线上的噪音也极有可能使传号“1”跳变到空号“0”。所以接收器以16倍的波特率对这种跳变进行检测,直至在连续8个接收时钟以后采样值仍然是低电平,才认为是一个真正的起始位,而不是噪音引起的,其中若有一次采样得到的为高电平则认为起始信号无效,返回初始状态重新等待起始信号的到来。找到起始位以后,就开始接收数据,最可靠的接收应该是接收时钟的出现时刻正好对着数据位的中央。由于在起始位检测时,已使时钟对准了位中央,用16倍波特率的时钟作为接收时钟,就是为了确保在位宽的中心时间对接收的位序列进行可靠采样,当采样计数器计数结束后所有数据位都已经输入完成。最后对停止位的高电平进行检测,若正确检测到高电平,说明本帧的各位正确接收完毕,将数据转存到数据寄存器中,否则出错。
采用有限状态机模型可以更清晰明确地描述接收器的功能,便于代码实现。接收器的状态转换图如图3所示,为突出主要过程,图中省略了奇偶校验的情况。接收器状态机由5个工作状态组成,分别是空闲状态、起始位确认、采样数据位、停止位确认和数据正确,触发状态转换的事件和在各个状态执行的动作见图中的文字说明。
图3 接收器状态转换图
根据状态图其主要的程序如下所示:
else if( RX_En_Sig )
case ( i )
4'd0 :
if( H2L_Sig ) begin i <= i + 1'b1; isCount<= 1'b1; end
4'd1 :
if( BPS_CLK ) begin i <= i + 1'b1; end
4'd2, 4'd3, 4'd4, 4'd5, 4'd6, 4'd7, 4'd8, 4'd9:
if( BPS_CLK ) begin i <= i + 1'b1; rData[ i- 2 ] <= RX_Pin_In; end
4'd10 :
if( BPS_CLK ) begin i <= i + 1'b1; end
4'd11 :
if( BPS_CLK ) begin i <= i + 1'b1; end
4'd12 :
begin i <= i + 1'b1; isDone <= 1'b1;isCount <= 1'b0; end
4'd13 :
begin i <= 4'd0; isDone <= 1'b0; end
endcase
其中起始位检测正确会触发RX_En_Sig,一次的定时采集时第 0 、1位数据(起始位),保持忽略态度。定时采集的是八位数据位,每一位数据位会依低位到最高位储存入 rData寄存器。此时程序前两位省略取2-9位就是传送的数据
1.3波特率发生器设计
波特率发生器实质是设计一个分频器,用于产生和RS 232通信同步的时钟。在系统中用一个计数器来完成这个功能,分频系数N决定了波特率的数值。该计数器一般工作在一个频率较高的系统时钟下,当计数到N/2时将输出置为高电平,再计数到N/2的数值后将输出置为低电平,如此反复即可得到占空比50%的波特率时钟,具体的波特率依赖于所使用的系统时钟频率和N的大小。如系统时钟频率是50 MHz,要求波特率是9600,N=1/50MHz=5208,如果从零开始算起就是5207个计数。然而,采集数据要求“在周期的中间”,那么结果是5208/2,结果等于2604。基本上rx_bps_module.v只有在Count_Sig拉高的时候,模块才开始计数。
利用verlog 所写的具体代码如下:
1.4发送器设计
图4发送数据原理图
当发送数据时,对于每一个数据的发送,每一位采用的是定时发送。假设,配置的波特率是9600bps,那么当有一个发送标志位时,数据将会以1/9600 的节拍将数据一位一位的发送出去。一帧数据有11位,需要12次定时。
主要代码为:
always @ ( posedgeCLK or negedge RSTn )
if( !RSTn )
begin
i <= 4'd0;
rTX <= 1'b1;
isDone <=1'b0;
end
else if( TX_En_Sig )
case ( i )
4'd0 :
if( BPS_CLK ) begin i <= i + 1'b1; rTX<= 1'b0; end
4'd1, 4'd2, 4'd3, 4'd4, 4'd5, 4'd6, 4'd7, 4'd8:
if( BPS_CLK ) begin i <= i + 1'b1; rTX<= TX_Data[ i - 1 ]; end
4'd9 :
if( BPS_CLK ) begin i <= i + 1'b1; rTX<= 1'b1; end
4'd10 :
if( BPS_CLK ) begin i <= i + 1'b1; rTX<= 1'b1; end
4'd11 :
if( BPS_CLK ) begin i <= i + 1'b1; isDone<= 1'b1; end
4'd12 :
begin i <= 4'd0; isDone <= 1'b0; end
endcase
2 数据传输仿真
Modelsim仿真工具是Model公司开发的。它支持Verilog、VHDL以及他们的混合仿真,它可以将整个程序分步执行,使设计者直接看到他的程序下一步要执行的语句,而且在程序执行的任何步骤任何时刻都可以查看任意变量的当前值,可以在Dataflow窗口查看某一单元或模块的输入输出的连续变化等,比quartus自带的仿真器功能强大的多,是目前业界最通用的仿真器之一。
2.1仿真串口发送模块
编写一个仿真激励程序用于单独仿真串口发送模块。
步骤0的时候,将数据8'h2E 发送至串口发送模块后使能串口发送模块,然后等待串口发送模块反馈完成信号。同样的动作也有 ... 步骤1是发送数据 8'h3f, 然而步骤2是发送数据 8'hdd。步骤3是停止动作
图5 发送数据仿真
放大第二个数据0x3f的发送过程,如下图所示。可以看出串口发送模块,发送数据的格式是一帧11 位。[0]开始位-逻辑0,[1:8]数据位,[9]校验位没有需要可以随便填,这里填逻辑1,[10]停止位逻辑1。
图6 发送过程仿真结果
图7 发送数据的延时仿真
串口发送模块配置的波特率是9600kbps ,所以一个数据逗留的时间是大约104us。在仿真结果中,在B0~B1 是数据[0],B1~B2 是数据[1] ...... B10~B11 是数据[10]。Bx~Bx之间的时间大约是104us。
2.2仿真串口接收模块
用串口发送模块作为串口接收模块的刺激,亦即串口接收模块的输入(复杂输入)。
用串口发送模块作为串口接收模块的刺激,亦即串口接收模块的输入(复杂输入)。下图是仿真虚拟环境env_rx_module.v 它组合了串口发送模块和串口接收模块。env_rx_module.v拥有TX_En_Sig , TX_Data , TX_Done_Sig , RX_En_Sig,RX_Done_Sig,RX_Data 等信号。在激励的过程中,需要对这些信号控制。
图8 接收模块仿真建模图
当RX_En_Sig 拉高的时候,串口接收模块开始准备接收数据了。当一帧11 位数据发送
至串口接收模块,并且被串口接收模块过滤。最后经过过滤的数据会输出至RX_Data ,
然后产生一个完成信号至RX_Done_Sig。(在这里数据格式四1 帧11 位,并且波特率为9600kbps。
仿真结果如下图所示:
图10 发送结果仿真图
上图仿真结果显示了“ 串口发送模块作为串口接收模块的刺激”的激励过程。( Cursor省略为C) 在C1~C2之间是第一帧数据的传送, C2~C3是第二位 数据的传送,其他的以此类推,然和C1~C12是一帧数据11位的传送过程。注意,每 个Cx~Cx之间的时间大约是104us,亦即9600kbps的波特率
3 结语
本设计是基于FPGA的UART设计,用时较少,逻辑消耗小,成熟稳定的实现了数据的发送与接收,可以兼容到自己的程序中。本设计的仿真程序实现了激励的所有功能,完成初步设计要求。
监听模块
module detect_module(clk4,rst4,Rx_Pin_In1,H2L_Sig);//帧开始监测模块
input clk4;
input rst4; //复位信号
input Rx_Pin_In1; //串行输入数据RX_Pin_In
output H2L_Sig;
reg H2L_F1;
reg H2L_F2;
always @ ( posedge clk4 or negedge rst4 )
if( !rst4 )
begin
H2L_F1 <= 1'b1;
H2L_F2 <= 1'b1;
end
else
begin
H2L_F1 <= Rx_Pin_In1;
H2L_F2 <= H2L_F1;
end
assign H2L_Sig = H2L_F2 & !H2L_F1;
endmodule
波特率
module rx_bps_module
(
clk1, rst1,
Count_Sig,
BPS_CLK
);
input clk1;
input rst1; //复位信号 RSTn,当RSTn=0,系统复位
input Count_Sig; //串口数据帧开始确认信号:Count_Sig,当 Count_Sig=1,表示串口输入帧开始时刻,持续一个系统时钟周期
output BPS_CLK; //BPS_CLK,当计数至每一位的中间位置,BPS_CLK=1,提示信号采集时间到,否则不进行信号采集
reg[12:0] Count_BPS;
always @ ( posedge clk1 or negedge rst1 )
if( !rst1 )
Count_BPS <= 13'd0;
else if( Count_BPS == 13'd5207 )
Count_BPS <= 13'd0;
else if( Count_Sig )
Count_BPS <= Count_BPS + 1'b1;
else
Count_BPS <= 13'd0;
assign BPS_CLK = ( Count_BPS == 12'd2604 ) ? 1'b1 : 1'b0;
endmodule
数据装载模块
module rx_control_module//数据装载模块
(
clk3, rst3,
H2L_Sig, RX_Pin_In2, BPS_CLK, RX_En_Sig1,
Count_Sig, RX_Data, RX_Done_Sig
);
input clk3;
input rst3; //复位信号 RSTn,当RSTn=0时,系统复位
input H2L_Sig; //帧开始信号H2L_Sig,当H2L_Sig=1,表示一帧信号开始
input RX_En_Sig1; //串口接收使能信号 RX_En_Sig,当RX_En_Sig=1,系统正常工作。反正不接收数据
input RX_Pin_In2; //串口数据输入信号: RX_Pin_In
input BPS_CLK; //位中心定位信号:BPS_CLK
output Count_Sig;
output[7:0] RX_Data;
output RX_Done_Sig;
reg[3:0] i;
reg[7:0] rData;
reg isCount;
reg isDone;
always @ ( posedge clk3 or negedge rst3 )
if( !rst3 )
begin
i <= 4'd0;
rData <= 8'd0;
isCount <= 1'b0;
isDone <= 1'b0;
end
else if( RX_En_Sig1 )
case ( i )
4'd0 :
if( H2L_Sig )
begin
i <= i + 1'b1;
isCount <= 1'b1;
end
4'd1 :
if( BPS_CLK )
begin
i <= i + 1'b1;
end
4'd2, 4'd3, 4'd4, 4'd5, 4'd6, 4'd7, 4'd8, 4'd9 :
if( BPS_CLK )
begin
i <= i + 1'b1;
rData[ i - 2 ] <= RX_Pin_In2;
end
4'd10 :
if( BPS_CLK )
begin
i <= i + 1'b1;
end
4'd11 :
if( BPS_CLK )
begin
i <= i + 1'b1;
end
4'd12 :
begin
i <= i + 1'b1;
isDone <= 1'b1;
isCount <= 1'b0;
end
4'd13 :
begin
i <= 4'd0;
isDone <= 1'b0;
end
endcase
assign Count_Sig = isCount;
assign RX_Data = rData;
assign RX_Done_Sig = isDone;
endmodule
数码管数据显示
module test_UART(CLK,RSTn,RX_Done_Sig,RX_Data2,SEG,SEL);
input CLK;
input RSTn;
input RX_Done_Sig; //串口数据接收结束信号: RX_Done_Sig,
//当RX_Done_Sig=1表示一帧串口数据接收完毕
input[7:0] RX_Data2; //装载好的串口数据:RX_Data,位宽为8bits
output[7:0] SEG;
output[7:0] SEL;
reg[7:0] SEG;
reg[2:0] SEL;
reg[23:0] CNT;
reg[2:0] dig;
always @(posedge CLK or negedge RSTn)
if(!RSTn) begin
dig <= 3'b000;
CNT <= 24'b0;
end
else begin
if(CNT == 24'd25000)
begin
CNT <= 24'b0;
if(dig == 3'd7)
dig <= 3'd0;
else
dig <= dig + 1'b1;
end
else
CNT <= CNT + 1'b1;
end
reg [7:0] Data = 8'b00000000; //数据寄存器
//Rx_Done_Sig信号上升沿来到时将串口接收到的数据送给Data
always @(posedge RX_Done_Sig)
Data <= RX_Data2;
always @(posedge CLK or negedge RSTn)
if(!RSTn) begin
SEG <= 8'b10000000;
SEL <= 3'b111;
end
else
case(dig)
3'd0 : begin SEG<= display(Data[0]);SEL <= 3'b111;end
3'd1 : begin SEG<= display(Data[1]);SEL <= 3'b110;end
3'd2 : begin SEG<= display(Data[2]);SEL <= 3'b101;end
3'd3 : begin SEG<= display(Data[3]);SEL <= 3'b100;end
3'd4 : begin SEG<= display(Data[4]);SEL <= 3'b011;end
3'd5 : begin SEG<= display(Data[5]);SEL <= 3'b010;end
3'd6 : begin SEG<= display(Data[6]);SEL <= 3'b001;end
3'd7 : begin SEG<= display(Data[7]);SEL <= 3'b000;end
default: begin SEG<= display(RX_Data2[1]);SEL<=3'b111;end
endcase
function [7:0] display;
input Data_In;
case(Data_In)
1'b0 : display = 8'b00111111;
1'b1 : display = 8'b00000110;
default : display = 8'b01111111;
endcase
endfunction
endmodule
顶层设计
module top(CLK,RSTn,RX_En_Sig,RX_Pin_In,SEG,SEL);
input CLK;
input RSTn;
input RX_En_Sig;
input RX_Pin_In;
output [7:0] SEG;
output [2:0] SEL;
wire H2L_Sig,RX_Pin_In1,RX_Pin_In2,RX_EN_Sig1;
wire Count_Sig;
wire BPS_CLK;
wire RX_Done_Sig;
wire [7:0] RX_Data,RX_Data2;
detect_module m1(.clk4(CLK),.rst4(RSTn),.Rx_Pin_In1(RX_Pin_In),.H2L_Sig(H2L_Sig));
rx_control_module m3(.clk3(CLK),.rst3(RSTn),.H2L_Sig(H2L_Sig),.RX_Pin_In2(RX_Pin_In),.BPS_CLK(BPS_CLK),
.RX_En_Sig1(RX_En_Sig),.Count_Sig(Count_Sig),.RX_Data(RX_Data),.RX_Done_Sig(RX_Done_Sig));
rx_bps_module m2(.clk1(CLK),.rst1(RSTn),.Count_Sig(Count_Sig),.BPS_CLK(BPS_CLK));
test_UART m4(.CLK(CLK),.RSTn(RSTn),.RX_Done_Sig(RX_Done_Sig),.RX_Data2(RX_Data),.SEG(SEG),.SEL(SEL));
endmodule