dxzky 发表于 2014-4-11 22:21:29

FPGA I2C通讯无应答

FPGA模拟I2C时序,配置一个CMOS M芯片,但通讯总是无法建立。
1.示波器得到的信号,如下。从图中可以看出,I2C时序:停止-开始-从机地址(0x80)-SDA拉高阻等待从机应答,但主机却迟迟得不到应答,同时,经过二十多个时钟后,会出现图中一开始的突然拉低,然后进入下一个状态。这个是什么情况?

2.另外,对于上图,还有个问题在于,不管是SCL还是SDA,VPP都在几百mv,这个也不对。以前都用单片机上拉电阻,但对于FPGA应该怎么写呢?
3.以前I2C通讯正常的时候,配置结果总是有不对的情况,SCL和SDA都是通过10cm左右杜邦线,连接FPGA于CMOS模块。这块是不是有什么影响?还是通过调整上拉电阻就可以解决呢?
谢过各位~
原始程序:
`timescale 1ns / 1ps

module iic(
                        led,led2,
                        clk,
                        scl,sda,rst_n,
                );

//外部接口
input clk;                // 50MHz
input rst_n;        //复位信号,低有效
output scl;                // I2C时钟端口
inout sda;                // I2C的数据端口

//LED test
output wire led, led2;
reg led_r;
reg led_r2;
assign led= led_r;
assign led2= led_r2;

//---------------------------------------------
//分频部分
reg cnt;        // cnt=0:scl上升沿,cnt=1:scl高电平中间,cnt=2:scl下降沿,cnt=3:scl低电平中间
reg cnt_delay;        //500循环计数,产生iic所需要的时钟
reg scl_r;                //时钟脉冲寄存器
assign scl = scl_r;        //产生iic所需要的时钟

always @ (posedge clk or negedge rst_n)
        if(!rst_n) cnt_delay <= 9'd0;
        else if(cnt_delay == 9'd499) cnt_delay <= 9'd0;        //计数到10us为scl的周期,即100KHz
        else cnt_delay <= cnt_delay+1'b1;        //计算时间
       
always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) cnt <= 3'd5;
        else begin
                case (cnt_delay)
                        9'd124:        cnt <= 3'd1;        //cnt=1:scl高电平中间,用于数据采样
                        9'd249:        cnt <= 3'd2;        //cnt=2:scl下降沿
                        9'd374:        cnt <= 3'd3;        //cnt=3:scl低电平中间,用于数据变化
                        9'd499:        cnt <= 3'd0;        //cnt=0:scl上升沿
                        default: cnt <= 3'd5;
                        endcase
                end
end


`define SCL_POS                (cnt==3'd0)                //cnt=0:scl上升沿
`define SCL_HIG                (cnt==3'd1)                //cnt=1:scl高电平中间,用于数据采样
`define SCL_NEG                (cnt==3'd2)                //cnt=2:scl下降沿
`define SCL_LOW                (cnt==3'd3)                //cnt=3:scl低电平中间,用于数据变化


always @ (posedge clk or negedge rst_n)
        if(!rst_n) scl_r <= 1'b0;
        else if(cnt==3'd0) scl_r <= 1'b1;        //scl信号上升沿
           else if(cnt==3'd2) scl_r <= 1'b0;        //scl信号下降沿


//---------------------------------------------
                //需要写入的地址和数据
                               
`define        DEVICE_READ                8'b1011_1001        //被寻址器件地址(读操作)
`define DEVICE_WRITE        8'b1000_0000        //被寻址器件地址(写操作)
`define BYTE_ADDR                8'b0101_0000        //写入/读出EEPROM的地址寄存器
`define        WRITE_DATA_H                8'b0000_0011        //写入EEPROM的数据
`define        WRITE_DATA_L                8'b0000_1111

reg db_r;                //在IIC上传送的数据寄存器


//---------------------------------------------
                //读、写时序
parameter IDLE1      =6'd0;
parameter START1       =6'd1;
parameter SEND_A1      =6'd2;
parameter SLV_ACK1    =6'd3;
parameter SEND_D1      =6'd4;
parameter SLV_ACK2    =6'd5;
parameter SEND_D1_HIGH   =6'd6;
parameter SLV_ACK3    =6'd7;
parameter SEND_D1_LOW   =6'd8;
parameter SLV_ACK4=6'd9;
parameter STOP1      =6'd10;

reg cstate;        //状态寄存器
reg sda_r;                //输出数据寄存器
reg sda_link;        //输出数据sda信号inout方向方向       
reg num;        //
assign sda = sda_link ? sda_r:1'bz;


always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin
                        cstate <= IDLE1;
                        sda_r <= 1'b1;
                        sda_link <= 1'b0;
                        num <= 4'd0;
                end
        else           
                case (cstate)
                        IDLE1:       
                                begin
                                        sda_link <= 1'b1;                        //数据线sda为input
                                        sda_r <= 1'b1;                       
                                        db_r <= `DEVICE_WRITE;        //送器件地址(写操作)
                                        cstate <= START1;               
                                end
                        START1:
                                begin
                                        if(`SCL_HIG)
                                                begin                //scl为高电平期间
                                                        sda_link <= 1'b1;        //数据线sda为output
                                                        sda_r <= 1'b0;                //拉低数据线sda,产生起始位信号
                                                        cstate <= SEND_A1 ;
                                                        num <= 4'd0;                //num计数清零
                                                end
                                        else
                                                cstate <= START1; //等待scl高电平中间位置到来
                                end
                        SEND_A1 :        begin
                                        if(`SCL_LOW) begin
                                                        if(num == 4'd8) begin       
                                                                        num <= 4'd0;                        //num计数清零
                                                                        sda_r <= 1'b1;
                                                                                //sda置为高阻态(input)
                                                                        cstate <= SLV_ACK1;
                                                                end
                                                        else begin
                                                                        cstate <= SEND_A1 ;
                                                                        num <= num+1'b1;
                                                                        case (num)
                                                                                4'd0: sda_r <= db_r;
                                                                                4'd1: sda_r <= db_r;
                                                                                4'd2: sda_r <= db_r;
                                                                                4'd3: sda_r <= db_r;
                                                                                4'd4: sda_r <= db_r;
                                                                                4'd5: sda_r <= db_r;
                                                                                4'd6: sda_r <= db_r;
                                                                                4'd7: sda_r <= db_r;
                                                                                default: ;
                                                                                endcase
                                                        //                sda_r <= db_r;        //送器件地址,从高位开始
                                                                end
                                                end
                        //                else if(`SCL_POS) db_r <= {db_r,1'b0};        //器件地址左移1bit
                                        else cstate <= SEND_A1 ;
                                end
                        SLV_ACK1:        begin
                                        //if(`SCL_NEG/*sda==1'b0*/) begin        //考虑应答位
                                        if(`SCL_HIG) begin        //考虑应答位
                                                        sda_link <= 1'b0;
                                                        if(sda===1'b0)begin
                                                        cstate <= SEND_D1;        //从机响应信号
                                                        db_r <= `BYTE_ADDR;        // 1地址       
                                                        end
                                                end
                                        else cstate <= SLV_ACK1;                //等待从机响应
                                end
                        SEND_D1:        begin
                                        if(`SCL_LOW) begin
                                                        if(num==4'd8) begin       
                                                                        num <= 4'd0;                        //num计数清零
                                                                        sda_r <= 1'b1;
                                                                        sda_link <= 1'b0;                //sda置为高阻态(input)
                                                                        cstate <= SLV_ACK2;
                                                                end
                                                        else begin
                                                                        sda_link <= 1'b1;                //sda作为output
                                                                        num <= num+1'b1;
                                                                        case (num)
                                                                                4'd0: sda_r <= db_r;
                                                                                4'd1: sda_r <= db_r;
                                                                                4'd2: sda_r <= db_r;
                                                                                4'd3: sda_r <= db_r;
                                                                                4'd4: sda_r <= db_r;
                                                                                4'd5: sda_r <= db_r;
                                                                                4'd6: sda_r <= db_r;
                                                                                4'd7: sda_r <= db_r;
                                                                                default: ;
                                                                                endcase
                                                        //                sda_r <= db_r;        //送EEPROM地址(高bit开始)               
                                                                        cstate <= SEND_D1;                                       
                                                                end
                                                end
                        //                else if(`SCL_POS) db_r <= {db_r,1'b0};        //器件地址左移1bit
                                        else cstate <= SEND_D1;                               
                                end
                        SLV_ACK2 :        begin
                                        //if(`SCL_NEG/*sda==1'b0*/) begin        //考虑应答位
                                        if(`SCL_HIG) begin        //考虑应答位
                                                        sda_link <= 1'b0;
                                                        if(sda===1'b0)
                                                begin                //从机响应信号
                                                                cstate <= SEND_D1_HIGH;         //写操作
                                                                db_r <= `WRITE_DATA_H;        //写入的数据       
                                                           end
                                                end
                                        else
                                        cstate <= SLV_ACK2 ;        //等待从机响应
                                end
                        SEND_D1_HIGH:        begin
                                        if(`SCL_LOW) begin
                                                        if(num==4'd8) begin       
                                                                        num <= 4'd0;                        //num计数清零
                                                                        sda_r <= 1'b1;
                                                                        sda_link <= 1'b0;                //sda置为高阻态(input)
                                                                        cstate <= SLV_ACK3 ;
                                                                end
                                                        else begin
                                                                        sda_link <= 1'b1;                //sda作为output
                                                                        num <= num+1'b1;
                                                                        case (num)
                                                                                4'd0: sda_r <= db_r;
                                                                                4'd1: sda_r <= db_r;
                                                                                4'd2: sda_r <= db_r;
                                                                                4'd3: sda_r <= db_r;
                                                                                4'd4: sda_r <= db_r;
                                                                                4'd5: sda_r <= db_r;
                                                                                4'd6: sda_r <= db_r;
                                                                                4'd7: sda_r <= db_r;
                                                                                default: ;
                                                                                endcase
                                                        //                sda_r <= db_r;        //送EEPROM地址(高bit开始)               
                                                                        cstate <= SEND_D1_HIGH;                                       
                                                                end
                                                end
                        //                else if(`SCL_POS) db_r <= {db_r,1'b0};        //器件地址左移1bit
                                        else cstate <= SEND_D1_HIGH;                               
                                end
                        SLV_ACK3 :        begin
                                //if(`SCL_NEG/*sda==1'b0*/) begin        //考虑应答位
                                                if(`SCL_HIG) begin        //考虑应答位
                                                        sda_link <= 1'b0;
                                                        if(sda===1'b0)
                                                begin                //从机响应信号
                                                                cstate <= SEND_D1_LOW;         //写操作
                                                                db_r <= `WRITE_DATA_L;        //写入的数据       
                                                                        end
                                                end
                                        else cstate <= SLV_ACK3 ;        //等待从机响应
                                end
                        SEND_D1_LOW:        begin
                                        if(`SCL_LOW) begin
                                                        if(num==4'd8) begin       
                                                                        num <= 4'd0;                        //num计数清零
                                                                        sda_r <= 1'b1;
                                                                        sda_link <= 1'b0;                //sda置为高阻态(input)
                                                                        cstate <= SLV_ACK4 ;
                                                                end
                                                        else begin
                                                                        sda_link <= 1'b1;                //sda作为output
                                                                        num <= num+1'b1;
                                                                        case (num)
                                                                                4'd0: sda_r <= db_r;
                                                                                4'd1: sda_r <= db_r;
                                                                                4'd2: sda_r <= db_r;
                                                                                4'd3: sda_r <= db_r;
                                                                                4'd4: sda_r <= db_r;
                                                                                4'd5: sda_r <= db_r;
                                                                                4'd6: sda_r <= db_r;
                                                                                4'd7: sda_r <= db_r;
                                                                                default: ;
                                                                                endcase
                                                        //                sda_r <= db_r;        //送EEPROM地址(高bit开始)               
                                                                        cstate <= SEND_D1_LOW;                                       
                                                                end
                                                end
                        //                else if(`SCL_POS) db_r <= {db_r,1'b0};        //器件地址左移1bit
                                        else cstate <= SEND_D1_LOW;                               
                                end
                        SLV_ACK4 :        begin
                                        //if(`SCL_NEG/*sda==1'b0*/) begin        //考虑应答位
                                                if(`SCL_HIG) begin        //考虑应答位
                                                        sda_link <= 1'b0;
                                                        if(sda===1'b0)
                                                begin                //从机响应信号
                                                                cstate <= STOP1;         //写操作
                                                                sda_r <= 1'b1;
                                                                //db_r <= `WRITE_DATA;        //写入的数据
                                                        end
                                                end
                                        else
                                        cstate <= SLV_ACK4 ;        //等待从机响应
                                end
                        STOP1:        begin
                                        if(`SCL_LOW) begin
                                                        sda_link <= 1'b1;
                                                        sda_r <= 1'b0;
                                                        cstate <= STOP1;
                                                end
                                        else if(`SCL_HIG) begin
                                                        sda_r <= 1'b1;        //scl为高时,sda产生上升沿(结束信号)
                                                        cstate <= IDLE1;
                                                        led_r<=1'b1;
                                                        led_r2<=1'b1;
                                                end
                                        else cstate <= STOP1;
                           end
                        endcase
                               
end
endmodule


fx568000 发表于 2015-3-20 15:34:46

推荐你看看黑金的资料,说的很细

fx568000 发表于 2015-3-20 15:35:35

应该是时序有问题

chun2495 发表于 2015-3-20 15:44:40

用nios 解决

lanliang714 发表于 2015-3-20 20:09:53

检查硬件看看

dxzky 发表于 2015-3-21 00:14:09

fx568000 发表于 2015-3-20 15:35
应该是时序有问题

谢谢确实是时序的问题,这个是去年的问题,后来在5月初解决了,如果没记错的话,主要是应答状态的状态机程序写得有点问题,在检测拉低拉高有些问题,另外,硬件也存在一些问题,更换硬件修改程序才基本解决。

dxzky 发表于 2015-3-21 00:15:32

chun2495 发表于 2015-3-20 15:44
用nios 解决

可惜我这是Xilinx公司的,没记错好像也有I2C的IP核可使用,不过最后还是自己用状态机模拟来写

dxzky 发表于 2015-3-21 00:16:30

lanliang714 发表于 2015-3-20 20:09
检查硬件看看

嗯,硬件确实有问题。CMOS芯片部分的模块电路设计存在问题,也对I2C产生了影响。
页: [1]
查看完整版本: FPGA I2C通讯无应答