本文目录
IIC(Inter-Integrated Circuit)是一种常用的串行通信协议,通常用于连接微控制器和各种外部设备(例如传感器、存储器、显示器等)。
●IIC、UART、SPI的比较:
通信协议 | UART | IIC | SPI |
---|---|---|---|
通信特征 | 异步串行全双工 | 同步串行半双工 | 同步串行全双工 |
接口 | TX、RX | SCL、SDA | MOSI、MISO、SCL、CS/NSS |
速度 | 多种波特率 | 100Khz、400Khz、3.4Mhz | |
数据帧格式 | 起始位+数据位+校验位+停止位 | 起始条件+位传输+应答+停止条件 | 四种模式:MODE0~MODE3 |
主从设备通信 | 没有主从 | 有主从 | 有主从 |
总线结构 | 一对一 | 一对多 | 一对多 |
一、知识点
上图中上拉电阻的作用:使IIC总线空闲时,SDA与SCL都为高电平状态。
1. 12C总线两线制包括: 串行数据SDA (Serial Data)、串行时钟SCL(Serial Clock)。
2. I2C总线上有主机和从机之分,可以有多个主机和多个从机。IIC总线上的电容不超过400pf,只要不超过这个电容量,任意挂多少个从机都可以。但是在通信的时刻,只能有一个作为主机,其他的都为从机。
3. 器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态(都可以作为发送器和接收器)。从机永远不会主动给主机发送数据。
4. 时钟线SCL必须由主机控制!主机控制时钟线SCL产生跳变沿,从而控制主机是接收数据还是发送数据。 谁控制SCL时钟线,谁就是主机!
5. 每个IIC设备都有一个唯一的地址。在通信开始之前,你需要知道要与主机进行通信的设备的地址。每个设备都有一个唯一的7位地址(4位固定地址+3位可编程地址),用于在总线上区分不同的设备。注:有些从设备的7位地址商家已经给固定好了,所以就不需要自行设计,查看商家给的手册就可以知道该地址。
如:AT24C02芯片中的A0、A1、A2就是可编程地址位,可以自行设计该位。下图中A0、A1、A2接地,则都为0。
6. 数据的有效性: 时钟线产生下降沿,发送方准备/发送数据。时钟线产生上升沿,接收方采集数据。
SDA数据线在 SCL 的每个时钟周期传输一位数据。传输时:
(1)SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据 “1”,为低电平时表示数据“ 0”。
(2)当 SCL为低电平时,SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。
二、IIC通信全流程
1. 硬件连接
- 确保正确连接了I2C总线的两根线(SCL - 时钟线,SDA - 数据线)到目标设备。
- 确保目标设备有正确的供电。
2. 初始化I2C模块
-
首先,初始化I2C模块,SDA\SCL模式,GPIO等功能参数。
-
引脚模式设置:
-
方案一:
SCL–推挽输出模式。
主机写数据: SDA–推挽输出模式。
主机读数据: SDA–上拉输入模式。
void SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//GPIOB9初始化设置
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz
GPIO_SetBits(GPIOB, GPIO_Pin_7); //上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
}
void SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//GPIOB9初始化设置
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入模式
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
}
- 方案二:
SCL、SDA–开漏输出模式。
这个文章我们使用方案一进行演示。
3. 选择目标设备地址(制造商预先分配,查看手册获取)
- 每个I2C设备都有一个唯一的地址。在通信开始之前,你需要知道你要与之通信的设备的地址。每个设备都有一个唯一的7位地址,用于在总线上区分不同的设备。
4. 主机发送起始信号
-
主机发送一个启动信号来开始I2C通信(唤醒所有的从机设备)。起始信号的生成标志着I2C通信的开始,接着就可以发送目标设备的地址和读写位来开始通信。
-
步骤:SCL为高电平时,SDA维持超过4us的高电平时间,SDA再变为低电平(起始信号),再维持超过4.7us的低电平时间。这就是启动信号的完整步骤。
void IIC_Start()
{
SCL=1;
SDA=1;
delay_us(5); //至少为4us,所以5us也可以
SDA=0;
delay_us(5); //至少为4。7us,所以5us也可以
SCL=0; //发送方准备数据
}
5. 主机发送目标设备地址和读/写位
- 发送目标设备的地址和指定读写方向。
注:跟哪个从机通讯,把从机的地址发出去。发送数据是8个bit,这8个bit位中前7个bit位是从机的地址,最后1个bit位是用来表示读或者写的。1表示读,0表示写;这个过程相当于主机往SDA上发了8个bit的数据。主要是为了寻找与其通信的从机。
●详细过程看过程7!!
6. 是否应答信号
- 当主机找到需要通信的从机后,从机需要回复应答信号。应答信号:0,非应答信号:1。
(1)主机给从机发送应答信号
void IIC_Send_Ack(u8 ack) //主机给从机发送应答信号
{
SDA_OUT(); //SDA数据线为输出模式
SCL=0; //发送方准备数据
if(ack) SDA=1; //非应答
else SDA=0; //应答信号
delay_us(5);
SCL = 1;
delay_us(5);
SCL = 0;
}
(2)主机接收(等待)从机的应答信号
从上图中可以看出,在保持SCL高电平的状态下,通过读取SDA线的电平状态来判断从机是否应答,由于SDA默认是为高信号(即非应答),所以通过从机操作SDA线,将SDA线拉低则视为应答,而不动则视为非应答。实现代码如下:
#define READ_SDA PBin(7) //读取SDA的状态 与GPIO_ReadInputDataBit()函数功能相同
uint8_t IIC_Receive_Ack(void) //主机接收从机的应答信号
{
uint8_t ucErrorTime = 0;
SDA_IN(); //SDA数据线为输入模式
SDA=1; //释放数据线
delay_us(5); //低电平保持时间
SCL=1;
delay_us(5); //低电平保持时间
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 1){ //SDA为高电平,一直为非应答状态,
ucErrorTime++; //上行代码可替换为 while(READ_SDA){} 同理
if(ucErrorTime > 250){ //当出错时间超过时
IIC_Stop(); //停止信号
return 1;//未接收到应答
}
}
SCL = 0;
return 0; //接收到应答
}
7. 读取/写入数据
时钟线SCL产生下降沿,发送方准备/发送数据。时钟线SCL产生上升沿,接收方采集数据。
- 如果是写入操作,发送要写入的数据到目标设备。由主机来控制SDA的电平变化
- 如果是读取操作,从目标设备读取数据。那就由从机来控制SDA的电平变化;
- 每次8bit的数据传输完成,都要有个应答信号,谁接收数据,谁来应答。
最后一位:读:1 , 写:0
●如果你已知从机的七位地址(0111 100)。
要进行写操作:Write_IIC_Byte (0x78); // (0111 1000)
要进行读操作:Write_IIC_Byte (0x79); // (0111 1001)
(1)主机写(发送)数据给从机
时钟线SCL产生下降沿,发送方准备/发送数据。注:SDA设置为推挽输出模式,且电平拉高。
(1)首先将时钟线 SCL 拉低,准备传输数据。
(2)循环发送8位数据。从数据的最高位开始发送。
(3)每发送完一个bit位的数据就要产生一个时钟脉冲。如:SCL先拉高再拉低。
//==============================================================================================*/
void Write_IIC_Byte(unsigned char IIC_Byte)
{
unsigned char i; //用于循环
unsigned char maxbit;
unsigned char date; //要发送的数据
date = IIC_Byte;
SCL=0; //发送方准备发送数据
for(i=0;i<8;i++)
{
maxbit=date&0x80; //0x80:1000 0000 高位开始发送
if(maxbit==0x80) SDA = 1; //写入数据1
else SDA = 0; //写入数据0
date=date<<1; //从最高位依次发送出去
//发送时钟脉冲
SCL = 1;
SCL = 0;
}
}
(2)主机从“从机”读取数据
时钟线SCL产生上升沿,接收方采集(读取)数据。注:SDA:设置为上拉输入模式!
(1)首先将时钟线 SCL 拉高,准备读取数据。
(2)循环接收8位数据。从数据的最高位开始接收。
(3)每接收完一个bit位的数据就要产生一个时钟脉冲。如:SCL先拉低再拉高。
unsigned char Read_IIC_Byte(void)
{
unsigned char i;
unsigned char received_byte = 0;
SCL = 0; // 发送方准备发送数据
for(i = 0; i < 8; i++)
{
received_byte =received_byte << 1; // 左移一位准备接收数据
SCL = 1; // 拉高SCL,接收方准备接收数据
if(SDA) // 读取SDA线的状态,如果为高则表示接收到了1
{
received_byte |= 0x01; // 设置最低位为1
}
SCL = 0; // 拉低SCL,准备接收下一个数据位
}
return received_byte;
}
8. 发送停止信号
- 发送停止信号以结束I2C通信。
- SCL(时钟线)为高电平时,SDA(数据线)由低电平变为高电平,表示通信结束。发送完停止信号后,SDA、SCL都为高电平状态(初始状态由于上拉电阻也为高电平状态)。
void IIC_Stop()
{
SCL=1;
SDA=0;
delay_us(5); //至少为4.7us
SDA=1;
delay_us(5); //至少为4.7us
}
9. 处理接收的数据
- 如果你进行了读取操作,确保处理接收到的数据。
此外,确保在进行I2C通信时,你了解所连接设备的I2C协议和通信规范,包括设备的地址和数据传输格式等。
三、实验(向AT24C02中写入一个字节与读取一个字节)
操作前提:要对AT24C02进行操作,需要查看AT24C02的芯片手册再进行下面的操作。下图就是手册中通信方式的内容。
如果已知从机AT24C02的七位地址(1010 000)
要进行写操作:Write_IIC_Byte (0xA0); // (1010 0000)
要进行读操作:Write_IIC_Byte (0xA1); // (1010 0001)
void Write_IIC_Data(uint8_t addr, uint8_t IIC_Data)
{
IIC_Start(); //发送起始信号
Write_IIC_Byte(0xA0); //写操作--从机的地址(包含读写位)
IIC_Receive_Ack(); //等待应答
Write_IIC_Byte(addr); //从机要写入数据的字地址,这里不包含读写位。
IIC_Receive_Ack(); //等待应答
Write_IIC_Byte(IIC_Data); //将数据写入
IIC_Receive_Ack();
IIC_Stop();
}
/* 往从机AT24C02中读取一个字节 */
uint8_t at24c02_Read_One_Byte(uint8_t addr)
{
uint8_t read_data = 0;
IIC_Start(); // 1、发送起始信号
Write_IIC_Byte(0xA0); // 2、发送通讯地址(写操作地址)
IIC_Receive_Ack(); // 3、等待应答信号
Write_IIC_Byte(addr); // 4、发送内存地址:0~255
IIC_Receive_Ack(); // 5、等待应答信号
IIC_Start(); // 6、发送起始信号
Write_IIC_Byte(0xA1); // 7、发送通讯地址(读操作地址)
IIC_Receive_Ack(); // 8、等待应答信号
read_data = Read_IIC_Byte(); //9、接收数据
IIC_Send_Ack(1); //发送非应答信号,0为应答
IIC_Stop(); // 10、发送停止信号
return read;
}