文章目錄
1. I2C簡介
I2C 是很常見的一種總線協議,I2C 是 NXP 公司設計的,I2C 使用兩條線在主控制器和從機之間進行數據通信。一條是 SCL(串行時鐘線),另外一條是 SDA(串行數據線),這兩條數據線需要接上拉電阻,總線空閒的時候 SCL 和 SDA 處於高電平。I2C 總線標準模式下速度可以達到 100Kb/S,快速模式下可以達到 400Kb/S。I2C 總線工作是按照一定的協議來運行的,接下來就看一下 I2C 協議。I2C 是支持多從機的,也就是一個 I2C 控制器下可以掛多個 I2C 從設備,這些不同的 I2C從設備有不同的器件地址,這樣 I2C 主控制器就可以通過 I2C 設備的器件地址訪問指定的 I2C設備了,一個 I2C 總線連接多個 I2C 設備如下圖所示
圖中 SDA 和 SCL 這兩根線必須要接一個上拉電阻,一般是 4.7K。其餘的 I2C 從器件都掛接到 SDA 和 SCL 這兩根線上,這樣就可以通過 SDA 和 SCL 這兩根線來訪問多個 I2C設備。
2. I2C通信協議
2.1 起始位
I2C 通信起始標誌,通過這個起始位就可以告訴 I2C 從機,“我”要開始進行 I2C 通信了。在 SCL 爲高電平的時候,SDA 出現下降沿就表示爲起始位,如下圖所示:
2.2 終止位
停止位就是停止 I2C 通信的標誌位,和起始位的功能相反。在 SCL 爲高電平的時候,SDA出現上升沿就表示爲停止位,如下圖 所示:
2.3 數據傳輸
I2C 總線在數據傳輸的時候要保證在 SCL 高電平期間,SDA 上的數據穩定,因此 SDA 上的數據變化只能在 SCL 低電平期間發生,如下圖所示:
2.4 應答信號
當 I2C 主機發送完 8 位數據以後會將 SDA 設置爲輸入狀態,等待 I2C 從機應答,也就是等到 I2C 從機告訴主機它接收到數據了。應答信號是由從機發出的,主機需要提供應答信號所需的時鐘,主機發送完 8 位數據以後緊跟着的一個時鐘信號就是給應答信號使用的。從機通過將 SDA 拉低來表示發出應答信號,表示通信成功,否則表示通信失敗。
下圖是一幀完整的I2C數據
2.5 寫時序
I2C寫時序如下圖中右側上面兩小圖所示。每一副小圖中橫線上策表示主機發送的信號或數據,橫線下冊表示從機發送的信號或數據。
I2C多次寫時序過程如下:
1. 主機發送起始信號,然後發送七位從機地址和寫標誌位
2. 從機給一個應答信號
3. 主機發送八位的寄存器地址
4. 從機給一個應答信號
5. 主機發送八位的數據
6. 從機給一個應答信號
… 循環執行 5 和 6 兩步 …
7. 數據發送完成時,主機發送停止信號,結束數據傳輸
2.6 讀時序
I2C多次讀時序過程如下:
1. 主機發送起始信號,然後發送七位從機地址和 寫 標誌位
2. 從機給一個應答信號
3. 主機發送八位的寄存器地址
4. 從機給一個應答信號
5. 主機再次發送起始信號,然後發送七位從機地址和 讀 標誌位
6. 從機給一個應答信號
7. 從機發送八位的數據
8. 主機給一個應答信號
… 循環執行 7 和 8 兩步 …
9. 主機不想繼續讀取數據時,從機發送8爲數據後,主機不應答,主機發送停止信號結束傳輸過程
3. STM8L052R8 I2C讀寫示例
3.1 i2c.h
#ifndef _I2C_H_
#define _I2C_H_
#define I2C_INTERFACE I2C1
#define I2C_CLK CLK_Peripheral_I2C1
#define I2C_SCL_PORT GPIOC
#define I2C_SCL_PIN GPIO_Pin_1
#define I2C_SDA_PORT GPIOC
#define I2C_SDA_PIN GPIO_Pin_0
void i2c_init(void);
void i2c_master_read(uint8_t addr, uint8_t *buf, uint8_t len);
void i2c_master_write(uint8_t addr, uint8_t *buf, uint8_t len);
#endif /* i2c.h */
3.2 i2c_init(初始化)
void i2c_init(void)
{
I2C_DeInit(I2C_INTERFACE);
GPIO_Init(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_Mode_Out_PP_High_Slow); // I2C1_SCL -> PC1
GPIO_Init(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_Mode_Out_PP_High_Slow); // I2C1_SDA -> PC0
CLK_PeripheralClockConfig(I2C_CLK, ENABLE); // 使能I2C1時鐘
// 速率 100kHz,7位
I2C_Init(I2C_INTERFACE, 10000, 0x01, I2C_Mode_I2C, I2C_DutyCycle_2, I2C_Ack_Enable, I2C_AcknowledgedAddress_7bit);
I2C_Cmd(I2C_INTERFACE, ENABLE); // 使能I2C1
}
3.3 發送多個字節
芯片手冊發送流程如下:
/*
* 功 能: i2c主機發送數據
* @param1: 從機地址/從機寄存器地址
* @param2: 要寫入的數據
* @param4: 要寫入數據的長度,單位字節
*/
void i2c_master_write(uint8_t addr, uint8_t *buf, uint8_t len)
{
//printf("write test 1\n");
delay(100);
while( I2C_GetFlagStatus(I2C_INTERFACE, I2C_FLAG_BUSY) ); // 等待空閒
// 產生一個起始信號。測試EV5,檢測從器件返回一個應答信號
I2C_GenerateSTART(I2C_INTERFACE, ENABLE);
while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_MODE_SELECT) );
//printf("write test 2\n");
// 設置I2C從器件地址,I2C主設備爲寫模式。測試EV6,檢測從器件返回一個應答信號
I2C_Send7bitAddress(I2C_INTERFACE, addr<<1, I2C_Direction_Transmitter);
while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) );
//printf("write test 3\n");
//2C_ClearFlag(I2C_INTERFACE, I2C_FLAG_ADDRESSSENTMATCHED); // must add
// 發送數據,檢測EV8_2從機返回一個應答信號
while(len > 0)
{
I2C_SendData(I2C_INTERFACE, *buf);
while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_BYTE_TRANSMITTED) );
if(len == 1) // 只有一個數據
{
break; // 跳出循環,發送停止信號
}
else // 有多個數據,循環發送
{
len--;
buf++;
}
}
// 發送停止信號
I2C_GenerateSTOP(I2C_INTERFACE, ENABLE);
}
3.4 讀取多個字節
芯片手冊讀取流程如下:
/*
* 功 能: i2c主機讀取數據
* @param1: 從機地址
* @param2: 存放讀取數據的緩存區
* @param4: 讀取數據的長度,單位字節
*/
void i2c_master_read(uint8_t addr, uint8_t *buf, uint8_t len)
{
#if 0
while( I2C_GetFlagStatus(I2C_INTERFACE, I2C_FLAG_BUSY) ); // 等待空閒
// 產生一個起始信號。測試EV5,檢測從器件返回一個應答信號
I2C_GenerateSTART(I2C_INTERFACE, ENABLE);
while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_MODE_SELECT) );
// 設置I2C從器件地址,I2C主設備爲寫模式。測試EV6,檢測從器件返回一個應答信號
I2C_Send7bitAddress(I2C_INTERFACE, addr, I2C_Direction_Transmitter);
while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) );
I2C_ClearFlag(I2C_INTERFACE, I2C_FLAG_ADDRESSSENTMATCHED); // must add
// 寄存器地址,檢測EV8_2從機返回一個應答信號
I2C_SendData(I2C_INTERFACE, *reg_addr);
while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_BYTE_TRANSMITTED) );
#endif
// 產生一個起始信號。測試EV5,檢測從器件返回一個應答信號
I2C_GenerateSTART(I2C_INTERFACE, ENABLE);
while( !I2C_CheckEvent(I2C_INTERFACE,I2C_EVENT_MASTER_MODE_SELECT) );
//printf("1\n");
// 設置I2C從器件地址,I2C主設備爲讀模式。測試EV6,檢測從器件返回一個應答信號
I2C_Send7bitAddress(I2C_INTERFACE, addr<<1, I2C_Direction_Receiver);
while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) );
//printf("2\n");
//I2C_ClearFlag(I2C_FLAG_ADDRESSSENTMATCHED);// must add
while(len > 0)
{
// 測試EV6
while( !I2C_CheckEvent(I2C_INTERFACE, I2C_EVENT_MASTER_BYTE_RECEIVED) );
*buf = I2C_ReceiveData(I2C_INTERFACE);
//printf("3\n");
if(len == 1)
{
// 讀到最後一個字節,無需應答,直接發送停止位
I2C_AcknowledgeConfig(I2C_INTERFACE, DISABLE);
I2C_GenerateSTOP(I2C_INTERFACE, ENABLE);
}
else
{
// 不是最後一個字節,發送應答信號,字節數減1,指向下一個存儲空間
I2C_AcknowledgeConfig(I2C_INTERFACE, ENABLE);
buf++;
}
len--;
}
I2C_AcknowledgeConfig(I2C_INTERFACE, ENABLE);
}