起因

本文的重心为讲解如何为一款芯片移植和实现 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 示例代码作为参考。

  1. from machine import I2C
  2. i2c = I2C(I2C.I2C0, freq=100000, scl=28, sda=29)
  3. devices = i2c.scan()
  4. print(devices)
  5. for device in devices:
  6. i2c.writeto(device, b'123')
  7. 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 操作代码如下。


  1. STATIC void mp_hal_i2c_delay(machine_i2c_obj_t *self) {
  2. // We need to use an accurate delay to get acceptable I2C
  3. // speeds (eg 1us should be not much more than 1us).
  4. mp_hal_delay_us_fast(self->us_delay);
  5. }
  6. STATIC void mp_hal_i2c_scl_low(machine_i2c_obj_t *self) {
  7. mp_hal_pin_od_low(self->scl);
  8. }
  9. STATIC int mp_hal_i2c_scl_release(machine_i2c_obj_t *self) {
  10. uint32_t count = self->us_timeout;
  11. mp_hal_pin_od_high(self->scl);
  12. mp_hal_i2c_delay(self);
  13. // For clock stretching, wait for the SCL pin to be released, with timeout.
  14. for (; mp_hal_pin_read(self->scl) == 0 && count; --count) {
  15. mp_hal_delay_us_fast(1);
  16. }
  17. if (count == 0) {
  18. return -MP_ETIMEDOUT;
  19. }
  20. return 0; // success
  21. }
  22. STATIC void mp_hal_i2c_sda_low(machine_i2c_obj_t *self) {
  23. mp_hal_pin_od_low(self->sda);
  24. }
  25. STATIC void mp_hal_i2c_sda_release(machine_i2c_obj_t *self) {
  26. mp_hal_pin_od_high(self->sda);
  27. }
  28. STATIC int mp_hal_i2c_sda_read(machine_i2c_obj_t *self) {
  29. return mp_hal_pin_read(self->sda);
  30. }

也就是说,其他芯片只需要提供如下操作即可将软 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 上没有存在这种问题。


  1. STATIC void mp_hal_i2c_sda_low(machine_hard_i2c_obj_t *self) {
  2. // mp_hal_pin_od_low(self->pin_sda);
  3. gpiohs_set_pin(self->pin_sda, 0);
  4. gpiohs_set_drive_mode(self->pin_sda, GPIO_DM_OUTPUT);
  5. }

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


  1. STATIC void mp_hal_i2c_sda_release(machine_hard_i2c_obj_t *self) {
  2. // mp_hal_pin_od_high(self->pin_sda);
  3. gpiohs_set_pin(self->pin_sda, 1);
  4. gpiohs_set_drive_mode(self->pin_sda, GPIO_DM_OUTPUT);
  5. gpiohs_set_drive_mode(self->pin_sda, GPIO_DM_INPUT);
  6. }

至此 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 。

  1. if(self->i2c == (i2c_device_number_t)I2C_DEVICE_3
  2. || self->i2c == (i2c_device_number_t)I2C_DEVICE_4
  3. || self->i2c == (i2c_device_number_t)I2C_DEVICE_5) {
  4. self->mode = MACHINE_I2C_MODE_MASTER_SOFT;
  5. }

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

  1. STATIC int machine_hard_i2c_writeto(mp_obj_base_t *self_in, uint16_t addr, const uint8_t *src, size_t len, bool stop) {
  2. // mp_obj_base_t *self = (mp_obj_base_t*)MP_OBJ_TO_PTR(self_in);
  3. machine_hard_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in);
  4. #if MICROPY_PY_MACHINE_SW_I2C
  5. if (self->mode == MACHINE_I2C_MODE_MASTER_SOFT) {
  6. return mp_machine_i2c_writeto(self, addr, src, len, stop);
  7. }
  8. #endif
  9. //TODO: stop not implement
  10. //TODO: send 0 byte date support( only send start, slave address, and wait ack, stop at last)
  11. int ret = maix_i2c_send_data(self->i2c, addr, src, len, 20);
  12. if(ret != 0)
  13. ret = -EIO;
  14. else
  15. ret = len;//TODO: get sent length actually
  16. return ret;
  17. }

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

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

  1. STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = {
  2. .readfrom = machine_hard_i2c_readfrom,
  3. .writeto = machine_hard_i2c_writeto,
  4. };

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

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

实现 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. ID3\C4.5\CART

    目录 树模型原理 ID3 C4.5 CART 分类树 回归树 树创建 ID3.C4.5 多叉树 CART分类树(二叉) CART回归树 ID3 C4.5 CART 特征选择 信息增益 信息增益比 基尼 ...

  2. python123期末四题编程题 -无空隙回声输出-文件关键行数-字典翻转输出-《沉默的羔羊》之最多单词

    1. 无空隙回声输出 描述 获得用户输入,去掉其中全部空格,将其他字符按收入顺序打印输出. ‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬ ...

  3. Vue.$set的使用场景

    有这样一个需求,用户可以增加多个输入框可以编辑:     实现的思路很简单,点击增加的时候,往一个数组里面push一条数据即可: <template> <div> <di ...

  4. 初学WebGL引擎-BabylonJS:第6篇-碰撞交错与挑选

    [playground]-collisions(碰撞) 先贴官方源码(机器翻译版本) var createScene = function () { var scene = new BABYLON.S ...

  5. BM算法学习

    根据阮一峰大大的文章实现,不过没实现“搜索词中的上一次出现位置”(我直接实时查找,显然应该预处理): 文章:http://www.ruanyifeng.com/blog/2013/05/boyer-m ...

  6. VS停止调试,IIS Express也跟着关闭了

    问题描述: 我们会时不时地用VS进行调试,当点击停止调试的时候,网站再刷新一下,便会出现网页走丢的现象,然后需要重新打开网站,很是麻烦,令人抓狂.如何解决呢? 首先说下,为啥会产生这种问题? 大致描述 ...

  7. java控制流程(二)

    一.循环结构 有一天你的女朋友让你写一百遍我爱你,你是要一行一行的手写出来,还是利用编程的循环结构写出来? while 语法: 表达式返回的为boolean值 while(表达式){ 需要循环的语句 ...

  8. FIddlerd的下载教程和使用教程

    ------------恢复内容开始------------ .打开官网,官网下载地址是https://www.telerik.com/download/fiddler .打开以后选择你的相关信息如下 ...

  9. 05_进程间通信 IPC

    1.进程间的通信方式 1.磁盘交互: 速度慢,不安全 2.socket套接字 3.管道通信(Pipe) 4.消息队列(Queue, Manager().Queue, JoinableQueue) 5. ...

  10. .net mvc web api上传图片/文件并重命名

    #region 上传图片 /// <summary> /// 上传图片到服务器 当error为0时成功,为1时失败 并从errmsg获取消息 /// </summary> // ...