luckfyzh

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

RS232是一种常见的串行通讯接口,一种常见的RS232通信接口是9针的接口(DB-9),如下图所示:

在RS232的针脚上,一般使用-3V-15V(有些文档说明中为-5V-15V)之间的任意电平表示逻辑1,而使用+3V+15V(有些文档说明中为5V15V)之间的任意电平表示逻辑0。图中对管脚的标号做了标注,其中pin 2是RXD,pin 3是TxD,pin 5是GND。只需要这三个端口就能完整的实现RS232的数据传输功能。稍后我们将在Vivado环境进行一个简单实现。

RS232的其余特性包括:

  • 允许采用双向全双工的通信方式(PC机可以在发射数据的同时通过该接口接收数据);
  • 可以最高以10KByte/s的速度进行数据通信。

在开始之前,首先定义清楚RS232信号线上的时序。在RS232关键的三根信号线当中并没有时钟线,相邻两个字符(一个字,一般假定为1个byte,8bits)之间需要开始和终值位进行标定,所以相邻字符的间隔完全可以是任意的,因而也可以认为这是一种异步通信方式。一般认为单个字符在进行传输的时候首先传输低位,我们进行以下约定。

  • 发射模块idle状态的时候TXD ='1';
  • 发射模块发送开始位start bit的时候TXD = '0';
  • 发射模块发送终止位stop bit的时候TXD = '1';

如果发送0x55的话发送数据线上的时序信号如下(注意发送'1'的时候信号线为低电平):

我们实现的传送速度为115200bauds,bauds(波特率)本身表示传送符号的速率,但是这里是异步通信方式,我们认为1个bit就是一个符号,包含所有数据位和起始终止符号位。传输速率为115200bauds的情况下,每个bits的持续时间为(1/115200=)8.7us。

1.波特率产生

在数字系统当中,时钟与数据速率紧密相关,时钟驱动着时序电路的数据流动,数据传输的速度与数字电路的时钟是紧密相关。在不考虑数据有效位的情况下,时钟速率与数据传输速率(bit/s)应该是相等的。前文提到,RS232的9根线当中并没有定义时钟线,那么115200 bauds的数据应该如何驱动呢。事实上,驱动时钟在数据发送端和接收端。假设接口全速运行,数据传送都是连续的,那么需要的最小时钟就是115.2kHz。如果有25MHz的启动时钟(\(clk\_freq\))的话如何产生115.2kHz的驱动时钟(\(BaudTick\))呢。

在200MHz的时钟驱动下每间隔217.014(25MHz/115.2kHz)个时钟周期就输出一个高电平(可以说是间隔217个数或218个数输出一个高电平,但是大多是间隔217个数),那么就能得到\(BaudTick\)。如果采用计数规则的话,就必须用到取模运算。为了充分利用二进制运算本身的特性,设计如下代码:

parameter ClkFrequency = 25000000; // 25MHz
parameter Baud = 115200;//115200 bauds,32bits数据精度
parameter BaudGeneratorAccWidth = 16;
parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4);//由于该parameter运算数字位整数,所以其输出一定为整数
reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc;
always @(posedge clk)
    BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc;//注意累加的时候不取最高位。

wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth];//最高位输出表示到达累加数字。

2.发射端

RS232发射端框图可以表达为

所以我们要完成的工作实际上就是并串的转换。另外,

  • 该模块要能够自动添加串行的起始终止位;
  • 该模块要能够响应TxD_start以开始数据传输(数据到来应该晚于Tx_start一个baudtick周期);
  • 在传输数据的时候,busy输出逻辑'1';

由于同时要处理数据位和数据起始和终止位,所以可以使用状态机构造该结构。

reg [3:0] state;

// the state machine starts when "TxD_start" is asserted, but advances when "BaudTick" is asserted (115200 times a second)
always @(posedge clk)
case(state)
    4'b0000: if(TxD_start) state <= 4'b0100;//idle
  4'b0100: if(BaudTick) state <= 4'b1000; // start
  4'b1000: if(BaudTick) state <= 4'b1001; // bit 0
  4'b1001: if(BaudTick) state <= 4'b1010; // bit 1
  4'b1010: if(BaudTick) state <= 4'b1011; // bit 2
  4'b1011: if(BaudTick) state <= 4'b1100; // bit 3
  4'b1100: if(BaudTick) state <= 4'b1101; // bit 4
  4'b1101: if(BaudTick) state <= 4'b1110; // bit 5
  4'b1110: if(BaudTick) state <= 4'b1111; // bit 6
  4'b1111: if(BaudTick) state <= 4'b0001; // bit 7
  4'b0001: if(BaudTick) state <= 4'b0010; // stop1
  4'b0010: if(BaudTick) state <= 4'b0000; // stop2
    default: if(BaudTick) state <= 4'b0000;//idle
endcase

数据输出端口表示为:

reg muxbit;

always @(state[2:0])
case(state[2:0])
  0: muxbit <= TxD_data[0];
  1: muxbit <= TxD_data[1];
  2: muxbit <= TxD_data[2];
  3: muxbit <= TxD_data[3];
  4: muxbit <= TxD_data[4];
  5: muxbit <= TxD_data[5];
  6: muxbit <= TxD_data[6];
  7: muxbit <= TxD_data[7];
endcase

// combine start, data, and stop bits together
//原始代码为(原始代码start状态有问题)
//assign TxD = (state<4) | (state[3] & muxbit);
assign TxD = (state<=4) | (state[3] & muxbit);

3.接收端

数据接收端的框图如下所示:

该模块要完成的功能是:

  • 串并转换,并且能够根据同步起始和终止位进行数据同步;
  • 在串并转换完成之后data_ready输出一个时钟周期(baudtick)逻辑'1',表明数据已经准备好;

由于在RS232的标准当中没有时钟线,所以采样的相位基本不可能保持一致。想象,如果采样的边沿时钟在数据跳变的边沿,这就总是会导致采样数据与发送数据不一致。因而,在接收系统当中一般都会采用过采样的机制来保证数据的可靠性。这里我们采用8倍过采样的时钟对数据进行数据采样。

另一点,数据在传输的时候可能会产生一些尖峰,直接进行采样是有一些不妥的。为了保持数据结果的可靠性,在8个过采样周期,连续的4个数据都一致,我们才输出这比较可靠的数据输出。为了不产生数据延时,我们使用两个寄存器来接收数据

reg [1:0] RxD_sync;
always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0], RxD};

再使用一个2位的计数器实现计数功能呢:

reg [1:0] RxD_cnt;
reg RxD_bit;

always @(posedge clk)
if(Baud8Tick)
begin
  if(RxD_sync[1] && RxD_cnt!=2'b11) RxD_cnt <= RxD_cnt + 1;
  else 
  if(~RxD_sync[1] && RxD_cnt!=2'b00) RxD_cnt <= RxD_cnt - 1;

  if(RxD_cnt==2'b00) RxD_bit <= 0;
  else
  if(RxD_cnt==2'b11) RxD_bit <= 1;
end

使用状态机有一个优势就是自动串行化

reg [3:0] state;

always @(posedge clk)
if(Baud8Tick)
case(state)
  4'b0000: if(~RxD_bit) state <= 4'b1000; // start bit found?
  4'b1000: if(next_bit) state <= 4'b1001; // bit 0
  4'b1001: if(next_bit) state <= 4'b1010; // bit 1
  4'b1010: if(next_bit) state <= 4'b1011; // bit 2
  4'b1011: if(next_bit) state <= 4'b1100; // bit 3
  4'b1100: if(next_bit) state <= 4'b1101; // bit 4
  4'b1101: if(next_bit) state <= 4'b1110; // bit 5
  4'b1110: if(next_bit) state <= 4'b1111; // bit 6
  4'b1111: if(next_bit) state <= 4'b0001; // bit 7
  4'b0001: if(next_bit) state <= 4'b0000; // stop bit
  default: state <= 4'b0000;
endcase

next_bit实际上标定了一种数据速率的关系,如下所示:

reg [2:0] bit_spacing;

always @(posedge clk)
if(state==0)
  bit_spacing <= 0;
else
if(Baud8Tick)
  bit_spacing <= bit_spacing + 1;

wire next_bit = (bit_spacing==7);

最后使用移位寄存机对数据进行暂存,然后定期置位data_ready信号即可

reg [7:0] RxD_data;
always @(posedge clk) if(Baud8Tick && next_bit && state[3]) RxD_data <= {RxD_bit, RxD_data[7:1]};

最后对输入输出进行封装

module serialGPIO(
    input clk,
    input RxD,
    input TxD_data_ready,
    output TxD,
	output RxD_data_ready,
    output reg [7:0] GPout,  // general purpose outputs
    input [7:0] GPin  // general purpose inputs
);

//wire RxD_data_ready;
wire [7:0] RxD_data;
async_receiver RX(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));
always @(posedge clk) if(RxD_data_ready) GPout <= RxD_data;

    async_transmitter TX(.clk(clk), .TxD(TxD), .TxD_start(TxD_data_ready), .TxD_data(GPin));
endmodule
posted on 2019-06-26 15:46  luckfyzh  阅读(1665)  评论(0编辑  收藏  举报