通过FPGA实现I2C

特别说明:以下所涉及代码参考网络、相关开发板厂家。只用于自己学习、理解和感悟。
I2C总线是由Philips公司开发的一种简单、双向二进制串行总线。只需要两根线即可在连接于总线上的器件之间信息传递。
I2C的标准速率为100kbit/s,快速模式时速率为400kbit/s,支持多机通讯,支持多主控模块,但同一时刻只允许有一个主控。由数据线SDA和时钟线SCL构成串行总线。每个电路和模块都有唯一的地址。
以下以AT24C04为例说明I2C的基本读写操作,I2C设备的操作可分为读写单个存储字节和多个存储字节。读写时序示意图如下所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如上图所示,I2C通信分为空闲状态、开始态、信息传输态、停止态。传输信息分为:设备地址、读写标志、应答信号、存储地址、数据。
空闲态:
SDA和SCL同时处于高电平(硬件电路中一般SDA和SCL线通过电阻上拉),进行总线释放。
开始态:
开始态时,SDA和SCL处于高电平状,当数据线SDA由高电平跳变到低电平后进入开始态。开始态代表一次数据传输的开始。
信息传输态:
I2C总线上,每一位数据都与一个时钟脉冲对应,即在每个数据位在相对应的时钟沿进行串行传送。在I2C总线上,时钟的高电平时,数据线上的数据保持稳定;时钟的低电平时,才允许电平改变状态。 数据信息传输过程中伴随着应答信号的发生。应答分位ACK和NACK。I2C总线上的数据都是以8位字节传送的。发送器每发送一个字节后,在第9个时钟脉冲期间释放数据线,同时等待接收器所反馈的应答信号。如果应答信号为低电平时,表示为有效应答(ACK);否则为无效应答(NACK)。无应答表示接收失败。如果主控制器在接收到最后一个字节后,发送一个NACK信号通知从设备结束数据发送,并释放SDA线,以便主设备发送一个停止信号。
停止态:
在时钟线保持高电平期间,数据线返回高电平即数据线被释放,称为I2C总线上的停止信号。接收到停止信号后I2C总线进入空闲状态。
读写时序如下所示:
1、写时序:
在这里插入图片描述
2、读时序:
在这里插入图片描述
在FPGA中,代码结构如下所示:
在这里插入图片描述

代码如下:
1、timescale.v代码

`timescale 1ns / 10ps

2、i2c_master_defines.v代码

`define I2C_CMD_NOP     4'b0000

`define I2C_CMD_START   4'b0001

`define I2C_CMD_STOP    4'b0010

`define I2C_CMD_WRITE   4'b0100

`define I2C_CMD_READ    4'b1000

3、i2c_master_bit_ctrl.v代码

`include "timescale.v"

`include "i2c_master_defines.v"
//位控制部分
//发送简单命令到SCL/SDA转换器
//每个命令有5个状态,A/B/C/D/idle。
module i2c_master_bit_ctrl(
 input   clk,        //系统时钟
 input   rst,        //同步使能高复位
 input   nReset,     //异步使能低复位
 input   ena,        //内核使能信号

 input[15:0] clk_cnt,    //时钟预分频值
	input[3:0]   cmd,       //从控制器来的命令
	output reg  cmd_ack,    //应答命令
	output reg  busy,       //i2c总线忙
	output reg  a1,         //i2c总线仲裁丢失

	input   din,            //数据输入
	output  reg dout,       //数据输出

 input   scl_i,          //i2c时钟线输入
	output  scl_o,          //i2c时钟线输出
	output  reg scl_oen,    //i2c时钟线输出使能(低使能)
	input   sda_i,          //i2c数据线输入
	output  sda_o,          //i2c数据线输出
	output  reg sda_oen     //i2c数据线输出使能(第使能)
);
reg[1:0]    cSCL,cSDA;      //采集SCL,SDA
reg[2:0]    fSCL,fSDA;      //SCL,SDA滤波器输入
reg     sSCL,sSDA;          //滤波和同步后的SCL,SDA输入
reg     dSCL,dSDA;          //延时变化后的dSCL,dSDA
reg     dscl_oen;           //延时变化后的时钟使能信号
reg     sda_chk;            //检查SDA输出(多主机仲裁)
reg     clk_en;             //时钟产生信号
reg     slave_wait;         //从设备就绪等待
reg[15:0]   cnt;            //时钟分频计数器(同步)
reg[13:0]   filter_cnt;     //滤波器时钟分频
//状态机变量
reg [17:0]  c_state;        //逻辑综合枚举状态
//在从设备没有准备好的时候,通过拉低SCL来达到延时的目的
//延时使能SCL
always @(posedge clk)
	dscl_oen <= #1 scl_oen;
// slave_wait is asserted when master wants to drive SCL high, 
//but the slave pulls it low 
// slave_wait remains asserted until the slave releases SCL
always @(posedge clk or negedge nReset)
 if(!nReset) slave_wait <= 1'b0;
	else        slave_wait <= (scl_oen & ~dscl_oen & ~sSCL) 
| (slave_wait & ~sSCL); 
//主设备SCL为高,但另一个主设备为低
//主设备开始倒计时(进行时钟同步)
wire    scl_sync = dSCL & ~sSCL & scl_oen;
//产生时钟使能信号  
always @(posedge clk or negedge nReset)
 if(~nReset)
   	 begin
       	 	cnt <= #1 16'h0;
       	 	clk_en <= 1'b1;
    	end
	else if(rst || ~|cnt || !ena || scl_sync)
    	begin
        	cnt <= #1 clk_cnt;
        	clk_en <= #1 1'b1;
    	end
	else if(slave_wait)
    	begin
        	cnt <= #1 cnt;
        	clk_en <= #1 1'b0;
    	end
	else
    	begin
        	cnt <= #1 cnt - 16'h1;
        	clk_en <= #1 1'b0;
    	end
//信号采集 
//降低亚稳态风险 
always @(posedge clk or negedge nReset)
 if(!nReset)
   		 begin
        		cSCL <= #1 2'b00;
       		 cSDA <= #1 2'b00;
   		 end 
	else if(rst)
    begin
        cSCL <= #1 2'b00;
        cSDA <= #1 2'b00;
    end
else
    begin
        cSCL <= {cSCL[0],scl_i};
        cSDA <= {cSDA[0],sda_i};
    end
//滤波SCL和SDA信号,(尝试)排除故障
always @(posedge clk or negedge nReset)
	 if(!nReset) filter_cnt <= 14'h0;
	else if(rst||!ena)  filter_cnt <= 14'h0;
	else if(~|filter_cnt) filter_cnt <= clk_cnt >> 2;
	else    filter_cnt <= filter_cnt - 1;
//滤波,filter_cnt频率是fSCLK频率的四倍
always @(posedge clk or negedge nReset)
	if(!nReset)
    		begin
        		fSCL <= 3'b111;
        		fSDA <= 3'b111; 
   		 end 
	else if(rst)
   		 begin
        		fSCL <= 3'b111;
        		fSDA <= 3'b111;
    	end
 else if(~|filter_cnt)
    	begin
        	fSCL <= {fSCL[1:0],cSCL[1]};
        	fSDA <= {fSDA[1:0],cSDA[1]};
   	 end
//总线时钟、数据信号同步以及延时。
//四倍频率采样SCL,使用三个连续位决策输出,进行同步。
  
always @(posedge clk or negedge nReset)
if(~nReset)
    begin
        sSCL <= #1 1'b1;
        sSDA <= #1 1'b1;
        
        dSCL <= #1 1'b1;
        dSDA <= #1 1'b1;
    end  
else if(rst)
    begin
        sSCL <= #1 1'b1;
        sSDA <= #1 1'b1;
        
        dSCL <= #1 1'b1;
        dSDA <= #1 1'b1;
    end
else 
    begin
        sSCL <= #1 & fSCL[2:1] | &fSCL[1:0] | (fSCL[2] & fSCL[0]);
        sSDA <= #1 & fSDA[2:1] | &fSDA[1:0] | (fSDA[2] & fSDA[0]);
        
        dSCL <= #1 sSCL;
        dSDA <= #1 dSDA;
    end  
//检测开始条件(SCL为高时,检测SDA的下降沿)        
reg sta_condition;    
//检测停止条件 (SCL为高时,检测SDA的上升沿)
reg sto_condition;

always @(posedge clk or negedge nReset)
if(~nReset)
    begin
        sta_condition <= #1 1'b0;
        sto_condition <= #1 1'b0;
    end            
else if(rst)
    begin
        sta_condition <= #1 1'b0;
        sto_condition <= #1 1'b0;            
    end 
else
    begin
        sta_condition <= #1 ~sSDA & dSDA & sSCL;
        sto_condition <= #1 sSDA & ~dSDA & sSCL;
    end
//生成I2C总线忙信号                                 
always @(posedge clk or negedge nReset)
if(!nReset)     busy <= #1 1'b0;
else if(rst)    busy <= #1 1'b0;
else            busy <= #1 (sta_condition | busy) & ~sto_condition;

//生成仲裁失败信号
//当仲裁失败情况:
//1)、主设备SDA为高,但是I2C总线为低;
//2)、不需要时停止检测。   
reg cmd_stop;
always @(posedge clk or negedge nReset)
if(~nReset) cmd_stop <= #1 1'b0;
else if(rst)    cmd_stop <= #1 1'b0;
else if(clk_en) cmd_stop <= #1 cmd == `I2C_CMD_STOP;
always @(posedge clk or negedge nReset)
if(~nReset) a1 <= #1 1'b0;
else if(rst)    a1 <= #1 1'b0;
else    a1 <= #1 (sda_chk & ~sSDA & sda_oen) | (|c_state & sto_condition & ~cmd_stop);
//生产dout信号,在SCL的上升沿存储SDA数据。    
always @(posedge clk)
if(sSCL & ~dSCL) dout <= #1 sSDA;
parameter[17:0] idle = 18'b0_0000_0000_0000_0000;
parameter[17:0] start_a = 18'b0_0000_0000_0000_0001;
parameter[17:0] start_b = 18'b0_0000_0000_0000_0010;
parameter[17:0] start_c = 18'b0_0000_0000_0000_0100;
parameter[17:0] start_d = 18'b0_0000_0000_0000_1000;
parameter[17:0] start_e = 18'b0_0000_0000_0001_0000;
parameter[17:0] stop_a = 18'b0_0000_0000_0010_0000;
parameter[17:0] stop_b = 18'b0_0000_0000_0100_0000; 
parameter[17:0] stop_c = 18'b0_0000_0000_1000_0000;
parameter[17:0] stop_d = 18'b0_0000_0001_0000_0000;
parameter[17:0] rd_a = 18'b0_0000_0010_0000_0000;
parameter[17:0] rd_b = 18'b0_0000_0100_0000_0000;
parameter[17:0] rd_c = 18'b0_0000_1000_0000_0000;
parameter[17:0] rd_d = 18'b0_0001_0000_0000_0000;
parameter[17:0] wr_a = 18'b0_0010_0000_0000_0000;
parameter[17:0] wr_b = 18'b0_0100_0000_0000_0000;
parameter[17:0] wr_c = 18'b0_1000_0000_0000_0000;
parameter[17:0] wr_d = 18'b1_0000_0000_0000_0000;
//状态机
always @(posedge clk or negedge nReset)
if(!nReset)
    begin
        c_state <= #1 idle;
        cmd_ack <= #1 1'b0;
        scl_oen <= #1 1'b1;
        sda_oen <= #1 1'b1;
        sda_chk <= #1 1'b0;
    end  
else if(rst | a1)
    begin
        c_state <= #1 idle;
        cmd_ack <= #1 1'b0;
        scl_oen <= #1 1'b1;
        sda_oen <= #1 1'b1;
        sda_chk <= #1 1'b0; 
    end 
else
    begin
        cmd_ack <= #1 1'b0;         //默认无应答+ assert cmd_ack only 1clk 
        if(clk_en)
            case(c_state)
                idle:
                    begin
                        case(cmd)
                            `I2C_CMD_START: c_state <= #1 start_a;
                            `I2C_CMD_STOP:  c_state <= #1 stop_a;
                            `I2C_CMD_WRITE: c_state <= #1 wr_a;
                            `I2C_CMD_READ:  c_state <= #1 rd_a;
                            default:        c_state <= #1 idle;
                        endcase    
                        scl_oen <= #1 scl_oen;      //保持SCL在相同状态
                        sda_oen <= #1 sda_oen;      //保持SDA在相同状态
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                start_a:
                    begin
                        c_state <= #1 start_b;      
                        scl_oen <= #1 scl_oen;     //保持SCL在同一状态
                        sda_oen <= #1 1'b1;          //设置SCL为高
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                start_b:
                    begin
                        c_state <= #1 start_c;
                        scl_oen <= #1 1'b1;         //设置SCL为高
                        sda_oen <= #1 1'b1;         //保持SDA为高
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                start_c:
                    begin
                        c_state <= #1 start_d;
                        scl_oen <= #1 1'b1;         //保持SCL为高
                        sda_oen <= #1 1'b0;         //设置SDA为低
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                start_d:
                    begin
                        c_state <= #1 start_e;
                        scl_oen <= #1 1'b1;             //保持SCL为高
                        sda_oen <= #1 1'b0;             //保持SDA为低
                        sda_chk <= #1 1'b0;             //不检测SDA输出
                    end
                start_e:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;             
                        scl_oen <= #1 1'b0;         //设置SCL为低
                        sda_oen <= #1 1'b0;         //保持SDA为低
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                stop_a:
                    begin
                        c_state <= #1 stop_b;
                        scl_oen <= #1 1'b0;         //保持SCL为低
                        sda_oen <= #1 1'b0;         //设置SDA为低
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                stop_b:
                    begin
                        c_state <= #1 stop_c;
                        scl_oen <= #1 1'b1;         //设置SCL为高
                        sda_oen <= #1 1'b0;         //保持SDA为低
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                stop_c:
                    begin
                        c_state <= #1 stop_d;
                        scl_oen <= #1 1'b1;         //保持SCL为高
                        sda_oen <= #1 1'b0;         //保持SDA为低
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                stop_d:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;
                        scl_oen <= #1 1'b1;         //保持SCL为高
                        sda_oen <= #1 1'b1;         //设置SDA为高
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                //读部分    
                rd_a:
                    begin
                        c_state <= #1 rd_b;
                        scl_oen <= #1 1'b0;         //保持SCL为低
                        sda_oen <= #1 1'b1;         //设置SDA为三态
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                rd_b:
                    begin
                        c_state <= #1 rd_c;
                        scl_oen <= #1 1'b1;     //设置SCL为高
                        sda_oen <= #1 1'b1;     //保持SDA为三态
                        sda_chk <= #1 1'b0;     //不检测SDA输出
                    end
                rd_c:
                    begin
                        c_state <= #1 rd_d;
                        scl_oen <= #1 1'b1;     //保持SCL为高
                        sda_oen <= #1 1'b1;     //保持SDA为三态
                        sda_chk <= #1 1'b0;     //不检测SDA输出
                    end
                rd_d:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;
                        scl_oen <= #1 1'b0;     //设置SCL为低
                        sda_oen <= #1 1'b1;     //保持SDA为三态
                        sda_chk <= #1 1'b0;     //不检测SDA输出
                    end
                //写         
                wr_a:
                    begin
                        c_state <= #1 wr_b;
                        scl_oen <= #1 1'b0;     //保持SCL为低
                        sda_oen <= #1 din;      //读din
                        sda_chk <= #1 1'b0;     //不检测SDA输出
                    end
                wr_b:
                    begin
                        c_state <= #1 wr_c;
                        scl_oen <= #1 1'b1;         //设置SCL为高
                        sda_oen <= #1 din;          //读din
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
                wr_c:
                    begin
                        c_state <= #1 wr_d;
                        scl_oen <= #1 1'b1;         //保持SCL为高
                        sda_oen <= #1 din;
                        sda_chk <= #1 1'b1;         //不检测SDA输出
                    end
                wr_d:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;
                        scl_oen <= #1 1'b0;         //设置SCL为低
                        sda_oen <= #1 din;
                        sda_chk <= #1 1'b0;         //不检测SDA输出
                    end
            endcase
        end          
                     
assign scl_o = 1'b0;        //SCL输出始终接地
assign sda_o = 1'b0;        //SDA输出始终接地                                        
            
endmodule

4、i2c_master_byte_ctrl.v源码
include "timescale.v"include “i2c_master_defines.v”
module i2c_master_byte_ctrl(
clk, rst, nReset, ena, clk_cnt, start, stop, read, write, ack_in, din,
cmd_ack, ack_out, dout, i2c_busy, i2c_al, scl_i, scl_o, scl_oen, sda_i, sda_o, sda_oen);
//输入与输出
input clk; //主时钟
input rst; //同步高使能复位
input nReset; //异步低使能复位
input ena; //内核使能信号

input[15:0] clk_cnt;        //时钟预分频值

//控制输入引脚
input start;
input stop;
input read;
input write;
input ack_in;
input[7:0] din;

//状态输出
output cmd_ack;
reg   cmd_ack;
output ack_out;
reg   ack_out;
output i2c_busy;
output i2c_al;
output[7:0] dout;
    //I2C信号
input scl_i;
output scl_o;
output scl_oen;
input sda_i;
output sda_o;
output sda_oen;
//状态机参数
parameter [4:0] ST_IDLE = 5'b0_0000;
parameter [4:0] ST_START = 5'b0_0001;
parameter [4:0] ST_READ = 5'b0_0010;
parameter [4:0] ST_WRITE = 5'b0_0100;
parameter [4:0] ST_ACK = 5'b0_1000;
parameter [4:0] ST_STOP = 5'b1_0000;
//信号为控制器
reg [3:0]   core_cmd;
reg         core_txd;
wire        core_ack,core_rxd;
//信号移位寄存器
reg [7:0]   sr;
reg         shift,ld;

//状态机信号
wire go;
reg [2:0] dcnt;
wire cnt_done;
//例化bit_controller
i2c_master_bit_ctrl bit_controller
(
.clk (clk),
.rst (rst),
.nReset (nReset),
.ena (ena),
.clk_cnt (clk_cnt),
.cmd (core_cmd),
.cmd_ack (core_ack),
.busy (i2c_busy),
.a1 (i2c_al),
.din (core_txd),
.dout (core_rxd),
.scl_i (scl_i),
.scl_o (scl_o),
.scl_oen (scl_oen),
.sda_i ( sda_i),
.sda_o (sda_o),
.sda_oen (sda_oen)
);
//生成go信号
assign go = (read | write | stop) & ~cmd_ack;
//数据输出到移位寄存器
assign dout = sr;
//生成移位寄存器
always @(posedge clk or negedge nReset)
if(!nReset)
sr <= #1 8’h0;
else if(rst)
sr <= #1 8’h0;
else if(ld)
sr <= #1 din;
else if(shift)
sr <= #1 {sr[6:0], core_rxd};
//生成计数器
always @(posedge clk or negedge nReset)
if(!nReset)
dcnt <= #1 3’h0;
else if(rst)
dcnt <= #1 3’h0;
else if(ld)
dcnt <= #1 3’h7;
else if(shift)
dcnt <= #1 dcnt - 3’h1;
assign cnt_done = ~(|dcnt);
reg [4:0] c_state;
//状态机
always @(posedge clk or negedge nReset)
if(!nReset)
begin
core_cmd <= #1 `I2C_CMD_NOP;
core_txd <= #1 1’b0;
shift <= #1 1’b0;
ld <= #1 1’b0;
cmd_ack <=#1 1’b0;
c_state <= #1 ST_IDLE;
ack_out <= #1 1’b0;
end
else if (rst | i2c_al)
begin
core_cmd <= #1 I2C_CMD_NOP;

        core_txd <= #1 1'b0;
        shift <= #1 1'b0;
        ld  <= #1 1'b0;
        cmd_ack <= #1 1'b0;
        c_state <= #1 ST_IDLE;
        ack_out <= #1 1'b0;
    end 
else
    begin
        core_txd <= #1 sr[7];
        shift <= #1 1'b0;
        ld <= #1 1'b0;
        cmd_ack <= #1 1'b0;
        case(c_state)
            ST_IDLE:
                begin
                    if(go)
                        begin
                            if(start)
                                begin
                                    c_state <= #1 ST_START;
                                    core_cmd <= #1 `I2C_CMD_START;                                      
                                end
                            else if(read)
                                begin
                                    c_state <= #1 ST_READ;
                                    core_cmd <= #1 `I2C_CMD_READ;
                                end
                            else if(write)
                                begin
                                    c_state <= #1 ST_WRITE;
                                    core_cmd <= #1 `I2C_CMD_WRITE;
                                end       
                            else
                                begin
                                    c_state <= #1 ST_STOP;
                                    core_cmd <= #1 `I2C_CMD_STOP;
                                end
                            ld <= #1 1'b1;
                        end
                end
            ST_START:
                begin
                    if(core_ack)
                        begin
                            if(read)
                                begin
                                    c_state <= #1 ST_READ;
                                    core_cmd <= #1 `I2C_CMD_READ;
                                end
                            else
                                begin
                                    c_state <= #1 ST_WRITE;
                                    core_cmd <= #1 `I2C_CMD_WRITE; 
                                end     
                        end
                    ld <= #1 1'b1;      
                end
            ST_WRITE:
                if(core_ack)
                    if(cnt_done)
                        begin
                            c_state <= #1 ST_ACK;
                            core_cmd <= #1 `I2C_CMD_READ;
                        end
                    else
                        begin
                            c_state <= #1 ST_WRITE;
                            core_cmd <= #1 `I2C_CMD_WRITE;
                            shift <= #1 1'b1;
                        end       
            ST_READ:
                if(core_ack)
                    begin
                        if(cnt_done)
                            begin
                                c_state <= #1 ST_ACK;
                                core_cmd <= #1 `I2C_CMD_WRITE;
                            end 
                        else
                            begin
                                c_state <= #1 ST_READ;
                                core_cmd <= #1 `I2C_CMD_READ;
                            end
                        shift <= #1 1'b1;
                        core_txd <= #1 ack_in;   
                    end
            ST_ACK:
                if(core_ack)
                    begin
                        if(stop)
                            begin
                                c_state <= #1 ST_STOP;
                                core_cmd <= #1 `I2C_CMD_STOP;
                            end
                        else
                            begin
                                c_state <= #1 ST_IDLE;
                                core_cmd <= #1 `I2C_CMD_NOP;
                                
                                cmd_ack <= #1 1'b1;
                            end 
                            
                            ack_out <= #1 core_rxd;
                            core_txd <= #1 1'b1;    
                    end
                else
                    core_txd <= #1 ack_in;
            ST_STOP:
                if(core_ack)
                    begin
                        c_state <= #1 ST_IDLE;
                        core_cmd <= #1 `I2C_CMD_NOP;
                        
                        cmd_ack <= #1 1'b1;
                    end
        endcase            
    end                      

endmodule

5、i2c_master_top.v代码

module i2c_master_top(
input rst,
input clk,
input[15:0] clk_div_cnt,

input scl_pad_i,
output scl_pad_o,
output scl_padoen_o,

input sda_pad_i,
output sda_pad_o,
output sda_padoen_o,
input i2c_addr_2byte,
input i2c_read_req,

output i2c_read_req_ack,
input i2c_write_req,
output i2c_write_req_ack,
input[7:0] i2c_slave_dev_addr,
input[15:0] i2c_slave_reg_addr,
input[7:0] i2c_write_data,
output reg[7:0] i2c_read_data,
output reg error
);
localparam S_IDLE           = 0;

localparam S_WR_DEV_ADDR = 1;
localparam S_WR_REG_ADDR = 2;
localparam S_WR_DATA = 3;
localparam S_WR_ACK = 4;
localparam S_WR_ERR_NACK = 5;
localparam S_RD_DEV_ADDR0 = 6;
localparam S_RD_REG_ADDR = 7;
localparam S_RD_DEV_ADDR1 = 8;
localparam S_RD_DATA = 9;
localparam S_RD_STOP = 10;
localparam S_WR_STOP = 11;
localparam S_WAIT = 12;
localparam S_WR_REG_ADDR1 = 13;
localparam S_RD_REG_ADDR1 = 14;
localparam S_RD_ACK = 15;

reg start;
reg stop;
reg read;
reg write;
reg ack_in;
reg[7:0] txr;
wire[7:0] rxr;
wire i2c_busy;
wire i2c_al;
wire done;
wire irxack;

reg[3:0] state,next_state;
assign i2c_read_req_ack = (state == S_RD_ACK); //I2C读请求应答标志
assign i2c_write_req_ack = (state == S_WR_ACK); //I2C写请求应答标志

//状态机状态切换
always @(posedge clk or negedge rst)
begin
if(rst)
state <= S_IDLE;
else
state <= next_state;
end

//状态机
always @(*)
begin
case(state)
S_IDLE: //在空闲态中检测是否有写请求或读请求
if(i2c_write_req)
next_state <= S_WR_DEV_ADDR;
else if(i2c_read_req)
next_state <= S_RD_DEV_ADDR0;
else
next_state <= S_IDLE;
S_WR_DEV_ADDR: //写设备地址
if(done && irxack)
next_state <= S_WR_ERR_NACK;
else if(done)
next_state <= S_WR_REG_ADDR;
else
next_state <= S_WR_DEV_ADDR;
S_WR_REG_ADDR: //写寄存器地址
if(done)
next_state <= i2c_addr_2byte ? S_WR_REG_ADDR1 : S_WR_DATA;
else
next_state <= S_WR_REG_ADDR;
S_WR_REG_ADDR1: //写寄存器地址
if(done)
next_state <= S_WR_DATA;
else
next_state <= S_WR_REG_ADDR1;
S_WR_DATA: //写数据
if(done)
next_state <= S_WR_STOP;
else
next_state <= S_WR_DATA;
S_WR_ERR_NACK: //写错误,无应答
next_state <= S_WR_STOP;
S_RD_ACK,S_WR_ACK: //读写应答
next_state <= S_WAIT;
S_WAIT: //空闲等待
next_state <= S_IDLE;
S_RD_DEV_ADDR0: //读设备地址
if(done && irxack)
next_state <= S_WR_ERR_NACK;
else if(done)
next_state <= S_RD_REG_ADDR;
else
next_state <= S_RD_DEV_ADDR0;
S_RD_REG_ADDR: //读寄存器地址
if(done)
next_state <= i2c_addr_2byte ? S_RD_REG_ADDR1 : S_RD_DEV_ADDR1;
else
next_state <= S_RD_REG_ADDR;
S_RD_REG_ADDR1: //读寄存器地址
if(done)
next_state <= S_RD_DEV_ADDR1;
else
next_state <= S_RD_REG_ADDR1;
S_RD_DEV_ADDR1: //读设备地址
if(done)
next_state <= S_RD_DATA;
else
next_state <= S_RD_DEV_ADDR1;
S_RD_DATA: //读数据
if(done)
next_state <= S_RD_STOP;
else
next_state <= S_RD_DATA;
S_RD_STOP: //数停止状态
if(done)
next_state <= S_RD_ACK;
else
next_state <= S_RD_STOP;
S_WR_STOP: //写停止
if(done)
next_state <= S_WR_ACK;
else
next_state <= S_WR_STOP;
default:
next_state <= S_IDLE;
endcase
end
//错误信号
always @(posedge clk or negedge rst)
begin
if(rst)
error <= 1’b0;
else if(state == S_IDLE)
error <= 1’b0;
else if(state == S_WR_ERR_NACK)
error <= 1’b1;
end
//状态信息
always @(posedge clk or negedge rst)
begin
if(rst)
state <= 1’b0;
else if(done)
state <= 1’b0;
else if(state == S_WR_DEV_ADDR || state == S_RD_DEV_ADDR0 || state == S_RD_DEV_ADDR1)
state <= 1’b1;
end
//停止信号
always @(posedge clk or negedge rst)
begin
if(rst)
stop <= 1’b0;
else if(done)
stop <= 1’b0;
else if(state == S_WR_STOP || state == S_RD_STOP)
stop <= 1’b1;
end
always @(posedge clk or negedge rst)
begin
if(rst)
ack_in <= 1’b0;
else
ack_in <= 1’b1;
end
//写信息
always @(posedge clk or negedge rst)
begin
if(rst)
write <= 1’b0;
else if(done)
write <= 1’b1;
else if(state == S_WR_DEV_ADDR || state == S_WR_REG_ADDR || state == S_WR_REG_ADDR1 || state == S_WR_DATA || state == S_RD_DEV_ADDR0 || state == S_RD_DEV_ADDR1 || state == S_RD_REG_ADDR || state == S_RD_REG_ADDR1)
write <= 1’b1;
end
//读信息
always @(posedge clk or negedge rst)
begin
if(rst)
read <= 1’b0;
else if(done)
read <= 1’b0;
else if(state == S_RD_DATA)
read <= 1’b1;
end
//I2C数据接收
always @(posedge clk or negedge rst)
if(rst)
i2c_read_data <= 8’h00;
else if(state == S_RD_DATA && done)
i2c_read_data <= rxr;

//I2C数据发送
always @(posedge clk or negedge rst)
begin
if(rst)
txr <= 8’d0;
else
case(state)
S_WR_DEV_ADDR,S_RD_DEV_ADDR0:
txr <={i2c_slave_dev_addr[7:1],1’b0};
S_RD_DEV_ADDR1:
txr <= {i2c_slave_dev_addr[7:1],1’b1};
S_WR_REG_ADDR,S_RD_REG_ADDR:
txr <= (i2c_addr_2byte == 1’b1) ? i2c_slave_reg_addr[15:8] : i2c_slave_reg_addr[7:0];
S_WR_REG_ADDR1,S_RD_REG_ADDR1:
txr <= i2c_slave_reg_addr[7:0];
S_WR_DATA:
txr <= i2c_write_data;
default:
txr <= 8’hff;
endcase
end
//例化byte_controller
i2c_master_byte_ctrl byte_controller
(
.clk (clk),
.rst (rst),
.nReset (1’b1),
.ena (1’b1),
.clk_cnt (clk_div_cnt),
.start (start),
.stop (stop),
.read (read),
.write (write),
.ack_in (ack_in),
.din (txr),
.cmd_ack (done),
.ack_out (irxack),
.dout (rxr),
.i2c_busy (i2c_busy),
.i2c_al (i2c_al),
.scl_i (scl_pad_i),
.scl_o (scl_pad_o),
.scl_oen (scl_padoen_o),
.sda_i (sda_pad_i),
.sda_o (sda_pad_o),
.sda_oen ( sda_padoen_o )
);

endmodule

猜你喜欢

转载自blog.csdn.net/csdnqiang/article/details/106579144
I2C
今日推荐