自我整理IIC详细总结和运用(案例AT24C02存储芯片)

本文详细介绍了IIC(内部集成电路)通信协议的基本概念、引脚功能、通信方式、通讯速度以及通讯流程。通过代码实例展示了如何与IIC总线上的设备如AT24C02进行数据传输,包括开始信号、数据发送、应答信号、数据接收和停止信号等步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,什么是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)
	{
		
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值