prepare simple example
This commit is contained in:
parent
9419afb49e
commit
df605bfd77
@ -4,11 +4,9 @@ A simple application for an
|
|||||||
[nRF52840](https://www.nordicsemi.com/Products/nRF52840) microcontroller using
|
[nRF52840](https://www.nordicsemi.com/Products/nRF52840) microcontroller using
|
||||||
[embassy](https://embassy.dev/).
|
[embassy](https://embassy.dev/).
|
||||||
|
|
||||||
It consists of multiple parts:
|
It shows information received with BLE on a
|
||||||
|
[HD44780 LCD like](https://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller)
|
||||||
- Configuration interface leveraging BLE
|
screen.
|
||||||
- Connect to WiFi in order subscribe to a configured MQTT topic
|
|
||||||
- Show information on an LCD screen
|
|
||||||
|
|
||||||
This is a learning environment, not a finished product.
|
This is a learning environment, not a finished product.
|
||||||
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
#[nrf_softdevice::gatt_service(uuid = "180f")]
|
|
||||||
pub struct BatteryService {
|
|
||||||
#[characteristic(uuid = "2a19", read, notify)]
|
|
||||||
pub battery_level: u8,
|
|
||||||
}
|
|
@ -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],
|
|
||||||
}
|
|
@ -1,8 +1,5 @@
|
|||||||
use crate::ble::config_service::ConfigService;
|
use crate::ble::msg_service::{MessageService, MessageServiceEvent};
|
||||||
use crate::ble::config_service::ConfigServiceEvent;
|
use crate::events;
|
||||||
|
|
||||||
use crate::ble::battery_service::BatteryService;
|
|
||||||
use crate::ble::battery_service::BatteryServiceEvent;
|
|
||||||
|
|
||||||
use defmt::info;
|
use defmt::info;
|
||||||
use nrf_softdevice::ble::gatt_server;
|
use nrf_softdevice::ble::gatt_server;
|
||||||
@ -11,27 +8,18 @@ use nrf_softdevice::ble::DisconnectedError;
|
|||||||
|
|
||||||
#[nrf_softdevice::gatt_server]
|
#[nrf_softdevice::gatt_server]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
pub bas: BatteryService,
|
pub config: MessageService,
|
||||||
pub config: ConfigService,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(conn: &Connection, server: &Server) -> DisconnectedError {
|
pub async fn run(conn: &Connection, server: &Server) -> DisconnectedError {
|
||||||
info!("gatt started");
|
info!("gatt started");
|
||||||
|
|
||||||
|
let event_pub = events::EVENTS.publisher().unwrap();
|
||||||
gatt_server::run(conn, server, |e| match e {
|
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 {
|
ServerEvent::Config(e) => match e {
|
||||||
ConfigServiceEvent::WifiSsidWrite(val) => {
|
MessageServiceEvent::MessageWrite(val) => {
|
||||||
info!("new ssid: {}", val);
|
info!("new message: {}", val);
|
||||||
}
|
event_pub.publish_immediate(events::Event::Message(val));
|
||||||
ConfigServiceEvent::WifiPwWrite(val) => {
|
|
||||||
info!("new pw: {}", val);
|
|
||||||
}
|
|
||||||
ConfigServiceEvent::MqttBrokerWrite(val) => {
|
|
||||||
info!("new broker: {}", val);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
8
display/src/ble/msg_service.rs
Normal file
8
display/src/ble/msg_service.rs
Normal 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
12
display/src/events.rs
Normal 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();
|
@ -7,9 +7,7 @@ const MAX_STRING_LENGTH: u32 = 128;
|
|||||||
const CONFIG_START: u32 = 0x80000;
|
const CONFIG_START: u32 = 0x80000;
|
||||||
|
|
||||||
pub enum ConfigItem {
|
pub enum ConfigItem {
|
||||||
Ssid,
|
Message,
|
||||||
WifiPw,
|
|
||||||
MqtBroker,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Config<'a> {
|
pub struct Config<'a> {
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
//! Code mostly translated from here (an excellent project):
|
//! 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
|
//! 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::Priority;
|
||||||
use embassy_nrf::interrupt::{self, InterruptExt};
|
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::twim::{self, Twim};
|
||||||
use embassy_nrf::{bind_interrupts, peripherals};
|
use embassy_nrf::{bind_interrupts, peripherals};
|
||||||
use embassy_time::Delay;
|
use embassy_time::Delay;
|
||||||
@ -50,7 +51,6 @@ pub enum Lines {
|
|||||||
|
|
||||||
pub struct Lcd<'a> {
|
pub struct Lcd<'a> {
|
||||||
i2c: Twim<'a, TWISPI0>,
|
i2c: Twim<'a, TWISPI0>,
|
||||||
lines: [&'a str; ROWS],
|
|
||||||
address: u8,
|
address: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,42 +104,31 @@ impl<'a> Lcd<'a> {
|
|||||||
Ok(())
|
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);
|
interrupt::SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0.set_priority(Priority::P2);
|
||||||
|
|
||||||
let config = twim::Config::default();
|
let config = twim::Config::default();
|
||||||
let i2c = Twim::new(spi0, Irqs, p03, p04, config);
|
let i2c = Twim::new(spi0, Irqs, p03, p04, config);
|
||||||
|
|
||||||
Lcd {
|
Lcd { i2c, address }
|
||||||
i2c,
|
|
||||||
lines: ["", ""],
|
|
||||||
address,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn scroll_buffer(&mut self) -> Result<(), twim::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn write_buffer(&mut self) -> Result<(), twim::Error> {
|
async fn write_buffer(&mut self, text: &str, line: Lines) -> Result<(), twim::Error> {
|
||||||
#[allow(clippy::needless_range_loop)]
|
for (position, c) in text.chars().enumerate() {
|
||||||
for line in 0..ROWS {
|
self.write_character(c, position as u8, LINE_OFFSETS[line as usize])
|
||||||
for (position, c) in self.lines[line].chars().enumerate() {
|
|
||||||
self.write_character(c, position as u8, LINE_OFFSETS[line])
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clear(&mut self) -> Result<(), twim::Error> {
|
pub async fn clear(&mut self) -> Result<(), twim::Error> {
|
||||||
self.cmd(CLEAR_DISPLAY, 0).await?;
|
self.cmd(CLEAR_DISPLAY, 0).await?;
|
||||||
self.lines = ["", ""];
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -163,9 +152,8 @@ impl<'a> Lcd<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn write(&mut self, text: &'a str, line: Lines) -> Result<(), twim::Error> {
|
pub async fn write(&mut self, text: &str, line: Lines) -> Result<(), twim::Error> {
|
||||||
self.lines[line as usize] = text;
|
self.write_buffer(text, line).await?;
|
||||||
self.write_buffer().await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use ble::gatt::Server;
|
use ble::{gatt::Server, msg_service::MAX_STRING_LENGTH};
|
||||||
use defmt_rtt as _;
|
use defmt_rtt as _;
|
||||||
use embassy_nrf as _;
|
use embassy_nrf::{self as _, gpio::Pin};
|
||||||
use embassy_time::Delay;
|
use embassy_time::Delay;
|
||||||
use embedded_hal_async::delay::DelayNs;
|
use embedded_hal_async::delay::DelayNs;
|
||||||
use futures::pin_mut;
|
use futures::pin_mut;
|
||||||
@ -14,11 +14,11 @@ use crate::lcd::Lcd;
|
|||||||
use defmt::{error, info, unwrap};
|
use defmt::{error, info, unwrap};
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
|
||||||
|
pub mod events;
|
||||||
pub mod ble {
|
pub mod ble {
|
||||||
pub mod advertisment;
|
pub mod advertisment;
|
||||||
pub mod battery_service;
|
|
||||||
pub mod config_service;
|
|
||||||
pub mod gatt;
|
pub mod gatt;
|
||||||
|
pub mod msg_service;
|
||||||
}
|
}
|
||||||
pub mod flash;
|
pub mod flash;
|
||||||
pub mod lcd;
|
pub mod lcd;
|
||||||
@ -37,9 +37,27 @@ async fn ble(sd: &'static Softdevice, server: Server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[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 d = Delay {};
|
||||||
|
let mut event_sub = events::EVENTS.subscriber().unwrap();
|
||||||
|
|
||||||
loop {
|
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()
|
lcd.scroll_buffer()
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|e| error!("Failed to scroll lcd: {}", e));
|
.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]
|
#[embassy_executor::main]
|
||||||
async fn main(spawner: Spawner) {
|
async fn main(spawner: Spawner) {
|
||||||
let peripherals = peripherals::init();
|
let peripherals = peripherals::init();
|
||||||
let mut lcd = Lcd::new(
|
let mut lcd = Lcd::new(
|
||||||
0x27,
|
0x27,
|
||||||
peripherals.TWISPI0,
|
peripherals.TWISPI0,
|
||||||
peripherals.P0_03,
|
peripherals.P0_03.degrade(),
|
||||||
peripherals.P0_04,
|
peripherals.P0_04.degrade(),
|
||||||
);
|
);
|
||||||
lcd.init().await.expect("Failed to initialize lcd");
|
lcd.init().await.expect("Failed to initialize lcd");
|
||||||
let (sd, server) = softdevice::run(spawner, DEVICE_NAME).expect("Failed to run softdevice");
|
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(ble(sd, server)));
|
||||||
unwrap!(spawner.spawn(tick(sd, lcd, 500)));
|
unwrap!(spawner.spawn(lcd_scroll(sd, lcd, 500)));
|
||||||
|
unwrap!(spawner.spawn(config(sd)));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user