Skip to content

Conversation

@protowouter
Copy link

Hi,

I decided to add support for the v2 7.5" EPD, since I'm working on a project with that screen.

My goal was to stay as close to the API you had established for the 2.9" screen. Since this model has a separate power input I had to change things around a bit.

Please let me know if you are interested in incorporating these changes, I would be happy to make any changes if required.

Kind regards,

Wouter

@MorganR
Copy link
Owner

MorganR commented Aug 12, 2025

Hello there! Your changes are welcome, but I apologise, I have been working on some pretty major changes in the background.

Had I realised this was starting to pick up some traction, I would have made these changes more incrementally and given some warning. Sorry again!

Would you mind taking a look at the new interface structure to see if this will work for you? I have split the single Epd interface into several smaller interfaces that can be composed. I've also added support for 2-bit grayscale (at least in the way it's supported on the 2.9" v2 display).

Copy link
Owner

@MorganR MorganR left a comment

Choose a reason for hiding this comment

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

Just a couple of comments from a quick pass. I really appreciate the effort you put in to document and test the new behaviour! I've found the datasheets to be pretty opaque at times, and I know how much work it takes to fully decipher them.

Comment on lines 12 to 26
assign_resources::assign_resources! {
spi_hw: SpiP {
spi: SPI1,
clk: PIN_10,
tx: PIN_11,
dma_tx: DMA_CH3,
cs: PIN_9,
},
epd_hw: DisplayP {
reset: PIN_12,
dc: PIN_8,
busy: PIN_13,
power: PIN_14,
},
}
Copy link
Owner

Choose a reason for hiding this comment

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

This should be all you need after syncing with main, the rest is now shareable in lib.rs with proper support for different SPI devices.

Copy link
Author

Choose a reason for hiding this comment

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

Still needed to add some bits for the power pin but this setup does indeed not need separate hardware code for the devices.

Copy link
Owner

Choose a reason for hiding this comment

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

Good point. WDYT of splitting the EpdHw trait, like I did for the old Epd trait? We could have something like SpiHw, PowerHw, etc, that each driver can combine as needed. I have to work out how to make the error type easy to use still.

Comment on lines 259 to 273
// Draw white text over only the black part of the check.
buffer.clear(BinaryColor::Off).unwrap();
text.character_style.text_color = Some(BinaryColor::On);
text.draw(&mut buffer).unwrap();
let buffer_bounds = buffer.bounding_box();
let half_width = buffer_bounds.size.width / 2;
let top_middle = buffer_bounds.top_left + Point::new(half_width as i32, 0);
let right_half = Rectangle::new(top_middle, Size::new(half_width, buffer_bounds.size.height));
// Cover the right-side of the text.
buffer.fill_solid(&right_half, BinaryColor::Off).unwrap();
// Just display white pixels (i.e. same as before plus left side of text).
expect!(
epd.display_partial_buffer(&mut spi, &buffer, text.bounding_box())
.await,
"Failed to display white half of text"
Copy link
Owner

Choose a reason for hiding this comment

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

This test might not make sense if the display doesn't support the "bypass" option that the 2.9" display does. Don't feel beholden to the tests I ran in the other file.

Copy link
Author

Choose a reason for hiding this comment

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

Well spotted, forgot i left that part in there.

Comment on lines +196 to +199
// Full bytes start at next multiple of 8 from x_start (inclusive if x_start is multiple)
let x_full_bytes_start = min(((x_start + 7) / 8) * 8, x_end);
// Full bytes end at last multiple of 8 before x_end (inclusive if x_end is multiple)
let x_full_bytes_end = max(x_end / 8 * 8, x_start);
Copy link
Owner

Choose a reason for hiding this comment

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

Thanks for this fix!

Copy link
Author

Choose a reason for hiding this comment

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

No problem!

}

// On this display the busy pin is active low.
impl<HW: EpdHw> BusyWait for HW {
Copy link
Author

Choose a reason for hiding this comment

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

This display has the busy pin active low. I couldn't figure out a proper way to encode this information in the type system, maybe you have an idea? Since i couldn't find a proper way i decided to copy it and swap high for low.

Copy link
Owner

Choose a reason for hiding this comment

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

I have an idea, I'll get back to you

Copy link
Owner

Choose a reason for hiding this comment

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

What if the EpdHw trait has a busy_when() function returning either PinState::High or PinState::Low? That should keep things fairly tidy and avoid the duplication.

Copy link
Owner

Choose a reason for hiding this comment

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

Or, if I can work out a nice way to split EpdHw into smaller traits, we can have both an BusyWhenLow and BusyWhenHigh trait, and implement this differently for both of them. I think that would be preferable because it means the driver can be clear about what it expects

@protowouter
Copy link
Author

No worries, it wasn't too much effort the incorporate the changes. In the end the code became a bit cleaner cause you only have the implement the features that are actually supported by the hardware.

Could you take a look at the new changes?

Copy link
Owner

@MorganR MorganR left a comment

Choose a reason for hiding this comment

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

I still need to give the main driver file a proper read, but here are a few suggestions in the meantime. Thank you for your patience 🙏

&mut self,
spi: &mut SPI,
buf: &dyn BufferView<BITS, FRAMES>,
area: Rectangle,
Copy link
Owner

Choose a reason for hiding this comment

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

Nit: prefer pass by reference

// Display text with fast refresh mode
let mut epd = expect!(
epd.set_refresh_mode(&mut spi, RefreshMode::Fast).await,
"Failed to switch to fast refresh m"
Copy link
Owner

Choose a reason for hiding this comment

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

Nit: "mode"

.unwrap();
expect!(
epd.display_framebuffer(&mut spi, &buffer).await,
"Failed to display check buffer"
Copy link
Owner

Choose a reason for hiding this comment

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

Nit: wrong message?

Unwrap is probably fine here. I tried to at least use expect the first time a method is called, but I think most of them can be unwrap after that.

info!("Displaying text with partial refresh over black buffer");
// Clear first.
buffer
.fill_solid(&buffer.bounding_box(), BinaryColor::On)
Copy link
Owner

Choose a reason for hiding this comment

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

You can use .clear(BinaryColor::On) when you want to fill the whole buffer.

"Failed to initialize EPD"
);

for i in 0..5 {
Copy link
Owner

Choose a reason for hiding this comment

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

It might be nice to have another info! before this section to explain what is being tested

Comment on lines +284 to +290
info!("Sleeping EPD");
let epd = expect!(epd.sleep(&mut spi).await, "Failed to put EPD to sleep");
Timer::after_secs(2).await;

info!("Waking EPD");
let epd = expect!(epd.wake(&mut spi).await, "Failed to wake EPD");
Timer::after_secs(1).await;
Copy link
Owner

Choose a reason for hiding this comment

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

This might not be needed, since you already tested sleeping above

}

// On this display the busy pin is active low.
impl<HW: EpdHw> BusyWait for HW {
Copy link
Owner

Choose a reason for hiding this comment

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

What if the EpdHw trait has a busy_when() function returning either PinState::High or PinState::Low? That should keep things fairly tidy and avoid the duplication.

@MorganR
Copy link
Owner

MorganR commented Sep 7, 2025

@protowouter would merging this PR make it easier to handle the busy pin state, and make it easier to add the PowerOn/OffHw traits?

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