I2C通信協(xié)議在嵌入式IC中應(yīng)用的特別廣泛,所以今天給大家詳細(xì)的講解一下,有解釋的不正確或不合理的地方歡迎大家提出意見。
IIC是一種半雙工串行同步通信協(xié)議,由數(shù)據(jù)線SDA和時鐘線SCL構(gòu)成串行總線,可用于發(fā)送和接收數(shù)據(jù),通常是由主設(shè)備發(fā)起,從設(shè)備被動響應(yīng),實現(xiàn)數(shù)據(jù)的傳輸。
02 I2C硬件原理圖
SDA: 數(shù)據(jù)線(雙向)
SCL: 時鐘線(主機控制)
因為I2C總線接口是開漏輸出(見下面的電氣特性圖),所以SDA和SCL必須接上拉電阻?。ㄒ话氵x用4.7K~10K的電阻)。
I2C總線上可以掛載多個主設(shè)備,以及多個從設(shè)備,在從機沒有收到主機的地址訪問信息前從機不會主動向主機發(fā)送數(shù)據(jù)。
03 I2C接口電氣特性
04 I2C總線數(shù)據(jù)傳輸起始和停止條件
起始條件: 在SCL為高電平期間,SDA產(chǎn)生一個下降沿信號
停止條件: 在SCL為高電平期間,SDA產(chǎn)生一個上升沿信號
//程序中的宏定義
#define HIGH 1
#define LOW 0
/* IO方向設(shè)置 */
#define SDA_IN() {GPIOA- >CRH&=0XFFFF0FFF;GPIOA- >CRH|=(uint32_t)8< 12;}
#define SDA_OUT() {GPIOA- >CRH&=0XFFFF0FFF;GPIOA- >CRH|=(uint32_t)3< 12;}
/* IO操作 */
#define IIC_SCL(n) (n?HAL_GPIO_WritePin(GPIOA,GPIO_PIN_12,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOA,GPIO_PIN_12,GPIO_PIN_RESET)) //SCL
#define IIC_SDA(n) (n?HAL_GPIO_WritePin(GPIOA,GPIO_PIN_11,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOA,GPIO_PIN_11,GPIO_PIN_RESET)) //SDA
#define READ_SDA HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_11) //輸入SDA
//產(chǎn)生IIC起始信號
void IIC_Start(void)
{
SDA_OUT();//sda線輸出
IIC_SDA(HIGH);
IIC_SCL(HIGH);
delay_us(4);
IIC_SDA(LOW);//START:when CLK is high,DATA change form High to low
delay_us(4);
IIC_SCL(LOW);//鉗住I2C總線,準(zhǔn)備發(fā)送或接收數(shù)據(jù)
}
//產(chǎn)生IIC停止信號
void IIC_Stop(void)
{
SDA_OUT();//sda線輸出
IIC_SCL(LOW);
IIC_SDA(LOW);//STOP:when CLK is high DATA change form low to High
delay_us(4);
IIC_SCL(HIGH);
IIC_SDA(HIGH);//發(fā)送I2C總線結(jié)束信號
delay_us(4);
}
05 數(shù)據(jù)傳輸格式
- 在SCL的每個時鐘脈沖期間傳輸 1 個數(shù)據(jù)位;
- 地址由 7 bit 構(gòu)成,最低位為讀寫命令,0:寫,1:讀
- SDA數(shù)據(jù)線上的 1 個字節(jié)由 8 個數(shù)據(jù)位組成,字節(jié)可以是設(shè)備地址、寄存器地址,也可以是寫入或從從機讀取的數(shù)據(jù);
- 首先傳輸數(shù)據(jù)的是最高有效位(MSB);
- 在啟動和停止條件之間,可以將任意數(shù)量字節(jié)的數(shù)據(jù)從主設(shè)備傳輸?shù)綇脑O(shè)備;
- 在時鐘周期的高相位期間,SDA線上的數(shù)據(jù)必須保持穩(wěn)定,因為SCL高時數(shù)據(jù)線上的變化被解釋為控制命令(啟動或停止);
應(yīng)答信號
Acknowledge (ACK) and Not Acknowledge (NACK);
數(shù)據(jù)的每個字節(jié)(包括地址字節(jié))后面跟著來自接收器的一個ACK位,ACK位允許接收機與發(fā)射機進行通信,告知該字節(jié)已成功接收,并可發(fā)送另一個字節(jié),這其實是I2C總線的一種數(shù)據(jù)校驗方式。
在接收機發(fā)送ACK之前,發(fā)射機必須釋放SDA線路。為了發(fā)送ACK位,接收器應(yīng)在ACK/NACK相關(guān)時鐘周期(第9個周期)的低相位期間拉低SDA線,以便在ACK/NACK相關(guān)時鐘周期的高相位期間SDA線穩(wěn)定在低電平,同時必須考慮設(shè)置和保持的時間。
當(dāng)然,主機也有可能會接收到費應(yīng)答信號NACK:
接收到非應(yīng)答信號NACK可能有以下原因:
1.接收器無法接收或發(fā)送,因為它正在執(zhí)行某些實時功能,并且尚未準(zhǔn)備好開始與主機通信;
2.在傳輸過程中,接收器獲取其不理解的數(shù)據(jù)或命令;
3.在傳輸過程中,接收器無法再接收任何數(shù)據(jù)字節(jié);
4.主接收器數(shù)據(jù)已經(jīng)讀取完畢,默認(rèn)NACK信號的從機發(fā)過來的;
等待應(yīng)答信號和產(chǎn)生應(yīng)答信號程序:
//主機等待從機應(yīng)答信號
uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime=0;
SDA_IN();//主機SDA設(shè)置為讀取模式
IIC_SDA(HIGH);//主機釋放SDA信號線
delay_us(1);
IIC_SCL(HIGH);
delay_us(1);
while(READ_SDA)//等待并讀取SDA狀態(tài)
{
ucErrTime++;
if(ucErrTime >250)//等待超時后結(jié)束本次數(shù)據(jù)傳輸
{
IIC_Stop();
return 1;
}
}
IIC_SCL(LOW);//時鐘輸出0
return 0;
}
//產(chǎn)生ACK應(yīng)答信號
void IIC_Ack(void)
{
IIC_SCL(LOW);
SDA_OUT();
IIC_SDA(LOW);
delay_us(2);
IIC_SCL(HIGH);
delay_us(2);
IIC_SCL(LOW);
}
//產(chǎn)生NACK非應(yīng)答信號
void IIC_NAck(void)
{
IIC_SCL(LOW);
SDA_OUT();
IIC_SDA(HIGH);
delay_us(2);
IIC_SCL(HIGH);
delay_us(2);
IIC_SCL(LOW);
}
06 主機通過I2C總線向從機設(shè)備寫數(shù)據(jù)
I2C總線按照如下示意圖向指定設(shè)備指定寄存器發(fā)送數(shù)據(jù):
I2C總線發(fā)送數(shù)據(jù)的過程
0.主機發(fā)送一個起始信號;
1.主機發(fā)送從機設(shè)備地址,最低位為0,表示寫命令,R/W=0;
2.主機等待接收從機的應(yīng)答信號;
3.主機發(fā)送設(shè)備寄存器地址;
4.主機等待接收從機的應(yīng)答信號;
5.主機發(fā)送一個字節(jié)數(shù)據(jù);
6.主機等待接收從機的應(yīng)答信號;
8.主機接收從機上傳的一個字節(jié)數(shù)據(jù)(一般情況下只發(fā)送一個字節(jié)數(shù)據(jù));
9.主機等待接收從機的應(yīng)答信號;
10.主機繼續(xù)發(fā)送數(shù)據(jù),等待從機應(yīng)答信號,重復(fù)步驟8和9;
11.主機發(fā)送一個停止信號;
主機模擬I2C發(fā)送數(shù)據(jù)代碼實現(xiàn)(基于STM32)
//發(fā)送一個字節(jié)數(shù)據(jù)
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
IIC_SCL(LOW);
for(t=0;t< 8;t++)
{
IIC_SDA((txd&0x80) >>7);
txd< <=1;
delay_us(2);
IIC_SCL(HIGH);
delay_us(2);
IIC_SCL(LOW);
delay_us(2);
}
}
//I2C總線向設(shè)指定設(shè)備指定寄存器寫一個字節(jié)數(shù)據(jù)
//devaddr:設(shè)備地址
//addr:寄存器地址
//data:待發(fā)送數(shù)據(jù)
void iicDevWriteByte(uint8_t devaddr,uint8_t addr,uint8_t data)
{
IIC_Start();//起始信號
IIC_Send_Byte(devaddr);//發(fā)送從機設(shè)備地址
IIC_Wait_Ack();
IIC_Send_Byte(addr);//發(fā)送寄存器地址
IIC_Wait_Ack();
IIC_Send_Byte(data);//發(fā)送數(shù)據(jù)
IIC_Wait_Ack();
IIC_Stop();//停止信號
}
//I2C總線向指定設(shè)備指定地址連續(xù)寫多個字節(jié)數(shù)據(jù)
//devaddr:設(shè)備地址
//addr:寄存器地址
//len:發(fā)送數(shù)據(jù)的長度
//wbuf:待發(fā)送數(shù)據(jù)緩存
void iicDevWrite(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *wbuf)
{
int i=0;
IIC_Start();//起始信號
IIC_Send_Byte(devaddr);//發(fā)送從機設(shè)備地址,發(fā)送寫命令,R/W=0
IIC_Wait_Ack();
IIC_Send_Byte(addr);//寄存器地址
IIC_Wait_Ack();
for(i=0; i< len; i++)
{
IIC_Send_Byte(wbuf[i]);
IIC_Wait_Ack();//等待ACK信號
}
IIC_Stop( );//停止信號
}
07 主機通過I2C總線讀取從機發(fā)送數(shù)據(jù)
I2C總線按照如下示意圖從指定從機的指定寄存器讀取數(shù)據(jù):
I2C總線讀取數(shù)據(jù)的過程
0.主機發(fā)送一個起始信號;
1.主機發(fā)送從機設(shè)備地址,最低位為0,表示寫命令,R/W=0;
2.主機等待接收從機的應(yīng)答信號;
3.主機發(fā)送設(shè)備寄存器地址;
4.主機等待接收從機的應(yīng)答信號;
5.主機重復(fù)發(fā)送一個起始信號;
6.主機發(fā)送從機設(shè)備地址,最低位為1,表示讀命令,R/W=1;
7.主機等待接收從機的應(yīng)答信號;
8.主機接收從機上傳的一個字節(jié)數(shù)據(jù);
9.若主機發(fā)送ACK應(yīng)答信號,繼續(xù)接收從機數(shù)據(jù);若主機發(fā)送NACK非應(yīng)答信號,停止接收數(shù)據(jù);
10.主機發(fā)送一個停止信號;
主機模擬I2C接收數(shù)據(jù)代碼實現(xiàn)(基于STM32)
//讀取一個字節(jié)數(shù)據(jù)
//ack=1時,發(fā)送ACK,表示還有數(shù)據(jù)待繼續(xù)讀取
//ack=0時,發(fā)送NACK,表示停止讀取數(shù)據(jù)
uint8_t IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA設(shè)置為輸入
for(i=0;i< 8;i++ )
{
IIC_SCL(LOW);
delay_us(2);
IIC_SCL(HIGH);
receive< <=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//發(fā)送nACK
else
IIC_Ack();//發(fā)送ACK
return receive;
}
//從指定設(shè)備指定寄存器地址讀取一個字節(jié)數(shù)據(jù)
//ReadAddr:開始讀數(shù)的地址
//temp:讀到的數(shù)據(jù)
uint8_t iicDevReadByte(uint8_t devaddr,uint8_t addr)
{
uint8_t temp=0;
IIC_Start();//起始信號
IIC_Send_Byte(devaddr);//發(fā)送從機設(shè)備地址,發(fā)送寫命令,R/W=0
IIC_Wait_Ack();
IIC_Send_Byte(addr);//發(fā)送寄存器地址
IIC_Wait_Ack();
IIC_Start();//Repeated START
IIC_Send_Byte(devaddr|1);//發(fā)送從機設(shè)備地址,發(fā)送讀命令,R/W=1
IIC_Wait_Ack();
temp=IIC_Read_Byte(0);
IIC_Stop();//停止信號
return temp;
}
//從指定設(shè)備指定寄存器地址連續(xù)讀取多個字節(jié)數(shù)據(jù)
//devaddr:從機設(shè)備地址
//addr:寄存器地址
//len:字節(jié)總長度
//rbuf:讀取數(shù)據(jù)緩存區(qū)
void iicDevRead(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *rbuf)
{
int i=0;
IIC_Start();//起始信號
IIC_Send_Byte(devaddr);//發(fā)送從機設(shè)備地址,發(fā)送寫命令,R/W=0
IIC_Wait_Ack();
IIC_Send_Byte(addr);//發(fā)送寄存器地址
IIC_Wait_Ack();
IIC_Start();//Repeated START
IIC_Send_Byte(devaddr|1);//發(fā)送從機設(shè)備地址,發(fā)送寫命令,R/W=1
IIC_Wait_Ack();
for(i=0; i< len; i++)
{
if(i==len-1)
{
rbuf[i]=IIC_Read_Byte(0);//全部數(shù)據(jù)接收完畢,主機發(fā)送NACK信號,產(chǎn)生停止信號
}
else
rbuf[i]=IIC_Read_Byte(1);//數(shù)據(jù)未全部接收完,主機發(fā)送ACK信號,繼續(xù)接收數(shù)據(jù)
}
IIC_Stop( );//停止信號
}
-
上拉電阻
+關(guān)注
關(guān)注
5文章
356瀏覽量
30515 -
寄存器
+關(guān)注
關(guān)注
31文章
5273瀏覽量
119657 -
I2C總線
+關(guān)注
關(guān)注
8文章
386瀏覽量
60743 -
SDA
+關(guān)注
關(guān)注
0文章
124瀏覽量
28046 -
開漏輸出
+關(guān)注
關(guān)注
0文章
34瀏覽量
7302
發(fā)布評論請先 登錄
相關(guān)推薦
評論