目錄
- IIC簡介
- 硬件電路連接
- I2C時序基本單元
- IIC完整數據幀
- MPU6050封裝
- 硬件IIC
- 內部電路
IIC簡介
IIC(Inter-Integrated Circuit)是 IIC Bus 簡稱,中文叫集成電路總線。它是一種串行通信總線,使用多主從架構,由飛利浦公司在1980年代為了讓主板、嵌入式系統或手機用以連接低速周邊設備而發展。自2006年10月1日起,使用I2C協議已經不需要支付專利費,但制造商仍然需要付費以獲取I2C從屬設備地址。
??IIC使用兩根信號線進行通信:一根時鐘線SCL,一根數據線SDA。IIC將SCL處于高時SDA拉低的動作作為開始信號,SCL處于高時SDA拉高的動作作為結束信號;傳輸數據時,SDA在SCL低電平時改變數據,在SCL高電平時保持數據,每個SCL脈沖的高電平傳遞1位數據。
IIC通信時一種同步,半雙工的通信協議,帶數據應答,支持總線掛載多設備(一主多從、多主多從)。
也正是因為IIC是一種同步時序,所有我們可以軟件模擬,串口通信就是一種異步時序,對時序要求很嚴格,所有我們不能模擬,需要硬件短路來實現才會精準收發數據。
硬件電路連接
所有I2C設備的SCL連在一起,SDA連在一起
設備的SCL和SDA均要配置成開漏輸出模式
SCL和SDA各添加一個上拉電阻,阻值一般為4.7KΩ左右
任何時候都是主機完全掌控SCL線,只有從機應答和從機發送的時候才會獲得SDA的掌控權。
IIC的設計禁止所有設備輸出強上拉的高電平。采用外置弱上拉的電阻加開漏輸出的電路結構。這么做的原因也是為了防止主機在結束的時候釋放SDA即拉高然后從機立刻拉低響應造成的短路。這個模式也存在“線與”的特性,只要有輸出低,那么最后就輸出低,所有輸出高才輸出高。
CPU和被控IC都是上圖右邊的結構。開漏輸出沒有強上拉,只有強下拉,當輸出高電平的時候,下管斷開,不輸出低,處于浮空狀態,此時就由上拉電阻拉高。
I2C時序基本單元
起始條件:SCL高電平期間,SDA從高電平切換到低電平
終止條件:SCL高電平期間,SDA從低電平切換到高電平
發送一個字節:
SCL低電平期間,主機將數據位依次放到SDA線上(高位先行),然后釋放SCL,從機將在SCL高電平期間讀取數據位,所以SCL高電平期間SDA不允許有數據變化,依次循環上述過程8次,即可發送一個字節。
接收一個字節:
SCL低電平期間,從機將數據位依次放到SDA線上(高位先行),然后釋放SCL,主機將在SCL高電平期間讀取數據位,所以SCL高電平期間SDA不允許有數據變化,依次循環上述過程8次,即可接收一個字節(主機在接收之前,需要釋放SDA)。
發送應答: 主機在接收完一個字節之后,在下一個時鐘發送一位數據,數據0表示應答,數據1表示非應答
接收應答: 主機在發送完一個字節之后,在下一個時鐘接收一位數據,判斷從機是否應答,數據0表示應答,數據1表示非應答(主機在接收之前,需要釋放SDA,從機控制,從機拉低就是從機發送應答)
IIC完整數據幀
IIC的完成數據幀包括指定地址寫,當前地址讀,指定地址讀
IIC是一主多從的通信協議,所以如果要和某個從機進行通信必須選中某個從機,所以每個從機就需要一個地址。主機在起始條件后需要先發送一個字節,所有從機都會收到一個字節和自己的地址比較。如果和自己的地址就響應主機的操作,所有同一個IIC總線必須掛載不一樣的地址設備。從機設備地址在IIC協議標準種分為七位和十位,七位的比較簡單應用也比較廣泛。在出廠的時候,廠商就會為他分配一個七位的地址,在芯片手冊中都能找到。如果有相同的地址芯片掛在在總線上就需要使用地址的可變部分,一般都是地址的后面一位或者幾位。
一個完整的數據幀在起始條件開始,結束條件結束。
指定地址寫:
在起始條件后就要跟一個從機地址+寫標志位,然后從機發送應答。然后發送指定的地址,然后從機再次應答。
當前地址讀:
對于指定設備(Slave Address),在當前地址指針指示的地址下,讀取從機數據(Data)。
當前地址指針上電默認是0,但是當我們指定地址寫后,然后當前地址讀,就是在寫的下一位進行讀取操作。
指定地址讀:
我們使用指定地址寫的指定地址時序,但是不寫入,此時當前地址指針就不會加一。然后再調用當前地址讀的時序,然后就是指定地址讀了。我們可以在指定地址讀前面加一個結束條件,然后再次發送起始條件,在當前地址讀。但是IIC協議官方規定的符合格式是一整個數據幀,就是先起始,再重復起始(在指定地址寫的時序后面再加一個起始條件),然后發當前地址讀,最后結束了再發送結束條件。
當讀取完數據不想再讀取了,就需要主機發送非應答,此時從機知道主機不想再接收了,就會釋放總線,交還給從機。
MPU6050封裝
軟件IIC配置:
總體操作:
1.初始化GPIO,包括打開時鐘,配置結構體,初始化選用的引腳
2.配置IIC開始函數
3.配置IIC結束函數
4.配置IIC發送一個字節函數
5.配置IIC接收一個字節函數
6.配置IIC發送應答函數
7.配置IIC接收應答函數
具體操作:
1.初始化GPIO,例如,選用Pin10為SCL線,Pin11為SDA線,配置IIC的GPIO為開漏輸出
void MyI2C_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);
}
1.IIC不論是SDA還是SCL都只有高低兩種狀態,所有使用時就是把GPIO電平配置為高或者低
例如:
GPIO_WriteBit(GPIOB,GPIO_Pin_10,1);Delay_us(10);GPIO_WriteBit(GPIOB,GPIO_Pin_11,1);Delay_us(10);
延時10微秒為操作時間,實測不延時也可以。
每次都配置GPIO不僅麻煩還不明了
所以就把GPIO封裝起來
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);Delay_us(10);
}//對SCL線封裝,便于操作,控制時鐘線
void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);Delay_us(10);
}//對SDA線封裝,便于操作,發送主機值
uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);Delay_us(10);return BitValue;
}//對SDA線封裝,便于操作,讀取從機發送的值
封裝完以后便可以很簡單的配置后面的函數
2.配置開始函數
IIC開啟需要在SCL為高的時候拉低SDA,這樣為開啟IIC信號,從機便知道,IIC開啟,主機要發送或者接收數據了
void MyI2C_Start(void)
{MyI2C_W_SDA(1);/*最好先拉高SDA確定,簡要原因在最后面的讀MPU6050的注釋里面有簡要說明,這樣只是一個以防外一,個人感覺不是特別重要*/MyI2C_W_SCL(1);MyI2C_W_SDA(0);MyI2C_W_SCL(0);//拉低SDA后再把SCL拉低,便可以進行數據傳送
3.配置結束函數 IIC結束需要在SCL為高的時候拉高SDA,這樣為關閉IIC信號,從機便知道,IIC關閉,主機要結束發送或者接收數據了,實際上,在配置的IIC函數里面,只有結束函數里面SCL以高結束,其他的都為低,這樣方便兩個函數銜接。
void MyI2C_Stop(void)
{MyI2C_W_SDA(0);//先拉低SDA確保待會可以產生上升沿MyI2C_W_SCL(1);MyI2C_W_SDA(1);
}
4.配置主機發送函數
主機在SCL低的時候只會發送一位,從機在SCL為高的時候一次也只接收一位,每次都是一位一位進行的
void MyI2C_SentByte(uint8_t Byte)
{uint8_t i;for(i=0;i<8;i++){MyI2C_W_SDA(Byte&(0x80>>i));MyI2C_W_SCL(1);MyI2C_W_SCL(0);}
}
一次發送八位數據也就是一個字節的數據,所以在for循環里面循環8次
在8次數據發送完以后,主機需要釋放SDA,這時從機會自己占據SDA線給主機發送應答后面寫的主機的接收應答就會主動去釋放SDA(所有的發送和接收都是相對主機而言的)
5.配置主機接收函數
uint8_t MyI2C_ReceiveByte(void)//主機接收時,從機在時鐘線拉低的時候只會發送一位數據
{uint8_t i,Byte=0x00;MyI2C_W_SDA(1);for(i=0;i<8;i++){ MyI2C_W_SCL(1);if(MyI2C_R_SDA()==1){Byte |= (0x80>>i);}//高位先行,所以右移MyI2C_W_SCL(0);}return Byte;
}
主機接收的數據要處理,所以要用變量存起來,發送的數據從機會自動處理
6.配置IIC發送應答
void MyI2C_SentAck(uint8_t AckBit)
{MyI2C_W_SDA(AckBit);//當發送完一個數據以后,SCL本身就是低的,所以前面不需要再給SCL低了MyI2C_W_SCL(1);MyI2C_W_SCL(0);
}
發送應答是主機接收了一個數據以后發送個主機的應答,SDA拉高,相當于主機發送1,為非應答,不需要再接收數據時就要發非應答,需要接收數據時,就在SDA拉低,為應答。從機發送完一個字節數據以后,會自動釋放SDA,此時主機應占據SDA,從機便會去讀取SDA的值,接收主機的應答(應答信號在第9個時鐘周期出現,這時發送器必須在這一時鐘位上釋放數據線,由接收設備拉低SDA電平來產生應答信號或非應答信號)
7.配置IIC接收應答
uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit;MyI2C_W_SDA(1);//主機主動空出SDA,從機會立刻占據,發送應答或者非應答信號MyI2C_W_SCL(1);//SCL拉高以后,主機便可以去讀取從機給的信號AckBit=MyI2C_R_SDA();MyI2C_W_SCL(0);//此時從機放手SDAreturn AckBit;
}
在主機發送完數據以后,主機應空出SDA線,此時從機會產生應答或者非應答,因為是軟件模擬IIC所以可以選擇讀取也可以不去讀取,選擇讀取便可以根據讀取的值判斷下一步要不要再繼續操作。因為一個時鐘信號只進行一位傳輸,所以從機檢測到電平變化以后如果接下來還是主機操作便不在占據SDA。
以上便是軟件IIC的所有配置
以MPU6050為例,演示IIC的進行
MPU6050初始化即IIC初始化
如果要給MPU6050寫一個字節的數據:
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{MyI2C_Start();//打開IIC通信MyI2C_SentByte(0xD0);//選中 MPU6050,最后一位為0為寫操作MyI2C_ReceiveAck();//從機發送應答,主機要接收應答MyI2C_SentByte(RegAddress);//主機繼續發送要寫的寄存器地址MyI2C_ReceiveAck();//主機接收從機的應答MyI2C_SentByte(Data);//主機發送要寫的數據MyI2C_ReceiveAck();//主機接收從機的應答MyI2C_Stop();//停止IIC通信SDA與SCL都為高
}
IIC開始以后,第一次發送的是硬件的地址,每一個硬件都有一個地址,出廠的時候寫好的,發送的數據前七位為地址,最后一位為讀寫位,0為寫,1為讀。
如果要讀MPU6050一個字節的數據:
uint8_t MPU6050_ReadReg(uint8_t RegAddress)//讀指定寄存器
{uint8_t Data;//接收讀出數據的變量MyI2C_Start();MyI2C_SentByte(0xD0);MyI2C_ReceiveAck();MyI2C_SentByte(RegAddress);MyI2C_ReceiveAck();//前面幾步確定地址MyI2C_Start();/*Start里面先SDA置1就是這里在上一步SCL為0的時候趕快為高然后重新開始,避免還沒為高的時候SCL已經拉高了。如果SDA還沒為高的時候SCL已經拉高了,這樣再產生下降沿之前產生的就是上升沿了,就是停止的意思了。但是接收應答后,從機釋放SDA,此時SDA就是高主機沒有進行操作,SDA一直為高,所以個人感覺不重要*/MyI2C_SentByte(0xD1);//對指定地址進行讀MyI2C_ReceiveAck();//從機要回應這個指令Data=MyI2C_ReceiveByte();//從機把指定地址的數據通過SDA線發出來MyI2C_SentAck(1);//主機回應1表示不給應答,從機便會結束發送MyI2C_Stop();//結束通信return Data;
}
因為無法直接指定寄存器讀,但可以指定寄存器寫,指定的地址指針在下一次操作前不變,所以指定地址寫,然后什么都不寫,重新開始讀,便可以指定地址讀。
再MPU5050手冊中,剛上電時是睡眠模式,無法寫入,只能讀出。所有需要解除睡眠模式,此時就需要0x6B上寫,0x6B地址是電源管理寄存器1,其中SLEEP位控制睡眠模式,在這個寄存器寫入0x00,這樣就能解除睡眠模式了。
在真正使用MPU6050之前還需要根據手冊初始化一下,比如電源管理寄存器,時鐘,陀螺儀,加速度計等各種寄存器。
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H#define SMPLRT_DIV 0x19
#define CONFIG 0x1A
#define GYRO_CONFIG 0x1B
#define ACCEL_CONFIG 0x1C
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B
#define PWR_MGMT_2 0x6C
#define WHO_AM_I 0x75
extern uint8_t AccX,AccY,AccZ,GyroX,GyroY,GyroZ;
#endif
void MPU6050_GetData(int16_t *AccX,int16_t *AccY,int16_t *AccZ,int16_t*GyroX,int16_t*GyroY,int16_t*GyroZ)
{uint8_t DataH,DataL;DataH=MPU6050_ReadReg(ACCEL_XOUT_H);DataL=MPU6050_ReadReg(ACCEL_XOUT_L);*AccX=(DataH<<8)|DataL;DataH=MPU6050_ReadReg(ACCEL_YOUT_H);DataL=MPU6050_ReadReg(ACCEL_YOUT_L);*AccY=(DataH<<8)|DataL;DataH=MPU6050_ReadReg(ACCEL_ZOUT_H);DataL=MPU6050_ReadReg(ACCEL_ZOUT_L);*AccZ=(DataH<<8)|DataL;DataH=MPU6050_ReadReg(GYRO_XOUT_H);DataL=MPU6050_ReadReg(GYRO_XOUT_L);*GyroX=(DataH<<8)|DataL;DataH=MPU6050_ReadReg(GYRO_YOUT_H);DataL=MPU6050_ReadReg(GYRO_YOUT_L);*GyroY=(DataH<<8)|DataL;DataH=MPU6050_ReadReg(GYRO_ZOUT_H);DataL=MPU6050_ReadReg(GYRO_ZOUT_L);*GyroZ=(DataH<<8)|DataL;
}
void MPU6050_Init(void)
{MyI2C_Init();MPU6050_WriteReg(PWR_MGMT_1,0x01);//電源管理1MPU6050_WriteReg(PWR_MGMT_2,0x00);//電源管理2MPU6050_WriteReg(SMPLRT_DIV,0x09);//采樣率分頻,10分頻MPU6050_WriteReg(CONFIG,0x06);//外部同步(不需要)與低通濾波MPU6050_WriteReg(GYRO_CONFIG,0x18);//陀螺儀配置寄存器MPU6050_WriteReg(ACCEL_CONFIG,0x18);//加速度計
}
硬件IIC
由于IIC是同步時序,所以軟件模擬II從時序,靈活且方便,用的范圍比較廣。
STM32內部集成了硬件IIC的電路,STM32內部集成了硬件I2C收發電路,可以由硬件自動執行時鐘生成、起始終止條件生成、應答位收發、數據收發等功能,用庫函數封裝好,直接讀取寄存器即可。減輕CPU的負擔。
比如:
支持多主機模型
支持7位/10位地址模式
支持不同的通訊速度,標準速度(高達100 kHz),快速(高達400 kHz)
支持DMA
兼容SMBus協議
STM32F103C8T6 硬件I2C資源:I2C1、I2C2
引腳映射:
C8T6的IIC2映射的PB10為SCL,PB11為SDA。IIC1的SCL為PB6,SDA為PB7。
內部電路
上半部分是SDA,數據控制部分。數據收發的核心部分是上面的數據寄存器和數據移位寄存器。當需要發送數據時,可以把一個字節數據寫到數據寄存器DR。當移位寄存器沒有數據移位時,DR中的值就會轉到移位寄存器中。在移位的過程中,可以直接把下一個數據放到數據寄存器中等著。當數據由數據寄存器轉到移位寄存器時就會置狀態寄存器的TXE
位為 1 ,表示發送寄存器為空。
接收的過程也是這樣,輸入的數據先放進移位寄存器,當一個字節后,數據從移位寄存器轉入DR。同時置標志位RXNE
,表示接收寄存器非空。此時就可以將數據從數據寄存器讀出來了。
對于起始條件,終止條件,應答位什么的都由數據控制電路完成。