RS232串口通信

来源:互联网 发布:星际老男孩淘宝店2016 编辑:程序博客网 时间:2024/04/30 04:08

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 )beginif(iTxdEnable == 1'b0)rvBaudGeneratorAcc <= 0;elservBaudGeneratorAcc <= rvBaudGeneratorAcc[pBaudGeneratorAccWidth-1:0]+pBaudGeneratorInc[pBaudGeneratorAccWidth:0];endassign oTxdBaudTick = rvBaudGeneratorAcc[pBaudGeneratorAccWidth];
接收时钟产生

wire [pBaudGeneratorAccWidth:0] wvConstValue = 2;//Rxd的波特率采样时钟always @ (posedge iClk )beginif(iRxdEnable == 1'b0)rvBaudGeneratorAcc8x <= wvConstValue<<(pBaudGeneratorAccWidth-2);elservBaudGeneratorAcc8x <= rvBaudGeneratorAcc8x[pBaudGeneratorAccWidth-1:0]+pBaudGeneratorInc8x[pBaudGeneratorAccWidth:0];endassign 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 )beginif(iTxdEnable == 1'b0)rvBaudGeneratorAcc <= 0;elservBaudGeneratorAcc <= rvBaudGeneratorAcc[pBaudGeneratorAccWidth-1:0]+pBaudGeneratorInc[pBaudGeneratorAccWidth:0];endassign oTxdBaudTick = rvBaudGeneratorAcc[pBaudGeneratorAccWidth];wire [pBaudGeneratorAccWidth:0] wvConstValue = 2;//Rxd的波特率采样时钟always @ (posedge iClk )beginif(iRxdEnable == 1'b0)rvBaudGeneratorAcc8x <= wvConstValue<<(pBaudGeneratorAccWidth-2);elservBaudGeneratorAcc8x <= rvBaudGeneratorAcc8x[pBaudGeneratorAccWidth-1:0]+pBaudGeneratorInc8x[pBaudGeneratorAccWidth:0];endassign 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 / 1psmodule BaudGenerator_tb();reg rClk;reg rEnable;wire wClk;wire wCLK8x;initialbeginrClk=0;rEnable = 1;rEnable = 0;#100 rEnable = 1;#100000 $stop;endalways #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, 14'b0100:if( iBaudTick )rvState <= 4'b1000;//start, 04'b1000:if( iBaudTick )rvState <= 4'b1001;//bit0, x4'b1001:if( iBaudTick )rvState <= 4'b1010;//bit1, x4'b1010:if( iBaudTick )rvState <= 4'b1011;//bit2, x4'b1011:if( iBaudTick )rvState <= 4'b1100;//bit3, x4'b1100:if( iBaudTick )rvState <= 4'b1101;//bit4, x4'b1101:if( iBaudTick )rvState <= 4'b1110;//bit5, x4'b1110:if( iBaudTick )rvState <= 4'b1111;//bit6, x4'b1111:if( iBaudTick )rvState <= 4'b0001;//bit7, x4'b0001:if( iBaudTick )rvState <= 4'b0010;//stop1, 14'b0010:if( iBaudTick )rvState <= 4'b0000;//stop2, 1default:rvState <= 4'b0000;endcase
当输入数据后,一个开始脉冲信号将需要发送的数据被锁存;只有当一个波特率脉冲发送来,并且状态处在数据的阶段,锁存数据的寄存器会右移一位。此处可以开出,锁存寄存器的bit0是用于发送的bit数据。
if( iStart && wReady )beginrvData <= ivData;endelse if( iBaudTick && rvState[3] )beginrvData <= 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)beginif(!iRstN)rRxdSync <= 2'b11;elserRxdSync <= {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)beginif(!iRstN)beginrvDataFilter <= 2'b11;endelsebeginif(iBaudTick)beginif(rRxdSync[1] && rvDataFilter!=2'b11)rvDataFilter <= rvDataFilter+1'b1;else if(~rRxdSync[1] && rvDataFilter!=2'b00)rvDataFilter <= rvDataFilter-1'b1;endendend
该部分指示除一个bit采样结束。
//数据采样个数计数reg [2:0] rTickCnt;always @(posedge iClk or negedge iRstN)beginif(!iRstN)rTickCnt <= 3'd0;else if(iBaudTick)rTickCnt <= (rState==4'b0000)?1'd0:(rTickCnt + 1'b1);endwire wSamplingNow = (rTickCnt == 3'd7) && iBaudTick;
当状态机处在idle的状态,则一个开始信号,进入采集模式。第1个必须是起始位,如果不是低电平,则说明是误触发,回到idle状态。
always @ (posedge iClk or negedge iRstN)beginif(!iRstN)rState <= 4'b0000;elsebeginif( rState==4'b0000 && wRxdStart)rState <= 4'b0100;//启动接收else if(iBaudTick && wSamplingNow)begincase(rState)4'b0100: if( rvDataFilter==2'b00) rState <= 4'b1000;//起始位, 确保不是扰动else rState <=4'b0000;4'b1000: rState <= 4'b1001;//bit04'b1001: rState <= 4'b1010;//bit14'b1010: rState <= 4'b1011;//bit24'b1011: rState <= 4'b1100;//bit34'b1100: rState <= 4'b1101;//bit44'b1101: rState <= 4'b1110;//bit54'b1110: rState <= 4'b1111;//bit64'b1111: rState <= 4'b0001;//bit74'b0001: rState <= 4'b0000;//stop bitdefault: rState <= 4'b0000;endcaseendendend
当处在数据阶段且有一个采集结束的脉冲,则把数据存储下来。
reg [7:0] rvRxdData;always @ (posedge iClk or negedge iRstN)beginif(!iRstN)rvRxdData = 8'd0;else if(wSamplingNow && rState[3])rvRxdData <= {wBitValue, rvRxdData[7:1]};endassign 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)beginif( !iRstN )beginrvData <= 8'd0;rvState <= 4'd0;endelsebeginif( iStart && wReady )beginrvData <= ivData;endelse if( iBaudTick && rvState[3] )beginrvData <= rvData>>1;endcase(rvState)4'b0000:if( iStart )rvState <= 4'b0100;//idle, 14'b0100:if( iBaudTick )rvState <= 4'b1000;//start, 04'b1000:if( iBaudTick )rvState <= 4'b1001;//bit0, x4'b1001:if( iBaudTick )rvState <= 4'b1010;//bit1, x4'b1010:if( iBaudTick )rvState <= 4'b1011;//bit2, x4'b1011:if( iBaudTick )rvState <= 4'b1100;//bit3, x4'b1100:if( iBaudTick )rvState <= 4'b1101;//bit4, x4'b1101:if( iBaudTick )rvState <= 4'b1110;//bit5, x4'b1110:if( iBaudTick )rvState <= 4'b1111;//bit6, x4'b1111:if( iBaudTick )rvState <= 4'b0001;//bit7, x4'b0001:if( iBaudTick )rvState <= 4'b0010;//stop1, 14'b0010:if( iBaudTick )rvState <= 4'b0000;//stop2, 1default:rvState <= 4'b0000;endcaseendendassign 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)beginif(!iRstN)rRxdSync <= 2'b11;elserRxdSync <= {rRxdSync[0], iRxd};end//下降边沿检测wire wRxdStart = (rRxdSync == 2'b10);//数据过滤reg [1:0] rvDataFilter;always @ (posedge iClk or negedge iRstN)beginif(!iRstN)beginrvDataFilter <= 2'b11;endelsebeginif(iBaudTick)beginif(rRxdSync[1] && rvDataFilter!=2'b11)rvDataFilter <= rvDataFilter+1'b1;else if(~rRxdSync[1] && rvDataFilter!=2'b00)rvDataFilter <= rvDataFilter-1'b1;endendendwire wBitValue = rvDataFilter[1];reg [3:0] rState;assign oBaudEnable = (rState!=4'b0000);//波特率使能//数据采样个数计数reg [2:0] rTickCnt;always @(posedge iClk or negedge iRstN)beginif(!iRstN)rTickCnt <= 3'd0;else if(iBaudTick)rTickCnt <= (rState==4'b0000)?1'd0:(rTickCnt + 1'b1);endwire wSamplingNow = (rTickCnt == 3'd7) && iBaudTick;always @ (posedge iClk or negedge iRstN)beginif(!iRstN)rState <= 4'b0000;elsebeginif( rState==4'b0000 && wRxdStart)rState <= 4'b0100;//启动接收else if(iBaudTick && wSamplingNow)begincase(rState)4'b0100: if( rvDataFilter==2'b00) rState <= 4'b1000;//起始位, 确保不是扰动else rState <=4'b0000;4'b1000: rState <= 4'b1001;//bit04'b1001: rState <= 4'b1010;//bit14'b1010: rState <= 4'b1011;//bit24'b1011: rState <= 4'b1100;//bit34'b1100: rState <= 4'b1101;//bit44'b1101: rState <= 4'b1110;//bit54'b1110: rState <= 4'b1111;//bit64'b1111: rState <= 4'b0001;//bit74'b0001: rState <= 4'b0000;//stop bitdefault: rState <= 4'b0000;endcaseendendendreg [7:0] rvRxdData;always @ (posedge iClk or negedge iRstN)beginif(!iRstN)rvRxdData = 8'd0;else if(wSamplingNow && rState[3])rvRxdData <= {wBitValue, rvRxdData[7:1]};endassign 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;initialbeginrClk = 0;rRstN = 1;#10 rRstN = 0;#10 rRstN = 1;#1000000 $stop;endalways #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)beginif(~rRstN)beginrvTxdData <= 8'h0;rCnt <= 2'd0;rTxdStart <= 1'b0;endelse if(wTxdBusy == 1'b0)begincase( 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;endcaserTxdStart <= 1'b1;endelsebeginif(rTxdStart == 1'b1)//rTxdStart置位后,wTxdBusy会迟一个时钟置位rCnt <= rCnt+1'b1;rvTxdData <= 8'h0;rTxdStart <= 1'b0;endendendmodule
(5)测试结果


原创粉丝点击