☰
TobudOS 上的 MicroPython pyb.SPI 外设编程指南:从总线初始化到全双工收发
2026/10/12 2:11:17 网站建设 项目流程

【免费下载链接】TobudOS

TobudOS 是面向物联网领域开发的实时操作系统,早期版本基于腾讯自研的物联网操作系统TencentOS Tiny,2020年由腾讯捐赠到开放原子开源基金会进行孵化,2023年正式更名为TobudOS,TobudOS具有低功耗,低资源占用,模块化,安全可靠等特点,可有效提升物联网终端产品开发效率,提供精简的 RTOS 内核,内核组件可裁剪可配置,可快速移植到多种主流 MCU (如 STM32 全系列) 及模组芯片上。而且,基于 RTOS 内核提供了丰富的物联网组件,内部集成主流物联网协议栈(如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等),可助力物联网终端设备及业务快速接入物联网云平台。

项目地址:https://gitcode.com/openatomfoundation/TobudOS
点击查看免费下载

导读

本文以 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 总线的物理引脚映射如下:

总线位置NSSSCKMISOMOSI对应 STM32 引脚
SPI(1)XX5X6X7X8PA4 / PA5 / PA6 / PA7
SPI(2)YY5Y6Y7Y8PB12 / 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)

各参数含义与取值范围如下表:

参数取值说明
modeSPI.CONTROLLER/SPI.PERIPHERAL总线工作模式,必选参数
baudrate整数SCK 时钟频率(仅对控制器模式有意义),默认 328125
prescaler-1 或 2/4/8/16/32/64/128/256由 APB 总线频率推导 SCK 的分频系数;一旦指定,将覆盖baudrate
polarity0 或 1空闲时钟线的电平:0 为低电平空闲,1 为高电平空闲
phase0 或 1数据采样边沿:0 在第一个时钟沿采样,1 在第二个时钟沿采样
bits8 或 16每个传输字的位数
firstbitSPI.MSB/SPI.LSB先发送最高有效位还是最低有效位
tiTrue/False为True时使用 TI(德州仪器)信号约定,否则使用 Motorola 约定
crcNone或多项式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,返回 buf

SPI.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,它揭示了一条清晰的调用链:

  1. 使能开关:该文件整体受#if MICROPY_PY_MACHINE_SPI保护(第 28 行)。在 mpconfigport.h 中,MP_USING_MACHINE_SPI宏会展开为MICROPY_PY_MACHINE_SPI (1)与MICROPY_PY_MACHINE_SOFTSPI (1),即硬件 SPI 与软件 SPI 同时启用。
  2. 对象模型: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]对象池中。
  3. 初始化:init()内部调用tos_hal_spi_init(&self->spi, self->port),把 Python 层的总线编号直接映射为 HAL 端口(第 108 行)。
  4. 数据传输:核心的machine_hard_spi_transfer调用tos_hal_spi_transfer(&self->spi, src, dest, len, timeout)完成一次全双工传输,超时按timeout + len * timeout_char随长度动态计算(第 134-137 行)。
  5. 模块注册: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,需要满足两个条件:

  1. 工程配置:在板级mpconfigboard.h中定义MICROPY_HW_SPI_NUM(如 BearPi 的 4),并在编译宏中定义MP_USING_MACHINE_SPI,使 mpconfigport.h 生效;
  2. 运行时接入: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 等),可助力物联网终端设备及业务快速接入物联网云平台。

项目地址:https://gitcode.com/openatomfoundation/TobudOS
点击查看免费下载
上一篇:一个.cmd文件,能同时搞定Windows激活和Office激活吗?
下一篇:BlockNote 表格 Markdown 导出与列宽处理:从 columnWidths 到快照文件的完整链路解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询