概述

  1. 所有 I2C 设备的 SCL 连在一起,SDA 连在一起.
  2. 设备的 SCL 和 SDA 均要配置成开漏输出模式.此模式会有一个 “线与” 的现象,即一个或多个设备输出低电平, 总线处于低电平.只有所有设备都输出高电平,总线才输出高电平.I2C可以利用此现象执行多主机模式的时钟同步和总线仲裁.
  3. SCL 和 SDA 各添加一个上拉电阻, 阻值一般为 4.7KΩ 左右.
  4. I2C 发送数据是先发送高位再发送低位.

I2C 时序特征

  1. 空闲状态: SCL 和 SDA 都保持为高电平.
  2. 起始信号: 起始位是一个时间段, 当 SCL 为高电平, 而 SDA 由高电平跳变为低电平, 产生起始信号.
  3. 停止信号: 停止位是一个时间段, 当 SCL 为高电平, 而 SDA 由低电平跳变为高电平, 产生停止信号.
  4. 起始信号和终止信号都是主机产生的,从机不允许产生起始和终止.如果允许,就是多主机模型.

I2C 传输数据

  1. 发送一个字节: SCL低电平期间,主机将数据位依次放到 SDA 线上(高位先行), SCL 高电平期间, 从机读取数据位. SCL高电平期间 SDA 不允许有数据变化, 依次循环上述过程8次,即可发送一个字节.
  2. 接收一个字节: SCL低电平期间,从机将数据位依次放到 SDA 线上(高位先行), SCL 高电平期间, 主机读取数据位. SCL高电平期间 SDA 不允许有数据变化, 依次循环上述过程8次, 即可接收一个字节(主机在接收之前,需要释放SDA).
  3. 发送应答: 主机在接收完一个字节之后, 在下一个时钟发送一位数据, 数据 0 表示应答, 数据 1 表示非应答.
  4. 接收应答: 主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据 0 表示应答,数据 1 表示非应答(主机在接收之前, 需要释放 SDA).
+ I2C 时序
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
对于指定设备(Slave Address),在当前地址指针指示地址下,读取从机数据(Data)
1. SCL 低电平期间变换数据, SCL 高电平期间读取数据. 2. 空闲状态下,SCL 与 SDA 都是高电平,主机需要向从机发送数据,SCL 高电平期间,拉低 SDA.产生起始条件(Start). 3. 接下来是一个字节的时序(内容是从机地址 + 读写位(0: 主机执行写操作,1: 主机执行读操作)). 4. 接下来是接收从机的应答位(Receive Ack).主机释放 SDA,从机拉低 SDA,依据 "线与",此时 SDA 为 0.

I2C 程序示例

+ I2C程序示例
MyI2C.h
MyI2C.c
#ifndef __MYI2C_H
#define __MYI2C_H

void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);

#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

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);
}

uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}

void MyI2C_Init(void)
{
    // 开启 APB2 总线上的 GPIOB 端口的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    // 设置 GPIO 的模式为开漏输出模式(只能输出高电平,若想输出低电平需要外部电路配合)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
    // 设置GPIO的输出速度为50MH
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}

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);
}

void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++)
	{
		/**
		 * 0x80 即 1000 0000
		 * Byte & (0x80 >> i) 即 将 Byte 的 每一位与 1 求与操作.
		 */ 
		MyI2C_W_SDA(Byte & (0x80 >> i));
		MyI2C_W_SCL(1);
		MyI2C_W_SCL(0);
	}
}

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;
}

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;
}

视频链接

参考链接

  1. I2C代码解析
  2. 野火STM32讲解