1. 介绍
RS232是一种全双工异步的总线,PC端使用如下的DB-9接口。其中有三个脚比较重要,分别为Pin2:Received Data(RXD)、Pin3:Transmit Data(TXD)、Pin5:Ground(GND)。
RS232是一种端到端的通信方式,在开始通信前,需要手动的确定两个RS232的端口通信参数是一致的。由于使用的习惯,这里只说明RS232串口的8-N-1,即8bit数据,无奇偶校验,1位停止位。
RS232串口的TXD线在空闲时,电平为高;当开始发送字节之前,发送一个低电平的起始位;发送起始位后,发送8bit的数据,此处数据的LSB先发送,MSB后发送;发送8bit数据后,发送高电平的结束位。
下图是发送0x55数据时候的波形:
下图是发送0xC4数据时候的波形:
RS232的速度用波特率来表示,波特率是每秒钟发送码元的个数,由于是二进制系统,所以1个码元由1bit表示,故波特率与比特率相同。在RS232串口中常用的波特率由9600,、115200等。波特率为115200是,1bit数据持续的时间为(1/115200)≈8.7us,发送1Byte就需要69.6us。由于串口发送1Byte的数据还需要辅助bit,例如开始位,结束位,故发送1Byte数据就需要8.7*10=87us;在1s时间间隔内,可以发送11.22KB的有效数据。
在RS232的标准中,使用-5V~-15V的表示逻辑高电平,使用5V~15V表示逻辑低电平。由于存在Max3232这类的电平转换芯片,所以我们只需要利用TTL电平。
2. 波特率的产生
(1)原理分析
波特率为115200的串口通信中,至少需要115200Hz的时钟产生波特率。此时,我们假设系统时钟比波特率时钟高16倍,则需要115200 Hz × 16 = 1.8432 MHz。系统时钟带有小数部分,不是常用的时钟。我们用2MHz的系统时钟替代1.8432MHz的系统时钟,则2MHz分频到115200Hz的分频比为(Clk / Baundrate)= 17.361111111...。在FPGA中,为了达到小数的分频比,可以利用一个10bit的寄存机,按59累加,溢出脉冲就是时钟分频后的波特率时钟,分频比为(2^累加器位宽 / 累加步长)=(2^10 / 59)= 17.3559,此刻的分频比的误差率是0.03%。
有时候,我们提供的时钟Clk,并不是2MHz,波特率也可能不是115200,那么需要怎么通过我时钟和波特率得到累加步长呢?根据上面的分析,可知不管是时钟除还是累加器与累加步长相除,都是为了得到相同的分频比,故列出分频比的等式为:
(Clk / Baundrate)=(2^累加器位宽 / 累加步长)
故得到累加步长的计算公式:
累加步长=(Baundrate / Clk)*2^累加器位宽=Baundrate * 2^累加器位宽 / Clk
在程序中,左位移N位,就是该数乘2的N次方,而累加步长为:
累加步长 = (Baundrate <<累加器位宽)/Clk
由于FPGA的数据处理位宽不会大于32bit,但有时候Baundrate <<累加器位宽的数据比较大,使得累加器数据无法计算。为此上下同时除以16,减小中间过程的数值。当然可以除以其他数值,但可以是其他2的幂次数。
累加步长 =(Baundrate<<(累加器位宽-4))/ (Clk>>4)
除法会产生小数,需要对结果进行四舍五入,在结果上加上1/2后,达到四舍五入的效果。
累加步长 =((Baundrate<<(累加器位宽-4))+ Clk>>5)/ (Clk>>4)
(2)波特率模块FPGA实现
波特率模块可以通过两个时钟使能管脚,分别产生数据发送时钟和串口接收时钟。
累加步长的计算如下。pBaudGeneratorInc是发送时钟的累加字;pBaudGeneratorInc8x是接收采样时钟的累加字,它是发送时钟的8倍,用来对1bit做8次采样。
localparam pBaudGeneratorInc = ((pBaud<<(pBaudGeneratorAccWidth-4))+(pClkFre>>5))/(pClkFre>>4);
localparam pBaudGeneratorInc8x = pBaudGeneratorInc<<3;
发送时钟产生
//Txd的波特率发射脉冲
always @ (posedge iClk )
begin
if(iTxdEnable == 1'b0)
rvBaudGeneratorAcc <= 0;
else
rvBaudGeneratorAcc <= rvBaudGeneratorAcc[pBaudGeneratorAccWidth-1:0]+pBaudGeneratorInc[pBaudGeneratorAccWidth:0];
end
assign oTxdBaudTick = rvBaudGeneratorAcc[pBaudGeneratorAccWidth];
接收时钟产生
wire [pBaudGeneratorAccWidth:0] wvConstValue = 2;
//Rxd的波特率采样时钟
always @ (posedge iClk )
begin
if(iRxdEnable == 1'b0)
rvBaudGeneratorAcc8x <= wvConstValue<<(pBaudGeneratorAccWidth-2);
else
rvBaudGeneratorAcc8x <= rvBaudGeneratorAcc8x[pBaudGeneratorAccWidth-1:0]+pBaudGeneratorInc8x[pBaudGeneratorAccWidth:0];
end
assign oRxdBaudTick = rvBaudGeneratorAcc8x[pBaudGeneratorAccWidth];
这里复位时,将累加器的数值设置成慢数值的一半。该设置的目的是确保在串口的1个bit有8个接收脉冲。
采用wvConstValue来移位是为了减少默认32bit的常数被被赋值给低数值而产生warning。
(3)完整代码与仿真
Verilog代码
//================================================================
// Filename : BaudGenerator.v
// Created On : 2017-05-16 09:44:30
// Last Modified : 2017-05-16 09:55:14
// Author : ChrisHuang
// Description :
//================================================================
module BaudGenerator
#(parameter pClkFre = 25000000,
parameter pBaud = 115200,
parameter pBaudGeneratorAccWidth = 16)(
input iClk,
input iTxdEnable,
input iRxdEnable,
output oTxdBaudTick,
output oRxdBaudTick
);
localparam pBaudGeneratorInc = ((pBaud<<(pBaudGeneratorAccWidth-4))+(pClkFre>>5))/(pClkFre>>4);
localparam pBaudGeneratorInc8x = pBaudGeneratorInc<<3;
reg [pBaudGeneratorAccWidth:0] rvBaudGeneratorAcc;
reg [pBaudGeneratorAccWidth:0] rvBaudGeneratorAcc8x;
//Txd的波特率发射脉冲
always @ (posedge iClk )
begin
if(iTxdEnable == 1'b0)
rvBaudGeneratorAcc <= 0;
else
rvBaudGeneratorAcc <= rvBaudGeneratorAcc[pBaudGeneratorAccWidth-1:0]+pBaudGeneratorInc[pBaudGeneratorAccWidth:0];
end
assign oTxdBaudTick = rvBaudGeneratorAcc[pBaudGeneratorAccWidth];
wire [pBaudGeneratorAccWidth:0] wvConstValue = 2;
//Rxd的波特率采样时钟
always @ (posedge iClk )
begin
if(iRxdEnable == 1'b0)
rvBaudGeneratorAcc8x <= wvConstValue<<(pBaudGeneratorAccWidth-2);
else
rvBaudGeneratorAcc8x <= rvBaudGeneratorAcc8x[pBaudGeneratorAccWidth-1:0]+pBaudGeneratorInc8x[pBaudGeneratorAccWidth:0];
end
assign oRxdBaudTick = rvBaudGeneratorAcc8x[pBaudGeneratorAccWidth];
endmodule
Testbench代码
//================================================================
// Filename : BaudGenerator_tb.v
// Created On : 2017-05-16 09:44:30
// Last Modified : 2017-05-16 09:55:14
// Author : ChrisHuang
// Description :
//================================================================
`timescale 1ns / 1ps
module BaudGenerator_tb();
reg rClk;
reg rEnable;
wire wClk;
wire wCLK8x;
initial
begin
rClk=0;
rEnable = 1;
rEnable = 0;
#100 rEnable = 1;
#100000 $stop;
end
always #10 rClk = ~rClk;
BaudGenerator #(50000000,115200,16) i1(
.iClk (rClk ),
.iTxdEnable (rEnable ),
.iRxdEnable (rEnable ),
.oTxdBaudTick (wClk ),
.oRxdBaudTick (wCLK8x )
);
endmodule
仿真结果
3. 数据发送
发送模块的开头如下图,除时钟和复位外,输入部分有开始发送信号、波特率脉冲,发送的数据,输出部分由TXD管脚和繁忙管脚。
单状态机处在非IDLE状态的时候,就是发送模块在发送的时刻,因此处在繁忙状态。
module Rs232Transmit(
input iClk,
input iRstN,
input iStart,
input iBaudTick,
input [7:0] ivData,
output oTxd,
output oBusy
);
//发送状态机
reg [3:0] rvState;
wire wReady = (rvState == 4'd0);
assign oBusy = ~wReady;
当开始脉冲发送来时,状态机进入开始阶段,然后再在波特率脉冲时刻,进入下一状态。由上文可知,当波特率为115200时这个间隔为8.7us左右。状态码的bit3为1时候,表示为数据状态,低3位则表示数据的次序。状态码的bit0的情况下,剩下采用独热码的状态码,即三个状态用三个bit。
case(rvState)
4'b0000:if( iStart ) rvState <= 4'b0100; //idle, 1
4'b0100:if( iBaudTick ) rvState <= 4'b1000; //start, 0
4'b1000:if( iBaudTick ) rvState <= 4'b1001; //bit0, x
4'b1001:if( iBaudTick ) rvState <= 4'b1010; //bit1, x
4'b1010:if( iBaudTick ) rvState <= 4'b1011; //bit2, x
4'b1011:if( iBaudTick ) rvState <= 4'b1100; //bit3, x
4'b1100:if( iBaudTick ) rvState <= 4'b1101; //bit4, x
4'b1101:if( iBaudTick ) rvState <= 4'b1110; //bit5, x
4'b1110:if( iBaudTick ) rvState <= 4'b1111; //bit6, x
4'b1111:if( iBaudTick ) rvState <= 4'b0001; //bit7, x
4'b0001:if( iBaudTick ) rvState <= 4'b0010; //stop1, 1
4'b0010:if( iBaudTick ) rvState <= 4'b0000; //stop2, 1
default: rvState <= 4'b0000;
endcase
当输入数据后,一个开始脉冲信号将需要发送的数据被锁存;只有当一个波特率脉冲发送来,并且状态处在数据的阶段,锁存数据的寄存器会右移一位。此处可以开出,锁存寄存器的bit0是用于发送的bit数据。
if( iStart && wReady )
begin
rvData <= ivData;
end
else if( iBaudTick && rvState[3] )
begin
rvData <= rvData>>1;
end
数据的输出,通过一个assign语句的输出。当非数据域时候,按位或的右边一定为0,所以通过rvState的数值判断发送1还是0。当发送数据域的时候,按位或左边一定为0,可以通过锁存寄存器的bit0获得发送的高低电平。
assign oTxd = (rvState<4) | (rvState[3]&&rvData[0]);
4. 接收模块
接收模块的输入有RXD、采样时钟、采用时钟使能脚,数据接收完成管脚、数据管脚。
module Rs232Receive(
input iClk,
input iRstN,
input iRxd,
input iBaudTick,
output oBaudEnable,
output oRxdDataReady,
output [7:0] ovRxdData
);
由于RXD是外部的管脚,所以需要通过两个D触发器实现时钟域同步,减小亚稳态的概率。当第1级输入为低,第2级为高,说明输入时钟有一个下降边沿。则开始采集数据。
//时钟域同步
reg [1:0] rRxdSync;
always @ (posedge iClk or negedge iRstN)
begin
if(!iRstN)
rRxdSync <= 2'b11;
else
rRxdSync <= {rRxdSync[0], iRxd};
end
//下降边沿检测
wire wRxdStart = (rRxdSync == 2'b10);
当状态机在非idle状态的时候,波特率使能管脚置位,通知波特率产生模块输出比波特率脉冲高8倍的采样时钟。
reg [3:0] rState;
assign oBaudEnable = (rState!=4'b0000); //波特率使能
采样时钟是波特率的8倍,所以在采样时钟过来是,采样一次,采用获得次数多出现的情况为bit的电平,减小数据错误的概率。
//数据过滤
reg [1:0] rvDataFilter;
always @ (posedge iClk or negedge iRstN)
begin
if(!iRstN)
begin
rvDataFilter <= 2'b11;
end
else
begin
if(iBaudTick)
begin
if(rRxdSync[1] && rvDataFilter!=2'b11)
rvDataFilter <= rvDataFilter+1'b1;
else if(~rRxdSync[1] && rvDataFilter!=2'b00)
rvDataFilter <= rvDataFilter-1'b1;
end
end
end
该部分指示除一个bit采样结束。
//数据采样个数计数
reg [2:0] rTickCnt;
always @(posedge iClk or negedge iRstN)
begin
if(!iRstN)
rTickCnt <= 3'd0;
else if(iBaudTick)
rTickCnt <= (rState==4'b0000)?1'd0:(rTickCnt + 1'b1);
end
wire wSamplingNow = (rTickCnt == 3'd7) && iBaudTick;
当状态机处在idle的状态,则一个开始信号,进入采集模式。第1个必须是起始位,如果不是低电平,则说明是误触发,回到idle状态。
always @ (posedge iClk or negedge iRstN)
begin
if(!iRstN)
rState <= 4'b0000;
else
begin
if( rState==4'b0000 && wRxdStart)
rState <= 4'b0100; //启动接收
else if(iBaudTick && wSamplingNow)
begin
case(rState)
4'b0100: if( rvDataFilter==2'b00) rState <= 4'b1000; //起始位, 确保不是扰动
else rState <=4'b0000;
4'b1000: rState <= 4'b1001; //bit0
4'b1001: rState <= 4'b1010; //bit1
4'b1010: rState <= 4'b1011; //bit2
4'b1011: rState <= 4'b1100; //bit3
4'b1100: rState <= 4'b1101; //bit4
4'b1101: rState <= 4'b1110; //bit5
4'b1110: rState <= 4'b1111; //bit6
4'b1111: rState <= 4'b0001; //bit7
4'b0001: rState <= 4'b0000; //stop bit
default: rState <= 4'b0000;
endcase
end
end
end
当处在数据阶段且有一个采集结束的脉冲,则把数据存储下来。
reg [7:0] rvRxdData;
always @ (posedge iClk or negedge iRstN)
begin
if(!iRstN)
rvRxdData = 8'd0;
else if(wSamplingNow && rState[3])
rvRxdData <= {wBitValue, rvRxdData[7:1]};
end
assign ovRxdData = rvRxdData;
当处在结束位、有一个采集脉冲、采集的是逻辑高则则通知外部模块取数据。
assign oRxdDataReady = (wSamplingNow && rState==4'b0001 && wBitValue); //确保停止位为逻辑高,ready脉冲持续时间为波特率脉冲的16倍
4. 完整代码
(1)发送器
//================================================================
// Filename : Rs232Transmit.v
// Created On : 2017-05-16 17:10:30
// Last Modified : 2017-05-16 17:55:14
// Author : ChrisHuang
// Description :
//================================================================
module Rs232Transmit(
input iClk,
input iRstN,
input iStart,
input iBaudTick,
input [7:0] ivData,
output oTxd,
output oBusy
);
//发送状态机
reg [3:0] rvState;
wire wReady = (rvState == 4'd0);
assign oBusy = ~wReady;
reg [7:0] rvData;
always @ (posedge iClk or negedge iRstN)
begin
if( !iRstN )
begin
rvData <= 8'd0;
rvState <= 4'd0;
end
else
begin
if( iStart && wReady )
begin
rvData <= ivData;
end
else if( iBaudTick && rvState[3] )
begin
rvData <= rvData>>1;
end
case(rvState)
4'b0000:if( iStart ) rvState <= 4'b0100; //idle, 1
4'b0100:if( iBaudTick ) rvState <= 4'b1000; //start, 0
4'b1000:if( iBaudTick ) rvState <= 4'b1001; //bit0, x
4'b1001:if( iBaudTick ) rvState <= 4'b1010; //bit1, x
4'b1010:if( iBaudTick ) rvState <= 4'b1011; //bit2, x
4'b1011:if( iBaudTick ) rvState <= 4'b1100; //bit3, x
4'b1100:if( iBaudTick ) rvState <= 4'b1101; //bit4, x
4'b1101:if( iBaudTick ) rvState <= 4'b1110; //bit5, x
4'b1110:if( iBaudTick ) rvState <= 4'b1111; //bit6, x
4'b1111:if( iBaudTick ) rvState <= 4'b0001; //bit7, x
4'b0001:if( iBaudTick ) rvState <= 4'b0010; //stop1, 1
4'b0010:if( iBaudTick ) rvState <= 4'b0000; //stop2, 1
default: rvState <= 4'b0000;
endcase
end
end
assign oTxd = (rvState<4) | (rvState[3]&&rvData[0]);
endmodule
(2)接收器
//================================================================
// Filename : Rs232Receive.v
// Created On : 2017-05-16 21:30:30
// Last Modified : 2017-05-16 21:55:14
// Author : ChrisHuang
// Description :
//================================================================
module Rs232Receive(
input iClk,
input iRstN,
input iRxd,
input iBaudTick,
output oBaudEnable,
output oRxdDataReady,
output [7:0] ovRxdData
);
//时钟域同步
reg [1:0] rRxdSync;
always @ (posedge iClk or negedge iRstN)
begin
if(!iRstN)
rRxdSync <= 2'b11;
else
rRxdSync <= {rRxdSync[0], iRxd};
end
//下降边沿检测
wire wRxdStart = (rRxdSync == 2'b10);
//数据过滤
reg [1:0] rvDataFilter;
always @ (posedge iClk or negedge iRstN)
begin
if(!iRstN)
begin
rvDataFilter <= 2'b11;
end
else
begin
if(iBaudTick)
begin
if(rRxdSync[1] && rvDataFilter!=2'b11)
rvDataFilter <= rvDataFilter+1'b1;
else if(~rRxdSync[1] && rvDataFilter!=2'b00)
rvDataFilter <= rvDataFilter-1'b1;
end
end
end
wire wBitValue = rvDataFilter[1];
reg [3:0] rState;
assign oBaudEnable = (rState!=4'b0000); //波特率使能
//数据采样个数计数
reg [2:0] rTickCnt;
always @(posedge iClk or negedge iRstN)
begin
if(!iRstN)
rTickCnt <= 3'd0;
else if(iBaudTick)
rTickCnt <= (rState==4'b0000)?1'd0:(rTickCnt + 1'b1);
end
wire wSamplingNow = (rTickCnt == 3'd7) && iBaudTick;
always @ (posedge iClk or negedge iRstN)
begin
if(!iRstN)
rState <= 4'b0000;
else
begin
if( rState==4'b0000 && wRxdStart)
rState <= 4'b0100; //启动接收
else if(iBaudTick && wSamplingNow)
begin
case(rState)
4'b0100: if( rvDataFilter==2'b00) rState <= 4'b1000; //起始位, 确保不是扰动
else rState <=4'b0000;
4'b1000: rState <= 4'b1001; //bit0
4'b1001: rState <= 4'b1010; //bit1
4'b1010: rState <= 4'b1011; //bit2
4'b1011: rState <= 4'b1100; //bit3
4'b1100: rState <= 4'b1101; //bit4
4'b1101: rState <= 4'b1110; //bit5
4'b1110: rState <= 4'b1111; //bit6
4'b1111: rState <= 4'b0001; //bit7
4'b0001: rState <= 4'b0000; //stop bit
default: rState <= 4'b0000;
endcase
end
end
end
reg [7:0] rvRxdData;
always @ (posedge iClk or negedge iRstN)
begin
if(!iRstN)
rvRxdData = 8'd0;
else if(wSamplingNow && rState[3])
rvRxdData <= {wBitValue, rvRxdData[7:1]};
end
assign ovRxdData = rvRxdData;
assign oRxdDataReady = (wSamplingNow && rState==4'b0001 && wBitValue); //确保停止位为逻辑高,ready脉冲持续时间为波特率脉冲的16倍
endmodule
(3)顶层文件
//================================================================
// Filename : UsartModule.v
// Created On : 2017-05-16 17:54:30
// Last Modified : 2017-05-16 17:55:14
// Author : ChrisHuang
// Description :
//================================================================
module UsartModule
#(parameter pClkFre = 50000000,
parameter pBaud = 115200,
parameter pBaudGeneratorAccWidth = 16)(
input iClk,
input iRstN,
input iTxdStart,
input [7:0] ivTxdData,
input iRxd,
output oTxd,
output oTxdBusy,
output oRxdDataReady,
output [7:0] ovRxdData
);
wire wTxdTick;
wire wRxdTick;
wire wRxdBaudEnable;
BaudGenerator #(pClkFre,pBaud,pBaudGeneratorAccWidth) BaudTick(
.iClk (iClk ),
.iTxdEnable (oTxdBusy ), //当发送时候,处于繁忙
.iRxdEnable (wRxdBaudEnable ),
.oTxdBaudTick (wTxdTick ),
.oRxdBaudTick (wRxdTick )
);
Rs232Transmit Transmit(
.iClk (iClk ),
.iRstN (iRstN ),
.iStart (iTxdStart ),
.iBaudTick (wTxdTick ),
.ivData (ivTxdData ),
.oTxd (oTxd ),
.oBusy (oTxdBusy )
);
Rs232Receive Receive(
.iClk (iClk ),
.iRstN (iRstN ),
.iRxd (iRxd ),
.iBaudTick (wRxdTick ),
.oBaudEnable (wRxdBaudEnable ),
.oRxdDataReady (oRxdDataReady ),
.ovRxdData (ovRxdData )
);
endmodule
(4)测试代码
//================================================================
// Filename : UsartModuleTxd_tb.v
// Created On : 2017-05-16 09:44:30
// Last Modified : 2017-05-16 09:55:14
// Author : ChrisHuang
// Description :
//================================================================
module UsartModuleTxd_tb();
reg rClk;
reg rRstN;
reg rTxdStart;
reg [7:0] rvTxdData;
//reg rRxd;
wire wTxd;
wire wTxdBusy;
wire wRxdDataReady;
wire [7:0] wvRxdData;
initial
begin
rClk = 0;
rRstN = 1;
#10 rRstN = 0;
#10 rRstN = 1;
#1000000 $stop;
end
always #10 rClk = ~rClk;
UsartModule i1(
.iClk (rClk ),
.iRstN (rRstN ),
.iTxdStart (rTxdStart ),
.ivTxdData (rvTxdData ),
.iRxd (wTxd),
.oTxd (wTxd ),
.oTxdBusy (wTxdBusy ),
.oRxdDataReady (wRxdDataReady ),
.ovRxdData (wvRxdData )
);
reg [1:0]rCnt;
always @ (posedge rClk or negedge rRstN)
begin
if(~rRstN)
begin
rvTxdData <= 8'h0;
rCnt <= 2'd0;
rTxdStart <= 1'b0;
end
else if(wTxdBusy == 1'b0)
begin
case( rCnt )
2'b00:rvTxdData <= 8'h55;
2'b01:rvTxdData <= 8'h5A;
2'b10:rvTxdData <= 8'hA5;
2'b11:rvTxdData <= 8'hAA;
default:rvTxdData <= 8'h55;
endcase
rTxdStart <= 1'b1;
end
else
begin
if(rTxdStart == 1'b1) //rTxdStart置位后,wTxdBusy会迟一个时钟置位
rCnt <= rCnt+1'b1;
rvTxdData <= 8'h0;
rTxdStart <= 1'b0;
end
end
endmodule