feat: animated lcd scroller

This commit is contained in:
cscherr 2025-05-20 12:54:15 +02:00
parent 87a8b04c35
commit be26f8f688
Signed by: cscherrNT
GPG key ID: 8E2B45BC51A27EA7

161
examples/lcd-animate.rs Executable file
View file

@ -0,0 +1,161 @@
#![no_main]
#![no_std]
use heapless::{String, Vec};
use panic_probe as _;
use defmt_rtt as _; // global logger
use cortex_m_rt::entry;
use hal::{
delay::Delay,
gpio::{Output, PushPull, gpioa::*, gpiob::*, gpioc::*},
pac,
prelude::*,
rcc::Config,
};
use hd44780_driver::HD44780;
#[defmt::panic_handler]
fn panic() -> ! {
cortex_m::asm::udf()
}
type Lcd = HD44780<
hd44780_driver::bus::FourBitBus<
PA9<Output<PushPull>>,
PC7<Output<PushPull>>,
PB5<Output<PushPull>>,
PB4<Output<PushPull>>,
PB10<Output<PushPull>>,
PA8<Output<PushPull>>,
>,
>;
const FPS: u32 = 12;
const LINES: usize = 4;
const CHARS: usize = 20;
const SIGNS_LEN: usize = 7;
const SIGNS: [char; SIGNS_LEN] = ['N', 'e', 'w', 'T', 'e', 'c', ' '];
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
let mut rcc = dp.RCC.freeze(Config::hsi16());
let gpioa = dp.GPIOA.split(&mut rcc);
let gpiob = dp.GPIOB.split(&mut rcc);
let gpioc = dp.GPIOC.split(&mut rcc);
// literal D4-D7 ports etc as written on the nucleo board, mapped to the D4-D7 ports of the LCD
// controller
let d4 = gpiob.pb5.into_push_pull_output();
let d5 = gpiob.pb4.into_push_pull_output();
let d6 = gpiob.pb10.into_push_pull_output();
let d7 = gpioa.pa8.into_push_pull_output();
// clock enable on D9
let en = gpioc.pc7.into_push_pull_output();
// register select on D8, the lib wants that but I'd just put it on ground otherwise
let rs = gpioa.pa9.into_push_pull_output();
// See https://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller#Interface
// for the pins of the LCD
let mut delay = cp.SYST.delay(rcc.clocks);
let mut led = gpioa.pa5.into_push_pull_output();
let mut lcd: Lcd = HD44780::new_4bit(rs, en, d4, d5, d6, d7, &mut delay)
.expect("could not init HD44780 driver");
lcd.set_display_mode(
hd44780_driver::DisplayMode {
cursor_visibility: hd44780_driver::Cursor::Invisible,
cursor_blink: hd44780_driver::CursorBlink::Off,
display: hd44780_driver::Display::On,
},
&mut delay,
)
.expect("could not set display properties");
lcd.reset(&mut delay).expect("could not reset the lcd");
let mut i: usize = 0;
let mut buf: Vec<String<CHARS>, LINES> = Vec::new();
loop {
led.set_high().unwrap();
reset_buf(&mut buf);
animation(&mut buf, i);
display(&buf, &mut lcd, &mut delay, i % LINES);
led.set_low().unwrap();
delay.delay_us(1_000_000 / FPS);
i += 1;
}
}
fn display<const LINES: usize, const CHARS: usize>(
buf: &Vec<String<CHARS>, LINES>,
lcd: &mut Lcd,
delay: &mut Delay,
which: usize,
) {
match which {
0 => {
lcd.set_cursor_pos(0, delay)
.expect("could not set cursor pos");
lcd.write_str(&buf[0], delay)
.expect("could not display string");
}
1 => {
lcd.set_cursor_pos(60, delay)
.expect("could not set cursor pos");
lcd.write_str(&buf[1], delay)
.expect("could not display string");
}
2 => {
lcd.set_cursor_pos(20, delay)
.expect("could not set cursor pos");
lcd.write_str(&buf[2], delay)
.expect("could not display string");
}
3 => {
// line 4 is a bit weird and needs some offset
let mut tmp: String<30> = String::new();
tmp.push_str(" ").unwrap();
tmp.push_str(&buf[3]).unwrap();
lcd.set_cursor_pos(80, delay)
.expect("could not set cursor pos");
lcd.write_str(&tmp, delay)
.expect("could not display string");
}
_ => unreachable!(),
}
}
fn animation<const LINES: usize, const CHARS: usize>(
buf: &mut Vec<String<CHARS>, LINES>,
frame: usize,
) {
for i in 0..CHARS {
for (bi, buf) in buf.iter_mut().enumerate() {
match buf.push(SIGNS[(i + bi * 2 + frame) % SIGNS_LEN]) {
Ok(_) => (),
Err(_e) => {
panic!("Could not push string in animation. i={}, bi={}", i, bi);
}
}
}
}
}
fn reset_buf<const LINES: usize, const CHARS: usize>(buf: &mut Vec<String<CHARS>, LINES>) {
buf.clear();
for _ in 0..LINES {
buf.push(String::new()).unwrap();
}
}