文章目录
1. I2C简介
I2C 是很常见的一种总线协议,I2C 是 NXP 公司设计的,I2C 使用两条线在主控制器和从机之间进行数据通信。一条是 SCL(串行时钟线),另外一条是 SDA(串行数据线),这两条数据线需要接上拉电阻,总线空闲的时候 SCL 和 SDA 处于高电平。I2C 总线标准模式下速度可以达到 100Kb/S,快速模式下可以达到 400Kb/S。I2C 总线工作是按照一定的协议来运行的,接下来就看一下 I2C 协议。I2C 是支持多从机的,也就是一个 I2C 控制器下可以挂多个 I2C 从设备,这些不同的 I2C从设备有不同的器件地址,这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C设备了,一个 I2C 总线连接多个 I2C 设备如下图所示
图中 SDA 和 SCL 这两根线必须要接一个上拉电阻,一般是 4.7K。其余的 I2C 从器件都挂接到 SDA 和 SCL 这两根线上,这样就可以通过 SDA 和 SCL 这两根线来访问多个 I2C设备。
2. I2C通信协议
2.1 起始位
I2C 通信起始标志,通过这个起始位就可以告诉 I2C 从机,“我”要开始进行 I2C 通信了。在 SCL 为高电平的时候,SDA 出现下降沿就表示为起始位,如下图所示:
2.2 终止位
停止位就是停止 I2C 通信的标志位,和起始位的功能相反。在 SCL 为高电平的时候,SDA出现上升沿就表示为停止位,如下图 所示:
2.3 数据传输
I2C 总线在数据传输的时候要保证在 SCL 高电平期间,SDA 上的数据稳定,因此 SDA 上的数据变化只能在 SCL 低电平期间发生,如下图所示:
2.4 应答信号
当 I2C 主机发送完 8 位数据以后会将 SDA 设置为输入状态,等待 I2C 从机应答,也就是等到 I2C 从机告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完 8 位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将 SDA 拉低来表示发出应答信号,表示通信成功,否则表示通信失败。
下图是一帧完整的I2C数据
2.5 写时序
I2C写时序如下图中右侧上面两小图所示。每一副小图中横线上策表示主机发送的信号或数据,横线下册表示从机发送的信号或数据。
I2C多次写时序过程如下:
1. 主机发送起始信号,然后发送七位从机地址和写标志位
2. 从机给一个应答信号
3. 主机发送八位的寄存器地址
4. 从机给一个应答信号
5. 主机发送八位的数据
6. 从机给一个应答信号
… 循环执行 5 和 6 两步 …
7. 数据发送完成时,主机发送停止信号,结束数据传输
2.6 读时序
I2C多次读时序过程如下:
1. 主机发送起始信号,然后发送七位从机地址和 写 标志位
2. 从机给一个应答信号
3. 主机发送八位的寄存器地址
4. 从机给一个应答信号
5. 主机再次发送起始信号,然后发送七位从机地址和 读 标志位
6. 从机给一个应答信号
7. 从机发送八位的数据
8. 主机给一个应答信号
… 循环执行 7 和 8 两步 …
9. 主机不想继续读取数据时,从机发送8为数据后,主机不应答,主机发送停止信号结束传输过程
3. STM8L052R8 I2C读写示例
3.1 i2c.h
#ifndef _I2C_H_
#define _I2C_H_
#define I2C_INTERFACE I2C1
#define I2C_CLK CLK_Peripheral_I2C1
#define I2C_SCL_PORT GPIOC
#define I2C_SCL_PIN GPIO_Pin_1
#define I2C_SDA_PORT GPIOC
#define I2C_SDA_PIN GPIO_Pin_0
void i2c_init(void);
void i2c_master_read(uint8_t addr, uint8_t *buf, uint8_t len);
void i2c_master_write(uint8_t addr, uint8_t *buf, uint8_t len);
#endif /* i2c.h */
3.2 i2c_init(初始化)
void i2c_init(void)
{
I2C_DeInit(I2C_INTERFACE);
GPIO_Init(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_Mode_Out_PP_High_Slow); // I2C1_SCL -> PC1
GPIO_Init(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_Mode_Out_PP_High_Slow); // I2C1_SDA -> PC0
CLK_PeripheralClockConfig(I2C_CLK, ENABLE); // 使能I2C1时钟
// 速率 100kHz,7位
I2C_Init(I2C_INTERFACE, 10000, 0x01, I2C_Mode_I2C, I2C_DutyCycle_2, I2C_Ack_Enable, I2C_AcknowledgedAddress_7bit);
I2C_Cmd(I2C_INTERFACE, ENABLE); // 使能I2C1
}
3.3 发送多个字节
芯片手册发送流程如下:
/*
* 功 能: i2c主机发送数据
* @param1: 从机地址/从机寄存器地址
* @param2: 要写入的数据
* @param4: 要写入数据的长度,单位字节
*/
void i2c_master_write(uint8_t addr, uint8_t *buf, uint8_t len)
{
//printf("write test 1\n");
delay(100);
while( I2C_GetFlagStatus(I2C_INTERFACE, I2C_FLAG_BUSY) ); // 等待空闲
// 产生一个起始信号。测试EV5,检测从器件返回一个应答信号
I2C_GenerateSTART(I2C_INTERFACE, ENABLE);
while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_MODE_SELECT) );
//printf("write test 2\n");
// 设置I2C从器件地址,I2C主设备为写模式。测试EV6,检测从器件返回一个应答信号
I2C_Send7bitAddress(I2C_INTERFACE, addr<<1, I2C_Direction_Transmitter);
while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) );
//printf("write test 3\n");
//2C_ClearFlag(I2C_INTERFACE, I2C_FLAG_ADDRESSSENTMATCHED); // must add
// 发送数据,检测EV8_2从机返回一个应答信号
while(len > 0)
{
I2C_SendData(I2C_INTERFACE, *buf);
while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_BYTE_TRANSMITTED) );
if(len == 1) // 只有一个数据
{
break; // 跳出循环,发送停止信号
}
else // 有多个数据,循环发送
{
len--;
buf++;
}
}
// 发送停止信号
I2C_GenerateSTOP(I2C_INTERFACE, ENABLE);
}
3.4 读取多个字节
芯片手册读取流程如下:
/*
* 功 能: i2c主机读取数据
* @param1: 从机地址
* @param2: 存放读取数据的缓存区
* @param4: 读取数据的长度,单位字节
*/
void i2c_master_read(uint8_t addr, uint8_t *buf, uint8_t len)
{
#if 0
while( I2C_GetFlagStatus(I2C_INTERFACE, I2C_FLAG_BUSY) ); // 等待空闲
// 产生一个起始信号。测试EV5,检测从器件返回一个应答信号
I2C_GenerateSTART(I2C_INTERFACE, ENABLE);
while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_MODE_SELECT) );
// 设置I2C从器件地址,I2C主设备为写模式。测试EV6,检测从器件返回一个应答信号
I2C_Send7bitAddress(I2C_INTERFACE, addr, I2C_Direction_Transmitter);
while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) );
I2C_ClearFlag(I2C_INTERFACE, I2C_FLAG_ADDRESSSENTMATCHED); // must add
// 寄存器地址,检测EV8_2从机返回一个应答信号
I2C_SendData(I2C_INTERFACE, *reg_addr);
while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_BYTE_TRANSMITTED) );
#endif
// 产生一个起始信号。测试EV5,检测从器件返回一个应答信号
I2C_GenerateSTART(I2C_INTERFACE, ENABLE);
while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_MODE_SELECT) );
//printf("1\n");
// 设置I2C从器件地址,I2C主设备为读模式。测试EV6,检测从器件返回一个应答信号
I2C_Send7bitAddress(I2C_INTERFACE, addr<<1, I2C_Direction_Receiver);
while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) );
//printf("2\n");
//I2C_ClearFlag(I2C_FLAG_ADDRESSSENTMATCHED);// must add
while(len > 0)
{
// 测试EV6
while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_BYTE_RECEIVED) );
*buf = I2C_ReceiveData(I2C_INTERFACE);
//printf("3\n");
if(len == 1)
{
// 读到最后一个字节,无需应答,直接发送停止位
I2C_AcknowledgeConfig(I2C_INTERFACE, DISABLE);
I2C_GenerateSTOP(I2C_INTERFACE, ENABLE);
}
else
{
// 不是最后一个字节,发送应答信号,字节数减1,指向下一个存储空间
I2C_AcknowledgeConfig(I2C_INTERFACE, ENABLE);
buf++;
}
len--;
}
I2C_AcknowledgeConfig(I2C_INTERFACE, ENABLE);
}