Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalized interface for different display models #54

Merged

Conversation

ColinTimBarndt
Copy link
Contributor

@ColinTimBarndt ColinTimBarndt commented Sep 15, 2024

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
#![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

  • Pins are passed via (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.
  • Unified 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 of lib.rs into setup.rs.
    • If setting up the display fails, the configuration is passed back in a tuple with the error. This allows recovering the pins in case of an error or retrying.
  • XDisplayOptions structs are using a builder pattern and most options are generically stored (mostly ZST).
  • XDisplayOptions structs are 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.
    • Support for Nx1 displays using a single line of memory (Contiguous1RMemoryMap).
    • Removed get_position function in favor of DisplayMemoryMap.
  • 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. The CharsetWithFallback trait and Fallback struct implementing it always return a byte. There are CharsetUniversal which covers the ASCII character subset that is supported by most/all displays, CharsetA00 and CharsetA02.
    • Writing strings and chars now converts to the internal charset instead of sending UTF8 to the LCD.
    • It is possible to implement your own charset. This could be useful once defining custom characters is supported.
  • Parity between blocking and non blocking implementations
    • 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.
    • Documentation for non blocking did not include .await in documentation examples.
  • Both blocking and non blocking implementations are now using the same bus structs.

@ColinTimBarndt
Copy link
Contributor Author

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 ColinTimBarndt mentioned this pull request Sep 16, 2024
@ColinTimBarndt
Copy link
Contributor Author

ColinTimBarndt commented Sep 17, 2024

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 ColinTimBarndt marked this pull request as ready for review September 17, 2024 02:09
@ColinTimBarndt
Copy link
Contributor Author

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

This was referenced Sep 17, 2024
@JohnDoneth
Copy link
Owner

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
Copy link
Contributor Author

ColinTimBarndt commented Sep 17, 2024

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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

Copy link
Owner

@JohnDoneth JohnDoneth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the changes look really good. Excellent work

Cargo.toml Show resolved Hide resolved
@JohnDoneth JohnDoneth merged commit 99152cc into JohnDoneth:master Sep 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants