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才能和乐鑫一决高下。这整洁代码的背后,是工程师静下心来日复一日的努力的完善的结果,中间经过多少次迭代,估计只有做这件事情的工程师才清楚。唯有心平气和,不急不躁的高手才能做到。真心地认为,想学习嵌入式的同学,可以把乐鑫的代码当做模仿的对象了。绝对是一份非常好的教材。
esp8266 I2C 实例解析及源码分析的更多相关文章
- ReentrantLock解析及源码分析
本文结构 Tips:说明一部分概念及阅读源码需要的基础内容 ReentrantLock简介 公平机制:对于公平机制和非公平机制进行介绍,包含对比 实现:Sync源码解析额,公平和非公平模式的加锁.解锁 ...
- Django(49)drf解析模块源码分析
前言 上一篇分析了请求模块的源码,如下: def initialize_request(self, request, *args, **kwargs): """ Retu ...
- Django 之 restframework 解析器源码分析
解析器分类: 1. JSONPaser ----> 解析 JSON-serialized data (解析JSON序列化的数据) 2.FormParser ---->解析form 表单中 ...
- 实例解析Collections源码,Iterator和ListIterator
比如一个视频或文章有多个页面标签设置,我们在看一篇文章或一个视频时,底部有为你推荐栏目. 如何根据这个文章或视频的标签,来实现这个推荐栏目呢. public List<VideoInfoVo&g ...
- layoutInflater参数解析与源码分析
关于LayoutInflater方法,无论是在listview的适配器中,还是在动态添加view的时候,都会出现它的身影,最开始我在看<第一行代码>时,不知道这个方法实际的参数到底指的是什 ...
- Java Collections 源码分析
Java Collections API源码分析 侯捷老师剖析了不少Framework,如MFC,STL等.侯老师有句名言: 源码面前,了无秘密 这句话还在知乎引起广泛讨论. 我对教授程序设计的一点想 ...
- Android应用层View绘制流程与源码分析
1 背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...
- Retrofit源码分析(一)
1.基本用法 创建接口 public interface GitHubService { @GET("users/{user}/repos") Observable<List ...
- ABP源码分析一:整体项目结构及目录
ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...
- jQuery源码分析系列
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...
随机推荐
- 使用Docker部署Tomcat
目录 使用Docker部署Tomcat 1. 获取镜像 2. 第一次启动tomcat 3.带参数启动 4.查看tomcat日志 5.时区问题 使用Docker部署Tomcat 1. 获取镜像 dock ...
- Tire树 学习笔记
定义与基本求法 定义 又称字典树,用边表示字母,从根节点到树上某一节点路径形成一个字符串. 例如 \(charlie:\) 基本求法 廷显然的,往树中存就行了,查询也是显然的,通过一道例题来理解吧: ...
- yapi的安装
1.官方网站:https://hellosean1025.github.io 2.看官方的文档,部署方法: 3. 根据文档 安装环境先: 4. 开始安装yapi 5. 可见需要启动一下 6.重启一下 ...
- npm 为 指定组织下的包 配置数据源 .npmrc配置
之前公司搭了一个 npm 服务器用于发布自己的包,本地可以使用 nrm 切换数据源并安装成功,但是到了 jenkins 部署的时候就下载失败了,解决办法如下: 1.在根目录下创建 npm 配置文件,文 ...
- CF1859
A 让 \(c\) 保存数组中所有最大的数,如果所有数都相等则 \(-1\). B 只需要记录每个序列的最小值和次小值,然后对次小值求前后缀和. C 枚举最大值 \(mx\),然后遍历 \(i:n\s ...
- Centos7部署django+uwsgi+nginx 转载: https://www.cnblogs.com/wztshine/p/16172154.html
Centos7部署django+uwsgi+nginx Django + uwsgi + nginx on Centos7 wsgi 介绍 本小节来自 廖雪峰 wsgi 一文 一个Web应用的本质 ...
- NC16641 [NOIP2007]守望者的逃离
题目链接 题目 题目描述 恶魔猎手尤迪安野心勃勃,他背叛了暗夜精灵,率领深藏在海底的娜迦族企图叛变.守望者在与尤迪安的交锋中遭遇了围杀,被困在一个荒芜的大岛上.为了杀死守望者,尤迪安开始对这个荒岛施咒 ...
- Linux IOS镜像中查看Kernel 版本号
开ISO镜像,到rpm包的目录里面去找到kernel-********.RPM这个包,中间的星号那一段就是内核版本
- 【Android】Message、Handler、MessageQueue、Looper 详解
1 前言 Handler 即处理器,常用于跨线程通讯:线程A 和线程 B 拥有同一个 handler 对象,在线程 A 中使用 handler 的 sendMessage() 方法发送消息,在线程 ...
- Laravel入坑指南(11)——列队
很高兴,我们来到了Laravel入坑指南的第11篇.这一系列的文章已经接近尾声了,在这一节里面,我们一起讨论列队的用法. 列队,顾名思义,将需要处理的任务一个一个排好队,等待处理程序来处理.这机的列队 ...