本文主要是介绍在mm32f3270上为micropython创建SPI模块,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
在mm32f3270上为micropython创建SPI模块
文章目录
- 在mm32f3270上为micropython创建SPI模块
- Introduction
- Architecture
- extmod/machine_spi.c
- 软件SoftSPI
- 硬件SPI
- ports/xx/machine_spi.c
Introduction
在machine中实现SPI,我看各家的代码都引用了extmod/machine_spi.c中的模型,看来我也跑不掉,那就不要自创武功,随大流就好。
大体看了一下代码,预计工作量会在三个方面:
- 了解extmod/machine_spi.c中对SPI的定义(SPI和SoftSPI)
- 搞清楚需要port/mm32/machine_spi.c中实现的部分,做好适配
- 处理好两个machine_spi.c,以及同平台的依赖关系,似乎要做一个hal_pin的适配
Architecture
在这个machine_spi.c中,很明确地要求在工程中启用宏"MICROPY_PY_MACHINE_SPI"。
OK,我也在mm32的mpconfigport.h中增加这个宏定义。
首先,我要把已经验证好的hal_spi.h和hal_spi.c文件放到mm32的驱动代码目录下面,在mm32的ports目录下创建machine_spi.h和machine_spi.c文件,可以预期不会再加更多的源文件了,只是在现有文件结构下改代码。
在具体代码的实现上,我将参考我已实现的machine_uart为参考,处理绑定引脚、多实例的具体问题。
然后在makefile增加新文件。
注意,SRC_C中的 $(SRC_MOD),不是在ports目录下的makefile中定义的,而是在extmod.mk文件中,然而,此处并没有包含extmod/machine_spi.c,所以仍需要手工引入。但阅读了别的包含machine_spi的makefile缺没却没有显示引用extmod的这个文件。算了,启动一次编译,看build log里有没有处理这个文件。
果然,build默认会把extmod全都过一遍,在启用MICROPY_PY_MACHINE_SPI的时候,extmod/machine_spi.c的代码正式起作用,送入编译器开编。
后续我根据报错的需求,不断添加代码,尽量确保编译通过。可以想见,再次全部编译通过的时候,也即将是移植完成。
extmod/machine_spi.c
之前的试编译中报错,提示在“mp_machine_soft_spi_print”函数中,有个“MP_HAL_PIN_FMT”没有定义。这里就要说道一下了。
extmod/machine_spi.c中有两部分:硬件SPI和软件SoftSPI。虽然我们希望使用硬件SPI,但因为micropython将这俩货放在一起,我们就仍要顺带完成SoftSPI的移植。
在stm32的modmachine.c中对machine模块内部的类定义时,有如下代码:
#if MICROPY_PY_MACHINE_SPI{ MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) },{ MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) },#endif
也就是ports目录下的machine_spi.c实现的machine_hard_spi_type,将会被当做SPI注册到系统中,而extmod/machine_spi.c中实现的mp_machine_soft_spi_type将使用SoftSPI这个名字。
从文章的顺序上,这里继读extmod/machine_spi.c的内容,但如果是调代码,这里就已经要在ports/xx/machine_spi.c中写一些代码框架了。
extmod/machine_spi.c的代码中通过代码注解描述了两个部分:
...
/******************************************************************************/
// MicroPython bindings for generic machine.SPI
.../******************************************************************************/
// Implementation of soft SPI
...
我曾经有念头,想先把SoftSPI的部分注释掉,仅实现纯硬件SPI的部分。。。算了,估计后面SoftI2C可能也要用,跑得了和尚跑不了庙,一起看吧。
STATIC mp_obj_t machine_spi_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {mp_obj_base_t *s = (mp_obj_base_t *)MP_OBJ_TO_PTR(args[0]);mp_machine_spi_p_t *spi_p = (mp_machine_spi_p_t *)s->type->protocol;spi_p->init(s, n_args - 1, args + 1, kw_args);return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_spi_init_obj, 1, machine_spi_init);
关于硬件SPI,最基本的是machine_spi_init()、machine_spi_deinit()和machine_spi_transfer()三个函数,这三个函数的实现都是调用s->type->protocol中注册的一堆函数。(在后面阅读代码时可以看到,这三个函数的底层都是在ports/xx/machine_spi.c中实现的,然后包装起来封装到protocol中装入mp_machine_spi_p_t类型的结构体中,然后再由ports/xx/machine_spi.c中引用组装成类模块。本文件中的spi_read()、spi_readinto()、spi_write()、spi_write_readinto()都是基于spi_transfer()定义的。
这里相当于是实现了SPI的一组通用操作的函数,但向上和向下约定了SPI的实现接口。
硬件SPI对底层的要求比较简单,软件SoftSPI相当于是对Pin模型的一个封装,稍微有点麻烦。但其实因为跟此处主题关联不大,所以在时间紧急的情况下,看似无关的工作量容易让人烦躁。
软件SoftSPI
软件SoftSPI直接操作GPIO引脚实现SPI通信。
baudrate_from_delay_half()函数,以500k作为目标频率,将传入的delay_half转换成delay的重复次数。
baudrate_to_delay_half()函数,以500k作为目标频率,将传入的baudrate转换成delay_half的时长。
这两个函数在搞什么鬼?
在mp_machine_soft_spi_print()函数中,mp_machine_soft_spi_obj_t是在extmod/machine_spi.h中定义的,可以不用管。然后就是“MP_HAL_PIN_FMT”,这是之前试编译出现的问题点,看一下别家的代码怎么实现的。
mimxrt在自己的mphalport.h中,包含了所有关于mp_hal_pin的定义,我也拿过来参考一下。刚好mm32的port中也有“machine_pin_obj_t”的定义,相似度最近。注意,随手要加上对machine_pin.h的包含,否则会报错找不到machine_pin_obj_t的定义。
仅仅加了四行代码就能编译通过了,WF。
#define MP_HAL_PIN_FMT "%q"
#define mp_hal_pin_obj_t const machine_pin_obj_t *
#define mp_hal_get_pin_obj(o) pin_find(o)
#define mp_hal_pin_name(p) ((p)->name)
mp_machine_soft_spi_make_new()函数内部调用的mp_hal_get_pin_obj()实际调用的是pin_find() 函数,而我在mm32上实现的pin_find()函数是兼容mimxrt的,所以可以无缝对接。兼容真香啊。
但这里仅仅是做了个接口定义,对引脚翻转的操作都放在了drivers/bus/softspi.c中,这里的mp_hal_pin_write()、mp_hal_pin_output()、mp_hal_delay_us_fast()等函数我在mm32中都没实现,怎么就编通了?
在py/mphal.h中有这么一段代码解释了我的疑问:
// If port HAL didn't define its own pin API, use generic
// "virtual pin" API from the core.
#ifndef mp_hal_pin_obj_t
#define mp_hal_pin_obj_t mp_obj_t
#define mp_hal_get_pin_obj(pin) (pin)
#define mp_hal_pin_read(pin) mp_virtual_pin_read(pin)
#define mp_hal_pin_write(pin, v) mp_virtual_pin_write(pin, v)
#include "extmod/virtpin.h"
#endif
但我已经定义了mp_hal_pin_obj_t了!!!可能之前编译好的模块乱入了,我要试着重新编译一下。还有mp_hal_pin_output还是要实现一下的。
重新编译竟然还可以通过???我在map文件中也没找到有用的信息。
试着把image下载到板子上运行一下,竟然也没有hardfault。怪哉。
好吧,之前一直担心软件SoftSPI乱入,至少现在暂时不用考虑SoftSPI,可以将注意力集中在硬件SPI。
硬件SPI
忍不了了,终于可以畅快地在ports/xx/machine_spi.c 中写代码了。
ports/xx/machine_spi.c
先参考esp8266的machine_hspi.c实现一个代码框架。设法打通调用路径再插代码。
呵呵,我在esp8288的实现中发现了一个小秘密:
if (dest == NULL) {// fast case when we only need to write datasize_t chunk_size = 1024;size_t count = len / chunk_size;size_t i = 0;for (size_t j = 0; j < count; ++j) {for (size_t k = 0; k < chunk_size; ++k) {spi_tx8fast(HSPI, src[i]);++i;}ets_loop_iter();}while (i < len) {spi_tx8fast(HSPI, src[i]);++i;}// wait for SPI transaction to completewhile (spi_busy(HSPI)) {}}
这里竟然可以用轮询模式啊。。。
我考虑的是,有一种情况,SPI作为单工模式工作时,类似于UART串口的只发不收和只收不发,软件只要把数塞给缓冲区就好了,由硬件自己搞。但再想想,SPI的接收也是由发送驱动的,全部是主动控制,用轮询问题不大。实际上UART的发送就是轮询实现的。如果是这样,后面的I2C也可以考虑类似的实现。
这里,我先在machine_spi.h中定义了一个machine_hw_spi_obj_t结构体。。。对应的引脚映射后面再补。。。相对于extmod/machine_spi.c,这里实现的spi都是“hw_spi”
…
一顿操作猛如虎。
…
编译一下,终于还是报错了。
LINK build-MB_F3270/firmware.elf
C:\msys64\usr\gcc-arm-none-eabi-10-2020-q4-major\bin\arm-none-eabi-ld.exe: build-MB_F3270/extmod/machine_spi.o: in function `mp_machine_soft_spi_transfer':
machine_spi.c:(.text.mp_machine_soft_spi_transfer+0x2): undefined reference to `mp_soft_spi_transfer'
C:\msys64\usr\gcc-arm-none-eabi-10-2020-q4-major\bin\arm-none-eabi-ld.exe: build-MB_F3270/extmod/machine_spi.o: in function `mp_machine_soft_spi_init':
machine_spi.c:(.text.mp_machine_soft_spi_init+0x62): undefined reference to `mp_soft_spi_ioctl'
C:\msys64\usr\gcc-arm-none-eabi-10-2020-q4-major\bin\arm-none-eabi-ld.exe: build-MB_F3270/extmod/machine_spi.o: in function `mp_machine_soft_spi_make_new':
machine_spi.c:(.text.mp_machine_soft_spi_make_new+0x7c): undefined reference to `mp_soft_spi_ioctl'
make: *** [Makefile:200: build-MB_F3270/firmware.elf] Error 1
mp_soft_spi_transfer()等三个函数找不到?估计没包进来。
追了一下代码,这几个函数是在drivers/bus/softspi.c中定义的,还是需要在makefile中把这个文件加进来。但这个文件中引用了了mp_pin_hal_xxxx的一堆宏函数。
算了,洗洗睡了,明天再搞。
先做一点体力活,把F3270的SPI引脚清单做出来,逐步进入状态。
查阅pin_info.xlsx文件,在mm32/boards/mb_f3270/machine_pin_board_pins.c中添加关于SPI的记录。搞定。
在stm32的makefile中,查找“softspi”关键字,找到了“driver_src_c”的定义,看来需要显式在makefile 增加drivers 目录下的文件。
DRIVERS_SRC_C += $(addprefix drivers/,\bus/softspi.c \)SRC_C += \main.c \...$(DRIVERS_SRC_C) \
再编一次。果然开始报错说找不到mp_hal_pin_xxx的一堆函数了。
参照mimxrt在mphalport.h的实现,也适配一下。
这里看到还有mp_hal_pin_open_drain()的实现,暂时不用管,可能是给后面SoftI2C用的。
#define mp_hal_pin_write(p, value) (GPIO_WriteBit(p->gpio_port, (1u << p->gpio_pin), value))
#define mp_hal_pin_read(p) (GPIO_ReadInputDataBit(p->gpio_port, (1u << p->gpio_pin)))
#define mp_hal_pin_output(p) machine_pin_set_mode(p, PIN_MODE_OUT_PUSHPULL);
#define mp_hal_pin_input(p) machine_pin_set_mode(p, PIN_MODE_IN_PULLUP);...#define mp_hal_delay_us_fast(us) mp_hal_delay_us(us)
在mm32/mphalport.h文件中添加了一些代码,同时在machine_pin.c文件中添加了machine_pin_set_mode()函数的实现。
再编一次。编通了。
顺便启用了SoftSPI。重新编译,现在到板子里试一下。
可以导入SPI模块,并且能dir到内部属性,但实例化的语句会触发hardfault。
初步分析是machine_hw_spi_make_new()函数中的动态内存分配代码导致的hardfault。
machine_hw_spi_obj_t *self = m_new_obj(machine_hw_spi_obj_t);self->base.type = &machine_hw_spi_type;
这个m_new_obj()函数最终调用了m_malloc()函数。。。关于动态内存分配的事情,这里暂时先不考虑。
按照以往的做法,用hw_spi_find()函数取代这里的new函数。
const machine_hw_spi_obj_t *hw_spi_find(mp_obj_t user_obj)
{/* 如果传入参数本身就是一个uart的实例,则直接送出这个UART。 */if ( mp_obj_is_type(user_obj, &machine_hw_spi_type) ){return user_obj;}/* 如果传入参数是一个uart通道号,则通过索引在UART清单中找到这个通道,然后送出这个通道。 */if ( mp_obj_is_small_int(user_obj) ){uint8_t idx = MP_OBJ_SMALL_INT_VALUE(user_obj);if ( idx < MACHINE_HW_SPI_NUM ){return machine_hw_spi_objs[idx];}}mp_raise_ValueError(MP_ERROR_TEXT("HW SPI doesn't exist"));
}
然后创建了一些函数用以完成硬件SPI的适配。。。到凌晨了,刚准备好代码,勉强调通了,还没测。不想写笔记了,洗洗先睡,明天再搞。
昨天午夜完成了一个版本,今天到办公室用逻辑分析仪测了一下,修复了micropython中SPI的相位设置同mm32底层驱动设置不一致的问题。
目前可以使用SPI模块的write()和read()方法进行读写。但是readinto()和write_readinto()函数在工作时仍然会抛异常,说是需要提供带buffer的协议。
>>> arr2=[]
>>> spi0.write_readinto(arr, arr2)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: object with buffer protocol required
我估计还是在移植micropython内核时没处理好动态内存分配malloc的问题。抽空还是要专门解决掉。
END
这篇关于在mm32f3270上为micropython创建SPI模块的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!