esp8266 I2C 實例解析及源碼分析

一  前言api

   做爲一個方案商兼芯片開發者,研究芯片和功能實現除了基本的工做須要,還有一層就是也變成了一種職業習慣。從芯片到方案,發現不少方案公司的人水平都比較堪憂,只會調用api,根本不會看底層的代碼實現邏輯。此次調試I2C掛載傳感器以後。app

做爲一個課題,筆者就好好地研究了一下ESP8266的I2C的源碼,沒想到的是,還收穫挺大的,具體什麼收穫,請看完代碼分析再說吧。函數

 

二 實例分析源碼分析

  1 和不少主控芯片同樣,esp8266的I2C接口也只是開放了master的底層,slave的底層沒有代碼實現部分。學習

     這個也許是需求考量,由於這種芯片通常的都是掛載傳感器,傳感器都是I2C slave的,也爲了方便掛載多個傳感器。ui

 

  2 主函數:spa

   初始化: i2c_example_master_init() 這裏主要是clk和sda的初始化和選擇。線程

   寫函數: i2c_example_master_mpu6050_write 該函數主要是負責往特定寄存器中寫入數據。調試

   讀函數: i2c_example_master_mpu6050_read 該函數主要負責從slave中讀取數據。rest

   特定傳感器初始化函數:i2c_example_master_mpu6050_init 該函數主要負責傳感器寄存器的初始化。

   簡簡單單的四個函數,就把I2C的全部功能囊括了,真是驚歎樂鑫的代碼整潔啊。

  這個只要按照例子操做,硬件ok的狀況下,通常都能讀到數據了。掛載多個傳感器的也只須要啓動多個線程便可。

 

三 底層源碼分析

   其實,假如要想深入理解I2C的協議的話,最好看一下底層代碼,幸運的是,esp8266 的I2C的底層代碼是提供了的。我簡單的閱讀以後,發現了全部的核心就在一個函數中:

該函數以下所示:

static void i2c_master_cmd_begin_static(i2c_port_t i2c_num)
{
    i2c_obj_t *p_i2c = p_i2c_obj[i2c_num];
    i2c_cmd_t *cmd;
    uint8_t dat;
    uint8_t len;
    int8_t i, k;
    uint8_t retVal;

    // This should never happen
    if (p_i2c->mode != I2C_MODE_MASTER) {
        return;
    }

    while (p_i2c->cmd_link.head) {
        cmd = &p_i2c->cmd_link.head->cmd;

        switch (cmd->op_code) {
            case (I2C_CMD_RESTART): {
                i2c_master_set_dc(i2c_num, 1, i2c_last_state[i2c_num]->scl);
                i2c_master_set_dc(i2c_num, 1, 1);
                i2c_master_wait(1);     // sda 1, scl 1
                i2c_master_set_dc(i2c_num, 0, 1);
                i2c_master_wait(1);     // sda 0, scl 1
            }
            break;

            case (I2C_CMD_WRITE): {
                p_i2c->status = I2C_STATUS_WRITE;

                for (len = 0; len < cmd->byte_num; len++) {
                    dat = 0;
                    retVal = 0;
                    i2c_master_set_dc(i2c_num, i2c_last_state[i2c_num]->sda, 0);

                    for (i = 7; i >= 0; i--) {
                        if (cmd->byte_num == 1 && cmd->data == NULL) {
                            dat = (cmd->byte_cmd) >> i;
                        } else {
                            dat = ((uint8_t) * (cmd->data + len)) >> i;
                        }

                        i2c_master_set_dc(i2c_num, dat, 0);
                        i2c_master_wait(1);
                        i2c_master_set_dc(i2c_num, dat, 1);
                        i2c_master_wait(2);

                        if (i == 0) {
                            i2c_master_wait(1);   // wait slaver ack
                        }

                        i2c_master_set_dc(i2c_num, dat, 0);
                    }

                    i2c_master_set_dc(i2c_num, i2c_last_state[i2c_num]->sda, 0);
                    i2c_master_set_dc(i2c_num, 1, 0);
                    i2c_master_set_dc(i2c_num, 1, 1);
                    i2c_master_wait(1);
                    retVal = i2c_master_get_dc(i2c_num);
                    i2c_master_wait(1);
                    i2c_master_set_dc(i2c_num, 1, 0);

                    if (cmd->ack.en == 1) {
                        if ((retVal & 0x01) != cmd->ack.exp) {
                            p_i2c->status = I2C_STATUS_ACK_ERROR;
                            return ;
                        }
                    }
                }
            }
            break;

            case (I2C_CMD_READ): {
                p_i2c->status = I2C_STATUS_READ;

                for (len = 0; len < cmd->byte_num; len++) {
                    retVal = 0;
                    i2c_master_set_dc(i2c_num, i2c_last_state[i2c_num]->sda, 0);

                    for (i = 0; i < 8; i++) {
                        i2c_master_set_dc(i2c_num, 1, 0);
                        i2c_master_wait(2);
                        i2c_master_set_dc(i2c_num, 1, 1);
                        i2c_master_wait(1);     // sda 1, scl 1
                        k = i2c_master_get_dc(i2c_num);
                        i2c_master_wait(1);

                        if (i == 7) {
                            i2c_master_wait(1);
                        }

                        k <<= (7 - i);
                        retVal |= k;
                    }

                    i2c_master_set_dc(i2c_num, 1, 0);
                    memcpy((uint8_t *)(cmd->data + len), (uint8_t *)&retVal, 1);
                    i2c_master_set_dc(i2c_num, i2c_last_state[i2c_num]->sda, 0);
                    i2c_master_set_dc(i2c_num, cmd->ack.val, 0);
                    i2c_master_set_dc(i2c_num, cmd->ack.val, 1);
                    i2c_master_wait(4);     // sda level, scl 1
                    i2c_master_set_dc(i2c_num, cmd->ack.val, 0);
                    i2c_master_set_dc(i2c_num, 1, 0);
                    i2c_master_wait(1);
                }
            }
            break;

            case (I2C_CMD_STOP): {
                i2c_master_wait(1);
                i2c_master_set_dc(i2c_num, 0, i2c_last_state[i2c_num]->scl);
                i2c_master_set_dc(i2c_num, 0, 1);
                i2c_master_wait(2);     // sda 0, scl 1
                i2c_master_set_dc(i2c_num, 1, 1);
                i2c_master_wait(2);     // sda 1, scl 1
            }
            break;
        }

        p_i2c->cmd_link.head = p_i2c->cmd_link.head->next;
    }

    p_i2c->status = I2C_STATUS_DONE;
    return;
}

  仔細閱讀這段代碼你就會發現,這個函數是esp8266的I2C的所有精華部分。 經過四個命令:restart,write read  stop 很清楚的列出了I2C的時序。假如這個時候,你對着示波器查看這些指令,再修改一下延時值,估計很快你就明白了I2C是怎麼的工做模式。

從這段代碼來看,esp8266的I2C是使用軟件模擬的。

 

四 總結

   經過分析I2C的代碼,很驚歎樂鑫的工程師的代碼水平,筆者也在幾個芯片公司待過,說實在的,感受代碼規範程度,只有st才能和樂鑫一決高下。這整潔代碼的背後,是工程師靜下心來日復一日的努力的完善的結果,中間通過多少次迭代,估計只有作這件事情的工程師才清楚。惟有心平氣和,不急不躁的高手才能作到。真心地認爲,想學習嵌入式的同窗,能夠把樂鑫的代碼當作模仿的對象了。絕對是一份很是好的教材。

相關文章
相關標籤/搜索