i2c
简介
I2C(Inter-Integrated Circuit)接口是一种双向、同步、串行通信总线标准,主要用于连接微控制器与各种嵌入式系统中的外围设备,如传感器、存储器、显示器控制器、模拟数字转换器(ADC)、数字模拟转换器(DAC)等。
基本特性
双线结构:
- SDA(Serial Data):串行数据线,用于传输数据和控制信息。
- SCL(Serial Clock):串行时钟线,由主设备提供,用于同步数据传输。
多主控系统:
- 允许多个主设备存在,但在同一时刻只有一个主设备可以控制总线。
半双工通信:
- 同一时刻只能进行数据的发送或接收,数据线(SDA)在每个时钟周期内只能传输一位数据。
设备地址:
- 每个连接到I2C总线的设备都有一个唯一的7位(或10位)地址,用于识别和寻址。
通信速率:
- 标准模式:100 kbps
- 快速模式:400 kbps
- 高速模式(HS-mode):高达3.4 Mbps(仅限于部分兼容设备)
- 低速模式(LS-mode):10 kbps(用于低功耗或长线通信)
电平标准:
- 一般采用+3.3V或+5V逻辑电平,支持开漏输出(Open Drain),通过上拉电阻将信号线拉至电源电压。
通信协议
主机发送基本格式: 主机接收接本格式:
以下是以表格形式描述的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);
}
}