JohnDoneth / hd44780-driver

Implementation of the embedded-hal traits for the HD44780.
MIT License
37 stars 40 forks source link

Generalized interface for different display models #54

Closed ColinTimBarndt closed 4 days ago

ColinTimBarndt commented 2 weeks ago

This is only a draft for now, because this is not finished. Though, I would like to get some feedback on what I'm trying to do here as these are some major changes (though mostly API compatible or very similar).

The changes I've made are moving the initialization logic into a separate module and incorporates a building pattern (example below). I've also added the concept of Charsets, because different displays support different characters/glyphs and this would also allow users to define their own (e.g. for custom characters/glyphs, which could be added latter. rel. #8) or if they have a display with a completely different Charset. It's also possible to detect whether a character is supported or not. I've added the Fallback<C: Charset, const FB: u8> struct to easily specify which character to use instead, defaulting to space.

Both the Charset and the DisplayMemoryMap are encoded as a generic parameter that are automatically inferred through the builder. The abstracted DisplayMemoryMap is respecting the scrollable margin of lines, allowing to set the cursor there. It's also possible to get the columns that one specific row has including the scrollable margin. This is for example important for 16x4 displays, as only the last 2 rows can scroll there.

Example

My Display is using the A00 Charset, which is some ASCII combined with Japanese Katakana. Currently, this library is just converting a string from UTF8 into raw bytes and sends that to the LCD. The LCD, however, doesn't speak UTF8, it only understands its own Charset. The added Charset trait is used for converting UTF8 into whatever the LCD speaks.

This allows me to render Japanese UTF8 strings directly onto the display:

photo_2024-09-16_01-22-43

ESP32 Code ```rs #![no_std] #![no_main] use esp_backtrace as _; use esp_hal::{ clock::ClockControl, delay::Delay, gpio::Io, i2c::I2C, peripherals::Peripherals, prelude::*, system::SystemControl, }; use hd44780_driver::{ charset::CharsetA00, memory_map::MemoryMap1602, setup::DisplayOptionsI2C, Cursor, CursorBlink, Display, DisplayMode, HD44780, }; use log::{error, info}; #[entry] fn main() -> ! { esp_println::logger::init_logger(log::LevelFilter::Debug); let peripherals = Peripherals::take(); let system = SystemControl::new(peripherals.SYSTEM); let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); let mut delay = Delay::new(&clocks); info!("Start"); let i2c = I2C::new( peripherals.I2C0, io.pins.gpio21, io.pins.gpio22, 100.kHz(), &clocks, ); let mut options = DisplayOptionsI2C::new(MemoryMap1602::new()) .with_i2c_bus(i2c, 0x27) .with_charset(CharsetA00::QUESTION_FALLBACK); let mut display = loop { match HD44780::new(options, &mut delay) { Err((options_back, error)) => { error!("Error creating LCD Driver: {error}"); options = options_back; delay.delay_millis(500); // try again } Ok(display) => break display, } }; display .set_display_mode( DisplayMode { display: Display::On, cursor_visibility: Cursor::Invisible, cursor_blink: CursorBlink::Off, }, &mut delay, ) .unwrap(); display.clear(&mut delay).unwrap(); display.reset(&mut delay).unwrap(); display.write_str("Hello, world! €", &mut delay).unwrap(); // € is unsupported and displayed as ? display.set_cursor_xy((6, 1), &mut delay).unwrap(); display .write_str("ハロー、ワールト゛!", &mut delay) .unwrap(); loop { delay.delay_millis(1000); //display.shift_display(Direction::Left, &mut delay).unwrap(); } } ```

Hardware Testing

So far, I only have my 16x2 display, but I have ordered a few different models to test on.

Changes

ColinTimBarndt commented 2 weeks ago

I'm also getting a HD44780 matrix display, I'm curious how that one works and if I can generalize the API further to get it working.

ColinTimBarndt commented 2 weeks ago

I think it's better if incorporating the matrix LCD display is a separate PR (and has lower priority), so this PR would be fully implemented now.

ColinTimBarndt commented 2 weeks ago

I will need some help with updating the other examples, as I can't get them to attempt building, nor do I have the hardware

JohnDoneth commented 1 week ago

I will need some help with updating the other examples, as I can't get them to attempt building, nor do I have the hardware

Yeah, I'm not sure how to handle that since I don't have a bunch of HD44780s setup anymore either. It might make sense to remove the examples for now and open issues to re-add them from those who have the hardware setup so we can get your work here merged.

ColinTimBarndt commented 1 week ago

I've ordered a few Arduino and Pi Pico boards now and can add examples for these and ESP32 on the weekend. I think that should cover the most common MCUs. Apart from that, the code is gonna be very similar for others.

It's probably fine if we wait until I added the examples before merging, there's no need to rush things.

ColinTimBarndt commented 5 days ago

I tried getting the driver to work on an RP2040 and suspect the corruption has something to do with the timings, but I'm not sure. The same setup works with an ESP32. Here, the data sent to the display is getting corrupted.

IMG_20240925_190742.jpg

ColinTimBarndt commented 5 days ago

In the end, it was just a bad connection. It is interesting to note that ESP32 did not have this issue and that the RP2040 HAL did not give me an error if the I2C connection failed.

ColinTimBarndt commented 4 days ago

I've added all examples that I planned on adding for this PR. I only added 4-bit and 8-bit examples for ESP32, because I noticed that I would have needed a logic level converter to avoid sinking 5V into 3.3V logic pins. This could have damaged my MCUs. For I2C, I was able to modify the backpacks by desoldering the builtin pullup resistors and instead pulled the I2C lines only to 3.3V, which worked. I could add the remaining examples to the other MCUs once I have logic level converters. All examples that I added were tested on hardware.

@JohnDoneth I feel like this PR is ready for review now. I've also added basic (for now) implementations for defmt and ufmt because each MCU has its own ecosystem using different printing libraries. It's enough to print our Error type at the moment, but I intend to open a separate PR for full support.

ColinTimBarndt commented 4 days ago

Sorry that this has become so huge, I'll keep my other PRs smaller