一,什么是IIC(基本概念):
内部集成电路(Inter Integrated circuit )的简称叫做IIC,是一种简单的、半双工同步通信的串行通信接口,IIC总线是上世纪80年代(1982年)由飞利浦公司设计出来,当时的目的是为了给MCU和外围芯片提供更简单的交互方式。
二,引脚说明:
IIC总线只需要两根引脚就可以实现通信,一根是数据线(SDA),另一根是时钟线(SCL),所有通过IIC接口通信的外围器件都挂载在IIC总线上,通过这种机制就可以实现多机通信。
可以看到,外围器件的时钟线和数据线都是挂载在IIC总线(由主控芯片提供),并且在空闲状态下所有器件的时钟线(SCL)和数据线(SDA)都被总线的上拉电阻拉高;
三,通讯方式:
如果IIC总线上挂载了多个外围器件,如何与某一个器件进行单独通信? 答: 寻址
理由:每个挂载在IIC总线上的外围器件都有独立的器件地址,主机发送开始信号后,只需要发送想要通信的设备的地址,如果设备收到地址并且匹配正确,则开始进行单独通信。
四,通讯速度:
IIC总线支持不同的通信速率,但是一般常用的标准速率100KHZ,但是有的外围器件可以支持高达400KHZ的通信速率,而由于IIC总线是半双工通信,所以同一时刻只能接收或者发送,也就是说,IIC总线一般是为了控制,不适合作为大量数据传输的接口。
五,通讯整个流程:
可以看到,在建立通信的时候主机需要发送开始信号,紧接着主机需要发出从器件的设备地址(7bit+1bit),从设备的物理地址是7bit,但是由于只有一根数据线,就需要说清楚数据的传输方向,数据的传输方向通过从设备的地址最低位进行表示(最低位是0,表示写操作,最低位是1,表示读操作),IIC总线提供了应答机制,也就是说从机收到了1个字节的数据之后,会在第九个脉冲发送给主机一个应答信号(1bit),如果主机收到从机的应答信号,则主机可以继续发送数据,反之,如果主机没有收到从机发送的应答信号,那主机就不应该继续发送数据,而是应该主动发出一个停止信号,表示停止通信。
六,通讯器件地址查询:(举例AT24C02芯片)
可以看到从器件的地址是7bit,可以通过硬件原理图以及从器件的数据手册进行查找,比如AT24C02芯片的设备地址是1010000,但是由于IIC协议在数据传输的时候是以8bit为单位进行传输,而IIC总线只有一根数据线,所以只能采用半双工的方式通信,就要求主机设置数据的传输方向,数据的传输方向由1bit进行控制,这1bit和从器件的设备地址一起发出。
七,空闲状态:
指的是不传输任何数据的时候就被称为空闲状态,IIC总线规定SDA数据线和SCL时钟线在不传输数据的时候都应该设置高电平,表示空闲。
八,通讯流程(代码实例):
第一步:发送开始信号
开始信号由主机发出,表示打算和所有的从器件进行通信,IIC总线规定在SCL时钟线保持高电平期间,把SDA数据线拉低,表示开始信号。
//IIC总线开始信号
void IIC_StartSignal(void)
{
//1.设置SDA引脚为输出模式
IIC_SDAOutputMode();
//2.确保SDA和SCL为高电平
IIC_SCL_WRITE(1);
IIC_SDA_WRITE(1);
Delay_us(5); //提高程序可靠性
//3.把SDA引脚拉低
IIC_SDA_WRITE(0);
Delay_us(5); // IIC总线的通信速率为100KHZ 1000000us = 100000HZ 10us = 1HZ
//4.把SCL引脚拉低,表示准备通信
IIC_SCL_WRITE(0);
}
第二步:数据发送
在主机发送开始信号后,就可以发送数据或者地址,IIC总线规定数据的收发都是MSB(高位先出),由于只有一个数据线,所以IIC采用串行方式把数据的每个bit位发出去。
由于SCL提供的脉冲周期是有规律的,所以IIC总线规定只能在SCL脉冲周期的高电平期间进行数据的读取或者写入,在SCL脉冲周期的低电平期间可以进行数据的修改。
//主机发送数据(从机读取数据)
void IIC_SendBytes(uint8_t Data)
{
uint8_t i= 0;
//1.设置SDA引脚为输出模式
IIC_SDAOutputMode();
//2.确保SDA和SCL为低电平
IIC_SCL_WRITE(0);
IIC_SDA_WRITE(0);
//3.循环发送bit
for(i=0;i<8;i++)
{
//MSB 高位先出 主机准备数据
if ( Data & 1<<(7-i) )
{
IIC_SDA_WRITE(1);
}
else
IIC_SDA_WRITE(0);
Delay_us(5);
//SCL为高电平 主机发送数据
IIC_SCL_WRITE(1);
Delay_us(5);
//SCL为低电平 主机准备数据
IIC_SCL_WRITE(0);
Delay_us(5);
}
}
第三步:应答信号(两种应答信号)
第一种:主机发送数据,从机进行应答
//主机等待从机应答 返回0 说明从机应答 返回1 说明从机没应答
uint8_t IIC_WaitAck(void)
{
uint8_t ack;
//1.设置SDA引脚为输入模式
IIC_SDAInputMode();
//2.SCL为低电平 从机准备数据
IIC_SCL_WRITE(0);
Delay_us(5);
//3.SCL为高电平 从机发送数据
IIC_SCL_WRITE(1);
Delay_us(5);
//主机判断从机的数据
if( IIC_SDA_READ == 1)
{
ack=1; //说明无应答
}
else
ack=0; //说明已应答
//4.SCL为低电平 主机忽略数据
IIC_SCL_WRITE(0);
Delay_us(5);
return ack;
}
第二种:从机发送数据,主机进行应答
//从机发送数据,主机接收数据并发送应答信号
void IIC_MasterAck(uint8_t ack)
{
//1.设置SDA引脚为输出模式
IIC_SDAOutputMode();
//2.确保SDA和SCL为低电平
IIC_SCL_WRITE(0);
IIC_SDA_WRITE(0);
//3.主机准备数据
if(ack)
IIC_SDA_WRITE(1);
else
IIC_SDA_WRITE(0);
Delay_us(5);
//4.SCL为高电平 主机发送数据
IIC_SCL_WRITE(1);
Delay_us(5);
//5.SCL为低电平 从机忽略数据
IIC_SCL_WRITE(0);
Delay_us(5);
}
第四步:数据接收
由于SCL提供的脉冲周期是有规律的,所以IIC总线规定只能在SCL脉冲周期的高电平期间进行数据的读取或者写入,在SCL脉冲周期的低电平期间可以进行数据的修改。
//主机读取数据(从机发送数据)
uint8_t IIC_ReadBytes(void)
{
uint8_t i = 0;
uint8_t Data = 0;
//1.设置SDA引脚为输入模式
IIC_SDAInputMode();
//2.SCL为低电平 从机准备数据
IIC_SCL_WRITE(0);
Delay_us(5);
//3.循环接收数据
for(i=0;i<8;i++)
{
//SCL为高电平 从机发送数据
IIC_SCL_WRITE(1);
Delay_us(5);
if( IIC_SDA_READ == 1) //说明接收的是1
{
Data |= 1<<(7-i);
}
//SCL为低电平 从机准备数据
IIC_SCL_WRITE(0);
Delay_us(5);
}
return Data;
}
第五步:停止信号
停止信号由主机发出,表示不打算和从器件继续通信,IIC总线规定在SCL时钟线保持高电平期间,把SDA数据线拉高,表示停止信号。
//IIC总线停止信号
void IIC_StopSignal(void)
{
//1.设置SDA引脚为输出模式
IIC_SDAOutputMode();
//2.确保SDA和SCL为低电平
IIC_SCL_WRITE(0);
IIC_SDA_WRITE(0);
//4.把SCL引脚拉高
IIC_SCL_WRITE(1);
Delay_us(5);
//5.把SDA引脚拉高
IIC_SDA_WRITE(1);
Delay_us(5); //确保SDA的电平状态可以被其他从器件检测到
}
九,标准案例:(AT24C02代码案例)
//IIC总线的引脚
#define IIC_SDA_WRITE(n) (n)?GPIO_SetBits(GPIOB,GPIO_Pin_9):GPIO_ResetBits(GPIOB,GPIO_Pin_9)
#define IIC_SCL_WRITE(n) (n)?GPIO_SetBits(GPIOB,GPIO_Pin_8):GPIO_ResetBits(GPIOB,GPIO_Pin_8)
#define IIC_SDA_READ GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)
//IIC总线开始信号
void IIC_StartSignal(void)
{
//1.设置SDA引脚为输出模式
IIC_SDAOutputMode();
//2.确保SDA和SCL为高电平
IIC_SCL_WRITE(1);
IIC_SDA_WRITE(1);
Delay_us(5); //提高程序可靠性
//3.把SDA引脚拉低
IIC_SDA_WRITE(0);
Delay_us(5); // IIC总线的通信速率为100KHZ 1000000us = 100000HZ 10us = 1HZ
//4.把SCL引脚拉低,表示准备通信
IIC_SCL_WRITE(0);
}
//IIC总线停止信号
void IIC_StopSignal(void)
{
//1.设置SDA引脚为输出模式
IIC_SDAOutputMode();
//2.确保SDA和SCL为低电平
IIC_SCL_WRITE(0);
IIC_SDA_WRITE(0);
//4.把SCL引脚拉高
IIC_SCL_WRITE(1);
Delay_us(5);
//5.把SDA引脚拉高
IIC_SDA_WRITE(1);
Delay_us(5); //确保SDA的电平状态可以被其他从器件检测到
}
//主机发送数据(从机读取数据)
void IIC_SendBytes(uint8_t Data)
{
uint8_t i= 0;
//1.设置SDA引脚为输出模式
IIC_SDAOutputMode();
//2.确保SDA和SCL为低电平
IIC_SCL_WRITE(0);
IIC_SDA_WRITE(0);
//3.循环发送bit
for(i=0;i<8;i++)
{
//MSB 高位先出 主机准备数据
if ( Data & 1<<(7-i) )
{
IIC_SDA_WRITE(1);
}
else
IIC_SDA_WRITE(0);
Delay_us(5);
//SCL为高电平 主机发送数据
IIC_SCL_WRITE(1);
Delay_us(5);
//SCL为低电平 主机准备数据
IIC_SCL_WRITE(0);
Delay_us(5);
}
}
//主机读取数据(从机发送数据)
uint8_t IIC_ReadBytes(void)
{
uint8_t i = 0;
uint8_t Data = 0;
//1.设置SDA引脚为输入模式
IIC_SDAInputMode();
//2.SCL为低电平 从机准备数据
IIC_SCL_WRITE(0);
Delay_us(5);
//3.循环接收数据
for(i=0;i<8;i++)
{
//SCL为高电平 从机发送数据
IIC_SCL_WRITE(1);
Delay_us(5);
if( IIC_SDA_READ == 1) //说明接收的是1
{
Data |= 1<<(7-i);
}
//SCL为低电平 从机准备数据
IIC_SCL_WRITE(0);
Delay_us(5);
}
return Data;
}
//主机等待从机应答 返回0 说明从机应答 返回1 说明从机没应答
uint8_t IIC_WaitAck(void)
{
uint8_t ack;
//1.设置SDA引脚为输入模式
IIC_SDAInputMode();
//2.SCL为低电平 从机准备数据
IIC_SCL_WRITE(0);
Delay_us(5);
//3.SCL为高电平 从机发送数据
IIC_SCL_WRITE(1);
Delay_us(5);
//主机判断从机的数据
if( IIC_SDA_READ == 1)
{
ack=1; //说明无应答
}
else
ack=0; //说明已应答
//4.SCL为低电平 主机忽略数据
IIC_SCL_WRITE(0);
Delay_us(5);
return ack;
}
//从机发送数据,主机接收数据并发送应答信号
void IIC_MasterAck(uint8_t ack)
{
//1.设置SDA引脚为输出模式
IIC_SDAOutputMode();
//2.确保SDA和SCL为低电平
IIC_SCL_WRITE(0);
IIC_SDA_WRITE(0);
//3.主机准备数据
if(ack)
IIC_SDA_WRITE(1);
else
IIC_SDA_WRITE(0);
Delay_us(5);
//4.SCL为高电平 主机发送数据
IIC_SCL_WRITE(1);
Delay_us(5);
//5.SCL为低电平 从机忽略数据
IIC_SCL_WRITE(0);
Delay_us(5);
}
//AT24C02初始化
void AT24C02_Init(void)
{
IIC_Init();
}
//AT24C02的字节写入 一次写入1字节
void AT24C02_WordWrite(uint8_t Address,uint8_t Data)
{
//1.主机发送开始信号
IIC_StartSignal();
//2.主机发送器件地址 写操作
IIC_SendBytes(0xA0);
//3.主机等待从机应答
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Device Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Device Address OK\n");
//4.主机发送存储地址 写操作
IIC_SendBytes(Address);
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Data Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Data Address OK\n");
//5.主机发送存储数据 写操作
IIC_SendBytes(Data);
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Write Data Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Write Data OK\n");
//6.主机发送停止信号
IIC_StopSignal();
}
//AT24C02读取1字节数据
uint8_t AT24C02_WordRead(uint8_t Address)
{
uint8_t data = 0;
//1.主机发送开始信号
IIC_StartSignal();
//2.主机发送器件地址 写操作
IIC_SendBytes(0xA0);
//3.主机等待从机应答
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Device Write Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Device Write Address OK\n");
//4.主机发送存储地址 打算读取数据的地址
IIC_SendBytes(Address);
//5.主机等待从机应答
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Data Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Data Address OK\n");
//6.主机再次发起开始信号
IIC_StartSignal();
//7.主机发送器件地址 读操作
IIC_SendBytes(0xA1);
//8.主机等待从机应答
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Device Read Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Device Read Address OK\n");
//9.主机读取1字节数据
data = IIC_ReadBytes();
//10.主机发送应答信号
IIC_MasterAck(1); //不应答
//11.主机发送停止信号
IIC_StopSignal();
return data;
}
//AT24C02读取n字节数据 存储地址 缓冲数组 数据个数
void AT24C02_RandomRead(uint8_t Address,uint8_t* RecvBuf,uint8_t DataLen)
{
//1.主机发送开始信号
IIC_StartSignal();
//2.主机发送器件地址 写操作
IIC_SendBytes(0xA0);
//3.主机等待从机应答
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Device Write Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Device Write Address OK\n");
//4.主机发送存储地址 打算读取数据的地址
IIC_SendBytes(Address);
//5.主机等待从机应答
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Data Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Data Address OK\n");
//6.主机再次发起开始信号
IIC_StartSignal();
//7.主机发送器件地址 读操作
IIC_SendBytes(0xA1);
//8.主机等待从机应答
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Device Read Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Device Read Address OK\n");
//9.主机读取n字节数据
DataLen = DataLen-1;
while(DataLen--) //读取n-1个
{
*RecvBuf++ = IIC_ReadBytes();
IIC_MasterAck(0); //表示主机收到
}
//主机接收最后1个字节
*RecvBuf = IIC_ReadBytes();
//10.主机发送应答信号
IIC_MasterAck(1); //不应答
//11.主机发送停止信号
IIC_StopSignal();
}
//AT24C02当前地址读 存储地址指的是AT24C02内部地址计数器中的地址
uint8_t AT24C02_CurrentAddressRead(void)
{
uint8_t data = 0;
//1.主机发送开始信号
IIC_StartSignal();
//2.主机发送器件地址 读操作
IIC_SendBytes(0xA1);
//3.主机等待从机应答
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Device Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Device Address OK\n");
//4.主机读取1字节数据
data = IIC_ReadBytes();
//5.主机发送应答信号
IIC_MasterAck(1);
//6.主机发送停止信号
IIC_StopSignal();
return data;
}
//AT24C02页写入 一次最多写入8字节
void AT24C02_PageWrite(uint8_t Page_Address,uint8_t *buf,uint8_t DataLen)
{
//1.主机发送开始信号
IIC_StartSignal();
//2.主机发送器件地址 写操作
IIC_SendBytes(0xA0);
//3.主机等待从机应答
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Device Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Device Address OK\n");
//4.主机发送存储地址 页地址
IIC_SendBytes(Page_Address);
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Ack Page Address Error\n");
IIC_StopSignal();
}
//printf("AT24C02 Ack Page Address OK\n");
//5.主机循环发送字节
while(DataLen--)
{
IIC_SendBytes(*buf++);
if( IIC_WaitAck() == 1 ) //如果没应答
{
printf("AT24C02 Write Data Error\n");
IIC_StopSignal();
}
}
//6.主机发送停止信号
IIC_StopSignal();
}
int main()
{
uint8_t recvbuf[10] = {0};
uint8_t data = 0;
//1.硬件初始化
USART1_Init(9600);
SysTick_Init();
AT24C02_Init();
//2.往AT24C02写入1字节数据 数据地址 0x00
AT24C02_WordWrite(0x00,'a');
Delay_ms(100); //等待AT24C02写完 写入1字节需要耗费5ms
//3.随机读
data = AT24C02_WordRead(0x00);
printf("Read Data From AT24C02 Is %c \n",data);
//4.主机写入n字节数据 "hello" 0x08指的是第2页的首地址 1~32页
AT24C02_PageWrite(0x08,(uint8_t *)"hello",5);
Delay_ms(100); //等待AT24C02写完 写入1字节需要耗费5ms
//5.主机读取n字节数据 0x08 --- 页地址
AT24C02_RandomRead(0x08,recvbuf,5);
printf("Read Data From Page Address Is %s \n",recvbuf);
//主程序的循环
while(1)
{
}
}