I2C協議指東

最近閒來無聊,入了一塊MPU6050,手頭本來就有一塊原子的STM32 MINI開發板,湊活着學習了一下IIC,特此總結。

IIC,是集成電路總線【Inter-Intergrated Circuit】的縮寫,屬於飛利浦公司的原創。

主要用兩根線:數據線SDA和時鐘線SCL。

關於時序方面本文就不截圖了,網上一大堆。

下面就具體說IIC的傳輸過程中,比較重要的幾個方法,下文的代碼均是在STM32中實現,是一種模擬IIC。

SCL爲輸出模式的PC(12),SDA則根據情況切換輸入和輸出模式,爲PC(11)。


1、開始信號

開始信號定義爲:SCL高電平時,SDA的下降沿

//開始信號
void IIC_Start(void)
{
	SDA_OUT();	 //SDA輸出模式
	IIC_SDA=1;
	IIC_SCL=1;
	delay_us(IIC_DELAY);
	IIC_SDA=0;//SCL高電平時SDA的下降沿
	delay_us(IIC_DELAY);
}

2、結束信號

結束信號定義爲:SCL高電平時,SDA的上升沿

//結束信號
void IIC_Stop(void)
{
	SDA_OUT();
	IIC_SDA=0;	
	IIC_SCL=1;
	delay_us(IIC_DELAY);
	IIC_SDA=1;//SCL高電平時SDA的上升沿
	delay_us(IIC_DELAY);
}

其中的SDA_OUT()是STM32的IO口模式設置,其他MCU可忽略或更改。IIC_DELAY是定義的宏,可以控制延遲時間從而控制IIC速率。


3、IIC寫一個字節

這裏的寫一個字節是說,控制了IIC總線的主機往總線上寫數據。

void IIC_Send_Byte(u8 data)
{
	u8 i;
	SDA_OUT();//輸出模式	
	for(i=0;i<8;i++)
	{
		IIC_SCL=0;//拉低時鐘 佔據總線
		delay_us(IIC_DELAY);
		IIC_SDA=(data&0x80)>>7;//每次1位,先高位
		data<<=1;
		delay_us(IIC_DELAY);
		IIC_SCL=1;
		delay_us(IIC_DELAY);		
	}
	IIC_SCL=0;	
}
這裏默認是先MSB後LSB,IIC_SDA根據數據位依次置1或0,傳輸數據時,SCL必須拉低,以此告訴其他器件“傳輸進行中”,在傳輸結束後,還需要再次拉高SCL總線。在送完一個字節後,拉低SCL,等待應答。


4、IIC讀一個字節

//IIC讀一個BYTE
u8 IIC_Read_Byte(void)
{
	u8 i,receive=0;

	SDA_IN();//輸入模式
	READ_SDA=1;
	for(i=0;i<8;i++)
	{
		receive<<=1;//先接收的是高位
		IIC_SCL=0;
		delay_us(IIC_DELAY);
		IIC_SCL=1;
		delay_us(IIC_DELAY);
		receive|=READ_SDA; 		
	}
	IIC_SCL=0;
	return receive;
}
這裏同樣的默認是先高位後低位,使用receive|=READ_SDA;來組成數據,接收數據位時,需要先拉低SCL再拉高SCL,然後再讀取SDA的數據。這裏的READ_SDA和IIC_SDA都是PC(11),只不過是不同的模式。


5、應答

在IIC中,應答不是必須的,所以對於應答的檢測其實也不是必須的

下面是應答和不應答的代碼。

//產生ACK應答
void IIC_Ack(void)
{
	SDA_OUT();
	IIC_SCL=0;
	delay_us(IIC_DELAY);
	IIC_SDA=0;
	delay_us(IIC_DELAY);
	IIC_SCL=1;
	delay_us(IIC_DELAY);
	IIC_SCL=0;//SDA爲低時 拉低時鐘線
	delay_us(IIC_DELAY);
}

//不產生ACK應答
void IIC_NAck(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=1;
	delay_us(IIC_DELAY);
	IIC_SCL=1;
	delay_us(IIC_DELAY);
	IIC_SCL=0;// SDA爲高時 SCL的脈衝
	delay_us(IIC_DELAY);
}

6、應答檢測

經過我的檢驗,當STM32寫MPU6050時,是不需要進行應答檢測的;但是當STM32讀MPU6050時,如果不進行應答檢測,就會出現數據出錯/檢測不到MPU6050等奇怪的錯誤,所以在應用IIC總線協議時,一律增加應答檢測是比較好的一種規範做法

應答檢測返回一個值,但是大多數情況中不需要用到這個返回值。

//應答信號確認
//1有ACK
//0無ACK
u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	
	SDA_IN();//	   SDA輸入模式
	IIC_SCL=0;
	delay_us(IIC_DELAY);
	IIC_SDA=1;
	delay_us(IIC_DELAY);
	IIC_SCL=1;
	delay_us(IIC_DELAY);

	while(READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 0;
		}
	}
	IIC_SCL=0;//關閉時鐘
	return 1;
}

如果SDA一直是高電平沒有被從設備【此處爲MPU6050】拉低,則說明MPU沒有應答,此時停止傳輸,並返回0.

如果接收到應答了,則把時鐘線拉低,等待下一次開始信號。

7、MPU6050相關。

關於IIC的所有函數已經講完了,下面貼一下MPU6050相關的操作。

//寫MPU60X0
u8 IIC_Write_One_Byte(u8 regaddr, u8 data)
{
	IIC_Start();                  //起始信號
    IIC_Send_Byte(SlaveAddress);   //發送設備地址+寫信號
    if(IIC_Wait_Ack()==0) 
	{
		IIC_Stop();
		return 0;
	}
	IIC_Send_Byte(regaddr);    //內部寄存器地址
    //IIC_Wait_Ack();
	IIC_Send_Byte(data);       //內部寄存器數據
    //IIC_Wait_Ack();
	IIC_Stop();                   //發送停止信號

	return 1;
}

//讀MPU60X0
u8 IIC_Read_One_Byte(u8 regaddr)
{
	u8 REG_data=0;
	IIC_Start();                  //起始信號
    IIC_Send_Byte(SlaveAddress);   //發送設備地址+寫信號
    if(IIC_Wait_Ack()==0) 
	{
		IIC_Stop();
		return 0;
	}
	IIC_Send_Byte(regaddr);     //發送存儲單元地址,從0開始	
	IIC_Wait_Ack();
	IIC_Start();                   //起始信號	
	IIC_Send_Byte(SlaveAddress+1);  //發送設備地址+讀信號	
	IIC_Wait_Ack();
	REG_data=IIC_Read_Byte();       //讀出寄存器數據,並且不應答
	IIC_NAck();						//不迴應
	IIC_Stop();                    //停止信號
	return REG_data;
}

可以看到寫一個字節的應答檢測被我註釋掉了,實驗證明依舊可以正確寫入MPU


以上就是IIC的所有內容。


總結:IIC主要使用SDA,SCL兩條線進行傳輸,其中SCL是獨立的SDA是接入總線的。當SCL爲高時,說明有“事件”:比如始信號、終止信號或者傳輸過程;當SCL爲低時,說明總線閒,只要某一個設備拉高總線,並使得SDA總線產生一個下降沿,則主設備就可以得知是哪個設備的請求。這種通過獨立SCL電平+SDA跳變的組合信號進行多設備整合的總線方案簡單、有效,容錯高,軟件上易於實現,硬件上則更加方便。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章