i2c

简介

I2C(Inter-Integrated Circuit)接口是一种双向、同步、串行通信总线标准,主要用于连接微控制器与各种嵌入式系统中的外围设备,如传感器、存储器、显示器控制器、模拟数字转换器(ADC)、数字模拟转换器(DAC)等。

基本特性

  1. 双线结构

    • SDA(Serial Data):串行数据线,用于传输数据和控制信息。
    • SCL(Serial Clock):串行时钟线,由主设备提供,用于同步数据传输。
  2. 多主控系统

    • 允许多个主设备存在,但在同一时刻只有一个主设备可以控制总线。
  3. 半双工通信

    • 同一时刻只能进行数据的发送或接收,数据线(SDA)在每个时钟周期内只能传输一位数据。
  4. 设备地址

    • 每个连接到I2C总线的设备都有一个唯一的7位(或10位)地址,用于识别和寻址。
  5. 通信速率

    • 标准模式:100 kbps
    • 快速模式:400 kbps
    • 高速模式(HS-mode):高达3.4 Mbps(仅限于部分兼容设备)
    • 低速模式(LS-mode):10 kbps(用于低功耗或长线通信)
  6. 电平标准

    • 一般采用+3.3V或+5V逻辑电平,支持开漏输出(Open Drain),通过上拉电阻将信号线拉至电源电压。

通信协议

主机发送基本格式: i2c-write 主机接收接本格式: i2c-read

以下是以表格形式描述的I2C协议格式,包括起始条件、数据传输、应答(ACK/NACK)和停止条件:

协议元素描述信号表示
起始条件(Start Condition)标志着一次I2C通信的开始SDA 从高电平向低电平变化,同时SCL保持高电平
重复起始条件(Repeated Start Condition)在无停止条件的情况下开始新的传输类似起始条件,但在前一次传输未结束时产生
地址帧主设备发送目标从设备的7位或10位地址和读写方向位(R/W)地址(7/10位) + R/W位(1位)
数据帧传输的数据字节,可以是主设备向从设备写入的数据或从设备向主设备读出的数据8位数据(MSB先发送)
应答(ACK / NACK)接收方对正确接收数据的确认ACK(低电平):表示数据接收成功
NACK(高电平):表示数据接收失败或传输结束
停止条件(Stop Condition)标志着一次I2C通信的结束SDA 从低电平向高电平变化,同时SCL保持高电平

详细说明:

  • 起始条件(Start Condition):每次通信开始时,主设备通过拉低SDA线(保持SCL为高电平)创建一个下降沿,通知总线上所有设备准备开始通信。

  • 重复起始条件(Repeated Start Condition):主设备在不发送停止条件的情况下,再次发出起始条件,以便在同一个传输序列中切换读写方向或与另一个从设备通信。

  • 地址帧:主设备发送一个7位或10位的从设备地址(具体取决于总线配置),地址的最后一位(最低位)是读写方向位(R/W)。0表示主设备将向从设备写入数据,1表示主设备将从从设备读取数据。

  • 数据帧:每个数据字节由8位组成,高位(MSB)先发送。主设备负责发起数据传输,并在写操作中发送数据;从设备在读操作中发送数据。数据在SCL的每个时钟周期内逐位传输,数据线在SCL高电平时保持稳定,在SCL低电平时改变。

  • 应答(ACK / NACK):每个数据字节传输完毕后,接收方(主设备或从设备,取决于读写方向)会在第9个时钟周期(ACK位时钟)通过SDA线发送一个应答位。低电平(ACK)表示数据正确接收,高电平(NACK)则表示数据接收错误或希望终止传输。在读操作的最后一个字节后,主设备通常发送NACK以指示传输结束。

  • 停止条件(Stop Condition):通信结束时,主设备拉高SDA线(保持SCL为高电平),形成一个上升沿,标志着一次I2C通信的结束。所有设备在此之后释放总线,准备下一次通信。

关键特性

  • 简化布线:仅需两条线即可实现多设备间的通信,降低了硬件成本和复杂度。
  • 设备热插拔:支持动态添加或移除设备,无需重启系统。
  • 故障隔离:单个设备故障不影响总线上的其他设备通信。
  • 总线仲裁:多主控环境中,通过检测SDA线电平自动解决总线控制权冲突。

应用领域

  • 消费电子:电视、音响、智能家电中的传感器、显示屏控制器等。
  • 工业控制:温度、湿度、压力、运动传感器的数据采集,设备状态监控。
  • 医疗设备:生物信号采集、设备配置与状态报告。
  • 汽车电子:车内环境监测、车身控制、多媒体系统组件通信。
  • 物联网(IoT):传感器网络、智能硬件、小型嵌入式系统间的互连。

示例代码

/**
 * @brief I2C 硬件接口
 * 
 * @file i2c_hard.c
 * @author 
 * @modify wenjf
 * @date 2018-10-23
 */
#include "i2c_hard.h"
#include <string.h>

// 针对一些特殊情况,没有办法直接返回2字节长度字段的问题
// #define I2C_RECV_MODE2

//--------------------------------------------------
//	I2C 接口配置
//--------------------------------------------------
#define I2Cx						I2C1
#define SLAVE_ADDR					(0x10)
//读写地址
#define I2C_WRITE_ADDR				(SLAVE_ADDR << 1)
#define I2C_READ_ADDR				(SLAVE_ADDR << 1 | 0x01)

/**
 * @brief I2C初始化配置
 * 
 */
void i2c_init_config(void)
{
	I2C_InitTypeDef  i2c_config;
	GPIO_InitTypeDef  gpio_config;
	
	//---------------------------------------
	//	打开I2C时钟
	//---------------------------------------
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,    ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,     ENABLE);

	//-----------------------------------------
	//	I2C初始化
	//-----------------------------------------
	gpio_config.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
	gpio_config.GPIO_Speed = GPIO_Speed_50MHz;
	gpio_config.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_Init(GPIOB, &gpio_config);
	
	i2c_config.I2C_Mode = I2C_Mode_I2C;
	i2c_config.I2C_DutyCycle = I2C_DutyCycle_2;
	i2c_config.I2C_OwnAddress1 = 0x00;
	i2c_config.I2C_Ack = I2C_Ack_Enable;
	i2c_config.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	i2c_config.I2C_ClockSpeed = 100000;
	I2C_Cmd(I2Cx, ENABLE);
	I2C_Init(I2Cx, &i2c_config);
	// -----------------------------------
	// 使能I2C应答
	// -----------------------------------
	I2C_AcknowledgeConfig(I2Cx, ENABLE);
}

/**
 * @brief I2C 数据写入
 * 
 * @param sBuf 待发送的数据
 * @param sLen 待发送的数据长度
 */
void i2c_write(u8* sBuf,u16 sLen)
{
	if((0x00 == sLen) || (NULL == sBuf))
	{
		return;
	}
	
	//------------------------------------
	//	START
	//------------------------------------
	I2C_GenerateSTART(I2Cx, ENABLE);
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));
	//------------------------------------
	//	发送写地址
	//------------------------------------
	I2C_Send7bitAddress(I2Cx, I2C_WRITE_ADDR, I2C_Direction_Transmitter);
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
	
	//-------------------------------------
	//	发送数据
	//-------------------------------------
	while(sLen)
	{
		I2C_SendData(I2Cx, *sBuf);
		while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
		sBuf++; 
		sLen--;       
	}
	//-------------------------------
	//	STOP
	//-------------------------------
	I2C_GenerateSTOP(I2Cx, ENABLE);
}

/**
 * @brief I2C 数据读取
 * 
 * @param rBuf 数据缓存
 * @param rLen 接收到的数据长度
 */
void i2c_read(u8* rBuf , u16 *rLen)
{
	u16 rev_len=0;
	u8* pTmpBuf = rBuf;

	if((0x00 == rLen) || (NULL == rBuf))
	{
	    return;
	}
	while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY))
		;
    I2C_AcknowledgeConfig(I2Cx, ENABLE);
	//----------------------------------------
	//	START
	//----------------------------------------
	I2C_GenerateSTART(I2Cx, ENABLE);
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));
	
	//----------------------------------------
	//	发送读地址
	//----------------------------------------
	I2C_Send7bitAddress(I2Cx, I2C_READ_ADDR, I2C_Direction_Receiver);
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
	//----------------------------------------
	//	先将后续的数据的长度接收过来(2 Bytes)
	//----------------------------------------
	rev_len = 2;
	while(rev_len)
	{
#ifdef I2C_RECV_MODE2
		if(rev_len == 1)
		{
			I2C_AcknowledgeConfig(I2Cx, DISABLE); 
		}
#endif		
	    while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)); 
	    *pTmpBuf = I2C_ReceiveData(I2Cx);
	    pTmpBuf++; 
	    rev_len--;       
	}
	rev_len = rBuf[0]*256 + rBuf[1] + 1;

	*rLen = rev_len + 2;

#ifdef I2C_RECV_MODE2
	I2C_AcknowledgeConfig(I2Cx, ENABLE);
	//----------------------------------------
	//	START
	//----------------------------------------
	I2C_GenerateSTART(I2Cx, ENABLE);
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));
	//----------------------------------------
	//	发送读地址
	//----------------------------------------
	I2C_Send7bitAddress(I2Cx, I2C_READ_ADDR, I2C_Direction_Receiver);
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
#endif

	while(rev_len)
	{
	    if(rev_len == 1)
	    {
			//----------------------------------
			//	最后一字节数据发送NACK,随后STOP
			//----------------------------------
			I2C_AcknowledgeConfig(I2Cx, DISABLE); 
			I2C_GenerateSTOP(I2Cx, ENABLE);          
	    }
	
	    while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)); 
	    *pTmpBuf = I2C_ReceiveData(I2Cx);
	    pTmpBuf++; 
	    rev_len--;       
	}
	
	// -----------------------------------
	// 使能I2C应答
	// -----------------------------------	
	I2C_AcknowledgeConfig(I2Cx, ENABLE);
}

/**
 * @brief 简单延时函数
 * 
 */
static void Delay(void)
{
	volatile uint32_t n = 0x00080000;
	uint32_t i = 0;
	for(i = 0; i < n; i ++)
	{
		;
	}
}

/**
 * @brief I2C 通讯示例
 * 
 */
void i2c_hard_example(void)
{
	uint8_t apdu[] = {0x00,0x05,0x00,0x84,0x00,0x00,0x08,0x8C};
	uint16_t apdu_len = 8;
	uint8_t res[32];
	uint16_t resLen = 0;
	
	Delay();
	
	i2c_init_config();
	i2c_write(apdu,apdu_len);
	i2c_read(res,&resLen);
	
	while(1)
	{
		apdu_len = 0;
	}
}
#include "i2c_slave.h"
#include "led.h"

// I2C接口
#define I2Cx						I2C1

void i2c_slave_init(uint8_t addr)
{
	I2C_InitTypeDef  i2c_config;
	GPIO_InitTypeDef  gpio_config;
	
	//---------------------------------------
	//	打开I2C时钟
	//---------------------------------------
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,    ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,     ENABLE);

	//-----------------------------------------
	//	I2C初始化
	//-----------------------------------------
	gpio_config.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
	gpio_config.GPIO_Speed = GPIO_Speed_50MHz;
	gpio_config.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_Init(GPIOB, &gpio_config);
	
	I2C_DeInit(I2Cx);
	
	i2c_config.I2C_Mode = I2C_Mode_I2C;
	i2c_config.I2C_DutyCycle = I2C_DutyCycle_2;
	i2c_config.I2C_OwnAddress1 = addr;
	i2c_config.I2C_Ack = I2C_Ack_Enable;
	i2c_config.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	i2c_config.I2C_ClockSpeed = 100000;
	I2C_Cmd(I2Cx, ENABLE);
	I2C_Init(I2Cx, &i2c_config);
	// -----------------------------------
	// 使能I2C应答
	// -----------------------------------
	I2C_AcknowledgeConfig(I2Cx, ENABLE);
}


void i2c_slave_send(uint8_t * data, uint32_t len)
{
	I2C_AcknowledgeConfig(I2Cx, ENABLE);
	//-------------------------------------
	// 等待地址匹配
	//-------------------------------------
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED));
	
	//-------------------------------------
	//	发送数据
	//-------------------------------------
	for(uint32_t i = 0; i < len; i++)
	{
		LED_ON();
		LED2_ON();
		
		I2C_SendData(I2Cx, *(data + i));
		if(i < len - 1)
		{
			while(!I2C_CheckEvent(I2Cx, I2C_EVENT_SLAVE_BYTE_TRANSMITTED));
			LED2_OFF();
		}
		else
		{
			//-------------------------------------
			// 等待NAK
			//-------------------------------------
			while(!I2C_CheckEvent(I2Cx, I2C_EVENT_SLAVE_ACK_FAILURE));
			LED_OFF();
			I2Cx->SR1 &= ~0x400;
		}
		
		#if 0
		if(I2Cx->SR1 & 0x04)
		{
			LED2_OFF();
		}

		if((I2Cx->SR1 & 0x400))
		{
			LED_OFF();
			I2Cx->SR1 &= ~0x400;
		}
		#endif
	}
}

void i2c_slave_recv(uint8_t * data, uint32_t len)
{
	I2C_AcknowledgeConfig(I2Cx, ENABLE);
	//-------------------------------------
	// 等待地址匹配
	//-------------------------------------
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED));

	//-------------------------------------
	//	发送数据
	//-------------------------------------
	for(uint32_t i = 0; i < len; i++)
	{
		while(!I2C_CheckEvent(I2Cx, I2C_EVENT_SLAVE_BYTE_RECEIVED));
		*(data + i) = I2C_ReceiveData(I2Cx);
	}

	//-------------------------------------
	// 等待STOP
	//-------------------------------------
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_SLAVE_STOP_DETECTED));
}


void i2c_slave_example(void)
{
	uint8_t rbuf[128] = {0};
	uint8_t sbuf[128] = {0};
	uint32_t len = 10;
	uint32_t i = 0;

	LED_Init();
	LED_OFF();
	
	// stm32的i2c配置的地址,包括读写bit
	// 将该bit置为0即可
	i2c_slave_init(0x50);
	
	while(1)
	{
		i2c_slave_recv(rbuf,len);

		for(i = 0; i < len; i++)
		{
			sbuf[i] = rbuf[i] + 0x30;
		}
		i2c_slave_send(sbuf,len);
	}
}