一 前言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才能和乐鑫一决高下。这整洁代码的背后,是工程师静下心来日复一日的努力的完善的结果,中间通过多少次迭代,估计只有作这件事情的工程师才清楚。惟有心平气和,不急不躁的高手才能作到。真心地认为,想学习嵌入式的同窗,能够把乐鑫的代码当作模仿的对象了。绝对是一份很是好的教材。