起因

本文的重心为讲解如何为一款芯片移植和实现 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. Gradle Wrapper

    Gradle Wrapper 当把本地一个项目放入到远程版本库的时候,如果这个项目是以gradle构建的,那么其他人从远程仓库拉取代码之后如果本地没有安装过gradle会无法编译运行,如果对gradl ...

  2. Java I/O体系从原理到应用(非原创)

    基础概念 在介绍I/O原理之前,先重温几个基础概念: 1 操作系统与内核 操作系统:管理计算机硬件与软件资源的系统软件内核:操作系统的核心软件,负责管理系统的进程.内存.设备驱动程序.文件和网络系统等 ...

  3. oracle再回首

    第一章 Oracle 数据库的使用   一. 数据库相关概念   1 什么是数据库 所谓的数据库其实就是数据的集合.用户可以对集合中的数据进行新增.查询.更新. 删除等操作.数据库是以一定方式储存在一 ...

  4. 焦大:seo思维光年(下)seo操作如何度量化

    http://www.wocaoseo.com/thread-57-1-1.html 如果不能度量就无法进行改进,所以度量化或数据化是网站分析和网站研究必须进行的一个方面,seo也不能例外.我在上篇文 ...

  5. WPF新手快速入门系列 2.绑定

    [概要] 上一章讲了布局,按照市面上的书籍每一本讲的顺序都不一样,本系列是希望大家能快速上手去应对工作需要,所以本章就直接开始讲绑定. 如有学习过程中想交流学习.疑惑解答可以来此QQ群交流:58074 ...

  6. 痞子衡嵌入式:导致串行NOR Flash在i.MXRT下无法正常下载/启动的常见因素之SFDP

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是导致串行NOR Flash在i.MXRT下无法正常下载/启动的常见因素之SFDP. i.MXRT系列MCU发布已两年多了,基于i.MXR ...

  7. RabbitMQ高级之消息限流与延时队列

    人生终将是场单人旅途,孤独之前是迷茫,孤独过后是成长. 楔子 本篇是消息队列RabbitMQ的第五弹. 上篇本来打算讲述RabbitMQ的一些高级用法: 如何保证消息的可靠性? 消息队列如何进行限流? ...

  8. private protected internal public

    //C#中的访问修饰符: //private,私有访问修饰符,被private访问修饰符修饰的成员只有在当前类的内部可以访问,其他地方一律不能访问[类中成员,如果不写访问修饰符则默认都是私有的] // ...

  9. Salesforce LWC学习(二十三) Lightning Message Service 浅谈

    本篇参考: https://trailhead.salesforce.com/content/learn/superbadges/superbadge_lwc_specialist https://d ...

  10. tokitsukaze and RPG(暴力优化)

    链接:https://ac.nowcoder.com/acm/contest/308/B 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言5242 ...