FPGA模块——IIC协议(FPGA做主机操作24C64)

编程入门 行业动态 更新时间:2024-10-10 17:24:23

FPGA<a href=https://www.elefans.com/category/jswz/34/1771428.html style=模块——IIC协议(FPGA做主机操作24C64)"/>

FPGA模块——IIC协议(FPGA做主机操作24C64)

FPGA模块——IIC协议(FPGA做主机操作24C64)

  • (1)EEPROM(24C64)
    • 1.向器件写数据时序
    • 2.向器件读数据时序
  • (2)IIC协议
  • (3)FPGA主机代码
    • 1.IIC读寄存器驱动(指定地址单次读写)
    • 2.使用 IIC模块

(1)EEPROM(24C64)

掉电不丢失,采用固定的读写协议。数据的稳定性和可重复擦写性突出

电路设计,可以配置24C64的地址
从器件进行开漏输出:输出低电平,输出高阻态来让上拉电路拉高给主机

传输的器件地址格式

发送字(寄存器)地址:
有16位,24C64实际用到13位寄存器地址

1.向器件写数据时序

单次写的协议:


连续写的协议:

2.向器件读数据时序

从当前地址读:

从当任意地址读:
要先向器件进行虚写命令,再进行读命令。

从任意地址连续读:
不读了主机FPGA进行非应答

(2)IIC协议

当SCL和SDA为高,SDA突然拉低的时候,是起始信号(下降沿)。
当SCL为高时,SDA突然拉高,是结束信号(上升沿)。高位在前
重点是:SCL在低电平期间SDA可以进行数据变化,而SCL在高电平期间,SDA的数据要保持稳定。

(3)FPGA主机代码

为了可适配性,iic用的都是用指定地址单次写(任意地址单次写)。
1.写:器件地址+写命令(8位)以及字节地址(8/16位)
2.写数据(8位)

指定地址单次读(任意地址单次读):
1.虚写:(器件地址+写命令)+ 字节地址(8/16位)
2.(起始信号)+(器件地址 + 读命令)
3.进行读数据(8位)
后面主机有个非应答,再发送结束信号

1.IIC读寄存器驱动(指定地址单次读写)

i2c_dri.v文件

(1)使用三段式状态机:

//1.同步时序描述状态转移, 把下个状态给当前状态。
always @(posedge dri_clk or negedge rst_n)

//2.组合逻辑判断状态转移条件。(哪个信号变化了要转状态)
always @(*)

//3.时序电路描述状态输出,写每个状态里面要输出什么。
always @(posedge dri_clk or negedge rst_n)

(2)使用此iic模块
输入:
1.触发iic运行的信号(1b)
2.字节地址位数信号(1b)
3.器件内寄存器地址(16b)
4.读/写控制位信号(1b)
5.要写的数据(8b)

输出:
1.读出的数据(8b)
2.iic一次操作完成信号(1b)
3.主机应答信号(报告是否正常)(1b)一直为0则是iic协议过程中一切正常,否则就输出1

外接器件:
SDA使用inout
SCL信号控制通信进程

module i2c_dri#(parameter   SLAVE_ADDR = 7'b1010000   ,  //EEPROM从机地址parameter   CLK_FREQ   = 26'd50_000_000, //模块输入的时钟频率parameter   I2C_FREQ   = 18'd250_000     //IIC_SCL的时钟频率)(                                                            input                clk        ,    input                rst_n      ,   //i2c interface                      input                i2c_exec   ,  //I2C触发执行信号input                bit_ctrl   ,  //字地址位控制(16b/8b)input                i2c_rh_wl  ,  //I2C读写控制信号input        [15:0]  i2c_addr   ,  //I2C器件内地址input        [ 7:0]  i2c_data_w ,  //I2C要写的数据output  reg  [ 7:0]  i2c_data_r ,  //I2C读出的数据output  reg          i2c_done   ,  //I2C一次操作完成output  reg          i2c_ack    ,  //I2C应答标志 0:应答 1:未应答output  reg          scl        ,  //I2C的SCL时钟信号inout                sda        ,  //I2C的SDA信号//user interface                   output  reg          dri_clk       //驱动I2C操作的驱动时钟);//localparam define
localparam  st_idle     = 8'b0000_0001; //空闲状态
localparam  st_sladdr   = 8'b0000_0010; //发送器件地址(slave address)
localparam  st_addr16   = 8'b0000_0100; //发送16位字地址
localparam  st_addr8    = 8'b0000_1000; //发送8位字地址
localparam  st_data_wr  = 8'b0001_0000; //写数据(8 bit)
localparam  st_addr_rd  = 8'b0010_0000; //发送器件地址读
localparam  st_data_rd  = 8'b0100_0000; //读数据(8 bit)
localparam  st_stop     = 8'b1000_0000; //结束I2C操作//reg define
reg            sda_dir   ; //I2C数据(SDA)方向控制
reg            sda_out   ; //SDA输出信号
reg            st_done   ; //状态结束
reg            wr_flag   ; //写标志
reg    [ 6:0]  cnt       ; //计数
reg    [ 7:0]  cur_state ; //状态机当前状态
reg    [ 7:0]  next_state; //状态机下一状态
reg    [15:0]  addr_t    ; //地址
reg    [ 7:0]  data_r    ; //读取的数据
reg    [ 7:0]  data_wr_t ; //I2C需写的数据的临时寄存
reg    [ 9:0]  clk_cnt   ; //分频时钟计数//wire define
wire          sda_in     ; //SDA输入信号
wire   [8:0]  clk_divide ; //模块驱动时钟的分频系数//*****************************************************
//**                    main code
//*****************************************************//SDA控制
assign  sda        = sda_dir ?  sda_out : 1'bz   ;  //SDA数据输出或高阻
assign  sda_in     = sda                         ;  //SDA数据输入
assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2 ;  //模块驱动时钟的分频系数//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) beginif(!rst_n) begindri_clk <=  1'b0;clk_cnt <= 10'd0;endelse if(clk_cnt == (clk_divide[8:1] - 9'd1)) beginclk_cnt <= 10'd0;dri_clk <= ~dri_clk;endelseclk_cnt <= clk_cnt + 10'b1;
end//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) beginif(!rst_n)cur_state <= st_idle;elsecur_state <= next_state;
end//组合逻辑判断状态转移条件
always @(*) beginnext_state = st_idle;case(cur_state)st_idle: begin                          //空闲状态if(i2c_exec) beginnext_state = st_sladdr;endelsenext_state = st_idle;endst_sladdr: beginif(st_done) beginif(bit_ctrl)                    //判断是16位还是8位字地址next_state = st_addr16;elsenext_state = st_addr8 ;endelsenext_state = st_sladdr;endst_addr16: begin                        //写16位字地址if(st_done) beginnext_state = st_addr8;endelse beginnext_state = st_addr16;endendst_addr8: begin                         //8位字地址if(st_done) beginif(wr_flag==1'b0)               //读写判断next_state = st_data_wr;elsenext_state = st_addr_rd;endelse beginnext_state = st_addr8;endendst_data_wr: begin                       //写数据(8 bit)if(st_done)next_state = st_stop;elsenext_state = st_data_wr;endst_addr_rd: begin                       //写地址以进行读数据if(st_done) beginnext_state = st_data_rd;endelse beginnext_state = st_addr_rd;endendst_data_rd: begin                       //读取数据(8 bit)if(st_done)next_state = st_stop;elsenext_state = st_data_rd;endst_stop: begin                          //结束I2C操作if(st_done)next_state = st_idle;elsenext_state = st_stop ;enddefault: next_state= st_idle;endcase
end//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin//复位初始化if(!rst_n) beginscl       <= 1'b1;sda_out   <= 1'b1;sda_dir   <= 1'b1;                          i2c_done  <= 1'b0;                          i2c_ack   <= 1'b0;                          cnt       <= 7'b0;                          st_done   <= 1'b0;                          data_r    <= 8'b0;                          i2c_data_r<= 8'b0;                          wr_flag   <= 1'b0;                          addr_t    <= 16'b0;                          data_wr_t <= 8'b0;                          end                                              else begin                                       st_done <= 1'b0 ;                            cnt     <= cnt +7'b1 ;                       case(cur_state)                              st_idle: begin                          //空闲状态scl     <= 1'b1;                     sda_out <= 1'b1;                     sda_dir <= 1'b1;                     i2c_done<= 1'b0;                     cnt     <= 7'b0;               if(i2c_exec) begin                   wr_flag   <= i2c_rh_wl ;         addr_t    <= i2c_addr  ;         data_wr_t <= i2c_data_w;  i2c_ack   <= 1'b0;                      end                                  end                                      st_sladdr: begin                         //写地址(器件地址和字地址)case(cnt)                            7'd1 : sda_out <= 1'b0;          //开始I2C7'd3 : scl <= 1'b0;              7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址7'd5 : scl <= 1'b1;              7'd7 : scl <= 1'b0;              7'd8 : sda_out <= SLAVE_ADDR[5]; 7'd9 : scl <= 1'b1;              7'd11: scl <= 1'b0;              7'd12: sda_out <= SLAVE_ADDR[4]; 7'd13: scl <= 1'b1;              7'd15: scl <= 1'b0;              7'd16: sda_out <= SLAVE_ADDR[3]; 7'd17: scl <= 1'b1;              7'd19: scl <= 1'b0;              7'd20: sda_out <= SLAVE_ADDR[2]; 7'd21: scl <= 1'b1;              7'd23: scl <= 1'b0;              7'd24: sda_out <= SLAVE_ADDR[1]; 7'd25: scl <= 1'b1;              7'd27: scl <= 1'b0;              7'd28: sda_out <= SLAVE_ADDR[0]; 7'd29: scl <= 1'b1;              7'd31: scl <= 1'b0;              7'd32: sda_out <= 1'b0;          //0:写7'd33: scl <= 1'b1;              7'd35: scl <= 1'b0;              7'd36: begin                     sda_dir <= 1'b0;             sda_out <= 1'b1;                         end                              7'd37: scl     <= 1'b1;            7'd38: begin                     //从机应答 st_done <= 1'b1;if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位     end                                          7'd39: begin                     scl <= 1'b0;                 cnt <= 7'b0;                 end                              default :  ;                     endcase                              end                                      st_addr16: begin                         case(cnt)                            7'd0 : begin                     sda_dir <= 1'b1 ;            sda_out <= addr_t[15];       //传送字地址end                              7'd1 : scl <= 1'b1;              7'd3 : scl <= 1'b0;              7'd4 : sda_out <= addr_t[14];    7'd5 : scl <= 1'b1;              7'd7 : scl <= 1'b0;              7'd8 : sda_out <= addr_t[13];    7'd9 : scl <= 1'b1;              7'd11: scl <= 1'b0;              7'd12: sda_out <= addr_t[12];    7'd13: scl <= 1'b1;              7'd15: scl <= 1'b0;              7'd16: sda_out <= addr_t[11];    7'd17: scl <= 1'b1;              7'd19: scl <= 1'b0;              7'd20: sda_out <= addr_t[10];    7'd21: scl <= 1'b1;              7'd23: scl <= 1'b0;              7'd24: sda_out <= addr_t[9];     7'd25: scl <= 1'b1;              7'd27: scl <= 1'b0;              7'd28: sda_out <= addr_t[8];     7'd29: scl <= 1'b1;              7'd31: scl <= 1'b0;              7'd32: begin                     sda_dir <= 1'b0;             sda_out <= 1'b1;   end                              7'd33: scl  <= 1'b1;             7'd34: begin                     //从机应答st_done <= 1'b1;     if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位    end        7'd35: begin                     scl <= 1'b0;                 cnt <= 7'b0;                 end                              default :  ;                     endcase                              end                                      st_addr8: begin                          case(cnt)                            7'd0: begin                      sda_dir <= 1'b1 ;             sda_out <= addr_t[7];         //字地址end                              7'd1 : scl <= 1'b1;              7'd3 : scl <= 1'b0;              7'd4 : sda_out <= addr_t[6];     7'd5 : scl <= 1'b1;              7'd7 : scl <= 1'b0;              7'd8 : sda_out <= addr_t[5];     7'd9 : scl <= 1'b1;              7'd11: scl <= 1'b0;              7'd12: sda_out <= addr_t[4];     7'd13: scl <= 1'b1;              7'd15: scl <= 1'b0;              7'd16: sda_out <= addr_t[3];     7'd17: scl <= 1'b1;              7'd19: scl <= 1'b0;              7'd20: sda_out <= addr_t[2];     7'd21: scl <= 1'b1;              7'd23: scl <= 1'b0;              7'd24: sda_out <= addr_t[1];     7'd25: scl <= 1'b1;              7'd27: scl <= 1'b0;              7'd28: sda_out <= addr_t[0];     7'd29: scl <= 1'b1;              7'd31: scl <= 1'b0;              7'd32: begin                     sda_dir <= 1'b0;         sda_out <= 1'b1;                    end                              7'd33: scl     <= 1'b1;          7'd34: begin                     //从机应答st_done <= 1'b1;     if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位    end   7'd35: begin                     scl <= 1'b0;                 cnt <= 7'b0;                 end                              default :  ;                     endcase                              end                                      st_data_wr: begin                        //写数据(8 bit)case(cnt)                            7'd0: begin                      sda_dir <= 1'b1;sda_out <= data_wr_t[7];     //I2C写8位数据end                              7'd1 : scl <= 1'b1;              7'd3 : scl <= 1'b0;              7'd4 : sda_out <= data_wr_t[6];  7'd5 : scl <= 1'b1;              7'd7 : scl <= 1'b0;              7'd8 : sda_out <= data_wr_t[5];  7'd9 : scl <= 1'b1;              7'd11: scl <= 1'b0;              7'd12: sda_out <= data_wr_t[4];  7'd13: scl <= 1'b1;              7'd15: scl <= 1'b0;              7'd16: sda_out <= data_wr_t[3];  7'd17: scl <= 1'b1;              7'd19: scl <= 1'b0;              7'd20: sda_out <= data_wr_t[2];  7'd21: scl <= 1'b1;              7'd23: scl <= 1'b0;              7'd24: sda_out <= data_wr_t[1];  7'd25: scl <= 1'b1;              7'd27: scl <= 1'b0;              7'd28: sda_out <= data_wr_t[0];  7'd29: scl <= 1'b1;              7'd31: scl <= 1'b0;              7'd32: begin                     sda_dir <= 1'b0;           sda_out <= 1'b1;                              end                              7'd33: scl <= 1'b1;              7'd34: begin                     //从机应答st_done <= 1'b1;     if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位    end          7'd35: begin                     scl  <= 1'b0;                cnt  <= 7'b0;                end                              default  :  ;                    endcase                              end                                      st_addr_rd: begin                        //写地址以进行读数据case(cnt)                            7'd0 : begin                     sda_dir <= 1'b1;             sda_out <= 1'b1;             end                              7'd1 : scl <= 1'b1;              7'd2 : sda_out <= 1'b0;          //重新开始7'd3 : scl <= 1'b0;              7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址7'd5 : scl <= 1'b1;              7'd7 : scl <= 1'b0;              7'd8 : sda_out <= SLAVE_ADDR[5]; 7'd9 : scl <= 1'b1;              7'd11: scl <= 1'b0;              7'd12: sda_out <= SLAVE_ADDR[4]; 7'd13: scl <= 1'b1;              7'd15: scl <= 1'b0;              7'd16: sda_out <= SLAVE_ADDR[3]; 7'd17: scl <= 1'b1;              7'd19: scl <= 1'b0;              7'd20: sda_out <= SLAVE_ADDR[2]; 7'd21: scl <= 1'b1;              7'd23: scl <= 1'b0;              7'd24: sda_out <= SLAVE_ADDR[1]; 7'd25: scl <= 1'b1;              7'd27: scl <= 1'b0;              7'd28: sda_out <= SLAVE_ADDR[0]; 7'd29: scl <= 1'b1;              7'd31: scl <= 1'b0;              7'd32: sda_out <= 1'b1;          //1:读7'd33: scl <= 1'b1;              7'd35: scl <= 1'b0;              7'd36: begin                     sda_dir <= 1'b0;            sda_out <= 1'b1;                    end7'd37: scl     <= 1'b1;7'd38: begin                     //从机应答st_done <= 1'b1;     if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位    end   7'd39: beginscl <= 1'b0;cnt <= 7'b0;enddefault : ;endcaseendst_data_rd: begin                        //读取数据(8 bit)case(cnt)7'd0: sda_dir <= 1'b0;7'd1: begindata_r[7] <= sda_in;scl       <= 1'b1;end7'd3: scl  <= 1'b0;7'd5: begindata_r[6] <= sda_in ;scl       <= 1'b1   ;end7'd7: scl  <= 1'b0;7'd9: begindata_r[5] <= sda_in;scl       <= 1'b1  ;end7'd11: scl  <= 1'b0;7'd13: begindata_r[4] <= sda_in;scl       <= 1'b1  ;end7'd15: scl  <= 1'b0;7'd17: begindata_r[3] <= sda_in;scl       <= 1'b1  ;end7'd19: scl  <= 1'b0;7'd21: begindata_r[2] <= sda_in;scl       <= 1'b1  ;end7'd23: scl  <= 1'b0;7'd25: begindata_r[1] <= sda_in;scl       <= 1'b1  ;end7'd27: scl  <= 1'b0;7'd29: begindata_r[0] <= sda_in;scl       <= 1'b1  ;end7'd31: scl  <= 1'b0;7'd32: beginsda_dir <= 1'b1;             sda_out <= 1'b1;end7'd33: scl     <= 1'b1;7'd34: st_done <= 1'b1;          //非应答7'd35: beginscl <= 1'b0;cnt <= 7'b0;i2c_data_r <= data_r;enddefault  :  ;endcaseendst_stop: begin                           //结束I2C操作case(cnt)7'd0: beginsda_dir <= 1'b1;             //结束I2Csda_out <= 1'b0;end7'd1 : scl     <= 1'b1;7'd3 : sda_out <= 1'b1;7'd15: st_done <= 1'b1;7'd16: begincnt      <= 7'b0;i2c_done <= 1'b1;            //向上层模块传递I2C结束信号enddefault  : ;endcaseendendcaseend
endendmodule

2.使用 IIC模块

就是根据上面的输入和输出信号,进行对应的使用

module e2prom_rw(input                 clk        , //时钟信号input                 rst_n      , //复位信号//i2c interfaceoutput   reg          i2c_rh_wl  , //I2C读写控制信号output   reg          i2c_exec   , //I2C触发执行信号output   reg  [15:0]  i2c_addr   , //I2C器件内地址output   reg  [ 7:0]  i2c_data_w , //I2C要写的数据input         [ 7:0]  i2c_data_r , //I2C读出的数据input                 i2c_done   , //I2C一次操作完成input                 i2c_ack    , //I2C应答标志//user interfaceoutput   reg          rw_done    , //E2PROM读写测试完成output   reg          rw_result    //E2PROM读写测试结果 0:失败 1:成功
);//parameter define
//EEPROM写数据需要添加间隔时间,读数据则不需要
parameter      WR_WAIT_TIME = 14'd5000; //写入间隔时间
parameter      MAX_BYTE     = 16'd256 ; //读写测试的字节个数//reg define
reg   [1:0]    flow_cnt  ; //状态流控制
reg   [13:0]   wait_cnt  ; //延时计数器//*****************************************************
//**                    main code
//*****************************************************//EEPROM读写测试,先写后读,并比较读出的值与写入的值是否一致
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginflow_cnt   <= 2'b0;i2c_rh_wl  <= 1'b0;i2c_exec   <= 1'b0;i2c_addr   <= 16'b0;i2c_data_w <= 8'b0;wait_cnt   <= 14'b0;rw_done    <= 1'b0;rw_result  <= 1'b0;        endelse begini2c_exec <= 1'b0;rw_done  <= 1'b0;case(flow_cnt)2'd0 : begin                                  wait_cnt <= wait_cnt + 14'b1;               //延时计数if(wait_cnt == (WR_WAIT_TIME - 14'b1)) begin  //EEPROM写操作延时完成wait_cnt <= 14'b0;if(i2c_addr == MAX_BYTE) begin         //256个字节写入完成i2c_addr  <= 16'b0;i2c_rh_wl <= 1'b1;flow_cnt  <= 2'd2;endelse beginflow_cnt <= flow_cnt + 2'b1;i2c_exec <= 1'b1;endendend2'd1 : beginif(i2c_done == 1'b1) begin                  //EEPROM单次写入完成flow_cnt   <= 2'd0;i2c_addr   <= i2c_addr + 16'b1;           //地址0~255分别写入i2c_data_w <= i2c_data_w + 8'b1;         //数据0~255end    end2'd2 : begin                                   flow_cnt <= flow_cnt + 2'b1;i2c_exec <= 1'b1;end    2'd3 : beginif(i2c_done == 1'b1) begin                 //EEPROM单次读出完成//读出的值错误或者I2C未应答,读写测试失败if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) beginrw_done <= 1'b1;rw_result <= 1'b0;endelse if(i2c_addr == (MAX_BYTE - 16'b1))begin //读写测试成功rw_done   <= 1'b1;rw_result <= 1'b1;end    else beginflow_cnt <= 2'd2;i2c_addr <= i2c_addr + 16'b1;endend                 enddefault : ;endcase    end
end    endmodule

更多推荐

FPGA模块——IIC协议(FPGA做主机操作24C64)

本文发布于:2024-02-06 06:36:12,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1746982.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:模块   协议   主机   操作   FPGA

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!