rudimentary lcd implementation

This commit is contained in:
Sebastian Hugentobler 2024-06-28 18:18:43 +02:00
parent a5aa57148f
commit f8894e9529
Signed by: shu
GPG key ID: BB32CF3CA052C2F0
10 changed files with 375 additions and 57 deletions

32
display/Cargo.lock generated
View file

@ -29,9 +29,12 @@ dependencies = [
"cortex-m-rt",
"defmt",
"defmt-rtt",
"ekv",
"embassy-embedded-hal",
"embassy-executor",
"embassy-nrf",
"embassy-time",
"embedded-hal-async",
"nrf-softdevice",
"panic-probe",
]
@ -209,9 +212,9 @@ dependencies = [
[[package]]
name = "defmt"
version = "0.3.6"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3939552907426de152b3c2c6f51ed53f98f448babd26f28694c95f5906194595"
checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0"
dependencies = [
"bitflags 1.3.2",
"defmt-macros",
@ -241,9 +244,9 @@ dependencies = [
[[package]]
name = "defmt-rtt"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "609923761264dd99ed9c7d209718cda4631c5fe84668e0f0960124cbb844c49f"
checksum = "bab697b3dbbc1750b7c8b821aa6f6e7f2480b47a99bc057a2ed7b170ebef0c51"
dependencies = [
"critical-section",
"defmt",
@ -258,6 +261,15 @@ dependencies = [
"litrs",
]
[[package]]
name = "ekv"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/ekv.git?rev=571a9c8863e9644682ebd41343eaf0444ea4bf87#571a9c8863e9644682ebd41343eaf0444ea4bf87"
dependencies = [
"embassy-sync 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"heapless",
]
[[package]]
name = "embassy-embedded-hal"
version = "0.1.0"
@ -821,9 +833,9 @@ dependencies = [
[[package]]
name = "panic-probe"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa6fa5645ef5a760cd340eaa92af9c1ce131c8c09e7f8926d8a24b59d26652b9"
checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0"
dependencies = [
"cortex-m",
"defmt",
@ -978,18 +990,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.60"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.60"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",

View file

@ -10,6 +10,11 @@ git = "https://github.com/embassy-rs/embassy.git"
rev = "3c52ef60b19468a8700d612699c869f0bdcae339"
features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"]
[dependencies.embassy-embedded-hal]
version = "0.1.0"
git = "https://github.com/embassy-rs/embassy.git"
rev = "3c52ef60b19468a8700d612699c869f0bdcae339"
[dependencies.embassy-executor]
version = "0.5.0"
git = "https://github.com/embassy-rs/embassy.git"
@ -29,10 +34,19 @@ rev = "3c53b8c454cc9331082053033485e713abcadbb5"
features = ["defmt", "ble-peripheral", "ble-central", "critical-section-impl", "ble-gatt-server", "nrf52840", "s140"]
[dependencies]
defmt = "0.3.6"
defmt-rtt = "0.4.0"
defmt = "0.3.8"
defmt-rtt = "0.4.1"
panic-probe = { version = "0.3.1", features = ["print-defmt"] }
panic-probe = { version = "0.3.2", features = ["print-defmt"] }
cortex-m = { version = "0.7.7", features = ["inline-asm"] }
cortex-m-rt = "0.7.4"
cortex-m-rt = "0.7.3"
embedded-hal-async = "1.0.0"
ekv = { version = "0.1.0", git = "https://github.com/embassy-rs/ekv.git", rev = "571a9c8863e9644682ebd41343eaf0444ea4bf87", features = [
"crc",
"page-size-4096",
"align-4",
"max-page-count-2048",
]}

169
display/src/lcd.rs Normal file
View file

@ -0,0 +1,169 @@
use embassy_nrf::interrupt::Priority;
use embassy_nrf::interrupt::{self, InterruptExt};
use embassy_nrf::peripherals::{P0_03, P0_04, TWISPI0};
use embassy_nrf::twim::{self, Twim};
use embassy_nrf::{bind_interrupts, peripherals};
use embassy_time::Delay;
use embedded_hal_async::delay::DelayNs;
bind_interrupts!(struct Irqs {
SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twim::InterruptHandler<peripherals::TWISPI0>;
});
const LCD_2_LINE: u8 = 0x08;
const LCD_4_BIT_MODE: u8 = 0x00;
const LCD_5X8DOTS: u8 = 0x00;
const CLEAR_DISPLAY: u8 = 0x01;
const DISPLAY_ON: u8 = 0x04;
const SCROLL_LEFT: u8 = 0x18;
const BLINK_ON: u8 = 0x01;
const CURSOR_ON: u8 = 0x02;
const BLINK_OFF: u8 = 0x00;
const CURSOR_OFF: u8 = 0x00;
const FUNCTION_SET: u8 = 0x20;
const DISPLAY_CONTROL: u8 = 0x08;
const BACKLIGHT: u8 = 0x08;
const RETURN_HOME: u8 = 0x02;
const SET_DDRAM_ADDR: u8 = 0x80;
const EN: u8 = 0b000_00100;
const RS: u8 = 0b000_00001;
const LINE_LENGTH: usize = 16;
const LINE_OFFSETS: [u8; 2] = [0x00, 0x40];
#[derive(Debug, Clone, Copy)]
pub enum Lines {
First,
Second,
}
pub struct Lcd<'a> {
i2c: Twim<'a, TWISPI0>,
lines: [&'a str; 2],
address: u8,
}
impl<'a> Lcd<'a> {
async fn write_four_bits(&mut self, nibble: u8) -> Result<(), twim::Error> {
self.i2c
.write(self.address, &[nibble | EN | BACKLIGHT])
.await?;
self.i2c
.write(self.address, &[(nibble & !EN) | BACKLIGHT])
.await?;
Ok(())
}
async fn cmd(&mut self, data: u8, mode: u8) -> Result<(), twim::Error> {
self.write_four_bits(mode | (data & 0xF0)).await?;
self.write_four_bits(mode | ((data << 4) & 0xF0)).await?;
Ok(())
}
pub async fn init(&mut self) -> Result<(), twim::Error> {
let mut delay = Delay {};
delay.delay_ms(100).await;
self.cmd(0x03, 0).await?;
self.cmd(0x03, 0).await?;
self.cmd(0x03, 0).await?;
self.cmd(0x02, 0).await?;
self.cmd(FUNCTION_SET | LCD_2_LINE | LCD_5X8DOTS | LCD_4_BIT_MODE, 0)
.await?;
self.cmd(DISPLAY_CONTROL | DISPLAY_ON | CURSOR_OFF | BLINK_OFF, 0)
.await?;
self.cmd(FUNCTION_SET | LCD_2_LINE | LCD_5X8DOTS | LCD_4_BIT_MODE, 0)
.await?;
self.cmd(RETURN_HOME, 0).await?;
self.clear().await?;
self.i2c.write(self.address, &[BACKLIGHT]).await?;
delay.delay_ms(10).await;
self.cmd(DISPLAY_CONTROL | DISPLAY_ON | CURSOR_ON | BLINK_ON, 0)
.await?;
self.set_cursor(0, LINE_OFFSETS[Lines::First as usize])
.await?;
Ok(())
}
pub fn new(address: u8, spi0: TWISPI0, p03: P0_03, p04: P0_04) -> 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,
}
}
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?;
}
Ok(())
}
async fn write_buffer(&mut self) -> Result<(), twim::Error> {
for line in 0..2 {
let mut position = 0;
for c in self.lines[line].chars() {
self.write_character(c, position, LINE_OFFSETS[line])
.await?;
position += 1;
}
}
Ok(())
}
pub async fn clear(&mut self) -> Result<(), twim::Error> {
self.cmd(CLEAR_DISPLAY, 0).await?;
self.lines = ["", ""];
Ok(())
}
async fn set_cursor(&mut self, position: u8, line_offset: u8) -> Result<(), twim::Error> {
self.cmd(SET_DDRAM_ADDR | line_offset + position, 0).await?;
Ok(())
}
async fn write_character(
&mut self,
character: char,
position: u8,
line_offset: u8,
) -> Result<(), twim::Error> {
self.set_cursor(position, line_offset).await?;
self.cmd(character as u8, RS).await?;
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?;
Ok(())
}
}

View file

@ -1,13 +1,14 @@
#![no_std]
#![no_main]
use ble::gatt::Server;
use defmt_rtt as _;
use embassy_nrf as _;
use nrf_softdevice::Softdevice;
use embassy_time::Delay;
use embedded_hal_async::delay::DelayNs;
use panic_probe as _;
use defmt::{info, unwrap};
use crate::lcd::Lcd;
use defmt::{error, info, unwrap};
use embassy_executor::Spawner;
pub mod ble {
@ -17,6 +18,9 @@ pub mod ble {
pub mod softdevice;
}
pub mod lcd;
pub mod peripherals;
const DEVICE_NAME: &str = "alert-me display";
#[embassy_executor::task]
@ -30,7 +34,37 @@ async fn ble(spawner: Spawner) {
}
}
#[embassy_executor::task]
async fn tick(mut lcd: lcd::Lcd<'static>, delay: u32) {
let mut d = Delay {};
loop {
lcd.scroll_buffer()
.await
.unwrap_or_else(|e| error!("Failed to scroll lcd: {}", e));
d.delay_ms(delay).await;
}
}
#[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,
);
lcd.init()
.await
.unwrap_or_else(|e| error!("Failed to initialize lcd: {}", e));
lcd.write("Hello, horse :)", lcd::Lines::First)
.await
.unwrap();
lcd.write("I am an electronic horse!", lcd::Lines::Second)
.await
.unwrap();
unwrap!(spawner.spawn(ble(spawner)));
unwrap!(spawner.spawn(tick(lcd, 300)));
}

View file

@ -0,0 +1,9 @@
use embassy_nrf::interrupt::Priority;
pub fn init() -> embassy_nrf::Peripherals {
let mut config = embassy_nrf::config::Config::default();
config.gpiote_interrupt_priority = Priority::P2;
config.time_interrupt_priority = Priority::P2;
embassy_nrf::init(config)
}