打印
[FPGA]

FPGA I2C通讯无应答

[复制链接]
1645|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
dxzky|  楼主 | 2014-4-11 22:27 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
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的数据端口
//---------------------------------------------
//分频部分
reg[2:0] cnt;        // cnt=0:scl上升沿,cnt=1:scl高电平中间,cnt=2:scl下降沿,cnt=3:scl低电平中间
reg[8:0] 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信号下降沿

//---------------------------------------------
                //读、写时序
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[5:0] cstate;        //状态寄存器
reg sda_r;                //输出数据寄存器
reg sda_link;        //输出数据sda信号inout方向方向        
reg[3:0] 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[7];
                                                                                4'd1: sda_r <= db_r[6];
                                                                                4'd2: sda_r <= db_r[5];
                                                                                4'd3: sda_r <= db_r[4];
                                                                                4'd4: sda_r <= db_r[3];
                                                                                4'd5: sda_r <= db_r[2];
                                                                                4'd6: sda_r <= db_r[1];
                                                                                4'd7: sda_r <= db_r[0];
                                                                                default: ;
                                                                                endcase
                                    
                                                                end
                                                end
     
                                        else cstate <= SEND_A1 ;
                                end
                        SLV_ACK1:        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[7];
                                                                                4'd1: sda_r <= db_r[6];
                                                                                4'd2: sda_r <= db_r[5];
                                                                                4'd3: sda_r <= db_r[4];
                                                                                4'd4: sda_r <= db_r[3];
                                                                                4'd5: sda_r <= db_r[2];
                                                                                4'd6: sda_r <= db_r[1];
                                                                                4'd7: sda_r <= db_r[0];
                                                                                default: ;
                                                                                endcase
                                 
                                                                        cstate <= SEND_D1;                                       
                                                                end
                                                end

                                        else cstate <= SEND_D1;                                
                                end
                        SLV_ACK2 :        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[7];
                                                                                4'd1: sda_r <= db_r[6];
                                                                                4'd2: sda_r <= db_r[5];
                                                                                4'd3: sda_r <= db_r[4];
                                                                                4'd4: sda_r <= db_r[3];
                                                                                4'd5: sda_r <= db_r[2];
                                                                                4'd6: sda_r <= db_r[1];
                                                                                4'd7: sda_r <= db_r[0];
                                                                                default: ;
                                                                                endcase
                  
                                                                        cstate <= SEND_D1_HIGH;                                       
                                                                end
                                                end

                                        else cstate <= SEND_D1_HIGH;                                
                                end
                        SLV_ACK3 :        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[7];
                                                                                4'd1: sda_r <= db_r[6];
                                                                                4'd2: sda_r <= db_r[5];
                                                                                4'd3: sda_r <= db_r[4];
                                                                                4'd4: sda_r <= db_r[3];
                                                                                4'd5: sda_r <= db_r[2];
                                                                                4'd6: sda_r <= db_r[1];
                                                                                4'd7: sda_r <= db_r[0];
                                                                                default: ;
                                                                                endcase
               
                                                                        cstate <= SEND_D1_LOW;                                       
                                                                end
                                                end

                                        else cstate <= SEND_D1_LOW;                                
                                end
                        SLV_ACK4 :        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;
                                                end
                                        else cstate <= STOP1;
                           end  
                        endcase                                
end
endmodule

相关帖子

沙发
ococ| | 2014-4-12 21:10 | 只看该作者
1.从机是否标准I2C从设备,还是有自定义的类I2C通信协议?
2.I2C通信的上拉电阻还是必要的,如果板子上没有上拉电阻可以在管脚定义时使用FPGA内部的上拉设置。

使用特权

评论回复
板凳
haitaox| | 2014-4-13 00:22 | 只看该作者
1.从你的示波器波形来看,绿线是SCL 黄线是SDA。黄线开始的时刻怎么是不高不低的状态?
2.你说VPP是几百mv?首先你现在用的是接口电平是多少,3.3v还是5v还是其他的。就算不接上下拉电阻,VPP也是可以达到最大幅值的。

使用特权

评论回复
地板
fwfjndx| | 2014-4-13 11:28 | 只看该作者
确保SCL和SDA引脚都接到上拉电阻,还有仔细查看程序

使用特权

评论回复
5
dxzky|  楼主 | 2014-4-15 20:16 | 只看该作者
fwfjndx 发表于 2014-4-13 11:28
确保SCL和SDA引脚都接到上拉电阻,还有仔细查看程序

恩,谢谢~不过我试了下,每次应答都会自己拉低,不管接不接CMOS,接示波器也会自己拉低。这种判断有什么问题么?
   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

使用特权

评论回复
6
dxzky|  楼主 | 2014-4-15 20:21 | 只看该作者
haitaox 发表于 2014-4-13 00:22
1.从你的示波器波形来看,绿线是SCL 黄线是SDA。黄线开始的时刻怎么是不高不低的状态?
2.你说VPP是几百mv ...

1.SDA这种状态确实也很奇怪,当我发送的最后一位是1的时候,就会出现这种现象,一直会持续20+个时钟,才会拉低。
2.第二个问题我已经解决了,主要是实验室示波器表笔质量和衰减的问题。接口电平是3.3V,但是整个程序应答会出现自动拉低的问题,这个我理解不了= =

使用特权

评论回复
7
dxzky|  楼主 | 2014-4-15 20:22 | 只看该作者
ococ 发表于 2014-4-12 21:10
1.从机是否标准I2C从设备,还是有自定义的类I2C通信协议?
2.I2C通信的上拉电阻还是必要的,如果板子上没有 ...

1.从机是标准I2C从设备。
2.FPGA的上拉设置我已经搞定了,thx~

使用特权

评论回复
8
haitaox| | 2014-4-18 12:59 | 只看该作者
dxzky 发表于 2014-4-15 20:21
1.SDA这种状态确实也很奇怪,当我发送的最后一位是1的时候,就会出现这种现象,一直会持续20+个时钟,才 ...

1.SDA处于半高的状态我之前碰到过,FPGA是I2C主设备,艾默生的电源是从设备。在I2C协议中,允许从设备延迟响应,即从设备处于忙碌状态暂时无法回应主机,此时,从设备会将SDA拉低。
标准的I2C协议,SDA和SCL的高电平都是通过上拉实现的,而不是驱动实现的。即SDA和SCL为高的时候,FPGA的引脚是三态的,通过外部的上拉电阻将电平拉高。
I2C主设备的SDA SCL三态之后,要判断是否是高电平,如果是高电平,那么从设备就处于正常状态。如果是低电平,那么就需要等待SCL SDA为高后再操作。
具体的你可以google i2c stretch arbitration

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
dxzky + 1 赞一个!
9
dxzky|  楼主 | 2014-4-23 16:54 | 只看该作者
haitaox 发表于 2014-4-18 12:59
1.SDA处于半高的状态我之前碰到过,FPGA是I2C主设备,艾默生的电源是从设备。在I2C协议中,允许从设备延 ...

thx~最后问题已经排除了,一方面是由于我们对阻塞和非阻塞幅值的理解有问题,导致高阻状态有误,另一方面是设备本身存在一定的问题,从而导致了最后的现象。不过还是很感谢你的回答~

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2

主题

11

帖子

0

粉丝