最近閒來無聊,入了一塊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速率。
這裏的寫一個字節是說,控制了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跳變的組合信號進行多設備整合的總線方案簡單、有效,容錯高,軟件上易於實現,硬件上則更加方便。