ahit 发表于 2011-9-25 08:43:26

请教关于I2C总线时序的问题

我是个初学者,最近买了马老师的第二版新书,正在学习中。
关于I2C的时序,不太明白,请马老师和各位前辈指点迷津,谢谢啦!

这是一段通过软件模拟I2c时序,接收数据的程序。
下面是我的理解:
* 接收第一个数据的之前SDA_Hight,意思是主机释放sda总线,等待从机设置好数据。
* 在for中先delay——scl hight——delay——scl low——delay
         等待数据稳定   主机在上升沿接收      scl下降    延迟
原程序:
//读一字节 ack: 1时应答,0时不应答
unsigned char I2C_read(unsigned char ack)
{
unsigned char i,ret;
SDA_Hight;
for(i=0;i<8;i++)
{
    I2C_Delay;         
    SCL_Low;                  
    I2C_Delay;
    SCL_Hight;               
    I2C_Delay;
    ret<<=1;
    if(SDA_Input)
      ret++;
}
SCL_Low;   
I2C_Delay;
if(ack)      //非应答
    SDA_Low;            
else          //应答
    SDA_Hight;
I2C_Delay;
SCL_Hight;
I2C_Delay;                  
SCL_Low;                     
I2C_Delay;
return(ret);
}

我的问题:
* 重新开始执行for循环的时候(即for的首尾相接时),会出现前后两个delay连在一起的情况,导致scl处于低电平的时间更长,请问这个对传输有无影响?
* 可否把程序改成下面的样子,即SDA_Hight-delay,然后for循环( scl hight——delay——scl low——delay)这样就不会出现重复delay的情况。
不知道这样是否正确?
修改成以下样子,是否正确?
修改的程序
原程序:
//读一字节 ack: 1时应答,0时不应答
unsigned char I2C_read(unsigned char ack)
{
unsigned char i,ret;
SDA_Hight;
I2C_Delay;                                        //这一句原来在for循环的开始,挪到此处
for(i=0;i<8;i++)
{
            
    SCL_Low;                  
    I2C_Delay;
    SCL_Hight;               
    I2C_Delay;
    ret<<=1;
    if(SDA_Input)
      ret++;
}
SCL_Low;   
I2C_Delay;
if(ack)      //非应答
    SDA_Low;            
else          //应答
    SDA_Hight;
I2C_Delay;
SCL_Hight;
I2C_Delay;                  
SCL_Low;                     
I2C_Delay;
return(ret);
}



http://cache.amobbs.com/bbs_upload782111/files_46/ourdev_679431KFUV3S.JPG
I2C时序 (原文件名:IMG_3908.JPG)

http://cache.amobbs.com/bbs_upload782111/files_46/ourdev_679432DYIURT.JPG
有疑问的程序(马老师的书上第2版484页) (原文件名:IMG_3909.JPG)

machao 发表于 2011-9-28 23:39:18

* 重新开始执行for循环的时候(即for的首尾相接时),会出现前后两个delay连在一起的情况,导致scl处于低电平的时间更长,请问这个对传输有无影响?

==》对传输无影响,稍微影响系统的运行效率

* 可否把程序改成下面的样子,即SDA_Hight-delay,然后for循环( scl hight——delay——scl low——delay)这样就不会出现重复delay的情况。
不知道这样是否正确?

可以,更加好些。

ahit 发表于 2011-9-29 08:13:34

谢谢马老师的解答,这么晚还专门回帖子!
看了之前马老师对类似问题的解答,现在对spi的时序稍稍明白了一些。正在用实验板验证中...

millwood0 发表于 2011-10-1 21:26:18

some quick comments:


"//读一字节 ack: 1时应答,0时不应答
unsigned char I2C_read(unsigned char ack) "

define two constants:
#define I2C_ACK 0 //send ack
#define I2C_NOACK 1 //send noack

so when you call I2C_read(), use I2C_read(I2C_ACK); or I2C_read(I2C_NOACK); to make the code easier to read and more portable.


"SDA_Hight; "

use SDA_High, not SDA_Hight - that "t" makes zero sense and sounds silly.

"I2C_Delay;                                        //这一句原来在for循环的开始,挪到此处 "

I would implement that delay into SDA_HIGH or SDA_LOW, just to make it more readable.

"for(i=0;i<8;i++) "

I would use mask

"    if(SDA_Input)
      ret++;   "

this will yield uneven timing: slower when SDA_Input = 1 and faster when SDA_Input = 0. try this

    if(SDA_Input) ret+=1;   
    else ret+=0;


"if(ack)      //非应答
    SDA_Low;            
else          //应答
    SDA_Hight; "

use

   if(I2C_ACK==ack) //send ack
   SDA_Low;
   else
   SDA_High;

for stylistic purposes, I would use I2C_LOW(I2C_SCL / I2C_SDA) or I2C_HIGH(I2C_SCL / I2C_SDA) instead. they are far more consistent than what you have here.

this is what I do:

//-----------------------i2c read------------------------------
//to be consistent with i2c protocol, use negative logic
//ack = 0 -> send ack
//ack = 1 -> no ack
unsigned char i2c_read(unsigned char ack) {
        unsigned char i, data_t=0;

        I2C_HIZ(I2C_SDA);                                                //let sda float
        i=0x80;
        do {
                I2C_LOW(I2C_SCL);                                        //clear i2c_scl
                data_t <<=1;                                                        //left shift the data
                i = i >> 1;
                I2C_HIGH(I2C_SCL);                                        //let scl float to high
                if (I2C_PORT_IN & I2C_SDA) data_t |= 0x01;        //set the last bit high
                else data_t |= 0x00;
        } while (i);
        I2C_LOW(I2C_SCL);                                                //pull scl low
        if (ack==I2C_ACK)
                {I2C_LOW(I2C_SDA);}                                        //send ack
        else
                {I2C_HIGH(I2C_SDA);}                                //send no-ack
        I2C_HIGH(I2C_SCL);                                                //send ack/no-ack
        I2C_LOW(I2C_SCL);
        return data_t;
}

ahit 发表于 2011-10-6 14:41:20

回复【3楼】millwood0
-----------------------------------------------------------------------

非常感谢millwood0的回复和指导!

关于程序的可读性、程序结构的清晰(无歧义)您的建议非常好,受教了!

if (ack==I2C_ACK) 这个非常好
而且if else 总是加{ },好!这个是GNU风格么?

另外您好像比较喜欢用do...while
是这样好还是个人喜好?
页: [1]
查看完整版本: 请教关于I2C总线时序的问题