2个版本
0.1.2 | 2024年1月15日 |
---|---|
0.1.1 | 2020年7月21日 |
#1984 在 嵌入式开发
在 nrf-softdevice 中使用
565KB
14K SLoC
nrf-softdevice
Rust对Nordic Semiconductor nRF系列SoftDevices的绑定
SoftDevices是Nordic为其微控制器编写的闭源C二进制文件,位于闪存底部,并在启动时首先调用。然后SoftDevice会调用您的应用程序或引导加载程序或闪存中直接跟在其后的任何内容。
它们功能齐全,经过实战考验,并预先经过蓝牙认证,因此在绑定到Rust时成为宝贵的蓝牙堆栈——至少直到我们获得一个经过认证的、可以商业发布的Rust蓝牙堆栈。不同的SoftDevices支持特定的芯片以及某些功能,例如仅作为外围设备工作,或同时作为外围设备和中心设备,甚至提供类似ant的替代无线电配置。
除了闭源的限制之外,SoftDevices的成本是它们会占用资源,如RAM和闪存,以及定时器外设和几个中断优先级,从而从您的应用程序中窃取资源。
高级绑定
nrf-softdevice
crate包含用于Softdevice的易于使用的高级Rust async/await绑定。
工作
- 安全的中断管理
- 异步闪存API
- 蓝牙中心(扫描和连接)
- 蓝牙外围设备(目前仅限于可连接的)
- GATT客户端
- GATT服务器
- L2CAP面向连接的通道
- 数据长度扩展
- ATT MTU扩展
- 获取/设置自己的BLE地址
要使用它,您必须指定以下Cargo功能
- 恰好一个软设备模型,例如功能
s140
。 - 恰好一个受支持的nRF芯片型号,例如功能
nrf52840
。
以下软设备得到支持。
- S112(仅外围设备)
- S113(仅外围设备)
- S122(仅中心设备)
- S132(中心和外围设备)
- S140 v7.x.x(中心和外围设备)
以下nRF芯片得到支持
- nRF52805
- nRF52810
- nRF52811
- nRF52820
- nRF52832
- nRF52833
- nRF52840
某些软设备只支持一些芯片,请查阅Nordic的文档以获取详细信息。
设置您的构建环境
该项目以前需要夜间工具链功能,但这些功能最近已经稳定。因此,请确保您的工具链是最新的,通过获取最新的稳定工具链来做到这一点。
rustup update
您还需要probe-rs
- 一个实用程序,用于启用cargo run
来在设备上运行嵌入式应用程序。请按照probe-rs网站
上的说明进行安装。
运行示例
以下说明适用于S140和nRF52840-DK。您可能需要相应地进行调整,可以通过修改示例文件夹中的cargo.toml
来完成,请检查nrf-softdevice
和nrf-softdevice-s140
依赖项声明。
需要烧写软设备。它不是构建的二进制文件的一部分。您只需要在开始时或完成全面芯片擦除后进行一次。
- 从Nordic的网站此处下载SoftDevice S140。支持的版本为7.x.x。
- 解压缩
- 如果您使用的是
- probe-rs
- 使用
probe-rs erase --chip nrf52840_xxAA
擦除闪存(您可能需要提供额外的--allow-erase-all
参数)。 - 使用
probe-rs download --verify --binary-format hex --chip nRF52840_xxAA s140_nrf52_7.X.X_softdevice.hex
- 使用
- nrfjprog
- 使用
nrfjprog --family NRF52 --chiperase --verify --program s140_nrf52_7.0.1_softdevice.hex
- 使用
- probe-rs
要运行示例,只需从examples
文件夹使用cargo run
cdexamples && cargorun --binble_bas_peripheral --featuresnrf52840-dk
示例也可以为针对S132软设备的nRF52832开发套件构建(功能标志nrf52832-dk
),或者为BBC micro:bit v2上的S140软设备构建的nRF52833(功能标志microbit-v2
)。在这些情况下,根据需要编辑.cargo/config.toml
。
配置软设备
首先,找出您选择的SoftDevice使用的闪存大小。查看发布说明,或通过谷歌搜索您的SoftDevice版本和“内存映射”。对于s132 v7.3,它列出的为0x26000,或以人类可读的数字为152K(十六进制的0x26000等于十进制的155648 / 1024字节 = 152K)。
将memory.x设置为将应用程序的闪存起始位置移动到SoftDevice大小之后,并从总可用大小中减去它。
MEMORY
{
/* NOTE 1 K = 1 KiBi = 1024 bytes */
/* These values correspond to the NRF52832 with SoftDevices S132 7.3.0 */
FLASH : ORIGIN = 0x00000000 + 152K, LENGTH = 512K - 152K
RAM : ORIGIN = 0x20000000 + 44K, LENGTH = 64K - 44K
}
您现在可以随意选择RAM的大小,因为如果您启用了defmt日志记录,SoftDevice将在您调用enable时告诉您正确的数字。
1 INFO softdevice RAM: 41600 bytes
└─ nrf_softdevice::softdevice::{impl#0}::enable @ /home/jacob/.cargo/git/checkouts/nrf-softdevice-03ef4aef10e777e4/fa369be/nrf-softdevice/src/fmt.rs:138
2 ERROR panicked at 'too little RAM for softdevice. Change your app's RAM start address to 2000a280'
您可以通过调整SoftDevice配置参数来控制该数字。特别是要注意并发连接参数。如果您不需要支持多个连接,这些参数可以真正减小RAM大小。
- conn_gap.conn_count:该配置下应用可以创建的并发连接数量
- periph_role_count:同时作为外围设备操作的最大连接数
- central_role_count:同时作为中心设备操作的最大连接数
接下来,您需要确定您的板子上是否有外部振荡器(提供更好的电池寿命)。但如果不确定,只需假设没有,并将SoftDevice设置为使用内部时钟。nRF52的一个常见没有外部晶振的配置可能为
clock: Some(raw::nrf_clock_lf_cfg_t {
source: raw::NRF_CLOCK_LF_SRC_RC as u8,
rc_ctiv: 16,
rc_temp_ctiv: 2,
accuracy: raw::NRF_CLOCK_LF_ACCURACY_500_PPM as u8,
}),
中断
SoftDevice在高优先级下执行时间敏感的无线处理。如果其定时被中断,它将引发“断言失败”错误。有两个常见的错误需要避免:(暂时)禁用软设备的中断,以及以过高的优先级运行中断。
这些错误将100%导致“断言失败”错误。如果您只是“稍微”这样做,例如仅在非常短的时间内禁用所有中断,事情可能看起来可以工作,但您将在运行数小时后得到“断言失败”错误。请务必严格按照这些指示操作。
默认情况下,不能从中断中使用Softdevice Driver(例如 Softdevice::run()
)。然而,usable-from-interrupts
功能启用了此功能。要使用此功能,需要一个 critical-section
实现方案。此存储库的内部实现(critical-section-impl
功能)被推荐,但其他与Softdevice兼容的实现也应该可以工作。
关键部分
某些外围设备和SWI/EGU的中断 为SoftDevice保留。这些中断的处理程序由软设备保留,您的应用程序中的处理程序不会被调用。
不要禁用软设备的中断。您绝对不能使用广泛使用的 cortex_m::interrupt::free
作为“禁用所有中断”的关键部分。相反,使用 critical-section
crate,该crate允许自定义关键部分实现
- 确保为
nrf-softdevice
启用critical-section-impl
Cargo功能。这使nrf-softdevice
发布一个自定义的关键部分实现,该实现仅禁用非SoftDevice中断。 - 使用
critical_section::with
而不是cortex_m::interrupt::free
。这使用自定义的关键部分实现。 - 使用
embassy_sync::blocking_mutex::CriticalSectionMutex
而不是cortex_m::interrupt::Mutex
。
确保您没有使用任何内部使用 cortex_m::interrupt::free
的库。
中断优先级
中断优先级级别0、1和4 为SoftDevice保留。请确保不要使用它们。
中断的默认优先级为0,因此对于您启用的每个中断,请确保明确设置优先级。例如
use embassy_nrf::interrupt::{self, InterruptExt};
interrupt::SPIM3.set_priority(interrupt::Priority::P3);
let mut spim = spim::Spim::new(p.SPI3, Irqs, p.P0_13, p.P0_16, p.P0_15, config);
如果您使用具有 gpiote
或 time-driver-rtc1
功能的 embassy-nrf
,您需要编辑您的embassy_config来移动这些优先级
// 0 is Highest. Lower prio number can preempt higher prio number
// Softdevice has reserved priorities 0, 1 and 4
let mut config = embassy_nrf::config::Config::default();
config.gpiote_interrupt_priority = Priority::P2;
config.time_interrupt_priority = Priority::P2;
let peripherals = embassy_nrf::init(config);
故障排除
中断优先级
如果您确认已经正确设置了中断,但仍然出现以下错误
[ERROR]Location<lib.rs:104>panicked at 'sd_softdevice_enable err SdmIncorrectInterruptConfiguration'
请确保在 embassy_nrf
中启用了 defmt
功能。
然后您可以使用以下代码来打印中断是否启用及其优先级
// NB! MAX_IRQ depends on chip used, for example: nRF52840 has 48 IRQs, nRF52832 has 38.
const MAX_IRQ: u16 = ...;
use embassy_nrf::interrupt::{Interrupt, InterruptExt};
for num in 0..=MAX_IRQ {
let interrupt = unsafe { core::mem::transmute::<u16, Interrupt>(num) };
let is_enabled = InterruptExt::is_enabled(interrupt);
let priority = InterruptExt::get_priority(interrupt);
defmt::println!("Interrupt {}: Enabled = {}, Priority = {}", num, is_enabled, priority);
}
中断编号映射到 Interrupt
枚举 中的值。
如果您的 SoftDevice 在启用时发生硬错误,并且您认为一切设置正确,请确保返回并执行完全芯片擦除或恢复,并重新闪存 SoftDevice。SoftDevice 之后需要留出几个字节的空间,这些空间应为 0xFF,但如果在现有二进制文件上闪存软设备,则可能不是这样。
外围设备冲突
如果出现以下运行时错误
Softdevice memory access violation. Your program accessed registers for a peripheral reserved to the softdevice. PC=2a644 PREGION=8192
检查应用程序使用哪些外围设备。
当启用时,Softdevice 使用外围设备数量来实现其功能(即使禁用),因此对 外围设备可用性 强制实施某些限制。
- 开启 - 外围设备未被 SoftDevice 使用,应用程序具有完全访问权限。
- 阻塞 - 外围设备被 SoftDevice 使用,并且禁用了所有应用程序访问。尽管如此,某些外围设备(RADIO、TIMER0、CCM 和 AAR)可以通过 Softdevice Radio Timeslot API 访问。
- 限制 - 外围设备被 SoftDevice 使用,但可以通过 SoftDevice API 有限地访问。例如
FLASH
、RNG
和TEMP
外围设备。
链接问题
如果出现以下链接错误
rust-lld: error: undefined symbol: _critical_section_release
请确保启用了功能 critical-section-impl
,并且将软设备包含在代码中,例如 use nrf_softdevice as _;
。
如果固件在闪存后运行超时,请确保链接脚本中 RAM 和 FLASH 区域的大小和位置正确。
底层原始绑定
nrf-softdevice-s1xx
仓库包含底层绑定,与软设备 C 标头一一对应。
它们使用 bindgen
生成,并进行了额外的后处理,以正确生成基于 svc
的软设备调用。
生成的代码由使用内联汇编的内联函数组成,确保尽可能低的开销。大多数时候您会看到它们作为调用函数中的一个单独的 svc
指令内联。以下是一个示例
#[inline(always)]
pub unsafe fn sd_ble_gap_connect(
p_peer_addr: *const ble_gap_addr_t,
p_scan_params: *const ble_gap_scan_params_t,
p_conn_params: *const ble_gap_conn_params_t,
conn_cfg_tag: u8,
) -> u32 {
let ret: u32;
core::arch::asm!("svc 140",
inout("r0") p_peer_addr => res,
inout("r1") p_scan_params => _,
inout("r2") p_conn_params => _,
inout("r3") conn_cfg_tag => _,
lateout("r12") _,
);
ret
}
生成
绑定是从标头使用 gen.sh
脚本生成的。
许可证
此仓库包含软设备标头,这些标头受 Nordic 的专有许可证 的许可。生成的 binding.rs
文件是标头的派生作品,因此也受 Nordics 许可证的约束。
高层绑定(nrf-softdevice)和生成代码(nrf-softdevice-gen)受以下任一许可证的许可
- Apache 许可证,版本 2.0(《LICENSE-APACHE》或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证(《LICENSE-MIT》或 http://opensource.org/licenses/MIT》)
任您选择。