找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1622

积分

0

好友

232

主题
发表于 4 天前 | 查看: 11| 回复: 0

最近系统梳理了一些在嵌入式与硬件开发中常用的通信协议,并探讨了它们在FPGA平台上的实现方法。本文将作为上篇,重点分析UART和PS/2协议的原理与硬件设计。

UART与FPGA实现概览

1. UART协议详解与FPGA实现

UART(通用异步收发传输器)是一种广泛使用的异步串行通信接口。它实际上是RS-232、RS-449等接口标准所依赖的底层通信核心,这些标准主要规定了电气特性、连接方式等物理层内容,而UART则定义了数据帧格式与传输控制逻辑,是理解网络通信协议的基础之一。

在深入代码前,先明确几个关键概念:

  • 波特率:每秒传输的二进制位数(bps),如9600、115200。
  • 起始位:标志一帧数据开始,为一个逻辑“0”电平。
  • 数据位:实际传输的数据,长度可以是5、7或8位,传输顺序为最低位(LSB)在前。
  • 奇偶校验位:用于简单的错误检测,发送方会根据数据位中“1”的个数自动填充此位,使“1”的总数保持奇数(奇校验)或偶数(偶校验)。
  • 停止位:标志一帧数据结束,为逻辑“1”电平,长度可以是1、1.5或2位。
  • 空闲位:线路无数据传输时的状态,保持为逻辑“1”。

时序图如下:
UART时序图

数据传输过程:

  • 发送:线路空闲时为高电平。发送指令触发后,先拉低一个位时间作为起始位,随后依次发送数据位(从最低位开始)、校验位和停止位(高电平)。
  • 接收:接收端持续检测线路。一旦发现下降沿(从高到低的跳变),便启动接收流程,按照约定的波特率采样并组装数据位,最后验证校验位。

由于UART没有专用的时钟线进行同步,为保证可靠性,通常采用过采样技术。例如,用16倍于波特率的时钟对每个数据位采样16次,取中间位置的采样值作为有效值,以此来对抗噪声和时序偏差,避免误码。

1.1 Verilog设计实例:UART回环测试

下面展示一个完整的UART回环实例,包含接收、发送和波特率生成模块。

1. 接收模块 (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

2. 发送模块 (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

3. 波特率时钟生成模块 (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

4. 顶层模块 (uart_top)

module uart_top(
    input clk,
    input rxd,
    output txd
);
    wire clk_9600;
    wire receive_ack;
    wire [7:0] data;

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

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

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

UART模块连接示意图

2. PS/2协议详解与FPGA实现

PS/2是一种双向同步串行通信协议,早期广泛用于连接键盘和鼠标。其接口为6针,但核心是时钟(Clock)数据(Data) 两根双向信号线(另加VCC和GND)。通信双方通过时钟同步,任何一方可通过将时钟线拉低来抑制对方发送数据。在PC架构中,主机(PC)拥有总线控制权。

PS/2的数据帧通常包含11-12位:

  1. 1个起始位(始终为逻辑0)
  2. 8个数据位(低位在前)
  3. 1个奇偶校验位(奇校验)
  4. 1个停止位(始终为逻辑1)
  5. 1个应答位(仅出现在主机发送数据给设备时)

PS/2设备到主机的时序图如下:
PS/2时序图
当时钟信号由设备产生并出现下降沿时,主机应读取数据线上的值。

2.1 Verilog设计实例:PS/2键盘解码

以下代码实现了FPGA作为主机,读取PS/2键盘扫描码的功能。设计中加入了数字滤波以消除机械按键带来的信号抖动,这是一种常见的数字逻辑设计技巧。

module ps2_keyboard(
    input clk,
    input clr,
    input PS2C,       // ps2 clock 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 <= 8'b0;
            ps2d_filter <= 8'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.




上一篇:国内GPU产业盘点:主流AI芯片公司与国产化替代现状分析
下一篇:cURL与Wireshark实战解析:HTTP连接建立与数据传输全流程
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 17:25 , Processed in 0.154043 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表