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

    前言

    接着上一篇 为 MaixPy 加入软 I2C 接口(移植 MicroPython 的 I2C) 的内容,如果不知道也没关系,我主要也是留一些足迹,方便后来人在其他芯片上按图索骥。

    如果你已经看过前一篇的软 I2C 的内容的话,那么我这篇 SPI 只是留做补充章节罢了。

    嗯,这次的起因是因为 K210 的 SPI 资源全部用完了,总共 4 个 SPI 硬件资源 spi0 给了 lcd st7789 ,spi1 给了 Sdcard SPI , spi3 给了 Flash w25qxx ,剩下的 spi2 只能做从机(slave mode),如果 MaixPy(MicroPython) 之上想要使用 SPI 去向一些传感器模块(flash、psram、rfid、lora、BME280、scl3300、epaper、lcd)通信就有些捉襟见肘,如在 MaixPy 上莫名其妙的读取不到 SD 卡文件的原因是因为 SPI1 被修改定义了。

    现在 amigo 硬件上存在两个 SPI 口,一个可以留给 wifi 的 spi 或 uart 做网卡用途,另一个则必须提供 spi 功能,而使用方法如下代码。

    
    from machine import SPI
    import utime
    from Maix import GPIO
    from fpioa_manager import fm
    
    fm.register(20, fm.fpioa.GPIO5, force=True)
    cs = GPIO(GPIO.GPIO5, GPIO.OUT)
    #cs.value(0)
    #utime.sleep_ms(2000)
    
    print(os.listdir())
    spi = SPI(SPI.SPI1, mode=SPI.MODE_MASTER, baudrate=400*1000, polarity=0, phase=0, bits=8, firstbit=SPI.MSB,
        sck=21, mosi=8, miso=15)#使用程序配置了 cs0 则无法读取 W25QXX
    print(os.listdir())
    print(spi)
    
    while True:
        fm.register(21, fm.fpioa.SPI1_SCLK, force=True)
        fm.register(8, fm.fpioa.SPI1_D0, force=True)
        fm.register(15, fm.fpioa.SPI1_D1, force=True)
        cs.value(0)
        write_data = bytearray([0x90, 0x00, 0x00, 0x00])
        spi.write(write_data)
        id_buf = bytearray(2)
        spi.readinto(id_buf, write=0xff)
        print(id_buf)
        print(time.ticks_ms())
        cs.value(1)
        #cs.value(0)
        utime.sleep_ms(200)
        #utime.sleep_ms(2200)
        fm.register(27, fm.fpioa.SPI1_SCLK, force=True)
        fm.register(28, fm.fpioa.SPI1_D0, force=True)
        fm.register(26, fm.fpioa.SPI1_D1, force=True)
        print(os.listdir())
    
    spi.deinit()
    

    可以看到,想要使用 SPI1 就必须在使用后归还 IO 因为它们不在同一个 IO 上,如果使用软 SPI 则可以极大的改善这个使用的问题。

    可以如何实现 MicroPython 中的软 SPI ?

    要知道 MicroPython 本来就是为 MCU 跨平台准备了很多软件逻辑的代码,如软 SPI 的实现在 https://github.com/micropython/micropython/blob/master/extmod/machine_spi.c 中,我们要做的就是将硬件的具体功能实现对接起来即可。

    在这里我们得知需要的 mp_soft_spi_transfer 关键函数在这里 https://github.com/micropython/micropython/blob/master/drivers/bus/softspi.c 此时我们就拥有了整个软 SPI 需要的核心收发逻辑。

    
    #include "drivers/bus/spi.h"
    
    int mp_soft_spi_ioctl(void *self_in, uint32_t cmd) {
        mp_soft_spi_obj_t *self = (mp_soft_spi_obj_t*)self_in;
    
        switch (cmd) {
            case MP_SPI_IOCTL_INIT:
                mp_hal_pin_write(self->sck, self->polarity);
                mp_hal_pin_output(self->sck);
                mp_hal_pin_output(self->mosi);
                mp_hal_pin_input(self->miso);
                break;
    
            case MP_SPI_IOCTL_DEINIT:
                break;
        }
    
        return 0;
    }
    
    void mp_soft_spi_transfer(void *self_in, size_t len, const uint8_t *src, uint8_t *dest) {
        mp_soft_spi_obj_t *self = (mp_soft_spi_obj_t*)self_in;
        uint32_t delay_half = self->delay_half;
    
        // only MSB transfer is implemented
    
        // If a port defines MICROPY_HW_SOFTSPI_MIN_DELAY, and the configured
        // delay_half is equal to this value, then the software SPI implementation
        // will run as fast as possible, limited only by CPU speed and GPIO time.
        #ifdef MICROPY_HW_SOFTSPI_MIN_DELAY
        if (delay_half == MICROPY_HW_SOFTSPI_MIN_DELAY) {
            for (size_t i = 0; i < len; ++i) {
                uint8_t data_out = src[i];
                uint8_t data_in = 0;
                for (int j = 0; j < 8; ++j, data_out <<= 1) {
                    mp_hal_pin_write(self->mosi, (data_out >> 7) & 1);
                    mp_hal_pin_write(self->sck, 1 - self->polarity);
                    data_in = (data_in << 1) | mp_hal_pin_read(self->miso);
                    mp_hal_pin_write(self->sck, self->polarity);
                }
                if (dest != NULL) {
                    dest[i] = data_in;
                }
            }
            return;
        }
        #endif
    
        for (size_t i = 0; i < len; ++i) {
            uint8_t data_out = src[i];
            uint8_t data_in = 0;
            for (int j = 0; j < 8; ++j, data_out <<= 1) {
                mp_hal_pin_write(self->mosi, (data_out >> 7) & 1);
                if (self->phase == 0) {
                    mp_hal_delay_us_fast(delay_half);
                    mp_hal_pin_write(self->sck, 1 - self->polarity);
                } else {
                    mp_hal_pin_write(self->sck, 1 - self->polarity);
                    mp_hal_delay_us_fast(delay_half);
                }
                data_in = (data_in << 1) | mp_hal_pin_read(self->miso);
                if (self->phase == 0) {
                    mp_hal_delay_us_fast(delay_half);
                    mp_hal_pin_write(self->sck, self->polarity);
                } else {
                    mp_hal_pin_write(self->sck, self->polarity);
                    mp_hal_delay_us_fast(delay_half);
                }
            }
            if (dest != NULL) {
                dest[i] = data_in;
            }
        }
    }
    
    const mp_spi_proto_t mp_soft_spi_proto = {
        .ioctl = mp_soft_spi_ioctl,
        .transfer = mp_soft_spi_transfer,
    };
    

    那么要完成的就是硬件 GPIO 模拟 SPI 的操作,有如下需要。

    • mp_hal_delay_us_fast
    • mp_hal_pin_write
    • mp_hal_pin_output
    • mp_hal_pin_input
    • mp_hal_pin_read
    mp_hal_pin_write(self->sck, self->polarity);
    mp_hal_pin_output(self->sck);
    mp_hal_pin_output(self->mosi);
    mp_hal_pin_input(self->miso);
    

    从初始化中可以得知这是标准的 主从 SPI 通信,也就是 sck/mosi/miso/cs ,大部分传感器都支持这种,如果需要 qspi 则在同目录下 https://github.com/micropython/micropython/blob/master/drivers/bus/qspi.c 获得。

    
    typedef struct _mp_soft_qspi_obj_t {
        mp_hal_pin_obj_t cs;
        mp_hal_pin_obj_t clk;
        mp_hal_pin_obj_t io0;
        mp_hal_pin_obj_t io1;
        mp_hal_pin_obj_t io2;
        mp_hal_pin_obj_t io3;
    } mp_soft_qspi_obj_t;
    
    

    可以看到存在 4 line 的数据 IO (D[0:3]) 这种定义,但我们目前不需要去实现它,我们就以常见的 motorola spi 为例吧。

    实现流程参考

    先对接实现 K210 的 GPIO 功能出来,参考在此 https://github.com/sipeed/MaixPy/blob/f9bb0bb5e807846e6b6e397b9ced2963c5472fab/components/micropython/port/src/standard_lib/machine/machine_spi.c#L114-L204

    
    #if MICROPY_PY_MACHINE_SW_SPI
    
    #include "gpiohs.h"
    
    #define mp_hal_delay_us_fast(s) mp_hal_delay_us(s)
    
    #define mp_spi_pin_output(pin) gpiohs_set_drive_mode(pin, GPIO_DM_OUTPUT)
    
    #define mp_spi_pin_input(pin) gpiohs_set_drive_mode(pin, GPIO_DM_INPUT)
    
    #define MICROPY_HW_SOFTSPI_MIN_DELAY 0
    
    #define SPI_SOFTWARE SPI_DEVICE_MAX
    
    STATIC void mp_spi_pin_write(uint8_t pin, uint8_t val)
    {
        gpiohs_set_pin(pin, val);
        mp_spi_pin_output(pin);
        mp_spi_pin_input(pin);
    }
    
    STATIC int mp_spi_pin_read(uint8_t pin)
    {
        mp_spi_pin_input(pin);
        return gpiohs_get_pin(pin);
    }
    
    void mp_soft_spi_transfer(void *self_in, size_t len, const uint8_t *src, uint8_t *dest) {
        machine_hw_spi_obj_t *self = (machine_hw_spi_obj_t*)self_in;
        uint32_t delay_half = self->delay_half;
    
        // printk("%s %d %d %d
    ", __func__, self->pin_sck, self->pin_d[0], self->pin_d[1]);
    
        // only MSB transfer is implemented
    
        // If a port defines MICROPY_HW_SOFTSPI_MIN_DELAY, and the configured
        // delay_half is equal to this value, then the software SPI implementation
        // will run as fast as possible, limited only by CPU speed and GPIO time.
        // #ifdef MICROPY_HW_SOFTSPI_MIN_DELAY
        if (delay_half == MICROPY_HW_SOFTSPI_MIN_DELAY) {
            for (size_t i = 0; i < len; ++i) {
                uint8_t data_out = src[i];
                uint8_t data_in = 0;
                for (int j = 0; j < 8; ++j, data_out <<= 1) {
                    mp_spi_pin_write(self->pin_d[0], (data_out >> 7) & 1);
                    mp_spi_pin_write(self->pin_sck, 1 - self->polarity);
                    if (self->pin_d[1] != -1)
                    {
                        data_in = (data_in << 1) | mp_spi_pin_read(self->pin_d[1]);
                    }
                    mp_spi_pin_write(self->pin_sck, self->polarity);
                }
                if (dest != NULL) {
                    dest[i] = data_in;
                }
            }
            return;
        }
        // #endif
    
        for (size_t i = 0; i < len; ++i) {
            uint8_t data_out = src[i];
            uint8_t data_in = 0;
            for (int j = 0; j < 8; ++j, data_out <<= 1) {
                mp_spi_pin_write(self->pin_d[0], (data_out >> 7) & 1);
                if (self->phase == 0) {
                    mp_hal_delay_us_fast(delay_half);
                    mp_spi_pin_write(self->pin_sck, 1 - self->polarity);
                } else {
                    mp_spi_pin_write(self->pin_sck, 1 - self->polarity);
                    mp_hal_delay_us_fast(delay_half);
                }
                if (self->pin_d[1] != -1)
                {
                    data_in = (data_in << 1) | mp_spi_pin_read(self->pin_d[1]);
                }
                if (self->phase == 0) {
                    mp_hal_delay_us_fast(delay_half);
                    mp_spi_pin_write(self->pin_sck, self->polarity);
                } else {
                    mp_spi_pin_write(self->pin_sck, self->polarity);
                    mp_hal_delay_us_fast(delay_half);
                }
            }
            if (dest != NULL) {
                dest[i] = data_in;
            }
        }
    }
    
    #endif
    

    这样就将收发的逻辑实现起来了,如果你有前一篇的基础,那么这个就是自然而然的事情,非常容易,关于 K210 的 GPIO 的工作差异,在这里就不再重新赘述。

    接回 MaixPy 的 SPI 模块

    要将其统一到 Python 代码中,我额外添加了 SPI_SOFTWARE 使得不再区分 SPI 编号,从而使得 SPI 设备自由定义。

    其中关键的 machine_xxx_spi_transfer 函数整合如下:

    https://github.com/sipeed/MaixPy/blob/f9bb0bb5e807846e6b6e397b9ced2963c5472fab/components/micropython/port/src/standard_lib/machine/machine_spi.c#L221-L239

    STATIC void machine_hw_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8_t *src, uint8_t *dest, int cs) {
        machine_hw_spi_obj_t *self = MP_OBJ_TO_PTR(self_in);
    
        if (self->state == MACHINE_HW_SPI_STATE_DEINIT) {
            mp_raise_msg(&mp_type_OSError, "[MAIXPY]SPI: transfer on deinitialized SPI");
            return;
        }
    #if MICROPY_PY_MACHINE_SW_SPI
        if(self->id == SPI_SOFTWARE)
        {
            mp_soft_spi_transfer(self, len, src, dest);
            return;
        }
    #endif
        if(dest==NULL)
            sipeed_spi_transfer_data_standard(self->id, cs, src, NULL, len, 0);
        else
            sipeed_spi_transfer_data_standard(self->id, cs, src, dest, len, len);
    }
    

    这代码很简单,没有太大的问题,再来就是补充初始化时的设备选择。
    https://github.com/sipeed/MaixPy/blob/f9bb0bb5e807846e6b6e397b9ced2963c5472fab/components/micropython/port/src/standard_lib/machine/machine_spi.c#L423-L441

    #if MICROPY_PY_MACHINE_SW_SPI
        if(self->id == SPI_SOFTWARE)// standard soft-spi mode
        {
            if (self->baudrate > 1000*1000) {
                self->delay_half = 0;
            }
    
            fpioa_set_function(self->pin_sck, FUNC_GPIOHS0 + self->pin_sck);
            fpioa_set_function(self->pin_d[0], FUNC_GPIOHS0 + self->pin_d[0]);
            mp_spi_pin_write(self->pin_sck, self->polarity);
            mp_spi_pin_output(self->pin_sck);
            mp_spi_pin_output(self->pin_d[0]);
            if (self->pin_d[1] != -1)
            {
                fpioa_set_function(self->pin_d[1], FUNC_GPIOHS0 + self->pin_d[1]);
                mp_spi_pin_input(self->pin_d[1]);
            }
        } else 
    #endif
    

    最后 Python 代码只需要改变 SPI0 为 SPI.SPI_SOFT 即可。

    from machine import SPI
    
    spi1 = SPI(SPI.SPI_SOFT, mode=SPI.MODE_MASTER, baudrate=10000000, polarity=0, phase=0, bits=8, firstbit=SPI.MSB, sck=28, mosi=29, miso=30, cs0=27)
    w = b'1234'
    r = bytearray(4)
    spi1.write(w)
    spi1.write(w)
    spi1.write_readinto(w, r)
    spi1.read(5, write=0x00)
    spi1.readinto(r, write=0x00)
    

    最后的总结

    可喜可贺,我终于可以有足够的 SPI 模块使用了,但也存在一些不完美,例如没有实现软 QSPI 也没有实现软从机,总得来说,还是有很多可以实现的地方。

    但那就留给有需要的时候再实现吧,因为我也只是刚好有需要才实现的。= -=~

    本来想写一下实现过程中遇到的一些问题的,但好像也没啥问题,主要也就是和 I2C 实现的时候对 GPIO 注意点使用就好了,我量测的 K210 的 GPIO 软实现的功能都时钟频率大概就在 50khz 附近。

    如果没有看过一篇软 I2C 的话,这篇省略了很多内容,不是很好理解的,我自己看完的体会就是这样的。
    2020年10月1日 junhuanchen

  • 相关阅读:
    系统角色权限问题
    解析JQuery Ajax
    JavaScriptSerializer序列化时间处理
    Javascript加载talbe(包含分页、数据下载功能)
    代理模式
    工厂模式
    单例模式
    Oracle内置函数
    Oracle大数据SQL语句优化
    Oracle大数据查询优化
  • 原文地址:https://www.cnblogs.com/juwan/p/13758779.html
Copyright © 2011-2022 走看看