一 前言
作为一个方案商兼芯片开发者,研究芯片和功能实现除了基本的工作需要,还有一层就是也变成了一种职业习惯。从芯片到方案,发现很多方案公司的人水平都比较堪忧,只会调用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才能和乐鑫一决高下。这整洁代码的背后,是工程师静下心来日复一日的努力的完善的结果,中间经过多少次迭代,估计只有做这件事情的工程师才清楚。唯有心平气和,不急不躁的高手才能做到。真心地认为,想学习嵌入式的同学,可以把乐鑫的代码当做模仿的对象了。绝对是一份非常好的教材。
作者:虚生 出处:https://www.cnblogs.com/dylancao/ 以音频和传感器算法为核心的智能可穿戴产品解决方案提供商 ,提供可穿戴智能软硬件解决方案的设计,开发和咨询服务。 勾搭热线:邮箱:1173496664@qq.com weixin:18019245820 市场技术对接群:347609188 |
![]() |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
2018-03-25 未来之路---写给某个程序员的话