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

//! 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
}
}
}