prepare simple example

This commit is contained in:
Sebastian Hugentobler 2024-11-16 16:55:15 +01:00
parent 9419afb49e
commit df605bfd77
Signed by: shu
GPG Key ID: BB32CF3CA052C2F0
9 changed files with 80 additions and 80 deletions

View File

@ -4,11 +4,9 @@ A simple application for an
[nRF52840](https://www.nordicsemi.com/Products/nRF52840) microcontroller using
[embassy](https://embassy.dev/).
It consists of multiple parts:
- Configuration interface leveraging BLE
- Connect to WiFi in order subscribe to a configured MQTT topic
- Show information on an LCD screen
It shows information received with BLE on a
[HD44780 LCD like](https://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller)
screen.
This is a learning environment, not a finished product.

View File

@ -1,5 +0,0 @@
#[nrf_softdevice::gatt_service(uuid = "180f")]
pub struct BatteryService {
#[characteristic(uuid = "2a19", read, notify)]
pub battery_level: u8,
}

View File

@ -1,12 +0,0 @@
const MAX_STRING_LENGTH: usize = 128;
#[nrf_softdevice::gatt_service(uuid = "437dc41e-d899-40ac-9c83-188c8c4d9fe7")]
pub struct ConfigService {
#[characteristic(uuid = "e7b9ebd9-57e0-4821-8fa3-55e22cd7b705", read, write)]
#[description = "WiFi SSID"]
pub wifi_ssid: [u8; MAX_STRING_LENGTH],
#[characteristic(uuid = "1bcc70df-179e-4853-bb74-c22380f491a3", read, write)]
pub wifi_pw: [u8; MAX_STRING_LENGTH],
#[characteristic(uuid = "3c9e4967-f792-4903-a968-490271cb7eeb", read, write)]
pub mqtt_broker: [u8; MAX_STRING_LENGTH],
}

View File

@ -1,8 +1,5 @@
use crate::ble::config_service::ConfigService;
use crate::ble::config_service::ConfigServiceEvent;
use crate::ble::battery_service::BatteryService;
use crate::ble::battery_service::BatteryServiceEvent;
use crate::ble::msg_service::{MessageService, MessageServiceEvent};
use crate::events;
use defmt::info;
use nrf_softdevice::ble::gatt_server;
@ -11,27 +8,18 @@ use nrf_softdevice::ble::DisconnectedError;
#[nrf_softdevice::gatt_server]
pub struct Server {
pub bas: BatteryService,
pub config: ConfigService,
pub config: MessageService,
}
pub async fn run(conn: &Connection, server: &Server) -> DisconnectedError {
info!("gatt started");
let event_pub = events::EVENTS.publisher().unwrap();
gatt_server::run(conn, server, |e| match e {
ServerEvent::Bas(e) => match e {
BatteryServiceEvent::BatteryLevelCccdWrite { notifications } => {
info!("battery notifications: {}", notifications)
}
},
ServerEvent::Config(e) => match e {
ConfigServiceEvent::WifiSsidWrite(val) => {
info!("new ssid: {}", val);
}
ConfigServiceEvent::WifiPwWrite(val) => {
info!("new pw: {}", val);
}
ConfigServiceEvent::MqttBrokerWrite(val) => {
info!("new broker: {}", val);
MessageServiceEvent::MessageWrite(val) => {
info!("new message: {}", val);
event_pub.publish_immediate(events::Event::Message(val));
}
},
})

View File

@ -0,0 +1,8 @@
pub const MAX_STRING_LENGTH: usize = 128;
#[nrf_softdevice::gatt_service(uuid = "437dc41e-d899-40ac-9c83-188c8c4d9fe7")]
pub struct MessageService {
#[characteristic(uuid = "e7b9ebd9-57e0-4821-8fa3-55e22cd7b705", read, write)]
#[description = "Message"]
pub message: [u8; MAX_STRING_LENGTH],
}

12
display/src/events.rs Normal file
View File

@ -0,0 +1,12 @@
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pubsub::PubSubChannel};
use crate::ble::msg_service::MAX_STRING_LENGTH;
#[derive(Clone, Copy)]
pub enum Event {
// New message.
Message([u8; MAX_STRING_LENGTH]),
}
/// PubSub channel to listen to events and publish them as well..
pub static EVENTS: PubSubChannel<CriticalSectionRawMutex, Event, 4, 4, 4> = PubSubChannel::new();

View File

@ -7,9 +7,7 @@ const MAX_STRING_LENGTH: u32 = 128;
const CONFIG_START: u32 = 0x80000;
pub enum ConfigItem {
Ssid,
WifiPw,
MqtBroker,
Message,
}
pub struct Config<'a> {

View File

@ -3,9 +3,10 @@
//! Code mostly translated from here (an excellent project):
//! https://github.com/Pi4J/pi4j-example-components/blob/main/src/main/java/com/pi4j/catalog/components/LcdDisplay.java
use embassy_nrf::gpio::AnyPin;
use embassy_nrf::interrupt::Priority;
use embassy_nrf::interrupt::{self, InterruptExt};
use embassy_nrf::peripherals::{P0_03, P0_04, TWISPI0};
use embassy_nrf::peripherals::TWISPI0;
use embassy_nrf::twim::{self, Twim};
use embassy_nrf::{bind_interrupts, peripherals};
use embassy_time::Delay;
@ -50,7 +51,6 @@ pub enum Lines {
pub struct Lcd<'a> {
i2c: Twim<'a, TWISPI0>,
lines: [&'a str; ROWS],
address: u8,
}
@ -104,34 +104,24 @@ impl<'a> Lcd<'a> {
Ok(())
}
pub fn new(address: u8, spi0: TWISPI0, p03: P0_03, p04: P0_04) -> Self {
pub fn new(address: u8, spi0: TWISPI0, p03: AnyPin, p04: AnyPin) -> Self {
interrupt::SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0.set_priority(Priority::P2);
let config = twim::Config::default();
let i2c = Twim::new(spi0, Irqs, p03, p04, config);
Lcd {
i2c,
lines: ["", ""],
address,
}
Lcd { i2c, address }
}
pub async fn scroll_buffer(&mut self) -> Result<(), twim::Error> {
if self.lines[0].len() > LINE_LENGTH || self.lines[1].len() > LINE_LENGTH {
self.cmd(SCROLL_LEFT, 0).await?;
}
self.cmd(SCROLL_LEFT, 0).await?;
Ok(())
}
async fn write_buffer(&mut self) -> Result<(), twim::Error> {
#[allow(clippy::needless_range_loop)]
for line in 0..ROWS {
for (position, c) in self.lines[line].chars().enumerate() {
self.write_character(c, position as u8, LINE_OFFSETS[line])
.await?;
}
async fn write_buffer(&mut self, text: &str, line: Lines) -> Result<(), twim::Error> {
for (position, c) in text.chars().enumerate() {
self.write_character(c, position as u8, LINE_OFFSETS[line as usize])
.await?;
}
Ok(())
@ -139,7 +129,6 @@ impl<'a> Lcd<'a> {
pub async fn clear(&mut self) -> Result<(), twim::Error> {
self.cmd(CLEAR_DISPLAY, 0).await?;
self.lines = ["", ""];
Ok(())
}
@ -163,9 +152,8 @@ impl<'a> Lcd<'a> {
Ok(())
}
pub async fn write(&mut self, text: &'a str, line: Lines) -> Result<(), twim::Error> {
self.lines[line as usize] = text;
self.write_buffer().await?;
pub async fn write(&mut self, text: &str, line: Lines) -> Result<(), twim::Error> {
self.write_buffer(text, line).await?;
Ok(())
}

View File

@ -1,9 +1,9 @@
#![no_std]
#![no_main]
use ble::gatt::Server;
use ble::{gatt::Server, msg_service::MAX_STRING_LENGTH};
use defmt_rtt as _;
use embassy_nrf as _;
use embassy_nrf::{self as _, gpio::Pin};
use embassy_time::Delay;
use embedded_hal_async::delay::DelayNs;
use futures::pin_mut;
@ -14,11 +14,11 @@ use crate::lcd::Lcd;
use defmt::{error, info, unwrap};
use embassy_executor::Spawner;
pub mod events;
pub mod ble {
pub mod advertisment;
pub mod battery_service;
pub mod config_service;
pub mod gatt;
pub mod msg_service;
}
pub mod flash;
pub mod lcd;
@ -37,9 +37,27 @@ async fn ble(sd: &'static Softdevice, server: Server) {
}
#[embassy_executor::task]
async fn tick(sd: &'static Softdevice, mut lcd: lcd::Lcd<'static>, delay: u32) {
async fn lcd_scroll(sd: &'static Softdevice, mut lcd: lcd::Lcd<'static>, delay: u32) {
fn until_first_zero(data: &[u8]) -> &[u8] {
match data.iter().position(|&x| x == 0) {
Some(pos) => &data[..pos],
None => data,
}
}
let mut d = Delay {};
let mut event_sub = events::EVENTS.subscriber().unwrap();
loop {
if let Some(events::Event::Message(new_msg)) = event_sub.try_next_message_pure() {
let new_msg = until_first_zero(&new_msg);
match core::str::from_utf8(new_msg) {
Ok(msg) => lcd.write(msg, lcd::Lines::First).await.unwrap(),
Err(_) => error!("failed to parse incoming message as utf8"),
}
}
lcd.scroll_buffer()
.await
.unwrap_or_else(|e| error!("Failed to scroll lcd: {}", e));
@ -48,22 +66,29 @@ async fn tick(sd: &'static Softdevice, mut lcd: lcd::Lcd<'static>, delay: u32) {
}
}
#[embassy_executor::task]
async fn config(sd: &'static Softdevice) {
let flash = Flash::take(sd);
pin_mut!(flash);
let config = flash::Config::new(flash);
loop {}
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let peripherals = peripherals::init();
let mut lcd = Lcd::new(
0x27,
peripherals.TWISPI0,
peripherals.P0_03,
peripherals.P0_04,
peripherals.P0_03.degrade(),
peripherals.P0_04.degrade(),
);
lcd.init().await.expect("Failed to initialize lcd");
let (sd, server) = softdevice::run(spawner, DEVICE_NAME).expect("Failed to run softdevice");
let flash = Flash::take(sd);
pin_mut!(flash);
let config = flash::Config::new(flash);
unwrap!(spawner.spawn(ble(sd, server)));
unwrap!(spawner.spawn(tick(sd, lcd, 500)));
unwrap!(spawner.spawn(lcd_scroll(sd, lcd, 500)));
unwrap!(spawner.spawn(config(sd)));
}