5.5 SPI 设备
2026/7/18大约 10 分钟设备驱动驱动设备SPIQSPI
5.5 SPI 设备
📚 本节导读
学习时长: 约 50 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言基础、MCU SPI 基础知识、5.1 设备驱动框架
🎯 学习目标
- 理解 OneOS 中 SPI 总线/设备的分层架构设计
- 掌握 SPI 四种工作模式(MODE 0/1/2/3)与配置参数
- 掌握 SPI 消息传输、同步收发等核心 API 的使用
- 理解 QSPI 扩展的架构与使用方法
- 能够编写完整的 SPI 设备驱动与数据收发程序
一、概述
SPI(Serial Peripheral Interface)是一种高速全双工同步串行通信总线。OneOS 将 SPI 抽象为总线/设备两层架构:os_spi_bus 表示 SPI 总线控制器,os_spi_device 表示挂载在总线上的 SPI 从设备。同时,OneOS 还提供了 QSPI(Quad SPI)扩展支持,通过多根数据线提升传输速率。SPI 设备驱动位于 drivers/spi/ 目录下,核心头文件为 spi.h。
SPI 设备框架的特点:
- 总线/设备分层:清晰分离总线控制器与从设备,一个总线上可挂载多个从设备
- 多模式支持:支持 CPOL/CPHA 四种标准 SPI 模式
- 消息链表:支持多消息链表传输,一次性完成多段数据收发
- QSPI 扩展:支持 Dual/Quad SPI,通过多数据线提升带宽
- 互斥保护:总线级互斥锁,防止多设备并发访问冲突
二、核心数据结构
2.1 SPI 总线结构体 os_spi_bus
struct os_spi_bus
{
struct os_device parent; /* 继承自 os_device_t */
uint8_t mode; /* 总线模式(SPI/QSPI) */
const struct os_spi_ops *ops; /* SPI 操作函数表 */
os_mutex_id lock; /* 总线互斥锁 */
struct os_spi_device *owner; /* 当前占用总线的设备 */
};os_spi_bus 代表一个 SPI 总线控制器,继承自 os_device_t。owner 字段记录当前占用总线的从设备,lock 互斥锁保证同一时刻只有一个设备访问总线。
2.2 SPI 从设备结构体 os_spi_device
struct os_spi_device
{
struct os_device parent; /* 继承自 os_device_t */
struct os_spi_bus *bus; /* 所属 SPI 总线 */
struct os_spi_configuration config; /* 设备配置 */
os_base_t cs_pin; /* 片选引脚 */
void *user_data; /* 用户自定义数据 */
};os_spi_device 代表挂载在 SPI 总线上的一个从设备,通过 bus 指针关联到所属总线,cs_pin 指定片选引脚。
2.3 SPI 操作函数表 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 设备参数(模式、数据宽度、最大频率) |
xfer | 执行一次 SPI 消息传输 |
2.4 SPI 配置结构体 os_spi_configuration
struct os_spi_configuration
{
uint8_t mode; /* SPI 模式(CPOL/CPHA 等) */
uint8_t data_width; /* 数据宽度(8/16/32 位) */
uint16_t reserved; /* 保留 */
uint32_t max_hz; /* 最大时钟频率 */
};2.5 SPI 消息结构体 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; /* 传输后是否释放片选 */
};os_spi_message 描述一次 SPI 传输任务,支持链表结构将多个消息串联,在一次传输中完成多段数据收发。cs_take 和 cs_release 控制片选信号的获取与释放。
2.6 QSPI 扩展结构体
QSPI 消息结构体 os_qspi_message:
struct os_qspi_message
{
struct os_spi_message parent; /* 继承自 SPI 消息 */
struct {
uint8_t content; /* 指令内容 */
uint8_t qspi_lines; /* 指令阶段使用的数据线数 */
} instruction;
struct {
uint32_t content; /* 地址/交替字节内容 */
uint8_t size; /* 地址/交替字节大小 */
uint8_t qspi_lines; /* 使用的数据线数 */
} address, alternate_bytes;
uint32_t dummy_cycles; /* 空周期数 */
uint8_t qspi_data_lines; /* 数据阶段使用的数据线数 */
};QSPI 配置结构体 os_qspi_configuration:
struct os_qspi_configuration
{
struct os_spi_configuration parent; /* 继承自 SPI 配置 */
uint32_t medium_size; /* 存储介质大小 */
uint8_t ddr_mode; /* 双倍数据速率模式 */
uint8_t qspi_dl_width; /* 最大数据线宽度(1/2/4) */
};QSPI 设备结构体 os_qspi_device:
struct os_qspi_device
{
struct os_spi_device parent; /* 继承自 SPI 设备 */
struct os_qspi_configuration config; /* QSPI 配置 */
void (*enter_qspi_mode)(struct os_qspi_device *device);
void (*exit_qspi_mode)(struct os_qspi_device *device);
};三、SPI 模式与配置标志
3.1 SPI 模式定义
#define OS_SPI_CPHA (1 << 0) /* 时钟相位 */
#define OS_SPI_CPOL (1 << 1) /* 时钟极性 */
#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 */| 模式 | CPOL | CPHA | 说明 |
|---|---|---|---|
OS_SPI_MODE_0 | 0 | 0 | 空闲时钟低电平,上升沿采样,下降沿输出 |
OS_SPI_MODE_1 | 0 | 1 | 空闲时钟低电平,下降沿采样,上升沿输出 |
OS_SPI_MODE_2 | 1 | 0 | 空闲时钟高电平,下降沿采样,上升沿输出 |
OS_SPI_MODE_3 | 1 | 1 | 空闲时钟高电平,上升沿采样,下降沿输出 |
3.2 配置标志
#define OS_SPI_LSB (0 << 2) /* 低位先传 */
#define OS_SPI_MSB (1 << 2) /* 高位先传 */
#define OS_SPI_MASTER (0 << 3) /* 主设备模式 */
#define OS_SPI_SLAVE (1 << 3) /* 从设备模式 */
#define OS_SPI_CS_HIGH (1 << 4) /* 片选高有效 */
#define OS_SPI_NO_CS (1 << 5) /* 无片选信号 */
#define OS_SPI_3WIRE (1 << 6) /* 三线模式(SI/SO 共用) */
#define OS_SPI_READY (1 << 7) /* 从设备拉低暂停 */3.3 总线模式
#define OS_SPI_BUS_MODE_SPI (1 << 0) /* 标准 SPI 模式 */
#define OS_SPI_BUS_MODE_QSPI (1 << 1) /* Quad SPI 模式 */
#define OS_SPI_BUS_MODE_XSPI (1 << 1) /* 扩展 SPI 模式(同 QSPI) */四、核心 API 详解
4.1 注册 SPI 总线
os_err_t os_spi_bus_register(struct os_spi_bus *bus,
const char *name,
const struct os_spi_ops *ops);- 注册 SPI 总线控制器到设备框架,通常由 BSP 层调用
bus:SPI 总线结构体指针name:总线名称,如"spi0"、"spi1"ops:SPI 操作函数表- 返回值:
OS_SUCCESS成功,其他值失败
4.2 挂载 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);- 将 SPI 从设备挂载到指定总线上
device:SPI 从设备结构体指针name:设备名称bus_name:目标总线名称cs_pin:片选引脚编号- 返回值:
OS_SUCCESS成功,其他值失败
4.3 硬件辅助挂载
os_err_t os_hw_spi_device_attach(const char *bus_name, const char *device_name, os_base_t cs_pin);- 简化版挂载函数,由 BSP 层调用
- 内部自动分配
os_spi_device结构体,完成设备注册
4.4 配置 SPI 设备
os_err_t os_spi_configure(struct os_spi_device *device, struct os_spi_configuration *cfg);- 配置 SPI 设备的模式、数据宽度、最大频率
- 调用底层
ops->configure完成硬件配置
4.5 获取/释放总线
os_err_t os_spi_take_bus(struct os_spi_device *device);
os_err_t os_spi_release_bus(struct os_spi_device *device);- 获取/释放 SPI 总线使用权
- 获取总线时会获取互斥锁,防止多设备并发访问
- 同一设备再次获取(
os_spi_take)时仅增加引用计数,不会重复获取锁
4.6 获取/释放设备
os_err_t os_spi_take(struct os_spi_device *device);
os_err_t os_spi_release(struct os_spi_device *device);- 获取/释放 SPI 设备,内部调用
os_spi_take_bus/os_spi_release_bus - 支持嵌套调用,引用计数机制保证正确的获取/释放配对
4.7 数据传输
os_size_t os_spi_transfer(struct os_spi_device *device, const void *send_buf, void *recv_buf, os_size_t length);- 同步传输数据,同时收发(全双工)
send_buf:发送数据缓冲区,可为OS_NULL(仅接收)recv_buf:接收数据缓冲区,可为OS_NULL(仅发送)- 返回值:实际传输的字节数
4.8 先发后收(半双工)
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);- 先发送命令/地址,再接收数据,适用于大多数 SPI 从设备
- 发送和接收阶段自动控制片选信号
4.9 分段发送
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);- 分两段发送数据,中间不释放片选
4.10 消息链表传输
struct os_spi_message *os_spi_transfer_message(struct os_spi_device *device, struct os_spi_message *message);- 传输消息链表,可一次性完成多段数据收发
- 通过
os_spi_message_append构建消息链表
4.11 内联辅助函数
/* 仅接收 */
OS_INLINE os_size_t os_spi_recv(struct os_spi_device *device, void *recv_buf, os_size_t length);
/* 仅发送 */
OS_INLINE os_size_t os_spi_send(struct os_spi_device *device, const void *send_buf, os_size_t length);
/* 收发单字节 */
OS_INLINE uint8_t os_spi_sendrecv8(struct os_spi_device *device, uint8_t data);
/* 收发双字节 */
OS_INLINE uint16_t os_spi_sendrecv16(struct os_spi_device *device, uint16_t data);
/* 追加消息到链表尾部 */
OS_INLINE void os_spi_message_append(struct os_spi_message *list, struct os_spi_message *message);4.12 QSPI 专用 API
/* 注册 QSPI 总线 */
os_err_t os_qspi_bus_register(struct os_spi_bus *bus, const char *name, const struct os_spi_ops *ops);
/* 配置 QSPI 设备 */
os_err_t os_qspi_configure(struct os_qspi_device *device, struct os_qspi_configuration *cfg);
/* QSPI 消息传输 */
os_size_t os_qspi_transfer_message(struct os_qspi_device *device, struct os_qspi_message *message);
/* 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);
/* QSPI 发送 */
os_err_t os_qspi_send(struct os_qspi_device *device, const void *send_buf, os_size_t length);五、使用示例
示例 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
/* 读取 Flash JEDEC ID */
void read_flash_id(void)
{
os_device_t *dev;
uint8_t cmd = 0x9F; /* 读 JEDEC ID 命令 */
uint8_t id[3] = {0}; /* 存储 ID */
dev = os_device_open_s(SPI_DEV_NAME);
if (dev == OS_NULL)
{
os_kprintf("Failed to open SPI device\r\n");
return;
}
/* 获取 SPI 设备 */
struct os_spi_device *spi_dev = SPI_DEVICE(dev);
/* 先发送命令,再读取 3 字节 ID */
os_spi_send_then_recv(spi_dev, &cmd, 1, id, 3);
os_kprintf("Flash JEDEC ID: %02X %02X %02X\r\n", id[0], id[1], id[2]);
os_device_close(dev);
}示例 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};
/* 获取总线 */
os_spi_take(spi_dev);
/* 全双工传输 4 字节 */
os_spi_transfer(spi_dev, tx_buf, rx_buf, 4);
/* 释放总线 */
os_spi_release(spi_dev);
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);
}示例 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}; /* 数据缓冲区 */
/* 构建消息链表 */
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);
/* 获取总线并传输 */
os_spi_take(spi_dev);
os_spi_transfer_message(spi_dev, &msg1);
os_spi_release(spi_dev);
os_kprintf("Read 32 bytes from SPI Flash\r\n");
os_device_close(dev);
}示例 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 控制器 */
return OS_SUCCESS;
}
static uint32_t my_spi_xfer(struct os_spi_device *device, struct os_spi_message *message)
{
/* 执行 SPI 传输 */
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_spi_bus_register(&my_spi_bus, "spi0", &my_spi_ops);
/* 2. 配置 SPI 设备 */
my_spi_dev.config.mode = OS_SPI_MODE_0;
my_spi_dev.config.data_width = 8;
my_spi_dev.config.max_hz = 10000000; /* 10 MHz */
/* 3. 挂载到总线 */
os_spi_bus_attach_device(&my_spi_dev, "spi_flash", "spi0", 10);
}示例 5:使用 QSPI 读取 Flash
#include <spi.h>
#include <device.h>
void qspi_read_flash_demo(void)
{
struct os_qspi_device *qspi_dev;
uint8_t rx_data[256];
qspi_dev = (struct os_qspi_device *)os_device_open_s("qspi_flash");
if (qspi_dev == OS_NULL) return;
/* 构建 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;
/* 获取总线并传输 */
os_spi_take((struct os_spi_device *)qspi_dev);
os_qspi_transfer_message(qspi_dev, &msg);
os_spi_release((struct os_spi_device *)qspi_dev);
os_kprintf("QSPI read 256 bytes done\r\n");
os_device_close((os_device_t *)qspi_dev);
}📝 本节小结
本节介绍了 OneOS 中 SPI 设备的使用方法,包括:
- 分层架构:
os_spi_bus总线控制器 +os_spi_device从设备,一个总线可挂载多个从设备 - 四种模式:通过 CPOL/CPHA 组合支持 MODE 0/1/2/3 四种标准 SPI 模式
- 配置参数:
os_spi_configuration包含模式、数据宽度、最大频率等配置 - 消息链表:
os_spi_message支持链表结构,可一次性完成多段数据收发 - 核心 API:
os_spi_transfer(全双工)、os_spi_send_then_recv(先发后收)、os_spi_transfer_message(消息链表)三大传输接口 - QSPI 扩展:通过
os_qspi_device、os_qspi_message等结构支持多线数据传输 - 总线互斥:通过互斥锁保证多设备安全访问
SPI 是嵌入式系统中常用的高速通信接口,掌握其分层架构和 API 是进行外设驱动开发的关键。