Compare commits

2 Commits

7 changed files with 231 additions and 280 deletions

View File

@@ -23,11 +23,3 @@ nrf-softdevice = { version = "0.1.0", features = ["defmt", "ble-peripheral", "bl
nrf-softdevice-s112 = "0.1.2" nrf-softdevice-s112 = "0.1.2"
panic-probe = { version = "0.3.2", features = ["print-defmt"] } panic-probe = { version = "0.3.2", features = ["print-defmt"] }
static_cell = "2.1.0" static_cell = "2.1.0"
[[bin]]
name = "commonsense"
path = "src/main.rs"
test = false
doctest = false
bench = false

View File

@@ -68,60 +68,3 @@ softdevice has to flashed to the chip.
[nrf-recover](https://docs.rs/crate/nrf-recover/latest) utility [nrf-recover](https://docs.rs/crate/nrf-recover/latest) utility
- [Pinning to use nRF52DK as programmer](https://devzone.nordicsemi.com/f/nordic-q-a/14058/external-programming-using-nrf52-dk) - [Pinning to use nRF52DK as programmer](https://devzone.nordicsemi.com/f/nordic-q-a/14058/external-programming-using-nrf52-dk)
- For examples of how to use embassy to program nRF chips, see https://github.com/embassy-rs/embassy/tree/main/examples (and look for nrfXXX) - For examples of how to use embassy to program nRF chips, see https://github.com/embassy-rs/embassy/tree/main/examples (and look for nrfXXX)
## Troubleshooting
### Core locking up
One possible cause for the core to lock up is to have a flash memory
configuration that doesn't match the selected softdevice.
#### Background
When the device powers up, it looks for certain things at certain positions
in memory, namely the interrupt vector table and the program entrypoint.
The interrupt vector table and entrypoint it finds are from the softdevice.
After initialization, the softdevice then attempts to hand over control to the
user program. The user program again has an interrupt vector table and an
entrypoint that are expected to be at a certain position in memory.
Specifically, they are expected to be right after the softdevice. If they are
not there, the device enters a hard fault state and locks up the core.
This means that the flash memory configuration in `memory.x` has to be correct
for the program to start up properly:
```
MEMORY
{
FLASH : ORIGIN = 0x00000000 + <softdevice size>, LENGTH = <flash memory size> - <softdevice size>
...
}
```
The softdevice size can be found in the release notes of the softdevice. This
is a pdf that is downloaded along with the hex file from the nordic website.
The flash memory size depends on the model of microcontroller used.
#### Solution
1. Make sure `memory.x` is configured correctly for the selected softdevice. E.g., for `S112` running on an `nRF52832` chip:
```
MEMORY
{
FLASH : ORIGIN = 0x00000000 + 100K, LENGTH = 512K - 100K
RAM : ORIGIN = 0x20000000 + 30K, LENGTH = 64K - 30K
}
```
2. Recover the device from it's locked state. This erases the flash memory:
```
$ probe-rs erase --chip nRF52832_xxAA --allow-erase-all
```
4. Compile and flash the program:
```
$ cargo run --bin blinky --release
```
The program will not run successfully at this time, as it requires the softdevice to be flashed as well.
2. Flash the desired softdevice, e.g., `S112`:
```
$ probe-rs download --verify --binary-format hex --chip nRF52832_xxAA s112_nrf52_7.3.0_softdevice.hex
```

View File

@@ -2,6 +2,6 @@ MEMORY
{ {
/* NOTE 1 K = 1 KiB = 1024 bytes */ /* NOTE 1 K = 1 KiB = 1024 bytes */
/* These values correspond to the NRF52832 with SoftDevice S112 v7.x */ /* These values correspond to the NRF52832 with SoftDevice S112 v7.x */
FLASH : ORIGIN = 0x00000000 + 100K, LENGTH = 512K - 100K FLASH : ORIGIN = 100K, LENGTH = 512K - 100K
RAM : ORIGIN = 0x20000000 + 0x24b8, LENGTH = 64K - 0x24b8 RAM : ORIGIN = 0x20000000 + 30K, LENGTH = 64K - 30K
} }

160
src/bin/ble_gatt_server.rs Normal file
View File

@@ -0,0 +1,160 @@
#![no_std]
#![no_main]
#![macro_use]
use defmt_rtt as _; // global logger
use embassy_nrf as _; // time driver
use panic_probe as _;
use embassy_nrf::interrupt::*;
use core::mem;
use defmt::{info, *};
use embassy_executor::Spawner;
use nrf_softdevice::ble::advertisement_builder::{
Flag, LegacyAdvertisementBuilder, LegacyAdvertisementPayload, ServiceList, ServiceUuid16,
};
use nrf_softdevice::ble::{gatt_server, peripheral};
use nrf_softdevice::{raw, Softdevice};
use embassy_nrf::gpio::{Level, Output, OutputDrive};
#[embassy_executor::task]
async fn softdevice_task(sd: &'static Softdevice) -> ! {
sd.run().await
}
#[nrf_softdevice::gatt_service(uuid = "180f")]
struct BatteryService {
#[characteristic(uuid = "2a19", read, notify)]
battery_level: u8,
}
#[nrf_softdevice::gatt_service(uuid = "9e7312e0-2354-11eb-9f10-fbc30a62cf38")]
struct FooService {
#[characteristic(
uuid = "9e7312e0-2354-11eb-9f10-fbc30a63cf38",
read,
write,
notify,
indicate
)]
foo: u16,
}
#[nrf_softdevice::gatt_server]
struct Server {
bas: BatteryService,
foo: FooService,
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
info!("Configuring embassy");
let mut embassy_config = embassy_nrf::config::Config::default();
embassy_config.gpiote_interrupt_priority = Priority::P2;
embassy_config.time_interrupt_priority = Priority::P2;
let p = embassy_nrf::init(embassy_config);
let mut led = Output::new(p.P0_18, Level::Low, OutputDrive::Standard);
led.set_high();
let config = nrf_softdevice::Config {
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,
}),
conn_gap: Some(raw::ble_gap_conn_cfg_t {
conn_count: 1,
event_length: 24,
}),
conn_gatt: Some(raw::ble_gatt_conn_cfg_t { att_mtu: 256 }),
gatts_attr_tab_size: Some(raw::ble_gatts_cfg_attr_tab_size_t {
attr_tab_size: raw::BLE_GATTS_ATTR_TAB_SIZE_DEFAULT,
}),
gap_role_count: Some(raw::ble_gap_cfg_role_count_t {
adv_set_count: 1,
periph_role_count: 1,
// central_role_count: 3,
// central_sec_count: 0,
// _bitfield_1: raw::ble_gap_cfg_role_count_t::new_bitfield_1(0),
}),
gap_device_name: Some(raw::ble_gap_cfg_device_name_t {
p_value: b"HelloRust" as *const u8 as _,
current_len: 9,
max_len: 9,
write_perm: unsafe { mem::zeroed() },
_bitfield_1: raw::ble_gap_cfg_device_name_t::new_bitfield_1(
raw::BLE_GATTS_VLOC_STACK as u8,
),
}),
..Default::default()
};
let sd = Softdevice::enable(&config);
let server = unwrap!(Server::new(sd));
unwrap!(spawner.spawn(softdevice_task(sd)));
static ADV_DATA: LegacyAdvertisementPayload = LegacyAdvertisementBuilder::new()
.flags(&[Flag::GeneralDiscovery, Flag::LE_Only])
.services_16(ServiceList::Complete, &[ServiceUuid16::BATTERY])
.full_name("HelloRust")
.build();
static SCAN_DATA: LegacyAdvertisementPayload = LegacyAdvertisementBuilder::new()
.services_128(
ServiceList::Complete,
&[0x9e7312e0_2354_11eb_9f10_fbc30a62cf38_u128.to_le_bytes()],
)
.build();
loop {
info!("Creating config");
let config = peripheral::Config::default();
info!("Creating adv object");
let adv = peripheral::ConnectableAdvertisement::ScannableUndirected {
adv_data: &ADV_DATA,
scan_data: &SCAN_DATA,
};
info!("Starting advertising");
let conn = unwrap!(peripheral::advertise_connectable(sd, adv, &config).await);
info!("advertising done!");
// Run the GATT server on the connection. This returns when the connection gets disconnected.
//
// Event enums (ServerEvent's) are generated by nrf_softdevice::gatt_server
// proc macro when applied to the Server struct above
let e = gatt_server::run(&conn, &server, |e| match e {
ServerEvent::Bas(e) => match e {
BatteryServiceEvent::BatteryLevelCccdWrite { notifications } => {
info!("battery notifications: {}", notifications)
}
},
ServerEvent::Foo(e) => match e {
FooServiceEvent::FooWrite(val) => {
info!("wrote foo: {}", val);
if let Err(e) = server.foo.foo_notify(&conn, &(val + 1)) {
info!("send notification error: {:?}", e);
}
}
FooServiceEvent::FooCccdWrite {
indications,
notifications,
} => {
info!(
"foo indications: {}, notifications: {}",
indications, notifications
)
}
},
})
.await;
info!("gatt_server run exited with error: {:?}", e);
}
}

24
src/bin/blinky.rs Normal file
View File

@@ -0,0 +1,24 @@
#![no_std]
#![no_main]
use defmt::info;
use embassy_executor::Spawner;
use embassy_nrf::gpio::{Level, Output, OutputDrive};
use embassy_time::Timer;
use nrf_softdevice::{raw, Softdevice};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_nrf::init(Default::default());
let mut led = Output::new(p.P0_20, Level::Low, OutputDrive::Standard);
info!("Starting blinky");
loop {
led.set_high();
Timer::after_millis(300).await;
led.set_low();
Timer::after_millis(300).await;
}
}

View File

@@ -0,0 +1,45 @@
#![no_std]
#![no_main]
use embassy_time::Timer;
use nrf_softdevice::{raw, Softdevice};
use defmt::*;
use embassy_executor::Spawner;
use embassy_nrf::twim::{self, Twim};
use embassy_nrf::{bind_interrupts, peripherals};
use {defmt_rtt as _, panic_probe as _};
const ADDRESS: u8 = 0x44;
bind_interrupts!(struct Irqs {
TWIM0_TWIS0_TWI0 => twim::InterruptHandler<peripherals::TWI0>;
});
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_nrf::init(Default::default());
info!("Initializing TWI...");
let config = twim::Config::default();
let mut twi = Twim::new(p.TWI0, Irqs, p.P0_14, p.P0_13, config);
loop {
unwrap!(twi.blocking_write(ADDRESS, &mut [0xFD]));
Timer::after_millis(10).await;
let mut buf = [0u8; 6];
unwrap!(twi.blocking_read(ADDRESS, &mut buf));
let temp_int = u16::from_be_bytes([buf[0], buf[1]]);
let temp_float: f32 = -45f32 + 175f32 * (temp_int as f32 / (2u32.pow(16) - 1) as f32);
let rh_int = u16::from_be_bytes([buf[3], buf[4]]);
let rh_float: f32 = -6f32 + 125f32 * (rh_int as f32 / (2u32.pow(16) - 1) as f32);
println!("{}\t{}", temp_float, rh_float);
Timer::after_millis(50).await;
}
}

View File

@@ -1,213 +0,0 @@
#![no_std]
#![no_main]
use static_cell::StaticCell;
use core::mem;
use embassy_nrf::interrupt::{self, InterruptExt};
use embassy_nrf::peripherals::TWISPI0;
use embassy_time::Timer;
use nrf_softdevice::ble::advertisement_builder::{
Flag, LegacyAdvertisementBuilder, LegacyAdvertisementPayload, ServiceList, ServiceUuid16,
};
use nrf_softdevice::ble::{gatt_server, peripheral, TxPower};
use nrf_softdevice::{raw, Softdevice};
use defmt::*;
use embassy_executor::Spawner;
use embassy_nrf::gpio::{Level, Output, OutputDrive};
use embassy_nrf::twim;
use embassy_nrf::{bind_interrupts, peripherals};
use {defmt_rtt as _, panic_probe as _};
//
//
// BLE code
//
//
#[nrf_softdevice::gatt_service(uuid = "181a")]
struct EnvironmentalService {
#[characteristic(uuid = "2a6e", read, notify)]
temperature: u32,
#[characteristic(uuid = "2a6f", read, notify)]
humidity: u32,
}
#[nrf_softdevice::gatt_server]
struct Server {
bas: EnvironmentalService,
}
#[embassy_executor::task]
async fn softdevice_task(sd: &'static Softdevice) -> ! {
sd.run().await
}
#[embassy_executor::task]
async fn ble_task(sd: &'static Softdevice, server: &'static Server) {
static ADV_DATA: LegacyAdvertisementPayload = LegacyAdvertisementBuilder::new()
.flags(&[Flag::GeneralDiscovery, Flag::LE_Only])
.services_16(ServiceList::Complete, &[ServiceUuid16::BATTERY])
.full_name("CommonSense")
.build();
static SCAN_DATA: LegacyAdvertisementPayload = LegacyAdvertisementBuilder::new()
.services_128(
ServiceList::Complete,
&[0x9e7312e0_2354_11eb_9f10_fbc30a62cf38_u128.to_le_bytes()],
)
.build();
loop {
let config = peripheral::Config {
tx_power: TxPower::Plus4dBm,
..Default::default()
};
let adv = peripheral::ConnectableAdvertisement::ScannableUndirected {
adv_data: &ADV_DATA,
scan_data: &SCAN_DATA,
};
let conn = unwrap!(peripheral::advertise_connectable(sd, adv, &config).await);
let _e = gatt_server::run(&conn, server, |_| {}).await;
Timer::after_secs(10).await;
}
}
//
//
// Sensing code
//
//
struct GenericTempHumiditySensor<T: twim::Instance> {
address: u8,
driver: twim::Twim<'static, T>,
}
// TODO: Implement CRC checks
impl<T: twim::Instance> GenericTempHumiditySensor<T> {
fn new(address: u8, driver: twim::Twim<'static, T>) -> Self {
Self { address, driver }
}
/// The procedure for reading out the temperature and humidity and converting them to floats is
/// detailed in the datasheet of the SHT41-AD1B-R2.
async fn read_temp_and_humidity(&mut self) -> (f32, f32) {
let mut buf = [0u8; 6];
unwrap!(self.driver.blocking_write(self.address, &mut [0xFD]));
Timer::after_millis(10).await;
unwrap!(self.driver.blocking_read(self.address, &mut buf));
let temp_int = u16::from_be_bytes([buf[0], buf[1]]);
let temp_float: f32 = -45f32 + 175f32 * (temp_int as f32 / (2u32.pow(16) - 1) as f32);
let rh_int = u16::from_be_bytes([buf[3], buf[4]]);
let rh_float: f32 = -6f32 + 125f32 * (rh_int as f32 / (2u32.pow(16) - 1) as f32);
return (temp_float, rh_float);
}
}
bind_interrupts!(struct Irqs {
SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twim::InterruptHandler<peripherals::TWISPI0>;
});
type TempHumiditySensor = GenericTempHumiditySensor<TWISPI0>;
#[embassy_executor::task]
async fn measurement_task(mut sensor: TempHumiditySensor, server: &'static Server) {
loop {
let (temp, humidity) = sensor.read_temp_and_humidity().await;
println!("{} °C\t{} %", temp, humidity);
let temp: u32 = (temp * 100.0) as u32;
let humidity: u32 = (humidity * 100.0) as u32;
unwrap!(server.bas.temperature_set(&temp));
unwrap!(server.bas.humidity_set(&humidity));
Timer::after_secs(5).await;
}
}
//
//
// Main code
//
//
static SERVER: StaticCell<Server> = StaticCell::new();
#[embassy_executor::main]
async fn main(spawner: Spawner) {
// Set up peripherals
interrupt::SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0.set_priority(interrupt::Priority::P2);
let mut embassy_config = embassy_nrf::config::Config::default();
embassy_config.gpiote_interrupt_priority = interrupt::Priority::P2;
embassy_config.time_interrupt_priority = interrupt::Priority::P2;
let p = embassy_nrf::init(embassy_config);
let config = twim::Config::default();
let twi = twim::Twim::new(p.TWISPI0, Irqs, p.P0_14, p.P0_13, config);
let sensor = TempHumiditySensor::new(0x44, twi);
let mut led = Output::new(p.P0_20, Level::Low, OutputDrive::Standard);
// Set up softdevice
let config = nrf_softdevice::Config {
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,
}),
conn_gap: Some(raw::ble_gap_conn_cfg_t {
conn_count: 1,
event_length: 24,
}),
conn_gatt: Some(raw::ble_gatt_conn_cfg_t { att_mtu: 256 }),
gatts_attr_tab_size: Some(raw::ble_gatts_cfg_attr_tab_size_t {
attr_tab_size: raw::BLE_GATTS_ATTR_TAB_SIZE_DEFAULT,
}),
gap_role_count: Some(raw::ble_gap_cfg_role_count_t {
adv_set_count: 1,
periph_role_count: 1,
}),
gap_device_name: Some(raw::ble_gap_cfg_device_name_t {
p_value: b"CommonSense" as *const u8 as _,
current_len: 9,
max_len: 9,
write_perm: unsafe { mem::zeroed() },
_bitfield_1: raw::ble_gap_cfg_device_name_t::new_bitfield_1(
raw::BLE_GATTS_VLOC_STACK as u8,
),
}),
..Default::default()
};
let sd = Softdevice::enable(&config);
let server = unwrap!(Server::new(sd));
let server: &'static mut Server = SERVER.init(server);
// Start tasks
unwrap!(spawner.spawn(softdevice_task(sd)));
unwrap!(spawner.spawn(ble_task(sd, server)));
unwrap!(spawner.spawn(measurement_task(sensor, server)));
loop {
led.set_high();
Timer::after_millis(500).await;
led.set_low();
Timer::after_millis(500).await;
}
}