nordic的SPI问题整理1

用SPI 收发数据的时候,经常遇到无法产生 EVENT,导致程序死在 while, 过不去的情况,SPI 引脚设置,和初始化都无异常,已使能.

`uint8_t SPI1_SendByte(uint8_t data)
{
uint8_t rx_data;

NRF_SPI1->TXD = data; // 发送数据
while (NRF_SPI1->EVENTS_READY == 0); // 等待传输完成
NRF_SPI1->EVENTS_READY = 0; // 清除事件标志

rx_data = (uint8_t)NRF_SPI1->RXD; // 读取接收到的数据
return rx_data;
}`
现已查明原因,确实是电路的原因:SCK 引脚被拉高了。导致无法正常产生 clock 信号,于是 TXD 寄存器中的数据无法移位出去,也就无法产生 Event.

STM32用SPI通信接口和UWB接口进行通信,吞吐速率,产品的吞吐测试的方法
针对你的数字钥匙场景(STM32 + UWB,如 DW1000/DW3000),实际有效吞吐量通常在 1-4 Mbps(约 0.125-0.5 MB/s)。虽然 SPI 时钟可以开到 20-40 MHz,但受限于 UWB 芯片的寄存器访问协议和测距业务逻辑,实际传输效率远低于理论值。

下面为你拆解速率瓶颈,并提供一套商用级吞吐测试方案。

一、吞吐速率能达到多少?

  1. 理论极限 vs 实际有效值

指标 理论极限 (SPI 总线) 实际有效 (UWB 业务) 说明

SPI 时钟频率 20-40 MHz (DW系列上限) 同左 受限于 UWB 芯片电气特性,PCB 稍长即需降频

总线理论吞吐 40 Mbps (5 MB/s) - 仅计算时钟速率

实际有效吞吐 - 1-4 Mbps (0.125-0.5 MB/s) 关键指标:扣除协议头、寄存器延迟、业务空闲

  1. 为什么实际速率这么低?(瓶颈分析)

• 协议开销大:DW1000/DW3000 的 SPI 每次操作都需携带 1-3 字节的头部(地址+读写标志),实际数据 payload 效率仅约 70%-80% 。

• 业务非连续:数字钥匙是“配置+触发+读结果”模式,而非持续流传输。大量时间消耗在 dwt_write32()、dwt_read32() 等短指令上,SPI 的 DMA 优势无法完全发挥。

• 时序安全间隔:商用产品需在 SPI 操作间插入 delay_us 或等待状态位,防止芯片忙状态丢包,进一步拉低平均速率。

二、商用产品吞吐测试方法(全网精华汇总)

商用测试不仅测“快不快”,更要测“稳不稳”。建议按以下三个维度构建测试体系。

维度 1:软件基准测试(研发阶段)

目的:量化驱动层极限性能,排除应用层干扰。

方法:

  1. 构造测试帧:在 STM32 端开辟 1KB-4KB 的缓冲区,填充伪随机数。

  2. 循环压力测试:
    ◦ 使用高精度定时器(如 DWT->CYCCNT)打点。

    ◦ 连续执行 N 次(如 1000 次)全双工 SPI 传输(模仿 UWB 读写)。

    ◦ 计算 Throughput = (N × 数据量 × 8) / 耗时(s)。

  3. 关键代码(示例):
    // STM32 HAL 示例
    uint32_t start = DWT->CYCCNT;
    for (int i = 0; i < 1000; i++) {
    HAL_SPI_TransmitReceive(&hspi, tx_buf, rx_buf, 1024, 1000);
    }
    uint32_t end = DWT->CYCCNT;
    float mbps = (1000.0 * 1024 * 8) / ((end - start) / (SystemCoreClock / 1e6));
    printf("SPI Throughput: %.2f Mbps\n", mbps);

    参考自嵌入式 SPI 吞吐量基准测试方法 。

维度 2:逻辑分析仪/示波器测试(硬件验证)

目的:抓取真实波形,分析信号完整性与实际 SCK 速率。

方法:

  1. 连接:将逻辑分析仪探头接在 SCK, MOSI, MISO, CS 上。

  2. 测量:
    ◦ SCK 实际频率:确认是否达到配置值(如 20 MHz)。

    ◦ CS 低电平时间:计算单次传输的有效数据时长。

    ◦ 字节间隔:检查 DMA 是否真正实现“背靠背”传输,还是存在 CPU 介入导致的间隙。

  3. 商用重点:在高温/低温环境下,观察 SCK 边沿是否清晰,是否存在因信号完整性导致的“降频”现象。

维度 3:业务场景仿真测试(系统级)

目的:模拟数字钥匙真实工作流,得到最真实的吞吐数据。

方法:

  1. 构建测试用例:
    ◦ 测距吞吐:模拟 1 秒内连续与 10 个锚点测距,统计 SPI 总通信量。

    ◦ 固件升级吞吐:模拟通过 SPI 向 UWB 芯片写入 64KB 固件镜像,计算升级时间。

  2. 注入异常:
    ◦ 在 SPI 传输中随机插入 1ms 延时,模拟系统繁忙场景,测试吞吐下降曲线。

    ◦ 统计丢包率/误码率(需在 MISO 回环写入特定校验码)。

维度 4:边界与压力测试(认证阶段)

目的:验证产品在最恶劣条件下的稳定性。

方法:

  1. 频率边界:从 1 MHz 逐步提升至芯片标称最大值(如 38 MHz),记录出现首个误码的频率点。
  2. 温度边界:在高低温箱(-40°C ~ +85°C)中运行吞吐测试,确认速率无突变。
  3. 并发压力:在 STM32 同时处理 BLE 中断和 UWB 测距时,测量 SPI 吞吐的抖动情况。

三、给你的数字钥匙项目建议

  1. 不要追求极致 SPI 速率:UWB 测距对时间戳精度要求极高,稳定性远大于吞吐量。建议将 SPI 时钟稳定在 10-20 MHz,确保长导线或车载环境下的信号质量。
  2. 测试重点:作为商用产品,你的测试报告应重点包含 “业务场景仿真测试” 的数据,例如:“单次测距 SPI 交互时长 ≤ 2ms”。
  3. 优化方向:若发现吞吐瓶颈,优先优化 SPI 事务合并(将多个寄存器读写合并为一个长帧),而非单纯提高时钟频率。

如果你能提供具体的 STM32 型号(如 H7/F4)和 UWB 芯片型号(DW3000/NCJ29D5),我可以为你提供更精确的 SPI 分频配置与 DMA 配置代码。

image

nordic的spi slave驱动功能

下面我把你贴的 Nordic SPIS(SPI Slave)驱动源码 做一个工程级、可落地的整理,重点放在 “SPI 作为从设备时,这套代码到底做了什么、怎么用、核心机制是什么”,而不是逐行翻译。

一、这份代码是干什么的?(一句话)

这是 Nordic nRF52 系列的 SPI Slave(SPIS)HAL 驱动源码

👉 用于让 nRF 芯片作为 SPI 从机,被 STM32 / SoC / MCU 作为 SPI 主机 来访问。

在你 数字钥匙项目(STM32 主控 + Nordic BLE + UWB) 中:

• STM32 = SPI Master

• Nordic nRF52832 = SPI Slave

• 这份代码就是 Nordic 侧的 SPI 从机驱动

二、核心功能总结(重点)

✅ 1. SPI 从机角色(Slave)

项目 说明

角色 SPI Slave(被主机访问)

通信方式 全双工

数据交换 主机发起 → 从机响应

典型用途 STM32 通过 SPI 给 Nordic 发命令 / 读状态 / 传密钥

✅ 2. 支持的核心能力

🔹 支持多个 SPIS 实例

SPIS0 / SPIS1 / SPIS2

通过宏 SPIS0_ENABLED 等控制是否启用。

🔹 支持 EasyDMA(关键)

• 使用 SPIS 硬件 DMA

• 不需要 CPU 逐字节搬运

• 支持:

• TX 缓冲区(从机发送给主机)

• RX 缓冲区(从机接收主机数据)

🔹 事件驱动(中断方式)

SPIS 不会轮询,而是 中断触发,通过回调函数通知应用:

事件 含义

NRF_DRV_SPIS_BUFFERS_SET_DONE 缓冲区设置完成

NRF_DRV_SPIS_XFER_DONE 一次 SPI 传输完成

🔹 状态机(核心设计)

SPIS_STATE_INIT
SPIS_BUFFER_RESOURCE_REQUESTED
SPIS_BUFFER_RESOURCE_CONFIGURED
SPIS_XFER_COMPLETED

👉 SPIS 不是“随时都能收发”
👉 必须按状态机流程走

✅ 3. 工作流程(最重要)

📌 正确的一次 SPI Slave 通信流程

① 初始化 SPIS

nrf_drv_spis_init(&spis_instance, &config, event_handler);

• 配置:

• SCK / MOSI / MISO / CSN

• SPI 模式(MODE0~3)

• 位序(MSB/LSB)

• 默认字符(DEF / ORC)

② 设置 TX / RX 缓冲区(必须)

nrf_drv_spis_buffers_set(
&spis_instance,
tx_buf, tx_len,
rx_buf, rx_len
);

⚠️ 这一步非常关键

• 必须在 SPI 被选中之前 设置好缓冲区

• 缓冲区 必须在 RAM 中(EasyDMA 要求)

• 设置完成后,SPIS 进入 SPIS_BUFFER_RESOURCE_CONFIGURED

③ 主机拉低 CS,开始 SPI 传输

• 主机(STM32)拉低 CS

• Nordic SPIS 触发 ACQUIRED 事件

• 自动使用你设置的 TX/RX 缓冲区

• 传输完成后触发 END 事件

④ 回调函数中收到事件

void spis_event_handler(nrf_drv_spis_event_t event)
{
if (event.evt_type == NRF_DRV_SPIS_XFER_DONE)
{
// event.rx_amount // 主机发给从机的数据长度
// event.tx_amount // 从机发给主机的数据长度
}
}

✅ 4. CS(片选)如何处理?

🔹 硬件自动 CS(推荐)

• CS 引脚由 SPIS 硬件监控

• 当主机拉低 CS:

• 触发 ACQUIRED

• 当主机拉高 CS:

• 触发 END

✅ 不需要你手动拉 CS

🔹 nRF52 异常 109 的 workaround(你代码中看到的部分)

define USE_DMA_ISSUE_WORKAROUND

• 针对 nRF52 DMA 在特定 CS 时序下的 Bug

• 用 GPIOTE 检测 CS 下降沿

• 防止 DMA 在 CS 拉低瞬间出错

👉 这是商用级代码才有的细节

✅ 5. 你这段代码的“核心 API”总结

API 作用

nrf_drv_spis_init() 初始化 SPI Slave

nrf_drv_spis_buffers_set() 设置 TX/RX 缓冲区

nrf_drv_spis_uninit() 反初始化

nrf_drv_spis_event_handler_t 事件回调

✅ 6. 对你项目的实际意义(数字钥匙)

✅ STM32 ↔ Nordic(SPI)

• STM32 是 SPI Master

• Nordic 是 SPI Slave

• 这份代码就是 Nordic 侧的 SPI 从机驱动

✅ 典型通信内容

方向 内容

STM32 → Nordic BLE 参数、UWB 配置、密钥

Nordic → STM32 状态、测距结果、ACK

七、一句话总结(记住这句)

这份代码实现了 Nordic nRF52832 的 SPI Slave 功能,通过 EasyDMA + 中断 + 状态机,让 Nordic 能被 STM32 当作 SPI 外设访问,是数字钥匙中 STM32 ↔ Nordic 通信的核心底层驱动。

如果你愿意,我可以继续帮你:

• ✅ 画一张 STM32(Master)↔ Nordic(Slave)SPI 通信时序图

• ✅ 给你 STM32 作为 SPI Master 的配套代码

• ✅ 帮你分析 为什么 SPIS 必须“先 set buffer 再等 CS”

• ✅ 帮你排查 SPI 通信偶发失败 / 数据错位

你可以直接说:
👉「我想看 STM32 侧怎么配 SPI 和 Nordic 对接」

nordic的spi寄存器模型
CS(片选)如何处理?
🔹 硬件自动 CS(推荐)

CS 引脚由 SPIS 硬件监控

当主机拉低 CS:

触发 ACQUIRED

当主机拉高 CS:

触发 END

✅ 不需要你手动拉 CS

SPI是不是只能主机写从机?或者只能主机读从机 ?所有的操作不能是从机发起操作,只能主机发起操作,是吗?

SPIS 不是“随时都能收发”,必须按状态机流程走

nordic的SPI使用流程

1,初始化配置:在这个步骤上和STM32是一样的

SCK / MOSI / MISO / CSN

SPI 模式(MODE0~3)

位序(MSB/LSB)

默认字符(DEF / ORC)

2,nordic采用中断回调来处理传输完成事件
void spis_event_handler(nrf_drv_spis_event_t event) { if (event.evt_type == NRF_DRV_SPIS_XFER_DONE) { spis_xfer_done = true; NRF_LOG_INFO(" Transfer completed. Received: %s\r\n",(uint32_t)m_rx_buf); } }

3,Nordic 的“任务 / 事件”模型介绍
Nordic 的 SPI 不是“写数据就发”,而是:
写寄存器 → 触发 TASK → 等待 EVENT

为什么 Nordic 要这样设计?
✅ 原因 1:低功耗 + 事件驱动

Nordic 面向 BLE / 低功耗

用 TASK / EVENT​ 可以让 CPU 休眠

DMA 自动完成

✅ 原因 2:SPIS(从机)必须这样

SPIS 不知道主机什么时候发

必须:

先设置缓冲区

等 ACQUIRED

再等 END

STM32 的 SPI Slave 模型远不如 Nordic 清晰
image

nordic的SPI slave中的任务指的是什么 ?
`/**

  • @brief SPIS tasks.
    /
    typedef enum
    {
    /
    lint -save -e30/
    NRF_SPIS_TASK_ACQUIRE = offsetof(NRF_SPIS_Type, TASKS_ACQUIRE), ///< Acquire SPI semaphore.
    NRF_SPIS_TASK_RELEASE = offsetof(NRF_SPIS_Type, TASKS_RELEASE), ///< Release SPI semaphore, enabling the SPI slave to acquire it.
    /
    lint -restore*/
    } nrf_spis_task_t;

/**

  • @brief SPIS events.
    /
    typedef enum
    {
    /
    lint -save -e30/
    NRF_SPIS_EVENT_END = offsetof(NRF_SPIS_Type, EVENTS_END), ///< Granted transaction completed.
    NRF_SPIS_EVENT_ACQUIRED = offsetof(NRF_SPIS_Type, EVENTS_ACQUIRED) ///< Semaphore acquired.
    /
    lint -restore*/
    } nrf_spis_event_t;
    `
    任务只有2种,一种是获取,另一种是结束。

image

nordic的事件机制是怎么体现的 ?有哪些事件 ?
image

image

nordic的SPI slave传输是怎么启动的

这是一个非常精准、也是很多人第一次看 Nordic SPIS 驱动都会产生的疑问。
你抓住了 SPIS(SPI Slave)最核心、也是最“反直觉”的一点。

SPI Slave 的“传输启动”,不是 START,而是 ACQUIRE。

下面我从 “为什么没有 START”、“ACQUIRE 是什么”、“代码上怎么触发传输” 三个层次给你彻底讲清楚。

一、先给结论(最重要)

✅ SPI Slave 不能自己“启动传输”

✅ SPI Slave 只能“申请使用权(ACQUIRE)”

✅ 真正的传输是由 SPI Master(STM32)拉 CS 触发的

👉 NRF_SPIS_TASK_ACQUIRE ≠ 启动 SPI 传输
👉 它是:告诉硬件“我已经准备好缓冲区了,可以用了”

二、为什么 SPI Slave 没有 START?

✅ SPI Master vs SPI Slave 的根本区别

角色 能否主动发起传输

SPI Master ✅ 可以(拉 CS + 发时钟)

SPI Slave ❌ 不可以

SPI Slave 是被动设备:

• 时钟是主机给的

• CS 是主机控制的

• 数据是在 CS 拉低之后 才开始交换的

所以:

SPIS 不需要、也不存在 “START 传输” 的任务

三、那 ACQUIRE 到底是什么?

1️⃣ ACQUIRE 的真实含义

NRF_SPIS_TASK_ACQUIRE = offsetof(NRF_SPIS_Type, TASKS_ACQUIRE)

👉 ACQUIRE = 申请 SPI 总线使用权

在 Nordic 的 SPIS 设计中:

• SPI 外设是 共享资源

• 在“多角色 / 低功耗”系统中:

• 你必须先 申请(ACQUIRE)

• 才能使用 TX / RX 缓冲区

• 否则 SPIS 会拒绝使用你设置的 buffer

2️⃣ ACQUIRE 做了什么?

当你调用:
nrf_spis_task_trigger(NRF_SPIS0, NRF_SPIS_TASK_ACQUIRE);

硬件会:

  1. 锁定 SPIS 资源

  2. 允许使用你之前设置的:
    nrf_spis_tx_buffer_set(...)
    nrf_spis_rx_buffer_set(...)

  3. 等待主机(STM32)拉低 CS

⚠️ 此时并没有开始传输!

四、SPI Slave 的“传输到底是怎么启动的?”

✅ 真正的启动条件是:CS 拉低

时序是这样的:

STM32 拉低 CS

Nordic SPIS 检测到 CS 拉低

硬件触发 EVENT_ACQUIRED

SPIS 自动开始使用 TX / RX buffer

SPI 时钟到来 → 数据交换

传输结束 → EVENT_END

👉 整个过程中,Nordic 没有“启动”任何东西

✅ 代码层面发生了什么?

1️⃣ 你设置缓冲区(DMA)

nrf_spis_tx_buffer_set(NRF_SPIS0, tx_buf, tx_len);
nrf_spis_rx_buffer_set(NRF_SPIS0, rx_buf, rx_len);

2️⃣ 你申请使用权(ACQUIRE)

nrf_spis_task_trigger(NRF_SPIS0, NRF_SPIS_TASK_ACQUIRE);

3️⃣ 主机(STM32)拉低 CS

HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);

4️⃣ Nordic 硬件自动触发事件

NRF_SPIS_EVENT_ACQUIRED
NRF_SPIS_EVENT_END

5️⃣ 你在回调中拿到数据

void spis_event_handler(nrf_drv_spis_event_t event)
{
if (event.evt_type == NRF_DRV_SPIS_XFER_DONE)
{
// rx_buf 里是 STM32 发来的数据
}
}

五、为什么 Nordic 要设计成 ACQUIRE 而不是直接“准备好”?

✅ 原因 1:防冲突(资源共享)

• SPIS 可能:

• 被 BLE 协议栈使用

• 被低功耗管理

• ACQUIRE 是一种 互斥机制

✅ 原因 2:DMA 安全

• 必须在 ACQUIRE 之后

• 才能确保 DMA 使用的是当前有效的 buffer

• 防止主机突然拉 CS 时,buffer 还没准备好

✅ 原因 3:低功耗

• 未 ACQUIRE 时:

• SPIS 不监听 CS

• 不消耗 DMA 资源

• ACQUIRE 后:

• 硬件才进入“可被选中”的状态

六、一句话总结(记住这句)

Nordic SPI Slave 没有 START,因为它不能主动发起传输;

它只有 ACQUIRE —— 告诉硬件“我已准备好缓冲区,等主机拉 CS”;

真正的 SPI 传输,是 STM32 拉 CS 那一刻才开始的。

七、如果你愿意,我可以继续帮你:

• ✅ 画一张 “SPIS ACQUIRE → CS → EVENT 时序图”

• ✅ 解释 为什么 SPIS 必须“先 ACQUIRE 再等 EVENT”

• ✅ 帮你分析 为什么没 ACQUIRE 会收到 0x00 / 0xFF

• ✅ 给你 STM32 ↔ Nordic SPIS 的“最小可用示例”

你可以直接说:
👉「我要 SPIS ACQUIRE 时序图」

posted @ 2026-04-17 17:45  wzm888  阅读(39)  评论(0)    收藏  举报