I2C總線協議的軟件模擬實現方法

I2C總線協議的軟件模擬實現方法

在上一篇博客中已經講過I2C總線通訊協議,本文講述I2C總線協議的軟件模擬實現方法。html

1. 簡述

所謂的I2C總線協議的軟件模擬實現方法,就是用軟件控制GPIO的輸入、輸出和高低電平變化,來模擬I2C總線通信過程當中SCL、SDA的電平變化來實現的。debug

2. I2C總線的封裝

每一個處理器對應的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;

3. 軟件模擬實現

3.1 I2C總線的初始化

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;
}

3.2 I2C總線的起始位

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;
}

3.3 I2C總線的結束位

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;
}

3.4 I2C總線的應答

爲了統一管理和使用方便,將I2C總線的等待應答、發送應答信號、發送非應答信號封裝在一塊兒進行管理。it

1)I2C總線的等待應答

在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;
}

2)I2C總線的發送應答

在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;
}

3.5 I2C總線的寫操做

// 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;
}

3.6 I2C總線的讀操做

// 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;
}
相關文章
相關標籤/搜索