IIC(Inter-Integrated Circuit)总线是一种短距离的数字通信协议,由Philips公司在1980年推出。它用于连接各种数字集成电路芯片,并能够支持多主机和多从机的通信。
IIC总线的特点包括:只需两条信号线(SDA和SCL),能够实现高效、可靠的数据传输;支持数据的双向传输;具有地址识别功能,可以同时与多个从设备进行通信;具有仲裁功能,解决主从设备之间的冲突问题;支持多种传输速率。
在多种应用中,IIC总线被广泛应用于传感器、存储器、实时时钟以及其他各种数字设备之间的通信。
备注:图片来源于:https://blog.csdn.net/weixin_42881419/article/details/104288391
IIC总线和串口都是数字通信协议,但它们之间存在着一些区别:
信号线数目:IIC总线只需两条信号线(SDA和SCL),而串口需要至少三条信号线(TXD、RXD和GND)。
数据的传输方式:IIC总线使用的是同步传输,数据在时钟的控制下以位序列的方式被传输。而串口(单片机)则使用的是异步传输,即每个数据字节之间有一个确定的时间间隔,没有时钟信号进行同步。
传输速率:IIC总线的传输速率通常比串口更低,最高只有几百Kbps。而串口可达到数Mbps的传输速率。
应用场景:IIC总线通常用于连接各种数字芯片,如存储器、实时时钟、传感器等,而串口通常用于连接外设,如鼠标、键盘、调试工具等。
总的来说,IIC总线和串口两种协议各有优劣,应根据具体的应用需求来选择合适的通信协议。
IIC总线可以实现多机通信,其技术原理是通过IIC总线上设备的唯一地址来实现的。每一个从机设备都有自己的7位地址,并且这些地址是唯一的。主机可以根据从机的地址来发送数据和接收数据。
在IIC总线通信中,主机负责发起通信,并控制整个通信过程的时序;从机等待主机的命令,并返回所需信息给主机。主机发送地址码并进行仲裁
,将总线控制权赋予被选中的从机,从机在确认后回复应答
信号,然后主机就可以对该从机进行读写操作。
需要注意的是,在多机通信时,一个从机的地址可能会与其他从机的地址相同。为避免地址冲突
,在IIC总线协议中,规定了不同层次的地址冲突解决方法
,最终保证所有从机设备的地址唯一
,并且可以正确地进行通信。
在IIC总线中,当多个主机同时请求访问总线时,就可能会产生冲突。为了解决这个问题,IIC总线采用了仲裁机制。
具体来说,任何一个主机在向总线发送数据之前,都必须先检测是否有其他主机正在使用总线。如果当前总线空闲,则该主机可以开始使用总线进行通信;如果当前总线被占用,则该主机必须放弃控制权,并等待一段时间后再次检测总线是否空闲。等待的时间是随机的,这样可以避免主机在同一时间重复试图获取总线的情况。如果多个主机同时请求访问总线,每一个主机根据IIC总线定义的优先级来决定是否放弃控制权。
最终,只有一个主机可以获得总线的控制权,其他主机必须等待。当一个主机完成操作并释放总线后,下一个主机可以接着使用总线进行通信。仲裁机制保证了IIC总线通信的可靠性和高效性,使得多个主从设备可以在同一总线上进行通信。
当多个从机同时向主机发送数据时,IIC总线的仲裁机制会根据从机的地址、请求访问的优先级来进行决策。如果当前正在进行IIC通信的从机与另一个请求访问的从机的地址相同,那么未完成的IIC通信将被中断,控制权转移到新到来的请求访问的从机上。
如果当前正在进行IIC通信的从机与新到来的请求访问的从机的地址不同,那么新的请求访问从机必须等待当前从机通信结束后,才能获得总线的控制权进行通信。
需要注意的是,在IIC总线中,每个从机在通信时都必须拥有唯一的地址,以便主机可以识别和控制各个从机的通信。同时,主机也必须采用合适数值的延迟时间和合理的响应时间,以确保正常的通信过程,尽量避免出现通信失败或者中断的情况。
IIC总线地址的唯一性是通过各个设备的硬件地址引脚来保证的。每个IIC设备在出厂时,都会被设置一个唯一的7位地址。而在IIC总线上,主控设备通过发送一个地址帧来选择要通信的从机设备,从机设备接收到相应的地址帧后, 会根据自己的地址与该地址进行比较,如果匹配成功,从机就会发送一个ACK信号,表示可以进行数据传输。如果地址不匹配,则不会发送ACK信号。
通过这样的方式,可以确保在IIC总线上每个设备的地址都是唯一的,从而保证了设备之间的通信互不干扰。此外,对于多个从机设备使用同一个地址的问题,可以通过在7位地址中添加一个“哑位”来解决。具体做法是,在设备地址最高位使用0和1两种状态,这样就可以将一个地址空间划分为两个部分,从而使得两个从机设备的地址不会重复。
在IIC总线协议中,地址冲突主要是由于多个主设备同时访问IIC总线造成的。在IIC总线协议中,针对不同层次的地址冲突,有以下解决方法:
Start-stop condition arbitration:当多个主设备同时发起IIC总线访问请求时,通过比较发起start信号的主设备的地址信息,来确定哪个主设备有权访问总线。
Bit-by-bit arbitration:当有多个设备同时在总线上传输数据时,通过比较每个设备发送的数据位,来确定哪个设备有权继续传输数据。
Priority resolution:设置设备的优先级,高优先级设备有权先访问总线。
需要注意的是,以上方法都是IIC总线协议中提供的机制,具体的实现可能会略有不同。
在IIC总线中,每个从机都必须拥有唯一的地址,以便主机可以识别和控制各个从机的通信。IIC总线采用了两种不同级别的地址,分别是7位地址和10位地址。
此外,在设计IIC总线时,还可以采用一些其他方法来确保地址的唯一性,如将地址码存储在EEPROM中,或者通过特殊的管脚引脚来设置地址码等等。这些方法可以有效地避免从机地址的冲突问题,保证IIC总线的稳定性和可靠性。
IIC(Inter-Integrated Circuit)总线是用于连接微处理器和外设的串行通信接口,其基本原理为同步串行通信,即数据按位进行传输,时钟信号用于同步两端的数据传输节奏。
IIC总线由两根线构成,一根为数据线SDA(Serial Data Line),一根为时钟线SCL(Serial Clock Line)。SDA线上的数据在SCL线上的时钟周期控制下进行传输。IIC总线采用主从式方式工作,其中主机可以是微处理器、DSP、FPGA等,而从机可以是EEPROM、温度传感器、ADC等各种外设设备。
IIC通信分为两种模式:传输数据和传输地址。在传输数据时,主机发送起始信号,然后发送从机地址和读/写位,接着从机发出应答信号,主机再发送要读或写的数据,接着从机返回数据应答信息。在传输地址时,主机首先发出起始信号,随后发送从机地址和读/写控制位,接着从机回应应答信号以确认地址接收正确,最后主机结束通信。
在IIC总线中,每个从机都必须拥有唯一的地址,以便主机可以识别和控制各个从机的通信。通过地址传输和应答机制,实现了从机与主机之间的通信。同时,IIC总线还采用了仲裁技术来解决多主机同时请求访问总线的情况。在主机对从机进行读写操作过程中,从机可以返回应答信号以表示数据是否成功接收。
总之,IIC总线具有通信速度快、可靠性高、适用于多种设备等特点,因此被广泛应用于各种嵌入式系统和智能设备中。
上面的通信讲解的太过笼统,实际上可以分为下列几步:
所以一般会在SCL,SDA与电源之间接一个上拉电阻,使空闲状态下SCL,SDA默认为高电平(上拉电阻一般在4.7k~10k之间)
SDA : 由高到低,起始信号(上升沿)
SDA:由低到高,终止信号(下降沿)
主机在IIC总线上进行数据通信时,需要通过发送一系列的信号来控制通信的过程。首先,主机需要发送起始信号(Start Signal),告诉从机通信即将开始,这是一种同步信号。接着,主机需要发送从机地址信息和读/写控制位(R/W bit),以确定要访问哪个从机,同时指定当前操作是读还是写。
主机先发送起始信号是为了向总线上的所有从机发出信号,让它们保持等待接受指令的状态,并且从机不会像SPI那样自行去取数据或保存好数据。起始信号的传输方式是SCL为高电平时,SDA线由高电平转向低电平。这样,其他设备接收到起始信号后知道后续通信是针对自己的而非其他设备的。
然后,主机将从机地址加上读/写控制位一起发送,并等待从机回传应答信号,以确认地址是否被正确识别。如果从机没有回传应答信号,则表示没有接收到该地址,通信失败。如果从机回传了应答信号,则主机就可以根据所需读写的数据类型决定下一步的操作。
总之,每次IIC总线通信都需要发送起始信号,它是整个通信序列的开端,同时也用于同步通信两端的节奏,使得通信过程可以顺畅地执行。由主机先发送从机地址和读/写控制位,是为了告诉从机这个操作是针对它的,并且确认从机地址是否被正确识别。
每当发送器传输完一个字节的数据之后,发送端会等待一定的时间,等接收方的应答信号。接收端通过拉低SDA数据线,给发送端发送一个应答信号,以提醒发送端我这边已经接受完成,数据可以继续传输,接下来,发送端就可以继续发送数据了
备注:来源于:https://blog.csdn.net/shaguahaha/article/details/70766665
重点
)备注:应答信号是有从机向主机回复的信息,上文中第一个应答信号其中0:写 1:读 其他的应答信号0:收到信息;1:没有收到或读取完成
重点
)下面代码通过IIC实现MCU读取BH1750光照传感器的值,MCU采用STM32F103C8T6, SCL为PB6,SDA为PB7。
#ifndef __BH1750_H
#define __BH1750_H
#include "sys.h"
//BH1750的地址
#define BH1750_Addr 0x46
//BH1750指令码
#define POWER_OFF 0x00
#define POWER_ON 0x01
#define MODULE_RESET 0x07
#define CONTINUE_H_MODE 0x10
#define CONTINUE_H_MODE2 0x11
#define CONTINUE_L_MODE 0x13
#define ONE_TIME_H_MODE 0x20
#define ONE_TIME_H_MODE2 0x21
#define ONE_TIME_L_MODE 0x23
//测量模式
#define Measure_Mode CONTINUE_H_MODE
//分辨率 光照强度(单位lx)=(High Byte + Low Byte)/ 1.2 * 测量精度
#if ((Measure_Mode==CONTINUE_H_MODE2)|(Measure_Mode==ONE_TIME_H_MODE2))
#define Resolurtion 0.5
#elif ((Measure_Mode==CONTINUE_H_MODE)|(Measure_Mode==ONE_TIME_H_MODE))
#define Resolurtion 1
#elif ((Measure_Mode==CONTINUE_L_MODE)|(Measure_Mode==ONE_TIME_L_MODE))
#define Resolurtion 4
#endif
#define BH1750_I2C_WR 0 /* 写控制bit */
#define BH1750_I2C_RD 1 /* 读控制bit */
/* 定义I2C总线连接的GPIO端口, 只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */
#define BH1750_GPIO_PORT_I2C GPIOB /* GPIO端口 */
#define BH1750_RCC_I2C_PORT RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
#define BH1750_I2C_SCL_PIN GPIO_Pin_6 /* 连接到SCL时钟线的GPIO */
#define BH1750_I2C_SDA_PIN GPIO_Pin_7 /* 连接到SDA数据线的GPIO */
/* 定义读写SCL和SDA的宏,已增加代码的可移植性和可阅读性 */
#if 0 /* 条件编译: 1 选择GPIO的库函数实现IO读写 */
#define BH1750_I2C_SCL_1() GPIO_SetBits(BH1750_GPIO_PORT_I2C, BH1750_I2C_SCL_PIN) /* SCL = 1 */
#define BH1750_I2C_SCL_0() GPIO_ResetBits(BH1750_GPIO_PORT_I2C, BH1750_I2C_SCL_PIN) /* SCL = 0 */
#define BH1750_I2C_SDA_1() GPIO_SetBits(BH1750_GPIO_PORT_I2C, BH1750_I2C_SDA_PIN) /* SDA = 1 */
#define BH1750_I2C_SDA_0() GPIO_ResetBits(BH1750_GPIO_PORT_I2C, BH1750_I2C_SDA_PIN) /* SDA = 0 */
#define BH1750_I2C_SDA_READ() GPIO_ReadInputDataBit(BH1750_GPIO_PORT_I2C, BH1750_I2C_SDA_PIN) /* 读SDA口线状态 */
#else /* 这个分支选择直接寄存器操作实现IO读写 */
/* 注意:如下写法,在IAR最高级别优化时,会被编译器错误优化 */
#define BH1750_I2C_SCL_1() BH1750_GPIO_PORT_I2C->BSRR = BH1750_I2C_SCL_PIN /* SCL = 1 */
#define BH1750_I2C_SCL_0() BH1750_GPIO_PORT_I2C->BRR = BH1750_I2C_SCL_PIN /* SCL = 0 */
#define BH1750_I2C_SDA_1() BH1750_GPIO_PORT_I2C->BSRR = BH1750_I2C_SDA_PIN /* SDA = 1 */
#define BH1750_I2C_SDA_0() BH1750_GPIO_PORT_I2C->BRR = BH1750_I2C_SDA_PIN /* SDA = 0 */
#define BH1750_I2C_SDA_READ() ((BH1750_GPIO_PORT_I2C->IDR & BH1750_I2C_SDA_PIN) != 0) /* 读SDA口线状态 */
#endif
void i2c_Start(void);
void i2c_Stop(void);
void i2c_SendByte(uint8_t _ucByte);
uint8_t i2c_ReadByte(void);
uint8_t i2c_WaitAck(void);
void i2c_Ack(void);
void i2c_NAck(void);
uint8_t i2c_CheckDevice(uint8_t _Address);
void BH1750_Init(void); //未包含IIC初始化
float LIght_Intensity(void); //读取光照强度的值
uint8_t BH1750_Byte_Write(uint8_t data);
uint16_t BH1750_Read_Measure(void);
void BH1750_Power_ON(void);
void BH1750_Power_OFF(void);
void BH1750_RESET(void);
#endif
#include "bh1750.h"
#include "sys.h"
/*
应用说明:
在访问I2C设备前,请先调用 i2c_CheckDevice() 检测I2C设备是否正常,该函数会配置GPIO
*/
static void I2C_BH1750_GPIOConfig(void);
/*
*********************************************************************************************************
* 函 数 名: i2c_Delay
* 功能说明: I2C总线位延迟,最快400KHz
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
static void i2c_Delay(void)
{
uint8_t i;
/*
下面的时间是通过逻辑分析仪测试得到的。
工作条件:CPU主频72MHz ,MDK编译环境,1级优化
循环次数为10时,SCL频率 = 205KHz
循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us
循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us
*/
for (i = 0; i < 10; i++);
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Start
* 功能说明: CPU发起I2C总线启动信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Start(void)
{
/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
BH1750_I2C_SDA_1();
BH1750_I2C_SCL_1();
i2c_Delay();
BH1750_I2C_SDA_0();
i2c_Delay();
BH1750_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Start
* 功能说明: CPU发起I2C总线停止信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Stop(void)
{
/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
BH1750_I2C_SDA_0();
BH1750_I2C_SCL_1();
i2c_Delay();
BH1750_I2C_SDA_1();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_SendByte
* 功能说明: CPU向I2C总线设备发送8bit数据
* 形 参:_ucByte : 等待发送的字节
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_SendByte(uint8_t _ucByte)
{
uint8_t i;
/* 先发送字节的高位bit7 */
for (i = 0; i < 8; i++)
{
if (_ucByte & 0x80)
{
BH1750_I2C_SDA_1();
}
else
{
BH1750_I2C_SDA_0();
}
i2c_Delay();
BH1750_I2C_SCL_1();
i2c_Delay();
BH1750_I2C_SCL_0();
if (i == 7)
{
BH1750_I2C_SDA_1(); // 释放总线
}
_ucByte <<= 1; /* 左移一个bit */
i2c_Delay();
}
}
/*
*********************************************************************************************************
* 函 数 名: i2c_ReadByte
* 功能说明: CPU从I2C总线设备读取8bit数据
* 形 参:无
* 返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(void)
{
uint8_t i;
uint8_t value;
/* 读到第1个bit为数据的bit7 */
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
BH1750_I2C_SCL_1();
i2c_Delay();
if (BH1750_I2C_SDA_READ())
{
value++;
}
BH1750_I2C_SCL_0();
i2c_Delay();
}
return value;
}
/*
*********************************************************************************************************
* 函 数 名: i2c_WaitAck
* 功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
* 形 参:无
* 返 回 值: 返回0表示正确应答,1表示无器件响应
*********************************************************************************************************
*/
uint8_t i2c_WaitAck(void)
{
uint8_t re;
BH1750_I2C_SDA_1(); /* CPU释放SDA总线 */
i2c_Delay();
BH1750_I2C_SCL_1(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
i2c_Delay();
if (BH1750_I2C_SDA_READ()) /* CPU读取SDA口线状态 */
re = 1;
else
re = 0;
BH1750_I2C_SCL_0();
i2c_Delay();
return re;
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Ack
* 功能说明: CPU产生一个ACK信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Ack(void)
{
BH1750_I2C_SDA_0(); /* CPU驱动SDA = 0 */
i2c_Delay();
BH1750_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
BH1750_I2C_SCL_0();
i2c_Delay();
BH1750_I2C_SDA_1(); /* CPU释放SDA总线 */
}
/*
*********************************************************************************************************
* 函 数 名: i2c_NAck
* 功能说明: CPU产生1个NACK信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_NAck(void)
{
BH1750_I2C_SDA_1(); /* CPU驱动SDA = 1 */
i2c_Delay();
BH1750_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
BH1750_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 数 名: I2C_BH1750_GPIOConfig
* 功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
static void I2C_BH1750_GPIOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(BH1750_RCC_I2C_PORT, ENABLE); /* 打开GPIO时钟 */
GPIO_InitStructure.GPIO_Pin = BH1750_I2C_SCL_PIN | BH1750_I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; /* 开漏输出 */
GPIO_Init(BH1750_GPIO_PORT_I2C, &GPIO_InitStructure);
/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
i2c_Stop();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_CheckDevice
* 功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
* 形 参:_Address:设备的I2C总线地址
* 返 回 值: 返回值 0 表示正确, 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t i2c_CheckDevice(uint8_t _Address)
{
uint8_t ucAck;
i2c_Start(); /* 发送启动信号 */
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
i2c_SendByte(_Address | BH1750_I2C_WR);
ucAck = i2c_WaitAck(); /* 检测设备的ACK应答 */
i2c_Stop(); /* 发送停止信号 */
return ucAck;
}
//BH1750写一个字节
//返回值 成功:0 失败:非0
uint8_t BH1750_Byte_Write(uint8_t data)
{
i2c_Start();
//发送写地址
i2c_SendByte(BH1750_Addr|0);
if(i2c_WaitAck()==1)
return 1;
//发送控制命令
i2c_SendByte(data);
if(i2c_WaitAck()==1)
return 2;
i2c_Stop();
return 0;
}
//BH1750读取测量数据
//返回值 成功:返回光照强度 失败:返回0
uint16_t BH1750_Read_Measure(void)
{
uint16_t receive_data=0;
i2c_Start();
//发送读地址
i2c_SendByte(BH1750_Addr|1);
if(i2c_WaitAck()==1)
return 0;
//读取高八位
receive_data=i2c_ReadByte();
i2c_Ack();
//读取低八位
receive_data=(receive_data<<8)+i2c_ReadByte();
i2c_NAck();
i2c_Stop();
return receive_data; //返回读取到的数据
}
//BH1750s上电
void BH1750_Power_ON(void)
{
BH1750_Byte_Write(POWER_ON);
}
//BH1750s断电
void BH1750_Power_OFF(void)
{
BH1750_Byte_Write(POWER_OFF);
}
//BH1750复位 仅在上电时有效
void BH1750_RESET(void)
{
BH1750_Byte_Write(MODULE_RESET);
}
//BH1750初始化
void BH1750_Init(void)
{
I2C_BH1750_GPIOConfig(); /* 配置GPIO */
BH1750_Power_ON(); //BH1750s上电
//BH1750_RESET(); //BH1750复位
BH1750_Byte_Write(Measure_Mode);
//SysTick_Delay_ms(120);
}
//获取光照强度
float LIght_Intensity(void)
{
return (float)(BH1750_Read_Measure()/1.1f*Resolurtion);
}
float Light = 0; //光照度
int main(void){
BH1750_Init(); //初始化BH1750
while(1)
{
// 延时
delay_ms(100);
if (!i2c_CheckDevice(BH1750_Addr))
{
Light = LIght_Intensity();
}
}
return 0;
}