STM32 I2C通信

1. 基本信息

1.1 I2C简介

项目说明
I2C (Inter IC Bus)由Philips公司开发的一种通用数据总线
通信线两根:SCL (Serial Clock)、SDA (Serial Data)
通信特性同步,半双工
数据验证带数据应答
设备支持支持总线挂载多设备(一主多从、多主多从)

1.2 硬件电路

项目说明
连接方式所有I2C设备的SCL连在一起,SDA连在一起
GPIO配置设备的SCL和SDA均要配置成开漏输出模式
上拉电阻SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

2. I2C时序基本单元

2.1 起始和终止条件

  • 起始条件:SCL高电平期间,SDA从高电平切换到低电平
  • 终止条件:SCL高电平期间,SDA从低电平切换到高电平

2.2 数据传输

操作描述
发送一个字节SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
接收一个字节SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
发送应答主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

3. I2C时序操作

操作类型描述
指定地址读对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
指定地址写对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
当前地址读对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

4. I2C实现

4.1 I2C软件实现

4.1.1 初始化

c
//I2C软件GPIO初始化
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_10 | GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}

4.1.2 基本操作函数

c
//SCL/SDA写函数
void MyI2C_W_SCL(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
    Delay_us(10);
}

void MyI2C_W_SDA(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
    Delay_us(10);
}

//SDA读函数
uint8_t MyI2C_R_SDA(void)
{
    uint8_t BitValue;
    BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
    Delay_us(10);
    return BitValue;
}

//开启I2C
void MyI2C_Start(void)
{
    MyI2C_W_SDA(1);
    MyI2C_W_SCL(1);
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(0);
}

//停止I2C
void MyI2C_Stop(void)
{
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(1);
    MyI2C_W_SDA(1);
}

//写入一个字节
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);
    }
}

//接收字节
uint8_t MyI2C_ReceiveByte(uint8_t AckBit)
{
    uint8_t i, Byte = 0;
    MyI2C_W_SDA(1);
    for (i = 0; i < 8; i++)
    {
        MyI2C_W_SCL(1);
        Byte |= (MyI2C_R_SDA() << (7 - i));
        MyI2C_W_SCL(0);
    }
    MyI2C_SendAck(AckBit);
    return Byte;
}

//发送应答
void MyI2C_SendAck(uint8_t AckBit)
{
    MyI2C_W_SDA(AckBit);
    MyI2C_W_SCL(1);
    MyI2C_W_SCL(0);
}

//接收应答
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;
}

4.2 I2C硬件实现

4.2.1 配置步骤

  1. 开启I2C/GPIO时钟
  2. 配置GPIO
  3. 配置I2C
  4. 使能I2C

4.2.2 初始化代码

c
void HardI2C2_Init(void)
{
    //使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    //初始化GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    /*配置I2C
    *1.I2C模式
    *2.占空比2:1
    *3.主机模式,不需要自己写地址
    *4.使能应答
    *5.7位地址
    *6.通讯频率为50MHz
    */
    I2C_InitTypeDef I2C_InitStructure;
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress1 = 0x00;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed = 50000;
    I2C_Init(I2C2, &I2C_InitStructure);
    
    I2C_Cmd(I2C2, ENABLE);
}

4.2.3 辅助函数

c
/**
 * @brief  等待I2C事件函数
 * @param  Event: 要等待的I2C事件
 * @param  Timeout: 超时时间(毫秒)
 * @retval 0: 事件等待成功
 * @retval 1: 事件等待超时
 * @description 等待指定的I2C事件发生,若超时则返回错误
 */
uint8_t HardI2C2_WaitEvent(uint32_t Event, uint32_t Timeout)
{
    uint32_t start_time = GetTick();
    while (!I2C_CheckEvent(I2C2, Event)) {
        if ((GetTick() - start_time) > Timeout) {
            return 1;
        }
    }
    return 0;
}

/**
 * @brief  发送I2C起始条件函数
 * @param  DevAddr: 设备地址
 * @param  Direction: 通信方向(0: 写操作, 1: 读操作)
 * @retval 0: 操作成功
 * @retval 1: 操作失败
 * @description 发送I2C起始条件并寻址设备,设置通信方向
 */
uint8_t HardI2C2_Start(uint8_t DevAddr, uint8_t Direction)
{
    I2C_GenerateSTART(I2C2, ENABLE);
    if (HardI2C2_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT, 1000)) {
        return 1;
    }
    
    I2C_Send7bitAddress(I2C2, DevAddr, Direction ? I2C_Direction_Receiver : I2C_Direction_Transmitter);
    
    if (Direction) {
        if (HardI2C2_WaitEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED, 1000)) {
            return 1;
        }
    } else {
        if (HardI2C2_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED, 1000)) {
            return 1;
        }
    }
    
    return 0;
}

/**
 * @brief  发送I2C停止条件函数
 * @param  无
 * @retval 无
 * @description 发送I2C停止条件,结束通信
 */
void HardI2C2_Stop(void)
{
    I2C_GenerateSTOP(I2C2, ENABLE);
    delay_us(10);
}

/**
 * @brief  发送一个字节函数
 * @param  Byte: 要发送的字节数据
 * @retval 0: 发送成功
 * @retval 1: 发送失败
 * @description 发送一个字节数据并等待发送完成
 */
uint8_t HardI2C2_SendByte(uint8_t Byte)
{
    I2C_SendData(I2C2, Byte);
    if (HardI2C2_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED, 1000)) {
        return 1;
    }
    return 0;
}

/**
 * @brief  接收一个字节函数
 * @param  Ack: 应答标志(0: 发送应答, 1: 发送非应答)
 * @retval 接收到的字节数据
 * @description 接收一个字节数据并发送应答
 */
uint8_t HardI2C2_ReceiveByte(uint8_t Ack)
{
    if (Ack == 0) {
        I2C_AcknowledgeConfig(I2C2, ENABLE);
    } else {
        I2C_AcknowledgeConfig(I2C2, DISABLE);
        HardI2C2_Stop();
    }
    
    if (HardI2C2_WaitEvent(I2C_EVENT_MASTER_BYTE_RECEIVED, 1000)) {
        return 0;
    }
    
    return I2C_ReceiveData(I2C2);
}

4.2.4 读写操作函数

c
/**
 * @brief  向设备寄存器写入一个字节函数
 * @param  DevAddr: 设备地址
 * @param  RegAddr: 寄存器地址
 * @param  Data: 要写入的数据
 * @retval 0: 写入成功
 * @retval 1: 写入失败
 * @description 向指定设备的指定寄存器写入一个字节数据
 */
uint8_t HardI2C2_WriteByte(uint8_t DevAddr, uint8_t RegAddr, uint8_t Data)
{
    if (HardI2C2_Start(DevAddr, 0)) {
        HardI2C2_Stop();
        return 1;
    }
    
    if (HardI2C2_SendByte(RegAddr)) {
        HardI2C2_Stop();
        return 1;
    }
    
    if (HardI2C2_SendByte(Data)) {
        HardI2C2_Stop();
        return 1;
    }
    
    HardI2C2_Stop();
    return 0;
}

/**
 * @brief  从设备寄存器读取一个字节函数
 * @param  DevAddr: 设备地址
 * @param  RegAddr: 寄存器地址
 * @param  Data: 存储读取数据的指针
 * @retval 0: 读取成功
 * @retval 1: 读取失败
 * @description 从指定设备的指定寄存器读取一个字节数据
 */
uint8_t HardI2C2_ReadByte(uint8_t DevAddr, uint8_t RegAddr, uint8_t *Data)
{
    if (HardI2C2_Start(DevAddr, 0)) {
        HardI2C2_Stop();
        return 1;
    }
    
    if (HardI2C2_SendByte(RegAddr)) {
        HardI2C2_Stop();
        return 1;
    }
    
    if (HardI2C2_Start(DevAddr, 1)) {
        HardI2C2_Stop();
        return 1;
    }
    
    *Data = HardI2C2_ReceiveByte(1);
    return 0;
}

/**
 * @brief  从设备寄存器读取多个字节函数
 * @param  DevAddr: 设备地址
 * @param  RegAddr: 起始寄存器地址
 * @param  Data: 存储读取数据的缓冲区
 * @param  Len: 要读取的字节数
 * @retval 0: 读取成功
 * @retval 1: 读取失败
 * @description 从指定设备的指定寄存器开始读取多个字节数据
 */
uint8_t HardI2C2_ReadMultiBytes(uint8_t DevAddr, uint8_t RegAddr, uint8_t *Data, uint8_t Len)
{
    uint8_t i;
    
    if (HardI2C2_Start(DevAddr, 0)) {
        HardI2C2_Stop();
        return 1;
    }
    
    if (HardI2C2_SendByte(RegAddr)) {
        HardI2C2_Stop();
        return 1;
    }
    
    if (HardI2C2_Start(DevAddr, 1)) {
        HardI2C2_Stop();
        return 1;
    }
    
    for (i = 0; i < Len; i++) {
        if (i == Len - 1) {
            Data[i] = HardI2C2_ReceiveByte(1);
        } else {
            Data[i] = HardI2C2_ReceiveByte(0);
        }
    }
    
    return 0;
}