【FPGA】FPGA实现IIC协议读写EEPROM(一) ----- IIC接口驱动实现

写在前面
FPGA实现IIC协议读写EEPROM相关文章:

IIC通信协议
【FPGA】FPGA实现IIC协议读写EEPROM(一) ----- IIC接口驱动实现
【FPGA】FPGA实现IIC协议读写EEPROM(二) -----EEPROM读写控制模块实现
【FPGA】FPGA实现IIC协议读写EEPROM(三) ----- 汇总篇

在上篇文章中已经对IIC协议进行了详细介绍,本文介绍IIC读写接口驱动模块的实现。

一、功能分析

IIC接口驱动模块功能是按照IIC协议时序将数据写入EEPROM或者从EEPROM读取数据。根据上一篇IIC协议文章介绍我们不难分析出通过IIC协议单字节读写EEPROM的过程。
1、单字节写操作
(1)SDA总线拉低发送起始信号,开始传输数据。
(2)发送写控制字(4位器件地址+3位片选位+写标志0),接收从机应答ACK。
(3)发送寄存器地址,接收从机应答ACK。
(4)发送数据字节,接收从机应答ACK。
(5)SDA总线拉高,发送停止位,数据传输结束。
在这里插入图片描述

2、单字节读操作(当前地址读)
(1)SDA总线拉低发送起始信号,开始传输数据。
(2)发送读控制字(4位器件地址+3位片选位+读标志1),接收从机应答ACK。
(3)接收读回的单字节数据,发送非应答信号NACK。
(4)SDA总线拉高,发送停止位,数据传输结束。
在这里插入图片描述

3、随机读
(1)SDA总线拉低发送起始信号,开始传输数据。
(2)虚写:发送写控制字(4位器件地址+3位片选位+写标志0),接收从机应答ACK。
(3)发送寄存器地址,接收从机应答ACK。
(4)SDA总线拉低发送起始信号,开始传输数据。
(5)实际读操作:发送读控制字(4位器件地址+3位片选位+读标志1),接收从机应答ACK。
(6)接收读回的单字节数据,发送非应答信号NACK。
(7)SDA总线拉高,发送停止位,数据传输结束。
在这里插入图片描述

4、顺序读
顺序读操作是在随机读的操作之后进行的,当收到读回的数据时,主机发送应答信号ACK给从机,表示正确接收到了该字节数据,然后进行下一字节数据的接收、发送ACK,当不需要接收数据时,发送非应答信号NACK,SDA总线拉高,发送停止位,数据传输结束。
在这里插入图片描述

二、输入/输出信号

IIC接口驱动模块如下:
在这里插入图片描述

信号说明:

信号I/O类型说明
clkinput50MHz系统时钟
rdout[7:0]ouput读回的数据
rdout_vldouput读回的数据有效标志
rw_doneouput读写一字节数据完成标志
sclouputIIC时钟总线,最大400KHz,本工程使用200KHz
sdainoutIIC双向数据总线
reqinput读写请求
cmd[3:0]input传输操作控制命令组合(写、读、起始、停止、应答、非应答)
wr_din[7:0]input发送一字节数据

三、IIC接口驱动状态机

IIC接口驱动模块状态转移图如下:
在这里插入图片描述
状态说明:
IDLE:空闲状态,等待读写请求。
START:发送起始位。当控制命令cmd[5:0]中含有起始命令时,进行该状态发送起始位。
WR_DATA:写数据状态。在写数据状态期间,将wr_din[7:0]按照IIC协议时序写入EEPROM。
RD_DATA:读数据状态。在读数据状态期间,按照IIC协议时序从EEPROM中读取数据。
REC_ACK:接收从机发送的ACK应答信号。
SEND_ACK:发送ACK或者NACK信号。
STOP:发送停止位。当控制命令cmd[5:0]中含有停止命令时,进行该状态发送停止位。

四、IIC接口驱动实现

i2c_interface接口驱动verilog代码如下:

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.13                        
//  Design Name: i2c_eeprom    
//  Module Name: i2c_interface         
//  Target Device: Cyclone IV E (EP4CE6F17C8)             
//  Tool versions: Quartus Prime 18.1             
//  Description: I2C接口驱动模块
//  **************************************************************
`include "param.v"

module i2c_interface (
    input               clk                 ,
    input               rst_n               ,

    //  eeprom_ctrl
    input               req                 ,       //  读写请求
    input   [7:0]       wr_din              ,       //  需要发送的一字节数据
    input   [3:0]       cmd                 ,       //  控制命令组合
    output  [7:0]       rdout               ,       //  读取的数据
    output              rdout_vld           ,       //  读取数据有效标志
    output              rw_done             ,       //  读写一字节完成标志
    //  EEPROM
    input               sda_in              ,
    output              sda_out             ,
    output              sda_out_en          ,
    output              scl
);
    //  参数定义
    localparam  IDLE    =   7'b000_0001 ,
                START   =   7'b000_0010 ,
                WR_DATA =   7'b000_0100 ,
                RD_DATA =   7'b000_1000 ,
                REC_ACK =   7'b001_0000 ,
                SEND_ACK=   7'b010_0000 ,
                STOP    =   7'b100_0000 ;

    //  信号定义
    reg     [6:0]       state_c         ;
    reg     [6:0]       state_n         ;

    reg     [7:0]       cnt_scl         ;       //  scl周期计数器
    wire                add_cnt_scl     ;
    wire                end_cnt_scl     ;

    reg     [3:0]       bit_num         ;       //  bit数
    reg     [3:0]       cnt_bit         ;       //  bit计数器
    wire                add_cnt_bit     ;
    wire                end_cnt_bit     ;

    reg     [7:0]       dout_data       ;       //  发送数据寄存
    reg     [3:0]       command         ;       //  控制命令寄存

    reg                 scl_dout         ;       //  SDA寄存     
    reg                 sda_dout        ;
    reg                 sda_dout_en     ;

    reg     [7:0]       rx_data         ;       //  接收读回的数据
    reg                 ack_flag        ;       //  ack响应标志


    //  状态转移条件
    wire                idle2start      ;
    wire                idle2wrdata     ;
    wire                idle2rddata     ;
    wire                start2wrdata    ;
    wire                start2rddata    ;
    wire                wrdata2recack   ;
    wire                rddata2sendack  ;
    wire                recack2idle     ;
    wire                recack2stop     ;
    wire                sendack2idle    ;
    wire                sendack2stop    ;
    wire                stop2idle       ;

    //  状态机
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            state_c <= IDLE ;
        end
        else begin
            state_c <= state_n ;
        end
    end

    always @(*)begin
        case (state_c)
            IDLE: begin
                if(idle2start)
                    state_n = START ;
                else if(idle2wrdata)
                    state_n = WR_DATA ;
                else if(idle2rddata)
                    state_n = RD_DATA ;
                else
                    state_n = state_c ;
            end
            START: begin
                if(start2wrdata)
                    state_n = WR_DATA ;
                else if(start2rddata)
                    state_n = RD_DATA ;
                else
                    state_n = state_c ;
            end
            WR_DATA: begin
                if(wrdata2recack)
                    state_n = REC_ACK ;
                else
                    state_n = state_c ;
            end
            RD_DATA: begin
                if(rddata2sendack)
                    state_n = SEND_ACK ;
                else
                    state_n = state_c ;
            end
            REC_ACK: begin
                if(recack2idle)
                    state_n = IDLE ;
                else if(recack2stop)
                    state_n = STOP ;
                else
                    state_n = state_c ;
            end
            REC_ACK: begin
                if(sendack2idle)
                    state_n = IDLE ;
                else if(sendack2stop)
                    state_n = STOP ;
                else
                    state_n = state_c ;
            end
            STOP: begin
                if(stop2idle)
                    state_n = IDLE ;
                else
                    state_n = state_c ;
            end
            default: state_n = IDLE ;
        endcase
    end

    //  状态转移条件
    assign  idle2start      = state_c == IDLE       && req && (cmd & `STA)                      ;
    assign  idle2wrdata     = state_c == IDLE       && req && (cmd & `WRITE)                    ;
    assign  idle2rddata     = state_c == IDLE       && req && (cmd & `READ)                     ;
    assign  start2wrdata    = state_c == START      && end_cnt_bit && (command & `WRITE)        ;   
    assign  start2rddata    = state_c == START      && end_cnt_bit && (command & `READ)         ;   
    assign  wrdata2recack   = state_c == WR_DATA    && end_cnt_bit                              ;
    assign  rddata2sendack  = state_c == RD_DATA    && end_cnt_bit                              ;
    assign  recack2idle     = state_c == REC_ACK    && end_cnt_bit && (command & `STO) == 0     ;
    assign  recack2stop     = state_c == REC_ACK    && end_cnt_bit && (command & `STO)          ;
    assign  sendack2idle    = state_c == SEND_ACK   && end_cnt_bit && (command & `STO) == 0     ;
    assign  sendack2stop    = state_c == SEND_ACK   && end_cnt_bit && (command & `STO)          ;
    assign  stop2idle       = state_c == STOP       && end_cnt_bit                              ;

    //  cnt_scl  200KHz  一个SCL周期为250个系统时钟周期
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_scl <= 0 ;
        end
        else if(add_cnt_scl)begin
            if(end_cnt_scl)begin
                cnt_scl <= 0 ;
            end				
            else begin	    
                cnt_scl <= cnt_scl + 1 ;
            end 		    
        end
    end                 
    assign	add_cnt_scl	= state_c != IDLE ;
    assign	end_cnt_scl	= add_cnt_scl && (cnt_scl == `SCL_PERIOD - 1) ;

    //  bit_num
    always @(*)begin
        if(state_c == RD_DATA || state_c == WR_DATA)begin
            bit_num = 8 ;
        end
        else begin
            bit_num = 1 ;
        end
    end

    //  cnt_bit
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_bit <= 0 ;
        end
        else if(add_cnt_bit)begin
            if(end_cnt_bit)begin
                cnt_bit <= 0 ;
            end				
            else begin	    
                cnt_bit <= cnt_bit + 1 ;
            end 		    
        end
    end           
    assign	add_cnt_bit	= end_cnt_scl ;
    assign	end_cnt_bit	= add_cnt_bit && (cnt_bit == bit_num - 1) ;

    //  dout_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            dout_data <= 0 ;
        end
        else if(req)begin
            dout_data <= wr_din ;
        end
    end

    //  command
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            command <= 0 ;
        end
        else if(req)begin
            command <= cmd ;
        end
    end

    //  scl_dout
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            scl_dout <= 1'b1 ;
        end
        else if(idle2start | idle2wrdata | idle2rddata)begin
            scl_dout <= 1'b0 ;
        end
        else if(add_cnt_scl && cnt_scl == `SCL_HALF)begin
            scl_dout <= 1'b1 ;
        end
        else if(end_cnt_scl && ~stop2idle)begin
            scl_dout <= 1'b0 ;
        end
    end

    //  sda_dout_en
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            sda_dout_en <= 1'b0 ;
        end
        else if(idle2start || idle2wrdata || rddata2sendack || sendack2stop || recack2stop)begin
            sda_dout_en <= 1'b1 ;
        end
        else if(idle2rddata || start2rddata || wrdata2recack || stop2idle)begin
            sda_dout_en <= 1'b0 ;
        end
    end

    //  sda_dout
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            sda_dout <= 1'b1 ;
        end
        else if(state_c == START)begin
            if(cnt_scl == `HIGH_HALF)begin
                sda_dout <= 1'b0 ;
            end
            else if(cnt_scl == `LOW_HALF)begin
                sda_dout <= 1'b1 ;
            end
        end
        else if(state_c == WR_DATA && cnt_scl == `LOW_HALF)begin
            sda_dout <= dout_data[7 - cnt_bit] ;
        end
        else if(state_c == SEND_ACK && cnt_scl == `LOW_HALF)begin
            sda_dout <= (command & `STO) ? 1'b1 : 1'b0 ;
        end
        else if(state_c == STOP)begin
            if(cnt_scl == `LOW_HALF)begin       
                sda_dout <= 1'b0;
            end
            else if(cnt_scl == `HIGH_HALF)begin    
                sda_dout <= 1'b1;               
            end 
        end
        else if(wrdata2recack | rddata2sendack)begin
            sda_dout <= 1'b1;  
        end
    end
    //  rx_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rx_data <= 0 ;
        end
        else if(state_c == RD_DATA && cnt_scl == `HIGH_HALF)begin
            rx_data[7 - cnt_bit] <= sda_in ;
        end
    end

    //  ack_flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            ack_flag <= 1'b0 ;
        end
        else if(state_c == REC_ACK && cnt_scl == `HIGH_HALF)begin
            ack_flag <= ~sda_in ;
        end
        else begin
            ack_flag <= 1'b0 ;
        end
    end

    //  输出
    assign rdout = rx_data ;   
    assign rdout_vld = rddata2sendack ;
    assign rw_done = stop2idle | sendack2idle | recack2idle ;
    assign sda_out = sda_dout ; 
    assign sda_out_en = sda_dout_en ;
    assign scl = scl_dout ;
    
endmodule     

param.v参数:

//  IIC时钟参数
`define  SCL_PERIOD  250
`define  SCL_HALF    125
`define  LOW_HALF    65 
`define  HIGH_HALF   190

//  控制命令
`define  WRITE    4'b1000     //  写
`define  READ     4'b0100     //  读
`define  STA      4'b0010     //  起始位
`define  STO      4'b0001     //  停止位

五、仿真测试

写数据:
在这里插入图片描述
读数据:
在这里插入图片描述
注意:
1. 这里仿真使用的是24LC04B的仿真模型,读数据的时候只能读0,读1显示的是高阻态。
2. 写周期是5ms,写完数据之后需要延时一段时间才能发送读请求,否则无法读回数据,这里我仿真延时300000个时钟周期。

### FPGA 驱动 ACS712 电流传感器 #### 硬件连接 为了使FPGA能够成功读取来自ACS712电流传感器的数据,硬件连接至关重要。典型的连接方式如下: - VCC:连接至电源正极(通常为5V) - GND:接地端子 - OUT:模拟输出引脚应接至FPGA的ADC输入通道;然而需要注意的是,大多数FPGA并不具备内置的模数转换功能,因此可能需要额外配置个外部ADC模块来处理这任务。 由于Xilinx 7系列FPGA本身不具备集成的ADC单元[^1],所以在实际应用中往往通过SPI/IIC接口外扩AD芯片实现对模拟量采集的支持。 #### 软件设计与示例代码 考虑到上述情况,在编写程序时需先完成对外部ADC设备的操作流程定义。下面给出段基于Verilog HDL编写的简单例子,假设已经有个工作正常的SPI ADC作为中介来获取ACS712输出电压值并将其传输给FPGA进行进步处理。 ```verilog module acs712_read ( input wire clk, // 主时钟信号 output reg [9:0] data_out,// 输出数据位宽取决于所使用的ADC精度 inout wire spi_miso, output wire spi_mosi, output wire spi_sck, output wire spi_cs_n // SPI片选信号 ); // 定义状态机变量和其他控制逻辑... always @(posedge clk) begin case(state) IDLE : if(start_conversion)...; CONVERSION : ... ; READ_DATA : ... endcase end // 实现具体的SPI通信协议细节... endmodule ``` 这段伪代码展示了如何构建个基本的状态机框架去管理整个测量过程中的不同阶段,包括启动次新的转换请求、等待转换结束以及最终从ADC读回结果等操作。对于特定型号的ADC器件,则还需要参照其官方文档补充完整的SPI交互指令集。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值