一 前言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才能和樂鑫一決高下。這整潔代碼的背後,是工程師靜下心來日復一日的努力的完善的結果,中間通過多少次迭代,估計只有作這件事情的工程師才清楚。惟有心平氣和,不急不躁的高手才能作到。真心地認爲,想學習嵌入式的同窗,能夠把樂鑫的代碼當作模仿的對象了。絕對是一份很是好的教材。