打印

利用GPIO口模拟I2C总线的时序及代码实现,非常详细。可以直接移植!

[复制链接]
2054|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
8号菜刀|  楼主 | 2014-5-12 16:57 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
/*
        作者:寻找真爱
        日期:2014.5.12
        功能:利用GPIO口模拟I2C总线,对传感器寄存器读取数据
        注意:如果需要移植些文件到其他设备上,需要更改的函数地方
        1、利用GPIO口模拟的数据线SDA和时钟线SCL,注意需要更改GPIO口
        2、从设备地址。不同的从设备,地址不一样,注意修改。(在I2C写寄存器和读寄存器的函数中,如果是写,在读的地址上加1.)
        经验:利用GPIO口模拟I2C总线时,需要注意的是,在应答信号和读数据的时候,一定要将SDA数据线配置为输入。
*/
#define SCL P0_5//定义P0.5口为时钟线
#define SDA P0_4//定义P0.4口为数据线

typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned short ushort;

/*延时函数*/
void Delay(void)
{
}

/*I2C总线实始化,将P0.4和P0.5配置为输出口,并且拉高*/
/*
        I2C总线的开始时序:
        1、将数据线SDA和时钟线SCL全部拉高
*/
void I2C_Init(void)
{
    P0SEL &= ~0x30;//将P0.4和P0.5配置为GPIO口
    P0DIR |= 0x30;//将P0.4和P0.5配置为输出
   
    P0 |= 0x30;//将P0.4和P0.5设置为高电平
}

/*I2C总线的开始信号*/
/*
        I2C总线的开始时序:
        1、将SDA数据线拉高
        2、将SCL时钟线拉高,延时一段时间
        3、在SCL为高电平时,拉低SDA,延时一段时间
        总结:就是在SCL为高电平时,在SDA上给一个下降沿,表示开始信号。
*/
void I2C_Start(void)
{
    SDA = 1;
    SCL = 1;
    Delay();
    SDA = 0;
    Delay();
}

/*I2C总线的停止信号*/
/*
        I2C总线的停止时序:
        1、将SDA数据线拉低
        2、将SCL数据线拉高,延时一段时间
        3、在SCL为高电平时,将SDA数据线拉高,延时一段时间
        总结:在SCL为高电平时,在SDA上给一下上升沿,表示停止信号。
*/
void I2C_Stop(void)
{
    SDA = 0;
    SCL = 1;
    Delay();
    SDA = 1;
    Delay();
}


/*I2C总线的应答信号*/
/*
        I2C总线应答时序:
        1、首先是需要将模拟数据线的SDA口配置为输入口。
        2、将时钟线SCL拉高,延时一段时间
        3、等待从设备应答,从设备应答时,SDA上会产生一个高电平,主设备的GPIO口检测到高电平,则表示应答成功。
        4、如果检测到应答信号,则拉SCL时钟线拉低。延时一段时间。
        5、将模拟SDA口的GPIO口配置为输出。
*/
void I2C_Ack(void)
{
    P0DIR &= ~0x10;//注意这里,数据线应该设置为输入
    uint i = 0;
   
    SCL = 1;
    Delay();
    //等待应答
    while((SDA == 1)&&(i<255))
      i++;
      
    SCL = 0;
    Delay();
    P0DIR |= 0x10;//将数据线SDA设置为输出
}

/*I2C总线写数据函数,将要写的数据传递给形参I2CBuf*/
/*
        I2C写数据时序:
        注意:由于I2C属于串行总线,所以数据都是一位一位的传输
        1、将SCL时钟线拉高,准备传输数据,延时一段时间
        2、将I2CBuf的最高位传输到SDA数据线上
        3、拉低SCL时钟线,延时一段时间
        小结:当SCL有一个上升沿时,传输数据。
        以上三步表示传输完一位数据。然后将I2CBuf左移一位,再重复以上三步。
        最后,将SCL位低,延时一段时间,再将SDA位高,延时一段时间。此操作是为了释放时钟线SCL和数据线SDA.
*/
void I2C_Write(uint I2CBuf)
{
    uint iwi;
    for(iwi = 0; iwi < 8; iwi++)
    {
        SCL = 0;
        Delay();
        if(I2CBuf & 0x80)
          SDA = 1;
        else
          SDA = 0;
        SCL = 1;
        Delay();
        I2CBuf = I2CBuf << 1;
        
    }
   
    SCL = 0;
    Delay();
    SDA = 1;
    Delay();
   

}

/*I2C总线读数据函数,该函数具有返回值I2CBuf*/
/*
        I2C读数据函数的时序:
        注意:首先需要将模拟SDA数据线的GPIO口配置为输入
        1、将时钟线SCL拉高,延时一段时间
        2、读取数据线SDA上的电平,然后写入I2CBuf的最低位。
        3、拉低SCL时钟线,延时一段时间
        小结:当SCL有一个下降沿时,读取数据。
        以上三步表示读取一位数据,读一个字节,需要重复以上操作步骤8次。
*/
uint I2C_Read(void)
{
    uint iri;
    uint I2CBuf = 0;

    P0DIR &= ~0x10;//注意这里,需要将SDA配置为输入。
    Delay();

    for(iri = 0; iri < 8; iri++)
    {
        I2CBuf = I2CBuf << 1;//此步不可少
        SCL = 1;
        Delay();
        if(SDA == 1)
          I2CBuf |= 0x01;
        else
          I2CBuf &= 0xfe;
        SCL = 0;
        Delay();
    }
   
    P0DIR |= 0X10;//将SDA配置为输出
    return I2CBuf;//将读到的数据存入I2CBuf中,然后返回给主调函数
   
}

/*
I2C总线写寄存器函数,将寄存器地址传给形参RegAdd,将要写入的数据传递给RegValue。
注意:地址和数据最好都采用十六进制
*/

/*
        I2C写寄存器函数的时序:
        1、主设备先发出一个开始信号
        2、然后写入从设备地址。每一个从设备的地址是唯一的。
        3、主设备接收应答信号
        4、主设备向从设备写入寄存器的地址
        5、主设备接收应答信号
        6、主设备向从设备写入数据
        7、主设备接收应答信号
        8、主设备发出停止信号
*/
void I2C_WriteReg(uint RegAdd, uint RegValue)
{
    I2C_Start();
    I2C_Write(0xd0);//0xd0是从设置的地址,不同的设备,地址不同。所以移植些函数时,需要更改此处。
    I2C_Ack();
    I2C_Write(RegAdd);//先写地址,此地址为需要写入数据的寄存器地址
    I2C_Ack();
    I2C_Write(RegValue);//写入数据
    I2C_Ack();
    I2C_Stop();
}

/*I2C总线读寄存器函数,将需要读取数据的寄存器的地址传递给形参RegAdd,此函数有返回值*/

/*
        I2C读寄存器函数的时序:
        1、主设备发出开始信号
        2、写入从设备地址
        3、主设备接收从设备发来的应答信号
        4、写入寄存器地址
        5、主设备接收应答信号
        6、主设备再次发出开始信号
        7、主设备向从设备写入(从设备的地址+1),此时加1表示要从寄存器中读数据
        8、从设备给出应答信号
        9、从从设备的寄存器中读出数据传给变量
        10、主设备发出停止信号
        11、返回读出的数据
*/
uint I2C_ReadReg(uint RegAdd)
{
    uchar date;
    I2C_Start();
    I2C_Write(0xd0);//先写入从设备的地址。表示要向从设备中写地址和数据
    I2C_Ack();
    I2C_Write(RegAdd);//写入寄存器的地址,数据将从些地址中读出
    I2C_Ack();
    I2C_Start();
    I2C_Write(0xd1);//从设备的地址加1,表示要读从设备中寄存器的数据
    I2C_Ack();
    date = I2C_Read();//从地址RegAdd中读出数据
    I2C_Stop();
    return date;//将读出的数据存入date中,然后返回给主调函数。
}

相关帖子

沙发
mmuuss586| | 2014-5-12 20:45 | 只看该作者
挺好的,再接再励。
要是哪天,你能用IO模拟USB就牛了;

使用特权

评论回复
板凳
mmuuss586| | 2014-5-12 20:46 | 只看该作者
用IO模拟串口,USB也挺好玩的;

使用特权

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

本版积分规则

1

主题

22

帖子

0

粉丝