在上一篇博客中已經講過I2C總線通訊協議,本文講述I2C總線協議的軟件模擬實現方法。html
所謂的I2C總線協議的軟件模擬實現方法,就是用軟件控制GPIO的輸入、輸出和高低電平變化,來模擬I2C總線通信過程當中SCL、SDA的電平變化來實現的。debug
每一個處理器對應的GPIO操做都有差別,即便是同一款處理器,不一樣的人也會有不一樣的GPIO封裝風格,就以我我的習慣用的GPIO方法爲例來進行講解。我習慣上將GPIO的組和位封裝爲一個結構體,這樣使用方便,看起來也更直觀。rest
typedef struct { unsigned char group; unsigned char bit; } gpio_t;
將I2C總線中使用的SCL和SDA的GPIO進一步進行封裝。code
typedef struct { gpio_t scl; gpio_t sda; } i2c_gpio_t;
將I2C總線軟件模擬部分當作驅動程序中的一個模塊來使用,定義一個結構體來封裝I2C模塊中的一些全局變量,如:GPIO、鎖等等。本文中的鎖只是爲了保證I2C的一個操做步驟是原子的,全部鎖的使用能夠忽略,若是想要了解更過關於鎖的使用方法,請關注另一篇博客(還沒來得及寫,之後會補充)。htm
typedef struct { i2c_gpio_t gpio; spinlock_t lock; struct mutex i2c_mutex; } i2c_info_t;
1)先初始化I2C總線,具體要作的內容是,先把外部調用I2C模塊時要使用的GPIO引腳,做爲參數傳遞到I2C模塊,用來進行一系列的操做。在這裏將GPIO做爲參數傳遞到I2C模塊後,保存在全局變量的結構體中。
2)再初始化I2C總線的GPIO引腳,即將用來代替模擬I2C總線中SCL、SDA引腳的GPIO設置爲輸出,並輸出高電平,由於兩條線上都接有上拉電阻,I2C總線空閒時默認SCL、SDA都處於高電平,也就是空閒狀態。
3)若是要使用鎖機制,須要在這一步中將鎖初始化。blog
// I2C模塊初始化 int i2c_init(i2c_gpio_t *gpio) { i2c_debug("i2c_init"); // 初始化鎖 spin_lock_init(&i2c_info.lock); mutex_init(&i2c_info.i2c_mutex); // 初始化全局變量中I2C的GPIO i2c_info.gpio.scl = gpio->scl; i2c_info.gpio.sda = gpio->sda; i2c_gpio_init(); return 0; }
// I2C的GPIO初始化 static void i2c_gpio_init(void) { i2c_debug("i2c_gpio_init"); i2c_sda_init(); i2c_scl_init(); }
// I2C的SCL初始化 static void i2c_scl_init(void) { i2c_debug("scl init"); SET_SCL_OUT; SET_SCL_HIGH; }
// I2C的SDA初始化 static void i2c_sda_init(void) { i2c_debug("sda init"); SET_SDA_OUT; SET_SDA_HIGH; }
I2C總線在開始通訊時要先發送一個起始位標誌,起始位是在SCL爲高電平時,SDA由高電平變爲低電平。get
// I2C總線的起始位 int i2c_start(void) { mutex_lock(&i2c_info.i2c_mutex); SET_SDA_OUT; udelay(I2C_DELAY); SET_SDA_HIGH; udelay(I2C_DELAY); SET_SCL_HIGH; udelay(I2C_DELAY); SET_SDA_LOW; udelay(I2C_DELAY); SET_SCL_LOW; udelay(I2C_DELAY); mutex_unlock(&i2c_info.i2c_mutex); return 0; }
I2C總線在數據傳輸完成後,須要發送一個結束位,來結束I2C通信,並釋放I2C總線,結束位是在SCL爲高電平時,SDA由低電平變爲高電平博客
// I2C總線的結束位 int i2c_stop(void) { mutex_lock(&i2c_info.i2c_mutex); SET_SDA_OUT; udelay(I2C_DELAY); SET_SCL_LOW; udelay(I2C_DELAY); SET_SDA_LOW; udelay(I2C_DELAY); SET_SCL_HIGH; udelay(I2C_DELAY); SET_SDA_HIGH; udelay(I2C_DELAY); mutex_unlock(&i2c_info.i2c_mutex); return 0; }
爲了統一管理和使用方便,將I2C總線的等待應答、發送應答信號、發送非應答信號封裝在一塊兒進行管理。it
在I2C總線通信時,主設備給從設備發送一個字節的數據後,要等待從設備的一個應答信號,這時候主設備處於等待應答狀態,須要檢測從設備的應答信號是否到來,若是從設備的應答信號到來,主設備就繼續給從設備發送下一個字節的數據,或者發送中止位結束I2C通信;若是在主設備等待超時後,從設備的應答信號時鐘不到來,就說明I2C總線通信中出現問題,主設備跳出等待,直接發送結束位,以結束I2C總線通信。io
// I2C總線的等待應答 static int i2c_wait_ack(void) { int ack_times = 0; int ret = 0; mutex_lock(&i2c_info.i2c_mutex); SET_SDA_OUT; udelay(I2C_DELAY); SET_SDA_HIGH; udelay(I2C_DELAY); SET_SDA_IN; udelay(I2C_DELAY); SET_SCL_LOW; udelay(I2C_DELAY); SET_SCL_HIGH; udelay(I2C_DELAY); ack_times = 0; // 檢測從設備應答信號 while (GET_SDA_VAL) { ack_times++; // 判斷等待是否超時 if (ack_times == 10) { ret = 1; i2c_error("i2c ack error, no ack"); break; } } SET_SCL_LOW; mutex_unlock(&i2c_info.i2c_mutex); return ret; }
在I2C總線通訊的時候,主設備每次接收到從設備發送的一個字節數據後,要給從設備發送應答信號(ACK)以繼續接收從設備的數據,或者給從設備發送非應答信號(NOACK)以結束接收從設備的數據。
應答信號(ACK)就是先拉低SDA線,並在SCL爲高電平期間保持SDA線爲低電平
// I2C總線發送應答信號 static int i2c_send_ack(void) { i2c_debug("i2c_send_ack"); mutex_lock(&i2c_info.i2c_mutex); SET_SDA_OUT; udelay(I2C_DELAY); SET_SCL_LOW; udelay(I2C_DELAY); SET_SDA_LOW; udelay(I2C_DELAY); SET_SCL_HIGH; udelay(I2C_DELAY); SET_SCL_LOW; udelay(I2C_DELAY); mutex_unlock(&i2c_info.i2c_mutex); return 0; }
非應答信號(NOACK)就是不要拉低SDA線(此時SDA線爲高電平),並在SCL爲高電平期間保持SDA線爲高電平。
// I2C總線發送非應答信號 static int i2c_send_noack(void) { i2c_debug("i2c_send_noack"); mutex_lock(&i2c_info.i2c_mutex); SET_SDA_OUT; udelay(I2C_DELAY); SET_SCL_LOW; udelay(I2C_DELAY); SET_SDA_HIGH; udelay(I2C_DELAY); SET_SCL_HIGH; udelay(I2C_DELAY); SET_SCL_LOW; udelay(I2C_DELAY); mutex_unlock(&i2c_info.i2c_mutex); return 0; }
// I2C總線的寫操做 int i2c_write_byte(u8 data) { unsigned long flag = 0; u8 i = 0; local_irq_save(flag); preempt_disable(); mutex_lock(&i2c_info.i2c_mutex); SET_SDA_OUT; udelay(I2C_DELAY); for (i = 0; i < 8; i++) { if (data & 0x80) { SET_SDA_HIGH; } else { SET_SDA_LOW; } udelay(I2C_DELAY); SET_SCL_HIGH; udelay(I2C_DELAY); SET_SCL_LOW; udelay(I2C_DELAY); data <<= 0x1; } mutex_unlock(&i2c_info.i2c_mutex); preempt_enable(); local_irq_restore(flag); return 0; }
int i2c_write_byte_with_ack(u8 data) { i2c_write_byte(data); if (i2c_ack(I2C_WAIT_ACK)) { i2c_error("wait ack failed, no ack"); i2c_stop(); return -1; } return 0; }
// I2C總線的讀操做 int i2c_read_byte(u8 *data) { unsigned long flag = 0; u8 ret = 0; u8 i = 0; local_irq_save(flag); preempt_disable(); mutex_lock(&i2c_info.i2c_mutex); SET_SDA_IN; udelay(I2C_DELAY); for (i = 0; i < 8; i++) { SET_SCL_HIGH; udelay(I2C_DELAY); ret <<= 1; if (GET_SDA_VAL) { ret |= 0x01; } SET_SCL_LOW; udelay(I2C_DELAY); } mutex_unlock(&i2c_info.i2c_mutex); preempt_enable(); local_irq_restore(flag); *data = ret; return 0; }