常用通信协议总结及FPGA实现(上)-FPGA常见问题社区-FPGA CPLD-ChipDebug

常用通信协议总结及FPGA实现(上)

1.UART

    UART是异步串行通信口的总称。它所包含的RS232RS449RS423等等是对应各种异步串行通信口的接口标准和总线标准。他们规定了通信口的电气特性、传输速率、连接特性和机械特性等一系列内容,实际上属于通信网络的底层概念,与通信协议没有直接关系。

    几个相关名词的解释:

    ·波特率:每秒钟传送的bit的个数。

    ·起始位:先发出一个逻辑0的信号,表示传输数据的开始。

    ·数据位:衡量通信中实际数据位的参数,标准数据位可以是5、7、8位,从最低位开始传输。

    ·奇偶校验位:UART发送时,检查发送数据中“1”的个数,自动在奇偶校验位添加1/0,用于发送数据的校验。

    ·停止位:一个数据的结束标志,可以为1位、1.5位、2位的高电平。

    ·空闲位:处于逻辑1状态,表示当前线路上无数据传输。

·时序图:

图片[1]-常用通信协议总结及FPGA实现(上)-FPGA常见问题社区-FPGA CPLD-ChipDebug

        ·发送数据过程:空闲状态,线路处于高电平,当收到发送数据指令后,拉低电平一个数据位的时间,接着数据按从低位到高位依次发送,数据发送完毕,接着发送奇偶校验位和停止位(停止位为高电平),一帧数据发送结束。

        ·接收数据过程:空闲状态,线路处于高电平,当检测到线路的下降沿,说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕,接着接收并比较奇偶校验位是否正确,如果正确则通知接收端设备准备接收数据或存入缓存。

        由于UART是异步传输,没有同步传输时钟。为保证数据传输的正确性,每个数据有16个时钟采样,取中间的采样值,以保证不会误码或滑码。

·设计实例:

 下面是一个UART的回环实例代码设计:

接收模块uart_rx:

module uart_rx(
    input rxd,
    input clk,
    output receive_ack,
    output reg [7:0] data_i
    );
    
    parameter IDLE = 0;
    parameter RECEIVE = 1;
    parameter RECEIVE_END = 2;
    
    reg [3:0] CS,NS;
    reg [4:0] count;
    reg [7:0] data_o_tmp;
    
    always@(posedge clk)
        CS <= NS;
    
    always@(*) begin
        NS <= CS;
        case(CS)
            IDLE:       if(!rxd) NS = RECEIVE;
            RECEIVE:    if(count == 7) NS = RECEIVE_END;else NS = NS;
            RECEIVE_END:NS = IDLE;
            default:    NS = IDLE;
        endcase
    end
    
    always@(posedge clk)
        if(CS == RECEIVE)
            count <= count + 1;
        else if(CS == IDLE | CS == RECEIVE_END)
            count <= 0;
    
    always @(posedge clk)
        if(CS == RECEIVE)begin
            data_i[6:0] <= data_i[7:1];
            data_i[7] <= rxd;
        end
        
    assign receive_ack = (CS == RECEIVE_END) ? 1 : 0;
             
endmodule

 

发送模块uart_tx:

module uart_tx(
    input [7:0] data_o,
    input       clk,
    input       receive_ack,
    output reg  txd
    );
    parameter IDLE          = 0;
    parameter SEND_START    = 1;
    parameter SEND_DATA     = 2;
    parameter SEND_END      = 3;
    
    reg [3:0] CS,NS;
    reg [4:0] count;
    reg [7:0] data_o_tmp;
    
    always @ (posedge clk)
        CS <= NS;
    
    always @ (*) begin
        NS <= CS;
        case(CS)
            IDLE:       begin if(receive_ack) NS = SEND_START;  end
            SEND_START: begin NS = SEND_DATA;                   end
            SEND_DATA:  begin if(count == 7) NS = SEND_END;     end
            SEND_END:   begin if(receive_ack) NS = SEND_START;  end
            default:    NS = IDLE;
        endcase
    end
    
    always @(posedge clk)
        if(CS == SEND_START)
            count <= count + 1;
        else if(CS == IDLE | CS == SEND_END)
            count <= 0;
        else
            count <= count;
    
    always @(posedge clk)
        if(CS == SEND_START)
            data_o_tmp <= data_o;
        else if(CS == SEND_DATA)
            data_o_tmp[6:0] <= data_o_tmp[7:1];
    
    always @(posedge clk)
        if(CS == SEND_START)
            txd <= 0;
        else if(CS == SEND_DATA)
            txd <= data_o_tmp;
        else if(CS == SEND_END)
            txd <= 1;        
    
endmodule
module uart_tx(
    input [7:0] data_o,
    input       clk,
    input       receive_ack,
    output reg  txd
    );
    parameter IDLE          = 0;
    parameter SEND_START    = 1;
    parameter SEND_DATA     = 2;
    parameter SEND_END      = 3;
    
    reg [3:0] CS,NS;
    reg [4:0] count;
    reg [7:0] data_o_tmp;
    
    always @ (posedge clk)
        CS <= NS;
    
    always @ (*) begin
        NS <= CS;
        case(CS)
            IDLE:       begin if(receive_ack) NS = SEND_START;  end
            SEND_START: begin NS = SEND_DATA;                   end
            SEND_DATA:  begin if(count == 7) NS = SEND_END;     end
            SEND_END:   begin if(receive_ack) NS = SEND_START;  end
            default:    NS = IDLE;
        endcase
    end
    
    always @(posedge clk)
        if(CS == SEND_START)
            count <= count + 1;
        else if(CS == IDLE | CS == SEND_END)
            count <= 0;
        else
            count <= count;
    
    always @(posedge clk)
        if(CS == SEND_START)
            data_o_tmp <= data_o;
        else if(CS == SEND_DATA)
            data_o_tmp[6:0] <= data_o_tmp[7:1];
    
    always @(posedge clk)
        if(CS == SEND_START)
            txd <= 0;
        else if(CS == SEND_DATA)
            txd <= data_o_tmp;
        else if(CS == SEND_END)
            txd <= 1;        
    
endmodule

 

特定波特率产生模块clk_div:

module clk_div(
    input clk,
    output reg clk_out
    );


    parameter baud_rata = 9600;
    parameter div_num = 'd125_000_000 /baud_rata;  //分频数等于时钟频率除以想要得到的波特率
    reg [15:0] num;


    always @(posedge clk) begin
        if(num == div_num) begin
            num <= 0;
            clk_out <= 1;
        end
        else begin
            num <= num + 1;
            clk_out <= 0;
        end
    end


endmodule

 

顶层文件uart_top:

module uart_top(
    input clk,
    input rxd,
    output txd
    );


    wire clk_9600;
    wire receive_ack;
    wire [7:0] data;


    uart_tx uart_tx
    (
        .clk        (clk_9600),
        .txd        (txd),
        .data_o     (data),
        .receive_ack(receive_ack)
    );


    uart_rx uart_rx
    (
        .clk        (clk_9600),
        .rxd        (rxd),
        .data_i     (data),
        .receive_ack(receive_ack)
    );


    clk_div clk_div
    (
        .clk        (clk),
        .clk_out    (clk_9600)
    );


endmodule

 


2.PS/2
    PS/2是一种双向同步串行通信协议。接口是一种6针的连接口,但只有四个引脚是有意义的,分别是Clock(时钟)、Data(数据)、VCC和GND。其中时钟和数据引脚是双向的。PS/2常用于连接某些输入设备,例如鼠标、键盘等。通信的两端通过时钟来同步,通过数据引脚来交换数据。任何一方想要抑制另外一方的通信,只需要将时钟引脚拉低即可。
    如果是PC和PS/2键盘之间通信,PC必须做主机,即PC可以抑制键盘发送数据,而键盘不能抑制PC发送数据。
PS/2的每一位数据帧包含11-12位,具体含义如下:
数据位名称 说明
1个起始位 总是逻辑0
8个数据位
低位在前
1个奇偶校验位 奇校验
1个停止位 总是逻辑1
1个应答位 仅用在主机对设备的通信中
·PS/2的时序图:

图片[2]-常用通信协议总结及FPGA实现(上)-FPGA常见问题社区-FPGA CPLD-ChipDebug

    由设备产生时钟和数据,主机根据时钟来读取数据。以FPGA和PS/2键盘为例,键盘产生时钟和数据,FPGA只需要读数据。当时钟下降沿时,FPGA记录数据信号。
·设计实例:
    主机为FPGA,根据PS/2的时序,得到键盘的按键值。虽然在时序图中,主机是在时钟下降沿读取数据,但实际上要为了排除噪声干扰,需要在FPGA端对信号进行滤波。下面给出设计代码。
  • module ps2_keyboard(
        input clk,
        input clr,
        input PS2C,       //ps2 clk in
        input PS2D,       //ps2 data in
        
        output [15:0] xkey
    );
    reg         PS2CF;
    reg         PS2DF;
    reg [7:0]   ps2c_filter;
    reg [7:0]   ps2d_filter;
    reg [10:0]  shift1;
    reg [10:0]  shift2;
    
    
    assign xkey = { shift2[8:1], shift1[8:1] };
    always @(posedge clk or posedge clr) begin
        if (clr) begin
            ps2c_filter <= 11'b0;
            ps2d_filter <= 11'b0;
            PS2CF <= 1;
            PS2DF <= 1;
        end
        else begin
            ps2c_filter[7] <= PS2C;
            ps2c_filter[6:0] <= ps2c_filter[7:1];
            ps2d_filter[7] <= PS2D;
            ps2d_filter[6:0] <= ps2d_filter[7:1];
            if(ps2c_filter == 8'b1111_1111)
                PS2CF <= 1;                         //去时钟毛刺
            else if(ps2c_filter == 8'b0000_0000)
                PS2CF <= 0;
            if(ps2d_filter == 8'b1111_1111)
                PS2DF <= 1;                         //去数据毛刺
            else if(ps2d_filter == 8'b0000_0000)
                PS2DF <= 0;
        end
    end
    
    
    always @(negedge PS2CF or posedge clr) begin
        if (clr) begin
            shift1 <= 11'b0;
            shift2 <= 11'b0;
        end
        else begin
            shift1 <= {PS2DF, shift1[10:1]};
            shift2 <= {shift1[0], shift2[10:1]}; 
        end
    end
    
    
    endmodule

     

参考文献:

[1]汤勇明,张圣清,陆佳华.搭建你的数字积木-数字电路与逻辑设计[M].北京:清华大学出版社,2017.

请登录后发表评论

    没有回复内容