rudimentary lcd implementation
This commit is contained in:
parent
a5aa57148f
commit
f8894e9529
10 changed files with 375 additions and 57 deletions
169
display/src/lcd.rs
Normal file
169
display/src/lcd.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
|
|
9
display/src/peripherals.rs
Normal file
9
display/src/peripherals.rs
Normal 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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue