esp8266 I2C 實例解析及源碼分析


一  前言

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

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

 

二 實例分析

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

     這個也許是需求考量,因為這種芯片一般的都是掛載傳感器,傳感器都是I2C slave的,也為了方便掛載多個傳感器。

 

  2 主函數:

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

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

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

   特定傳感器初始化函數: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才能和樂鑫一決高下。這整潔代碼的背后,是工程師靜下心來日復一日的努力的完善的結果,中間經過多少次迭代,估計只有做這件事情的工程師才清楚。唯有心平氣和,不急不躁的高手才能做到。真心地認為,想學習嵌入式的同學,可以把樂鑫的代碼當做模仿的對象了。絕對是一份非常好的教材。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM