基于FPGA的IIC协议通信-强化篇

IIC的介绍

IIC 协议(Inter-Integrated Circuit ,可简写为 I2C ),是一种用于各种电子设备之间进行通信和数据交换的同步串行通信协议 。它是由飞利浦(Philips)公司开发的两线式同步串行总线,只需要两根线就可在连接于总线上的器件之间传送信息。 IIC 多用于一主多从通信,通信速率较慢,传输距离短,任意时刻只能有一个主机。IIC是三大低速总线之一,通信速率:UART < IIC < SPI。

IIC的应用场景

1.数据采集领域的串行 AD,IIC负责将模拟量转化为数字量后的结果"取回来"。

2.图像处理领域的摄像头配置

3.存储类芯片EEPROM配置

4.将传感器的数字量(TMP461温度传感器),通过IIC传输

IIC设备连接方式

当总线为高电平时,主从机的输入端均为高电平;当总线为低电平时,主从机的输入端均为低电平。

多设备的OC/OD门的器件,输出可以接到一起,均接到总线上,形成一个线与逻辑,同时这个总线只能输出低电平,所以我们需要在总线上接上拉电阻(2K-4.7K)接到电源1.7-3.6V,既可完成通信。

IIC通信总线接上拉电阻的原因

因为I2C总线的输出端采用的是"开漏(Open-Drain)/OC门"结构,它本身没有能力输出高电平,必须依靠上拉电阻将电平拉高。

OC/OD门

OC门(集电极开路)和OD门(漏极开路) 本质上是"同一种东西",只是分别用在双极性晶体管(BJT)和场效应管(MOSFET)两种不同的半导体上。 它们共同的核心特征是:只能把电平拉低,不能主动拉高,所以输出高电平必须依赖外部的上拉电阻。电阻值一般为2.2K~4.7K,然后接到电源1.7V~3.6V。

OC门(集电极开路)内部结构 :内部是一个NPN型三极管(BJT)。三极管的发射极(E) 接地,**集电极(C)**作为输出引脚直接引出,内部不接任何电源。所以叫"集电极开路"。

OD门(漏极开路)内部结构 :内部是一个N沟道场效应管(MOSFET)。MOS管的源极(S) 接地,**漏极(D)**作为输出引脚直接引出,内部不接任何电源。所以叫"漏极开路"。

OC/OD工作原理 :输入高电平(逻辑1)时:管子导通 ,输出引脚直接对地短路,输出低电平(0V) 。输入低电平(逻辑0)时:管子截止 ,输出引脚内部断开了与地的连接,处于高阻态(悬空) 。此时必须靠外部接的上拉电阻,才能把引脚拉到高电平(VCC)。在输入端接一个反相器就是逻辑1时,总线被上拉,逻辑0时总线输出0。

开漏(OD门)+ 上拉电阻实现的"线与"功能

OC/OD门能接成线与结构以及实现输出与输入之间的电平变换。

线与结构的好处:一个主机发送数据,多个从机可以同时接收到一样的数据。

IIC通信引脚

IIC通信只有两根通信线,分别为

SCL:串行时钟线,用于数据收发同步。

SDA:双向串行数据线,用于传输数据。

特点:1.支持一主多从通信和多主多从通信,通信总线可以挂载多个设备。我们一般使用一主多从通信。多主多从通信需要配合总线仲裁(Arbitration)时钟同步(Clock Synchronization) 机制。仲裁机制:如果两个主机同时试图发送数据,谁先发送"0"(低电平)(起始位)谁就赢,因为"0"会把"1"覆盖掉(线与功能)。输掉的主机会立刻检测到冲突并放弃总线,不会导致数据错乱或硬件烧毁。

2.每个连接到总线的设备都有一个独立的地址(查看芯片数据手册),主机可以利用这个地址进行不同设备之间的访问。

3.当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。

4.IIC的通信速率由SCK决定,SCK频率要在400Khz以内,一般是200Khz或250Khz。

IIC通信时序

IIC写时序

IIC写时序分为:空闲状态,起始位,设备地址,读写位,应答位,寄存器地址,应答位,数据位,应答位,停止位。

PC:IIC通信是每发送1BYTE时,从机发一个ACK应答,然后再发1BYTE,从机ACK应答。且高位先发。

空闲状态:默认为高电平

起始位 :在SCL为高电平时,产生一个下降沿,代表起始位

7bit设备地址:在SCL为低电平时,更新bit位;在SCL为高电平时,保持不变。高位先发。

1bit读写位 :W操作为低电平,R操作为高电平。与7bit设备地址构成1BYTE。

ACK1:从机应答位,默认低电平。如若检测到为高,则没有该设备地址的从机。

寄存器地址:一般为8bit,高位先发。在SCL为低电平时,更新bit位;在SCL为高电平时,保持不变。

ACK2:从机应答位,默认低电平。如若检测到为高,则该设备地址的从机,没有收到寄存器地址数据或该从机没有这个寄存器地址。

**数据位:**一般为8bit,高位先发。在SCL为低电平时,更新bit位;在SCL为高电平时,保持不变。

**ACK3:**从机应答位,默认低电平。如若检测到为高,则该设备地址的从机,没有收到数据。

停止位: 在SCL为高电平时,产生一个上降沿,代表停止位。

从机ACK应答:意味着FPGA/单片机发送完1BYTE数据,释放了SDA数据总线,输出高阻态/悬空,此时SDA数据总线被上拉电阻拉高保持高电平。从机如果收到主机发来1BYTE数据,那么会将SDA数据线拉低,表示从机应答,FPGA/单片机检测到SDA被拉低,则表示从机的状态正常。

IIC写多字节数据时序

等价于

IIC读时序

IIC读时序分为:空闲状态、起始位、器件地址、写位、应答位、寄存器地址、应答位、起始位、设备地址、读位、从机应答、从机发送1BYTE、非应答、停止位。

IIC读多字节时序

从机不应答主控的排查方法

排查从机:1.检查从机供电是否正常

2.检查从机的设备地址引脚是否与我们发送的设备地址一致

3.检查从机电路工作是否正常

排查主机:1.使用示波器或SignalTap II抓一下主机发送的IIC波形是否正确

基于FPGA的IIC写模块

绘制模块框图及波形图

模块代码编写

复制代码
module  IIC_wr(
    input   wire        sys_clk     ,//50Mhz
    input   wire        sys_rst_n   ,
    input   wire[6:0]   device_addr ,
    input   wire[7:0]   reg_addr    ,
    input   wire[7:0]   wr_data     ,
    input   wire        data_flag   ,//数据更新标志
    
    output  reg         done        ,//发送完成标志
    output  reg         SCL         ,//250Khz
    inout   wire        SDA          //双向总线
);
//双向总线推荐使用组合逻辑wire型,reg型quartus不会报错,会多两个警告
//如果定义为reg型modelsim仿真会报错。
parameter   CNT_MAX     = 8'd100;
parameter   BIT_CNT_MAX = 8'd8  ;

localparam  IDEL    = 8'd0;
localparam  S0      = 8'd1;//BEGIN   
localparam  S1      = 8'd2;//D_ADDR  
localparam  S2      = 8'd3;//WR      
localparam  S3      = 8'd4;//ACK1    
localparam  S4      = 8'd5;//REG_ADDR 
localparam  S5      = 8'd6;//ACK2    
localparam  S6      = 8'd7;//DATA    
localparam  S7      = 8'd8;//ACK3    
localparam  S8      = 8'd9;//STOP    
localparam  S9      = 8'd10;//END

//寄存要发送的数据
reg [6:0]   device_addr_reg;
reg [7:0]   reg_addr_reg   ;
reg [7:0]   wr_data_reg    ;
//////////////////////////
reg [7:0]   state   ;
reg [7:0]   cnt     ;
reg [7:0]   bit_cnt ;
// 新增:SDA内部驱动寄存器+三态使能
reg sda_out;
reg sda_en;
//SDA
assign SDA = sda_en ? sda_out : 1'bz;
////////////////////////// 数据锁存 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        begin
            device_addr_reg <= 7'd0;
            reg_addr_reg    <= 8'd0;
            wr_data_reg     <= 8'd0;
        end
    else    if(data_flag)
        begin
            device_addr_reg <= device_addr;
            reg_addr_reg    <= reg_addr   ;
            wr_data_reg     <= wr_data    ;
        end
end
////////////////////////// 周期计数器cnt //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        cnt <=  8'd0;
    else    if(state == IDEL || state == S9 || cnt == CNT_MAX - 1'b1)
        cnt <=  8'd0;
    else
        cnt <=  cnt + 1'b1;
end
////////////////////////// bit位计数器bit_cnt //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        bit_cnt <= 8'd0;
    else    if(cnt == CNT_MAX/2 - 1'b1 && bit_cnt == BIT_CNT_MAX && !SCL || state == S7)
        bit_cnt <= 8'd0;
    else    if(cnt == CNT_MAX/2 - 1'b1 && !SCL)
        bit_cnt <= bit_cnt + 1'b1;
    else
        bit_cnt <= bit_cnt;
end
////////////////////////// SCL时钟生成 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        SCL <=  1'b1;
    else    if(cnt == CNT_MAX - 1'b1 && state == S8)
        SCL <=  1'b1;
    else    if(cnt == CNT_MAX - 1'b1)
        SCL <=  ~SCL;
    else
        SCL <=  SCL;
end
/* //SDA
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        SDA <=  1'b1;
    else
        case(state)
            IDEL:   SDA <=  1'b1;
            S0  :
                if(cnt == CNT_MAX/2 - 1'b1)
                    SDA <=  1'b0;
            S1  : SDA <=  device_addr_reg[8'd7 - bit_cnt];
            S2  : SDA <=  1'b0;//W
            S3  : SDA <=  1'bZ;//释放SDA
            S4  : SDA <=  reg_addr_reg[8'd8 - bit_cnt];
            S5  : SDA <=  1'bZ;//释放SDA
            S6  : SDA <=  wr_data_reg[8'd8 - bit_cnt];
            S7  : SDA <=  1'bZ;//释放SDA
            S8  : 
                if(cnt == CNT_MAX/2 - 1'b1)
                    SDA <=  1'b1;
            S9  : SDA <=  1'b1;
            default:SDA <=  1'b1;
        endcase
end */

////////////////////////// SDA内部输出逻辑 sda_out + sda_en //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        begin
            sda_out <= 1'b1;
            sda_en  <= 1'b1;
        end
    else
        begin
            sda_out <= sda_out;
            sda_en  <= 1'b1; // 默认主机驱动SDA
            case(state)
                IDEL:
                    begin sda_out <= 1'b1; sda_en <= 1'b1; end
                S0: // 起始信号:SCL高时拉低SDA
                    begin
                        sda_out <= (cnt == CNT_MAX/2 - 1'b1) ? 1'b0 : sda_out;
                        sda_en  <= 1'b1;
                    end
                S1: // 7位从地址
                    begin
                        sda_out <= device_addr_reg[8'd7 - bit_cnt];
                        sda_en  <= 1'b1;
                    end
                S2: // 写位0
                    begin sda_out <= 1'b0; sda_en <= 1'b1; end
                S3: // 释放SDA,等待从机ACK
                    begin sda_out <= 1'b1; sda_en <= 1'b0; end
                S4: // 寄存器地址8bit,修复索引 7-bit_cnt
                    begin
                        sda_out <= reg_addr_reg[8'd8 - bit_cnt];
                        sda_en  <= 1'b1;
                    end
                S5: // 释放SDA等ACK2
                    begin sda_out <= 1'b1; sda_en <= 1'b0; end
                S6: // 写入数据,修复索引
                    begin
                        sda_out <= wr_data_reg[8'd8 - bit_cnt];
                        sda_en  <= 1'b1;
                    end
                S7: // 释放SDA等ACK3
                    begin sda_out <= 1'b0; sda_en <= 1'b0; end
                S8: // 停止信号:SCL高时拉高SDA
                    begin
                        sda_out <= (cnt == CNT_MAX/2 - 1'b1) ? 1'b1 : sda_out;
                        sda_en  <= 1'b1;
                    end
                S9: // 空闲
                    begin sda_out <= 1'b1; sda_en <= 1'b1; end
                default:
                    begin sda_out <= 1'b1; sda_en <= 1'b1; end
            endcase
        end
end
////////////////////////// done完成标志 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        done <= 1'b0;
    else    if(state == S9)
        done <= 1'b1;
    else
        done <= 1'b0;
end
//state
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        begin
            state   <= IDEL;
        end
    else
        case(state)
            IDEL:
                begin
                    if(data_flag)
                        begin
                            state   <=  S0;
                        end
                    else
                        begin
                            state   <= IDEL;
                        end
                end
            S0  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL)
                        begin
                            state   <=  S1;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S1  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX-1'b1)
                        begin
                            state   <=  S2;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S2  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
                        begin
                            state   <=  S3;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S3  :
                begin
                    if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
                        begin
                            state   <= S0;
                        end
                    
                    else    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
                        begin
                            state   <=  S4;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S4  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
                        begin
                            state   <=  S5;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S5  :
                begin
                    if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
                        begin
                            state   <= S0;
                        end
                    
                    else    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
                        begin
                            state   <=  S6;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S6  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
                        begin
                            state   <=  S7;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S7  :
                begin
                    if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
                        begin
                            state   <= S0;
                        end
                    
                    else    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
                        begin
                            state   <=  S8;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S8  :
                begin
                    if(cnt == CNT_MAX - 1'b1 && SCL && bit_cnt == 0)
                        begin
                            state   <=  S9;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S9  :
                begin
                    state   <= IDEL;
                end
            default:
                begin
                    state   <= IDEL;
                end
        endcase
end
endmodule

仿真代码编写

复制代码
`timescale  1ns/1ps
module  IIC_wr_tb();

reg         sys_clk     ;
reg         sys_rst_n   ;
reg [6:0]   device_addr ;
reg [7:0]   reg_addr    ;
reg [7:0]   wr_data     ;
reg         data_flag   ;
reg         SDA_flag    ;

wire        done        ;
wire        SCL         ;
wire        SDA         ;//双向总线,tb文件只能定义为wire型

initial
begin
    sys_clk     = 1'b0;
    sys_rst_n   = 1'b0;
    device_addr = 7'b100_1100;
    reg_addr    = 8'b1010_0101;
    wr_data     = 8'b1110_0010;
    data_flag   = 1'b0;
    #123
    sys_rst_n   = 1'b1;
    #123456
    data_flag   = 1'b1;
    #20
    data_flag   = 1'b0;
end

always #10 sys_clk = ~sys_clk;

always@(posedge sys_clk)
begin
    case(IIC_wr_inst.state)
        8'd4:SDA_flag <= 1'b0;
        8'd6:SDA_flag <= 1'b0;
        8'd8:SDA_flag <= 1'b0;
        default:SDA_flag <= 1'b1;
    endcase
end

assign SDA = (SDA_flag == 1'b0) ? 1'b1 : 1'bz;

IIC_wr  IIC_wr_inst(
    .sys_clk    (sys_clk    ),//50Mhz
    .sys_rst_n  (sys_rst_n  ),
    .device_addr(device_addr),
    .reg_addr   (reg_addr   ),
    .wr_data    (wr_data    ),
    .data_flag  (data_flag  ),//数据更新标志

    .done       (done       ),//发送完成标志
    .SCL        (SCL        ),//250Khz
    .SDA        (SDA        )
);
endmodule

仿真验证

仿真验证通过。

IIC读写模块的合体IIC_driver

绘制模块框图及波形图

编写模块代码

复制代码
module  IIC_driver(
    input   wire        sys_clk     ,//50Mhz
    input   wire        sys_rst_n   ,
    input   wire[6:0]   device_addr ,
    input   wire[7:0]   reg_addr    ,
    input   wire[7:0]   wr_data     ,
    input   wire        start       ,//读/写数据开始标志
    input   wire[1:0]   cmd         ,
    
    output  reg         SCL         ,//250Khz
    inout   wire        SDA         ,//双向总线
    output  reg [7:0]   rd_data     ,
    output  reg         done         //发送/接收完成标志
);
//双向总线推荐使用组合逻辑wire型,reg型quartus不会报错,会多两个警告
//如果定义为reg型modelsim仿真会报错。
parameter   CNT_MAX     = 8'd100;
parameter   BIT_CNT_MAX = 8'd8  ;
//cmd读写指令
localparam  WR      = 2'b01;
localparam  RD      = 2'b11;
//状态
localparam  IDEL    = 8'd0;
localparam  S0      = 8'd1;//BEGIN   
localparam  S1      = 8'd2;//D_ADDR  
localparam  S2      = 8'd3;//WR      
localparam  S3      = 8'd4;//ACK1    
localparam  S4      = 8'd5;//REG_ADDR 
localparam  S5      = 8'd6;//ACK2    
//写时序
localparam  S6      = 8'd7;//DATA    
localparam  S7      = 8'd8;//ACK3    
//读时序
localparam  S8      = 8'd9;//BEGIN2
localparam  S9      = 8'd10;//D_ADDR
localparam  S10     = 8'd11;//RD
localparam  S11     = 8'd12;//ACK3  
localparam  S12     = 8'd13;//RD_DATA
localparam  S13     = 8'd14;//NACK
//停止位
localparam  S14     = 8'd15;//STOP 
localparam  S15     = 8'd16;//END
//寄存要发送的数据
reg [6:0]   device_addr_reg;
reg [7:0]   reg_addr_reg   ;
reg [7:0]   wr_data_reg    ;
//////////////////////////
reg [7:0]   state   ;
reg [7:0]   cnt     ;
reg [7:0]   bit_cnt ;
// 新增:SDA内部驱动寄存器+三态使能
reg sda_out;
reg sda_en;
//SDA
assign SDA = sda_en ? sda_out : 1'bz;
////////////////////////// 数据锁存 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        begin
            device_addr_reg <= 7'd0;
            reg_addr_reg    <= 8'd0;
            wr_data_reg     <= 8'd0;
        end
    else    if(start)
        begin
            device_addr_reg <= device_addr;
            reg_addr_reg    <= reg_addr   ;
            if(cmd == WR)
                wr_data_reg     <= wr_data    ;
            else
                wr_data_reg     <= 8'd0;
        end
end
////////////////////////// 周期计数器cnt //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        cnt <=  8'd0;
    else    if(state == IDEL || state == S15 || cnt == CNT_MAX - 1'b1)
        cnt <=  8'd0;
    else
        cnt <=  cnt + 1'b1;
end
////////////////////////// bit位计数器bit_cnt //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        bit_cnt <= 8'd0;
    else    if(cnt == CNT_MAX/2 - 1'b1 && bit_cnt == BIT_CNT_MAX && !SCL || state == S7 || state == S13)
        bit_cnt <= 8'd0;
    else    if(cnt == CNT_MAX/2 - 1'b1 && !SCL)
        bit_cnt <= bit_cnt + 1'b1;
    else
        bit_cnt <= bit_cnt;
end
////////////////////////// SCL时钟生成 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        SCL <=  1'b1;
    else    if(cnt == CNT_MAX - 1'b1 && state == S14)
        SCL <=  1'b1;
    else    if(cnt == CNT_MAX - 1'b1)
        SCL <=  ~SCL;
    else
        SCL <=  SCL;
end

////////////////////////// SDA内部输出逻辑 sda_out + sda_en //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        begin
            sda_out <= 1'b1;
            sda_en  <= 1'b1;
        end
    else
        begin
            sda_out <= sda_out;
            sda_en  <= 1'b1; // 默认主机驱动SDA
            case(state)
                IDEL:
                    begin sda_out <= 1'b1; sda_en <= 1'b1; end
                S0: // 起始信号:SCL高时拉低SDA
                    begin
                        sda_out <= (cnt == CNT_MAX/2 - 1'b1) ? 1'b0 : sda_out;
                        sda_en  <= 1'b1;
                    end
                S1: // 7位从地址
                    begin
                        sda_out <= device_addr_reg[8'd7 - bit_cnt];
                        sda_en  <= 1'b1;
                    end
                S2: // 写位0
                    begin sda_out <= 1'b0; sda_en <= 1'b1; end
                S3: // 释放SDA,等待从机ACK
                    begin sda_out <= 1'b1; sda_en <= 1'b0; end
                S4: // 寄存器地址8bit,修复索引 7-bit_cnt
                    begin
                        sda_out <= reg_addr_reg[8'd8 - bit_cnt];
                        sda_en  <= 1'b1;
                    end
                S5: // 释放SDA等ACK2
                    begin sda_out <= 1'b1; sda_en <= 1'b0; end
                //写时序
                S6: // 写入数据,修复索引
                    begin
                        sda_out <= wr_data_reg[8'd8 - bit_cnt];
                        sda_en  <= 1'b1;
                    end
                S7: // 释放SDA等ACK3
                    begin sda_out <= 1'b0; sda_en <= 1'b0; end
                //读时序
                S8://读操作第二个起始位
                    begin
                        sda_out <= (cnt == CNT_MAX/2 - 1'b1) ? 1'b0 : sda_out;
                        sda_en  <= 1'b1;
                    end
                S9://读操作第二次发7位从地址
                    begin
                        sda_out <= device_addr_reg[8'd7 - bit_cnt];
                        sda_en  <= 1'b1;
                    end
                S10://读位1
                    begin sda_out <= 1'b1; sda_en <= 1'b1; end
                S11:// 释放SDA,等待从机ACK
                    begin sda_out <= 1'b1; sda_en <= 1'b0; end
                S12://读数据,释放SDA,等待从机发完数据
                    begin sda_out <= 1'b1; sda_en <= 1'b0; end
                S13://NACK,继续释放SDA
                    begin sda_out <= 1'b1; sda_en <= 1'b0; end
                S14: // 停止信号:SCL高时拉高SDA
                    begin
                        sda_out <= (cnt == CNT_MAX/2 - 1'b1) ? 1'b1 : sda_out;
                        sda_en  <= 1'b1;
                    end
                S15: // 空闲
                    begin sda_out <= 1'b1; sda_en <= 1'b1; end
                default:
                    begin sda_out <= 1'b1; sda_en <= 1'b1; end
            endcase
        end
end
//rd_data
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        rd_data <= 8'd0;
    else    if(state == S12 && SCL && cnt == CNT_MAX/2 - 1'b1)
        rd_data <= {rd_data[6:0],SDA};
    else
        rd_data <= rd_data;
end
////////////////////////// done完成标志 //////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        done <= 1'b0;
    else    if(state == S15)
        done <= 1'b1;
    else
        done <= 1'b0;
end
///////////////////////////state状态机////////////////////////
always@(posedge sys_clk or negedge sys_rst_n)
begin
    if(!sys_rst_n)
        begin
            state   <= IDEL;
        end
    else
        case(state)
            IDEL:
                begin
                    if(start)
                        begin
                            state   <=  S0;
                        end
                    else
                        begin
                            state   <= IDEL;
                        end
                end
            S0  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL)
                        begin
                            state   <=  S1;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S1  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX-1'b1)
                        begin
                            state   <=  S2;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S2  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
                        begin
                            state   <=  S3;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S3  :
                begin
                    if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
                        begin
                            state   <= S0;
                        end
                    
                    else    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
                        begin
                            state   <=  S4;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S4  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
                        begin
                            state   <=  S5;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S5  :
                begin
                    if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
                        begin
                            state   <= S0;
                        end
                    
                    else    if(cmd == WR && cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
                        begin
                            state   <=  S6;
                        end
                    else    if(cmd == RD && cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
                        begin
                            state   <=  S8;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S6  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
                        begin
                            state   <=  S7;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S7  :
                begin
                    if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
                        begin
                            state   <= S0;
                        end
                    
                    else    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
                        begin
                            state   <=  S14;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S8   :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL)
                        begin
                            state   <=  S9;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S9   :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX-1'b1)
                        begin
                            state   <=  S10;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S10  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
                        begin
                            state   <=  S11;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S11  :
                begin
                    if(SCL && cnt == CNT_MAX/2 && SDA)//非应答
                        begin
                            state   <= S0;
                        end
                    
                    else    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
                        begin
                            state   <=  S12;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S12  :
                begin
                    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == BIT_CNT_MAX)
                        begin
                            state   <=  S13;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S13  ://NACK
                begin
                    if(SCL && cnt == CNT_MAX/2 && !SDA)//应答
                        begin
                            state   <= S0;
                        end
                    
                    else    if(cnt == CNT_MAX / 2 - 1'b1 && !SCL && bit_cnt == 0)
                        begin
                            state   <=  S14;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S14  :
                begin
                    if(cnt == CNT_MAX - 1'b1 && SCL && bit_cnt == 0)
                        begin
                            state   <=  S15;
                        end
                    else
                        begin
                            state   <= state;
                        end
                end
            S15  :
                begin
                    state   <= IDEL;
                end
            default:
                begin
                    state   <= IDEL;
                end
        endcase
end
endmodule

使用IIC_driver读取TMP461的温度

绘制模块框图及状态图

在读取TMP461时,要注意查看原理图和芯片手册,确定器件设备地址及寄存器地址。

编写模块代码

IIC_read_TMP461模块

复制代码
module  IIC_read_TMP461(
    input   wire        clk     ,
    input   wire        rst_n   ,
    
    output  wire[7:0]   ch1_LT_high_byte,
    output  wire[7:0]   ch1_LT_low_byte ,
    output  wire[7:0]   ch2_LT_high_byte,
    output  wire[7:0]   ch2_LT_low_byte ,
    output  wire        SCL             ,
    inout   wire        SDA        
);

wire        done;
wire[7:0]   rd_data;

wire[6:0]   device_addr;
wire[7:0]   reg_addr   ;
wire        start      ;
wire[1:0]   cmd        ;
// wire[7:0]   wr_data    ;


fsm_ctrl    fsm_ctrl_inst(
    .clk             (clk  ),//250Khz
    .rst_n           (rst_n),
    .done            (done),//IIC发送完成标志
    .rd_data         (rd_data),//IIC读取的数据
    .rd_data_valid   (),//IIC读取的数据的有效标志

    .start           (start),//IIC读/写开始标志
    .cmd             (cmd),//cmd用于区分start是读还是写,WR:01,RD:11
    .device_addr     (device_addr),//设备地址
    .reg_addr        (reg_addr),//寄存器地址
    .ch1_LT_high_byte(ch1_LT_high_byte),//第1个TMP461芯片本地温度的高字节
    .ch1_LT_low_byte (ch1_LT_low_byte ),//第1个TMP461芯片本地温度的低字节
    .ch2_LT_high_byte(ch2_LT_high_byte),//第2个TMP461芯片本地温度的高字节
    .ch2_LT_low_byte (ch2_LT_low_byte ) //第2个TMP461芯片本地温度的低字节
);

IIC_driver  IIC_driver_inst(
    .sys_clk    (clk  ),//50Mhz
    .sys_rst_n  (rst_n),
    .device_addr(device_addr),
    .reg_addr   (reg_addr   ),
    .wr_data    (),
    .start      (start      ),//读/写数据开始标志
    .cmd        (cmd        ),

    .SCL        (SCL),//250Khz
    .SDA        (SDA),//双向总线
    .rd_data    (rd_data),
    .done       (done   ) //发送/接收完成标志
);
endmodule

fsm_ctrl模块

复制代码
module  fsm_ctrl(
    input   wire        clk             ,//250Khz
    input   wire        rst_n           ,
    input   wire        done            ,//IIC发送完成标志
    input   wire[7:0]   rd_data         ,//IIC读取的数据
    input   wire        rd_data_valid   ,//IIC读取的数据的有效标志

    output  reg         start           ,//IIC读/写开始标志
    output  reg [1:0]   cmd             ,//cmd用于区分start是读还是写,WR:01,RD:11
    output  reg [6:0]   device_addr     ,//设备地址
    output  reg [7:0]   reg_addr        ,//寄存器地址
    output  reg [7:0]   ch1_LT_high_byte,//第1个TMP461芯片本地温度的高字节
    output  reg [7:0]   ch1_LT_low_byte ,//第1个TMP461芯片本地温度的低字节
    output  reg [7:0]   ch2_LT_high_byte,//第2个TMP461芯片本地温度的高字节
    output  reg [7:0]   ch2_LT_low_byte  //第2个TMP461芯片本地温度的低字节
);

parameter   TIME_1S =   250_000;

localparam  WR      =   2'b01;
localparam  RD      =   2'b11;

wire[6:0]   device_addr_mem [1:0];//设备地址数组

assign  device_addr_mem[0] = 7'h48;
assign  device_addr_mem[1] = 7'h4E;

wire[7:0]   reg_addr_mem [1:0];//寄存器地址数组

assign  reg_addr_mem[0] = 8'h00;
assign  reg_addr_mem[1] = 8'h15;

reg [31:0]  delay_cnt   ;
reg [7:0]   state       ;

localparam  IDLE    =   8'd0 ;
localparam  S0      =   8'd1 ;
localparam  S1      =   8'd2 ;
localparam  S2      =   8'd3 ;
localparam  S3      =   8'd4 ;
localparam  S4      =   8'd5 ;
localparam  S5      =   8'd6 ;
localparam  S6      =   8'd7 ;
localparam  S7      =   8'd8 ;
localparam  S8      =   8'd9 ;
localparam  S9      =   8'd10;
localparam  S10     =   8'd11;
localparam  S11     =   8'd12;

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            delay_cnt   <=  32'd0   ;
            state       <=  IDLE    ;
            start       <=  1'b0    ;
            cmd         <=  2'b00   ;
            device_addr <=  7'h00   ;
            reg_addr    <=  8'h00   ;
            ch1_LT_high_byte <= 8'd0;
            ch1_LT_low_byte  <= 8'd0;
            ch2_LT_high_byte <= 8'd0;
            ch2_LT_low_byte  <= 8'd0;
        end
    else
        case(state)
            IDLE:
                if(delay_cnt == TIME_1S)
                    begin
                        state       <=  S0    ;
                        delay_cnt   <=  32'd0   ;
                    end
                else
                    begin
                        delay_cnt   <=  delay_cnt + 1'd1   ;
                        state       <=  IDLE    ;
                        start       <=  1'b0    ;
                        cmd         <=  2'b00   ;
                        device_addr <=  7'h00   ;
                        reg_addr    <=  8'h00   ;
                    end
            S0  :
                begin
                    state       <=  S1      ;
                    start       <=  1'b1    ;//开始读的标志
                    cmd         <=  RD      ;//写指令
                    device_addr <=  device_addr_mem[0];
                    reg_addr    <=  reg_addr_mem[0];
                end
            S1  :
                if(done)
                    begin
                        state       <=  S2    ;
                    end
                else
                    begin
                        state       <=  state   ;
                        start       <=  1'b0    ;
                        cmd         <=  RD      ;
                        device_addr <=  device_addr_mem[0];
                        reg_addr    <=  reg_addr_mem[0];
                    end
            S2  ://读取芯片1的本地温度的高字节
                begin
                    state       <=  S3   ;
                    ch1_LT_high_byte    <=  rd_data;
                end
            S3  :
                begin
                    state       <=  S4      ;
                    start       <=  1'b1    ;//开始读的标志
                    cmd         <=  RD      ;//写指令
                    device_addr <=  device_addr_mem[0];
                    reg_addr    <=  reg_addr_mem[1];
                end
            S4  :
                if(done)
                    begin
                        state       <=  S5    ;
                    end
                else
                    begin
                        state       <=  state   ;
                        start       <=  1'b0    ;
                        cmd         <=  RD      ;
                        device_addr <=  device_addr_mem[0];
                        reg_addr    <=  reg_addr_mem[1];
                    end
            S5  ://读取芯片1的本地温度的低字节
                begin
                    state       <=  S6   ;
                    ch1_LT_low_byte    <=  rd_data;
                end
            S6  :
                begin
                    state       <=  S7      ;
                    start       <=  1'b1    ;//开始读的标志
                    cmd         <=  RD      ;//写指令
                    device_addr <=  device_addr_mem[1];
                    reg_addr    <=  reg_addr_mem[0];
                end
            S7  :
                if(done)
                    begin
                        state       <=  S8    ;
                    end
                else
                    begin
                        state       <=  state   ;
                        start       <=  1'b0    ;
                        cmd         <=  RD      ;//写指令
                        device_addr <=  device_addr_mem[1];
                        reg_addr    <=  reg_addr_mem[0];
                    end
            S8  ://读取芯片2的本地温度的高字节
                begin
                    state       <=  S9   ;
                    ch2_LT_high_byte    <=  rd_data;
                end
            S9  :
                begin
                    state       <=  S10      ;
                    start       <=  1'b1    ;//开始读的标志
                    cmd         <=  RD      ;//写指令
                    device_addr <=  device_addr_mem[1];
                    reg_addr    <=  reg_addr_mem[1];
                end
            S10 :
                if(done)
                    begin
                        state       <=  S11    ;
                    end
                else
                    begin
                        state       <=  state   ;
                        start       <=  1'b0    ;
                        cmd         <=  RD      ;//写指令
                        device_addr <=  device_addr_mem[1];
                        reg_addr    <=  reg_addr_mem[1];
                    end
            S11 ://读取芯片2的本地温度的低字节
                begin
                    state       <=     IDLE;
                    ch2_LT_low_byte    <=  rd_data;
                end
            default:
                begin
                    delay_cnt   <=  32'd0   ;
                    state       <=  IDLE    ;
                    start       <=  1'b0    ;
                    cmd         <=  2'b00   ;
                    device_addr <=  7'h00   ;
                    reg_addr    <=  8'h00   ;
                    ch1_LT_high_byte <= 8'd0;
                    ch1_LT_low_byte  <= 8'd0;
                    ch2_LT_high_byte <= 8'd0;
                    ch2_LT_low_byte  <= 8'd0;
                end
        endcase
end
endmodule
相关推荐
梦开始的地方 �1 天前
实例化和句柄
fpga开发
lf2824814311 天前
11 FM调制与解调的仿真
fpga开发
蓝悦无人机1 天前
Altium Designer 2024 PCB高级功能:不同Pitch的BGA设计要求
fpga开发·pcb·硬件设计·ad2024
ALINX技术博客2 天前
【黑金云课堂】FPGA技术教程Linux开发:系统定制
linux·运维·fpga开发
塔能物联运维2 天前
**塔能两相液冷:量化交易场景下的硬核验证,±1.5℃控温如何转化为真金白银**
人工智能·fpga开发·两相液冷
西安景驰电子3 天前
PCIe 授时卡原理及应用
运维·服务器·windows·单片机·嵌入式硬件·fpga开发
落chen3 天前
基于Vivado的FIFO-IP核-强化篇
fpga开发
X_xcccc3 天前
2026智能硬件核心:顶尖SOM方案与开发服务深度解析
fpga开发·智能硬件
公众号:fuwuqiBMC3 天前
(转自“服务器BMC”)服务器BMC芯片——AST1840
运维·服务器·fpga开发