5.5 SPI 设备
5.5 SPI 设备
📚 本节导读
学习时长: 约 90 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言基础、MCU SPI 基础知识、5.1 设备驱动框架、互斥锁基本用法
SPI(Serial Peripheral Interface)是嵌入式系统中常用的高速全双工同步串行通信总线,广泛用于 SPI Flash、LCD、传感器、SD 卡等外设。本节讲解 OneOS 中 SPI 设备的完整用法。OneOS 将 SPI 抽象为总线/设备两层架构:os_spi_bus 表示 SPI 总线控制器,os_spi_device 表示挂载在总线上的从设备。同时,OneOS 还提供了 QSPI(Quad SPI)扩展支持,通过多根数据线提升传输速率。SPI 设备驱动位于 drivers/spi/ 目录,核心头文件为 spi.h。
本节会带你:
- 看懂 SPI 总线/设备两层抽象如何基于 5.1 的设备框架实现"一套总线驱动服务多个从设备";
- 掌握
os_spi_bus、os_spi_device、os_spi_configuration、os_spi_message、os_spi_ops等核心结构体的每个字段(含位域/位掩码演算); - 掌握 CPOL/CPHA 四种标准 SPI 模式与配置标志的位运算含义(配数值演算);
- 逐个拆解
os_spi_bus_register、os_spi_bus_attach_device、os_spi_transfer、os_spi_send_then_recv/send、os_spi_transfer_message、os_spi_take/release、os_spi_configure的完整源码; - 理解 QSPI 扩展的架构与 QSPI 专用 API;
- 通过多个可运行示例掌握 SPI Flash 读写、全双工传输、消息链表、QSPI 读数据的实际开发。
说明:本节所有源码均取自项目
drivers/spi/spi.h、drivers/spi/spi_core.c、drivers/spi/spi_dev.c、drivers/spi/qspi_core.c,与项目源码逐字一致(含原始英文注释)。
🎯 学习目标
- 理解 OneOS 中 SPI 总线/设备分层架构的设计理念
- 掌握
os_spi_bus、os_spi_device、os_spi_configuration、os_spi_message、os_spi_ops各字段含义与来源 - 掌握 SPI 四种工作模式(MODE 0/1/2/3)与配置标志的位运算含义
- 掌握 SPI 消息传输、同步收发、总线获取/释放等核心 API 的使用
- 理解 QSPI 扩展的架构与使用方法
- 理解更换 MCU 时如何移植 BSP 层 SPI 驱动(认识
drv_spi.c、实现os_spi_ops、接入三层选择机制) - 能够编写完整的 SPI 设备驱动与数据收发程序
一、概述
SPI(Serial Peripheral Interface)是一种高速全双工同步串行通信总线,通过 SCLK(时钟)、MOSI(主机输出)、MISO(主机输入)、CS(片选) 四根线进行通信。OneOS 将 SPI 抽象为总线/设备两层架构:os_spi_bus 表示 SPI 总线控制器,os_spi_device 表示挂载在总线上的 SPI 从设备。同时,OneOS 还提供了 QSPI(Quad SPI)扩展支持,通过多根数据线提升传输速率。SPI 设备驱动位于 drivers/spi/ 目录下,核心头文件为 spi.h。
1.1 SPI 两层抽象的分层架构
OneOS SPI 设备在5.1 设备驱动框架的基础上做了两层封装,从应用层到硬件共四层:
┌────────────────────────────────────────────────────────────┐
│ 应用层(Application) │
│ os_spi_transfer / os_spi_send_then_recv/send │
│ os_spi_transfer_message / os_qspi_transfer_message │
│ os_device_read_block / os_device_write_block │
└──────────────────────────────┬─────────────────────────────┘
│ 通过 bus->ops->xfer / configure
▼
┌────────────────────────────────────────────────────────────┐
│ SPI 框架层(drivers/spi/spi_core.c / spi_dev.c) │
│ os_spi_transfer(加锁→配owner→调ops→解锁) │
│ os_spi_bus_register / attach_device / take/release │
│ _spi_bus_device_read/write/control(设备框架层封装) │
│ QSPI 扩展层(qspi_core.c) │
└──────────────────────────────┬─────────────────────────────┘
│ 调用 bus->ops->xfer / configure
▼
┌────────────────────────────────────────────────────────────┐
│ BSP 厂商 SPI 驱动(如 stm32 的 drv_spi.c) │
│ 实现 struct os_spi_ops 的 configure/xfer 两个函数指针 │
│ 操作具体 MCU 的 SPI 外设寄存器 + 片选 GPIO │
└──────────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 硬件(MCU SPI 外设 + CS GPIO) │
└────────────────────────────────────────────────────────────┘关键理解:SPI 框架层不直接操作硬件。os_spi_transfer 只负责"加互斥锁 → 配置 owner → 转发给 bus->ops->xfer → 解锁",真正操作寄存器与片选引脚的是 BSP 厂商驱动实现的 struct os_spi_ops。这让上层应用与具体 MCU 平台解耦。
1.2 SPI 设备使用场景
一句话核心
SPI 设备适用于在同一根高速同步总线上挂载多个从设备,以片选线 CS 寻址、全双工、短报文/高吞吐为特征的场景。
与其他总线/外设的机制对比
| 维度 | SPI | I2C | Serial(UART) | QSPI |
|---|---|---|---|---|
| 接线 | 3~4 根(MOSI/MISO/SCK/CS) | 2 根(SCL+SDA) | 2~3 根(TX/RX/GND) | 多根数据线 |
| 总线拓扑 | 一主多从,片选寻址 | 多主多从,地址寻址 | 点对点为主 | 一主一从为主 |
| 速率 | 快(可达几十 MHz) | 慢中(100k1M Hz) | 中(115200 等) | 更快(多线并行) |
| 是否全双工 | 是(MOSI/MISO 同时) | 否(单线分时) | 是(TX/RX 同时) | 是 |
| 设备识别 | 片选线 CS | 7/10 位地址 | 无 | 片选线 CS |
| 应用场景 | SPI Flash、LCD、传感器 | 传感器、EEPROM、RTC | 日志、通信、调试 | 大容量 SPI Flash |
为什么用 SPI 而不是别的:SPI Flash、高吞吐传感器需要高速率,SPI 同步时钟可达几十 MHz;I2C 速率低但省引脚(2 线挂多从机);UART 是点对点且速率有限。当需要"快"时,SPI 是首选。
典型应用场景
场景 1:一主多从(SPI 总线挂多个从设备)
┌──────────┐ SCLK ┌──────────┐
│ 主控 MCU ├─── MOSI ───►│ SPI Flash │ CS0
│ (主机) ├─── MISO ◄───│ (W25Q128)│
│ ├── CS0 ─────►│ │
│ ├── CS1 ─────►┌──────────┐
│ ├── CS2 ─────►│ LCD 屏 │ CS1
└──────────┘ └──────────┘为什么用 SPI:多个从设备共享 SCLK/MOSI/MISO,各自用一根 CS 片选线区分。发送数据前先拉低目标设备的 CS,数据传输结束后拉高。相比 I2C 的地址寻址,SPI 的片选寻址更简单直接,适合高速场景。
实际工作:任务 A os_spi_send_then_recv(spi_flash, &cmd, 1, id, 3) 读 Flash ID(BSP 驱动自动拉低 Flash 的 CS);任务 B os_spi_transfer(lcd, tx, rx, n) 刷屏(拉低 LCD 的 CS)。同一根总线分时复用,由总线互斥锁保证不冲突。
场景 2:SPI Flash 的"命令 + 地址 + 数据"事务(先发后收)
主机发送: CS↓ 0x03(读命令) 0x00 0x00 0x00(24位地址) ░░(高阻) CS↑
主机接收: (MOSI 发命令/地址) data[0] 至 data[n]为什么用 SPI:读 Flash 数据前必须先发"读命令 + 地址",再收数据。os_spi_send_then_recv 把"先发后收"封装成一条消息,CS 在发送阶段拉低、接收阶段结束后拉高。
实际工作:os_spi_send_then_recv(spi_flash, cmd_buf, 4, data, 256) 先发 4 字节(命令 + 3 字节地址),再收 256 字节数据,一次调用完成,中间 CS 保持低电平。
场景 3:任务间通过 SPI 访问共享外设(互斥保护)
为什么用 SPI:SPI 传输是阻塞式的(os_spi_transfer 会 os_mutex_lock 等待总线),若两个任务同时访问会互相干扰。总线设备内置互斥锁 lock,保证同一时刻只有一个任务在总线上传输。
实际工作:任务 A 正在读 Flash(持有总线锁),任务 B 此时想刷屏,os_mutex_lock(bus->lock, OS_WAIT_FOREVER) 让 B 阻塞等待,直到 A 的 os_spi_transfer 返回并解锁,B 才获得总线继续传输。
选型边界表(何时不该用 SPI)
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 只需低速率、少引脚挂多从机 | I2C | SPI 每加一个从机就多一根 CS,引脚开销大 |
| 只需点对点流式通信(日志/调试) | Serial(UART) | SPI 需要额外时钟线且协议更重 |
| 只传一个开关状态/标志位 | GPIO | 无需协议开销 |
| 需要多主多从、热插拔 | CAN/USB | SPI 本质是一主多从 |
结合源码的使用注意点
- 阻塞等待:
os_spi_transfer用os_mutex_lock(bus->lock, OS_WAIT_FOREVER),在中断上下文调用会死锁,因此 SPI 传输应在任务上下文中调用。 - owner 机制:每次传输前,若
bus->owner != device,会先调用ops->configure重新配置总线(因为不同设备可能有不同模式/速率),再置owner = device(见 4.3)。 os_spi_take/os_spi_release不等于os_spi_take_bus/os_spi_release_bus:前者只是控制片选 CS 的拉低/拉高(发一个cs_take=1或cs_release=1的空消息);后者才是获取/释放总线互斥锁(见 4.5/4.6)。- 消息链表:
os_spi_message用next指针串成链表,cs_take/cs_release控制在第一条消息拉低 CS、最后一条拉高,实现一次传输多段数据(见 4.7)。
二、核心数据结构(字段详解)
本节对照 drivers/spi/spi.h 讲解 SPI 涉及的五个核心结构体。来源:drivers/spi/spi.h。
2.1 SPI 消息结构体 struct os_spi_message
struct os_spi_message
{
const void *send_buf;
void *recv_buf;
os_size_t length;
struct os_spi_message *next;
unsigned cs_take : 1;
unsigned cs_release : 1;
};| 字段 | 类型 | 含义 |
|---|---|---|
send_buf | const void * | 发送数据缓冲区指针(可为 OS_NULL,仅接收) |
recv_buf | void * | 接收数据缓冲区指针(可为 OS_NULL,仅发送) |
length | os_size_t | 本次消息传输的数据字节数 |
next | struct os_spi_message * | 指向下一条消息的指针(组成链表) |
cs_take | 位域 1 位 | 传输前是否拉低片选 CS |
cs_release | 位域 1 位 | 传输后是否拉高片选 CS |
一条 os_spi_message 描述一次原子传输操作。多个消息通过 next 指针串成链表,os_spi_transfer_message 可一次性完成多段数据收发,中间 CS 保持有效。
2.2 SPI 配置结构体 struct os_spi_configuration
struct os_spi_configuration
{
uint8_t mode;
uint8_t data_width;
uint16_t reserved;
uint32_t max_hz;
};| 字段 | 类型 | 含义 |
|---|---|---|
mode | uint8_t | SPI 模式(CPOL/CPHA/位序/主从等标志位组合,见第三章) |
data_width | uint8_t | 数据宽度(通常 8 或 16 位) |
reserved | uint16_t | 保留字段 |
max_hz | uint32_t | 最大时钟频率(Hz) |
mode 是一个位掩码,把时钟极性、相位、位序、主从、片选极性等多个配置压缩到一个字节里(见 3.1/3.2 数值演算)。
2.3 SPI 总线结构体 struct os_spi_bus
struct os_spi_bus
{
struct os_device parent;
uint8_t mode;
const struct os_spi_ops *ops;
os_mutex_id lock;
struct os_spi_device *owner;
};| 字段 | 类型 | 含义 |
|---|---|---|
parent | struct os_device | 内嵌的通用设备对象,用于把总线注册进 5.1 设备框架 |
mode | uint8_t | 总线模式(OS_SPI_BUS_MODE_SPI/OS_SPI_BUS_MODE_QSPI/XSPI) |
ops | const struct os_spi_ops * | SPI 操作函数表(configure/xfer) |
lock | os_mutex_id | 总线互斥锁,保证同一时刻只有一个设备访问总线 |
owner | struct os_spi_device * | 当前占用总线的从设备(用于判断是否需要重新配置总线) |
关键理解:parent 内嵌 os_device,使 SPI 总线能注册到设备框架(类型为 OS_DEVICE_TYPE_SPIBUS)。owner 记录当前占用总线的从设备——当 bus->owner != device 时,下次传输前需先调用 ops->configure 重新配置总线并更新 owner(见 4.3)。
2.4 SPI 从设备结构体 struct os_spi_device
struct os_spi_device
{
struct os_device parent;
struct os_spi_bus *bus;
struct os_spi_configuration config;
os_base_t cs_pin;
void *user_data;
};| 字段 | 类型 | 含义 |
|---|---|---|
parent | struct os_device | 内嵌的通用设备对象,用于注册到设备框架(类型为 OS_DEVICE_TYPE_SPIDEVICE) |
bus | struct os_spi_bus * | 所属 SPI 总线设备指针(该从设备挂在哪条总线上) |
config | struct os_spi_configuration | 该从设备的 SPI 配置(模式/数据宽度/最大频率) |
cs_pin | os_base_t | 片选引脚编号(os_hw_spi_device_attach 会配置为 GPIO 输出) |
user_data | void * | 用户自定义数据指针 |
关键理解:os_spi_device 描述"挂在哪条总线 + 用什么配置 + 用哪个 CS 引脚"。一个 os_spi_bus 上可挂多个 os_spi_device,各自配置独立,通过 bus 指针关联到同一总线。
2.5 SPI 操作函数表 struct os_spi_ops
struct os_spi_ops
{
os_err_t (*configure)(struct os_spi_device *device, struct os_spi_configuration *configuration);
uint32_t (*xfer)(struct os_spi_device *device, struct os_spi_message *message);
};| 函数指针 | 说明 |
|---|---|
configure | 配置 SPI 硬件参数(模式、数据宽度、最大频率),返回 OS_SUCCESS 成功 |
xfer | 执行一次 SPI 消息传输,返回成功传输的字节数(0 表示失败) |
关键理解:configure 返回 os_err_t(OS_SUCCESS/OS_EIO 等错误码);xfer 返回 uint32_t(字节数,0 表示失败)。框架层用 xfer 返回值是否为 0 判断传输成败(见 4.3)。
2.6 QSPI 扩展结构体
QSPI(Quad SPI)在标准 SPI 基础上扩展多根数据线,提升带宽。QSPI 结构体通过在标准 SPI 结构体上内嵌继承 + 扩展实现(来源:drivers/spi/spi.h)。
QSPI 消息结构体 os_qspi_message:
struct os_qspi_message
{
struct os_spi_message parent;
/* Instruction stage */
struct
{
uint8_t content;
uint8_t qspi_lines;
} instruction;
/* Address and alternate_bytes stage */
struct
{
uint32_t content;
uint8_t size;
uint8_t qspi_lines;
} address, alternate_bytes;
/* Dummy_cycles stage */
uint32_t dummy_cycles;
/* Number of lines in qspi data stage, the other configuration items are in parent */
uint8_t qspi_data_lines;
};| 阶段 | 字段 | 含义 |
|---|---|---|
parent | struct os_spi_message | 继承自 SPI 消息(含收发缓冲、长度、CS 控制) |
instruction | content/qspi_lines | 指令阶段:指令内容 + 使用的数据线数(1/2/4) |
address | content/size/qspi_lines | 地址阶段:地址内容 + 地址大小 + 数据线数 |
alternate_bytes | content/size/qspi_lines | 交替字节阶段:同上(可选) |
dummy_cycles | uint32_t | 空周期数(用于时序延时) |
qspi_data_lines | uint8_t | 数据阶段使用的数据线数(1/2/4) |
QSPI 配置结构体 os_qspi_configuration:
struct os_qspi_configuration
{
struct os_spi_configuration parent;
/* The size of medium */
uint32_t medium_size;
/* Double data rate mode */
uint8_t ddr_mode;
/* The data lines max width which QSPI bus supported, such as 1, 2, 4 */
uint8_t qspi_dl_width;
};| 字段 | 类型 | 含义 |
|---|---|---|
parent | struct os_spi_configuration | 继承自 SPI 配置(模式/数据宽度/最大频率) |
medium_size | uint32_t | 存储介质大小(用于判断地址是 3 字节还是 4 字节,见 6.4) |
ddr_mode | uint8_t | 双倍数据速率(DDR)模式 |
qspi_dl_width | uint8_t | 支持的最大数据线宽度(1/2/4) |
QSPI 设备结构体 os_qspi_device:
struct os_qspi_device
{
struct os_spi_device parent;
struct os_qspi_configuration config;
void (*enter_qspi_mode)(struct os_qspi_device *device);
void (*exit_qspi_mode)(struct os_qspi_device *device);
};| 字段 | 类型 | 含义 |
|---|---|---|
parent | struct os_spi_device | 继承自 SPI 设备 |
config | struct os_qspi_configuration | QSPI 配置 |
enter_qspi_mode | 函数指针 | 进入 QSPI 模式回调(多线传输) |
exit_qspi_mode | 函数指针 | 退出 QSPI 模式回调 |
关键理解:QSPI 结构体通过结构体嵌套继承(parent 内嵌父结构体)复用标准 SPI 的字段,同时扩展 QSPI 特有的阶段字段(指令/地址/交替字节/空周期/数据线数)。os_qspi_message 的 parent 本身就是 os_spi_message,因此可传给 SPI 的 ops->xfer。
三、SPI 模式与配置标志
本节对照 drivers/spi/spi.h 讲解 SPI 模式与配置标志。所有标志都是位掩码,可组合使用。来源:drivers/spi/spi.h。
3.1 时钟极性与相位(CPOL/CPHA)
#define OS_SPI_CPHA (1 << 0) /* Bit[0]:CPHA, clock phase */
#define OS_SPI_CPOL (1 << 1) /* Bit[1]:CPOL, clock polarity */| 宏 | 数值 | 含义 |
|---|---|---|
OS_SPI_CPHA | 0x01 | 时钟相位(bit0),数据采样/输出边沿 |
OS_SPI_CPOL | 0x02 | 时钟极性(bit1),空闲时钟电平 |
数值演算:
(1 << 0) = 0b0001 = 0x01;(1 << 1) = 0b0010 = 0x02。两个位各自独立,组合成 MODE 0~3(见 3.3)。
源码头文件还用一段注释精确定义了四种组合的时序行为:
/*
* At CPOL=0 the base value of the clock is zero
* - For CPHA=0, data are captured on the clock's rising edge (low->high transition)
* and data are propagated on a falling edge (high->low clock transition).
* - For CPHA=1, data are captured on the clock's falling edge and data are
* propagated on a rising edge.
* At CPOL=1 the base value of the clock is one (inversion of CPOL=0)
* - For CPHA=0, data are captured on clock's falling edge and data are propagated
* on a rising edge.
* - For CPHA=1, data are captured on clock's rising edge and data are propagated
* on a falling edge.
*/3.2 位序、主从与扩展标志
#define OS_SPI_LSB (0 << 2) /* Bit[2]: 0-LSB */
#define OS_SPI_MSB (1 << 2) /* Bit[2]: 1-MSB */
#define OS_SPI_MASTER (0 << 3) /* SPI master device */
#define OS_SPI_SLAVE (1 << 3) /* SPI slave device */| 宏 | 数值 | 含义 |
|---|---|---|
OS_SPI_LSB | 0x00 | 低位先传(LSB first) |
OS_SPI_MSB | 0x04 | 高位先传(MSB first,bit2 = 1) |
OS_SPI_MASTER | 0x00 | 主设备模式 |
OS_SPI_SLAVE | 0x08 | 从设备模式(bit3 = 1) |
#define OS_SPI_CS_HIGH (1 << 4) /* Chipselect active high */
#define OS_SPI_NO_CS (1 << 5) /* No chipselect */
#define OS_SPI_3WIRE (1 << 6) /* SI/SO pin shared */
#define OS_SPI_READY (1 << 7) /* Slave pulls low to pause */| 宏 | 数值 | 含义 |
|---|---|---|
OS_SPI_CS_HIGH | 0x10 | 片选高有效(bit4) |
OS_SPI_NO_CS | 0x20 | 无片选信号(bit5) |
OS_SPI_3WIRE | 0x40 | 三线模式,SI/SO 共用(bit6) |
OS_SPI_READY | 0x80 | 从设备拉低暂停(bit7) |
模式掩码:
#define OS_SPI_MODE_MASK (OS_SPI_CPHA | OS_SPI_CPOL | OS_SPI_MSB | OS_SPI_SLAVE |\
OS_SPI_CS_HIGH | OS_SPI_NO_CS | OS_SPI_3WIRE | OS_SPI_READY)数值演算(模式掩码):
计算项 代入计算 结果 OS_SPI_CPHA0x010b0000_0001OS_SPI_CPOL0x020b0000_0010OS_SPI_MSB0x040b0000_0100OS_SPI_SLAVE0x080b0000_1000OS_SPI_CS_HIGH0x100b0001_0000OS_SPI_NO_CS0x200b0010_0000OS_SPI_3WIRE0x400b0100_0000OS_SPI_READY0x800b1000_0000或运算组合 全或 0b1111_1111=0xFF
OS_SPI_MODE_MASK = 0xFF 覆盖了 mode 的所有有效位。os_spi_configure 中用 cfg->mode & OS_SPI_MODE_MASK 只保留这些有效位,屏蔽其他干扰位(见 4.4)。
3.3 四种标准 SPI 模式
#define OS_SPI_MODE_0 (0 | 0) /* CPOL = 0, CPHA = 0 */
#define OS_SPI_MODE_1 (0 | OS_SPI_CPHA) /* CPOL = 0, CPHA = 1 */
#define OS_SPI_MODE_2 (OS_SPI_CPOL | 0) /* CPOL = 1, CPHA = 0 */
#define OS_SPI_MODE_3 (OS_SPI_CPOL | OS_SPI_CPHA) /* CPOL = 1, CPHA = 1 */数值演算(四种模式的实际值):
宏 展开 结果 OS_SPI_MODE_00 | 00x00OS_SPI_MODE_10 | 0x010x01OS_SPI_MODE_20x02 | 00x02OS_SPI_MODE_30x02 | 0x010x03
| 模式 | 数值 | CPOL | CPHA | 空闲时钟 | 采样边沿 |
|---|---|---|---|---|---|
OS_SPI_MODE_0 | 0x00 | 0 | 0 | 低电平 | 上升沿 |
OS_SPI_MODE_1 | 0x01 | 0 | 1 | 低电平 | 下降沿 |
OS_SPI_MODE_2 | 0x02 | 1 | 0 | 高电平 | 下降沿 |
OS_SPI_MODE_3 | 0x03 | 1 | 1 | 高电平 | 上升沿 |
为什么必须选对模式:SPI 从设备的数据手册会指定其工作模式(如 W25Q128 Flash 支持 MODE 0 和 MODE 3)。若主机的 CPOL/CPHA 与从机不匹配,采样时刻错误,会读到全 0 或乱码。
3.4 总线模式
#define OS_SPI_BUS_MODE_SPI (1 << 0)
#define OS_SPI_BUS_MODE_QSPI (1 << 1)
#define OS_SPI_BUS_MODE_XSPI (1 << 1)| 宏 | 数值 | 含义 |
|---|---|---|
OS_SPI_BUS_MODE_SPI | 0x01 | 标准 SPI 模式 |
OS_SPI_BUS_MODE_QSPI | 0x02 | Quad SPI 模式(4 线数据) |
OS_SPI_BUS_MODE_XSPI | 0x02 | 扩展 SPI 模式(同 QSPI) |
os_spi_bus_register 把总线 mode 设为 OS_SPI_BUS_MODE_SPI(见 4.1);os_qspi_bus_register 把它设为 OS_SPI_BUS_MODE_QSPI(见 6.2)。XSPI 与 QSPI 数值相同,是别名。
3.5 设备控制命令
#define OS_SPI_DEVICE_CTRL_RW IOC_SPIDEVICE(0x00)IOC_SPIDEVICE(cmd) 宏来自 drivers/device.h:
#define IOC_SPIDEVICE(cmd) ((OS_DEVICE_TYPE_SPIDEVICE << 8) | (cmd))其中 OS_DEVICE_TYPE_SPIDEVICE 是设备类型枚举的一个成员(drivers/device.h 中枚举顺序:CHAR=0, BLOCK=1, NETIF=2, MTD=3, CAN=4, RTC=5, SOUND=6, GRAPHIC=7, I2CBUS=8, USBDEVICE=9, USBHOST=10, SPIBUS=11, SPIDEVICE=12, SDIO=13 等)。
数值演算:
计算项 代入计算 结果 OS_DEVICE_TYPE_SPIDEVICE枚举第 13 项 12=0x0CIOC_SPIDEVICE(0x00)(0x0C << 8) | 0x000x0C00
即 OS_SPI_DEVICE_CTRL_RW = 0x0C00,用于 os_device_control 通过设备框架命令 SPI 从设备执行一次读写(见 5.9 的 _spidev_device_control)。
四、核心 API 详解
本节逐个拆解 drivers/spi/spi_core.c 中的核心函数。来源:drivers/spi/spi_core.c。
4.1 os_spi_bus_register —— 注册 SPI 总线
函数签名
os_err_t os_spi_bus_register(struct os_spi_bus *bus,
const char *name,
const struct os_spi_ops *ops)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
bus | struct os_spi_bus * | SPI 总线结构体指针 |
name | const char * | 总线名称,如 "spi0"、"spi1" |
ops | const struct os_spi_ops * | SPI 操作函数表(configure/xfer) |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 注册成功 |
| 其他 | 注册失败(os_spi_bus_device_init 的返回值) |
完整源码
os_err_t os_spi_bus_register(struct os_spi_bus *bus, const char *name, const struct os_spi_ops *ops)
{
os_err_t result;
result = os_spi_bus_device_init(bus, name);
if (result != OS_SUCCESS)
return result;
/* Initialize mutex lock */
bus->lock = os_mutex_create(OS_NULL, name, OS_FALSE);
/* Set ops */
bus->ops = ops;
/* Initialize owner */
bus->owner = OS_NULL;
/* Set bus mode */
bus->mode = OS_SPI_BUS_MODE_SPI;
return OS_SUCCESS;
}分步详解
第一步:初始化并注册到设备框架(条件 os_spi_bus_device_init 成功)
result = os_spi_bus_device_init(bus, name);
if (result != OS_SUCCESS)
return result;调用 os_spi_bus_device_init(见 5.1)把总线注册进设备框架(类型 OS_DEVICE_TYPE_SPIBUS、挂 spi_bus_ops)。若失败直接返回。
第二步:创建总线互斥锁(无条件执行)
/* Initialize mutex lock */
bus->lock = os_mutex_create(OS_NULL, name, OS_FALSE);创建非递归互斥锁,命名为总线名,供 os_spi_transfer 等加锁/解锁使用。
第三步:设置 ops、owner、模式(无条件执行)
/* Set ops */
bus->ops = ops;
/* Initialize owner */
bus->owner = OS_NULL;
/* Set bus mode */
bus->mode = OS_SPI_BUS_MODE_SPI;挂上 ops 表,owner 初始为 OS_NULL(尚无设备占用),模式设为标准 SPI。最后返回 OS_SUCCESS。
条件走向总结
os_spi_bus_register() 条件走向总结:
├── result = os_spi_bus_device_init(bus, name)(注册到设备框架)
│ ├── result != OS_SUCCESS → 返回 result
│ └── result == OS_SUCCESS → 继续
├── bus->lock = os_mutex_create(OS_NULL, name, OS_FALSE)(创建互斥锁)
├── bus->ops = ops
├── bus->owner = OS_NULL
├── bus->mode = OS_SPI_BUS_MODE_SPI
└── 返回 OS_SUCCESS4.2 os_spi_bus_attach_device —— 挂载 SPI 从设备
函数签名
os_err_t os_spi_bus_attach_device(struct os_spi_device *device,
const char *name,
const char *bus_name,
os_base_t cs_pin)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_spi_device * | SPI 从设备结构体指针 |
name | const char * | 从设备名称 |
bus_name | const char * | 目标总线名称 |
cs_pin | os_base_t | 片选引脚编号 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 挂载成功 |
OS_FAILURE | 未找到目标总线或类型不是 OS_DEVICE_TYPE_SPIBUS |
| 其他 | os_spidev_device_init 的返回值 |
完整源码
/* clang-format off */
os_err_t os_spi_bus_attach_device(struct os_spi_device *device, const char *name, const char *bus_name, os_base_t cs_pin)
{
os_err_t result;
os_device_t *bus;
/* Get physical spi bus */
bus = os_device_open_s(bus_name);
if (bus != OS_NULL && bus->type == OS_DEVICE_TYPE_SPIBUS)
{
device->bus = (struct os_spi_bus *)bus;
/* Initialize spidev device */
result = os_spidev_device_init(device, name);
if (result != OS_SUCCESS)
return result;
memset(&device->config, 0, sizeof(device->config));
device->cs_pin = cs_pin;
return OS_SUCCESS;
}
/* Not found the host bus */
return OS_FAILURE;
}
/* clang-format on */分步详解
第一步:按名称打开目标总线(无条件执行)
/* Get physical spi bus */
bus = os_device_open_s(bus_name);调用 os_device_open_s 打开名为 bus_name 的总线设备。
第二步:校验总线存在且类型为 SPI 总线(条件 bus != OS_NULL && bus->type == OS_DEVICE_TYPE_SPIBUS)
if (bus != OS_NULL && bus->type == OS_DEVICE_TYPE_SPIBUS)
{
device->bus = (struct os_spi_bus *)bus;
/* Initialize spidev device */
result = os_spidev_device_init(device, name);
if (result != OS_SUCCESS)
return result;
memset(&device->config, 0, sizeof(device->config));
device->cs_pin = cs_pin;
return OS_SUCCESS;
}
/* Not found the host bus */
return OS_FAILURE;为什么还要校验 bus->type:os_device_open_s 只按名称返回设备,不区分类型。若找到一个同名但类型不是 SPI 总线的设备,直接当 SPI 总线用会类型错乱。校验 bus->type == OS_DEVICE_TYPE_SPIBUS 确保找到的确实是 SPI 总线。任一条件不满足则返回 OS_FAILURE。
第三步:绑定总线、初始化从设备、置空配置(无条件执行,条件进入 if)
device->bus = (struct os_spi_bus *)bus;
/* Initialize spidev device */
result = os_spidev_device_init(device, name);
if (result != OS_SUCCESS)
return result;
memset(&device->config, 0, sizeof(device->config));
device->cs_pin = cs_pin;
return OS_SUCCESS;把 device->bus 指向总线,调用 os_spidev_device_init(见 5.2)注册从设备(类型 OS_DEVICE_TYPE_SPIDEVICE),清零配置,记录 cs_pin,返回成功。
关键理解:
os_spi_bus_attach_device会把device->config清零(memset为 0),因此挂载后必须先调用os_spi_configure设置模式/数据宽度/最大频率,才能正常传输。
条件走向总结
os_spi_bus_attach_device() 条件走向总结:
├── bus = os_device_open_s(bus_name)
├── bus != OS_NULL 且 bus->type == OS_DEVICE_TYPE_SPIBUS
│ ├── device->bus = bus
│ ├── result = os_spidev_device_init(device, name)
│ │ ├── result != OS_SUCCESS → 返回 result
│ │ └── result == OS_SUCCESS → 继续
│ ├── memset(&device->config, 0, sizeof(config))
│ ├── device->cs_pin = cs_pin
│ └── 返回 OS_SUCCESS
└── 否则(总线不存在或类型不符)→ 返回 OS_FAILURE4.3 os_spi_transfer —— 全双工传输(核心)
函数签名
os_size_t os_spi_transfer(struct os_spi_device *device, const void *send_buf, void *recv_buf, os_size_t length)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_spi_device * | SPI 从设备指针 |
send_buf | const void * | 发送缓冲区(可为 OS_NULL,仅接收) |
recv_buf | void * | 接收缓冲区(可为 OS_NULL,仅发送) |
length | os_size_t | 传输字节数 |
| 返回值 | 说明 |
|---|---|
> 0 | 成功传输的字节数 |
0 | 传输失败(加锁失败、配置失败或底层 xfer 失败) |
完整源码
os_size_t os_spi_transfer(struct os_spi_device *device, const void *send_buf, void *recv_buf, os_size_t length)
{
os_err_t result;
struct os_spi_message message;
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result == OS_SUCCESS)
{
if (device->bus->owner != device)
{
/* Not the same owner as current, re-configure SPI bus */
result = device->bus->ops->configure(device, &device->config);
if (result == OS_SUCCESS)
{
/* Set SPI bus owner */
device->bus->owner = device;
}
else
{
/* Configure SPI bus failed */
//os_set_errno(OS_EIO);
result = 0;
goto __exit;
}
}
/* Initial message */
message.send_buf = send_buf;
message.recv_buf = recv_buf;
message.length = length;
message.cs_take = 1;
message.cs_release = 1;
message.next = OS_NULL;
/* Transfer message */
result = device->bus->ops->xfer(device, &message);
if (result == 0)
{
//os_set_errno(OS_EIO);
goto __exit;
}
}
else
{
//os_set_errno(OS_EIO);
return 0;
}
__exit:
os_mutex_unlock(device->bus->lock);
return result;
}分步详解
第一步:加总线互斥锁(条件 os_mutex_lock 成功)
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result == OS_SUCCESS)
{
if (device->bus->owner != device)
{
/* Not the same owner as current, re-configure SPI bus */
result = device->bus->ops->configure(device, &device->config);
if (result == OS_SUCCESS)
{
/* Set SPI bus owner */
device->bus->owner = device;
}
else
{
/* Configure SPI bus failed */
//os_set_errno(OS_EIO);
result = 0;
goto __exit;
}
}
/* Initial message */
message.send_buf = send_buf;
message.recv_buf = recv_buf;
message.length = length;
message.cs_take = 1;
message.cs_release = 1;
message.next = OS_NULL;
/* Transfer message */
result = device->bus->ops->xfer(device, &message);
if (result == 0)
{
//os_set_errno(OS_EIO);
goto __exit;
}
}
else
{
//os_set_errno(OS_EIO);
return 0;
}
__exit:
os_mutex_unlock(device->bus->lock);
return result;os_mutex_lock(bus->lock, OS_WAIT_FOREVER) 获取总线互斥锁。为什么用互斥锁:SPI 是共享总线,若任务 A 传输到一半任务 B 抢占,两条传输会在 SCLK/MOSI 上交叉错乱。总线锁保证同一时刻只有一条传输。若加锁失败返回 0。
第二步:owner 检查并重新配置(条件 device->bus->owner != device)
if (device->bus->owner != device)
{
/* Not the same owner as current, re-configure SPI bus */
result = device->bus->ops->configure(device, &device->config);
if (result == OS_SUCCESS)
{
/* Set SPI bus owner */
device->bus->owner = device;
}
else
{
/* Configure SPI bus failed */
//os_set_errno(OS_EIO);
result = 0;
goto __exit;
}
}为什么需要 owner 检查:总线上可能挂了多个设备,每个设备的模式/速率不同。若上次占用总线的是设备 A,现在换设备 B 传输,必须先按 B 的配置重新配置硬件(ops->configure),再置 owner = B。若配置失败返回 0。
数值演算(owner 判断):设总线上挂 A、B 两个设备,A 用 MODE 0、B 用 MODE 3:
操作 owner 判断 结果 A 第一次传输 OS_NULLowner != A配置为 MODE 0,owner=A A 第二次传输 A owner == A无需重配,直接传输 B 第一次传输 A owner != B重新配置为 MODE 3,owner=B B 第二次传输 B owner == B无需重配
第三步:构造单条消息并传输(无条件执行,前提走到 if 分支)
/* Initial message */
message.send_buf = send_buf;
message.recv_buf = recv_buf;
message.length = length;
message.cs_take = 1;
message.cs_release = 1;
message.next = OS_NULL;
/* Transfer message */
result = device->bus->ops->xfer(device, &message);
if (result == 0)
{
//os_set_errno(OS_EIO);
goto __exit;
}构造单条消息:cs_take=1(传输前拉低 CS)、cs_release=1(传输后拉高 CS)。调用 ops->xfer 完成传输,返回字节数;若为 0 则失败。
第四步:解锁并返回(无条件执行)
__exit:
os_mutex_unlock(device->bus->lock);
return result;释放总线锁,返回传输字节数。
条件走向总结
os_spi_transfer() 条件走向总结:
├── os_mutex_lock(bus->lock, OS_WAIT_FOREVER)
│ ├── 加锁失败 → 返回 0
│ └── 加锁成功 → 继续
├── bus->owner != device(换了设备)
│ ├── ops->configure 成功 → owner = device,继续
│ └── ops->configure 失败 → result = 0,goto __exit
├── bus->owner == device(无需重配)→ 继续
├── 构造单条消息(cs_take=1, cs_release=1, next=NULL)
├── result = ops->xfer(device, &message)
│ ├── result == 0(失败)→ goto __exit
│ └── result > 0(成功)→ 继续
├── __exit: os_mutex_unlock(bus->lock)
└── 返回 result4.4 os_spi_configure —— 配置 SPI 设备
函数签名
os_err_t os_spi_configure(struct os_spi_device *device, struct os_spi_configuration *cfg)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_spi_device * | SPI 从设备指针 |
cfg | struct os_spi_configuration * | 目标配置 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 配置成功 |
完整源码
os_err_t os_spi_configure(struct os_spi_device *device, struct os_spi_configuration *cfg)
{
os_err_t result;
OS_ASSERT(device != OS_NULL);
/* Set configuration */
device->config.data_width = cfg->data_width;
device->config.mode = cfg->mode & OS_SPI_MODE_MASK;
device->config.max_hz = cfg->max_hz;
if (device->bus != OS_NULL)
{
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result == OS_SUCCESS)
{
if (device->bus->owner == device)
{
device->bus->ops->configure(device, &device->config);
}
/* Release lock */
os_mutex_unlock(device->bus->lock);
}
}
return OS_SUCCESS;
}分步详解
第一步:保存配置(无条件执行)
OS_ASSERT(device != OS_NULL);
/* Set configuration */
device->config.data_width = cfg->data_width;
device->config.mode = cfg->mode & OS_SPI_MODE_MASK;
device->config.max_hz = cfg->max_hz;把 cfg 的数据宽度、模式、最大频率写入 device->config。cfg->mode & OS_SPI_MODE_MASK 用掩码只保留有效位(见 3.2 数值演算),屏蔽无关位。
第二步:若已占用总线则立即应用配置(条件 device->bus != OS_NULL 且 owner == device)
if (device->bus != OS_NULL)
{
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result == OS_SUCCESS)
{
if (device->bus->owner == device)
{
device->bus->ops->configure(device, &device->config);
}
/* Release lock */
os_mutex_unlock(device->bus->lock);
}
}
return OS_SUCCESS;只有当该设备已占用总线(owner == device)时才立即调用 ops->configure 应用配置到硬件;若设备还未获得总线(owner 是别的设备),只保存配置,等下次 os_spi_transfer 获得总线时再应用。这样避免了"改配置却打断别的设备在总线上传输"。
条件走向总结
os_spi_configure() 条件走向总结:
├── 保存 config.data_width/mode(带掩码)/max_hz
├── device->bus != OS_NULL
│ ├── os_mutex_lock(bus->lock)
│ │ ├── 加锁成功
│ │ │ ├── owner == device → ops->configure(device, &config)(应用配置)
│ │ │ └── owner != device → 不应用(仅保存)
│ │ │ └── os_mutex_unlock
│ │ └── 加锁失败 → 跳过
├── device->bus == OS_NULL → 跳过
└── 返回 OS_SUCCESS4.5 获取/释放总线(os_spi_take_bus / os_spi_release_bus)
这两个函数管理总线互斥锁的获取与释放,并负责 owner 配置。
函数签名
os_err_t os_spi_take_bus(struct os_spi_device *device)
os_err_t os_spi_release_bus(struct os_spi_device *device)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_spi_device * | SPI 从设备指针 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 成功 |
OS_BUSY | 总线忙(os_spi_take_bus 加锁失败) |
OS_EIO | 配置失败 |
完整源码(os_spi_take_bus)
os_err_t os_spi_take_bus(struct os_spi_device *device)
{
os_err_t result = OS_SUCCESS;
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result != OS_SUCCESS)
{
//os_set_errno(OS_BUSY);
return OS_BUSY;
}
/* Reset errno */
//os_set_errno(OS_SUCCESS);
/* Configure SPI bus */
if (device->bus->owner != device)
{
/* Not the same owner as current, re-configure SPI bus */
result = device->bus->ops->configure(device, &device->config);
if (result == OS_SUCCESS)
{
/* Set SPI bus owner */
device->bus->owner = device;
}
else
{
/* Configure SPI bus failed */
//os_set_errno(OS_EIO);
/* Release lock */
os_mutex_unlock(device->bus->lock);
return OS_EIO;
}
}
return result;
}完整源码(os_spi_release_bus)
os_err_t os_spi_release_bus(struct os_spi_device *device)
{
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
OS_ASSERT(device->bus->owner == device);
/* Release lock */
os_mutex_unlock(device->bus->lock);
return OS_SUCCESS;
}分步详解(os_spi_take_bus)
第一步:加锁并判断(条件 os_mutex_lock 结果)
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result != OS_SUCCESS)
{
return OS_BUSY;
}获取总线互斥锁,失败返回 OS_BUSY。
第二步:owner 检查并配置(条件 owner != device)
if (device->bus->owner != device)
{
result = device->bus->ops->configure(device, &device->config);
if (result == OS_SUCCESS)
{
device->bus->owner = device;
}
else
{
os_mutex_unlock(device->bus->lock);
return OS_EIO;
}
}若换了设备,重新配置总线并更新 owner;配置失败释放锁并返回 OS_EIO。
分步详解(os_spi_release_bus)
第一步:断言 owner 正确并解锁(无条件执行)
OS_ASSERT(device->bus->owner == device);
os_mutex_unlock(device->bus->lock);
return OS_SUCCESS;为什么断言 owner == device:只有获得总线锁且成为 owner 的设备才能释放总线。若其他设备尝试释放,说明逻辑错误(未获取就释放),尽早暴露。
关键理解——
take_bus/release_bus与take/release的区别:
os_spi_take_bus/os_spi_release_bus:获取/释放总线互斥锁(os_mutex_lock/unlock),并处理 owner 配置;os_spi_take/os_spi_release:仅控制片选 CS 的拉低/拉高(发一个空消息,见 4.6)。
二者是不同的机制:前者管总线使用权,后者管片选电平。
4.6 获取/释放设备片选(os_spi_take / os_spi_release)
注意:这两个函数并不调用 os_spi_take_bus/os_spi_release_bus,也没有引用计数机制。它们只是构造一个空消息,通过 ops->xfer 控制片选 CS 的拉低/拉高。来源:drivers/spi/spi_core.c。
函数签名
os_err_t os_spi_take(struct os_spi_device *device)
os_err_t os_spi_release(struct os_spi_device *device)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_spi_device * | SPI 从设备指针 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 成功 |
| 其他 | 底层 ops->xfer 的返回值 |
完整源码(os_spi_take)
os_err_t os_spi_take(struct os_spi_device *device)
{
os_err_t result;
struct os_spi_message message;
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
memset(&message, 0, sizeof(message));
message.cs_take = 1;
result = device->bus->ops->xfer(device, &message);
return result;
}完整源码(os_spi_release)
os_err_t os_spi_release(struct os_spi_device *device)
{
os_err_t result;
struct os_spi_message message;
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
memset(&message, 0, sizeof(message));
message.cs_release = 1;
result = device->bus->ops->xfer(device, &message);
return result;
}分步详解(os_spi_take)
第一步:构造仅拉低片选的消息(无条件执行)
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
memset(&message, 0, sizeof(message));
message.cs_take = 1;
result = device->bus->ops->xfer(device, &message);
return result;把消息清零后仅置 cs_take = 1(其余字段全 0,length = 0),调用 ops->xfer。BSP 驱动发现 cs_take = 1 就拉低 CS(见 ST 的 spixfer 中 os_pin_write(device->cs_pin, PIN_LOW))。os_spi_release 相反,置 cs_release = 1 拉高 CS。
为什么需要 os_spi_take/os_spi_release:某些需要手动控制片选保持时序的场景(如多个消息之间 CS 保持低电平、或特殊外设时序),可用这对函数手动拉低/拉高 CS,配合 cs_take/cs_release 为 0 的消息使用。
修正说明:本节早期版本曾误述
os_spi_take/os_spi_release会调用os_spi_take_bus/os_spi_release_bus并具备引用计数。实际源码中并无引用计数,os_spi_take/os_spi_release只控制片选,请以本节为准。
条件走向总结
os_spi_take():纯顺序执行(无分支)
└── 构造零消息 + cs_take=1 → ops->xfer → 返回结果(BSP 拉低 CS)
os_spi_release():纯顺序执行(无分支)
└── 构造零消息 + cs_release=1 → ops->xfer → 返回结果(BSP 拉高 CS)4.7 os_spi_transfer_message —— 消息链表传输
函数签名
struct os_spi_message *os_spi_transfer_message(struct os_spi_device *device, struct os_spi_message *message)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_spi_device * | SPI 从设备指针 |
message | struct os_spi_message * | 消息链表头指针 |
| 返回值 | 说明 |
|---|---|
OS_NULL | 传输成功(链表全部完成)或 message 为 OS_NULL |
| 非空指针 | 传输失败时返回失败的那条消息指针(或加锁失败返回原 message) |
完整源码
struct os_spi_message *os_spi_transfer_message(struct os_spi_device *device, struct os_spi_message *message)
{
os_err_t result;
struct os_spi_message *index;
OS_ASSERT(device != OS_NULL);
/* Get first message */
index = message;
if (index == OS_NULL)
return index;
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result != OS_SUCCESS)
{
//os_set_errno(OS_BUSY);
return index;
}
/* Reset errno */
//os_set_errno(OS_SUCCESS);
/* Configure SPI bus */
if (device->bus->owner != device)
{
/* Not the same owner as current, re-configure SPI bus */
result = device->bus->ops->configure(device, &device->config);
if (result == OS_SUCCESS)
{
/* Set SPI bus owner */
device->bus->owner = device;
}
else
{
/* Configure SPI bus failed */
//os_set_errno(OS_EIO);
goto __exit;
}
}
/* Transmit each SPI message */
while (index != OS_NULL)
{
/* Transmit SPI message */
result = device->bus->ops->xfer(device, index);
if (result == 0)
{
//os_set_errno(OS_EIO);
break;
}
index = index->next;
}
__exit:
/* Release bus lock */
os_mutex_unlock(device->bus->lock);
return index;
}分步详解
第一步:取链表头并判空(条件 message != OS_NULL)
OS_ASSERT(device != OS_NULL);
/* Get first message */
index = message;
if (index == OS_NULL)
return index;message 为 OS_NULL 时直接返回(无需传输)。
第二步:加总线锁(条件 os_mutex_lock 成功)
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result != OS_SUCCESS)
{
return index;
}获取总线锁,失败返回原 index(链表头)。
第三步:owner 检查并配置(条件 owner != device)
if (device->bus->owner != device)
{
result = device->bus->ops->configure(device, &device->config);
if (result == OS_SUCCESS)
{
device->bus->owner = device;
}
else
{
goto __exit;
}
}同 4.3 第二步,配置失败跳转 __exit。
第四步:遍历链表逐条传输(循环条件 index != OS_NULL)
while (index != OS_NULL)
{
result = device->bus->ops->xfer(device, index);
if (result == 0)
{
break;
}
index = index->next;
}逐条调用 ops->xfer 传输链表中的每条消息。index 从链表头开始,用 index->next 前进;某条传输失败(返回 0)则 break 停止,index 停在失败的那条上。
第五步:解锁并返回(无条件执行)
__exit:
/* Release bus lock */
os_mutex_unlock(device->bus->lock);
return index;释放锁,返回 index。
关键理解——返回语义:返回
OS_NULL表示全部消息传输成功(index遍历到末尾变成OS_NULL);返回非空指针表示某条消息失败,该指针就是失败的消息,可据此定位(见示例 3 的os_spi_message_append构建链表)。
条件走向总结
os_spi_transfer_message() 条件走向总结:
├── index = message
├── message == OS_NULL → 返回 OS_NULL
├── os_mutex_lock(bus->lock)
│ ├── 加锁失败 → 返回 index(链表头)
│ └── 加锁成功 → 继续
├── owner != device → ops->configure
│ ├── 成功 → owner = device
│ └── 失败 → goto __exit
├── while (index != OS_NULL)
│ ├── result = ops->xfer(device, index)
│ │ ├── result == 0(失败)→ break(index 停在失败消息)
│ │ └── result > 0 → index = index->next
├── __exit: os_mutex_unlock
└── 返回 index(OS_NULL=全部成功,非空=失败消息)4.8 os_spi_send_then_recv —— 先发后收(半双工)
函数签名
os_err_t os_spi_send_then_recv(struct os_spi_device *device,
const void *send_buf,
os_size_t send_length,
void *recv_buf,
os_size_t recv_length)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_spi_device * | SPI 从设备指针 |
send_buf | const void * | 发送缓冲区(命令/地址) |
send_length | os_size_t | 发送字节数 |
recv_buf | void * | 接收缓冲区 |
recv_length | os_size_t | 接收字节数 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 传输成功 |
OS_EIO | 传输失败(加锁失败、配置失败或 xfer 失败) |
完整源码
os_err_t os_spi_send_then_recv(struct os_spi_device *device,
const void *send_buf,
os_size_t send_length,
void *recv_buf,
os_size_t recv_length)
{
os_err_t result;
struct os_spi_message message;
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result == OS_SUCCESS)
{
if (device->bus->owner != device)
{
/* Not the same owner as current, re-configure SPI bus */
result = device->bus->ops->configure(device, &device->config);
if (result == OS_SUCCESS)
{
/* Set SPI bus owner */
device->bus->owner = device;
}
else
{
/* Configure SPI bus failed */
result = OS_EIO;
goto __exit;
}
}
/* Send data */
message.send_buf = send_buf;
message.recv_buf = OS_NULL;
message.length = send_length;
message.cs_take = 1;
message.cs_release = 0;
message.next = OS_NULL;
result = device->bus->ops->xfer(device, &message);
if (result == 0)
{
result = OS_EIO;
goto __exit;
}
/* Recv data */
message.send_buf = OS_NULL;
message.recv_buf = recv_buf;
message.length = recv_length;
message.cs_take = 0;
message.cs_release = 1;
message.next = OS_NULL;
result = device->bus->ops->xfer(device, &message);
if (result == 0)
{
result = OS_EIO;
goto __exit;
}
result = OS_SUCCESS;
}
else
{
return OS_EIO;
}
__exit:
os_mutex_unlock(device->bus->lock);
return result;
}分步详解
第一步:加锁并配置 owner(条件 owner != device)
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result == OS_SUCCESS)
{
if (device->bus->owner != device)
{
result = device->bus->ops->configure(device, &device->config);
if (result == OS_SUCCESS)
{
device->bus->owner = device;
}
else
{
result = OS_EIO;
goto __exit;
}
}
}
else
{
return OS_EIO;
}与 4.3 相同:加锁、owner 检查、必要时重配总线。加锁成功且 owner 就绪后,进入"发送阶段"和"接收阶段"(见下方第二/三步)。
第二步:发送阶段(无条件执行)
/* Send data */
message.send_buf = send_buf;
message.recv_buf = OS_NULL;
message.length = send_length;
message.cs_take = 1;
message.cs_release = 0;
message.next = OS_NULL;
result = device->bus->ops->xfer(device, &message);
if (result == 0)
{
result = OS_EIO;
goto __exit;
}构造发送消息:cs_take = 1(传输前拉低 CS)、cs_release = 0(暂不释放,为接收阶段保持 CS 有效)。调用 xfer 发送命令/地址。
第三步:接收阶段(无条件执行)
/* Recv data */
message.send_buf = OS_NULL;
message.recv_buf = recv_buf;
message.length = recv_length;
message.cs_take = 0;
message.cs_release = 1;
message.next = OS_NULL;
result = device->bus->ops->xfer(device, &message);
if (result == 0)
{
result = OS_EIO;
goto __exit;
}
result = OS_SUCCESS;构造接收消息:cs_take = 0(不重新拉低,延续发送阶段的 CS 低电平)、cs_release = 1(接收结束后拉高 CS)。调用 xfer 读数据。两阶段之间 CS 保持低电平,形成完整的"命令+地址→数据"事务。
第四步:解锁并返回(无条件执行)
__exit:
os_mutex_unlock(device->bus->lock);
return result;条件走向总结
os_spi_send_then_recv() 条件走向总结:
├── os_mutex_lock
│ ├── 加锁失败 → 返回 OS_EIO
│ └── 加锁成功 → 继续
├── owner != device → ops->configure
│ ├── 成功 → owner = device
│ └── 失败 → result=OS_EIO, goto __exit
├── 发送阶段:构造消息(cs_take=1, cs_release=0) → ops->xfer
│ ├── 失败(result==0) → result=OS_EIO, goto __exit
│ └── 成功 → 继续
├── 接收阶段:构造消息(cs_take=0, cs_release=1) → ops->xfer
│ ├── 失败 → result=OS_EIO, goto __exit
│ └── 成功 → result = OS_SUCCESS
├── __exit: os_mutex_unlock
└── 返回 result4.9 os_spi_send_then_send —— 分段发送
函数签名
os_err_t os_spi_send_then_send(struct os_spi_device *device,
const void *send_buf1,
os_size_t send_length1,
const void *send_buf2,
os_size_t send_length2)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_spi_device * | SPI 从设备指针 |
send_buf1/send_length1 | const void */os_size_t | 第一段发送缓冲/长度 |
send_buf2/send_length2 | const void */os_size_t | 第二段发送缓冲/长度 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 两段都发送成功 |
OS_EIO | 失败 |
完整源码(与 os_spi_send_then_recv 结构完全相同,只是两阶段都是发送):
os_err_t os_spi_send_then_send(struct os_spi_device *device,
const void *send_buf1,
os_size_t send_length1,
const void *send_buf2,
os_size_t send_length2)
{
os_err_t result;
struct os_spi_message message;
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
result = os_mutex_lock(device->bus->lock, OS_WAIT_FOREVER);
if (result == OS_SUCCESS)
{
if (device->bus->owner != device)
{
/* Not the same owner as current, re-configure SPI bus */
result = device->bus->ops->configure(device, &device->config);
if (result == OS_SUCCESS)
{
/* Set SPI bus owner */
device->bus->owner = device;
}
else
{
/* Configure SPI bus failed */
result = OS_EIO;
goto __exit;
}
}
/* Send data1 */
message.send_buf = send_buf1;
message.recv_buf = OS_NULL;
message.length = send_length1;
message.cs_take = 1;
message.cs_release = 0;
message.next = OS_NULL;
result = device->bus->ops->xfer(device, &message);
if (result == 0)
{
result = OS_EIO;
goto __exit;
}
/* Send data2 */
message.send_buf = send_buf2;
message.recv_buf = OS_NULL;
message.length = send_length2;
message.cs_take = 0;
message.cs_release = 1;
message.next = OS_NULL;
result = device->bus->ops->xfer(device, &message);
if (result == 0)
{
result = OS_EIO;
goto __exit;
}
result = OS_SUCCESS;
}
else
{
return OS_EIO;
}
__exit:
os_mutex_unlock(device->bus->lock);
return result;
}分步详解:与 4.8 完全对应,只是第二步/第三步都是发送。第一步加锁配置 owner;第二步发第一段(cs_take=1 拉低 CS);第三步发第二段(cs_release=1 拉高 CS),两段之间 CS 保持低电平。适用于"写命令 + 写数据"连续两段发送。
条件走向总结
os_spi_send_then_send() 条件走向总结:
├── os_mutex_lock
│ ├── 加锁失败 → 返回 OS_EIO
│ └── 加锁成功 → 继续
├── owner != device → ops->configure(失败 → result=OS_EIO, goto __exit)
├── 发送第一段:构造消息(cs_take=1, cs_release=0) → ops->xfer
│ ├── 失败 → result=OS_EIO, goto __exit
│ └── 成功 → 继续
├── 发送第二段:构造消息(cs_take=0, cs_release=1) → ops->xfer
│ ├── 失败 → result=OS_EIO, goto __exit
│ └── 成功 → result = OS_SUCCESS
├── __exit: os_mutex_unlock
└── 返回 result4.10 内联辅助函数(os_spi_recv/send/sendrecv8/sendrecv16/message_append)
这些函数定义在 drivers/spi/spi.h 中,是 OS_INLINE 内联函数,封装常用操作。
完整源码
OS_INLINE os_size_t os_spi_recv(struct os_spi_device *device, void *recv_buf, os_size_t length)
{
return os_spi_transfer(device, OS_NULL, recv_buf, length);
}
OS_INLINE os_size_t os_spi_send(struct os_spi_device *device, const void *send_buf, os_size_t length)
{
return os_spi_transfer(device, send_buf, OS_NULL, length);
}
OS_INLINE uint8_t os_spi_sendrecv8(struct os_spi_device *device, uint8_t data)
{
uint8_t value;
os_spi_send_then_recv(device, &data, 1, &value, 1);
return value;
}
OS_INLINE uint16_t os_spi_sendrecv16(struct os_spi_device *device, uint16_t data)
{
uint16_t value;
os_spi_send_then_recv(device, &data, 2, &value, 2);
return value;
}
OS_INLINE void os_spi_message_append(struct os_spi_message *list, struct os_spi_message *message)
{
OS_ASSERT(list != OS_NULL);
if (message == OS_NULL)
return; /* not append */
while (list->next != OS_NULL)
{
list = list->next;
}
list->next = message;
message->next = OS_NULL;
}| 函数 | 说明 |
|---|---|
os_spi_recv | 仅接收:os_spi_transfer(device, OS_NULL, recv_buf, length) |
os_spi_send | 仅发送:os_spi_transfer(device, send_buf, OS_NULL, length) |
os_spi_sendrecv8 | 收发单字节(内部用 os_spi_send_then_recv) |
os_spi_sendrecv16 | 收发双字节(内部用 os_spi_send_then_recv) |
os_spi_message_append | 把 message 追加到 list 链表尾部 |
分步详解(os_spi_message_append)
第一步:断言链表头非空(无条件执行)
OS_ASSERT(list != OS_NULL);
if (message == OS_NULL)
return; /* not append */list 必须非空(作为链表头);message 为空则不追加(安全返回)。
第二步:遍历到链表尾(循环条件 list->next != OS_NULL)
while (list->next != OS_NULL)
{
list = list->next;
}从链表头沿 next 走到尾节点(next 为空的节点)。
第三步:追加并收尾(无条件执行)
list->next = message;
message->next = OS_NULL;把尾节点的 next 指向新 message,并把新节点的 next 置空(成为新的尾)。这样链表不断增长。
五、SPI 设备框架层封装
本节讲解 drivers/spi/spi_dev.c 中的框架层设备封装函数,它们把 SPI 总线/设备接入 5.1 设备框架。来源:drivers/spi/spi_dev.c。
5.1 总线设备初始化与框架层操作(os_spi_bus_device_init / spi_bus_ops)
函数签名
os_err_t os_spi_bus_device_init(struct os_spi_bus *bus, const char *name)os_spi_bus_register(4.1)内部调用它把总线注册进设备框架。
完整源码
os_err_t os_spi_bus_device_init(struct os_spi_bus *bus, const char *name)
{
struct os_device *device;
OS_ASSERT(bus != OS_NULL);
device = &bus->parent;
/* Set device type */
device->type = OS_DEVICE_TYPE_SPIBUS;
/* Initialize device interface */
device->ops = &spi_bus_ops;
/* Register to device manager */
return os_device_register(device, name);
}框架层操作表 spi_bus_ops:
/* clang-format off */
const static struct os_device_ops spi_bus_ops =
{
.read = _spi_bus_device_read,
.write = _spi_bus_device_write,
.control = _spi_bus_device_control
};
/* clang-format on */分步详解
第一步:配置父设备类型与 ops(无条件执行)
device = &bus->parent;
/* Set device type */
device->type = OS_DEVICE_TYPE_SPIBUS;
/* Initialize device interface */
device->ops = &spi_bus_ops;把 bus->parent 的类型设为 OS_DEVICE_TYPE_SPIBUS,挂上 spi_bus_ops(含 _spi_bus_device_read/write/control)。
第二步:注册到设备管理器(无条件执行)
return os_device_register(device, name);用 os_device_register 注册。
总线框架层读写函数 _spi_bus_device_read/_spi_bus_device_write:
static os_ssize_t _spi_bus_device_read(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size)
{
struct os_spi_bus *bus;
bus = (struct os_spi_bus *)dev;
OS_ASSERT(bus != OS_NULL);
OS_ASSERT(bus->owner != OS_NULL);
return os_spi_transfer(bus->owner, OS_NULL, buffer, size);
}
static os_ssize_t _spi_bus_device_write(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size)
{
struct os_spi_bus *bus;
bus = (struct os_spi_bus *)dev;
OS_ASSERT(bus != OS_NULL);
OS_ASSERT(bus->owner != OS_NULL);
return os_spi_transfer(bus->owner, buffer, OS_NULL, size);
}关键理解:通过总线设备(os_device_open_s("spi0"))的 read/write 会转发给当前 owner 设备(bus->owner)。_spi_bus_device_control 当前是空实现(TODO),预留控制命令扩展。
5.2 从设备初始化与框架层操作(os_spidev_device_init / spi_device_ops)
函数签名
os_err_t os_spidev_device_init(struct os_spi_device *dev, const char *name)os_spi_bus_attach_device(4.2)内部调用它注册从设备。
完整源码
os_err_t os_spidev_device_init(struct os_spi_device *dev, const char *name)
{
struct os_device *device;
OS_ASSERT(dev != OS_NULL);
device = &(dev->parent);
/* Set device type */
device->type = OS_DEVICE_TYPE_SPIDEVICE;
device->ops = &spi_device_ops;
/* Register to device manager */
return os_device_register(device, name);
}框架层操作表 spi_device_ops:
/* clang-format off */
const static struct os_device_ops spi_device_ops =
{
.read = _spidev_device_read,
.write = _spidev_device_write,
.control = _spidev_device_control
};
/* clang-format on */从设备框架层读写/控制函数:
static os_ssize_t _spidev_device_read(os_device_t *dev, os_off_t pos, void *buffer, os_size_t size)
{
struct os_spi_device *device;
device = (struct os_spi_device *)dev;
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
return os_spi_transfer(device, OS_NULL, buffer, size);
}
static os_ssize_t _spidev_device_write(os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size)
{
struct os_spi_device *device;
device = (struct os_spi_device *)dev;
OS_ASSERT(device != OS_NULL);
OS_ASSERT(device->bus != OS_NULL);
return os_spi_transfer(device, buffer, OS_NULL, size);
}
static os_err_t _spidev_device_control(os_device_t *dev, int cmd, void *args)
{
os_err_t ret = OS_SUCCESS;
struct os_spi_message *message;
struct os_spi_device *device;
device = (struct os_spi_device *)dev;
switch (cmd)
{
case OS_SPI_DEVICE_CTRL_RW:
message = (struct os_spi_message *)args;
ret = os_spi_transfer(device, message->send_buf, message->recv_buf, message->length);
break;
case 1:
break;
default:
break;
}
return ret;
}分步详解(_spidev_device_control)
第一步:按命令分发(条件 cmd 的值)
switch (cmd)
{
case OS_SPI_DEVICE_CTRL_RW:
message = (struct os_spi_message *)args;
ret = os_spi_transfer(device, message->send_buf, message->recv_buf, message->length);
break;
case 1:
break;
default:
break;
}
return ret;OS_SPI_DEVICE_CTRL_RW(0x0C00,见 3.5 数值演算)从 args 取出 os_spi_message,调用 os_spi_transfer 执行一次读写。其余命令默认返回 OS_SUCCESS。
关键理解:从设备的
read/write就是os_spi_transfer(device, send_buf, recv_buf, length)的封装;control支持通过OS_SPI_DEVICE_CTRL_RW命令用os_device_control发起传输。这使得应用层既可用os_device_read_block/write_block,也可用os_device_control操作 SPI 从设备。
六、QSPI 扩展 API 详解
本节讲解 drivers/spi/qspi_core.c 中的 QSPI 函数。来源:drivers/spi/qspi_core.c。
6.1 os_qspi_configure —— 配置 QSPI 设备
函数签名
os_err_t os_qspi_configure(struct os_qspi_device *device, struct os_qspi_configuration *cfg)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_qspi_device * | QSPI 设备指针 |
cfg | struct os_qspi_configuration * | QSPI 配置 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 配置成功 |
完整源码
os_err_t os_qspi_configure(struct os_qspi_device *device, struct os_qspi_configuration *cfg)
{
OS_ASSERT(device != OS_NULL);
OS_ASSERT(cfg != OS_NULL);
struct os_qspi_device *qspi_device = (struct os_qspi_device *)device;
os_err_t result = OS_SUCCESS;
/* Copy configuration items */
qspi_device->config.parent.mode = cfg->parent.mode;
qspi_device->config.parent.max_hz = cfg->parent.max_hz;
qspi_device->config.parent.data_width = cfg->parent.data_width;
qspi_device->config.parent.reserved = cfg->parent.reserved;
qspi_device->config.medium_size = cfg->medium_size;
qspi_device->config.ddr_mode = cfg->ddr_mode;
qspi_device->config.qspi_dl_width = cfg->qspi_dl_width;
result = os_spi_configure(&device->parent, &cfg->parent);
return result;
}分步详解
第一步:保存 QSPI 配置(无条件执行)
struct os_qspi_device *qspi_device = (struct os_qspi_device *)device;
os_err_t result = OS_SUCCESS;
/* Copy configuration items */
qspi_device->config.parent.mode = cfg->parent.mode;
qspi_device->config.parent.max_hz = cfg->parent.max_hz;
qspi_device->config.parent.data_width = cfg->parent.data_width;
qspi_device->config.parent.reserved = cfg->parent.reserved;
qspi_device->config.medium_size = cfg->medium_size;
qspi_device->config.ddr_mode = cfg->ddr_mode;
qspi_device->config.qspi_dl_width = cfg->qspi_dl_width;把 cfg 的 SPI 配置字段与 QSPI 特有字段(medium_size/ddr_mode/qspi_dl_width)全部拷贝到 qspi_device->config。
第二步:复用 SPI 配置(无条件执行)
result = os_spi_configure(&device->parent, &cfg->parent);
return result;调用 os_spi_configure(&device->parent, &cfg->parent) 复用标准 SPI 配置逻辑(保存模式/数据宽度/最大频率,必要时应用到硬件)。
6.2 os_qspi_bus_register —— 注册 QSPI 总线
函数签名
os_err_t os_qspi_bus_register(struct os_spi_bus *bus, const char *name, const struct os_spi_ops *ops)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
bus | struct os_spi_bus * | 总线指针 |
name | const char * | 总线名称 |
ops | const struct os_spi_ops * | 操作函数表 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 注册成功 |
完整源码
os_err_t os_qspi_bus_register(struct os_spi_bus *bus, const char *name, const struct os_spi_ops *ops)
{
os_err_t result = OS_SUCCESS;
result = os_spi_bus_register(bus, name, ops);
if (result == OS_SUCCESS)
{
/* Set SPI bus to qspi modes */
bus->mode = OS_SPI_BUS_MODE_QSPI;
}
return result;
}分步详解
第一步:复用 SPI 总线注册(无条件执行)
result = os_spi_bus_register(bus, name, ops);先调用 os_spi_bus_register(4.1)完成基础注册(此时 bus->mode = OS_SPI_BUS_MODE_SPI)。
第二步:切换为 QSPI 模式(条件注册成功)
if (result == OS_SUCCESS)
{
/* Set SPI bus to qspi modes */
bus->mode = OS_SPI_BUS_MODE_QSPI;
}
return result;若注册成功,把 bus->mode 改为 OS_SPI_BUS_MODE_QSPI。
6.3 os_qspi_transfer_message —— QSPI 消息传输
函数签名
os_size_t os_qspi_transfer_message(struct os_qspi_device *device, struct os_qspi_message *message)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_qspi_device * | QSPI 设备指针 |
message | struct os_qspi_message * | QSPI 消息指针 |
| 返回值 | 说明 |
|---|---|
> 0 | 成功传输的字节数 |
0 | 失败(加锁失败或 xfer 失败) |
完整源码
os_size_t os_qspi_transfer_message(struct os_qspi_device *device, struct os_qspi_message *message)
{
os_err_t result;
OS_ASSERT(device != OS_NULL);
OS_ASSERT(message != OS_NULL);
result = os_mutex_lock((device->parent.bus->lock), OS_WAIT_FOREVER);
if (result != OS_SUCCESS)
{
//os_set_errno(OS_BUSY);
return 0;
}
/* Reset errno */
//os_set_errno(OS_SUCCESS);
/* Configure SPI bus */
if (device->parent.bus->owner != &device->parent)
{
/* Not the same owner as current, re-configure SPI bus */
result = device->parent.bus->ops->configure(&device->parent, &device->parent.config);
if (result == OS_SUCCESS)
{
/* Set SPI bus owner */
device->parent.bus->owner = &device->parent;
}
else
{
/* Configure SPI bus failed */
//os_set_errno(OS_EIO);
goto __exit;
}
}
/* Transmit each SPI message */
result = device->parent.bus->ops->xfer(&device->parent, &message->parent);
if (result == 0)
{
//os_set_errno(OS_EIO);
}
__exit:
/* Release bus lock */
os_mutex_unlock((device->parent.bus->lock));
return result;
}分步详解
第一步:加总线锁(条件 os_mutex_lock 成功)
result = os_mutex_lock((device->parent.bus->lock), OS_WAIT_FOREVER);
if (result != OS_SUCCESS)
{
return 0;
}通过 device->parent.bus->lock 获取总线锁,失败返回 0。
第二步:owner 检查并配置(条件 owner != &device->parent)
if (device->parent.bus->owner != &device->parent)
{
result = device->parent.bus->ops->configure(&device->parent, &device->parent.config);
if (result == OS_SUCCESS)
{
device->parent.bus->owner = &device->parent;
}
else
{
goto __exit;
}
}注意用 &device->parent(即 struct os_spi_device *)作为 owner——QSPI 设备把其内嵌的 SPI 设备 parent 作为总线 owner。配置失败跳转 __exit。
第三步:把 QSPI 消息当 SPI 消息传输(无条件执行)
result = device->parent.bus->ops->xfer(&device->parent, &message->parent);
if (result == 0)
{
//os_set_errno(OS_EIO);
}关键点:&message->parent 是 os_qspi_message 内嵌的 os_spi_message,直接传给 ops->xfer。QSPI 的阶段信息(指令/地址/空周期/数据线数)由 BSP 驱动的 xfer 从 message->parent 之后的 QSPI 扩展字段读取(通过类型转换)。result == 0 表示传输失败。
第四步:解锁并返回(无条件执行)
__exit:
/* Release bus lock */
os_mutex_unlock((device->parent.bus->lock));
return result;条件走向总结
os_qspi_transfer_message() 条件走向总结:
├── os_mutex_lock(parent.bus->lock)
│ ├── 加锁失败 → 返回 0
│ └── 加锁成功 → 继续
├── owner != &device->parent → ops->configure(&parent, &parent.config)
│ ├── 成功 → owner = &device->parent
│ └── 失败 → goto __exit
├── result = ops->xfer(&device->parent, &message->parent)
├── result == 0(失败)→ 标记 EIO
├── __exit: os_mutex_unlock
└── 返回 result6.4 os_qspi_send_then_recv / os_qspi_send —— QSPI 高层接口
这两个函数自动解析发送缓冲的前若干字节为"指令 + 地址",并构建 os_qspi_message 完成一次 QSPI 传输。它们简化了 QSPI 的使用——调用者只需把 [指令 | 地址 | 数据] 打包进一个发送缓冲。
函数签名
os_err_t os_qspi_send_then_recv(struct os_qspi_device *device,
const void *send_buf,
os_size_t send_length,
void *recv_buf,
os_size_t recv_length)
os_err_t os_qspi_send(struct os_qspi_device *device, const void *send_buf, os_size_t length)参数 / 返回值说明(os_qspi_send_then_recv)
| 参数 | 类型 | 说明 |
|---|---|---|
device | struct os_qspi_device * | QSPI 设备指针 |
send_buf | const void * | 发送缓冲(首字节为指令,其后为地址) |
send_length | os_size_t | 发送长度(指令+地址+可能的空周期字节) |
recv_buf | void * | 接收缓冲区 |
recv_length | os_size_t | 接收长度 |
| 返回值 | 说明 |
|---|---|
recv_length | 传输成功(返回期望接收长度) |
OS_FAILURE | 发送长度不足(无法解析地址) |
OS_EIO | 传输失败 |
完整源码(os_qspi_send_then_recv)
os_err_t os_qspi_send_then_recv(struct os_qspi_device *device,
const void *send_buf,
os_size_t send_length,
void *recv_buf,
os_size_t recv_length)
{
OS_ASSERT(send_buf);
OS_ASSERT(recv_buf);
OS_ASSERT(send_length != 0);
struct os_qspi_message message;
unsigned char *ptr = (unsigned char *)send_buf;
os_size_t count = 0;
os_err_t result = 0;
message.instruction.content = ptr[0];
message.instruction.qspi_lines = 1;
count++;
/* Get address */
if (send_length > 1)
{
if (device->config.medium_size > 0x1000000 && send_length >= 5)
{
/* Medium size greater than 16Mb, address size is 4 Byte */
message.address.content = (ptr[1] << 24) | (ptr[2] << 16) | (ptr[3] << 8) | (ptr[4]);
message.address.size = 32;
count += 4;
}
else if (send_length >= 4)
{
/* Address size is 3 Byte */
message.address.content = (ptr[1] << 16) | (ptr[2] << 8) | (ptr[3]);
message.address.size = 24;
count += 3;
}
else
{
return OS_FAILURE;
}
message.address.qspi_lines = 1;
}
else
{
/* No address stage */
message.address.content = 0;
message.address.qspi_lines = 0;
message.address.size = 0;
}
message.alternate_bytes.content = 0;
message.alternate_bytes.size = 0;
message.alternate_bytes.qspi_lines = 0;
/* Set dummy cycles */
if (count != send_length)
{
message.dummy_cycles = (send_length - count) * 8;
}
else
{
message.dummy_cycles = 0;
}
/* Set recv buf and recv size */
message.parent.recv_buf = recv_buf;
message.parent.send_buf = OS_NULL;
message.parent.length = recv_length;
message.parent.cs_take = 1;
message.parent.cs_release = 1;
message.qspi_data_lines = 1;
result = os_qspi_transfer_message(device, &message);
if (result == 0)
{
result = OS_EIO;
}
else
{
result = recv_length;
}
return result;
}分步详解(os_qspi_send_then_recv)
第一步:解析指令字节(无条件执行)
OS_ASSERT(send_buf);
OS_ASSERT(recv_buf);
OS_ASSERT(send_length != 0);
struct os_qspi_message message;
unsigned char *ptr = (unsigned char *)send_buf;
os_size_t count = 0;
os_err_t result = 0;
message.instruction.content = ptr[0];
message.instruction.qspi_lines = 1;
count++;取发送缓冲首字节作为指令(ptr[0]),指令阶段用 1 线;count 累计已消费字节(初始 1)。
第二步:解析地址(条件 send_length > 1)
if (send_length > 1)
{
if (device->config.medium_size > 0x1000000 && send_length >= 5)
{
message.address.content = (ptr[1] << 24) | (ptr[2] << 16) | (ptr[3] << 8) | (ptr[4]);
message.address.size = 32;
count += 4;
}
else if (send_length >= 4)
{
message.address.content = (ptr[1] << 16) | (ptr[2] << 8) | (ptr[3]);
message.address.size = 24;
count += 3;
}
else
{
return OS_FAILURE;
}
message.address.qspi_lines = 1;
}
else
{
/* No address stage */
message.address.content = 0;
message.address.qspi_lines = 0;
message.address.size = 0;
}地址字节数由 medium_size 决定:若介质大小 > 16Mb(0x1000000)且发送长度够 5 字节,用 4 字节地址(32 位);否则用 3 字节地址(24 位);发送长度不足 4 字节则返回 OS_FAILURE。count 相应累加 4 或 3。
数值演算(地址解析):设
send_buf = {0x6B, 0x00, 0x12, 0x34, 0x56},medium_size = 0x4000000(> 0x1000000):
计算项 代入计算 结果 ptr[1] << 240x00 << 240x00000000ptr[2] << 160x12 << 160x00120000ptr[3] << 80x34 << 80x00003400ptr[4]0x560x00000056或运算组合 0x00000000 | 0x00120000 | 0x00003400 | 0x000000560x00123456address.size32
第三步:计算空周期(条件 count != send_length)
if (count != send_length)
{
message.dummy_cycles = (send_length - count) * 8;
}
else
{
message.dummy_cycles = 0;
}若发送缓冲里除了指令和地址还有额外字节(count != send_length),把这些字节当作空周期字节,每个字节 8 个时钟周期((send_length - count) * 8)。
第四步:设置接收消息并传输(无条件执行)
message.parent.recv_buf = recv_buf;
message.parent.send_buf = OS_NULL;
message.parent.length = recv_length;
message.parent.cs_take = 1;
message.parent.cs_release = 1;
message.qspi_data_lines = 1;
result = os_qspi_transfer_message(device, &message);
if (result == 0)
{
result = OS_EIO;
}
else
{
result = recv_length;
}
return result;设置接收缓冲/长度、cs_take=1、cs_release=1、数据阶段 1 线,调用 os_qspi_transfer_message 传输。成功返回 recv_length,失败返回 OS_EIO。
os_qspi_send 与 os_qspi_send_then_recv 结构类似,只是把解析出的地址后剩余字节作为发送数据(parent.send_buf = ptr + count、parent.length = length - count),且 dummy_cycles = 0。若 length - count > 0 则 qspi_data_lines = 1,否则为 0。成功返回 length。
七、使用示例
以下示例基于项目头文件
drivers/spi/spi.h的接口,均含返回值检查。
示例 1:SPI Flash 读取 ID
#include <spi.h>
#include <device.h>
#include <os_task.h>
#define SPI_BUS_NAME "spi0"
#define SPI_DEV_NAME "spi_flash"
#define FLASH_CS_PIN 10
void read_flash_id(void)
{
os_device_t *dev;
uint8_t cmd = 0x9F; /* 读 JEDEC ID 命令 */
uint8_t id[3] = {0}; /* 存储 ID */
/* 1. 打开 SPI 从设备 */
dev = os_device_open_s(SPI_DEV_NAME);
if (dev == OS_NULL)
{
os_kprintf("Failed to open SPI device\r\n");
return;
}
/* 2. 获取 SPI 设备结构体 */
struct os_spi_device *spi_dev = SPI_DEVICE(dev);
/* 3. 先发送命令,再读取 3 字节 ID */
os_err_t ret = os_spi_send_then_recv(spi_dev, &cmd, 1, id, 3);
if (ret != OS_SUCCESS)
{
os_kprintf("SPI read ID failed: %d\r\n", ret);
os_device_close(dev);
return;
}
os_kprintf("Flash JEDEC ID: %02X %02X %02X\r\n", id[0], id[1], id[2]);
os_device_close(dev);
}实际效果:打开 spi_flash 后,先发 1 字节读 ID 命令 0x9F,再读 3 字节 ID,成功则打印 JEDEC ID(如 EF 40 18)。os_spi_send_then_recv 返回 OS_SUCCESS 表示成功。
示例 2:SPI 全双工传输
#include <spi.h>
#include <device.h>
void spi_full_duplex_demo(void)
{
os_device_t *dev = os_device_open_s("spi_dev0");
if (dev == OS_NULL) return;
struct os_spi_device *spi_dev = SPI_DEVICE(dev);
uint8_t tx_buf[4] = {0xAA, 0xBB, 0xCC, 0xDD};
uint8_t rx_buf[4] = {0};
/* 全双工传输 4 字节,返回实际传输字节数 */
os_size_t ret = os_spi_transfer(spi_dev, tx_buf, rx_buf, 4);
if (ret != 4)
{
os_kprintf("SPI transfer failed: %d\r\n", ret);
os_device_close(dev);
return;
}
os_kprintf("SPI transfer done. RX: %02X %02X %02X %02X\r\n",
rx_buf[0], rx_buf[1], rx_buf[2], rx_buf[3]);
os_device_close(dev);
}实际效果:os_spi_transfer 全双工同时发送 tx_buf 并接收 rx_buf,返回传输字节数。若返回不等于 4 则失败。os_spi_transfer 内部自动拉低/拉高 CS(cs_take=1、cs_release=1),无需手动 os_spi_take/release。
示例 3:SPI 消息链表传输
#include <spi.h>
#include <device.h>
void spi_message_list_demo(void)
{
os_device_t *dev = os_device_open_s("spi_dev0");
if (dev == OS_NULL) return;
struct os_spi_device *spi_dev = SPI_DEVICE(dev);
uint8_t cmd = 0x03; /* 读命令 */
uint8_t addr[3] = {0x00, 0x00, 0x00}; /* 24 位地址 */
uint8_t data[32] = {0}; /* 数据缓冲区 */
/* 构建消息链表:第一条拉低 CS,最后一条拉高 CS */
struct os_spi_message msg1 = {
.send_buf = &cmd,
.recv_buf = OS_NULL,
.length = 1,
.cs_take = 1, /* 获取片选 */
.cs_release = 0,
.next = OS_NULL,
};
struct os_spi_message msg2 = {
.send_buf = addr,
.recv_buf = OS_NULL,
.length = 3,
.cs_take = 0,
.cs_release = 0,
.next = OS_NULL,
};
struct os_spi_message msg3 = {
.send_buf = OS_NULL,
.recv_buf = data,
.length = 32,
.cs_take = 0,
.cs_release = 1, /* 释放片选 */
.next = OS_NULL,
};
/* 连接为链表 */
os_spi_message_append(&msg1, &msg2);
os_spi_message_append(&msg1, &msg3);
/* 传输消息链表 */
struct os_spi_message *ret = os_spi_transfer_message(spi_dev, &msg1);
if (ret != OS_NULL)
{
os_kprintf("SPI message transfer failed\r\n");
os_device_close(dev);
return;
}
os_kprintf("Read 32 bytes from SPI Flash\r\n");
os_device_close(dev);
}实际效果:用 os_spi_message_append 把三条消息串成链表:msg1 发命令(cs_take=1 拉低 CS),msg2 发地址(CS 保持),msg3 读 32 字节(cs_release=1 拉高 CS)。os_spi_transfer_message 返回 OS_NULL 表示全部成功。
示例 4:注册 SPI 总线与设备
#include <spi.h>
#include <device.h>
/* 底层 SPI 操作实现 */
static os_err_t my_spi_configure(struct os_spi_device *device, struct os_spi_configuration *cfg)
{
/* 配置硬件 SPI 控制器(模式/数据宽度/速率),例如写 SPI 寄存器 */
/* 读取 cfg->mode 判断 CPOL/CPHA/主从,cfg->max_hz 设速率 */
return OS_SUCCESS;
}
static uint32_t my_spi_xfer(struct os_spi_device *device, struct os_spi_message *message)
{
/* 执行 SPI 传输:根据 message->cs_take/cs_release 拉低/拉高 CS */
/* 循环收发 message->length 个字节 */
return message->length;
}
static const struct os_spi_ops my_spi_ops =
{
.configure = my_spi_configure,
.xfer = my_spi_xfer,
};
static struct os_spi_bus my_spi_bus;
static struct os_spi_device my_spi_dev;
void my_spi_init(void)
{
/* 1. 注册 SPI 总线 */
os_err_t ret = os_spi_bus_register(&my_spi_bus, "spi0", &my_spi_ops);
if (ret != OS_SUCCESS)
{
os_kprintf("SPI bus register failed: %d\r\n", ret);
return;
}
/* 2. 挂载从设备 */
ret = os_spi_bus_attach_device(&my_spi_dev, "spi_flash", "spi0", 10);
if (ret != OS_SUCCESS)
{
os_kprintf("SPI device attach failed: %d\r\n", ret);
return;
}
/* 3. 配置从设备(挂载时 config 被清零,必须重新配置) */
struct os_spi_configuration cfg;
cfg.mode = OS_SPI_MODE_0;
cfg.data_width = 8;
cfg.max_hz = 10000000; /* 10 MHz */
ret = os_spi_configure(&my_spi_dev, &cfg);
if (ret != OS_SUCCESS)
{
os_kprintf("SPI configure failed: %d\r\n", ret);
return;
}
os_kprintf("SPI bus and device registered\r\n");
}实际效果:注册总线 "spi0" → 挂载从设备 "spi_flash"(绑定 CS=10)→ 配置为 MODE 0、8 位、10 MHz。注意挂载后 config 被清零,必须调用 os_spi_configure 重新配置(见 4.2 关键理解)。
示例 5:使用 QSPI 读取 Flash
#include <spi.h>
#include <device.h>
void qspi_read_flash_demo(void)
{
os_device_t *dev;
uint8_t rx_data[256];
uint8_t cmd_addr[4];
/* 1. 打开 QSPI 设备(QSPI 设备结构体内嵌在 os_device 中) */
dev = os_device_open_s("qspi_flash");
if (dev == OS_NULL) return;
struct os_qspi_device *qspi_dev = (struct os_qspi_device *)dev;
/* 2. 用 send_then_recv 简化:首字节为指令,其后为地址 */
cmd_addr[0] = 0x6B; /* Fast Read Quad I/O */
cmd_addr[1] = 0x00; /* 24 位地址高字节 */
cmd_addr[2] = 0x00;
cmd_addr[3] = 0x00;
os_err_t ret = os_qspi_send_then_recv(qspi_dev, cmd_addr, 4, rx_data, 256);
if (ret != 256)
{
os_kprintf("QSPI read failed: %d\r\n", ret);
os_device_close(dev);
return;
}
os_kprintf("QSPI read 256 bytes done\r\n");
os_device_close(dev);
}实际效果:用 os_qspi_send_then_recv 简化 QSPI 读:发送缓冲首字节 0x6B 作指令,后 3 字节作 24 位地址,然后读 256 字节。该函数自动解析指令/地址并调用 os_qspi_transfer_message。
示例 6:手动构造 QSPI 消息(精细控制各阶段)
#include <spi.h>
#include <device.h>
void qspi_manual_message_demo(void)
{
os_device_t *dev = os_device_open_s("qspi_flash");
if (dev == OS_NULL) return;
struct os_qspi_device *qspi_dev = (struct os_qspi_device *)dev;
uint8_t rx_data[256] = {0};
/* 手动构造 QSPI 消息,精细控制指令/地址/空周期/数据线数 */
struct os_qspi_message msg = {0};
msg.instruction.content = 0x6B; /* Fast Read Quad I/O */
msg.instruction.qspi_lines = 1; /* 指令阶段使用 1 线 */
msg.address.content = 0x000000;
msg.address.size = 24;
msg.address.qspi_lines = 4; /* 地址阶段使用 4 线 */
msg.dummy_cycles = 8; /* 8 个空周期 */
msg.qspi_data_lines = 4; /* 数据阶段使用 4 线 */
msg.parent.send_buf = OS_NULL;
msg.parent.recv_buf = rx_data;
msg.parent.length = 256;
msg.parent.cs_take = 1;
msg.parent.cs_release = 1;
os_size_t ret = os_qspi_transfer_message(qspi_dev, &msg);
if (ret == 0)
{
os_kprintf("QSPI transfer failed\r\n");
os_device_close(dev);
return;
}
os_kprintf("QSPI transfer done, %d bytes\r\n", ret);
os_device_close(dev);
}实际效果:手动填充 os_qspi_message 的每个阶段(指令 1 线、地址 4 线、8 空周期、数据 4 线),调用 os_qspi_transfer_message。BSP 驱动从 message->parent 之后的 QSPI 扩展字段读取这些阶段参数完成多线传输。返回 0 表示失败。
八、更换 MCU(SPI 驱动移植)—— 全局视角
本节从全局视角讲解:当你的项目要换一颗新的 MCU 时,OneOS 的 SPI 设备驱动要做什么才能在新平台上跑起来。核心结论与 5.2/5.3/5.4 一致:应用层与框架层完全不用动,只需要新写/移植 BSP 层的 SPI 驱动。
在动手移植之前,先认识 BSP 层驱动文件到底承担了什么职责——这是理解移植步骤的前提。
8.1 认识 BSP 层驱动:drv_spi.c
BSP 层的 SPI 驱动主要由 drv_spi.c 承担,实现"struct os_spi_ops + 片选控制 + 注册"三件事:
| 文件 | 作用 |
|---|---|
drv_spi.c | 实现 struct os_spi_ops(configure/xfer)+ 片选 GPIO 控制 + probe 注册 |
(部分厂商)drv_spi.h | 声明 SPI 总线实例的引脚/时钟配置宏 |
以 STM32 为例(drivers/hal/st/drivers/drv_spi.c),它做了这几件事:
(1)定义"SPI 总线实例"结构体 struct stm32_spi
struct stm32_spi
{
struct os_spi_bus spi_bus;
SPI_HandleTypeDef *hspi;
struct stm32_spi_config *config;
struct
{
DMA_HandleTypeDef handle_rx;
DMA_HandleTypeDef handle_tx;
} dma;
uint8_t spi_dma_flag;
os_list_node_t list;
};spi_bus:内嵌的struct os_spi_bus,是注册进设备框架的"公共部分";hspi:指向 STM32 HAL 的 SPI 句柄(对应具体 SPI 外设);dma/spi_dma_flag:DMA 发送/接收句柄与标志(SPI_USING_RX_DMA_FLAG/SPI_USING_TX_DMA_FLAG);list:把多个 SPI 实例挂到全局链表stm32_spi_list。
(2)实现 struct os_spi_ops 的两个函数并聚合
static const struct os_spi_ops stm32_spi_ops = {
.configure = spi_configure,
.xfer = spixfer,
};spi_configure 解析 cfg->mode(用 OS_SPI_SLAVE/OS_SPI_3WIRE/OS_SPI_CPHA/OS_SPI_CPOL 等位判断)设置 HAL 句柄的 Init 参数并 HAL_SPI_Init;spixfer 根据 message->cs_take/cs_release 拉低/拉高 device->cs_pin,再调用 HAL_SPI_TransmitReceive 完成全双工传输。
(3)写 probe 函数并注册
static int stm32_spi_probe(const os_driver_info_t *drv, const os_device_info_t *dev)
{
os_err_t result = 0;
os_ubase_t level;
struct stm32_spi *st_spi = os_calloc(1, sizeof(struct stm32_spi));
OS_ASSERT(st_spi);
st_spi->hspi = (SPI_HandleTypeDef *)dev->info;
struct os_spi_bus *spi_bus = &st_spi->spi_bus;
os_spin_lock_irqsave(&gs_device_lock, &level);
os_list_add_tail(&stm32_spi_list, &st_spi->list);
os_spin_unlock_irqrestore(&gs_device_lock, level);
result = os_spi_bus_register(spi_bus, dev->name, &stm32_spi_ops);
OS_ASSERT(result == OS_SUCCESS);
LOG_D(DBG_TAG, "%s bus init done", dev->name);
return result;
}
OS_DRIVER_INFO stm32_spi_driver = {
.name = "SPI_HandleTypeDef",
.probe = stm32_spi_probe,
};
OS_DRIVER_DEFINE(stm32_spi_driver, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_MIDDLE);probe 里做三件事:①从 dev->info 取 SPI 硬件句柄;②把实例挂入全局链表(供中断/DMA 回调查找);③调用 os_spi_bus_register(见 4.1)注册总线。
注意:STM32 的
drv_spi.c通过os_spi_bus_register只注册了总线;从设备由 BSP 的板级文件(或drv_spi.h的宏)用os_hw_spi_device_attach/os_spi_bus_attach_device挂载(见 4.2)。
(4)不同 MCU 的同类文件
| 厂商 | 路径 | 区别 |
|---|---|---|
| ST | drivers/hal/st/drivers/drv_spi.c | 用 STM32 HAL(HAL_SPI_TransmitReceive*) |
| GD | drivers/hal/gd/drivers/f403/drv_spi.c | 用 GD32 库 |
| NRF | drivers/hal/nrf/drivers/nrf52/drv_spi.c | 用 nRF5 SDK 的 SPIM 模块 |
(5)"根据所选 MCU 决定"到底是怎么决定的?—— 构建系统的三层选择机制
与 5.2/5.3/5.4 的机制完全一致,只是把 drv_spi.c 换成对应文件:
第一层:选择"厂商宏"(templates/configs/Kconfig 的 choice)→ 选 STM32 定义 MANUFACTOR_STM32。
第二层:SRC_HAL 决定加载哪家目录(templates/configs/SConscript 的 inq_table['MANUFACTOR_STM32']='st')。
第三层:weave.yaml 决定哪些源文件被编译(ST 的 drivers/hal/st/drivers/weave.yaml):
group_name: hal/drivers
# 依赖宏控:只有定义了 MANUFACTOR_STM32 才编译整个组
depend_macro:
- MANUFACTOR_STM32
source_file:
- drv_spi.c ? {is_define(['OS_USING_SPI', 'HAL_SPI_MODULE_ENABLED'])}drv_spi.c ? {is_define(['OS_USING_SPI', 'HAL_SPI_MODULE_ENABLED'])} 意思是:只有当同时定义了 OS_USING_SPI(使能 SPI 设备组件)和 HAL_SPI_MODULE_ENABLED(使能 SPI 硬件模块)时,才编译 drv_spi.c。
这两个宏到底写在哪里?
is_define 里的两个宏来自两个不同的位置,分别控制"OneOS 框架组件"与"厂商 HAL 硬件库":
| 宏 | 控制什么 | 定义在哪里 | 谁来决定 |
|---|---|---|---|
OS_USING_SPI | OneOS 的 SPI 设备组件(框架层 spi_core.c/spi_dev.c 是否编译) | drivers/spi/Kconfig(menuconfig 勾选)→ 写入 oneos_config.h 的 #define | Kconfig 配置 |
HAL_SPI_MODULE_ENABLED | ST 的 HAL SPI 驱动模块(stm32f1xx_hal_spi.c 是否编译) | 板级 stm32f1xx_hal_conf.h 的 #define | STM32CubeMX 生成 |
OS_USING_SPI 的来源(drivers/spi/Kconfig):
menu "SPI"
config OS_USING_SPI
bool "Using SPI Bus/Device device drivers"
default n
config OS_USING_QSPI
depends on OS_USING_SPI
select OS_SFLASH_SUPPORT
bool "Enable QSPI mode"
default n
endmenu- 在
menuconfig中勾选 "Using SPI Bus/Device device drivers" 后,构建系统把CONFIG_OS_USING_SPI=y写入oneos_config.h,编译时展开为#define OS_USING_SPI。 - 同时
drivers/spi/weave.yaml的depend_macro: [OS_USING_SPI]用它控制整个 SPI 组件目录是否编译。
HAL_SPI_MODULE_ENABLED 的来源(板级 stm32f1xx_hal_conf.h):
/*#define HAL_SMARTCARD_MODULE_ENABLED */
#define HAL_SPI_MODULE_ENABLED
#define HAL_SRAM_MODULE_ENABLED
#define HAL_TIM_MODULE_ENABLED
#define HAL_UART_MODULE_ENABLED- 这是 STM32 HAL 库的模块开关,由 STM32CubeMX 根据你在 CubeMX 里是否勾选 SPI 外设来生成(注释/取消注释)。
- 它不在 Kconfig 里,而是 CubeMX 直接写进 HAL 配置头文件。
为什么必须两个都满足:drv_spi.c 是"BSP 桥接层"——它既要调用 OneOS 框架的 os_spi_bus_register(依赖 OS_USING_SPI),又要调用 STM32 HAL 的 HAL_SPI_* 函数(依赖 HAL_SPI_MODULE_ENABLED)。两者缺一,drv_spi.c 都会因缺头文件或缺函数而编译失败。所以用 is_define 双条件兜底,避免"框架开了但硬件库没开"或反之导致的编译错误。
各厂商的条件编译并不完全相同(真实源码对比)
HAL_SPI_MODULE_ENABLED 是 STM32 专属宏(来自 CubeMX/HAL)。不同厂商的 drv_spi.c 判断的宏各不相同,这正是"每个厂商 drv_spi.c 平级实现、各自条件编译"的体现。项目里共有 27 个 drv_spi.c(apollo、beken、cmiot、csky、fm、gd、gd32v、hdsc、mm32、nationstech、nrf、st 等厂商),它们的 weave.yaml 条件各不相同:
| 厂商 | 文件 | drv_spi.c 的条件编译 | 硬件相关宏 |
|---|---|---|---|
| ST | drivers/hal/st/drivers/weave.yaml | drv_spi.c ? {is_define(['OS_USING_SPI', 'HAL_SPI_MODULE_ENABLED'])} | HAL_SPI_MODULE_ENABLED(来自 STM32CubeMX 的 stm32f1xx_hal_conf.h) |
| GD | drivers/hal/gd/drivers/f403/weave.yaml | drv_spi.c ? {is_define('OS_USING_SPI')} | 无额外硬件宏(drv_gpio.c 由 (is_define('OS_USING_PIN')) or (is_define('OS_USING_SPI')) 编译,SPI 驱动直接依赖 GPIO) |
| NRF | drivers/hal/nrf/drivers/nrf52/weave.yaml | drv_spi.c ? {is_define("OS_USING_SPI") and is_define("BSP_USING_SPI")} | BSP_USING_SPI(板级开关,非 HAL 模块宏) |
真实源码(GD 的 drivers/hal/gd/drivers/f403/weave.yaml):
group_name: hal/drivers
depend_macro:
- SERIES_GD32F403
source_file:
- drv_common.c
- drv_gpio.c ? {(is_define('OS_USING_PIN')) or (is_define('OS_USING_SPI'))}
- drv_spi.c ? {is_define('OS_USING_SPI')}真实源码(NRF 的 drivers/hal/nrf/drivers/nrf52/weave.yaml):
group_name: hal/drivers
depend_macro:
- SERIES_NRF52
source_file:
- drv_uart.c ? {is_define("OS_USING_SERIAL") and is_define("BSP_USING_UART")}
- drv_spi.c ? {is_define("OS_USING_SPI") and is_define("BSP_USING_SPI")}关键差异:
| 厂商 | 条件判断 | 含义 |
|---|---|---|
| ST | OS_USING_SPI + HAL_SPI_MODULE_ENABLED | 需同时开框架组件 + STM32CubeMX 硬件模块 |
| GD | 仅 OS_USING_SPI | 只开框架组件即可(GD 的 SPI 驱动用 drv_gpio.c 做引脚,不依赖独立 HAL 宏) |
| NRF | OS_USING_SPI + BSP_USING_SPI | 需开框架组件 + 板级 BSP_USING_SPI 开关 |
为什么写法不同:不同厂商的硬件抽象方式不同。STM32 的 HAL 需要单独的 HAL_SPI_MODULE_ENABLED 开关;GD 的 SPI 驱动依赖其 drv_gpio.c(由 OS_USING_SPI 间接触发编译);NRF 用板级 BSP_USING_* 宏控制具体外设。这套"厂商级 depend_macro + 文件级 is_define"的 weave.yaml 正是各厂商平级实现、各自条件编译的落地机制。
要点:移植到新厂商时,除了写
drv_spi.c,还必须在该厂商自己的 weave.yaml 里把drv_spi.c加入source_file并写好对应的is_define条件,否则即使选对了厂商,驱动也不会被编译进固件。
各宏到底定义在哪个文件?(具体定位)
综合上面三家的条件编译,涉及的宏定义位置汇总如下:
| 宏 | 定义文件 | 定义方式 | 示例 |
|---|---|---|---|
OS_USING_SPI | drivers/spi/Kconfig(menuconfig 勾选)→ 展开到 oneos_config.h | #define OS_USING_SPI | 见"这两个宏到底写在哪里?"小节 |
OS_USING_QSPI | drivers/spi/Kconfig | #define OS_USING_QSPI(depends on OS_USING_SPI) | config OS_USING_QSPI |
HAL_SPI_MODULE_ENABLED(ST) | 板级 board/CubeMX_Config/Inc/stm32f1xx_hal_conf.h | #define HAL_SPI_MODULE_ENABLED(CubeMX 生成) | 见"这两个宏到底写在哪里?"小节 |
BSP_USING_SPI(NRF) | oneos_config.h | #define BSP_USING_SPI(板级配置) | NRF 板级 oneos_config.h |
BSP_USING_SPI0/1/2(NRF) | oneos_config.h | #define BSP_USING_SPI0 等,控制每个 SPI 实例 | NRF 板级 oneos_config.h |
EVAL_SPI0 等引脚/时钟宏(GD) | drivers/hal/gd/drivers/f403/drv_spi.h | #define EVAL_SPI0_SCK_PIN GPIO_PIN_5 等(GPIO 引脚、RCU 时钟) | 见下方源码 |
真实源码(ST 的 board/CubeMX_Config/Inc/stm32f1xx_hal_conf.h):
/*#define HAL_SMARTCARD_MODULE_ENABLED */
#define HAL_SPI_MODULE_ENABLED
#define HAL_SRAM_MODULE_ENABLED
#define HAL_TIM_MODULE_ENABLED
#define HAL_UART_MODULE_ENABLED真实源码(NRF 的 templates/nrf52840-pca10056/oneos_config.h):
#define BSP_USING_FLASH
#define BSP_USING_RTC
#define BSP_USING_SPI
#define BSP_USING_SPI1
/* end of HAL */真实源码(GD 的 drivers/hal/gd/drivers/f403/drv_spi.h 引脚宏):
#define EVAL_SPI0 SPI0
#define EVAL_SPI0_GPIO_CLK RCU_GPIOA
#define EVAL_SPI0_CLK RCU_SPI0
#define EVAL_SPI0_GPIO_PORT GPIOA
#define EVAL_SPI0_MISO_PIN GPIO_PIN_6
#define EVAL_SPI0_MOSI_PIN GPIO_PIN_7
#define EVAL_SPI0_SCK_PIN GPIO_PIN_5
#define EVAL_SPI0_GPIO_AF_IDX GPIO_AF_5宏定义位置汇总图:
┌─────────────────────────────────────────────────────────────────────┐
│ menuconfig 勾选(Kconfig) │
│ drivers/spi/Kconfig → config OS_USING_SPI │
│ │ │
│ ▼ 展开为 #define │
│ oneos_config.h │
│ #define OS_USING_SPI │
│ #define OS_USING_QSPI │
│ #define BSP_USING_SPI (NRF:板级配置) │
│ #define BSP_USING_SPI0 (NRF:具体实例) │
├─────────────────────────────────────────────────────────────────────┤
│ STM32CubeMX 生成(HAL 库模块开关) │
│ board/CubeMX_Config/Inc/stm32f1xx_hal_conf.h │
│ #define HAL_SPI_MODULE_ENABLED │
├─────────────────────────────────────────────────────────────────────┤
│ 厂商 BSP 头文件(引脚/时钟宏) │
│ drivers/hal/gd/drivers/f403/drv_spi.h │
│ #define EVAL_SPI0_SCK_PIN GPIO_PIN_5 等 │
└─────────────────────────────────────────────────────────────────────┘要点:
OS_USING_*(框架组件开关)在oneos_config.h;HAL_*_MODULE_ENABLED(ST HAL 库开关)在 CubeMX 生成的stm32f1xx_hal_conf.h;BSP_USING_*(板级外设开关)在oneos_config.h;EVAL_*/引脚宏(厂商引脚定义)在厂商drv_xxx.h。理解这些宏的归属文件,才能在改配置时找到正确位置。
完整链路图(以"选 STM32 + 开 SPI 组件"为例):
① 配置阶段
templates/configs/Kconfig
选择 MANUFACTOR_STM32 → 定义宏 MANUFACTOR_STM32
② 目录选择
SConscript: inq_table['MANUFACTOR_STM32'] = 'st' → SRC_HAL = st
drivers/hal/Kconfig: source "$OS_ROOT/hal/$(SRC_HAL)/Kconfig" → 加载 hal/st/Kconfig
③ 文件级条件编译
drivers/hal/st/drivers/weave.yaml
depend_macro: MANUFACTOR_STM32 (组级:必须选 STM32)
drv_spi.c ? {is_define(['OS_USING_SPI',
'HAL_SPI_MODULE_ENABLED'])} (文件级:还要开 SPI + SPI 硬件)
↓ 条件都满足才编译
drv_spi.c → 链接进固件8.2 移植前的全局视角:分层全景图
回顾 SPI 设备的完整分层(本书 1.1 节已详解):应用层 → SPI 框架层(spi_core.c/spi_dev.c)→ BSP 层(drv_spi.c)→ 硬件层。
移植的关键洞察:
- 只有 BSP 层和 MCU 强相关(依赖具体 SPI 寄存器、HAL 库、片选引脚、时钟树)。
- 框架层和应用层与 MCU 无关(只依赖
spi.h定义的抽象接口)。 - 所以"换 MCU"本质上 = 重写/移植 BSP 层的
drv_spi.c(实现struct os_spi_ops的configure/xfer+ probe 注册)。
8.3 移植需要提供的"接口契约"
要移植,必须先满足 BSP 层与框架层之间的契约——也就是 struct os_spi_ops 的两个函数(见 2.5 节)必须全部实现,并映射到新 MCU 的硬件能力:
| ops 函数 | 需实现的能力 | 新 MCU 上对应什么 |
|---|---|---|
configure | 配置 SPI 硬件参数(模式/数据宽度/最大频率),返回 os_err_t | 设置 SPI 控制寄存器(CPOL/CPHA/主从)、数据宽度、波特率分频 |
xfer | 执行一次消息传输,返回字节数(0 表示失败);处理 cs_take/cs_release | 操作 SPI 数据寄存器全双工收发,片选 GPIO 拉低/拉高 |
此外还需要:
- SPI 总线实例结构体
struct xxx_spi:内嵌struct os_spi_bus,扩展硬件句柄、DMA 控制块、链表节点(如 ST 的struct stm32_spi)。 - 片选引脚管理:
os_spi_device->cs_pin对应的 GPIO 需配置为输出;xfer里按cs_take/cs_release拉低/拉高。 - probe/初始化函数:取硬件信息、挂 ops,最后调用
os_spi_bus_register,并通过OS_DRIVER_DEFINE让系统自动调用。 - 从设备挂载:板级文件用
os_hw_spi_device_attach/os_spi_bus_attach_device把从设备挂到总线(见 4.2)。
8.4 移植的标准流程(5 步)
下面以"把 SPI 驱动从 STM32 移植到一颗新 MCU"为例,给出通用流程:
第 1 步:定义 SPI 总线实例结构体与硬件配置
/* 内嵌框架层 os_spi_bus,扩展本 MCU 硬件信息 */
struct my_spi
{
struct os_spi_bus spi_bus; /* 框架层公共部分 */
MY_SPI_HandleTypeDef hspi; /* 新 MCU 的 SPI 句柄 */
os_list_node_t list; /* 挂到实例链表 */
};第 2 步:实现两个 ops 函数
以 configure 与 xfer 为例:
static os_err_t my_spi_configure(struct os_spi_device *device, struct os_spi_configuration *cfg)
{
struct my_spi *spi = os_container_of(device->bus, struct my_spi, spi_bus);
/* 根据 cfg->mode 判断 CPOL/CPHA/主从,设置新 MCU 的 SPI 控制寄存器 */
if (cfg->mode & OS_SPI_CPOL)
MY_SPI_SetPolarity(&spi->hspi, HIGH);
else
MY_SPI_SetPolarity(&spi->hspi, LOW);
/* 根据 cfg->max_hz 设置波特率分频 */
MY_SPI_SetBaudRate(&spi->hspi, cfg->max_hz);
return OS_SUCCESS;
}
static uint32_t my_spi_xfer(struct os_spi_device *device, struct os_spi_message *message)
{
struct my_spi *spi = os_container_of(device->bus, struct my_spi, spi_bus);
uint32_t i;
/* 片选控制 */
if (message->cs_take)
os_pin_write(device->cs_pin, PIN_LOW);
if (message->cs_release)
os_pin_write(device->cs_pin, PIN_HIGH);
/* 全双工收发 message->length 字节 */
for (i = 0; i < message->length; i++)
{
uint8_t tx = (message->send_buf) ? ((uint8_t *)message->send_buf)[i] : 0xff;
uint8_t rx = MY_SPI_Exchange(&spi->hspi, tx);
if (message->recv_buf)
((uint8_t *)message->recv_buf)[i] = rx;
}
return message->length;
}关键点:
- 用
os_container_of(device->bus, struct my_spi, spi_bus)从总线指针反查到 BSP 实例(spi_bus内嵌在my_spi里)。 configure必须正确解析OS_SPI_CPOL/OS_SPI_CPHA/OS_SPI_SLAVE/OS_SPI_3WIRE等位(见 3.2),否则模式错乱。xfer必须处理cs_take/cs_release(片选拉低/拉高),并返回成功传输的字节数(失败返回 0)。
第 3 步:定义并导出 ops 操作表
static const struct os_spi_ops my_spi_ops = {
.configure = my_spi_configure,
.xfer = my_spi_xfer,
};第 4 步:写 probe 初始化函数并注册
static int my_spi_probe(const os_driver_info_t *drv, const os_device_info_t *dev)
{
struct my_spi *spi = os_calloc(1, sizeof(struct my_spi));
OS_ASSERT(spi);
spi->hspi = (MY_SPI_HandleTypeDef *)dev->info;
os_list_add_tail(&my_spi_list, &spi->list);
/* 注册总线 */
return os_spi_bus_register(&spi->spi_bus, dev->name, &my_spi_ops);
}
OS_DRIVER_INFO my_spi_driver = {
.name = "MY_SPI_HandleTypeDef",
.probe = my_spi_probe,
};
OS_DRIVER_DEFINE(my_spi_driver, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_MIDDLE);关键点:用 OS_DRIVER_DEFINE + OS_DRIVER_INFO 声明驱动信息,让系统在设备初始化阶段自动调用 probe;os_spi_bus_register 完成总线注册。
第 5 步:挂载从设备并验证
板级文件里用 os_hw_spi_device_attach 挂从设备:
/* 挂载 SPI Flash 到 spi0 总线,CS 用 pin 10 */
os_err_t ret = os_hw_spi_device_attach("spi0", "spi_flash", 10);
if (ret != OS_SUCCESS)
{
os_kprintf("SPI device attach failed: %d\r\n", ret);
return;
}移植完成后,用本节"示例 1(读 Flash ID)"或"示例 4(全双工)"验证:
os_device_t *dev = os_device_open_s("spi_flash");
if (dev == OS_NULL) return;
struct os_spi_device *spi_dev = SPI_DEVICE(dev);
uint8_t cmd = 0x9F, id[3] = {0};
os_err_t ret = os_spi_send_then_recv(spi_dev, &cmd, 1, id, 3);
if (ret == OS_SUCCESS)
os_kprintf("ID: %02X %02X %02X\r\n", id[0], id[1], id[2]);- 能读到正确的 ID →
configure模式、xfer全双工、CS 控制都正确。 - 换一个模式(MODE 3)再试 → 验证
OS_SPI_CPOL/OS_SPI_CPHA解析正确。
拓展:一颗 MCU 上有多个 SPI 外设时,如何注册?
一颗 MCU 往往有多个 SPI 外设(SPI1、SPI2 等),它们各自独立,需各注册一次,os_spi_bus_register 出 spi0、spi1 等多个总线:
| 情形 | 说明 | 注册次数 |
|---|---|---|
| 同厂商不同 SPI 外设(SPI1/SPI2) | 共用同一套 ops 表(stm32_spi_ops),但每个外设各有一个实例结构体(struct stm32_spi),各调一次 os_spi_bus_register | 每个外设注册 1 次 |
不同厂商的 drv_spi.c | ST/GD/NRF 各有一份,实现同一契约 | 每个工程只编译其中一个(按所选 MCU) |
关键点:多个 SPI 外设共用一套 ops 表(stm32_spi_ops 是 static const),但每个外设有自己的实例结构体(里面有自己的 hspi、dma、list)。probe 对 dev->info 里的每个外设实例都调用一次,从而注册出多个总线。
8.5 芯片未适配时该怎么办?(新建 BSP 还是补型号)
与 5.2/5.3/5.4 的判断完全一致:已有厂商(补型号)vs 全新厂商(新建 BSP)。
8.5.1 判断标准
| 情况 | 判断标准 | 工作量大不大 |
|---|---|---|
| 情况 A:已有厂商 | 厂商目录已存在(如 st/),只是具体型号没列 | 小,通常不改 SPI 驱动 |
| 情况 B:全新厂商 | 厂商在 drivers/hal/ 完全没有目录 | 大,需整套新建 BSP + 移植 SPI 驱动 |
8.5.2 情况 A:已有厂商,只是具体型号没适配
同厂商同系列 SPI 寄存器结构高度一致,drv_spi.c 的 struct os_spi_ops 实现通常直接复用:
- 第 1 步:确认
drv_spi.c的 SPI 外设个数/引脚覆盖你的型号。 - 第 2 步:在厂商 Kconfig 加"芯片型号"选项,使其
select正确的时钟树与HAL_SPI_MODULE_ENABLED。 - 第 3 步:用厂商工具生成外设配置(
peripherals.c,含 SPI 引脚复用)。 - 第 4 步:写板级文件,用
os_hw_spi_device_attach挂从设备。 - 第 5 步:验证。
要点:情况 A 的核心是"型号配置 + SPI 引脚复用",SPI 驱动代码基本不用动。
8.5.3 情况 B:全新厂商,需要从零新建 BSP
- 第 1 步:新建
drivers/hal/my_chip/目录(含drv_spi.c、Kconfig、weave.yaml、board 文件、启动文件)。 - 第 2 步:按 8.4 的 5 步实现 SPI 驱动。
- 第 3 步:接入三层选择机制——Kconfig choice 加
MANUFACTOR_MYCHIP、SConscript 加inq_table['MANUFACTOR_MYCHIP']='my_chip'、weave.yaml 加drv_spi.c ? {is_define(['OS_USING_SPI', 'HAL_SPI_MODULE_ENABLED'])}。 - 第 4 步:验证。
8.5.4 两种情况的对比速查
| 维度 | 情况 A(已有厂商补型号) | 情况 B(全新厂商新建 BSP) |
|---|---|---|
| 是否新建目录 | 否,复用厂商目录 | 是,新建 drivers/hal/my_chip/ |
是否改 drv_spi.c | 通常不改(或改引脚/实例配置) | 从零实现两个 ops |
是否改厂商宏/SRC_HAL | 否 | 是 |
| 是否改 weave.yaml | 否 | 是(新增 drv_spi.c 的 is_define) |
| 工作量 | 小 | 大 |
8.6 移植检查清单(快速自查)
| 检查项 | 是否满足 |
|---|---|
struct os_spi_ops 的 configure/xfer 两个函数全部实现并绑定 | ☐ |
configure 正确解析 OS_SPI_CPOL/OS_SPI_CPHA/OS_SPI_SLAVE 等位 | ☐ |
xfer 处理 cs_take/cs_release 拉低/拉高片选 | ☐ |
xfer 返回成功传输字节数(失败返回 0) | ☐ |
os_container_of 从 bus 反查实例正确 | ☐ |
os_spi_bus_register 在应用使用前被调用(OS_DRIVER_DEFINE) | ☐ |
从设备用 os_hw_spi_device_attach/attach_device 挂载并 os_spi_configure | ☐ |
| 多 SPI 外设时,每个外设独立实例并各注册一次 | ☐ |
挂载后从设备 config 被清零,已重新 os_spi_configure | ☐ |
8.7 移植的价值与注意事项
价值:一旦 BSP 层移植完成,应用层代码一行不改即可在多个 MCU 间复用。
注意事项:
- 模式必须匹配:
configure必须正确解析 CPOL/CPHA/主从标志,否则从设备采样错误读乱码。 xfer的 CS 与返回语义:xfer要同时处理cs_take/cs_release和返回字节数,二者都错会导致传输失败或 CS 悬空。os_spi_take/release与take_bus/release_bus的区别:前者控片选、后者管总线锁,移植时不要混淆。- 挂载后必须重新配置:
os_spi_bus_attach_device会清零config,不os_spi_configure会导致模式/速率全 0 出错。 - owner 重配:换设备传输会触发
ops->configure,确保configure实现健壮(能在传输中被反复调用)。
📝 本节小结
核心要点回顾
- 两层抽象设计:
os_spi_bus(总线控制器)与os_spi_device(从设备)分离,一套总线驱动服务多个从设备。 - 消息驱动传输:
os_spi_message描述一次传输,next指针串成链表,cs_take/cs_release控制片选。 - 四种模式:
OS_SPI_CPOL(bit1)/OS_SPI_CPHA(bit0)组合成 MODE 0~3,模式必须与从机匹配。 - 标志位体系:
OS_SPI_MSB/LSB、MASTER/SLAVE、CS_HIGH/NO_CS/3WIRE/READY位于不同 bit,可用|组合、&判断,OS_SPI_MODE_MASK = 0xFF屏蔽无关位。 - owner 机制:
os_spi_transfer加总线锁后,若owner != device先ops->configure重配再置 owner,保证不同设备配置正确。 - 获取/释放:
os_spi_take_bus/release_bus管总线互斥锁;os_spi_take/release只控片选 CS(无引用计数)。 - 传输接口:
os_spi_transfer(全双工)、os_spi_send_then_recv/send(先发后收/分段发)、os_spi_transfer_message(消息链表,返回OS_NULL表示全成功)。 - QSPI 扩展:
os_qspi_message内嵌os_spi_message,扩展指令/地址/空周期/数据线数阶段;os_qspi_send_then_recv自动解析指令+地址。 - 更换 MCU 移植(见第八章):移植核心 = 实现
struct os_spi_ops两个函数 + 片选控制 + probe 注册;框架层与应用层不用改;换 MCU 本质是换SRC_HAL对应的那份drv_spi.c。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
在中断上下文调用 os_spi_transfer | 会 os_mutex_lock 阻塞,中断里无法睡眠 → 应在任务上下文调用 |
| 模式与从机不匹配 | 采样时刻错误读乱码 → 按从机数据手册选对 MODE 0~3 |
挂载后不 os_spi_configure | os_spi_bus_attach_device 清零 config → 必须重新配置 |
误以为 os_spi_take/release 管理总线锁 | 它们只控片选;管总线锁的是 os_spi_take_bus/release_bus |
xfer 不处理 cs_take/cs_release | 片选不拉低/拉高 → 从机不响应或一直占用 → 必须按消息标志控制 CS |
xfer 返回语义错误 | 框架用返回值是否为 0 判成败 → 成功返回字节数,失败返回 0 |
多设备共用总线时误用 os_spi_take/release 代替 take_bus/release_bus | 会并发冲突 → 需要并发保护时用 take_bus/release_bus |
os_spi_transfer_message 返回非空未检查 | 非空表示有消息失败 → 用返回值定位失败消息 |
移植时未正确解析 OS_SPI_CPOL/OS_SPI_CPHA | 模式错乱读乱码 → configure 必须按位判断 |
下一步
- 5.6 Touch设备:了解 OneOS Touch 设备的注册、配置与触控数据读取。