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