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 芯片的寄存器访问协议和测距业务逻辑,实际传输效率远低于理论值。
下面为你拆解速率瓶颈,并提供一套商用级吞吐测试方案。
一、吞吐速率能达到多少?
- 理论极限 vs 实际有效值
指标 理论极限 (SPI 总线) 实际有效 (UWB 业务) 说明
SPI 时钟频率 20-40 MHz (DW系列上限) 同左 受限于 UWB 芯片电气特性,PCB 稍长即需降频
总线理论吞吐 40 Mbps (5 MB/s) - 仅计算时钟速率
实际有效吞吐 - 1-4 Mbps (0.125-0.5 MB/s) 关键指标:扣除协议头、寄存器延迟、业务空闲
- 为什么实际速率这么低?(瓶颈分析)
• 协议开销大:DW1000/DW3000 的 SPI 每次操作都需携带 1-3 字节的头部(地址+读写标志),实际数据 payload 效率仅约 70%-80% 。
• 业务非连续:数字钥匙是“配置+触发+读结果”模式,而非持续流传输。大量时间消耗在 dwt_write32()、dwt_read32() 等短指令上,SPI 的 DMA 优势无法完全发挥。
• 时序安全间隔:商用产品需在 SPI 操作间插入 delay_us 或等待状态位,防止芯片忙状态丢包,进一步拉低平均速率。
二、商用产品吞吐测试方法(全网精华汇总)
商用测试不仅测“快不快”,更要测“稳不稳”。建议按以下三个维度构建测试体系。
维度 1:软件基准测试(研发阶段)
目的:量化驱动层极限性能,排除应用层干扰。
方法:
-
构造测试帧:在 STM32 端开辟 1KB-4KB 的缓冲区,填充伪随机数。
-
循环压力测试:
◦ 使用高精度定时器(如 DWT->CYCCNT)打点。◦ 连续执行 N 次(如 1000 次)全双工 SPI 传输(模仿 UWB 读写)。
◦ 计算 Throughput = (N × 数据量 × 8) / 耗时(s)。
-
关键代码(示例):
// 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 速率。
方法:
-
连接:将逻辑分析仪探头接在 SCK, MOSI, MISO, CS 上。
-
测量:
◦ SCK 实际频率:确认是否达到配置值(如 20 MHz)。◦ CS 低电平时间:计算单次传输的有效数据时长。
◦ 字节间隔:检查 DMA 是否真正实现“背靠背”传输,还是存在 CPU 介入导致的间隙。
-
商用重点:在高温/低温环境下,观察 SCK 边沿是否清晰,是否存在因信号完整性导致的“降频”现象。
维度 3:业务场景仿真测试(系统级)
目的:模拟数字钥匙真实工作流,得到最真实的吞吐数据。
方法:
-
构建测试用例:
◦ 测距吞吐:模拟 1 秒内连续与 10 个锚点测距,统计 SPI 总通信量。◦ 固件升级吞吐:模拟通过 SPI 向 UWB 芯片写入 64KB 固件镜像,计算升级时间。
-
注入异常:
◦ 在 SPI 传输中随机插入 1ms 延时,模拟系统繁忙场景,测试吞吐下降曲线。◦ 统计丢包率/误码率(需在 MISO 回环写入特定校验码)。
维度 4:边界与压力测试(认证阶段)
目的:验证产品在最恶劣条件下的稳定性。
方法:
- 频率边界:从 1 MHz 逐步提升至芯片标称最大值(如 38 MHz),记录出现首个误码的频率点。
- 温度边界:在高低温箱(-40°C ~ +85°C)中运行吞吐测试,确认速率无突变。
- 并发压力:在 STM32 同时处理 BLE 中断和 UWB 测距时,测量 SPI 吞吐的抖动情况。
三、给你的数字钥匙项目建议
- 不要追求极致 SPI 速率:UWB 测距对时间戳精度要求极高,稳定性远大于吞吐量。建议将 SPI 时钟稳定在 10-20 MHz,确保长导线或车载环境下的信号质量。
- 测试重点:作为商用产品,你的测试报告应重点包含 “业务场景仿真测试” 的数据,例如:“单次测距 SPI 交互时长 ≤ 2ms”。
- 优化方向:若发现吞吐瓶颈,优先优化 SPI 事务合并(将多个寄存器读写合并为一个长帧),而非单纯提高时钟频率。
如果你能提供具体的 STM32 型号(如 H7/F4)和 UWB 芯片型号(DW3000/NCJ29D5),我可以为你提供更精确的 SPI 分频配置与 DMA 配置代码。

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 清晰

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种,一种是获取,另一种是结束。

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


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);
硬件会:
-
锁定 SPIS 资源
-
允许使用你之前设置的:
nrf_spis_tx_buffer_set(...)
nrf_spis_rx_buffer_set(...) -
等待主机(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 时序图」
浙公网安备 33010602011771号