I2C编程

I2C模块简介

与GPIO和USART一样,I2C也是STM32F103C8T6 的一个片上外设。STM32F103C8T6 的 I2C 模块是一款兼容 I2C v2.1 规范的高性能通信接口,支持标准模式 (100kbps) 和快速模式 (400kbps),可配置为主机或从机模式,具备 7 位 / 10 位地址识别、PEC 错误检测、DMA 传输和中断驱动等功能。该微控制器提供最多 2 个 I2C 接口,通过 PB6/PB7 或 PB8/PB9 (I2C1) 以及 PB10/PB11 (I2C2) 引脚实现,广泛用于连接传感器、显示器、存储器等外设;本节我们使用stm32作为主机,向从机OLED显示器发送数据

I2C模块框图

在这里插入图片描述

  • 数据寄存器:包括发送数据寄存器和接收数据寄存器,作用是暂存要发送(接收)的数据

  • 状态寄存器:I2C模块中共有两组状态寄存器,分别是SR1和SR2,图中状态寄存器的作用是:

    • SB : 起始位发送,指示主模式下起始条件已发送

    • ADDR: 地址发送 / 匹配,指示地址已发送 (主机) 或地址已匹配 (从机)

    • BTF: 字节传输完成,指示一个字节传输完成

    • RxNE: 接收数据寄存器非空,指示接收数据寄存器中有数据

    • TxE: 发送数据寄存器空, 指示发送数据寄存器为空

    • AF : 应答失败

    • BUSY:总线忙,指示 I2C 总线是否忙

  • 控制寄存器:

    • ACK: 应答使能

    • STOP: 停止条件生成

    • START: 起始条件生成

在配置完I2C的占空比,波特率后,SCL控制器就会发送时间脉冲,确保 I2C 总线的数据传输时序正确和通信可靠

接线图

在这里插入图片描述

I2C初始化

引脚初始化

STM32F103C8T6有两个I2C资源,分别是I2C1、I2C2,其中I2C1的默认引脚是PB6(SCL),PB7(SDA),开启重映射后是PB8(SCL),PB9(SDA),I2C2的默认引脚是PB10(SCL),PB11(SDA),无重映射引脚,因为要实现逻辑线与的功能,所有引脚的模式都设置为复用开漏输出

在这里插入图片描述

//对I2C1重映射
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);

    //初始化GPIO引脚
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOB,&GPIO_InitStructure);

I2C模块初始化

  • 速度模式:stm32支持标准模式 (100kbps) 和快速模式 (400kbps)

在这里插入图片描述

  • 占空比:只有在快速模式下才能设置占空比,我们使用默认占空比1/2

在这里插入图片描述

	//使能I2C时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
    //重启I2C模块
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);
    //初始化I2C模块
    I2C_InitTypeDef I2C_InitStructure;
    I2C_InitStructure.I2C_ClockSpeed = 400000;
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_Init(I2C1,&I2C_InitStructure);
    //闭合I2C1总开关
    I2C_Cmd(I2C1,ENABLE);

将GPIO引脚初始化与I2C模块初始化封装成My_I2C_Init函数

void My_I2C_Init(){
    //对I2C1重映射
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
    //初始化GPIO引脚
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOB,&GPIO_InitStructure);

    //使能I2C时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
    //重启I2C模块
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);
    //初始化I2C模块
    I2C_InitTypeDef I2C_InitStructure;
    I2C_InitStructure.I2C_ClockSpeed = 400000;
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_Init(I2C1,&I2C_InitStructure);
    //闭合I2C1总开关
    I2C_Cmd(I2C1,ENABLE);
}

发送数据

发送数据的步骤如下:

  1. 等待总线空闲
  2. 发送起始位
  3. 发送地址
  4. 发送数据
  5. 发送停止位
int My_I2C_SendBytes(uint8_t* Addr,uint8_t* pData,uint16_t size){
    //等待总线空闲
    while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) == SET);

    //发送起始位
    I2C_GenerateSTART(I2C1,ENABLE);
    while(I2C_GetFlagStatus(I2C1,I2C_FLAG_SB) == RESET);

    //寻址阶段
    I2C_ClearFlag(I2C1,I2C_FLAG_AF);
    I2C_SendData(I2C1,Addr&0XFE);
    while(1)
    {
        if(I2C_GetFlagStatus(I2C1,I2C_FLAG_ADDR) == SET)
            break;
        if(I2C_GetFlagStatus(I2C1,I2C_FLAG_AF) == SET)
        {
            I2C_GenerateSTOP(I2C1,ENABLE);
            return -1;
        }
    }
    //清除ADDR(硬件规定)
    I2C_ReadRegister(I2C1,I2C_Register_SR1);
    I2C_ReadRegister(I2C1,I2C_Register_SR2);

    //发送数据
    for(uint16_t i=0;i<size;i++)
    {
        while (1)
        {
            if(I2C_GetFlagStatus(I2C1,I2C_FLAG_AF) == SET)
            {
                I2C_GenerateSTOP(I2C1,ENABLE);
                return -2;
            }
            if(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE) == SET)
                break;
        }
        I2C_SendData(I2C1,pData[i]); 
    }
    while (1)
    {
        if(I2C_GetFlagStatus(I2C1,I2C_FLAG_AF) == SET)
        {
            I2C_GenerateSTOP(I2C1,ENABLE);
            return -2;
        }
        if(I2C_GetFlagStatus(I2C1,I2C_FLAG_BTF) == SET)
            break;
    }

    //发送停止位
    I2C_GenerateSTOP(I2C1,ENABLE);
    return 0;
}

在这里插入图片描述

现在我们向OLED显示屏发送命令把OLED点亮,OLED地址为0X78

int main()
{ 
	My_I2C_Init();
	
	uint8_t commands[] = {0X00,0X8D,0X14,0XAF,0XA5};

	My_I2C_SendBytes(0X78,commands,5);

	while(1)
	{
		
	}
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接收数据

  1. 发送起始位和地址
  2. 发送ACK或NAK
  3. 发送停止位

对于读取数据个数的不同,我们做出以下分类:
在这里插入图片描述

需要注意的是,必须在等待RxNE之前就发送ACK和STOP,否则从机会来不及接收应答

int My_I2C_ReceiveBytes(uint8_t Addr,uint8_t* pBuffer,uint16_t size){
    //发送起始位
    I2C_GenerateSTART(I2C1,ENABLE);
    while(I2C_GetFlagStatus(I2C1,I2C_FLAG_SB) == RESET);
    
    //寻址
    I2C_ClearFlag(I2C1,I2C_FLAG_AF);
    I2C_SendData(I2C1,Addr|0x01);
    while (1)
    {
        if(I2C_GetFlagStatus(I2C1,I2C_FLAG_AF) == SET)
        {
            //寻址失败
            I2C_GenerateSTOP(I2C1,ENABLE);
            return -1;
        }
        if(I2C_GetFlagStatus(I2C1,I2C_FLAG_ADDR) == SET)
            break;
    }
    //清除ADDR(硬件规定)
    I2C_ReadRegister(I2C1,I2C_Register_SR1);
    I2C_ReadRegister(I2C1,I2C_Register_SR2);

    if(size == 1)
    {
        //向ACK写0
        I2C_AcknowledgeConfig(I2C1,DISABLE);
        //写停止位
        I2C_GenerateSTOP(I2C1,ENABLE);
        //等待RxNE置位
        while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET);
        *pBuffer[0] = I2C_ReceiveData(I2C1);
    }
    else if(size == 2)
    {
        //向ACK写1
        I2C_AcknowledgeConfig(I2C1,ENABLE);
        //等待RxNE置位
        while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET);
        *pBuffer[0] = I2C_ReceiveData(I2C1);
        //向ACK写0
        I2C_AcknowledgeConfig(I2C1,DISABLE);
        //写停止位
        I2C_GenerateSTOP(I2C1,ENABLE);
        //等待RxNE置位
        while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET);
        *pBuffer[1] = I2C_ReceiveData(I2C1);
    }
    else
    {
        //向ACK写1
        I2C_AcknowledgeConfig(I2C1,ENABLE);
        //循环size-1次
        for(uint16_t i=0;i<size-1;i++)
        {
            while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET);
            *pBuffer[i] = I2C_ReceiveData(I2C1);
        }
        //向ACK写0
        I2C_AcknowledgeConfig(I2C1,DISABLE);
        //写停止位
        I2C_GenerateSTOP(I2C1,ENABLE);
        //等待RxNE置位
        while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET);
        *pBuffer[size-1] = I2C_ReceiveData(I2C1);
    }
    return 0;
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐