zoukankan      html  css  js  c++  java
  • RS232

    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
    
  • 相关阅读:
    Emgucv使用中常用函数总结
    设置 omitempty 标签忽略空值字段
    Goland 中定义实时模板
    gorm Update
    Gorm 日期格式错误
    Gorm 多张表的自动迁移
    Sql取出各科分数前三名的学生,Sql各科成绩前三的学生
    美团Leaf分布式ID Leaf安装和使用,美团Leaf snowflake雪花算法模式,美团Leaf segment号段模式
    RocketMQ可靠消息最终一致性解决方案
    Windows安装RocketMQ,RocketMQ Windows安装和使用
  • 原文地址:https://www.cnblogs.com/lafiizh/p/11090290.html
Copyright © 2011-2022 走看看