zoukankan      html  css  js  c++  java
  • 为 MaixPy 加入软 I2C 接口(移植 MicroPython 的 I2C)

    起因

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

  • 相关阅读:
    TRansportation ANalysis and SIMulation System
    源数据的换行符
    小学生的加减乘除
    ORDER BY today_used ASC' % (MAX_USED_TIMES)
    线程污染 重复请求
    SQLite支持的并发访问数
    数组和链表的对比
    第一类 第二类 反向 螺旋 数学归纳法
    阶乘
    api 爬虫 避免相同 input 在信息未更新 情况下 重复请求重复
  • 原文地址:https://www.cnblogs.com/juwan/p/13758083.html
Copyright © 2011-2022 走看看