nordic开发经验
1,nordic和stm32不一样,应用程序和协议栈要分开来下载,步骤明确,要先下载BLE协议栈代码。
是的,你的理解完全正确。
Nordic 协议栈架构
┌─────────────────────────────────────────┐
│ nRF52840/5340 Flash 布局 │
├─────────────────────────────────────────┤
│ 0x0000_0000 - 0x0002_6000 (152KB) │
│ ┌─────────────────────────────────┐ │
│ │ SoftDevice (S140 v7.x) │ │
│ │ • BLE 协议栈(不可修改) │ │
│ │ • 烧录到固定地址,只读 │ │
│ │ • Nordic 提供 hex/bin 文件 │ │
│ └─────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ 0x0002_6000 - 0x000F_8000 (848KB) │
│ ┌─────────────────────────────────┐ │
│ │ Application Code (你的代码) │ │
│ │ • main.c │ │
│ │ • SPI 驱动 │ │
│ │ • CCC OOB 协议实现 │ │ ← 只改这里
│ │ • UWB 参数协商逻辑 │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
关键要点
| 项目 | 说明 |
|---|---|
| SoftDevice 地址 | 固定 0x0000_0000,不可更改 |
| SoftDevice 是否可修改 | ❌ 不可修改,Nordic 封闭源码 |
| Application 起始地址 | 由 SoftDevice 头文件自动配置(通常是 0x26000) |
| 你需要编写的代码 | 仅 Application 层 |
| UWB 参数协商 | 在 Application 中实现,通过 API 调用 SoftDevice |
代码结构示意
// 你的代码(Application)- 需要编写
#include "nrf_sdh.h" // SoftDevice 头文件(Nordic 提供)
#include "nrf_sdh_ble.h" // BLE API(调用 SoftDevice)
#include "ble_gap.h" // GAP API
// UWB 参数协商 - 你的实现
void ccc_uwb_negotiation(void) {
// 1. 调用 SoftDevice API 发送 BLE 数据
sd_ble_gattc_write(conn_handle, &write_params);
// 2. 处理收到的 UWB 配置(你的解析逻辑)
parse_uwb_config(notification_data);
// 3. 通过 SPI 上报 STM32(你的驱动)
spi_send_to_stm32(uwb_config);
}
开发流程
1. 下载 SoftDevice hex 文件到 0x0000_0000
(使用 nRF Connect Programmer 或 J-Link)
2. 编写你的 Application 代码
3. 编译链接,生成 Application hex
(起始地址自动从 0x26000 开始)
4. 下载 Application 到 0x0002_6000
5. 运行:SoftDevice 初始化 → 跳转到 Application
一句话总结
SoftDevice 是固定烧录的"黑盒子",你只需编写 Application 代码,通过 Nordic 提供的 API 调用 BLE 功能,UWB 参数协商完全在 Application 中实现。
2,开发环境搭建
https://blog.csdn.net/qq_36347513/article/details/103531213
清风电子社区
http://www.qfv8.com/forum.php?mod=forumdisplay&fid=53
SPI从机调试
nodic52832提供作为SPI从机的能直接跑通的代码了吗
有,Nordic 官方 SDK 里已经带了可直接编译跑通的 SPI 从机(SPIS)示例,nRF52832 可以直接用。
1. 官方示例位置(nRF5 SDK)
以常用的 nRF5 SDK v17.1.0 为例:
examples/peripheral/spis/
- 工程:
pca10040(nRF52832 DK) - 编译:Keil / GCC / SES 均可直接编译
- 依赖:开启
SPIS_ENABLED、SPIS0_ENABLED,无需其他外设
2. 极简可直接跑通代码(基于 SDK)
#include "nrf_drv_spis.h"
#include "nrf_log.h"
#include "app_error.h"
#define SPIS_INSTANCE 0
static const nrf_drv_spis_t m_spis = NRF_DRV_SPIS_INSTANCE(SPIS_INSTANCE);
// 缓冲区(必须全局/静态,RAM)
static uint8_t m_tx_buf[16] = {0x11,0x22,0x33,0x44};
static uint8_t m_rx_buf[16];
// 事件回调:传输完成
static void spis_event_handler(nrf_drv_spis_event_t event)
{
if (event.evt_type == NRF_DRV_SPIS_XFER_DONE)
{
NRF_LOG_INFO("RX: %d bytes", event.rx_amount);
// 重新设置缓冲区,准备下一次传输
APP_ERROR_CHECK(nrf_drv_spis_buffers_set(
&m_spis, m_tx_buf, sizeof(m_tx_buf), m_rx_buf, sizeof(m_rx_buf)));
}
}
// SPI从机初始化
static void spis_init(void)
{
nrf_drv_spis_config_t cfg = NRF_DRV_SPIS_DEFAULT_CONFIG;
cfg.csn_pin = 4; // CSN
cfg.miso_pin = 5; // MISO
cfg.mosi_pin = 6; // MOSI
cfg.sck_pin = 7; // SCK
cfg.mode = NRF_DRV_SPIS_MODE_0; // 与主机一致
cfg.bit_order = NRF_DRV_SPIS_BIT_ORDER_MSB_FIRST;
APP_ERROR_CHECK(nrf_drv_spis_init(&m_spis, &cfg, spis_event_handler));
// 第一次设置缓冲区
APP_ERROR_CHECK(nrf_drv_spis_buffers_set(
&m_spis, m_tx_buf, sizeof(m_tx_buf), m_rx_buf, sizeof(m_rx_buf)));
}
int main(void)
{
// 初始化日志、时钟等
NRF_LOG_INIT(NULL);
NRF_LOG_DEFAULT_BACKENDS_INIT();
spis_init();
while (1)
{
__WFE(); // 休眠,等待SPI传输
}
}
3. 关键配置(sdk_config.h)
#define SPIS_ENABLED 1
#define SPIS0_ENABLED 1
#define NRF_LOG_ENABLED 1
4. 常见坑(直接能跑的前提)
- 缓冲区必须全局/静态(不能栈上),否则 DMA 会硬fault。
- 模式/位序与主机一致(CPOL/CPHA、MSB/LSB)。
- SoftDevice 不影响 SPIS:S112/S113/S132 均可同时用 SPIS。
- DK 引脚:PCA10040 上默认 SPI 引脚是 4/5/6/7,可直接用。
5. 要不要我给你整理一份“nRF52832 SPI 主机+从机”的可直接编译工程包(含 Keil 工程、sdk_config、引脚分配),你直接下载就能烧录测试?

蓝牙协商UWB的参数,使用GATT时,UUID是蓝牙的标准SIG中的UUID,还是用户自定义的UUID ?
结论:不是蓝牙 SIG 标准 UUID,必须使用用户自定义 UUID。
核心依据
-
标准 UUID 定义:查阅蓝牙 SIG 官方《Assigned Numbers》文档,
0x2A02、0x2A03、0x2A04均属于 GAP(通用访问配置文件)的标准特征 UUID。0x2A02:Peripheral Privacy Flag(外设隐私标志)0x2A03:Reconnection Address(重连接地址)0x2A04:Peripheral Preferred Connection Parameters(外设首选连接参数)
这些标准特征是蓝牙协议预留的,用于固定的底层连接管理,绝不能被挪作 UWB 参数协商这种应用层功能,否则会导致协议栈异常。
-
行业实践:所有 UWB 相关的蓝牙通信方案(如室内定位、测距配置),在 GATT 层定义服务和特征时,全部使用厂商自定义的 128-bit UUID。
例如,常见的自定义 UUID 格式为6E4000xx-B5A3-F393-E0A9-E50E24DCCA9E等厂商特定格式。
正确做法
你需要为你的 UWB 配置服务和特征分配唯一的 128-bit UUID,并确保通信双方的定义一致。
代码示例
// 自定义服务 UUID (示例)
#define UUID_UWB_SERVICE 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00
// 自定义特征 UUID (示例)
#define UUID_UWB_CHAR_CONFIG 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0xAA, 0x01, 0x00, 0x00
#define UUID_UWB_CHAR_CONTROL 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0xAA, 0x02, 0x00, 0x00
#define UUID_UWB_CHAR_RANGING 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0xAA, 0x03, 0x00, 0x00
总结
使用标准 UUID 会破坏蓝牙协议栈的正常逻辑,导致设备无法被正确识别或连接异常。请务必使用自定义 UUID 来实现 UWB 参数协商功能。
要不要我给你提供一份基于 nRF SDK 的完整 UWB 服务 GATT 初始化代码模板,包含自定义 UUID、属性读写权限和通知配置?
为什么 ble_uuid_t 能同时表示 16-bit 和 128-bit UUID

nordic在SPI的读写是怎么实现的 ?和stm32的区别是什么 ?
Nordic(nRF52832)的 SPI 读写,核心是硬件 SPIM/SPIS + EasyDMA + 任务/事件驱动;而 STM32 是SPI 外设 + 独立 DMA + 状态寄存器轮询/中断,两者在寄存器模型、DMA 集成、CS 管理和低功耗适配上差异很大。
一、Nordic SPI 读写实现(nRF52832)
1. 硬件模块:SPI / SPIM / SPIS
- SPI:基础主机,无 DMA,适合小数据、阻塞式。
- SPIM:主机 + EasyDMA(核心),自动搬移 RAM ↔ 外设,CPU 只需触发任务、等待事件。
- SPIS:从机 + EasyDMA,支持双向缓冲。
- 引脚:SCK、MOSI、MISO、CS(可任意 GPIO 映射)。
2. 读写流程(SPIM + EasyDMA,最常用)
- 配置:引脚、模式(CPOL/CPHA)、波特率、位序;注册TX/RX 缓冲区到 EasyDMA。
- 触发传输:写
TASKS_START,硬件自动发时钟、从TXD缓冲区逐位移出、同时将 MISO 数据移入RXD缓冲区。 - 完成通知:传输结束触发
EVENTS_END(中断或轮询),CPU 可直接处理RXD数据,无需干预移位。 - 停止:写
TASKS_STOP,触发EVENTS_STOPPED。
3. 关键寄存器(任务/事件模型)
- TASKS_START:启动传输(写 1 触发)。
- EVENTS_END:传输完成(读 1 表示完成)。
- TXD.MAXCNT/RXD.MAXCNT:DMA 传输长度(1~255 字节)。
- TXD.PTR/RXD.PTR:DMA 缓冲区地址(需 RAM 区)。
4. 代码示例(阻塞式读写)
nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(0);
nrf_drv_spi_config_t cfg = NRF_DRV_SPI_DEFAULT_CONFIG;
cfg.sck_pin = 5;
cfg.mosi_pin = 6;
cfg.miso_pin = 7;
cfg.cs_pin = 8; // 硬件CS,或用软件GPIO
nrf_drv_spi_init(&spi, &cfg, NULL, NULL);
uint8_t tx[] = {0x01, 0x02};
uint8_t rx[2];
// 1次全双工传输:发tx、收rx
nrf_drv_spi_transfer(&spi, tx, 2, rx, 2);
二、与 STM32 SPI 的核心区别
1. 架构与模型
- Nordic:任务/事件驱动 + 内置 EasyDMA。
无状态寄存器轮询,CPU 只发“开始”、等“完成”,数据全由 DMA 自动搬移,低功耗友好(传输时 CPU 可休眠)。 - STM32:状态寄存器 + 独立 DMA 控制器。
需轮询SPI_SR(TXE/RXNE/BSY)或开 DMA 中断;DMA 是独立外设,需额外配置映射、通道、优先级。
2. DMA 集成
- Nordic EasyDMA:
- 无缝集成 SPI,无需额外 DMA 配置,只需指定缓冲区地址和长度。
- 支持字节自动打包/解包,仅占用 1 个中断(传输完成)。
- 限制:缓冲区必须在 RAM(不能是 Flash),长度 ≤255 字节。
- STM32 DMA:
- 独立外设,需配置
DMA_CCR、DMA_CPAR、DMA_CMAR等。 - 支持多通道、多外设共享,长度无严格限制(受内存约束)。
- 中断多(TC/TE/HT),需手动处理中断标志。
- 独立外设,需配置
3. 片选(CS/NSS)管理
- Nordic:
- 硬件 CS:SPIM 可自动拉/拉低 CS,传输时序严格对齐。
- 软件 CS:任意 GPIO,灵活但需手动控制时序。
- STM32:
- 硬件 NSS:仅支持主模式下的从机选择输入(多主机),主机输出 NSS 需软件控制。
- 常用软件 CS:GPIO 模拟,兼容性强但时序需手动保证。
4. 低功耗与中断
- Nordic:
- 传输时 CPU 可进入 WFE/WFI 休眠,仅 DMA 工作,功耗极低。
- 单中断(
EVENTS_END),中断开销小。
- STM32:
- 传输时 CPU 通常需保持运行(轮询)或开 DMA 中断,休眠支持有限。
- 中断源多(SPI + DMA),需手动清除标志,开销大。
5. 数据处理
- Nordic:全双工同步完成,TX/RX 长度必须相等(或忽略 RX)。
- STM32:支持非对称 TX/RX(如发 1 字节、收 4 字节),需分两次传输或用 DMA 链表。
三、核心差异对照表
| 维度 | Nordic(nRF52832 SPIM) | STM32(SPI + DMA) |
|---|---|---|
| 驱动模型 | 任务/事件,无轮询 | 状态寄存器轮询/中断 |
| DMA | 内置 EasyDMA,零配置 | 独立 DMA 控制器,复杂配置 |
| CS 管理 | 硬件自动/软件 GPIO | 仅软件 GPIO(常用) |
| 低功耗 | 传输时 CPU 可休眠 | 传输时 CPU 多为运行 |
| 中断开销 | 单中断(传输完成) | 多中断(SPI + DMA) |
| 数据长度 | ≤255 字节(DMA 限制) | 无严格限制(内存决定) |
| 使用难度 | 简单(API 封装好) | 较复杂(寄存器多、配置繁琐) |
四、nRF52832 作为 SPI 从机(SPIS)
- 支持 EasyDMA 双向缓冲,主机发时钟时自动收/发数据。
- 初始化:
nrf_drv_spis_init,配置 SCK、MOSI、MISO、CS(输入)。 - 传输:主机拉 CS → 发时钟 → SPIS 自动从 TX 缓冲区发数据、收数据到 RX 缓冲区 → 触发
EVENTS_END。
要不要我给你一份 nRF52832 作为 SPI 从机(SPIS)的完整初始化+读写代码,可直接编译运行?
浙公网安备 33010602011771号