1#![no_std]
57#![allow(dead_code, non_camel_case_types, non_upper_case_globals)]
58use embedded_hal::{
59 blocking::delay::{DelayMs, DelayUs},
60 blocking::i2c::{Write, WriteRead},
61};
62use mcp230xx::{Direction, Level, Mcp23008, Mcp230xx, Register};
63
64const RS_PIN: Mcp23008 = Mcp23008::P1;
65const ENABLE_PIN: Mcp23008 = Mcp23008::P2;
66const DATA_D4_PIN: Mcp23008 = Mcp23008::P3;
67const DATA_D5_PIN: Mcp23008 = Mcp23008::P4;
68const DATA_D6_PIN: Mcp23008 = Mcp23008::P5;
69const DATA_D7_PIN: Mcp23008 = Mcp23008::P6;
70const BACKLIGHT_PIN: Mcp23008 = Mcp23008::P7;
71
72const DATA_PINS: [Mcp23008; 4] = [DATA_D4_PIN, DATA_D5_PIN, DATA_D6_PIN, DATA_D7_PIN];
74
75const LCD_CMD_CLEARDISPLAY: u8 = 0x01; const LCD_CMD_RETURNHOME: u8 = 0x02; const LCD_CMD_ENTRYMODESET: u8 = 0x04; const LCD_CMD_DISPLAYCONTROL: u8 = 0x08; const LCD_CMD_CURSORSHIFT: u8 = 0x10; const LCD_CMD_FUNCTIONSET: u8 = 0x20; const LCD_CMD_SETCGRAMADDR: u8 = 0x40; const LCD_CMD_SETDDRAMADDR: u8 = 0x80; const LCD_FLAG_ENTRYRIGHT: u8 = 0x00; const LCD_FLAG_ENTRYLEFT: u8 = 0x02; const LCD_FLAG_ENTRYSHIFTINCREMENT: u8 = 0x01; const LCD_FLAG_ENTRYSHIFTDECREMENT: u8 = 0x00; const LCD_FLAG_DISPLAYON: u8 = 0x04; const LCD_FLAG_DISPLAYOFF: u8 = 0x00; const LCD_FLAG_CURSORON: u8 = 0x02; const LCD_FLAG_CURSOROFF: u8 = 0x00; const LCD_FLAG_BLINKON: u8 = 0x01; const LCD_FLAG_BLINKOFF: u8 = 0x00; const LCD_FLAG_DISPLAYMOVE: u8 = 0x08; const LCD_FLAG_CURSORMOVE: u8 = 0x00; const LCD_FLAG_MOVERIGHT: u8 = 0x04; const LCD_FLAG_MOVELEFT: u8 = 0x00; const LCD_FLAG_8BITMODE: u8 = 0x10; const LCD_FLAG_4BITMODE: u8 = 0x00; const LCD_FLAG_2LINE: u8 = 0x08; const LCD_FLAG_1LINE: u8 = 0x00; const LCD_FLAG_5x10_DOTS: u8 = 0x04; const LCD_FLAG_5x8_DOTS: u8 = 0x00; pub enum LcdDisplayType {
115 Lcd20x4,
117 Lcd20x2,
119 Lcd16x2,
121}
122
123impl LcdDisplayType {
124 const fn rows(&self) -> u8 {
126 match self {
127 LcdDisplayType::Lcd20x4 => 4,
128 LcdDisplayType::Lcd20x2 => 2,
129 LcdDisplayType::Lcd16x2 => 2,
130 }
131 }
132
133 const fn cols(&self) -> u8 {
135 match self {
136 LcdDisplayType::Lcd20x4 => 20,
137 LcdDisplayType::Lcd20x2 => 20,
138 LcdDisplayType::Lcd16x2 => 16,
139 }
140 }
141
142 const fn row_offsets(&self) -> [u8; 4] {
145 match self {
146 LcdDisplayType::Lcd20x4 => [0x00, 0x40, 0x14, 0x54],
147 LcdDisplayType::Lcd20x2 => [0x00, 0x40, 0x00, 0x40],
148 LcdDisplayType::Lcd16x2 => [0x00, 0x40, 0x10, 0x50],
149 }
150 }
151}
152
153pub struct LcdBackpack<I2C, D> {
154 register: Mcp230xx<I2C, Mcp23008>,
155 delay: D,
156 lcd_type: LcdDisplayType,
157 display_function: u8,
158 display_control: u8,
159 display_mode: u8,
160}
161
162pub enum Error<I2C_ERR> {
164 I2cError(I2C_ERR),
166 InterruptPinError,
168 RowOutOfRange,
170 ColumnOutOfRange,
172 #[cfg(feature = "defmt")]
174 FormattingError,
175}
176
177impl<I2C_ERR> From<I2C_ERR> for Error<I2C_ERR> {
178 fn from(err: I2C_ERR) -> Self {
179 Error::I2cError(err)
180 }
181}
182
183impl<I2C_ERR> From<mcp230xx::Error<I2C_ERR>> for Error<I2C_ERR> {
184 fn from(err: mcp230xx::Error<I2C_ERR>) -> Self {
185 match err {
186 mcp230xx::Error::BusError(e) => Error::I2cError(e),
187 mcp230xx::Error::InterruptPinError => Error::InterruptPinError,
188 }
189 }
190}
191
192#[cfg(feature = "defmt")]
193impl<I2C_ERR> defmt::Format for Error<I2C_ERR>
194where
195 I2C_ERR: defmt::Format,
196{
197 fn format(&self, fmt: defmt::Formatter) {
198 match self {
199 Error::I2cError(e) => defmt::write!(fmt, "I2C error: {:?}", e),
200 Error::InterruptPinError => defmt::write!(fmt, "Interrupt pin not found"),
201 Error::RowOutOfRange => defmt::write!(fmt, "Row out of range"),
202 Error::ColumnOutOfRange => defmt::write!(fmt, "Column out of range"),
203 Error::FormattingError => defmt::write!(fmt, "Formatting error"),
204 }
205 }
206}
207
208impl<I2C, I2C_ERR, D> LcdBackpack<I2C, D>
209where
210 I2C: Write<Error = I2C_ERR> + WriteRead<Error = I2C_ERR>,
211 D: DelayMs<u16> + DelayUs<u16>,
212{
213 pub fn new(lcd_type: LcdDisplayType, i2c: I2C, delay: D) -> Self {
215 Self::new_with_address(lcd_type, i2c, delay, 0x20)
216 }
217
218 pub fn new_with_address(lcd_type: LcdDisplayType, i2c: I2C, delay: D, address: u8) -> Self {
220 let register = match Mcp230xx::<I2C, Mcp23008>::new(i2c, address) {
221 Ok(r) => r,
222 Err(_) => panic!("Could not create MCP23008"),
223 };
224
225 Self {
226 register,
227 delay,
228 lcd_type,
229 display_function: LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE,
230 display_control: LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF,
231 display_mode: LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT,
232 }
233 }
234
235 pub fn delay(&mut self) -> &mut D {
237 &mut self.delay
238 }
239
240 pub fn init(&mut self) -> Result<&mut Self, Error<I2C_ERR>> {
242 self.register
244 .set_direction(BACKLIGHT_PIN, Direction::Output)?;
245 self.register.set_gpio(BACKLIGHT_PIN, Level::High)?;
246
247 for pin in DATA_PINS.iter() {
249 self.register.set_direction(*pin, Direction::Output)?;
250 }
251
252 self.register.set_direction(RS_PIN, Direction::Output)?;
254 self.register.set_direction(ENABLE_PIN, Direction::Output)?;
255
256 self.delay().delay_ms(50);
258
259 self.register.set_gpio(RS_PIN, Level::Low)?;
261 self.register.set_gpio(ENABLE_PIN, Level::Low)?;
262
263 self.write_4_bits(0x03)?;
265 self.delay().delay_ms(5);
266 self.write_4_bits(0x03)?;
267 self.delay().delay_ms(5);
268 self.write_4_bits(0x03)?;
269 self.delay().delay_us(150);
270 self.write_4_bits(0x02)?;
271
272 self.send_command(LCD_CMD_FUNCTIONSET | self.display_function)?;
274 self.send_command(LCD_CMD_DISPLAYCONTROL | self.display_control)?;
275 self.send_command(LCD_CMD_ENTRYMODESET | self.display_mode)?;
276 self.clear()?;
277 self.home()?;
278
279 Ok(self)
280 }
281
282 pub fn clear(&mut self) -> Result<&mut Self, Error<I2C_ERR>> {
288 self.send_command(LCD_CMD_CLEARDISPLAY)?;
289 self.delay().delay_ms(2);
290 Ok(self)
291 }
292
293 pub fn home(&mut self) -> Result<&mut Self, Error<I2C_ERR>> {
295 self.send_command(LCD_CMD_RETURNHOME)?;
296 self.delay().delay_ms(2);
297 Ok(self)
298 }
299
300 pub fn set_cursor(&mut self, col: u8, row: u8) -> Result<&mut Self, Error<I2C_ERR>> {
302 if row >= self.lcd_type.rows() {
303 return Err(Error::RowOutOfRange);
304 }
305 if col >= self.lcd_type.cols() {
306 return Err(Error::ColumnOutOfRange);
307 }
308
309 self.send_command(
310 LCD_CMD_SETDDRAMADDR | (col + self.lcd_type.row_offsets()[row as usize]),
311 )?;
312 Ok(self)
313 }
314
315 pub fn show_cursor(&mut self, show_cursor: bool) -> Result<&mut Self, Error<I2C_ERR>> {
317 if show_cursor {
318 self.display_control |= LCD_FLAG_CURSORON;
319 } else {
320 self.display_control &= !LCD_FLAG_CURSORON;
321 }
322 self.send_command(LCD_CMD_DISPLAYCONTROL | self.display_control)?;
323 Ok(self)
324 }
325
326 pub fn blink_cursor(&mut self, blink_cursor: bool) -> Result<&mut Self, Error<I2C_ERR>> {
328 if blink_cursor {
329 self.display_control |= LCD_FLAG_BLINKON;
330 } else {
331 self.display_control &= !LCD_FLAG_BLINKON;
332 }
333 self.send_command(LCD_CMD_DISPLAYCONTROL | self.display_control)?;
334 Ok(self)
335 }
336
337 pub fn show_display(&mut self, show_display: bool) -> Result<&mut Self, Error<I2C_ERR>> {
339 if show_display {
340 self.display_control |= LCD_FLAG_DISPLAYON;
341 } else {
342 self.display_control &= !LCD_FLAG_DISPLAYON;
343 }
344 self.send_command(LCD_CMD_DISPLAYCONTROL | self.display_control)?;
345 Ok(self)
346 }
347
348 pub fn scroll_display_left(&mut self) -> Result<&mut Self, Error<I2C_ERR>> {
350 self.send_command(LCD_CMD_CURSORSHIFT | LCD_FLAG_DISPLAYMOVE | LCD_FLAG_MOVELEFT)?;
351 Ok(self)
352 }
353
354 pub fn scroll_display_right(&mut self) -> Result<&mut Self, Error<I2C_ERR>> {
356 self.send_command(LCD_CMD_CURSORSHIFT | LCD_FLAG_DISPLAYMOVE | LCD_FLAG_MOVERIGHT)?;
357 Ok(self)
358 }
359
360 pub fn left_to_right(&mut self) -> Result<&mut Self, Error<I2C_ERR>> {
362 self.display_mode |= LCD_FLAG_ENTRYLEFT;
363 self.send_command(LCD_CMD_ENTRYMODESET | self.display_mode)?;
364 Ok(self)
365 }
366
367 pub fn right_to_left(&mut self) -> Result<&mut Self, Error<I2C_ERR>> {
369 self.display_mode &= !LCD_FLAG_ENTRYLEFT;
370 self.send_command(LCD_CMD_ENTRYMODESET | self.display_mode)?;
371 Ok(self)
372 }
373
374 pub fn autoscroll(&mut self, autoscroll: bool) -> Result<&mut Self, Error<I2C_ERR>> {
376 if autoscroll {
377 self.display_mode |= LCD_FLAG_ENTRYSHIFTINCREMENT;
378 } else {
379 self.display_mode &= !LCD_FLAG_ENTRYSHIFTINCREMENT;
380 }
381 self.send_command(LCD_CMD_ENTRYMODESET | self.display_mode)?;
382 Ok(self)
383 }
384
385 pub fn create_char(
387 &mut self,
388 location: u8,
389 charmap: [u8; 8],
390 ) -> Result<&mut Self, Error<I2C_ERR>> {
391 self.send_command(LCD_CMD_SETCGRAMADDR | ((location & 0x7) << 3))?;
392 for &charmap_byte in charmap.iter() {
393 self.write_data(charmap_byte)?;
394 }
395 Ok(self)
396 }
397
398 pub fn print(&mut self, text: &str) -> Result<&mut Self, Error<I2C_ERR>> {
400 for c in text.chars() {
401 self.write_data(c as u8)?;
402 }
403 Ok(self)
404 }
405
406 fn write_4_bits(&mut self, value: u8) -> Result<(), Error<I2C_ERR>> {
412 let mut register_contents = self.register.read(Register::GPIO.into())?;
414
415 for (index, pin) in DATA_PINS.iter().enumerate() {
417 let bit_mask = 1 << (*pin as u8);
418 register_contents &= !bit_mask;
419 if value & (1 << index) != 0 {
420 register_contents |= bit_mask;
421 }
422 }
423
424 register_contents &= !(1 << (ENABLE_PIN as u8));
426
427 self.register
429 .write(Register::GPIO.into(), register_contents)?;
430
431 self.delay().delay_us(1);
433 register_contents |= 1 << (ENABLE_PIN as u8); self.register
435 .write(Register::GPIO.into(), register_contents)?;
436 self.delay().delay_us(1);
437 register_contents &= !(1 << (ENABLE_PIN as u8)); self.register
439 .write(Register::GPIO.into(), register_contents)?;
440 self.delay().delay_us(100);
441
442 Ok(())
443 }
444
445 fn write_8_bits(&mut self, value: u8) -> Result<(), Error<I2C_ERR>> {
447 self.write_4_bits(value >> 4)?;
448 self.write_4_bits(value & 0x0F)?;
449 Ok(())
450 }
451
452 pub fn send_command(&mut self, command: u8) -> Result<(), Error<I2C_ERR>> {
454 self.register.set_gpio(RS_PIN, Level::Low)?;
455 self.write_8_bits(command)?;
456 Ok(())
457 }
458
459 pub fn write_data(&mut self, value: u8) -> Result<(), Error<I2C_ERR>> {
461 self.register.set_gpio(RS_PIN, Level::High)?;
462 self.write_8_bits(value)?;
463 Ok(())
464 }
465
466 fn pulse_enable(&mut self) -> Result<(), Error<I2C_ERR>> {
468 self.register.set_gpio(ENABLE_PIN, Level::Low)?;
469 self.delay().delay_us(1);
470 self.register.set_gpio(ENABLE_PIN, Level::High)?;
471 self.delay().delay_us(1);
472 self.register.set_gpio(ENABLE_PIN, Level::Low)?;
473 self.delay().delay_us(100);
474
475 Ok(())
476 }
477}
478
479impl<I2C, I2C_ERR, D> core::fmt::Write for LcdBackpack<I2C, D>
481where
482 I2C: Write<Error = I2C_ERR> + WriteRead<Error = I2C_ERR>,
483 D: DelayMs<u16> + DelayUs<u16>,
484{
485 fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
486 if let Err(_error) = self.print(s) {
487 return Err(core::fmt::Error);
488 }
489 Ok(())
490 }
491}