【免费下载链接】TobudOS
TobudOS 是面向物联网领域开发的实时操作系统,早期版本基于腾讯自研的物联网操作系统TencentOS Tiny,2020年由腾讯捐赠到开放原子开源基金会进行孵化,2023年正式更名为TobudOS,TobudOS具有低功耗,低资源占用,模块化,安全可靠等特点,可有效提升物联网终端产品开发效率,提供精简的 RTOS 内核,内核组件可裁剪可配置,可快速移植到多种主流 MCU (如 STM32 全系列) 及模组芯片上。而且,基于 RTOS 内核提供了丰富的物联网组件,内部集成主流物联网协议栈(如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等),可助力物联网终端设备及业务快速接入物联网云平台。
导读
本文以 pyb.SPI 模块文档 为核心,系统讲解在 TobudOS 物联网实时操作系统上使用 MicroPython 驱动 SPI 总线的完整方法:如何构造 SPI 对象、如何通过init()配置时钟速率、极性、相位、位宽、字节序与 CRC,如何用send/recv/send_recv完成单工与全双工数据传输,并深入 TobudOS 移植层源码,揭示machine.SPI与内核 HAL 之间的底层调用关系。读完本文,你将能够在 TobudOS 的 MicroPython 环境中独立编写驱动 SPI 外设(如 Flash、LCD、传感器)的脚本,并理解从 Python API 到硬件寄存器的完整数据通路。
SPI 协议与 pyb.SPI 类概览
SPI(Serial Peripheral Interface)是一种由控制器(Controller)驱动的串行通信协议。在物理层面,它至少需要 3 根信号线:
- SCK:串行时钟线,由控制器产生;
- MOSI:控制器输出、外设输入的数据线;
- MISO:外设输出、控制器输入的数据线。
与 I2C 类似,SPI 也是设备间通信的总线协议,二者的使用模型非常接近——先创建总线对象,再初始化,然后收发数据。主要区别在于初始化参数:SPI 需要配置模式、时钟速率、极性与相位等参数,而这些参数直接决定了时序协议是否与外设匹配。
在 TobudOS 的 MicroPython 移植中,SPI 硬件类以machine.SPI形式注册(见 modmachine.c 中的MP_QSTR_SPI),其 API 与 pyb.SPI 文档描述保持同源。文档给出了最简洁的初始化示例:
from pyb import SPI spi = SPI(1, SPI.CONTROLLER, baudrate=600000, polarity=1, phase=0, crc=0x7)其中唯一必需的位置参数是mode,即SPI.CONTROLLER(控制器)或SPI.PERIPHERAL(外设);其余参数均可选,并在创建对象时完成总线初始化。
构造 SPI 对象:总线编号与物理引脚
通过pyb.SPI(bus, ...)可在指定总线上构造 SPI 对象。bus可以是整数1或2,也可以是位置别名'X'或'Y':
- 不带额外参数时,对象仅被创建而不执行初始化——它保留该总线上一次初始化(如果存在)的设置;
- 带额外参数时,创建的同时会调用
init()完成初始化,参数含义与init()完全一致。
以 Pyboard 为例,两条 SPI 总线的物理引脚映射如下:
| 总线 | 位置 | NSS | SCK | MISO | MOSI | 对应 STM32 引脚 |
|---|---|---|---|---|---|---|
SPI(1) | X | X5 | X6 | X7 | X8 | PA4 / PA5 / PA6 / PA7 |
SPI(2) | Y | Y5 | Y6 | Y7 | Y8 | PB12 / PB13 / PB14 / PB15 |
文档特别说明:当前 SPI 驱动并未使用 NSS 引脚,因此该引脚空闲,可留作其他用途(例如用普通 GPIO 手动控制片选)。这一设计在驱动外设时非常实用——你可以自行决定片选信号的拉低/拉高时机。
在 TobudOS 的板级移植中,SPI 总线的数量由板级配置文件决定。例如 BearPi 开发板的 mpconfigboard.h 中定义了MICROPY_HW_SPI_NUM 4,表示该板可用的 SPI 硬件实例数为 4;对应地,machine_hw_spi.c 中通过machine_hard_spi_obj_all[MICROPY_HW_SPI_NUM]静态分配对象池。因此在不同开发板上,可用的总线编号以该板的mpconfigboard.h为准。
init():SPI 总线初始化参数详解
init()是 SPI 配置的核心入口,完整签名如下:
SPI.init(mode, baudrate=328125, *, prescaler=-1, polarity=1, phase=0, bits=8, firstbit=SPI.MSB, ti=False, crc=None)各参数含义与取值范围如下表:
| 参数 | 取值 | 说明 |
|---|---|---|
mode | SPI.CONTROLLER/SPI.PERIPHERAL | 总线工作模式,必选参数 |
baudrate | 整数 | SCK 时钟频率(仅对控制器模式有意义),默认 328125 |
prescaler | -1 或 2/4/8/16/32/64/128/256 | 由 APB 总线频率推导 SCK 的分频系数;一旦指定,将覆盖baudrate |
polarity | 0 或 1 | 空闲时钟线的电平:0 为低电平空闲,1 为高电平空闲 |
phase | 0 或 1 | 数据采样边沿:0 在第一个时钟沿采样,1 在第二个时钟沿采样 |
bits | 8 或 16 | 每个传输字的位数 |
firstbit | SPI.MSB/SPI.LSB | 先发送最高有效位还是最低有效位 |
ti | True/False | 为True时使用 TI(德州仪器)信号约定,否则使用 Motorola 约定 |
crc | None或多项式 | None表示不启用 CRC,否则传入多项式指定符 |
其中polarity与phase的组合(即 SPI 模式 0~3)决定了控制器与外设之间的时序握手方式,是驱动能否正常通信的关键。crc用于需要校验的链路,普通场景传None即可。
关于时钟频率的重要说明
文档强调:SPI 实际时钟频率不一定会等于请求的baudrate。原因在于硬件只支持"APB 总线频率除以分频系数"的离散频率集合,可用的分频系数为 2、4、8、16、32、64、128、256。在 Pyboard 上,SPI(1)挂载于 AHB2,SPI(2)挂载于 AHB1(两者 APB 时钟源不同,可参考pyb.freq查询)。
因此,当需要对 SPI 时钟频率进行精确控制时,应直接指定prescaler而不是baudrate。打印 SPI 对象时,会显示计算得到的实际波特率与选中的分频系数,可用于核对配置是否符合预期:
>>> print(spi) SPI(1, SPI.CONTROLLER, baudrate=600000, polarity=1, phase=0, bits=8)数据收发:send、recv 与 send_recv
SPI 是同步全双工协议,时钟由控制器产生,因此收发天然可以同时进行。pyb.SPI 提供三个基本方法:
SPI.send(send, *, timeout=5000)
向总线发送数据。send可以是单个整数(发送 1 个字节)或缓冲区对象(如bytes、bytearray)。timeout为等待发送完成的超时时间(毫秒)。返回值恒为None:
spi.send(0xA5) # 发送单个字节 spi.send(b'\x01\x02\x03') # 发送 3 个字节SPI.recv(recv, *, timeout=5000)
从总线接收数据。recv可以是整数(表示要接收的字节数)或可变缓冲区(接收到的字节将写入其中)。返回规则:
- 若
recv是整数,则返回一个包含所收字节的新缓冲区; - 若
recv是缓冲区,则返回传入的同一个缓冲区(原地填充)。
data = spi.recv(4) # 接收 4 字节,返回新 bytes 对象 buf = bytearray(4) spi.recv(buf) # 接收 4 字节写入 buf,返回 bufSPI.send_recv(send, recv=None, *, timeout=5000)
同时发送与接收,这是 SPI 全双工能力的直接体现。send为待发送数据(整数或缓冲区);recv为接收缓冲区,可以省略(此时自动新建缓冲区),也可以与send是同一个缓冲区(原地收发)。返回值是包含接收字节的缓冲区:
data = spi.send_recv(b'1234') # 发送 4 字节并接收 4 字节 buf = bytearray(4) spi.send_recv(b'1234', buf) # 发送 4 字节,接收结果写入 buf spi.send_recv(buf, buf) # 以 buf 为源同时收发 4 字节这种"发送即接收"的写法在读写 Flash、LCD 等外设时非常常见:向从设备写入命令字节的同时,读回状态或数据。
常量:模式与位序
pyb.SPI 提供四个类常量:
| 常量 | 用途 |
|---|---|
SPI.CONTROLLER | 初始化总线为控制器模式 |
SPI.PERIPHERAL | 初始化总线为外设模式 |
SPI.MSB | 先传输最高有效位(默认) |
SPI.LSB | 先传输最低有效位 |
mode与firstbit参数直接引用这些常量,避免手写魔法数字。
总线关闭与对象生命周期
SPI.deinit()用于关闭 SPI 总线,释放硬件资源。在 TobudOS 移植实现中,deinit对应调用tos_hal_spi_deinit并复位初始化标记(见 machine_hw_spi.c);而init()在已初始化状态下重复调用会抛出OSError("SPI(%u) has been initialized"),tos_hal_spi_init返回非 0 时则抛出ValueError("SPI(%u) doesn't exist")——这解释了 测试用例 中创建非法总线编号(如 -1、0)会捕获ValueError的原因。
底层实现:从 machine.SPI 到 TobudOS HAL
pyb.SPI 文档描述的是类 API 契约,而 TobudOS 仓库中的实际移植位于 machine_hw_spi.c,它揭示了一条清晰的调用链:
- 使能开关:该文件整体受
#if MICROPY_PY_MACHINE_SPI保护(第 28 行)。在 mpconfigport.h 中,MP_USING_MACHINE_SPI宏会展开为MICROPY_PY_MACHINE_SPI (1)与MICROPY_PY_MACHINE_SOFTSPI (1),即硬件 SPI 与软件 SPI 同时启用。 - 对象模型:
machine_hard_spi_obj_t内嵌了 HAL 层句柄hal_spi_t spi与端口号hal_spi_port_t port(第 41-48 行),并将所有 SPI 实例静态分配到machine_hard_spi_obj_all[MICROPY_HW_SPI_NUM]对象池中。 - 初始化:
init()内部调用tos_hal_spi_init(&self->spi, self->port),把 Python 层的总线编号直接映射为 HAL 端口(第 108 行)。 - 数据传输:核心的
machine_hard_spi_transfer调用tos_hal_spi_transfer(&self->spi, src, dest, len, timeout)完成一次全双工传输,超时按timeout + len * timeout_char随长度动态计算(第 134-137 行)。 - 模块注册:
machine_hard_spi_type以MP_QSTR_SPI名字挂入 machine 模块全局表(modmachine.c),并通过mp_machine_spi_locals_dict复用标准 MicroPython SPI 方法表(定义于 extmod/machine_spi.c)。
也就是说,pyb.SPI文档中的init / deinit / send / recv / send_recv语义,在 TobudOS 移植中由machine.SPI通过tos_hal_spi_*系列接口落到各芯片厂商驱动(如 STM32 HAL、GD32 标准库等)之上,最终驱动硬件寄存器。
测试用例:API 行为验证
仓库自带的测试 tests/pyb/spi.py 覆盖了 SPI 对象的主要行为,可当作入门参考:
- 按编号创建与非法编号校验:对 -1、0、1、2 逐个尝试
SPI(bus),非法的抛出ValueError; - 多种初始化方式:
SPI(1, SPI.CONTROLLER)、带baudrate的初始化、以及全参数形式SPI(1, SPI.CONTROLLER, 500000, polarity=1, phase=0, bits=8, firstbit=SPI.MSB, ti=False, crc=None); - 模式切换:
spi.init(SPI.PERIPHERAL, phase=1)后打印对象,可观察到模式、极性、相位的变化; - 外设模式接收错误:外设模式下
recv()会因总线无数据而抛OSError,测试通过循环接收捕获该异常; - 基本收发:
send(1)、recv(1)、send_recv(1)均指定timeout=100执行; - 资源释放:最后调用
deinit()。
对应期望输出见 spi.py.exp,例如SPI(1, SPI.CONTROLLER, baudrate= , polarity=1, phase=0, bits=8)。
在 TobudOS 工程中启用 MicroPython SPI
要在具体开发板上使用 SPI,需要满足两个条件:
- 工程配置:在板级
mpconfigboard.h中定义MICROPY_HW_SPI_NUM(如 BearPi 的 4),并在编译宏中定义MP_USING_MACHINE_SPI,使 mpconfigport.h 生效; - 运行时接入:MicroPython 解释器在 TobudOS 中以一个内核任务的形式运行。参考 micropython_demo.c,
application_entry中通过tos_task_create创建名为"micropython"的任务(优先级 3、栈 4KB),任务内循环调用mp_main()执行用户脚本。
启动后,在 MicroPython REPL 或脚本中即可按文档所述方式操作 SPI,例如驱动一块基于 SPI 的 Flash/LCD 芯片:
from machine import SPI, Pin cs = Pin('PA0', Pin.OUT) # 用普通 GPIO 手动控制片选(NSS 空闲) spi = SPI(1, SPI.CONTROLLER, baudrate=1000000, polarity=0, phase=0, bits=8, firstbit=SPI.MSB) cs.value(0) # 拉低片选,选中从设备 spi.send(b'\x90\x00\x00\x00') # 发送读 ID 命令 buf = bytearray(4) spi.send_recv(b'\x00\x00\x00\x00', buf) # 全双工读回 4 字节 cs.value(1) # 释放片选 print(hex(buf[0]), hex(buf[1]), hex(buf[2]))小结
SPI 是物联网终端中最常用的高速外设总线之一。pyb.SPI文档给出了完整、可复用的 API 契约:init()负责模式、时钟、极性/相位、位宽、位序与 CRC 的配置,send/recv/send_recv覆盖单工与全双工收发场景,deinit()负责资源释放。结合 TobudOS 仓库的 machine_hw_spi.c 与 mpconfigport.h 可以确认:该 API 在 TobudOS 移植中通过tos_hal_spi_init/tos_hal_spi_transfer/tos_hal_spi_deinit逐级映射到芯片 HAL,真正做到了"Python 一行配置,硬件即刻响应"。
编写 SPI 驱动脚本时的三个关键检查点:极性/相位(polarity + phase)是否与外设数据手册一致、时钟频率是否落在硬件分频集合内(必要时用 prescaler 精调)、片选时序是否由你的 GPIO 逻辑正确控制。把握这三点,即可在 TobudOS + MicroPython 环境下快速打通各类 SPI 外设。
【免费下载链接】TobudOS
TobudOS 是面向物联网领域开发的实时操作系统,早期版本基于腾讯自研的物联网操作系统TencentOS Tiny,2020年由腾讯捐赠到开放原子开源基金会进行孵化,2023年正式更名为TobudOS,TobudOS具有低功耗,低资源占用,模块化,安全可靠等特点,可有效提升物联网终端产品开发效率,提供精简的 RTOS 内核,内核组件可裁剪可配置,可快速移植到多种主流 MCU (如 STM32 全系列) 及模组芯片上。而且,基于 RTOS 内核提供了丰富的物联网组件,内部集成主流物联网协议栈(如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等),可助力物联网终端设备及业务快速接入物联网云平台。
相关推荐
MicroPython pyb.SPI 完全指南:pyboard 硬件 SPI 总线的初始化、引脚映射与数据收发实战
MicroPython pyb.SPI 完全指南:pyboard 硬件 SPI 总线的初始化、引脚映射与数据收发实战 本篇技术指南围绕 MicroPython
嵌入式语言运行时编程语言解释器编译器物联网系统编程TobudOS MicroPython 外设编程:pyb.I2C 双线串行协议接口完整指南
TobudOS MicroPython 外设编程:pyb.I2C 双线串行协议接口完整指南 TobudOS 在 components/language/micr
TobudOS 上的 MicroPython machine.SPI 指南:硬件 SPI 与 SoftSPI 的构造、初始化与读写实战
TobudOS 上的 MicroPython machine.SPI 指南:硬件 SPI 与 SoftSPI 的构造、初始化与读写实战 本文基于 compone
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考