zoukankan      html  css  js  c++  java
  • 单字节读写I2C的verilog实现

    本设计用verilog实现了一个简单的I2C协议,实现功能为往固定地址先写入一个字节,然后再读出该字节。

    涉及到的EEPROM为Atmel家的AT24C04,4Kbit存储空间,8位位宽,需要9位宽的地址,其他细节参见规格书doc0180。

    AT24C04支持5种读写模式:字节写,页写,当前地址读,随机读,顺序读。在当前地址读或顺序读时序的最后不发送NAK,则可以继续下一个地址的数据,直到主控给出一个NAK。

    另外需要注意一点是,AT24C04需要至多5ms的写时间,即在写操作的结束时序到下一个操作的开始时许之间需要间隔5ms,以保证器件对写入数据的固化。

    简化起见,本设计使用了一个1s的从开始写到开始读的时间间隔。

    接口程序

    `timescale 1ns/1ps
    
    // AT24C04接口程序,只支持字节写入和随机读取
    // 本接口接收指定地坿的单字节写入和单字节读取
    module i2c_intf #(
        parameter SYS_FREQ = 200_000_000,
        parameter SCL_FREQ = 100_000
        )(
        input wire clk, nrst,
        // 写信号
        input wire wrreq,
        input wire [8:0] waddr, 
        input wire [7:0] wdata,
        input wire rdreq,
        // 读信号
        input wire [8:0] raddr,
        output reg [7:0] rdata,
        output reg vld,
        // 忙线指示信号
        output reg rdy,
        // i2c接口信号
        output reg scl,
        inout sda
        );
        
        reg sda_out;
        // 用于观察信号
        wire sda_in;
        
        assign sda = (sda_out == 0) ? 1'b0 : 1'bz;
        assign sda_in = sda;
        // I2C的SCL周期
        localparam SCL_T = SYS_FREQ / SCL_FREQ;
    
        // 由于AT24C04为4K容量,数据长度8位,需要9bit地址,最高位存于器件地址中
        localparam DADDR_6 = 6'b101000;
        
        reg [7:0] device_addr;
        
        // SCL计数
        reg [15:0] cnt_scl;
        wire add_cnt_scl;
        wire end_cnt_scl;
        // bit计数
        reg [3:0] cnt_bit;
        wire add_cnt_bit;
        wire end_cnt_bit;
        // cnt计数状态内执行顺序
        reg [3:0] cnt_step;
        wire add_cnt_step;
        wire end_cnt_step;
        // 变量对应不同状态需要执行的步骤数
        reg [3:0] bit_num, step_num;
            
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                device_addr <= 0;
            else if(state_c == S_WR_BYTE && cnt_step == 2 - 1 || state_c == S_RD_RANDOM && cnt_step == 2 - 1)
                device_addr <= {DADDR_6, waddr[8], 1'b0};
            else if(state_c == S_RD_RANDOM && cnt_step == 5 - 1)
                device_addr <= {DADDR_6, raddr[8], 1'b1};
        end
    
        // 状态划分为:空闲,写字节,随机写
        localparam S_IDLE         = 6'b000_001;
        localparam S_WR_BYTE     = 6'b000_010;
        localparam S_RD_RANDOM    = 6'b000_100;
        
        reg [5:0] state_c, state_n;
        
        wire idle2wr_byte;
        wire idle2rd_random;
        wire wr_byte2idle;
        wire rd_random2idle;
        
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                state_c <= S_IDLE;
            else
                state_c <= state_n;
        end
        
        always @* begin
            case (state_c)
                S_IDLE: begin
                        if(idle2wr_byte)
                            state_n = S_WR_BYTE;
                        else if(idle2rd_random)
                            state_n = S_RD_RANDOM;
                        else
                            state_n = state_c;
                    end
                S_WR_BYTE: begin
                        if(wr_byte2idle)
                            state_n = S_IDLE;
                        else
                            state_n = state_c;
                    end
                S_RD_RANDOM: begin
                        if(rd_random2idle)
                            state_n = S_IDLE;
                        else
                            state_n = state_c;
                    end
                default: state_n = state_c;
            endcase
        end
        
        assign idle2wr_byte        = state_c == S_IDLE     && wrreq;
        assign idle2rd_random    = state_c == S_IDLE        && rdreq;
        assign wr_byte2idle        = state_c == S_WR_BYTE    && end_cnt_step;
        assign rd_random2idle    = state_c == S_RD_RANDOM && end_cnt_step;
        
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                cnt_scl <= 0;
            else if(add_cnt_scl) begin
                if(end_cnt_scl)
                    cnt_scl <= 0;
                else
                    cnt_scl <= cnt_scl + 1'b1;
            end
        end
        assign add_cnt_scl = state_c != S_IDLE;
        assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_T - 1;
            
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                cnt_bit <= 0;
            else if(add_cnt_bit) begin
                if(end_cnt_bit)
                    cnt_bit <= 0;
                else
                    cnt_bit <= cnt_bit + 1'b1;
            end
        end
        assign add_cnt_bit = end_cnt_scl;
        assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_num - 1;
        // 状态内以开始,读,写或结束为一个步骤
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                cnt_step <= 0;
            else if(add_cnt_step) begin
                if(end_cnt_step)
                    cnt_step <= 0;
                else
                    cnt_step <= cnt_step + 1'b1;
            end
        end
        assign add_cnt_step = end_cnt_bit;
        assign end_cnt_step = add_cnt_step && cnt_step == step_num - 1;
    
        // 写单个字节分为以下步骤:开始,写器件地址,写存储地址,写数据,结束
        // 读单个字节分为以下步骤:开始,写器件地址,写存储地址,开始时序,写器件地址,读数据,结束
        always @* begin
            if(state_c == S_IDLE) begin
                step_num = 0;
                bit_num = 0;
            end
            else if(state_c == S_WR_BYTE) begin
                step_num = 5;
                if(cnt_step == 1 - 1 || cnt_step == step_num - 1)
                    bit_num = 1;
                else
                    bit_num = 9;
            end
            else if(state_c == S_RD_RANDOM) begin
                step_num = 7;
                if(cnt_step == 1 - 1 || cnt_step == 4 - 1 || cnt_step == step_num - 1)
                    bit_num = 1;
                else
                    bit_num = 9;
            end
            else begin
              step_num = 0;
              bit_num = 0;
            end
        end
        
        // scl信号前半个周期为低,后半个周期为高,且第1个半个周期保持高电平
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                scl <= 1;
            else if(add_cnt_scl && cnt_scl == SCL_T / 2 - 1)
                scl <= 1;
            else if(end_cnt_scl && !end_cnt_step)
                scl <= 0;
        end
        
        // 重点,SDA信号
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                sda_out <= 1;
            else begin
                // 开始时序
                if((cnt_step == 1 - 1 || state_c == S_RD_RANDOM && cnt_step == 4 - 1) && cnt_scl == SCL_T * 3 / 4 - 1)
                    sda_out <= 0;
                // 结束时序,且在结束时钟周期需要需要事先将SDA拉低
                else if(cnt_step == step_num - 1 && cnt_scl == SCL_T / 4 - 1)
                    sda_out <= 0;
                else if(cnt_step == step_num - 1 && cnt_scl == SCL_T * 3 / 4 - 1)
                    sda_out <= 1;
                // 在应答周期将SDA拉高
                else if(cnt_bit == 9 - 1 && cnt_scl == SCL_T / 4 - 1)
                    sda_out <= 1;
                // 器件地址
                else if((state_c == S_WR_BYTE && cnt_step == 2 - 1 || state_c == S_RD_RANDOM && (cnt_step == 2 - 1 || cnt_step == 5 - 1)) && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                    sda_out <= device_addr[7 - cnt_bit];
                // 写地址
                else if(state_c == S_WR_BYTE && cnt_step == 3 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                    sda_out <= waddr[7 - cnt_bit];
                // 写数据
                else if(state_c == S_WR_BYTE && cnt_step == 4 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                    sda_out <= wdata[7 - cnt_bit];
                // 读地址
                else if(state_c == S_RD_RANDOM && cnt_step == 3 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                    sda_out <= raddr[7 - cnt_bit];
            end
        end
    
        // 读数据代码
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                rdata <= 0;
            else if(state_c == S_RD_RANDOM && cnt_step == 6 - 1 && cnt_scl == SCL_T / 4 * 3 - 1 && cnt_bit != 9 - 1)
                rdata[7 - cnt_bit] <= sda;
        end
        
        // 读数据有效指示信号
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                vld <= 0;
            else if(state_c == S_RD_RANDOM && end_cnt_step)
                vld <= 1;
            else
                vld <= 0;
        end
        
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                rdy <= 1;
            else if(state_c == S_IDLE)
                rdy <= 1;
            else
                rdy <= 0;
        end
        
        // ILA例化代码
        ila_read ila_read_u (
        .clk    (clk    ), // input wire clk
        .probe0 (vld    ), // input wire [7:0] probe0
        .probe1 (scl    ),
        .probe2 (sda_out),
        .probe3 (cnt_step),
        .probe4 (cnt_bit),
        .probe5 (rdata  ),
        .probe6 (wrreq  ),
        .probe7 (rdreq  ),
        .probe8 (sda_in )
        ); 
    endmodule

    顶层测试例程

    `timescale 1ns / 1ps
    
    module i2c_example(
        input wire clk_p, clk_n, nrst,
        input wire key_in,
        output wire scl,
        inout sda    
        );
        
        parameter SYS_FREQ = 200_000_000;
        parameter SCL_FREQ = 100_000;
        
        // 差分时钟信号转为单端信号
        IBUFGDS #(
            .DIFF_TERM("FALSE"),
            .IBUF_LOW_PWR("TRUE"),
            .IOSTANDARD("DEFAULT")
        )  IBUFGDS_inst(
            .O(clk),
            .I(clk_p),
            .IB(clk_n)
        );
        
        reg [31:0] cnt;
        wire add_cnt;
        wire end_cnt;
        reg flag;
        wire [7:0] data;
        wire vld;
        wire wrreq;
        
        // 加入按键模块用于调试
        wire key_out;
        reg key_out_ff;
        
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                key_out_ff <= 0;
            else
                key_out_ff <= key_out;
        end
        
        assign wrreq = key_out == 0 && key_out_ff == 1;
        
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                flag <= 0;
            else if(wrreq)
                flag <= 1;
            else if(end_cnt)
                flag <= 0;
        end
               
        always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                cnt <= 0;
            else if(add_cnt) begin
                if(end_cnt)
                    cnt <= 0;
                else
                    cnt <= cnt + 1'b1;
            end
        end
        assign add_cnt = flag;
        assign end_cnt = add_cnt && cnt == SYS_FREQ - 1;
        
        i2c_intf #(SYS_FREQ, SCL_FREQ) i2c_intf_u(
            .clk     (clk    ), 
            .nrst    (nrst   ),
            .wrreq   (wrreq  ),
            .waddr   (9'h03  ), 
            .wdata   (8'h55  ),
            
            .rdreq   (end_cnt),
            .raddr   (9'h03  ),
            .rdata   (data   ),
            .vld     (vld    ),
            
            .rdy     (       ),
            
            .scl     (scl    ),
            .sda     (sda    )
            );
        
        debounce debounce_u(
            .clk    (clk    ), 
            .nrst   (nrst   ),
            .key_in (key_in ),
            .key_out(key_out)
        );               
    endmodule
  • 相关阅读:
    mysql锁 实战测试代码
    Memcache教程 Memcache零基础教程
    Moneybookers API支付方式开发 步骤
    dede判断当前文章
    Windows下的Memcache安装 linux下的Memcache安装
    Ecshop:后台添加新功能栏目以及管理权限设置
    解决echsop兼容jquery(transport.js的冲突)的问题
    PHP AJAX JSONP实现跨域请求使用实例
    shell基础 -- 基本语法
    神器之strace
  • 原文地址:https://www.cnblogs.com/qingkai/p/7743488.html
Copyright © 2011-2022 走看看