diff --git a/display/README.md b/display/README.md index bd5f6e4..be82d67 100644 --- a/display/README.md +++ b/display/README.md @@ -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. diff --git a/display/src/ble/battery_service.rs b/display/src/ble/battery_service.rs deleted file mode 100644 index fb88877..0000000 --- a/display/src/ble/battery_service.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[nrf_softdevice::gatt_service(uuid = "180f")] -pub struct BatteryService { - #[characteristic(uuid = "2a19", read, notify)] - pub battery_level: u8, -} diff --git a/display/src/ble/config_service.rs b/display/src/ble/config_service.rs deleted file mode 100644 index 6ee44ff..0000000 --- a/display/src/ble/config_service.rs +++ /dev/null @@ -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], -} diff --git a/display/src/ble/gatt.rs b/display/src/ble/gatt.rs index bf0cbc8..3c7bde5 100644 --- a/display/src/ble/gatt.rs +++ b/display/src/ble/gatt.rs @@ -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)); } }, }) diff --git a/display/src/ble/msg_service.rs b/display/src/ble/msg_service.rs new file mode 100644 index 0000000..f485acb --- /dev/null +++ b/display/src/ble/msg_service.rs @@ -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], +} diff --git a/display/src/events.rs b/display/src/events.rs new file mode 100644 index 0000000..2f0c368 --- /dev/null +++ b/display/src/events.rs @@ -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 = PubSubChannel::new(); diff --git a/display/src/flash.rs b/display/src/flash.rs index 8ec142f..81b8e7e 100644 --- a/display/src/flash.rs +++ b/display/src/flash.rs @@ -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> { diff --git a/display/src/lcd.rs b/display/src/lcd.rs index 28dd327..8d5763d 100644 --- a/display/src/lcd.rs +++ b/display/src/lcd.rs @@ -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(()) } diff --git a/display/src/main.rs b/display/src/main.rs index 66e9fee..1156cce 100644 --- a/display/src/main.rs +++ b/display/src/main.rs @@ -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))); }