FPGA模块——IIC协议(FPGA做主机操作24C64)
- EEPROM(24C64)
- 向器件写数据时序
- 向器件读数据时序
- IIC协议
- FPGA主机代码
- IIC读寄存器驱动(指定地址单次读写)
- 使用 IIC模块
EEPROM(24C64)
掉电不丢失,采用固定的读写协议。数据的稳定性和可重复擦写性突出
电路设计,可以配置24C64的地址
从器件进行开漏输出:输出低电平,输出高阻态来让上拉电路拉高给主机
传输的器件地址格式
发送字(寄存器)地址:
有16位,24C64实际用到13位寄存器地址
向器件写数据时序
单次写的协议:
连续写的协议:
向器件读数据时序
从当前地址读:
从当任意地址读:
要先向器件进行虚写命令,再进行读命令。
从任意地址连续读:
不读了主机FPGA进行非应答
IIC协议
当SCL和SDA为高,SDA突然拉低的时候,是起始信号(下降沿)。
当SCL为高时,SDA突然拉高,是结束信号(上升沿)。高位在前
重点是:SCL在低电平期间SDA可以进行数据变化,而SCL在高电平期间,SDA的数据要保持稳定。
FPGA主机代码
为了可适配性,iic用的都是用指定地址单次写(任意地址单次写)。
1.写:器件地址+写命令(8位)以及字节地址(8/16位)
2.写数据(8位)
指定地址单次读(任意地址单次读):
1.虚写:(器件地址+写命令)+ 字节地址(8/16位)
2.(起始信号)+(器件地址 + 读命令)
3.进行读数据(8位)
后面主机有个非应答,再发送结束信号
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
使用 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