起因

本文的重心为讲解如何为一款芯片移植和实现 micropython 的通用组件,但会顺带解释不同芯片的工作方式和特性。

国际惯例,先有起因,再谈问题的解决,所以记得上次总结的 关于 K210 MaixPy 的 I2C 读取设备,搜索不到设备,通信失败的一些原因以及解决方案。

而这次终于出现了两个 I2C 从机扫不到的情况,分别是 MLX90640 和 tcs34725 传感器。

可能の问题分析

我们需要注意一个事实就是,无论是在 STM32 / ESP32 / K210 时期都会发生的事情,只要 I2C 主机和从机设计的上拉电阻不合理,经常会出现从机上拉能力不足导致无法向主机应答,虽然说,不应该让软件向硬件妥协,但事实就是,硬件做好了,在不改变电路走线的情况下克服这个问题,也是软件应该做的。(毕竟是硬件)

我们做一下简单分析,如 I2C 扫不到地址,如 I2C 配置后无法连接,关于扫不到地址,我们可以知道 Scan I2C 地址的方法可以为 主机 发生 从机地址 后等待从机 hold 住 SDA 此时主机 read SDA 被拉起 可知 从机做出了 ACK 应答,表示该地址上存在从机,关于这个流程和描述详细可以看看国产芯片对 I2C 主从实现的流程描述,这里我推荐 GD32 / STM32 的中文编程手册,对小白比较友好。

在 MaixPy 中 硬 I2C 使用的是 read 地址查找,软 I2C 则为 write 后 read 。

不过问题往往并非一个 scan 不到的问题,如在初次上电工作正常,配置了 I2C 后就再也得不到数据了。关于这个问题,我做一个简单的示意图,主要原因也和 I2C 的信号衰减,还有从机上拉能力有关,还有从机传感器自身的问题。

如果从硬件上看,这种情况可能是从机开始工作后的与主机的通路上的电平开始衰减,在主机在发送或接收数据的时候,要么上拉能力不足以到达主机与从机识别的电平,要么到达的时间太慢,主从机没能接收到彼此的应答,此时就会出现主从机接收不到数据超时的情况,而关于在 K210 的问题我们在前一次的事件上也给出了解答,所以这次将通过 GPIO 实现的软 I2C 将克服这个问题,关于 GPIO 的内部实现且不讨论,本文将重点介绍软件逻辑的实现过程。

可以如何实现 MaixPy 的 I2C 功能(MicroPython)。

通常来说,实现一个软 I2C 不难,但如何为 MicroPython 实现该功能,并且不影响原有功能,共存使用,所以我们先构建一个 MicroPython 的标准 I2C 示例代码作为参考。

from machine import I2C

i2c = I2C(I2C.I2C0, freq=100000, scl=28, sda=29)
devices = i2c.scan()
print(devices) for device in devices:
i2c.writeto(device, b'123')
i2c.readfrom(device, 3)

事实上从 esp8266 / esp32 之后才开始使用了 machine 模块,早期的 stm32 micropytho 用得所谓的 pyb 就像智障,不为其他芯片做考虑,官方也意识到了这个问题,但已经改不过来了,或许可以额外补充该接口的定义后再迭代到统一,但也不是现在了。

我们知道这份 Python 代码就是我们最终要实现的目标,无论硬软 I2C 都应该可以通过这份代码正常工作。

从这里 https://github.com/micropython/micropython/blob/master/extmod/machine_i2c.c 我们可以获取 MicroPython 官方对软实现功能逻辑的抽象模块,我们可以看到关键的 I2C 操作代码如下。


STATIC void mp_hal_i2c_delay(machine_i2c_obj_t *self) {
// We need to use an accurate delay to get acceptable I2C
// speeds (eg 1us should be not much more than 1us).
mp_hal_delay_us_fast(self->us_delay);
} STATIC void mp_hal_i2c_scl_low(machine_i2c_obj_t *self) {
mp_hal_pin_od_low(self->scl);
} STATIC int mp_hal_i2c_scl_release(machine_i2c_obj_t *self) {
uint32_t count = self->us_timeout; mp_hal_pin_od_high(self->scl);
mp_hal_i2c_delay(self);
// For clock stretching, wait for the SCL pin to be released, with timeout.
for (; mp_hal_pin_read(self->scl) == 0 && count; --count) {
mp_hal_delay_us_fast(1);
}
if (count == 0) {
return -MP_ETIMEDOUT;
}
return 0; // success
} STATIC void mp_hal_i2c_sda_low(machine_i2c_obj_t *self) {
mp_hal_pin_od_low(self->sda);
} STATIC void mp_hal_i2c_sda_release(machine_i2c_obj_t *self) {
mp_hal_pin_od_high(self->sda);
} STATIC int mp_hal_i2c_sda_read(machine_i2c_obj_t *self) {
return mp_hal_pin_read(self->sda);
}

也就是说,其他芯片只需要提供如下操作即可将软 I2C 实现,实现后我们再来说说如何硬软功能结合。

  • mp_hal_i2c_delay

    • mp_hal_delay_us_fast
  • mp_hal_i2c_scl_low
    • mp_hal_pin_od_low
  • mp_hal_i2c_scl_release
    • mp_hal_pin_od_high
    • mp_hal_pin_read
  • mp_hal_i2c_sda_low
    • mp_hal_pin_od_low
  • mp_hal_i2c_sda_release
    • mp_hal_pin_od_high
  • mp_hal_i2c_sda_read
    • mp_hal_pin_read

不仅要实现 I2C 的 SCL 和 SDA 的 release 和 low 以及 sda 的 read ,还要实现 GPIO 的 od 开漏的 high 和 low 就可以将其对接到最终的工作流程中,这样你就可以实现了软 I2C 功能,是不是很简单?我相信你也可以的。

MicroPython 软 I2C 移植后出现的问题

我认为移植逻辑是很容易的一件事情,难的反而是要结合硬件的实际情况来判断问题,所以在 K210 MaixPy 上实现 I2C 后就当场去世了,嗯,根本不能用。

最初从机不应答的时候,量测数据后发现输出结果不一样,所以我怀疑 I2C 的逻辑有问题,但经过调试后发现,实际上是 GPIO 的工作机制存在一些误差或者说差异,这里拿一张我很久之前记录下来的图,现在拿这张图出来解释解释。

在 K210 中 I2C 的引脚配置由内部硬件完成,现在单独拿到 GPIO 模拟实现,我们需要注意的就是 GPIO 的配置,通常我们的软件逻辑都是先配置后再设置电平输出,但 K210 的函数封装中存在配置的时候 GPIO 输出会被打开,这就导致了上一次的 GPIO 状态被输出,所以我们的逻辑要改成 先配置电平,再配置输出,否正它会像下图一样出现。

这跟 GPIO 的实现也有很大的关系,但从逻辑上来看,或许 K210 这种才是正确的逻辑,以往的可能是因为内部逻辑设计的比较好,所以在 esp32 上没有存在这种问题。


STATIC void mp_hal_i2c_sda_low(machine_hard_i2c_obj_t *self) {
// mp_hal_pin_od_low(self->pin_sda);
gpiohs_set_pin(self->pin_sda, 0);
gpiohs_set_drive_mode(self->pin_sda, GPIO_DM_OUTPUT);
}

接着遇到的问题是 K210 主机的 SDA GPIO 配置了开漏输出还是会导致从机无法拉低信号做出应答,所以要求主机的 GPIO 在输出后立刻转回输入,这事实上有一些不合常理,可能这就是 K210 吧。


STATIC void mp_hal_i2c_sda_release(machine_hard_i2c_obj_t *self) {
// mp_hal_pin_od_high(self->pin_sda);
gpiohs_set_pin(self->pin_sda, 1);
gpiohs_set_drive_mode(self->pin_sda, GPIO_DM_OUTPUT);
gpiohs_set_drive_mode(self->pin_sda, GPIO_DM_INPUT);
}

至此 K210 的 MaixPy 的软 I2C 就完成拉,相信在知道了这些细节后,在其他芯片的移植上面可以多一些经验和理解。

软 I2C 代码实现参考和关键函数

最后,我们开始整合到 硬 I2C 中,整理的过程很简单,唯独需要注意的是,如何区分定义硬软的工作方式和定义。

关于这个的实现,可以参考这份代码的实现 https://github.com/sipeed/MaixPy/blob/master/components/micropython/port/src/standard_lib/machine/machine_i2c.c

只需要注意两个地方,初始化时设置为 软设备的 标记 MACHINE_I2C_MODE_MASTER_SOFT 。

    if(self->i2c == (i2c_device_number_t)I2C_DEVICE_3
|| self->i2c == (i2c_device_number_t)I2C_DEVICE_4
|| self->i2c == (i2c_device_number_t)I2C_DEVICE_5) {
self->mode = MACHINE_I2C_MODE_MASTER_SOFT;
}

从而让 I2C 的逻辑函数可以判断使用的函数。

STATIC int machine_hard_i2c_writeto(mp_obj_base_t *self_in, uint16_t addr, const uint8_t *src, size_t len, bool stop) {
// mp_obj_base_t *self = (mp_obj_base_t*)MP_OBJ_TO_PTR(self_in);
machine_hard_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); #if MICROPY_PY_MACHINE_SW_I2C
if (self->mode == MACHINE_I2C_MODE_MASTER_SOFT) {
return mp_machine_i2c_writeto(self, addr, src, len, stop);
}
#endif
//TODO: stop not implement
//TODO: send 0 byte date support( only send start, slave address, and wait ack, stop at last)
int ret = maix_i2c_send_data(self->i2c, addr, src, len, 20);
if(ret != 0)
ret = -EIO;
else
ret = len;//TODO: get sent length actually
return ret;
}

不过我实现的手段是让软设备的操作截断后续的逻辑,这个看个人的实现心情决定。

关于 MicroPython 的软设备的实现函数只需要注意关键的回调函数,如:

STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = {
.readfrom = machine_hard_i2c_readfrom,
.writeto = machine_hard_i2c_writeto,
};

本来按预期来说,正确的实现手段是传递该结构体所需要的接口进去完成正确的接口替换,但我没有这么做,所以只提及到这里。

最后的问题总结和后续问题的改善

实现 I2C 的过程中,保证了接口一致,并在 MaixPy 中测试为 50khz 的时钟频率,同时解决了 I2C 通信不稳定以及通信不到的情况,但我们需要注意的是这绝非上策,只是因为 GPIO 的驱动能力强,能够保证信号的及时罢了,如果可以,还是要多多检讨硬件的设计,这样才能真正解决这个问题。

在这不久后我实现了 SPI ,然后发现定义方面出了一些问题,我在 I2C 的时候惯性思维设计为 I2C3+ 以后的设备资源,但事实上只需要将其定义为 I2C_SOFT 即可,根本不需要在意有几个设备资源,因为它只与 pin 脚有关,这或许也是先入为主导致的问题吧,所以今后在实现接口的时候,要多参考其他人的接口设计来实现。

那么,快快试试吧!

就酱紫!

2020年10月1日 junhuanchen

为 MaixPy 加入软 I2C 接口(移植 MicroPython 的 I2C)的更多相关文章

  1. (6)s3c2440用I2C接口访问EEPROM

    在前面阅读理解了I2C的官方协议文档后,就拿s3c2440和EEPROM来验证一下. 本来是想用s3c2440的SDA和SCL管脚复用为GPIO来模拟的,但在没有示波器的情况下搞了一周,怎么都出不来, ...

  2. 解决STM32 I2C接口死锁在BUSY状态的方法讨论

    关于STM32的I2C接口死锁在BUSY状态无法恢复的现象,网上已有很多讨论,看早几年比较老的贴子,有人提到复位MCU也无法恢复.只有断电才行的状况,那可是相当严重的问题.类似复位也无法恢复的情况是存 ...

  3. JZ2440 裸机驱动 第12章 I2C接口

    本章目标: 了解I2C总线协议: 掌握S3C2410/S3C2440中I2C接口的使用方法: 12.1 I2C总线协议及硬件介绍 12.1.1 I2C总线协议 1 I2C总线的概念 2 I2C总线的信 ...

  4. 树莓派配置RTC时钟(DS3231,I2C接口)

    1.购买基于DS3231的RTC时钟模块,并且支持3.3V的那种 2.配置树莓派 a.打开树莓派的i2c接口 sudo raspi-config -->Interfacing Options - ...

  5. LWIP network interface 即 LWIP 的 硬件 数据 接口 移植 首先 详解 STM32 以太网数据 到达 的第一站: ETH DMA 中断函数

    要 运行  LWIP  不光 要实现  OS  的 一些 接口  ,还要 有 硬件 数据 接口 移植 ,即 网线上 来的 数据 怎么个形式 传递给  LWIP ,去解析 做出相应的 应答  ,2017 ...

  6. linux 标准i2c接口(一)

    一:I2C设备操作方式: 1.  应用程序操作法:i2c的设备的驱动可以直接利用linux内核提供的i2c-dev.c文件提供的ioctl函数接口在应用层实现对i2c设备的读写,但是在应用层使用ioc ...

  7. EEPROM的操作---SPI接口和I2C接口

    参考:http://blog.csdn.net/yuanlulu/article/details/6163106 ROM最初不能编程,出厂什么内容就永远什么内容,不灵活.后来出现了PROM,可以自己写 ...

  8. i2c接口笔记

    一. i2c基础知识 1. NACK信号:当在第9个时钟脉冲的时候SDA线保持高电平,就被定义为NACK信号.Master要么产生STOP条件来放弃这次传输,或者重复START条件来发起一个新的开始. ...

  9. I2C总线和S5PV210的I2C总线控制器

    一.什么是I2C通信协议? 1.物理接口:SCL + SDA (1)SCL(serial clock):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道. (2)SDA(serial ...

随机推荐

  1. 医疗seo常用的图标工具

    http://www.wocaoseo.com/thread-304-1-1.html 下面是一些医疗seo常用的一些图表工具,这些都是些最简单的工具,主要放置这里以防止以后有作用. 1,医疗的常用搜 ...

  2. 不要再学 JSP 了,学 SpringBoot + Thymeleaf + Vue吧

    老读者就请肆无忌惮地点赞吧,微信搜索[沉默王二]关注这个在九朝古都洛阳苟且偷生的程序员.本文 GitHub github.com/itwanger 已收录,里面还有我精心为你准备的一线大厂面试题. 读 ...

  3. Python 爬虫+tkinter界面 实现历史天气查询

    文章目录 一.实现效果 1. python代码 2. 运行效果 二.基本思路 1. 爬虫部分 2. tkinter界面 一.实现效果 很多人学习python,不知道从何学起.很多人学习python,掌 ...

  4. 让这个Java语言的开源商城系统火起来

    Java是一门非常优秀的面向对象编程语言,功能强大且简单易用,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,凭借其简单性.面向对象.分布式.健壮性.安全性.平台独立与可 ...

  5. Labview学习之路(二)截屏时弹出来的窗口总是关闭

    当屏幕上存在一些弹出来的窗口时,总是会出现一按下截图快捷键那些窗口就关闭的情况,开始我也很苦恼,后来我发现,只要按顺序按下  Ctrl    Alt      A   就可以让那些窗口不关闭,记住一定 ...

  6. sqlmap 的 --forms之理解

    对于一个页面的form表单中的数据进行注入测试 方法有三个 ①burp抓包 将数据储存为文本文件  然后 sqlmap中使用 -r 参数进行测试 ②使用 --data参数,将数据进行测试 ③直接使用- ...

  7. UGUI核心元素、基本控件、复合控件和高级控件

    UGUI的核心元素: Anchor(锚点):每个控件都有一个Anchor属性,控件的4个顶点,分别与Anchor的4个点保持不变的距离,不受屏幕分辨率变化的影响. 系统默认设置控件的Anchor位置在 ...

  8. Fitness - 07.23 - Congratulation!

    倒计时161天 运动54分钟,共计5组半,5.8公里.拉伸5分钟. 每组跑步10分钟(6.5KM/h),走路1分钟(5.5KM/h). 终于突破了耐力跑的一天,可喜可贺! 差一点就到6公里了,觉得自己 ...

  9. django学习(二)

    1.反向解析 什么是方向解析呢? 通过一些方法得到一个结果,该结果可以直接访问对应url出发视图函数. 先给一个路由和视图函数起一个别名.但是我们要注意的是反向解析的别名是不可以冲突的!!!不然会出现 ...

  10. Java Web项目实现写日志功能

    第一步:导入log4j-1.2.16的jar包 第二步:在servlet包里编写写日志的servlet,代码如下: public class InitServlet extends HttpServl ...