15 Commits

Author SHA1 Message Date
08c67a0151 Fix comment 2025-10-04 20:15:04 +02:00
e769494e3a Remove unnecessary code 2025-10-04 20:07:38 +02:00
ae7bd7b921 Fix configs: Make software compatible with nRF52810 again 2025-10-04 20:05:49 +02:00
3ffa59f5dc Explicitly set transmit power 2025-10-04 19:55:31 +02:00
392675dd47 Code style 2025-10-04 18:56:48 +02:00
e5e1d7cede Modify memory.x to reserve only as much RAM for the softdevice as necessary 2025-10-04 18:41:40 +02:00
1a42e27189 Implement first version of sending out measurements over BLE 2025-10-04 18:41:11 +02:00
2bff0a54c7 Refactor HumiditySensor to directly take Twim() as argument; Move measurement into separate task 2025-10-04 15:47:26 +02:00
72930892c6 Code style 2025-10-04 02:10:46 +02:00
f92bbf5d7d Refactor temp and humidity measurement code into structs with traits 2025-10-04 02:07:12 +02:00
7f6a3807c1 Add settings to disable test warnings to Cargo.toml 2025-10-04 02:06:47 +02:00
3a76aa6ef6 Make memory.x more similar to the nRF52810 version 2025-10-03 01:05:59 +02:00
164bc16e88 Make ble_gatt_server.rs work with the nRF552 DK 2025-10-03 01:05:59 +02:00
c6bc560c59 Adapt Cargo.toml, .cargo/config.toml, and memory.x to the nRF52832 2025-10-03 01:05:59 +02:00
629396e404 Add troubleshooting note about fixing locked up core 2025-10-03 01:05:05 +02:00
7 changed files with 278 additions and 232 deletions

View File

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

View File

@@ -68,3 +68,60 @@ softdevice has to flashed to the chip.
[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)
- 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

@@ -5,4 +5,3 @@ MEMORY
FLASH : ORIGIN = 0x00000000 + 100K, LENGTH = 192K - 100K
RAM : ORIGIN = 0x20000000 + 0x24b8, LENGTH = 24K - 0x24b8
}

View File

@@ -1,162 +0,0 @@
#![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();
info!("Hello World!");
// 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);
// }
}

View File

@@ -1,24 +0,0 @@
#![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

@@ -1,45 +0,0 @@
#![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;
}
}

213
src/main.rs Normal file
View File

@@ -0,0 +1,213 @@
#![no_std]
#![no_main]
use static_cell::StaticCell;
use core::mem;
use embassy_nrf::interrupt::{self, InterruptExt};
use embassy_nrf::peripherals::TWI0;
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 {
TWIM0_TWIS0_TWI0 => twim::InterruptHandler<peripherals::TWI0>;
});
type TempHumiditySensor = GenericTempHumiditySensor<TWI0>;
#[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::TWIM0_TWIS0_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.TWI0, 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;
}
}