杰理 SDK 文档中心
首页
首页
  • 项目概览

    • AD23N SDK 概述与芯片平台
    • 工程结构与模块划分
  • 快速开始

    • 开发环境搭建与工具链
    • 编译构建指南
    • 烧录与固件升级工具
  • 应用框架与产品工作流

    • 应用入口与模式调度
    • 音乐播放应用
    • MIDI 解码与键盘演奏
    • 录音应用
    • LINEIN 与扩音应用
    • USB 从设备应用
    • 待机、软关机与空闲检测
    • 公共 UI 与 LED 显示
  • 音频子系统

    • 音频解码器框架
    • 音频编码器框架
    • 音效算法库
    • 音频管理与输出通路
  • 存储与文件系统

    • 文件系统层
    • NOR Flash 与虚拟机存储
    • 设备与设备管理
  • 系统服务与运行时

    • 消息机制与事件分发
    • 按键扫描与输入处理
    • 电源管理与低功耗控制
    • 定时器与系统任务
  • 外设驱动与平台

    • CPU 平台与启动流程
    • USB 协议栈与主机/设备驱动
    • SPI 与通用外设接口
  • 固件升级与构建工具

    • 固件升级机制
    • 编译后处理与镜像打包
    • 构建系统与命令行工具

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 的核心设计意图是灵活性优先:

  1. 引脚任意映射——spi_platform_data.port[6] 允许将 CLK/DO/DI/D2/D3/CS 分配到任意 IO(IO_PORTA_xx 等),未使用的引脚填 0xff,这使 PCB 布线约束大幅放宽;
  2. 模式可配置——支持全双工 1bit(BIDIR_1BIT)、半双工 1bit(UNIDIR_1BIT)、半双工 2bit(UNIDIR_2BIT)、半双工 4bit(UNIDIR_4BIT,仅 SPI1/SPI2)四种模式,对应 QSPI/DUAL 类 Flash 与普通外设;
  3. 传输方式可选——同一套 API 提供阻塞字节/缓冲、DMA、ISR 中断三种路径,开发者按实时性要求取舍;
  4. 极性与相位独立——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):

  1. hw_spix_clear_isr_len(spi) 清空长度计数("每包数据前清空,在通信过程中调用,会导致数据长不准");
  2. 调用 spi_dma_transmit_for_isr(spi, buf, len, rw) 或 spi_buf_transmit_for_isr(...) 启动传输,不等待;
  3. 轮询或回调中查询 hw_spix_get_isr_status(spi),直到变为 SPI_TX_FINISH(发送)或 SPI_RX_FINISH(接收);
  4. 用 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]),随后预先挂载接收缓冲,等待主机时钟:

  1. hw_spix_clear_isr_len(spi) 清长度计数;
  2. spi_buf_transmit_for_isr(spi, NULL, rx_buf, 256, 1) 挂接收缓冲(rw=1);
  3. 主机产生时钟,从机逐字节/逐 DMA 填充 rx_buf;
  4. 查询 hw_spix_get_isr_status(spi) == SPI_RX_FINISH 确认整包收完;
  5. 若需连续接收,重复步骤 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]u8IO_PORTA_xxCLK 引脚,任意 IO;未使用填 0xff
port[1]u8IO_PORTA_xxDO(主机发送/从机发送)引脚
port[2]u8IO_PORTA_xxDI(主机接收/从机接收)引脚
port[3]u8IO_PORTA_xxD2(wp),4bit 模式数据线
port[4]u8IO_PORTA_xxD3(hold),4bit 模式数据线
port[5]u80xffCS,仅从机使用;主机填 0xff
roleenum spi_roleSPI_ROLE_MASTER主/从角色
modeenum spi_modeSPI_MODE_BIDIR_1BIT数据宽度与收发模式
bit_modeenum spi_bit_modeSPI_FIRST_BIT_MSB位序(MSB/LSB/BIT3/BIT4)
cpolbit0空闲时钟电平:0 低 / 1 高
cphabit0采样沿:0 第一沿 / 1 第二沿
ie_enbit0中断使能:0 关 / 1 开
irq_priority5bit3中断优先级(0~31)
spi_isr_callback函数指针NULLISR 回调,ie_en=1 时有效
clku321000000波特率(Hz)

编译期使能宏(spi_hw.h)

宏默认值说明
SUPPORT_SPI00是否使能 SPI0(系统已占用,默认关闭)
SUPPORT_SPI11是否使能 SPI1
SPI1_SUPPORT_UNIDIR_4BIT1SPI1 是否支持 4bit 模式
SUPPORT_SPI21是否使能 SPI2
SPI2_SUPPORT_UNIDIR_4BIT1SPI2 是否支持 4bit 模式
HW_SPI_MAX_NUM3最大 SPI 通道数

软件 SPI 工作模式(spi_soft.h)

枚举值含义
SPI_CPOL0_CPHA0CPOL=0, CPHA=0, MSB first, 空闲低, 上升沿采样
SPI_CPOL0_CPHA1CPOL=0, CPHA=1, MSB first, 空闲低, 下降沿采样
SPI_CPOL1_CPHA0CPOL=1, CPHA=0, MSB first, 空闲高, 下降沿采样
SPI_CPOL1_CPHA1CPOL=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)
Prev
USB 协议栈与主机/设备驱动