Coder Social home page Coder Social logo

nrf-softdevice's Introduction

nrf-softdevice

Rust bindings for Nordic Semiconductor nRF series SoftDevices.

SoftDevices are a closed source C binary written by Nordic for their microcontrollers that sits at the bottom of flash and is called first on startup. The softdevice then calls your application or bootloader or whatever is sitting directly after it in flash.

They are full featured, battle tested, and pre qualified for bluetooth certification and thus make valuable bluetooth stacks when bindgened to Rust -- at least until we get a Rust bluetooth stack certified to be shipped commercially. Different SoftDevices support specific chips as well as certain features, like working only as a peripheral, or both a peripheral and central, or even offer alternate radio configuration like ant.

Besides the handicap of being closed source, the cost of SoftDevices is they steal away resources like ram and flash as well as timer peripherals and several priorities of interrupts from your application.

High-level bindings

The nrf-softdevice crate contains high-level easy-to-use Rust async/await bindings for the Softdevice.

Working:

  • Safe interrupt management
  • Async flash API
  • Bluetooth central (scanning and connecting)
  • Bluetooth peripheral (advertising, connectable-only for now)
  • GATT client
  • GATT server
  • L2CAP Connection-Oriented channels
  • Data length extension
  • ATT MTU extension
  • Get/set own BLE address

To use it you must specify the following Cargo features:

  • exactly one softdevice model, for example feature s140.
  • exactly one supported nRF chip model, for example feature nrf52840.

The following softdevices are supported.

  • S112 (peripheral only)
  • S113 (peripheral only)
  • S122 (central only)
  • S132 (central and peripheral)
  • S140 v7.x.x (central and peripheral)

The following nRF chips are supported

  • nRF52805
  • nRF52810
  • nRF52811
  • nRF52820
  • nRF52832
  • nRF52833
  • nRF52840

Some softdevices support only some chips, check Nordic's documentation for details.

Setting up your build environment

This project used to require nightly toolchain features, which have been recently stabilized. Therefore please ensure that your toolchains are up to date, by fetching latest stable toolchain:

rustup update

You will also need probe-rs - a utility to enable cargo run to run embedded applications on a device. Install it following the instructions on the probe-rs website.

Running examples

The following instructions are for the S140 and nRF52840-DK. You may have to adjust accordingly and can do so by modifying the cargo.toml of the examples folder - please check out the nrf-softdevice and nrf-softdevice-s140 dependency declarations.

Flashing the softdevice is required. It is NOT part of the built binary. You only need to do it once at the beginning, or after doing full chip erases.

  • Download SoftDevice S140 from Nordic's website here. Supported versions are 7.x.x
  • Unzip
  • As a debug client, if you are using
    • probe-rs:
      • Erase the flash with probe-rs erase --chip nrf52840_xxAA (You may have to supply additional --allow-erase-all argument).
      • Flash the SoftDevice with probe-rs download --verify --binary-format hex --chip nRF52840_xxAA s140_nrf52_7.X.X_softdevice.hex
    • nrfjprog:
      • Flash the SoftDevice with nrfjprog --family NRF52 --chiperase --verify --program s140_nrf52_7.0.1_softdevice.hex

To run an example, simply use cargo run from the examples folder:

  • cd examples && cargo run --bin ble_bas_peripheral --features nrf52840-dk

Examples can also built for nrf52832 development kit targeting S132 softdevice (feature flag nrf52832-dk), or for nrf52833 targeting S140 softdevice on the BBC micro:bit v2 (feature flag microbit-v2). In these cases, edit .cargo/config.toml as needed.

Configuring a SoftDevice

The first thing to do is find out how much flash the SoftDevice you've chosen uses. Look in the release notes, or google for your SoftDevice version and "memory map". For an s132 v7.3 its listed as 0x26000, or in human readable numbers 152K (0x26000 in hex is 155648 in decimal / 1024 bytes = 152K)

Set the memory.x to move your applications flash start to after the SoftDevice size and subtract it from the total available size:

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
}

You can pick mostly anything for ram right now as if you have defmt logging enabled, the SoftDevice will tell you what the right number is when you call 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'

You have some control over that number by tweaking the SoftDevice configuration parameters. See especially the concurrent connection parameters. If you dont need to support multiple connections these can really decrease your ram size:

  • conn_gap.conn_count The number of concurrent connections the application can create with this configuration
  • periph_role_count Maximum number of connections concurrently acting as a peripheral
  • central_role_count Maximum number of connections concurrently acting as a central

Next you need to find out if your board has an external oscillator (which provides better battery life) But if in doubt just assume it doesn't and set the SoftDevice to use an internal clock. A common no external crystal configuration for nRF52 might be

        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,
        }),

Interrupts

The SoftDevice does time-critical radio processing at high priorities. If its timing is disrupted, it will raise "assertion failed" errors. There's two common mistakes to avoid: (temporarily) disabling the softdevice's interrupts, and running your interrupts at too high priority.

These mistakes WILL cause "assertion failed" errors, 100% guaranteed. If you do these only "a little bit", such as disabling all interrupts but for very short periods of time only, things may appear to work, but you will get "assertion failed" errors after hours of running. Make sure to follow them to the letter.

The Softdevice Driver (e.g. Softdevice::run()) cannot be used from interrupts by default. However, the usable-from-interrupts feature enables this functionality. To use this feature, a critical-section implementation is required. This crate's internal implementation (critical-section-impl feature) is recommended, but other Softdevice-compatible implementations should also work.

Critical sections

Interrupts for certain peripherals and SWI/EGUs are reserved for the SoftDevice. Interrupt handlers for them are reserved by the softdevice, the handlers in your application won't be called.

DO NOT disable the softdevice's interrupts. You MUST NOT use the widely-used cortex_m::interrupt::free for "disable all interrupts" critical sections. Instead, use the critical-section crate, which allows custom critical-section implementations:

  • Make sure the critical-section-impl Cargo feature is enabled for nrf-softdevice. This makes nrf-softdevice emit a custom critical section implementation that disables only non-softdevice interrupts.
  • Use critical_section::with instead of cortex_m::interrupt::free. This uses the custom critical-section impl.
  • Use embassy_sync::blocking_mutex::CriticalSectionMutex instead of cortex_m::interrupt::Mutex.

Make sure you're not using any library that internally uses cortex_m::interrupt::free as well.

Interrupt priority

Interrupt priority levels 0, 1, and 4 are reserved for the SoftDevice. Make sure to not use them.

The default priority level for interrupts is 0, so for every single interrupt you enable, make sure to set the priority level explicitly. For example:

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);

If you're using embassy-nrf with the gpiote or time-driver-rtc1 features enabled, you'll need to edit your embassy_config to move those priorities:

// 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);

Troubleshooting

Interrupt priorities

If you are sure you have set interrupts correctly, but are still getting an error like below:

[ERROR]Location<lib.rs:104>panicked at 'sd_softdevice_enable err SdmIncorrectInterruptConfiguration'

Make sure the defmt feature is enabled on embassy_nrf.

You can then use this code to print whether an interrupt is enabled, and its priority:

// 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 numbers map to what they are in the Interrupt enum.

If your SoftDevice is hardfaulting on enable and you think you have everything right, make sure to go back and do a full chip erase or recover, and reflash the SoftDevice again. A few bytes of empty space after the SoftDevice are required to be 0xFF, but might not be if the softdevice was flashed over an existing binary.

Peripheral conflicts

If the following runtime error occurs

Softdevice memory access violation. Your program accessed registers for a peripheral reserved to the softdevice. PC=2a644 PREGION=8192

check which peripherals are used by application.

Softdevice uses number of peripherals for its functionality when its enabled (and even disabled), and therefore enforces certain limits to availability of peripherals:

  1. Open - peripheral is not used by SoftDevice and application has full access.
  2. Blocked - peripheral is used by SoftDevice, and all application access is disabled. Though, certain peripherals (RADIO, TIMER0, CCM, and AAR) could be accessed via the Softdevice Radio Timeslot API.
  3. Restricted - peripheral is used by SoftDevice, but it can have limited access via SoftDevice API. For example FLASH, RNG and TEMP peripherals.

Linking issues

If the following linking error occurs

rust-lld: error: undefined symbol: _critical_section_release

make sure the feature critical-section-impl is enabled and also that the softdevice is included in the code, e.g. use nrf_softdevice as _;.

If running the firmware timeouts after flashing, make sure the size and location of the RAM and FLASH region in the linker script is correct.

Low-level raw bindings

The nrf-softdevice-s1xx crates contain low-level bindings, matching 1-1 with the softdevice C headers.

They are generated with bindgen, with extra post-processing to correctly generate the svc-based softdevice calls.

Generated code consists of inline functions using inline ASM, ensuring the lowest possible overhead. Most of the times you'll see them inlined as a single svc instruction in the calling function. Here is an example:

#[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
}

Generating

The bindings are generated from the headers with the gen.sh script.

License

This repo includes the softdevice headers, which are licensed under Nordic's proprietary license. Generated binding.rs files are a derived work of the headers, so they are also subject to Nordic's license.

The high level bindings (nrf-softdevice) and the generator code (nrf-softdevice-gen) are licensed under either of

at your option.

nrf-softdevice's People

Contributors

adinack avatar albertskog avatar alexmoon avatar alimoal avatar andreas-move-innovation avatar bartmassey avatar bobmcwhirter avatar chrysn avatar dirbaio avatar dzervas avatar eupn avatar fnafnio avatar haobogu avatar huntc avatar jacobrosenthal avatar julidi avatar kext avatar lonesometraveler avatar lulf avatar matoushybl avatar mehmetalianil avatar plaes avatar rise0chen avatar tarfu avatar taylor-shift avatar timokroeger avatar univa avatar ve5li avatar xgroleau avatar yandrik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nrf-softdevice's Issues

Macro to make writing GATT clients easier?

Here's an example of how GATT clients are written now: https://github.com/akiles/nrf-softdevice/blob/7567bffb617135a68138762e2a7ca2793a26fe75/example/src/bin/ble_bas_central.rs#L32-L73

For every characteristic, we have to:

  • Add field to hold the handle for the value, the cccd (if needed)
  • Add code on discovered_characteristic that matches the UUID and fills the fields if matches, and checks all necessary operations are supported (read/write/notification/etc)
  • Add code on discovery_complete that checks all needed UUID are filled.
  • Add methods to read/write/etc the characteristic (not yet done in above code).

That's all boring boilerplate which could be generated with a proc macro.

#[gatt_client::Client(uuid=180F)]
struct BatteryServiceClient {
    #[characteristic(uuid=2A19, read, notify)]
    battery_level: u8,
}

which would generate an impl of gatt_client::Client like the above, and methods to use the characteristics such as:

async fn battery_level_read(&self) -> Result<u8, gatt_client::ReadError>

For receiving notifications/indications user would have to implement their own function such as on_battery_level_notification on the struct.

Suggestions and comments on the proposed API welcome :)

Upgrade to sdk 17.0.2

No hurry, I more wanted to document that the current softdevice thats checked in (do you want to check that in?) and the generated files are from 16.0.0

Failing to build on OS X

Very excited to use this project and help where I can!

I'm finding the getting started a bit rough. Out of the box, and noting that I'm using OS X:

% ./test-build.sh 
+ cd examples
+ cargo build --target thumbv7em-none-eabihf --features cortex-m-rtic --bins
  Downloaded rtic-syntax v0.4.0
  Downloaded cortex-m-rtic v0.5.5
  Downloaded cortex-m-rtic-macros v0.5.2
  Downloaded rtic-core v0.3.0
  Downloaded 4 crates (149.3 KB) in 1.05s
   Compiling stable_deref_trait v1.2.0
   Compiling vcell v0.1.2
   Compiling bitfield v0.13.2
   Compiling r0 v0.2.2
   Compiling futures-core v0.3.8
   Compiling futures-sink v0.3.8
   Compiling scopeguard v1.1.0
   Compiling autocfg v1.0.1
   Compiling futures-task v0.3.8
   Compiling pin-utils v0.1.0
   Compiling nb v1.0.0
   Compiling futures-io v0.3.8
   Compiling hashbrown v0.9.1
   Compiling void v1.0.2
   Compiling anyfmt v0.1.0 (https://github.com/akiles/embassy#2e062f56)
   Compiling rand_core v0.5.1
error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: unknown debugging option: `trap-unreachable`

error: could not compile `scopeguard`

To learn more, run the command again with --verbose.
warning: build failed, waiting for other jobs to finish...
error: build failed

I also note that CI is failing presently at commit 41f00393ddd5607c415ecc3d1862703c85411820. However, previous working CI commit also fails for me. Something to do with my OS X environment?

Setting gatt value from interrupts

I see that many things changed in the codebase.

I am also back on my ble project, and I am trying to have a gatt server working.

I am looking at: https://github.com/akiles/nrf-softdevice/blob/master/examples/src/bin/ble_bas_peripheral.rs

How am I supposed to set the gatt server value from within the code? I mean, for example, imagine I have a timer interrupt in which I read some values from an i2c peripheral, how can I write it to BatteryService.foo for currently connected devices to get the update? (I still use rtic, but I don't think it should change much)

Cannot build the example having built the library

Having difficulty getting started with an example noting that I've successfully run the test-build.sh script.

As per the README, if I try:

cargo run --bin ble_bas_peripheral

...then:

% cargo run --bin ble_bas_peripheral 
   Compiling nrf-softdevice-s140 v0.1.1 (/Users/huntc/Projects/hacking/nrf-softdevice/nrf-softdevice-s140)
   Compiling cortex-m-rt v0.6.13
warning: unused import: `core::str::FromStr`
 --> nrf-softdevice-macro/src/lib.rs:5:5
  |
5 | use core::str::FromStr;
  |     ^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: 1 warning emitted

   Compiling nrf52840-pac v0.9.0
   Compiling panic-probe v0.1.0
LLVM ERROR: Global variable 'Reset' has an invalid section specifier '.Reset': mach-o section specifier requires a segment and section separated by a comma.
error: could not compile `cortex-m-rt`

To learn more, run the command again with --verbose.
warning: build failed, waiting for other jobs to finish...
error: invalid register `r0`: unknown register
    --> nrf-softdevice-s140/src/bindings.rs:1737:9
     |
1737 |         inout("r0") to_asm(p_mutex) => ret,
     |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: invalid register `r1`: unknown register
    --> nrf-softdevice-s140/src/bindings.rs:1738:9
     |
1738 |         lateout("r1") _,
     |         ^^^^^^^^^^^^^^^

error: invalid register `r2`: unknown register
    --> nrf-softdevice-s140/src/bindings.rs:1739:9

...and many more. I've also tried enabling features:

cargo run --features s140,nrf52840,ble-central,ble-peripheral,ble-l2cap,ble-gatt-client,ble-gatt-server --bin ble_bas_peripheral

Using with cortex-m-rtic

I tried to use the softdevice with cortex-m-rtic, but if I specify "peripherals=true" and try to access CLOCK, I got the following crash

1879│                 None
1880│             } else {
1881│                 Some(unsafe { Peripherals::steal() })
1882│             }
1883│         })
1884│     }
1885│     #[doc = r"Unchecked version of `Peripherals::take`"]
1886│     #[inline]
1887│     pub unsafe fn steal() -> Self {
1888├───────> DEVICE_PERIPHERALS = true;
1889│         Peripherals {
1890│             FICR: FICR {
1891│                 _marker: PhantomData,
1892│             },
1893│             UICR: UICR {
1894│                 _marker: PhantomData,
1895│             },
1896│             CLOCK: CLOCK {
1897│                 _marker: PhantomData,
/home/kuon/.cargo/registry/src/github.com-1ecc6299db9ec823/nrf52840-pac-0.9.0/src/lib.rs
35                  hal::clocks::Clocks::new(cx.device.CLOCK).enable_ext_hfosc();
Breakpoint 1 at 0x2724c
Breakpoint 2 at 0x27264
Function "rust_begin_unwind" not defined.
Make breakpoint pending on future shared library load? (y or [n]) [answered N; input not from terminal]
Breakpoint 3 at 0x271e4: file src/main.rs, line 22.
semihosting is enabled

Loading section .vector_table, size 0x100 lma 0x27000
Loading section .text, size 0x12c lma 0x27100
Start address 0x00027100, load size 556
Transfer rate: 1 KB/sec, 278 bytes/write.
Note: automatically using hardware breakpoints for read-only addresses.

Breakpoint 3, nrf52840_pac::Peripherals::steal () at /home/kuon/.cargo/registry/src/github.com-1ecc6299db9ec823/nrf52840-pac-0.9.0/src/lib.rs
:1888
1888            DEVICE_PERIPHERALS = true;
(gdb)

And if I try to use it with no peripherals, I got the following crash:

 4│
 5│ pub use bare_metal::{CriticalSection, Mutex, Nr};
 6│
 7│ /// Disables all interrupts
 8│ #[inline]
 9│ pub fn disable() {
10│     match () {
11│         #[cfg(all(cortex_m, feature = "inline-asm"))]
12│         () => unsafe {
13├───────────> llvm_asm!("cpsid i" ::: "memory" : "volatile");
14│         },
15│
16│         #[cfg(all(cortex_m, not(feature = "inline-asm")))]
17│         () => unsafe {
18│             extern "C" {
19│                 fn __cpsid();
20│             }
21│
22│             // XXX do we need a explicit compiler barrier here?
/home/kuon/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.6.3/src/interrupt.rs
13                  llvm_asm!("cpsid i" ::: "memory" : "volatile");
Breakpoint 1 at 0x271d0
Breakpoint 2 at 0x271e8
Function "rust_begin_unwind" not defined.
Make breakpoint pending on future shared library load? (y or [n]) [answered N; input not from terminal]
Breakpoint 3 at 0x271b4: file src/main.rs, line 22.
semihosting is enabled

Loading section .vector_table, size 0x100 lma 0x27000
Loading section .text, size 0xec lma 0x27100
Start address 0x00027100, load size 492
Transfer rate: 1 KB/sec, 246 bytes/write.
Note: automatically using hardware breakpoints for read-only addresses.

Breakpoint 3, cortex_m::interrupt::disable () at /home/kuon/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.6.3/src/interrupt.rs:1
3
13                  llvm_asm!("cpsid i" ::: "memory" : "volatile");
(gdb)

Should I not try to use rtic and consider it incompatible with the softdevice?

gatt: Panics when receiving writes with bad length

How to reproduce:

  • Create a writable char of type u8.
  • Connect to the device with nRF Connect, write an empty value to it.

I have no idea why this happens.

there's this vlen attr (for "variable length") https://github.com/embassy-rs/nrf-softdevice/blob/master/nrf-softdevice/src/ble/gatt_server.rs#L21
it's false if MIN_LEN==MAX_LEN: https://github.com/embassy-rs/nrf-softdevice/blob/master/nrf-softdevice-macro/src/lib.rs#L246
which is the case for something like u8 https://github.com/embassy-rs/nrf-softdevice/blob/master/nrf-softdevice/src/ble/gatt_traits.rs#L39-L40

So either I'm misunderstanding what vlen does, or there's some bug somewhere (in our code or in the softdevice??)

Use of `cortex_m::interrupt::free` in examples

In the readme you say that you should never use cortex_m::interrupt::free because of softdevice. However, in the examples the alloc-cortex-m crate is used, which uses cortex_m::interrupt::free in their implementation here.

Temperature sensor

Should be easy, just calling sd_temp_get.

Maybe check if there's a HAL trait out there and implement that.

More efficient handling of 128-bit uuids

The softdevice handles 128bit uuids in a funny manner: you register them at runtime to get an "uuid type", and then to use it you specify the type, and bytes 12-13. This means if you do a custom service that requires many UUIDs, you can save flash and ram by making them only differ in bytes 12-13: you only register the "base" uuid once, then use the same uuid type with just different bytes 12-13.

If we want to take advantage of this we could make a macro like this:

uuids!(
    FOO_SVC = "4a04fbec-229b-11eb-98bd-2f03a9d5f786";
    FOO_CHAR_1 = "4a04fbec-229b-11eb-98bd-2f030000f786";
    FOO_CHAR_2 = "4a04fbec-229b-11eb-98bd-2f030001f786";
    FOO_CHAR_3 = "4a04fbec-229b-11eb-98bd-2f030002f786";
    FOO_CHAR_4 = "4a04fbec-229b-11eb-98bd-2f030003f786";
    FOO_CHAR_5 = "4a04fbec-229b-11eb-98bd-2f030004f786";
    FOO_CHAR_6 = "4a04fbec-229b-11eb-98bd-2f030005f786";
)

which would generate code to register the "base uuid" just once if uuids are equal in bytes other than 12-13

Better logs on softdevice faults

Current logs don't give any actionable info on how to deal with the error, so people get stuck: #45 https://github.com/akiles/embassy/issues/42

For NRF_FAULT_ID_SD_ASSERT:

  • suggest checking interrupts (99% of the times this is the cause of the fault)
  • suggest searching the PC in the nordic forums

For NRF_FAULT_ID_APP_MEMACC:

  • explain the cause, print the PC value.

It'd be great to get probe-run and other tools to print a stack trace on NRF_FAULT_ID_APP_MEMACC but I'm not sure if it's possible :S

Rtic example will panic when taking peripherals

The current examples use pub fn take_peripherals() -> (nrf_softdevice::Peripherals, Peripherals) in example_common.rs but this crashes with an error when using rtic.

error: `Unwrap of a None option value`
└─ firmware::common::take_peripherals @ src/example_common.rs:40

The fix implies to put them in Resources

    struct Resources {
        timer: Timer<TIMER1, Periodic>,
        sdp: Option<nrf_softdevice::Peripherals>,
    }
   // init
      let sdp = nrf_softdevice::Peripherals {
            AAR: cx.device.AAR,
            ACL: cx.device.ACL,
            CCM: cx.device.CCM,
            CLOCK: cx.device.CLOCK,
            ECB: cx.device.ECB,
            EGU1: cx.device.EGU1,
            EGU2: cx.device.EGU2,
            EGU5: cx.device.EGU5,
            MWU: cx.device.MWU,
            NVMC: cx.device.NVMC,
            POWER: cx.device.POWER,
            RADIO: cx.device.RADIO,
            RNG: cx.device.RNG,
            RTC0: cx.device.RTC0,
            SWI1: cx.device.SWI1,
            SWI2: cx.device.SWI2,
            SWI5: cx.device.SWI5,
            TEMP: cx.device.TEMP,
            TIMER0: cx.device.TIMER0,
        };
        let sdp = Some(sdp);

        init::LateResources {
            sdp,
            timer,
        }
// when creating soft device
        let sd = Softdevice::enable(cx.resources.sdp.take().unwrap(), &config);

API to build advertising data

Right now, to build data packets, we have to do something like:

    #[rustfmt::skip]
    let adv_data = &[
        0x02, 0x01, raw::BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE as u8,
        0x03, 0x03, 0x09, 0x18,
        0x0a, 0x09, b'H', b'e', b'l', b'l', b'o', b'R', b'T', b'I', b'C',
    ];
    #[rustfmt::skip]
    let scan_data = &[
        0x03, 0x03, 0x09, 0x18,
    ];

We need an API to build those array without having to fiddle with bytes.

I'd like to help, but I could not find the reference documentation for those data packet.

I guessed that adv_data is an array of length(1byte), type(1byte), data(length - 1 bytes). But I am not very familiar with BLE and found dozen of different documentation with no real reference for this.

Receive notifications in gatt_client

Is there a way for gatt_client to receive notifications? I am trying to understand nrf-softdevice-macro and I see there is some sort of event generated in nrf-softdevice-macro/src/lib.rs:

if notify {
    let case_notification = format_ident!("{}Notification", name_pascal);
    code_event_enum.extend(quote_spanned!(ch.span=>
        #case_notification(#ty),
    ));
}

But how do I capture it, there is no gatt_client::run() like there is with gatt_server? Maybe I'm looking in the wrong places..

Happy to contribute documentation and/or code if I can just get a little hint!

Data Length Extension negotiation

The BLE data length extension must be negotiated using sd_ble_gap_data_length_update and events on_data_length_update_request, on_data_length_update

Nordic's equivalent code is in nrf_ble_gatt.c

Better Config struct

The current Config struct is very ugly because it uses the original C structs.

  • Very unergonomic, especially when they have bitfields.
  • All fields are Options, which is
  • Users override a field all-or-nothing. If they want to change a single setting of a group they can't, they have to specify Some and then the whole struct.

The reason for the Options this is to allow users to not set them, in which case we don't even call sd_ble_cfg_set, and the softdevice uses a defaul configuration. Unfortunately the softdevice headers don't document the default of many settings :(

Ideally it'd be a pure-Rust struct with much better ergonomics and safety, implementing Default::default with defaults matching the softdevice's. We'd call sd_ble_cfg_set for absolutely every setting even if they're defaults.

Macro error

I'm trying to dive in and try your set of crate, but I have an error that maybe you have an idea where it originates from:

error[E0425]: cannot find value `irq` in this scope
   --> /home/kuon/.cargo/git/checkouts/nrf-softdevice-9b99539d60cc72a7/52329d2/nrf-softdevice/src/util/macros.rs:30:40
    |
30  |                 defmt::error!($msg, $( $i ),*);
    |                                        ^^ not found in this scope
    |
   ::: /home/kuon/.cargo/git/checkouts/nrf-softdevice-9b99539d60cc72a7/52329d2/nrf-softdevice/src/interrupt.rs:248:5
    |
248 |     assert_app_accessible_irq!(irq);
    |     -------------------------------- in this macro invocation
    |

Freeze after Disconnect

When I run any of the ble_bas_peripheral example, the device (nrf52840 dk) seems to freeze. It does not go back to advertising nor are there any tracing messages. I also ran the gatt server from within the rtic example and the tick task seems to stop as well.
The disconnect event is not logged before that happens.

It feels like this is actually the softdevice crashing, but maybe it is not configured correctly. Any idea what could cause this or how to track it down?

Support `run`ing multiple GATT services

Right now, you can register multiple GATT services and call their set/get/notify methods in parallel. However, you can't have multiple registered services subscribed to events since gatt_server::run uses a portal internally.
A workaround right now is to create dummy wrapper server that dispatches to each individual service, but that's pretty hack-y.

It would be nice to have a run macro that could support multiple services, e.g.:

let srv_a: ServiceA = unwrap!(gatt_server::register(sd));
let srv_b: ServiceB = unwrap!(gatt_server::register(sd));
...
let res = gatt_server::run!(&conn,
  &srv_a, |_e| defmt::info!("Handle service A event"),
  &srv_b, |_e| defmt::info!("Handle service B event"),
).await;

As far as I can tell, this interface would be feasible to implement as the macro would just need to internally chain the servers' on_write methods.

entered unreachable code at Portal<T>::wait_once

I'm seeing this error in production somewhat rarely. I haven't been able to repro it yet.

 ERROR device            > 93281.127 panicked at 'internal error: entered unreachable code'
stack backtrace:
   0: core::panicking::panic
        at /root/.rustup/toolchains/nightly-2021-10-16-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panicking.rs:41
   1: __defmt_default_panic
        at /root/.cargo/git/checkouts/defmt-7f5b74b4e6ff55d4/50e3db3/defmt/src/lib.rs:367
   2: nrf_softdevice::util::portal::Portal<T>::wait_once::{{closure}}::{{closure}}
   3: nrf_softdevice::util::portal::Portal<T>::call
   4: nrf_softdevice::ble::on_evt
   5: nrf_softdevice::events::run::{{closure}}::{{closure}}
        at /root/.cargo/git/checkouts/nrf-softdevice-c6f1e1abd1ad6893/6a01c4c/nrf-softdevice/src/events.rs:73
   6: <futures_util::future::poll_fn::PollFn<F> as core::future::future::Future>::poll
        at /root/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.18/src/future/poll_fn.rs:56
   7: nrf_softdevice::events::run::{{closure}}
        at /root/.cargo/git/checkouts/nrf-softdevice-c6f1e1abd1ad6893/6a01c4c/nrf-softdevice/src/events.rs:54
   8: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
        at /root/.rustup/toolchains/nightly-2021-10-16-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/future/mod.rs:80
   9: nrf_softdevice::softdevice::Softdevice::run::{{closure}}
        at /root/.cargo/git/checkouts/nrf-softdevice-c6f1e1abd1ad6893/6a01c4c/nrf-softdevice/src/softdevice.rs:323
  10: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
        at /root/.rustup/toolchains/nightly-2021-10-16-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/future/mod.rs:80
  11: application::ble::softdevice_task::task::{{closure}}
        at /builds/firmware/firmware/ak-application/src/application/ble.rs:131
  12: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
        at /root/.rustup/toolchains/nightly-2021-10-16-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/future/mod.rs:80
  13: embassy::executor::raw::TaskStorage<F>::poll
        at /root/.cargo/git/checkouts/embassy-00cf184034ab5a1f/7561fa1/embassy/src/executor/raw/mod.rs:183
  14: core::cell::Cell<T>::get
        at /root/.rustup/toolchains/nightly-2021-10-16-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cell.rs:436
  15: embassy::executor::raw::timer_queue::TimerQueue::update
        at /root/.cargo/git/checkouts/embassy-00cf184034ab5a1f/7561fa1/embassy/src/executor/raw/timer_queue.rs:35
  16: embassy::executor::raw::Executor::poll::{{closure}}
        at /root/.cargo/git/checkouts/embassy-00cf184034ab5a1f/7561fa1/embassy/src/executor/raw/mod.rs:329
  17: embassy::executor::raw::run_queue::RunQueue::dequeue_all
        at /root/.cargo/git/checkouts/embassy-00cf184034ab5a1f/7561fa1/embassy/src/executor/raw/run_queue.rs:71
  18: embassy::executor::raw::Executor::poll
        at /root/.cargo/git/checkouts/embassy-00cf184034ab5a1f/7561fa1/embassy/src/executor/raw/mod.rs:308
  19: cortex_m::asm::inline::__wfe
        at /root/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.3/src/../asm/inline.rs:177
  20: cortex_m::asm::wfe
        at /root/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.3/src/asm.rs:49
  21: embassy::executor::arch::Executor::run
        at /root/.cargo/git/checkouts/embassy-00cf184034ab5a1f/7561fa1/embassy/src/executor/arch/cortex_m.rs:54
  22: application::sys::__cortex_m_rt_main
  23: application::sys::__cortex_m_rt_main
        at /builds/firmware/firmware/ak-application/src/application/../sys.rs:158
error: the stack appears to be corrupted beyond this point

Update examples to work with latest embassy

Latest embassy always requires a clock to be set (this itself may be an embassy bug, have to look into it).

Either way, the examples should Just Work and they don't right now. They're missing manual clock setup like the following, or a way to set the irq priority with #[embassy::main]

    unsafe { embassy_nrf::system::configure(Default::default()) };

    let irq = interrupt::take!(RTC1);
    irq.set_priority(Priority::Level3); // levels 0-1 are reserved for the softdevice
    let rtc = unsafe { embassy_nrf::peripherals::RTC1::steal() };
    let rtc = RTC.put(rtc::RTC::new(rtc, irq));
    rtc.start();
    unsafe { embassy::time::set_clock(rtc) };

Investigate PPI safety

The softdevice has a bunch of sd_ppi_* functions, which makes me think we can't freely use PPI when softdevice is enabled. This may conflict with PPI usage in nrf-hal or other libs.

At the very least we should document it.

How to have exclusive connection

How can I do the following?

  • start advertizing
  • when I got a connection, stop advertizing and don't accept new connection
  • restart advertizing when the connection is closed by the the client

Now in my advertize loop, I have the following:

    loop {
        info!("Advertising start!");
        let conn = peripheral::advertise(
            sd,
            peripheral::ConnectableAdvertisement::ScannableUndirected {
                adv_data,
                scan_data,
            },
        )
        .await
        .dewrap();

        info!("Advertising done!");

        // Detach the connection so it isn't disconnected when dropped.
        conn.detach();
    }

I feel like I should do some loop instead of conn.detach() but I don't know if you exposed the APIs yet.

nrf-softdevice-gen broken

Fallout from #1

moving the .cargo/config up means nrf-softdevice-gen is currently broken unless you pass an override --target or just comment out the target in .cargo/config

Mixing std and no_std in a single workspace is kind of a pain

You probably want to move it outside the other no_std stuff or the entire repo?

nrf51 series

Is there an alternative package for the nrf51 series, or how difficult would it be to support?

Bond Management Service missing?

I was trying to restrict who can connect over Bluetooth to a device. To my understanding (and I don't really have much understanding of Bluetooth) I would need to bind the connection. And that is what the "Bond Management Service" is for.

However, I couldn't find anything related to that in nrf-softdevice. Is that just missing, or is there another way to deal with this?

ATT MTU negotiation

Similarly to #5, the ATT MTU must be negotiated using sd_ble_gattc_exchange_mtu_request. I think it's only necessary if we're the GATT client.

Make Softdevice::enable fully safe by taking ownership of the forbidden peripherals

Currently the user can enable the softdevice and then use the forbidden peripherals, which will cause undefined behavior or softdevice faults.

The way of fixing this is forcing enable() to take owned instances of the forbidden PAC peripherals.

The forbidden peripherals for s140 are:

RADIO
RTC0
TIMER0
RNG
ECB
CCM_AAR
TEMP
SWI5_EGU5

Question: How to safely configure SoftDevice peripherals before giving up ownership?

Hi,

First off, thanks for all this great work on embassy and nrf-softdevice!

Sorry if this is a noob question.

I am trying to combine this crate with the usb-device. I have both working separately.

For the USB stuff I use enable_ext_hfosc to configure the clock.

Something like this:

let periph = Peripherals::take().unwrap();
let clocks = nrf52840_hal::Clocks::new(periph.CLOCK);
let clocks = clocks.enable_ext_hfosc();

As Softdevice::Peripherals requires ownership of CLOCK I don't see a way to do this other than manually setting the register.

L2CAP

This is not a very high priority because it seems L2CAP CoC channels are not very widely used. If you do find them useful for your project, please do reach out!

Disallow declaring IRQ handlers for reserved interrupts?

Currently this builds fine in user code

#[interrupt]
fn RADIO() {
    info!("RADIO triggered!")
}

but will not work since RADIO is a softdevice-reserved interrupt. I think it does work before enabling the softdevice, and after enabling it'll just never fire (the softdevice never forwards that irq to the application).

Not sure if this should be somehow prevented from compiling (if that's even possible).

Error reported via Rust Analyzer in VS Code

I'm seeing VS Code's Rust Analyzer complain about an error with ScanConfig, yet I'm bewildered as to why. I'm able to jump into ScanConfig and can see the declaration of tx_power that it requires. Here's a screenshot:

image

Examples do not compile

Compilation of the examples fails with:

error[E0599]: no method named `dewrap` found for enum `core::result::Result<(), SpawnError>` in the current scope
   --> examples/src/bin/ble_bas_central.rs:163:42
 note: the method `dewrap` exists but the following trait bounds were not satisfied:
            `SpawnError: Format`
            which is required by `core::result::Result<(), SpawnError>: example_common::Dewrap<()>`

I assume that the derived implementations for defmt are not being generated.

Radio timeslot API

This would be mainly useful to run concurrently Bluetooth with other protocols (such as ESB for example).

This is not a very high priority because its use is quite niche. If you do find them useful for your project, please do reach out!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.