SPI 与通用外设接口
AD23N 平台的 SPI 外设子系统文档,覆盖硬件 SPI(HW_SPI0/1/2)主机/从机四种工作模式、阻塞/DMA/中断三种传输方式,以及基于 GPIO 模拟的软件 SPI(soft SPI)通用接口。
Purpose and Scope
本页面向 AD23N(SH59 内核)MCU SDK 的 SPI 与通用外设接口能力,完整说明:
- 硬件 SPI 的通道划分(
HW_SPI0/1/2)、四种enum spi_mode工作模式、主/从角色; spi_platform_data平台配置结构的每个字段及其作用;- 主机阻塞接口、DMA 接口、中断(ISR)接口的调用约定与内部状态机(
enum hw_spi_isr_status); - 从机无 CS 的固定长度传输模型;
- 软件模拟 SPI(
spi_soft)的 API 与适用场景; - 基于
spi_demo.c的真实使用示例。
以下内容属于其他页面、不在本页展开:GPIO 通用输入输出配置(见 GPIO 页面)、DMA 控制器通用框架、时钟树与波特率时钟源配置(见时钟页面)、外设设备模型与 ioctl 框架。
Overview
SPI(Serial Peripheral Interface)是 AD23N 平台上最常用的同步串行外设接口之一,SDK 同时提供硬件 SPI(使用芯片内置 SPI 控制器,支持 DMA 与中断)与软件 SPI(纯 GPIO 位翻转模拟,任意 IO 均可映射)。
硬件 SPI 的核心设计意图是灵活性优先:
- 引脚任意映射——
spi_platform_data.port[6]允许将 CLK/DO/DI/D2/D3/CS 分配到任意 IO(IO_PORTA_xx等),未使用的引脚填0xff,这使 PCB 布线约束大幅放宽; - 模式可配置——支持全双工 1bit(BIDIR_1BIT)、半双工 1bit(UNIDIR_1BIT)、半双工 2bit(UNIDIR_2BIT)、半双工 4bit(UNIDIR_4BIT,仅 SPI1/SPI2)四种模式,对应 QSPI/DUAL 类 Flash 与普通外设;
- 传输方式可选——同一套 API 提供阻塞字节/缓冲、DMA、ISR 中断三种路径,开发者按实时性要求取舍;
- 极性与相位独立——
cpol/cpha两个 bit 位独立控制时钟空闲电平与采样沿,兼容绝大多数从设备时序。
典型使用场景包括:外挂 SPI Flash / EEPROM、LCD 屏驱动、传感器采集、与另一个 MCU 的主从通信、以及通过软件 SPI 复用普通 IO 驱动低速 SPI 器件。
Architecture
flowchart TD
subgraph sg_App["应用层"]
App["应用代码 / spi_demo.c"]
end
subgraph sg_API["公共 API 层 (sdk/include_lib/cpu/spi.h)"]
Open["spi_open / spi_deinit / spi_suspend / spi_resume"]
Block["spi_send_byte / spi_recv_byte / spi_send_recv_byte"]
Dma["spi_dma_send / spi_dma_recv"]
Isr["spi_byte_transmit_for_isr / spi_buf_transmit_for_isr / spi_dma_transmit_for_isr"]
Status["hw_spix_get_isr_status / hw_spix_get_isr_len / hw_spix_clear_isr_len"]
end
subgraph sg_HW["硬件层 (sh59/spi_hw.h)"]
SPI0["HW_SPI0 系统保留"]
SPI1["HW_SPI1 支持4bit"]
SPI2["HW_SPI2 支持4bit"]
end
subgraph sg_Soft["软件模拟 SPI (spi_soft)"]
SoftAPI["soft_spi_open / soft_spi_send_recv_byte / soft_spi_dma_send ..."]
GPIO["GPIO 位翻转模拟时序"]
end
subgraph sg_IO["物理引脚"]
IO1["任意 IO: CLK/DO/DI/D2/D3/CS"]
end
App --> Open
App --> Block
App --> Dma
App --> Isr
Isr --> Status
Open --> SPI1
Open --> SPI2
SPI0 -. "系统占用" .- Open
SPI1 --> IO1
SPI2 --> IO1
App --> SoftAPI
SoftAPI --> GPIO
GPIO --> IO1
架构分为四层:应用层通过统一的公共 API(spi.h)访问硬件 SPI,或通过 spi_soft.h 访问软件模拟 SPI。硬件 SPI 的三个通道中 HW_SPI0 已被系统占用(spi_hw.h 中 SUPPORT_SPI0 0 且枚举注释标明 "SPI0系统已使用"),应用通常使用 HW_SPI1/HW_SPI2,两者都支持 4bit 模式。底层引脚任意映射,CLK/DO/DI/D2/D3/CS 均可分配到任意 IO。
flowchart TD
subgraph sg_Modes["spi_mode 工作模式"]
M1["SPI_MODE_BIDIR_1BIT<br/>全双工: di收 do发"]
M2["SPI_MODE_UNIDIR_1BIT<br/>半双工: do分时收发"]
M3["SPI_MODE_UNIDIR_2BIT<br/>半双工: di&do 2bit"]
M4["SPI_MODE_UNIDIR_4BIT<br/>半双工: di&do&wp&hold 4bit<br/>(仅 SPI1/SPI2)"]
end
subgraph sg_Roles["spi_role 角色"]
R1["SPI_ROLE_MASTER 主机"]
R2["SPI_ROLE_SLAVE 从机"]
end
subgraph sg_Trans["传输路径"]
T1["阻塞: spi_send_byte / spi_recv_byte"]
T2["DMA: spi_dma_send / spi_dma_recv"]
T3["中断: *_for_isr + ISR 状态机"]
end
M1 --> T1
M1 --> T2
M1 --> T3
M2 --> T1
M3 --> T2
M4 --> T2
R1 --> T1
R2 --> T3
模式与角色组合决定可用 API:全双工模式(BIDIR_1BIT)支持阻塞字节收发与 spi_send_recv_byte;半双工多 bit 模式主要面向 DMA 批量传输(如 Flash 读写);从机场景通常走中断/DMA 接口,因为从机无法主动发起传输,必须由主机时钟驱动。
核心实现详解
硬件 SPI 平台数据结构:spi_platform_data
所有硬件 SPI 的行为都通过 struct spi_platform_data 配置,该结构定义在公共头文件 spi.h 中,是 spi_open() 的核心入参:
typedef struct spi_platform_data {
u8 port[6]; //CLK, DO, DI, D2(wp), D3(hold), cs(只用于slave),未使用的io配0xff
enum spi_role role; //master or slave
enum spi_mode mode; //模式,选项为enum spi_mode中的枚举常量
enum spi_bit_mode bit_mode;
u8 cpol: 1; //clk level in idle state:0:low, 1:high
u8 cpha: 1; //sampling edge:0:first, 1:second
u8 ie_en: 1; //ie enbale:0:disable, 1:enable
u8 irq_priority: 5; //中断优先级
void (*spi_isr_callback)(hw_spi_dev spi, enum hw_spi_isr_status sta); //spi isr callback
u32 clk; //波特率
} spi_hardware_info;
Source: spi.h
字段设计意图说明:
port[6](引脚映射):固定顺序为 CLK、DO、DI、D2(wp)、D3(hold)、CS。每个元素是任意 IO 的枚举值(如IO_PORTA_01)。不使用的引脚必须写0xff。注意 CS 仅在从机角色下配置——主机不操作 CS,由应用自行用 GPIO 控制从设备片选(demo 中主机 CS 填0xff并注释 "主机不操作cs");role/mode:决定该通道是主还是从、采用哪种数据宽度模式;bit_mode:位序,取值来自enum spi_bit_mode(见下),默认SPI_FIRST_BIT_MSB与绝大多数 SPI 器件一致;cpol/cpha(位域):CPOL 决定空闲时钟电平,CPHA 决定采样沿。SDK 把它们压缩为位域,注意是裸 bit 语义而非 0/1 布尔值;ie_en:是否使能中断。置 1 后需要配合spi_isr_callback使用;置 0 时走阻塞/DMA 轮询路径;irq_priority:5bit 中断优先级(0~31);spi_isr_callback:中断回调,原型void (*)(hw_spi_dev, enum hw_spi_isr_status),可后续用hw_spix_irq_change_callback()动态更换;clk:目标波特率(Hz),demo 中为1000000L(1MHz)。
通道枚举与芯片能力(spi_hw.h)
#define SUPPORT_SPI0 0 //是否使能SPI0
#define SUPPORT_SPI1 1 //是否使能SPI1
#define SPI1_SUPPORT_UNIDIR_4BIT 1 //spi 4bit
#define SUPPORT_SPI2 1 //是否使能spi2
#define SPI2_SUPPORT_UNIDIR_4BIT 1 //spi 4bit
#define HW_SPI_MAX_NUM 3
typedef enum spi_index {
HW_SPI0, //SPI0系统已使用
HW_SPI1,
HW_SPI2,
} hw_spi_dev;
enum spi_bit_mode {
SPI_FIRST_BIT_MSB, //7,6,5,4,3,2,1,0
SPI_FIRST_BIT_LSB, //0,1,2,3,4,5,6,7
SPI_FIRST_BIT_BIT3, //3,2,1,0,7,6,5,4
SPI_FIRST_BIT_BIT4, //4,5,6,7,0,1,2,3
};
Source: spi_hw.h
关键结论:
hw_spi_dev就是通道句柄,HW_SPI0已被系统占用,应用层可用HW_SPI1、HW_SPI2;- 4bit 模式(QSPI 式四线)仅
SPI1/SPI2支持,由编译宏SPI1_SUPPORT_UNIDIR_4BIT/SPI2_SUPPORT_UNIDIR_4BIT控制; enum spi_bit_mode支持 MSB、LSB 以及两种特殊的半字节优先序(BIT3/BIT4),用于兼容位序奇特的器件(如部分 Flash 的连续读模式);SPI_MODE_UNIDIR_4BIT模式下 D2(wp) 与 D3(hold) 引脚被复用为数据线,这解释了port[6]中第 4、5 个元素的存在意义。
生命周期:打开、关闭、挂起、恢复
公共 API 提供完整的生命周期管理:
spi_open(spi, spi_info):初始化通道并返回错误码(负数表示失败,demo 中log_error("spi master init error(%d)!"...)处理);spi_deinit(spi):反初始化,释放引脚与中断;spi_suspend(spi)/spi_resume(spi):低功耗挂起/恢复,用于系统休眠场景;spi_set_bit_mode(spi, mode)/spi_set_baud(spi, baud)/spi_get_baud(spi):运行时动态调整位序与波特率,无需重新 open。
设计意图:spi_open 是唯一的重型初始化入口,把引脚、模式、极性、中断、波特率一次性下发到硬件寄存器;spi_set_baud 允许在运行中按从设备速率切换(例如先以慢速读 Flash JEDEC ID,再切高速读数据),避免反复 open/close 的开销。
中断接口与 ISR 状态机
中断路径的核心是 enum hw_spi_isr_status 状态机与配套查询函数:
enum hw_spi_isr_status {
SPI_WAITING_PND, // 等待传输完成标志
SPI_TX_FINISH, // 发送完成
SPI_RX_FINISH, // 接收完成
SPI_PND_ERROR, // 传输错误
};
Source: spi.h
中断传输的调用范式(见 demo):
hw_spix_clear_isr_len(spi)清空长度计数("每包数据前清空,在通信过程中调用,会导致数据长不准");- 调用
spi_dma_transmit_for_isr(spi, buf, len, rw)或spi_buf_transmit_for_isr(...)启动传输,不等待; - 轮询或回调中查询
hw_spix_get_isr_status(spi),直到变为SPI_TX_FINISH(发送)或SPI_RX_FINISH(接收); - 用
hw_spix_get_isr_len(spi)取实际传输长度——带符号语义:负值表示走 DMA(-len),正值表示字节模式(+len),结合状态可知收发方向。
stateDiagram-v2
[*] --> SPI_WAITING_PND: 发起 *_for_isr 传输
SPI_WAITING_PND --> SPI_TX_FINISH: rw=0 发送完成
SPI_WAITING_PND --> SPI_RX_FINISH: rw=1 接收完成
SPI_WAITING_PND --> SPI_PND_ERROR: 总线/配置错误
SPI_TX_FINISH --> SPI_WAITING_PND: 下一包 (先 hw_spix_clear_isr_len)
SPI_RX_FINISH --> SPI_WAITING_PND: 下一包 (先 hw_spix_clear_isr_len)
SPI_PND_ERROR --> SPI_WAITING_PND: 恢复后重发
从机场景没有"发起"概念,demo 的从机测试用 spi_buf_transmit_for_isr(spi, NULL, spi_rx_buf, 256, 1) 预先挂好接收缓冲,然后死循环等待 SPI_RX_FINISH——从机必须提前准备缓冲区,主机时钟一到就开始填充,固定 256 字节长度(demo 注释 "固定256长")。从机长度查询专用接口 hw_spix_slave_get_dma_len() 返回当前已传输的 DMA 个数,但不能区分收发方向。
软件 SPI(spi_soft):GPIO 模拟的通用外设接口
软件 SPI 是"通用外设接口"的另一半:不依赖芯片 SPI 控制器,用 GPIO 位翻转逐 bit 产生时序,API 形状与硬件 SPI 高度一致:
typedef const int spi_soft_dev;
int soft_spi_open(spi_soft_dev spi);
void soft_spi_close(spi_soft_dev spi);
void soft_spi_set_bit_mode(spi_soft_dev spi, int data_wide);
int soft_spi_set_baud(spi_soft_dev spi, u32 baud);
u32 soft_spi_get_baud(spi_soft_dev spi);
void soft_spi_suspend(spi_soft_dev spi);
void soft_spi_resume(spi_soft_dev spi);
int soft_spi_send_byte(spi_soft_dev spi, u8 byte);
u8 soft_spi_recv_byte(spi_soft_dev spi, int *err);
u8 soft_spi_send_recv_byte(spi_soft_dev spi, u8 byte, int *err);
int soft_spi_dma_recv(spi_soft_dev spi, void *buf, u32 len);
int soft_spi_dma_send(spi_soft_dev spi, const void *buf, u32 len);
Source: spi_soft.h
工作模式以四种 CPOL/CPHA 组合枚举(SPI_CPOL0_CPHA0 ~ SPI_CPOL1_CPHA1),均为 MSB first,与硬件 SPI 的 cpol/cpha 位域一一对应但语义更直观。设计取舍:
- 优点:任意 IO 都能当 SPI 用,不受硬件控制器引脚限制;可同时虚拟出多个 SPI 实例,缓解
HW_SPI0被占用后通道不足的问题; - 代价:
soft_spi_dma_*只是名义上的 DMA——位翻转由 CPU 完成,实际是阻塞软件搬运,不产生真实 DMA 传输,吞吐远低于硬件 SPI,适合低速器件; - 波特率由
soft_spi_set_baud通过延时/时钟分频近似实现,精度受 GPIO 翻转开销限制。
选择建议:高速/大数据量(Flash 读写、LCD 刷屏)必须用硬件 SPI + DMA;低速、引脚受限、或需要多个 SPI 实例时用 soft SPI。
核心流程
主机 DMA 发送(阻塞路径)时序
sequenceDiagram
participant App as 应用 (spi_demo.c)
participant API as spi.h 公共API
participant HW as SPI1/SPI2 控制器
participant IO as 物理引脚
App->>API: spi_open(HW_SPI1, &pdata) 配置 port/mode/cpol/cpha/clk
API->>HW: 初始化寄存器、映射IO、设置波特率
API-->>App: ret (0成功 / 负数失败)
App->>API: spi_dma_send(spi, buf, 50)
API->>HW: 配置DMA源地址/长度, 启动传输
HW->>IO: CLK驱动, DO逐bit输出
HW-->>API: 传输完成置PND
API-->>App: 返回 (阻塞等PND后返回)
App->>API: spi_deinit(spi)
API->>HW: 释放引脚与中断
主机中断(ISR)收发流程
sequenceDiagram
participant App as 应用
participant ISR as 中断回调
participant HW as SPI控制器
App->>App: 配置 ie_en=1 + spi_isr_callback
App->>HW: spi_buf_transmit_for_isr(spi, tx, rx, len, 0) 全双工
loop 等待完成
App->>HW: hw_spix_get_isr_status(spi) == SPI_TX_FINISH?
HW-->>App: 未完成则 wdt_clear() 继续轮询
end
App->>HW: hw_spix_get_isr_len(spi) 取实际长度
HW-->>App: +len (字节模式) 或 -len (DMA模式)
从机接收流程
从机是被动方:spi_open 时配置 SPI_ROLE_SLAVE + CS 引脚(port[5]),随后预先挂载接收缓冲,等待主机时钟:
hw_spix_clear_isr_len(spi)清长度计数;spi_buf_transmit_for_isr(spi, NULL, rx_buf, 256, 1)挂接收缓冲(rw=1);- 主机产生时钟,从机逐字节/逐 DMA 填充
rx_buf; - 查询
hw_spix_get_isr_status(spi) == SPI_RX_FINISH确认整包收完; - 若需连续接收,重复步骤 1~4(demo 为
while(1)循环 +wdt_clear()喂狗)。
从机模式下 CS 由硬件感知,demo 注释明确从机不开中断 DMA 模式时用 hw_spix_slave_get_dma_len() 查询已传输个数。
使用示例
以下示例均提取自 SDK 自带的 spi_demo.c,展示从初始化到三种传输路径的完整用法。
示例 1:主机阻塞 DMA 发送
void spi_master_test(hw_spi_dev spi)
{
struct spi_platform_data spix_p_data_test = {
.port = {
IO_PORTA_01, //clk any io
IO_PORTA_02, //do any io
IO_PORTA_03, //di any io
IO_PORTA_04, //d2 any io
IO_PORTA_05, //d3 any io
0xff, //cs any io(主机不操作cs)
},
.role = SPI_ROLE_MASTER,
.mode = SPI_MODE_BIDIR_1BIT,
.bit_mode = SPI_FIRST_BIT_MSB,
.cpol = 0,//clk level in idle state:0:low, 1:high
.cpha = 0,//sampling edge:0:first, 1:second
.ie_en = 0, //ie enbale:0:disable, 1:enable
.irq_priority = 3,
.spi_isr_callback = NULL, //spi isr callback
.clk = 1000000L,
};
//init
int ret = spi_open(spi, &spix_p_data_test);
if (ret < 0) {
log_error("spi master init error(%d)!", ret);
}
//dma tx(阻塞等pnd)
u8 spi_tx_buf_test[50];
log_info("spi(%d) master block dma tx test:", spi);
memset(spi_tx_buf_test, 0xa5, sizeof(spi_tx_buf_test));
ret = spi_dma_send(spi, spi_tx_buf_test, 50);
if (ret < 0) {
log_error("spi master dma send error(%d)!", ret);
}
log_info("spi(%d) master block dma tx ok!\n", spi);
spi_deinit(spi);
Source: spi_demo.c
要点:ie_en=0 时 spi_dma_send 阻塞等待 PND;主机 CS 填 0xff,片选由外部 GPIO 自理;spi_open 失败返回负错误码需检查。
示例 2:主机中断 DMA 收发 + 长度查询
spix_p_data_test.ie_en = 1;
spix_p_data_test.spi_isr_callback = spi_master_isr_callback_test;
ret = spi_open(spi, &spix_p_data_test);
if (ret < 0) {
log_error("spi master init error(%d)!", ret);
}
//dma tx(中断)
memset(spi_tx_buf_test, 0x39, sizeof(spi_tx_buf_test));
log_info("spi(%d) master irq dma tx test:", spi);
spi_dma_transmit_for_isr(spi, spi_tx_buf_test, sizeof(spi_tx_buf_test), 0); //rw:1-rx; 0-tx
while (hw_spix_get_isr_status(spi) != SPI_TX_FINISH) {
wdt_clear();
}
log_info("spi(%d) master irq dma tx ok!(len:%d)\n", spi, hw_spix_get_isr_len(spi));
//dma rx(中断)
log_info("spi(%d) master irq dma rx test:", spi);
memset(spi_tx_buf_test, 0x39, sizeof(spi_tx_buf_test));
spi_dma_transmit_for_isr(spi, spi_tx_buf_test, sizeof(spi_tx_buf_test), 1); //rw:1-rx; 0-tx
while (hw_spix_get_isr_status(spi) != SPI_RX_FINISH) {
wdt_clear();
}
log_info("spi(%d) master irq dma rx ok!len:%d\n", spi, hw_spix_get_isr_len(spi));
Source: spi_demo.c
要点:ie_en=1 后中断接口不等待即返回;发送完成看 SPI_TX_FINISH,接收完成看 SPI_RX_FINISH;循环体内 wdt_clear() 防止长传输触发看门狗复位——这是嵌入式轮询等待的标准防护。
示例 3:主机中断全双工缓冲收发
hw_spix_irq_change_callback(spi, NULL);
u8 spi_rx_buf_test[100];
memset(spi_rx_buf_test, 0, sizeof(spi_rx_buf_test));
memset(spi_tx_buf_test, 0xb6, sizeof(spi_tx_buf_test));
hw_spix_clear_isr_len(spi);//clear len
spi_buf_transmit_for_isr(spi, spi_tx_buf_test, spi_rx_buf_test, sizeof(spi_tx_buf_test), 0);//rw:1-rx; 0-tx,全双工
while (hw_spix_get_isr_status(spi) != SPI_TX_FINISH) {
wdt_clear();
}
log_info("spi(%d) master buf rx: (len:%d)", spi, hw_spix_get_isr_len(spi));
log_info_hexdump(spi_rx_buf_test, sizeof(spi_rx_buf_test));
Source: spi_demo.c
要点:hw_spix_irq_change_callback(spi, NULL) 可临时移除回调;rw=0 时同时给出 tx_buf 与 rx_buf 即全双工(demo 注释 "全双工");hw_spix_clear_isr_len 必须在每包开始前调用。字节级替代方案 spi_byte_transmit_for_isr 每字节等待一次 SPI_TX_FINISH,吞吐低、仅用于逐字节控制场景。
示例 4:从机无 CS 的固定长度接收
while (1) { //tx
hw_spix_clear_isr_len(spi);//clear len
spi_buf_transmit_for_isr(spi, NULL, spi_rx_buf_test, sizeof(spi_rx_buf_test), 1);//rw:1-rx; 0-tx,全双工
while (hw_spix_get_isr_status(spi) != SPI_RX_FINISH) {
wdt_clear();
}
log_info("spi(%d) slave buf rx:(len:%d)", spi, hw_spix_get_isr_len(spi));
log_info_hexdump(spi_rx_buf_test, sizeof(spi_rx_buf_test));
}
Source: spi_demo.c
要点:从机接收固定 256 字节长包,tx_buf 传 NULL、rx_buf 指向目标缓冲、rw=1;从机必须提前挂缓冲,否则主机时钟到来时无处存数据。该循环配合 wdt_clear() 常驻等待,实际产品中应改为中断回调驱动。
Configuration Options
spi_platform_data 配置字段(spi_open 入参)
| 字段 | 类型 | 默认/典型值 | 说明 |
|---|---|---|---|
port[0] | u8 | IO_PORTA_xx | CLK 引脚,任意 IO;未使用填 0xff |
port[1] | u8 | IO_PORTA_xx | DO(主机发送/从机发送)引脚 |
port[2] | u8 | IO_PORTA_xx | DI(主机接收/从机接收)引脚 |
port[3] | u8 | IO_PORTA_xx | D2(wp),4bit 模式数据线 |
port[4] | u8 | IO_PORTA_xx | D3(hold),4bit 模式数据线 |
port[5] | u8 | 0xff | CS,仅从机使用;主机填 0xff |
role | enum spi_role | SPI_ROLE_MASTER | 主/从角色 |
mode | enum spi_mode | SPI_MODE_BIDIR_1BIT | 数据宽度与收发模式 |
bit_mode | enum spi_bit_mode | SPI_FIRST_BIT_MSB | 位序(MSB/LSB/BIT3/BIT4) |
cpol | bit | 0 | 空闲时钟电平:0 低 / 1 高 |
cpha | bit | 0 | 采样沿:0 第一沿 / 1 第二沿 |
ie_en | bit | 0 | 中断使能:0 关 / 1 开 |
irq_priority | 5bit | 3 | 中断优先级(0~31) |
spi_isr_callback | 函数指针 | NULL | ISR 回调,ie_en=1 时有效 |
clk | u32 | 1000000 | 波特率(Hz) |
编译期使能宏(spi_hw.h)
| 宏 | 默认值 | 说明 |
|---|---|---|
SUPPORT_SPI0 | 0 | 是否使能 SPI0(系统已占用,默认关闭) |
SUPPORT_SPI1 | 1 | 是否使能 SPI1 |
SPI1_SUPPORT_UNIDIR_4BIT | 1 | SPI1 是否支持 4bit 模式 |
SUPPORT_SPI2 | 1 | 是否使能 SPI2 |
SPI2_SUPPORT_UNIDIR_4BIT | 1 | SPI2 是否支持 4bit 模式 |
HW_SPI_MAX_NUM | 3 | 最大 SPI 通道数 |
软件 SPI 工作模式(spi_soft.h)
| 枚举值 | 含义 |
|---|---|
SPI_CPOL0_CPHA0 | CPOL=0, CPHA=0, MSB first, 空闲低, 上升沿采样 |
SPI_CPOL0_CPHA1 | CPOL=0, CPHA=1, MSB first, 空闲低, 下降沿采样 |
SPI_CPOL1_CPHA0 | CPOL=1, CPHA=0, MSB first, 空闲高, 下降沿采样 |
SPI_CPOL1_CPHA1 | CPOL=1, CPHA=1, MSB first, 空闲高, 上升沿采样 |
API Reference
硬件 SPI 公共 API(spi.h)
int spi_open(hw_spi_dev spi, spi_hardware_info *spi_info)
按 spi_info 配置初始化指定通道:映射引脚、设置模式/位序/极性、配置中断与波特率。
参数:
spi(hw_spi_dev):HW_SPI0/HW_SPI1/HW_SPI2,应用用 SPI1/SPI2;spi_info(spi_hardware_info *):平台配置结构指针。
返回: 0 成功;负数错误码(demo 中以 ret < 0 判定并打印)。
void spi_deinit(hw_spi_dev spi) / void spi_suspend(hw_spi_dev spi) / void spi_resume(hw_spi_dev spi)
反初始化 / 低功耗挂起 / 恢复。挂起用于系统休眠,恢复后无需重新 open。
void spi_set_bit_mode(hw_spi_dev spi, enum spi_mode mode)
运行时切换工作模式(如从 1bit 切到 2bit/4bit 读写 Flash)。
int spi_set_baud(hw_spi_dev spi, u32 baud) / u32 spi_get_baud(hw_spi_dev spi)
运行时设置/查询波特率,便于按从设备能力动态调速。
阻塞字节接口
u8 spi_recv_byte(hw_spi_dev spi, int *err):阻塞接收 1 字节,错误经err传出;int spi_send_byte(hw_spi_dev spi, u8 byte):阻塞发送 1 字节,返回错误码;u8 spi_send_recv_byte(hw_spi_dev spi, u8 byte, int *err):全双工——同时发送并接收 1 字节,返回接收值。仅SPI_MODE_BIDIR_1BIT下语义完整。
DMA 阻塞接口
int spi_dma_recv(hw_spi_dev spi, void *buf, u32 len):DMA 接收len字节到buf,阻塞等待完成;int spi_dma_send(hw_spi_dev spi, const void *buf, u32 len):DMA 发送len字节,阻塞等待完成。失败返回负数。
中断控制
void spi_set_ie(hw_spi_dev spi, u8 en):开/关中断;u8 spi_get_pending(hw_spi_dev spi):查询 PND(传输完成标志);void spi_clear_pending(hw_spi_dev spi):清除 PND;void hw_spix_irq_change_callback(hw_spi_dev spi, void (*cb)(hw_spi_dev, enum hw_spi_isr_status)):动态更换 ISR 回调,传NULL可移除。
中断传输接口(主从通用,不等待 PND)
void spi_byte_transmit_for_isr(hw_spi_dev spi, u8 tx_byte, u8 *rx_byte, u8 rw):中断方式收发 1 字节;rw=1收、rw=0发、全双工时rx_byte非 NULL;void spi_buf_transmit_for_isr(hw_spi_dev spi, u8 *tx_buf, u8 *rx_buf, int len, u8 rw):中断方式收发len字节;某方向不用传NULL;void spi_dma_transmit_for_isr(hw_spi_dev spi, void *buf, int len, u8 rw):中断方式 DMA 收发。
中断状态查询
enum hw_spi_isr_status hw_spix_get_isr_status(hw_spi_dev spi):返回SPI_WAITING_PND/SPI_TX_FINISH/SPI_RX_FINISH/SPI_PND_ERROR;void hw_spix_clear_isr_len(hw_spi_dev spi):每包数据前清空长度计数(通信中调用会导致长度统计不准);int hw_spix_get_isr_len(hw_spi_dev spi):返回通信长度,带符号:负数表示 DMA(-len),正数表示字节模式(+len);int hw_spix_slave_get_dma_len(hw_spi_dev spi):从机专用,返回当前已传输 DMA 个数,不区分收发方向(从机不开中断 DMA 模式时使用)。
软件 SPI API(spi_soft.h)
soft_spi_open/close/set_bit_mode/set_baud/get_baud/suspend/resume/send_byte/recv_byte/send_recv_byte/dma_recv/dma_send——签名与硬件 SPI 对应接口一致,spi_soft_dev 即 const int 设备号。soft_spi_set_bit_mode 的 data_wide 对应数据宽度,工作模式由 enum spi_soft_work_mode 四种 CPOL/CPHA 组合决定。注意 soft_spi_dma_* 为软件模拟,非真实 DMA。
失败模式、边界情况与并发
错误处理路径
spi_open返回负数:初始化失败(引脚冲突、模式不支持、波特率越界等),demo 中统一log_error后继续/退出。调用方必须检查返回值,否则后续传输在未初始化控制器上行为未定义;- DMA 发送返回负数:
spi_dma_send/spi_dma_recv失败时返回负错误码,demo 以ret < 0判定; SPI_PND_ERROR:ISR 状态机中的错误态,常见于从机在无主机时钟时超时、引脚配置错误或 4bit 模式误用在不支持的通道(SPI_MODE_UNIDIR_4BIT仅 SPI1/SPI2)。恢复方法:重新spi_clear_pending/hw_spix_clear_isr_len后重发该包。
边界情况
- 主机 CS 由外部 GPIO 管理:
port[5]对主机无效(demo 填0xff并注释 "主机不操作cs")。多从设备场景需自行维护各从设备的片选 GPIO 时序,SPI 驱动不提供 CS 仲裁; HW_SPI0不可用:枚举注释 "SPI0系统已使用",应用直接使用会与系统外设冲突;hw_spix_clear_isr_len必须在每包前调用:头文件明确警告"在通信过程中调用,会导致数据长不准"——中途清计数会使hw_spix_get_isr_len返回值不可信;hw_spix_get_isr_len带符号语义:负数为 DMA、正数为字节模式,调用方若直接当无符号长度用会得到超大值;- 从机必须预挂缓冲:从机没有发起传输的能力,
spi_buf_transmit_for_isr传入的rx_buf必须在主机时钟到达前就绪;demo 固定 256 字节长,超出部分行为未定义; - 字节级中断接口吞吐低:
spi_byte_transmit_for_isr每字节需等待一次SPI_TX_FINISH,100 字节数据产生 100 次轮询,不适合大数据包(demo 中该路径被#if 0屏蔽,默认走buf接口)。
并发与实时性
- 中断路径是单通道串行模型:同一
hw_spi_dev上发起新传输前必须确认前一包完成(SPI_TX_FINISH/SPI_RX_FINISH),SDK 未提供多任务队列,多任务并发调用同一通道需要应用层加互斥; - 轮询等待中的看门狗:所有
while (status != FINISH)循环内都调用wdt_clear(),避免长传输(尤其从机无时钟挂死时)触发 WDT 复位——这同时意味着从机在无主机时钟时会永久轮询,产品代码应改用中断回调或超时退出; - ISR 回调运行在中断上下文:
spi_isr_callback内禁止长时间阻塞/大内存操作,只应置标志或投递消息; - DMA 与 CPU 共享缓冲:DMA 传输期间
buf不能被应用修改或释放(demo 用栈上u8 buf[50],传输完成前函数不返回,安全;中断模式下则需保证缓冲生命周期跨越整个传输)。
性能与运维注意事项
- 硬件 SPI + DMA 是唯一的高吞吐路径:1bit/2bit/4bit 模式下 DMA 搬运由控制器完成,适合 Flash/LCD 批量数据;阻塞字节接口仅适合控制类小包;
- 波特率上限:
clk受系统时钟源约束,实际波特率以spi_get_baud返回为准;4bit 模式在相同clk下等效吞吐为 1bit 的 4 倍; - 软件 SPI 吞吐极低:GPIO 位翻转受 CPU 频率与 IO 翻转开销限制,且
soft_spi_dma_*不产生真实 DMA,仅适合低速器件或临时调试; - 调试技巧:demo 全程使用
log_info_hexdump打印收发缓冲,便于比对主机/从机数据一致性;spi_master_isr_callback_test打印sta可观察 ISR 状态迁移。
扩展点
- 动态切换模式/波特率:
spi_set_bit_mode+spi_set_baud支持运行时在 1bit↔2bit↔4bit 与不同速率间切换,是实现"慢速读 ID → 快速读数据"类 Flash 驱动的标准做法; - ISR 回调替换:
hw_spix_irq_change_callback允许按业务阶段更换回调(如先统计后搬运),无需重新spi_open; - 软件 SPI 实例化:
spi_soft_dev为const int设备号,可通过新增设备号在任意 IO 上创建额外 SPI 实例,弥补硬件通道不足; - 4bit 模式扩展:
SPI1_SUPPORT_UNIDIR_4BIT/SPI2_SUPPORT_UNIDIR_4BIT编译宏可裁剪 4bit 支持以节省资源,配合port[3]/port[4]的 D2/D3 引脚定义。
相关链接
- spi.h - 硬件 SPI 公共 API
- spi_hw.h - 通道枚举与芯片能力宏
- spi_demo.c - 主机/从机完整示例
- spi_soft.h - 软件模拟 SPI API
- spi_soft.c - 软件 SPI 实现
- 相关页面:GPIO 与引脚复用、时钟与波特率配置、DMA 控制器、外设设备模型(ioctl)