一、關於i2c協議概述
I2C總線協議只需要2根信號線即可完成數據的傳輸,這兩根線分別是時鐘線SCL和信號線SDA。I2C線上有且只有1個主設備Master和若干個從設備Slave,區別Master和Slave的標準是SCL,即誰是SCL的提供者,誰就是Master,而與SDA無關。這點尤其需要注意,發送SDA不能作爲區別Master和Slave的標準。
關於I2C總線再作以下說明:
1-兩條總線SDA和SCL都必須接上拉電阻,這是爲了確保兩條總線在空閒時都是高電平,上拉電阻的經典取值是10kΩ;
2-I2C總線上可以掛載多個主機和多個從機,但是同一時間只允許1個主機和1個從機進行通信;
3-總線上每一個設備都有一個獨立的地址,通過該地址實現通信;
4-總線上的設備是“線與”的關係,即任一設備的管腳輸出低電平都可以將該管腳所在總線的電平拉低,線與關係是時鐘同步和總線仲裁的硬件基礎;
5-I2C傳輸速度有標準速度模式(SS Mode)、快速模式(FS Mode)和高速模式(HS Mode)三種,數據傳輸速率分別爲100kbps、400kbps和3.4Mbps。
協議層:協議層規定了通訊的起始停止信號、數據有效性、響應、總線仲裁、時鐘同步、地址廣播等內容。
1、總線空閒與信號起始終止
I2C協議規定SDA和SCL都爲高電平時總線空閒(not busy)。
I2C協議規定SCL保持高電平、SDA由高變低爲起始信號(start),所有命令和數據的傳輸必須以起始信號爲首。
I2C協議規定SCL保持高電平、SDA由低變高爲終止信號(stop)。所有命令和數據的傳輸必須以終止信號爲尾。
2、數據有效
I2C協議規定在總線上出現起始信號start後,若SCL在高電平期間SDA保持電平不變,則SDA的狀態表示有效數據(data valid)。在傳輸數據時SDA的改變必須只能發生在SCL爲
低電平期間,每一bit數據有1個時鐘脈衝時長。
3、應答和非應答
I2C協議規定每個被尋址設備在接收1字節數據後都必須向發送字節的設備發送應答(ACK)信號,確認的器件必須在應答時鐘脈衝期間下拉SDA線,使得SDA線在應答相關時鐘脈衝
SCL爲高電平期間穩定爲低電平。
I2C協議規定與ACK信號相反的信號爲非應答(not ACK)信號。在主器件從從器件中讀取數據時,主器件必須在讀取的最後1字節數據後在SDA總線上產生not ACK信號以示意從器
件停止發送數據。not ACK信號是在SCL爲高電平期間保持SDA也爲高電平。
4、地址廣播
地址廣播是I2C協議規定的尋址方式。它是指主設備在產生start信號後,各個從設備開始關注總線SDA信號,此時主設備在總線上生成需接受/發送數據的從設備的
地址(Address),相當於向總線上所有從設備廣播了這一地址。每個從設備將總線上的地址與自己的地址相對比,不一致的退出接收,一致的繼續接收,直到8bit地址數據
廣播完畢,仍然留下的那一個從設備就是主設備的尋址目標。
5、總線仲裁解決的是多個主設備競爭使用同一總線的問題。
假設主控器1要發送的數據DATA1爲“101 ……”;主控器2要發送的數據DATA2爲“1001 ……”總線被啓動後兩個主控器在每發送一個數據位時都要對自己的輸出電平進行檢測,
只要檢測的電平與自己發出的電平一致,他們就會繼續佔用總線。在這種情況下總線還是得不到仲裁。當主控器1發送第3位數據“1”時(主控器2發送“0” ),由於“線與”的
結果SDA上的電平爲“0”,這樣當主控器1檢測自己的輸出電平時,就會測到一個與自身不相符的“0”電平。這時主控器1只好放棄對總線的控制權;因此主控器2就成爲總線的
唯一主宰者。(實例來自博客)
從中可以得出:參與仲裁的所有主控器都不會丟失數據;參與仲裁的所有主控器沒有固定的優先級別,而是遵循低電平優先的原則。
6、時鐘同步
時鐘同步是用來解決中控器和被控器的數據傳輸速率不相同的問題。
被控器可以通過將SCL主動拉低並延長其低電平時間的方法來通知主控器,當主控器在準備下一次傳送時發現SCL爲低電平,就會等待,直至被控器完成操作並釋放SCL線的
控制控制權。這樣,主控器實際上受到被控器的時鐘同步控制。由此可見,SCL線上的低電平是由時鐘低電平最長的器件決定,高電平的時間由高電平時間最短的器件決定。
需要說明的是,不管是總線仲裁還是時鐘同步,它們得以實現的基礎是SDA總線的“線與”性質,而這是由I2C總線獨特的IO結構決定的。另外,總線仲裁和時鐘同步之間並
不存在特定的先後關係,它們往往同時發生。
二、GPIO模擬I2C協議的C代碼實現
下面將用C語言實現上面所描述的I2C總線協議的各個動作,並將這些分散的動作整合起來實現字節的讀寫操作。
1、首先需要定義2個IO口以連接兩根總線SDA和SC,例如我這裏使用250和251,然後定義i2c速率,即delay的值,這裏設置爲10k,即udelay(100);
#define DELAY_TIME_HXD 100 //設置i2c clk, hxd019大概要求10k左右
#define GPIO_SDA 250
#define GPIO_SCL 251
2、初始化gpio模擬的i2c通訊SDA和SCL, 拉高SDA和SCL,初始化的效果是SDA和SCL總線上全部呈現高電平
static int gpio_i2c_init(void)
{
int ret;
if(hxd019d_dbg_en == 1)
printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);
if (!gpio_is_valid(GPIO_SDA)) {
printk("GPIO_SDA: %d is invalid\n", GPIO_SDA);
return -ENODEV;
}
ret = gpio_request(GPIO_SDA,"GPIO_SDA");
if(ret < 0)
{
printk("request GPIO_SDA error\n");
return ret;
}
if (!gpio_is_valid(GPIO_SCL)) {
printk("GPIO_SCL: %d is invalid\n", GPIO_SCL);
return -ENODEV;
}
ret = gpio_request(GPIO_SCL,"GPIO_SCL");
if(ret < 0)
{
printk("request GPIO_SCL error\n");
return ret;
}
if (!gpio_is_valid(GPIO_BUSY)) {
printk("GPIO_BUSY: %d is invalid\n", GPIO_BUSY);
return -ENODEV;
}
ret = gpio_request(GPIO_BUSY,"GPIO_BUSY");
if(ret < 0)
{
printk("request GPIO_BUSY error\n");
return ret;
}
ret = gpio_direction_input(GPIO_BUSY); // set GPIO_BUSY as input IO
if(ret < 0)
{
printk("set GPIO_BUSY direction fail");
return ret;
}
gpio_direction_output(GPIO_SDA, 1);
gpio_direction_output(GPIO_SCL, 1);
return 0;
}
3、起始信號,用GPIO模擬起始信號,SCL保持高電平、SDA由高變低。
static int iic_i2cstart_hxd019(void)
{
if(hxd019d_dbg_en == 1)
printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);
gpio_direction_output(GPIO_SDA, 1);
gpio_direction_output(GPIO_SCL, 1);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SDA, 0);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SCL, 0);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
return 0;
}
4、終止信號:用GPIO模擬起始信號,SCL保持高電平、SDA由低變高。
static int iic_i2cstop_hxd019(void)
{
if(hxd019d_dbg_en == 1)
printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);
gpio_direction_output(GPIO_SDA, 0);
gpio_direction_output(GPIO_SCL, 0);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SCL, 1);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SDA, 1);
udelay(DELAY_TIME_HXD);
return 0;
}
5、主控讀取ack應答信號,即主控平臺讀取ACK對應的IO口處的電平。
uint8_t GetACKSign_hxd019(void) //主控器讀取ACK對應的IO口處的電平
{
uint8_t ACKSign;
if(hxd019d_dbg_en == 1)
printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);
gpio_direction_input(GPIO_SDA);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SCL, 1);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
ACKSign = gpio_get_value(GPIO_SDA);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SCL, 0);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
return ACKSign;
}
6、主控發送ack信號,即主控平臺向ACK對應的IO口發送低電平。
void SendACKSign_hxd019(void) //主控器發送ACK
{
if(hxd019d_dbg_en == 1)
printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SDA, 0);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SCL, 1);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SCL, 0);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
return ;
}
7、主控發送not ACK信號,即主控平臺向ACK對應的IO口發送高電平。
void SendNoACKSign_hxd019(void) //主控器發送not ack
{
if(hxd019d_dbg_en == 1)
printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SCL, 1);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SCL, 0);
return ;
}
8、單字節讀操作,它的邏輯是初始化—主控器發送起始信號—主控器逐bit讀取SDA線上信號。
static int iic_I2CReadData_hxd019(uint8_t* pbData,int num) //單字節讀操作
{
uint8_t readdata = 0;
int i=8;
gpio_direction_input(GPIO_SDA);
while (i--)
{
readdata <<= 1;
gpio_direction_output(GPIO_SCL, 1);
udelay(DELAY_TIME_HXD);
readdata |= gpio_get_value(GPIO_SDA);
gpio_direction_output(GPIO_SCL, 0);
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
}
gpio_direction_output(GPIO_SCL, 0);
udelay(DELAY_TIME_HXD);
*pbData = readdata;
if(num >= (ReadBytesTotal -1))
SendNoACKSign_hxd019();
else
SendACKSign_hxd019();
udelay(DELAY_TIME_HXD);
udelay(DELAY_TIME_HXD);
return 0;
}
9、單字節寫操作,它的邏輯是初始化—主控器發送起始信號—主控器逐bit往SDA線上寫數據。
static uint8_t iic_I2CWriteData_hxd019(uint8_t bData) //單字節寫操作
{
uint8_t Data_Bit,ACKSign;
int i = 0;
if(hxd019d_dbg_en == 1)
printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);
gpio_direction_output(GPIO_SCL, 0);
udelay(DELAY_TIME_HXD);
for(i = 7;i >= 0; i--)
{
udelay(DELAY_TIME_HXD);
Data_Bit= (bData >> i) & 0x01;
if(Data_Bit)
gpio_direction_output(GPIO_SDA, 1);
else
gpio_direction_output(GPIO_SDA, 0);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SCL, 1);
udelay(DELAY_TIME_HXD);
gpio_direction_output(GPIO_SCL, 0);
}
ACKSign=GetACKSign_hxd019();
return ACKSign;
}
10、主控檢查是否接收到ACK,主控器在發完第1個地址字節後,按規定被控期需要向主控器回覆一個ACK信號,
主控器如果能在總線上檢測到這個ACK,就繼續向被控器傳送數據,否則視爲本次數據傳送失敗。
uint8_t i2c_ack_check(uint8_t ctrl_byte)
{
iic_i2cstart_hxd019();
iic_I2CWriteData_hxd019(ctrl_byte);
if(GetACKSign_hxd019() == 0)
{
// time delay here is not necessary, just to make waveforms more readable
udelay(DELAY_TIME_HXD);
gpio_direction_input(GPIO_SDA); // set SDA as input
gpio_direction_input(GPIO_SCL); // set SCL as input
return 0;
}
else
{
// time delay here is to save computing resource
udelay(DELAY_TIME_HXD);
return 1;
}
}
11、i2c讀寫寄存器操作
int gpio_analog_i2c_read(unsigned char I2cRegAddr)//讀寄存器操作
{
int i = 100;
//spin_lock(&a_lock);
unsigned char bValue = 0;
unsigned char devAddr = 0;
if(hxd019d_dbg_en == 1)
printk("hxd019d: ------%s(%d)-------\n", __func__, __LINE__);
devAddr = (chipSlaveAddr << 1);
iic_i2copen_hxd019();
udelay(DELAY_TIME_HXD);
iic_i2cstart_hxd019();
udelay(DELAY_TIME_HXD);
iic_I2CWriteData_hxd019(devAddr);
udelay(DELAY_TIME_HXD);
iic_I2CWriteData_hxd019(I2cRegAddr);
udelay(DELAY_TIME_HXD);
iic_i2cstart_hxd019();
udelay(DELAY_TIME_HXD);
iic_I2CWriteData_hxd019((devAddr | 0x1));
udelay(DELAY_TIME_HXD);
iic_I2CReadData_hxd019(&bValue,i);
udelay(DELAY_TIME_HXD);
iic_i2cstop_hxd019();
udelay(DELAY_TIME_HXD);
iic_i2cclose_hxd019();
udelay(DELAY_TIME_HXD);
//spin_unlock(&a_lock);
return bValue;
}
int gpio_analog_i2c_write(unsigned char I2cRegAddr,unsigned char data) //寫寄存器操作
{
//spin_lock(&a_lock);
unsigned char devAddr = 0;
if(hxd019d_dbg_en == 1)
printk("hxd019d: ------%s(%d)-------\n", __func__, __LINE__);
devAddr = chipSlaveAddr << 1;
iic_i2copen_hxd019();
udelay(DELAY_TIME_HXD);
iic_i2cstart_hxd019();
udelay(DELAY_TIME_HXD);
iic_I2CWriteData_hxd019(devAddr);
udelay(DELAY_TIME_HXD);
iic_I2CWriteData_hxd019(I2cRegAddr);
udelay(DELAY_TIME_HXD);
//iic_i2cstart();
//udelay(DELAY_TIME);
//iic_I2CWriteData((devAddr | 0x1));
//udelay(DELAY_TIME);
//iic_I2CReadData(&bValue);
iic_I2CWriteData_hxd019(data);
udelay(DELAY_TIME_HXD);
iic_i2cstop_hxd019();
udelay(DELAY_TIME_HXD);
iic_i2cclose_hxd019();
udelay(DELAY_TIME_HXD);
//spin_unlock(&a_lock);
return 0;
}