You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
9.0 KiB
Rust
263 lines
9.0 KiB
Rust
//! Driver for LiquidCrystal Displays plugs to the GPIO of the board.
|
|
//! The LCD need to be an Hitachi HD44780 chipset compatible.
|
|
//!
|
|
//! This driver inspired from the one from the arduino libriaries
|
|
//! (https://github.com/arduino-libraries/LiquidCrystal) and the
|
|
//! documentation from wikipedia (https://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller)
|
|
//! and the actual doc (https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
|
|
|
|
use crate::bsp::aarch64::time::time_manager;
|
|
use crate::gpio;
|
|
use crate::traits::time::TimeManager;
|
|
use core::time::Duration;
|
|
|
|
/// The font size
|
|
pub enum FontSize {
|
|
Font5x8Dots,
|
|
Font5x10Dots,
|
|
}
|
|
|
|
impl FontSize {
|
|
fn function_code(&self) -> u8 {
|
|
match self {
|
|
Self::Font5x8Dots => Lcd::FUNCTION_SET_5X8_DOTS,
|
|
Self::Font5x10Dots => Lcd::FUNCTION_SET_5X10_DOTS,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The structure handling the LCD periferic
|
|
pub struct Lcd {
|
|
/// The Register Select pinout.
|
|
rs_pin: usize,
|
|
/// The Read/Write pinout.
|
|
///
|
|
/// If set to None, assumes the rw pin of the lcd is connected to the ground (write state)
|
|
rw_pin: Option<usize>,
|
|
/// The Enable pinout.
|
|
///
|
|
/// TODO: wiki says falling-edge triggered, arduino comment says activated by a HIGH pulse.
|
|
e_pin: usize,
|
|
/// First 4 data bits pins.
|
|
///
|
|
/// Set to None if the lcd is in 4-bit mode.
|
|
first_data_bits_pins: Option<[usize; 4]>,
|
|
/// Last 4 data bits pins.
|
|
last_data_bits_pins: [usize; 4],
|
|
/// The number of line of the LCD.
|
|
nb_lines: usize,
|
|
/// The number of row of the LCD.
|
|
nb_rows: usize,
|
|
/// Weither or not the LCD has been initialized in the rigth state.
|
|
initialized: bool,
|
|
}
|
|
|
|
impl Lcd {
|
|
|
|
/// The instruction to turn off the display
|
|
const INSTRUCTION_DISPLAY_OFF: u8 = 0b0000_1000;
|
|
/// The instruction to clear the display
|
|
const INSTRUCTION_DISPLAY_CLEAR: u8 = 0b0000_0001;
|
|
|
|
/// The bit indicating a function set instruction
|
|
const FUNCTION_SET_INDICATOR: u8 = 0b0010_0000;
|
|
/// The bit indicating the use of only one line
|
|
const FUNCTION_SET_ONE_LINE_INDICATOR: u8 = 0b0000_0000;
|
|
/// The bit indicating the use of only two lines
|
|
const FUNCTION_SET_TWO_LINES_INDICATOR: u8 = 0b0000_1000;
|
|
/// The bit indicating the use of the 5x8 font
|
|
const FUNCTION_SET_5X8_DOTS: u8 = 0b0000_0000;
|
|
/// The bit indicating the use of the 5x10 font
|
|
const FUNCTION_SET_5X10_DOTS: u8 = 0b0000_0100;
|
|
|
|
/// Instanciate a [`Lcd`] object in 4 bits mode.
|
|
pub const fn new_4bits_mode(
|
|
rs_pin: usize,
|
|
rw_pin: Option<usize>,
|
|
e_pin: usize,
|
|
db4: usize,
|
|
db5: usize,
|
|
db6: usize,
|
|
db7: usize,
|
|
nb_lines: usize,
|
|
nb_rows: usize,
|
|
) -> Self {
|
|
Self {
|
|
rs_pin,
|
|
rw_pin,
|
|
e_pin,
|
|
first_data_bits_pins: None,
|
|
last_data_bits_pins: [db4, db5, db6, db7],
|
|
nb_lines,
|
|
nb_rows,
|
|
initialized: false,
|
|
}
|
|
}
|
|
|
|
/// Instanciate a [`Lcd`] object in 8 bits mode.
|
|
pub const fn new_8bits_mode(
|
|
rs_pin: usize,
|
|
rw_pin: Option<usize>,
|
|
e_pin: usize,
|
|
db0: usize,
|
|
db1: usize,
|
|
db2: usize,
|
|
db3: usize,
|
|
db4: usize,
|
|
db5: usize,
|
|
db6: usize,
|
|
db7: usize,
|
|
nb_lines: usize,
|
|
nb_rows: usize,
|
|
) -> Self {
|
|
Self {
|
|
rs_pin,
|
|
rw_pin,
|
|
e_pin,
|
|
first_data_bits_pins: Some([db0, db1, db2, db3]),
|
|
last_data_bits_pins: [db4, db5, db6, db7],
|
|
nb_lines,
|
|
nb_rows,
|
|
initialized: false,
|
|
}
|
|
}
|
|
|
|
/// Initialize the LCD.
|
|
pub fn init(&mut self, font_size: FontSize) -> Result<(), &'static str> {
|
|
gpio::set_pin_fonction(self.rs_pin, gpio::PinFunction::Output)?;
|
|
gpio::set_pin_output_state(self.rs_pin, gpio::PinOutputState::Low)?;
|
|
if let Some(rw_pin) = self.rw_pin {
|
|
gpio::set_pin_fonction(rw_pin, gpio::PinFunction::Output)?;
|
|
gpio::set_pin_output_state(rw_pin, gpio::PinOutputState::Low)?;
|
|
}
|
|
gpio::set_pin_fonction(self.e_pin, gpio::PinFunction::Output)?;
|
|
gpio::set_pin_output_state(self.e_pin, gpio::PinOutputState::Low)?;
|
|
if let Some(first_data_bits_pins) = self.first_data_bits_pins {
|
|
for pin in first_data_bits_pins.into_iter() {
|
|
gpio::set_pin_fonction(pin, gpio::PinFunction::Output)?;
|
|
gpio::set_pin_output_state(pin, gpio::PinOutputState::Low)?;
|
|
}
|
|
}
|
|
for pin in self.last_data_bits_pins.into_iter() {
|
|
gpio::set_pin_fonction(pin, gpio::PinFunction::Output)?;
|
|
gpio::set_pin_output_state(pin, gpio::PinOutputState::Low)?;
|
|
}
|
|
|
|
let db4 = self.last_data_bits_pins[0];
|
|
let db5 = self.last_data_bits_pins[1];
|
|
|
|
// The initialization process as described in figure 23/24 of the doc
|
|
// First, wait 15 ms after poweron. To be sure, wait 30 ms
|
|
time_manager().sleep(Duration::from_millis(30));
|
|
|
|
// Currently the state of the lcd is undefined. We must make sur
|
|
// to be in 8bits mode by sendy 0b0011_0000 3 times.
|
|
gpio::set_pin_output_state(db4, gpio::PinOutputState::High)?;
|
|
gpio::set_pin_output_state(db5, gpio::PinOutputState::High)?;
|
|
self.pulse()?;
|
|
time_manager().sleep(Duration::from_micros(4100));
|
|
self.pulse()?;
|
|
time_manager().sleep(Duration::from_micros(100));
|
|
self.pulse()?;
|
|
time_manager().sleep(Duration::from_micros(100));
|
|
gpio::set_pin_output_state(db4, gpio::PinOutputState::Low)?;
|
|
gpio::set_pin_output_state(db5, gpio::PinOutputState::Low)?;
|
|
|
|
// Now we are in 8bits mode, if we only have 4 data pins, we
|
|
// needs to set the mode to 4 bits.
|
|
if self.first_data_bits_pins.is_none() {
|
|
gpio::set_pin_output_state(db5, gpio::PinOutputState::High)?;
|
|
self.pulse()?;
|
|
}
|
|
|
|
let mut function = Self::FUNCTION_SET_INDICATOR;
|
|
if self.first_data_bits_pins.is_some() {
|
|
function |= 0b0001_0000;
|
|
}
|
|
|
|
if self.nb_lines == 1 {
|
|
function |= Self::FUNCTION_SET_ONE_LINE_INDICATOR;
|
|
} else if self.nb_lines == 2 {
|
|
function |= Self::FUNCTION_SET_TWO_LINES_INDICATOR;
|
|
} else {
|
|
return Err("Only 1 and 2 lines modes are supported");
|
|
}
|
|
|
|
function |= font_size.function_code();
|
|
self.send_data(function)?;
|
|
|
|
self.initialized = true;
|
|
|
|
self.display_off()?;
|
|
self.display_clear()?;
|
|
// TODO: self.set_entry_mode(stuff)?;
|
|
|
|
todo!();
|
|
}
|
|
|
|
/// Turn the display off.
|
|
pub fn display_off(&self) -> Result<(), &'static str> {
|
|
if self.initialized {
|
|
self.send_data(Self::INSTRUCTION_DISPLAY_OFF)
|
|
} else {
|
|
Err("Cannot control the LCD without initializing it first")
|
|
}
|
|
}
|
|
|
|
/// Clear the display
|
|
pub fn display_clear(&self) -> Result<(), &'static str> {
|
|
if self.initialized {
|
|
self.send_data(Self::INSTRUCTION_DISPLAY_CLEAR)
|
|
} else {
|
|
Err("Cannot control the LCD without initializing it first")
|
|
}
|
|
}
|
|
|
|
/// Tell the LCD that the data is ready to be read.
|
|
fn pulse(&self) -> Result<(), &'static str> {
|
|
gpio::set_pin_output_state(self.e_pin, gpio::PinOutputState::Low)?;
|
|
time_manager().sleep(Duration::from_nanos(500));
|
|
gpio::set_pin_output_state(self.e_pin, gpio::PinOutputState::High)?;
|
|
time_manager().sleep(Duration::from_nanos(500));
|
|
gpio::set_pin_output_state(self.e_pin, gpio::PinOutputState::Low)?;
|
|
time_manager().sleep(Duration::from_micros(50));
|
|
Ok(())
|
|
}
|
|
|
|
/// Send data to the lcd.
|
|
fn send_data(&self, data: u8) -> Result<(), &'static str> {
|
|
if let Some(first_data_bits_pins) = self.first_data_bits_pins {
|
|
for (i, pin) in first_data_bits_pins.iter().enumerate() {
|
|
let value = Self::get_bit_value(data, i);
|
|
gpio::set_pin_output_state(*pin, value)?;
|
|
}
|
|
for (i, pin) in self.last_data_bits_pins.iter().enumerate() {
|
|
let value = Self::get_bit_value(data, i+4);
|
|
gpio::set_pin_output_state(*pin, value)?;
|
|
}
|
|
self.pulse()
|
|
} else {
|
|
for (i, pin) in self.last_data_bits_pins.iter().enumerate() {
|
|
let value = Self::get_bit_value(data, i+4);
|
|
gpio::set_pin_output_state(*pin, value)?;
|
|
}
|
|
self.pulse()?;
|
|
for (i, pin) in self.last_data_bits_pins.iter().enumerate() {
|
|
let value = Self::get_bit_value(data, i+4);
|
|
gpio::set_pin_output_state(*pin, value)?;
|
|
}
|
|
self.pulse()
|
|
}
|
|
}
|
|
|
|
/// Return the value of the pin correponding to the bit `i` of `data`.
|
|
#[inline]
|
|
fn get_bit_value(data: u8, i: usize) -> gpio::PinOutputState {
|
|
if (data & (1 << i)) != 0 {
|
|
gpio::PinOutputState::High
|
|
} else {
|
|
gpio::PinOutputState::Low
|
|
}
|
|
}
|
|
}
|