I2C的基本用法

1、I2C通信协议

1.1 概述

I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线。共包含两根通信线:SCL(Serial Clock)、SDA(Serial Data)。属于同步通信,支持半双工带数据应答。支持总线挂载多设备可以支持一主多从和多住多从模式。本文主要介绍一主多从,读写一个字节的情况。

1.2 硬件电路

所有I2C设备的SCL连在一起,SDA连在一起。
在这里插入图片描述
设备的SCL和SDA均要配置成开漏输出模式。各添加一个上拉电阻,阻值一般为4.7KΩ左右。当设备输出低电平时,总线上的电位被拉低。输出高电平时,设备的IO口处于高组态,总线上的电位被外部电路上拉至高电位。
开漏输出只有驱动低电平的能力,无法驱动高电平。这能够有效避免总线上一个设备输出高电平,一个输出低电平时导致短路的情况。
在这里插入图片描述

1.3 I2C时序

主机对SCL有绝对的掌控权,可以决定是否释放SDA。谁发送数据,谁占用SDA。

1.3.1 I2C时序基本单元

空闲状态:在I2C总线没有数据收发的情况下,SCL和SDA都处于高电位(由外接的上拉电阻驱动,IO口输出1,在开漏模式下位高阻态)。
起始条件:在SCL高电平持续期间,SDA产生下降沿(高电位到低电位的跳变)
结束条件:在SCL高电平持续期间,SDA产生上升沿(低电位到高电位的跳变)
I2C的起始条件和结束条件
发送一个字节:SCL低电平,主机通过SDA发送一位数据,SCL高电平,从机读取一位数据。循环八次。高位先行。不允许在SCL高电平期间去改变SDA的电位。

接收一个字节:SCL低电平,从机通过SDA发送一位数据,SCL高电平,主机读取一位数据。循环八次。高位先行。不允许在SCL高电平期间去改变SDA的电位。(主机接收数据前,需要释放SDA的控制权)
发送一个字节
应答的接收和发送:数据接收方需要在接收到每个字节数据后发送应答位。数据0表示应答,数据1表示非应答。谁接收数据,谁发送应答位

在这里插入图片描述

1.3.1 I2C时序

(1)指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
在这里插入图片描述
①主机发送起始信号
②主机发送指定设备的地址码(7位)+ 写(0),主机释放SDA
③从机发送应答位0,主机接收应答
④主机发送Slave的指定地址(Reg Address),主机释放SDA
⑤从机发送应答位0,主机接收应答
⑥主机发送数据(Data),主机释放SDA
⑦从机发送应答位0,主机接收应答
…(可以循环执行⑥⑦)
⑧结束通信,主机发送停止信号。
(2)当前地址读
在这里插入图片描述
①主机发送起始信号
②主机发送指定设备的地址码(7位)+ 读(1),主机释放SDA
③从机发送应答位0,主机接收应答,
④从机占用SDA,发送数据(Data),从机机释放SDA
⑤主机发送应答位1,发送停止信号
(3)指定地址读
将上面两个时序组合起来,先发送指定地址写,来将当前地址设置为指定位置。
在这里插入图片描述
①主机发送起始信号
②主机发送指定设备的地址码(7位)+ 写(0),主机释放SDA
③从机发送应答位0,主机接收应答
④主机发送Slave的指定地址(Reg Address),主机释放SDA
⑤从机发送应答位0,主机接收应答
⑥主机发送停止信号后,发送重新开始信号
⑦主机发送指定设备的地址码(7位)+ 读(1),主机释放SDA
⑧从机发送应答位0,主机接收应答,
⑩从机占用SDA,发送数据(Data),从机机释放SDA
⑪主机发送应答位1,发送停止信号

2、软件模拟

为了实现I2C通信,可以使用软件模拟的方式。这也是实际中常用的一种方式。

#include "MyI2C.h"
#include "Delay.h"

// SCL:PB6, SDA:PB7,软件模拟可以选择任意的GPIO端口
/*
功能:对SCL的写操作
参数:BitValue(uint8_t):发送的位数据,0/1
返回值:无
*/ 
void MyI2C_W_SCL(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_6, (BitAction)BitValue);
    Delay_us(10);
}
/*
功能:对SDA的写操作
参数:BitValue(uint8_t):发送的位数据,0/1
返回值:无
*/ 
void MyI2C_W_SDA(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_7, (BitAction)BitValue);
    Delay_us(10);
}

/*
功能:对SDA的读操作
参数:无
返回值:BitValue(uint8_t):接收的位数据,0/1
*/ 
// 主机对SDA的读操作
uint8_t MyI2C_R_SDA(void)
{
    uint8_t BitValue;
    BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7);
    Delay_us(10);
    return BitValue;
}

// 初始化
void MyI2C_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOF, &GPIO_InitStructure);

    GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7);
}

// 发送起始信号
void MyI2C_Start(void)
{
    MyI2C_W_SDA(1);
    MyI2C_W_SCL(1); 
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(0);
}

// 发送停止信号
void MyI2C_Stop(void)
{
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(1); 
    MyI2C_W_SDA(1);
}

/*
功能:发送一个字节的数据
参数:Byte(uint8_t):发送的字节
返回值:无
*/ 
void MyI2C_SendByte(uint8_t Byte)
{
    uint8_t i;
    for(i = 0; i < 8; i++)
    {
        MyI2C_W_SDA(Byte & (0x80>>i));
        MyI2C_W_SCL(1);
        MyI2C_W_SCL(0);
    }
}

/*
功能:接收一个字节的数据
参数:无
返回值:Byte(uint8_t):接收的字节
*/ 
uint8_t MyI2C_ReceiveByte(void)
{
    uint8_t i, Byte = 0x00;
    MyI2C_W_SDA(1);
    for( i = 0; i < 8; i++)
    {
        MyI2C_W_SCL(1);
        if ( MyI2C_R_SDA() == 1) { Byte |= (0x80 >> i);}
        MyI2C_W_SCL(0);
    }
    return Byte;
}

/*
功能:主机发送应答位
参数:应答位(uint8_t):0表示应答,1表示非应答
返回值:无
*/ 
void MyI2C_SendAck(uint8_t AckBit)
{
    MyI2C_W_SDA(AckBit);
    MyI2C_W_SCL(1);
    MyI2C_W_SCL(0);

}

/*
功能:主机接收应答位
参数:无
返回值:应答位(uint8_t)
*/ 
uint8_t MyI2C_ReceiveAck(void)
{
    uint8_t AckBit;
    MyI2C_W_SDA(1);
    MyI2C_W_SCL(1);
    AckBit = MyI2C_R_SDA();
    MyI2C_W_SCL(0);
    return AckBit;
}

以MPU6050为例,使用I2C通信实现其驱动。

#include "MPU.h"

#define MPU_ADDRESS 0xD0

/*  
功能:将数据写入到MPU指定地址
参数:  RegAddress(uint8_t):寄存器地址;
        Data(uint8_t):要写入的数据
返回值:无
*/
void MPU_WriteReg(uint8_t RegAddress, uint8_t Data)
{
    MyI2C_Start();
    MyI2C_SendByte(MPU_ADDRESS);
    MyI2C_ReceiveAck();
    MyI2C_SendByte(RegAddress);
    MyI2C_ReceiveAck();
    MyI2C_SendByte(Data);
    MyI2C_ReceiveAck();
    MyI2C_Stop();
}

/*  
功能:  读取MPU指定地址的数据
参数:  RegAddress(uint8_t):寄存器地址;
返回值:无
*/
uint8_t MPU_ReadReg(uint8_t RegAddress)
{
    uint8_t Data;
    MyI2C_Start();
    MyI2C_SendByte(MPU_ADDRESS);
    MyI2C_ReceiveAck();
    MyI2C_SendByte(RegAddress);
    MyI2C_ReceiveAck();

    MyI2C_Start();
    MyI2C_SendByte(MPU_ADDRESS | 0x01);
    MyI2C_ReceiveAck();
    Data = MyI2C_ReceiveByte();
    MyI2C_ReceiveAck();
    MyI2C_SendAck(1);
    MyI2C_Stop();

    return Data;
}
/*  
功能:  初始化,配置MPU6050
参数:  无
返回值:无
*/

void MPU_Init(void)
{
    MyI2C_Init();
    MPU_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
    MPU_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
    MPU_WriteReg(MPU6050_SMPLRT_DIV, 0x00);
    MPU_WriteReg(MPU6050_CONFIG, 0x06);
    MPU_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
    MPU_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}

/*  
功能:  读取MPU6050的ID
参数:  无
返回值:MPU6050_WHO_AM_I(uint8_t)寄存器中的值
*/
uint8_t MPU_GetID(void)
{
    return MPU_ReadReg(MPU6050_WHO_AM_I);
}

/*  
功能:  读取MPU6050的X,Y,Z轴方向的加速度和角速度
参数:  用于接收数据的指针
返回值:
*/
void MPU_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
                    int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
    uint16_t DataH, DataL;

    DataH = MPU_ReadReg(MPU6050_ACCEL_XOUT_H);
    DataL = MPU_ReadReg(MPU6050_ACCEL_XOUT_L);
    *AccX = (DataH << 8) | DataL;

    DataH = MPU_ReadReg(MPU6050_ACCEL_YOUT_H);
    DataL = MPU_ReadReg(MPU6050_ACCEL_YOUT_L);
    *AccY = (DataH << 8) | DataL;

    DataH = MPU_ReadReg(MPU6050_ACCEL_ZOUT_H);
    DataL = MPU_ReadReg(MPU6050_ACCEL_ZOUT_L);
    *AccZ = (DataH << 8) | DataL;

    DataH = MPU_ReadReg(MPU6050_GYRO_XOUT_H);
    DataL = MPU_ReadReg(MPU6050_GYRO_XOUT_L);
    *GyroX = (DataH << 8) | DataL;

    DataH = MPU_ReadReg(MPU6050_GYRO_YOUT_H);
    DataL = MPU_ReadReg(MPU6050_GYRO_YOUT_L);
    *GyroY = (DataH << 8) | DataL;

    DataH = MPU_ReadReg(MPU6050_GYRO_ZOUT_H);
    DataL = MPU_ReadReg(MPU6050_GYRO_ZOUT_L);
    *GyroZ = (DataH << 8) | DataL;
}

3、I2C外设

根据数据手册上的流程图,调用相应的库函数。
在这里插入图片描述
在这里插入图片描述

#include "MPU.h"

#define MPU_ADDRESS 0xD0

void MPU_WaitEvent(I2C_TypeDef * I2Cx, uint32_t I2C_EVENT)
{
    uint32_t Timeout;
    Timeout = 10000;
    while( I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
    {
        Timeout -- ;
        if (Timeout == 0)
        {
            break;
        }
    }
}


void MPU_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	
    I2C_GenerateSTART(I2C1, ENABLE);
    MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);

    I2C_Send7bitAddress(I2C1, MPU_ADDRESS, I2C_Direction_Transmitter);
    MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);

    I2C_SendData(I2C1, RegAddress);
    MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING); 

    I2C_SendData(I2C1, Data);
    MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED);

    I2C_GenerateSTOP(I2C1, ENABLE);
}

uint8_t MPU_ReadReg(uint8_t RegAddress)
{
    uint8_t Data;

    I2C_GenerateSTART(I2C1, ENABLE);
    MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);

    I2C_Send7bitAddress(I2C1, MPU_ADDRESS, I2C_Direction_Transmitter);
    MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);

    I2C_SendData(I2C1, RegAddress);
    MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED);

    I2C_GenerateSTART(I2C1, ENABLE);
    MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);

    I2C_Send7bitAddress(I2C1, MPU_ADDRESS, I2C_Direction_Receiver);
    MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);

    I2C_AcknowledgeConfig(I2C1, DISABLE);
    I2C_GenerateSTOP(I2C1, ENABLE);

    MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED);
    Data = I2C_ReceiveData(I2C1);

    I2C_AcknowledgeConfig(I2C1, ENABLE);

    return Data;
}


void MPU_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    I2C_InitTypeDef I2C_InitStructure;
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_ClockSpeed = 50000;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_OwnAddress1 = 0x00;
    I2C_Init(I2C1, &I2C_InitStructure);

    I2C_Cmd(I2C1, ENABLE);


    MPU_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
    MPU_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
    MPU_WriteReg(MPU6050_SMPLRT_DIV, 0x00);
    MPU_WriteReg(MPU6050_CONFIG, 0x06);
    MPU_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
    MPU_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}

uint8_t MPU_GetID(void)
{
    return MPU_ReadReg(MPU6050_WHO_AM_I);
}

void MPU_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
                    int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
    uint16_t DataH, DataL;

    DataH = MPU_ReadReg(MPU6050_ACCEL_XOUT_H);
    DataL = MPU_ReadReg(MPU6050_ACCEL_XOUT_L);
    *AccX = (DataH << 8) | DataL;

    DataH = MPU_ReadReg(MPU6050_ACCEL_YOUT_H);
    DataL = MPU_ReadReg(MPU6050_ACCEL_YOUT_L);
    *AccY = (DataH << 8) | DataL;

    DataH = MPU_ReadReg(MPU6050_ACCEL_ZOUT_H);
    DataL = MPU_ReadReg(MPU6050_ACCEL_ZOUT_L);
    *AccZ = (DataH << 8) | DataL;

    DataH = MPU_ReadReg(MPU6050_GYRO_XOUT_H);
    DataL = MPU_ReadReg(MPU6050_GYRO_XOUT_L);
    *GyroX = (DataH << 8) | DataL;

    DataH = MPU_ReadReg(MPU6050_GYRO_YOUT_H);
    DataL = MPU_ReadReg(MPU6050_GYRO_YOUT_L);
    *GyroY = (DataH << 8) | DataL;

    DataH = MPU_ReadReg(MPU6050_GYRO_ZOUT_H);
    DataL = MPU_ReadReg(MPU6050_GYRO_ZOUT_L);
    *GyroZ = (DataH << 8) | DataL;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值