05-I2C详解

一. 什么是IIC

1. 相关概念和硬件连接

  • IIC也称I2C,是一个多主从的串行总线,由飞利浦公司发明的通讯总线.
  • 属于半双工同步传输类总线,仅由两条线就能完成多机通讯,一条SCL时钟线,另外一条双向数据线SDA.
  • I2C总线要求每个设备SCL/SDA线都是漏极开路模式,因此必须带上拉电阻才能正常工作.(这就是高阻态的应用场景, 高阻态下设备对外界不会产生干扰)
  • I2C数据传输速率有标准模式(100kbps)、快速模式(400kbps)和高速模式(3.4Mbps)
  • 支持总线挂载多设备(一主多从、多主多从), 本文只讨论一主一从.

下图为I2C硬件连接方式, 以及i2c常见设备.

二. I2C时序基本单元

1. 起始信号和停止信号

  • 空闲状态: 当处于非通信状态时, SCL和SDA线会由于外部上拉电阻处于非高电平状态.
  • 起始信号: 设备从空闲状态进入起始状态时, 首先SDA线会先拉低, 然后SCL线拉低. 当从机接受到这个信号后, 就开始进入数据接受状态.
  • 停止信号: 在SCL处于高电平期间, SDA线由低电平跳变为高电平, 当从设备接收到这一信号后, 代表此次通信结束.
  • 起始信号和停止信号都是主机发出.

2. 数据发送和接收

  • 串口通信是低位先行,i2c是高位先行
  • 数据发送: 在SCL为低电平的时候SDA需要将要发送的数据按位从高到低依次发送出去, 在SCL为高电平的时候SDA需要保持电平状态稳定不能发生改变.(例如要发送0xA1, SCL第一个低电平时候SDA要从低电平变为高电平, SCL变为高电平时候SDA要维持高电平状态. SCL第二个低电平时SDA要从高电平变成低电平, SCL变为高电平时SDA要维持低电平状态...)
  • 数据接收: 和数据反送过程刚好相反, 从机需要按位发送数据从高到低, 发完一个字节数据后, 主机需要回复应答信号或非应答信号.

2. I2C完整通信过程

  • 以数据发送为例: 在数据开始传输前, 设备处于空闲状态此时SCL和SDA线都处于高电平. 当要开始发送数据的时候, 主设备会发出一个起始信号, 紧接着主机会发送一个字节数据, 前7位用于寻址最后一位用于控制读写, 此时主机将释放对SDA的控制权. 当从设备接收到起始信号后就开始监听后面这个地址数据是否符合自己的地址, 如果是就会回一个应答信号反之则不做任何动作. 主机接收到应答信号后, 将进行第二次数据传输, 直到从设备发出非应答信号后, 主机发出停止信号此次通信结束.

三. 驱动代码

1. GPIO模拟I2C发送数据

#include "stm32f10x.h"

void GPIO_Led_Init(void); 
void GPIO_I2C_Init(void);
void I2C_Send_Address(char address);
void delay_us(uint32_t us);

#define LED_POINT_PORT          GPIOC
#define LED_POINT               GPIO_Pin_13

#define I2C_AF_PORT             GPIOB
#define I2C_AF_SCL              GPIO_Pin_10
#define I2C_AF_SDA              GPIO_Pin_11

#define DRIVER_ADDRESS          0xA2

#define DELAY_TIEM_US           10

int main()
{
    GPIO_Led_Init();
    GPIO_I2C_Init();
    I2C_Send_Address(DRIVER_ADDRESS);
    
    while(1);
}


void GPIO_Led_Init() 
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = LED_POINT;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    
    GPIO_Init(LED_POINT_PORT, &GPIO_InitStruct);
    GPIO_SetBits(LED_POINT_PORT, LED_POINT);
}

void GPIO_I2C_Init()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = I2C_AF_SCL | I2C_AF_SDA;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    
    GPIO_Init(I2C_AF_PORT, &GPIO_InitStruct);
}


void I2C_Send_Address(char address)
{
    int i;
    
    //start point
    GPIO_WriteBit(I2C_AF_PORT, I2C_AF_SCL, Bit_SET);
    delay_us(DELAY_TIEM_US);
    GPIO_WriteBit(I2C_AF_PORT, I2C_AF_SDA, Bit_SET);
    delay_us(DELAY_TIEM_US);
    
    //send data
    GPIO_WriteBit(I2C_AF_PORT, I2C_AF_SDA, Bit_RESET);
    delay_us(DELAY_TIEM_US);
    GPIO_WriteBit(I2C_AF_PORT, I2C_AF_SCL, Bit_RESET);
    delay_us(DELAY_TIEM_US);
    
    for(i = 7; i >= 0; i --)
    {
       
        GPIO_WriteBit(I2C_AF_PORT, I2C_AF_SDA, (address & (1 << i)));
        delay_us(DELAY_TIEM_US);
        GPIO_WriteBit(I2C_AF_PORT, I2C_AF_SCL, Bit_SET);
        delay_us(DELAY_TIEM_US);
        GPIO_WriteBit(I2C_AF_PORT, I2C_AF_SCL, Bit_RESET);
        delay_us(DELAY_TIEM_US);
    }

    GPIO_WriteBit(I2C_AF_PORT, I2C_AF_SDA, Bit_SET);
    delay_us(DELAY_TIEM_US);
    GPIO_WriteBit(I2C_AF_PORT, I2C_AF_SCL, Bit_SET);
    delay_us(DELAY_TIEM_US);
    
    if(GPIO_ReadInputDataBit(GPIOB, I2C_AF_SDA) == 0)
    {
        GPIO_ResetBits(LED_POINT_PORT, LED_POINT);
    }
    
    
}

void delay_us(uint32_t us)
{    
  volatile unsigned int num;
  volatile unsigned int t;
 
  
  for (num = 0; num < us; num++)
  {
    t = 11;
    while (t != 0)
    {
      t--;
    }
  }
}


2. 固件库方式驱动

posted @   一步一磕头的菜鸡  阅读(424)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
点击右上角即可分享
微信分享提示