打印

软件模拟I2C总线

[复制链接]
1422|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
JerryWu75|  楼主 | 2017-1-14 15:27 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
  周末放松,在家整理工作资料. 看到一段2000年前后编写,并且现在偶尔使用的代码,拿出来给大家分享.  这是一个软件模拟I2C总线代码,用于通过GPIO口驱动类似于AT24C02一类的器件。
  该代码使用标准C语言编写,已经在Atmel,Renases,NEC,TI,TOSHIBA,ST,Microchip等多家公司的不同型号8位,16位,以及32位的MCU,DSP,Cortext-M3/M4上使用。
  该代码驱动AT24C02,BQ32000,PCF8563等I2C总线的器件。

  这是我参加工作后写的第一段设备驱动代码,后来换了多次工作,不同的平台,不同的产品,不同的应用,一直使用没有作过太多的修改。

=======================================================================================================
//以下是I2C.H内容
//根据不同的芯片平台,实现vWatchDog函数
extern void vWatchDog(void);
//I2C_SCK的GPIO定义
#define I2CSCLH     P1.6=1
#define I2CSCLL     P1.6=0
//I2C_SDA的GPIO定义
#define I2CSDA      P1.7
#define I2CSDAH     P1.7=1
#define I2CSDAL     P1.7=0
//转换I2C_SDA的GPIO方向
#define I2CSDAOUT   PM1.7=0
#define I2CSDAIN    PM1.7=1

extern void vI2CInit(void);
extern void vI2CDelay(void);
extern void vI2CSendReStart(void);
extern void vI2CSendStart(void);
extern void vI2CSendStop(void);
extern void vI2CRdWtACK(void);
extern unsigned char ucI2CWaitACK(void);
extern void vI2CSendByte(unsigned char);
extern unsigned char ucI2CRecvByte(void);

========================================================================================================
//以下是I2C.C内容
//初始化I2C总线
void vI2CInit(void)
{
        //这里进行GPIO口的设置,并初始化I2C总线
        I2CSCLH;
        I2CSDAH;
        I2CSDAOUT;
        return ;
}
//软件延时,这个延时函数并不精确,如果需要精确延时可以使用MCU的定时器来实现
//
void vI2CDelay(void)
{
        register unsigned char ucI;
        ucI=20;
        while(ucI){
                vWatchDog(); //清除看门狗,防止看门狗溢出导致MCU复位
                --ucI;
        }
        return ;
}
//重新发送start信号
void vI2CSendReStart(void)
{
        I2CSDAOUT;
        I2CSDAH;
        vI2CDelay();
        I2CSCLH;
        vI2CDelay();
        vI2CDelay();
        I2CSDAL;
        return ;
}
//发送start信号
void vI2CSendStart(void)
{
        I2CSDAOUT;
        I2CSCLH;
        vI2CDelay();
        I2CSDAL;
        return ;
}
//发送stop信号
void vI2CSendStop(void)
{
        register unsigned int uI;

        I2CSDAOUT;
        I2CSDAL;
        I2CSCLH;
        vI2CDelay();
        I2CSDAH;
        vI2CDelay();
        vI2CDelay();

        uI=1000;
        while(uI){
                vWatchDog(); //清除看门狗,防止看门狗溢出导致MCU复位
                --uI;
        }
        I2CSCLH;
        I2CSDAH;
        return ;
}
//发送ACK信号
void vI2CRdWtACK(void)
{
        I2CSDAOUT;
        vI2CDelay();
        I2CSDAL;
        I2CSCLH;
        vI2CDelay();
        vI2CDelay();
        I2CSCLL;
        vI2CDelay();
        vI2CDelay();
        return ;
}
//等待ACK信号
unsigned char ucI2CWaitACK(void)
{
        unsigned char ucI2CNoAck=0;
        I2CSCLL;
        vI2CDelay();
        I2CSDAH;
        I2CSDAIN;
        vI2CDelay();
        vI2CDelay();
        I2CSCLH;
        vI2CDelay();
        vI2CDelay();
        if(I2CSDA){
                ucI2CNoAck=1; //没有等到ACK
        }
        I2CSCLL;
        vI2CDelay();
        I2CSDAL;
        vI2CDelay();
        vI2CDelay();
        return ucI2CNoAck; //返回ACK状态
}
//发送一个字节数据到I2C总线
void vI2CSendByte(unsigned char ucI2CTmp)
{
        register unsigned char ucI;
        I2CSDAOUT;
        vI2CDelay();
        for(ucI=0;ucI<8;ucI++){
                I2CSCLL;
                vI2CDelay();
                if(ucI2CTmp&0x80){
                        I2CSDAH;
                } else {
                        I2CSDAL;
                }
                ucI2CTmp<<=1;
                vI2CDelay();
                I2CSCLH;
                vI2CDelay();
        }
        return ;
}
//从I2C总线读取一个字节的数据
unsigned char ucI2CRecvByte(void)
{
        register unsigned char ucI,ucI2CTmp;
        I2CSCLL;
        I2CSDAH;
        I2CSDAIN;
        vI2CDelay();
        vI2CDelay();
        for(ucI=0;ucI<8;ucI++){
                I2CSCLH;
                vI2CDelay();
                ucI2CTmp<<=1;
                if(I2CSDA){
                        ucI2CTmp|=0x01;
                } else {
                        ucI2CTmp&=0xfe;
                }
                I2CSCLL;
                vI2CDelay();
                vI2CDelay();
        }
        return ucI2CTmp;
}


相关帖子

沙发
JerryWu75|  楼主 | 2017-1-14 15:28 | 只看该作者
应用代码:

#include "i2c.h"

void vReadI2C(void)
{
        unsigned char ucI2CAck=0;
        vI2CSendStart(); /* Start the I2C. */
        vI2CSendByte(0xa0); /* Send the device addr. */
        ucI2CAck+=ucI2CWaitACK();
        vI2CSendByte(0x00); /* Send the data addr. */
        ucI2CAck+=ucI2CWaitACK();
        vI2CDelay();
        vI2CDelay();
        vI2CDelay();
        vI2CSendReStart(); /* Re-start. */
        vI2CSendByte(0xa1); /* Read. */
        ucI2CAck+=ucI2CWaitACK();

        ucI2CRecvByte(); //Read a byte data
        ucI2CAck+=ucI2CRdWtACK();

        ucI2CRecvByte();

        vI2CDelay();
        vI2CSendStop(); /* Stop the I2C. */
        vI2CDelay();
        vI2CDelay();
        if(ucI2CAck!=0){ //I2C没有应答
                //错误处理
        }
        return ;
}

使用特权

评论回复
板凳
JerryWu75|  楼主 | 2017-1-14 15:32 | 只看该作者
本帖最后由 JerryWu75 于 2017-1-14 15:33 编辑

void WriteI2C()
{
    unsigned char ucI2CAck=0;
    vI2CSendStart();
    vI2CSendByte(0xa0);
    ucI2CAck+=ucI2CWaitACK();
    vI2CSendByte(0x00); //数据地址
    ucI2CAck+=ucI2CWaitACK();
    vI2CSendByte(0x01); //数据
    ucI2CAck+=ucI2CWaitACK();

    vI2CSendStop();
    vI2CDelay();
    vI2CDelay();    if(ucI2CAck!=0){
        //ACK错误,进行错误处理
    }
}

使用特权

评论回复
地板
yan心跳| | 2017-1-14 18:18 | 只看该作者
很棒,干货

使用特权

评论回复
5
这条路能走多远| | 2017-1-14 22:33 | 只看该作者
你好楼主,我问个关于I2C采集传感器的问题,希望解能答下我的疑惑。ADXL345有个数据输出速率,对应匹配了I2C或SPI最低的通信速率,我想问是不是如果I2C或SPI通信速率已经满足且超过了最低的速率,当单片机主动采集传感器的数据时,收到的是无应答信号,总之,传感器输出速率1S有多少个 ,单片机就1S收多少个。还是我理解错误??

使用特权

评论回复
6
JerryWu75|  楼主 | 2017-1-15 11:53 | 只看该作者
这条路能走多远 发表于 2017-1-14 22:33
你好楼主,我问个关于I2C采集传感器的问题,希望解能答下我的疑惑。ADXL345有个数据输出速率,对应匹配了I2 ...

没有用过这个器件,所以只能从数据手册来分析。
从数据手册看,其I2C的通信速率能够支持100kHz和400kHz的标准I2C协议速率模式。这个事实上和采样速率是没有关系的。I2C速率定义了I2C_SCK一个时钟周期的时间长度。而采样速率定义的是芯片内部的ADC多久更新一次结果寄存器。
例如寄存器0x32,是X轴数据0寄存器,芯片以X(0.1Hz~3200Hz)的速率更新这个寄存器。
而你的程序以I2C通信速率通过I2C总线读取这个寄存器。

具体到你收到的无应信号,这个要看你的I2C的信号是否正确。
另外需要关注芯片的0x38和0x39寄存器的设置,在读之前需要先判断FIFO是否有数据。

使用特权

评论回复
7
justtest111| | 2017-1-15 12:19 | 只看该作者
用Delay来实现延时的话通讯速率是无法实现标准速率的吧?

使用特权

评论回复
8
JerryWu75|  楼主 | 2017-1-15 12:21 | 只看该作者
本帖最后由 JerryWu75 于 2017-1-15 12:31 编辑
justtest111 发表于 2017-1-15 12:19
用Delay来实现延时的话通讯速率是无法实现标准速率的吧?

这个其实没有太大的关系,100kHz或者400kHz是指I2C器件允许的最小SCK周期,在这个速率以下进行通信都不会有问题。

至少从我这么多年的应用来看是这样。
如果需要实现标准速率,最好通过定时器来实现,当然如果有内置的I2C硬件来实现更好。

使用特权

评论回复
9
这条路能走多远| | 2017-1-15 16:51 | 只看该作者
JerryWu75 发表于 2017-1-15 11:53
没有用过这个器件,所以只能从数据手册来分析。
从数据手册看,其I2C的通信速率能够支持100kHz和400kHz的 ...

我本来也想通过那个dataready 或者overun中断来采集数据,数据手册上说有新数据更新dataxzy的寄存器,就置位,可是我单片机中断是跳变沿方式的,总是测不到。
你说看那个38,39寄存器的方法我没用过,相当一种查询中断吧。
因为买传感器时候,卖家给了个51的测试程序,它的单字节发送,等待了个信号,直接返回。不做判断就继续收发了。所以我当时觉得纳闷。囫囵吞枣没有管,觉得反正显示的数也正确。现在用到ST芯片就越想越觉得不对,应该是数采多了。(没采到,但却用上一次存储的值)
我I2C应该是没错的 因为我可以单次读取到传感器的地址。
楼主能不能留下关于数据处理的方法,是不是一定要用到操作系统?

使用特权

评论回复
10
JerryWu75|  楼主 | 2017-1-16 13:33 | 只看该作者
这条路能走多远 发表于 2017-1-15 16:51
我本来也想通过那个dataready 或者overun中断来采集数据,数据手册上说有新数据更新dataxzy的寄存器,就 ...

感觉不是I2C通信的问题。
建议你发个新贴,详细地描述下你的问题来专门讨论.

使用特权

评论回复
11
feelhyq| | 2017-1-16 14:22 | 只看该作者
但是如果在实时操作系统里面用 软件模拟I2C有时候不能很好的应用,比如在惯性导航的时候,数据量比较大的时候需要使用到DMA+I2C从而释放出CPU,但是一旦适用软件模拟I2C,也许会一直阻塞住CPU,导致事实性比较差。

使用特权

评论回复
12
这条路能走多远| | 2017-1-16 14:55 | 只看该作者
JerryWu75 发表于 2017-1-16 13:33
感觉不是I2C通信的问题。
建议你发个新贴,详细地描述下你的问题来专门讨论. ...

发了也没人回我。。你能回我很不错了。。哎。。我发过拉~~我还是自己慢慢排除问题吧。。

使用特权

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

本版积分规则

个人签名:10年以上嵌入式系统软/硬件开发. MCU-DSP-ARM-FPGA,汇编-C-linux. SZ AS-AI

16

主题

410

帖子

14

粉丝