Closed ColinTimBarndt closed 4 days 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.
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.
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
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.
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.
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.
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.
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.
Sorry that this has become so huge, I'll keep my other PRs smaller
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:
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
(Eight|Four)BitBusPins
instead of 10 or 6 arguments. This also improves the destroy function signature and clippy no longer needs to be suppressed.destroy
function for all Buses and the driver itself allows recovering pins and buses.new
function on the driver that returns all pins on error for better error handling. This function generically takes any options struct (DisplayOptions(8Bit|4Bit|I2C)
, one for each bus type). All setup logic has been moved out oflib.rs
intosetup.rs
.Copy
unless pins/bus were configured. This allows copying a single configuration for multiple displays.DisplayMemoryMap
trait was added to abstract the way display memory works internally. It maps 2D coordinates to contiguous slices in the display memory (start address + columns/length). It also allows addressing off-screen characters (scrollable area) where this library would have error'ed previously.Contiguous1RMemoryMap
).get_position
function in favor ofDisplayMemoryMap
.Charset
trait was added to abstract the internal character set that a display may support. It maps unicode characters to optional bytes to be sent to the display. TheCharsetWithFallback
trait andFallback
struct implementing it always return a byte. There areCharsetUniversal
which covers the ASCII character subset that is supported by most/all displays,CharsetA00
andCharsetA02
.set_cursor_pos
implementations were different, I chose the one that non_blocking used as there is no coordinate mangling involved.set_cursor_xy
was not implemented for non blocking..await
in documentation examples.