-
Notifications
You must be signed in to change notification settings - Fork 191
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
Add LCD_CAM I8080 Driver #1086
Add LCD_CAM I8080 Driver #1086
Conversation
I think a good example would be to flip color of full screen and log average fps. I reached 28 fps in such a test which is a huge improvement over what I could achieve without it which was around 10 fps. |
I integrated this into my app (did it a bit hacky at this time) and it's working great! From around 8 fps I'm now on 32 fps which includes app rendering. So many thanks for this! Here are a few comments:
I can contribute my changes mentioned above, just don't think it's possible for me to add to the PR? Anyway, both changes I've made are really simple (but important). Looking to see this quickly merged. |
I was actually considering this today. Though a lot of the stuff is private anyway so I don't think it would make a difference to users really. |
This is what the config looks like in my esp-idf project. pub struct I8080Config {
pub clock_config: ClockConfig,
pub format: Format,
/// Setup cycles expected = this value + 1. (6 bits)
pub setup_cycles: usize,
/// Hold cycles expected = this value + 1. (13 bits)
pub hold_cycles: usize,
/// The output LCD_CD is delayed by module clock LCD_CLK.
pub cd_mode: DelayMode,
/// The output data bits are delayed by module clock LCD_CLK.
pub output_bit_mode: DelayMode,
}
pub struct ClockConfig {
/// Select LCD module source clock.
/// 0: clock source is disabled.
/// 1: XTAL_CLK.
/// 2: PLL_D2_CLK.
/// 3: PLL_F160M_CLK.
pub clk_sel: usize,
/// Integral LCD clock divider value. (8 bits)
pub clkm_div_num: usize,
/// Fractional clock divider numerator value. (6 bits)
pub clkm_div_b: usize,
/// Fractional clock divider denominator value. (6 bits)
pub clkm_div_a: usize,
/// LCD_PCLK = LCD_CLK / this (6 bits)
pub pclk_divider: Option<usize>,
}
#[derive(Default)]
pub struct Format {
pub invert_every_two_bytes: bool,
pub invert_bit_order: bool,
pub invert_byte_order: bool,
pub enable_2byte_mode: bool,
}
#[repr(u8)]
#[derive(Default, Copy, Clone, Eq, PartialEq)]
pub enum DelayMode {
/// Output without delay.
#[default]
None = 0,
/// Delayed by the rising edge of LCD_CLK.
RaisingEdge = 1,
/// Delayed by the falling edge of LCD_CLK.
FallingEdge = 2,
} |
BTW: Doesn't Comments on the config:
The config looks good to me but I don't know what the conventions for |
Yes Thanks for the comments, I'll apply them where sensible. I won't be using the config struct as is, it's just a reference.
16 bit buses may want to send 8 bit data sometimes. Practically every command that isn't about drawing is 8 bits wide. |
@yanshay You should be able to use this without having to modify it now. Both byte order config and background transfers are available. Let me know if it works for you. |
I managed to get this working! It wasn't as trivial though as it was before, took me some time to figure out how to get this work and why it works that way. Probably worth explaining the idea in comments. I understand that you introduced the Several things I have questions about:
Is that the best approach? |
Another point after some more testing - this version is a bit slower than previous one, when I switch to this version the rendering+display takes about 5% more time. In total I test on 6720 short dma transfers and the added time is about 20-25 microsec. Because of the way my application renders and it is in parallel to the display it could be that only in some cases the slowdown is affecting time measurement, so could be the difference of the pure display part is more. Causes I can think of are:
BTW: Do you know what's the max freq that can be used with WT32-SC01-Plus? |
It that but more importantly it's a defense against leakapocalypse. We need to make sure the user doesn't free the buffer whilst the DMA transfer is happening.
The buffer doesn't need to be mut, it's just convenient for the caller to do so when ownership is being passed around.
This is something I want to allow, and there's precedent for it in esp-hal. You'll see it called "circlular" DMA, which let's you stream data to the peripheral. I've got a TODO in the PR description.
I'm not sure to be honest, need some proper thought. I don't think the embedded Rust community has an answer for this yet.
I could buy this as a reason. Are you calling
http://www.lcdwiki.com/res/MRB3951/ST7796S-Sitronix.pdf Looks like 15kHz. (66ns per write) |
That makes sense, didn't think of it, even though with display applications it sounds like more common for fixed buffer. Still most likely in Rust not u8 but rather some other type (like Rgb565), and potentially render only a slice of a buffer, so caller will need workaround this passing ownership. At least I couldn't find a way not to, but I still have what to learn in Rust.
Only calling
66ns -> 15 Mhz. |
I think 15 MHz is just the tested frequency, higher may work but it's out of spec. |
@bjoernQ mind if I could get a review? (You seem to be most up to date on the problems I'm trying to solve here) I'm particularly interested in what you think about how I handled the peripheral split and the split of the GDMA channel. I'm also not sure how to support 8 bit vs 16 bit buses. 8 bit buses can send one byte at a time but 16 bit buses can either send one byte or two bytes at a time. Are there any other driver that solve this same problem? Last and least important, any thoughts on how to expose the config to users? |
Thanks for working on this! The code looks quite sane to me. I think the PARL_IO peripheral on C6/H2 is closest to LCD_CAM. The peripheral split is following the same basic idea but PARL_IO currently takes the full DMA channel - but the approach here looks good to me. In PARL_IO the configuration of the data pins is even a bit more flexible - there we have ways to configure 1,2,4,8 and (only for C6 and only if RX is not used) 16 data lines. (in the TX example this is used here:
Probably something similar to that could be used here. From a brief look at the TRM there are basically two configuration "options": RGB and i8080? Since with the current approach a user would already decide on the mode by using the constructor of e.g. i8080 the user might just pass a config-struct into the constructor. Probably the user would need to set all of them always so we probably won't really need to use a builder for that. We could provide common defaults (if they exist) as |
This isn't 100% done (I want to support larger transactions) but I'm ready for a proper review now. |
One thing I unfortunately forgot about before: esp-hal/esp-hal-common/src/dma/gdma.rs Lines 621 to 635 in f542eb4
While on the S3 every channel can be used for every peripheral that is not the case for ESP32 and S2 - so in this case we wouldn't really need it currently but we should add the Another thing we want is module level documentation like we have for other peripherals (but you already mentioned you are not 100% done) There are a few TODOs left and some commented-out code like Usually register modifications look more like this in other drivers lcd_cam.lcd_user().modify(|_, w| {
w.lcd_8bits_order().bit(false).
lcd_bit_order().bit(false).
lcd_byte_order().bit(false).
lcd_2byte_en().bit(is_2byte_mode).
// Be able to send data out in LCD sequence when LCD starts.
lcd_dout().set_bit().
// Data length in fixed mode. (13 bits)
lcd_dout_cyclelen().variant(0).
// Disable continuous output.
lcd_always_out_en().clear_bit()
}); Overall, this looks quite good to me already. Maybe @jessebraham or @MabezDev have additonal comments? One last thing: I am unfortunately not able to really test this since I don't have a compatible display but I think @yanshay already tested this successfully. Would be good to have it tested when the code is final |
Sure, once it's final let me know and I'll test what I can. Just note that some (not all) of what I can test requires an application I'm working on, and I compile it by cherry picking lcd_cam over 0.14.1 since I can't get main to compile with my app due to various dependency issues conflicts with other libs (embassy related mostly). |
Changed it to use |
I think I've address all your comments now bjoernQ. |
esp32s3-hal/examples/lcd_i8080.rs
Outdated
@@ -0,0 +1,232 @@ | |||
//! Drives a display | |||
//! | |||
//! This assumes that pins are...... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems this comment was forgotten to adjust
I only have that one last comment regarding the example comment - otherwise looks great. Once we have the comment adjusted, I'm fine with this Maybe @jessebraham or @MabezDev have something to add? If not, we can get this in Would be great if @yanshay could double check that the higher frequency work fine again Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM - Thanks for all the effort!
Will merge it after lunch if no one else comes up with a good reason to not merge it 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I had been meaning to do a review of this for a lil while. Took some time now, it all looks good to me! Very clean implementation overall. It would be nice after a mipidsi display driver is added to come back to this example and update it.
I retested at high frequency and I still see some issues at high frequency. Much less but still there. I played with the code a bit and found that if I only enable interrupts in the Maybe a solution is to expose some option to use the standard interrupts, I assume that would be useful to have anyway in some applications. For example, for proper async support, to wake up when dma completes. Thoughts? |
The glitches you're seeing are likely due to screen tearing, which tends to happen with these I8080 displays that have a frame buffer. Coordinating the drawing with the TE (tear enable) pin helps but I've never tried it. |
I'm not sure I understand. What is that 'TE' pin you refer to? With the interrupt enabled it works perfectly, without there are glitches. It's very consistent behavior. Also note that I'm not rendering and sending full screen to the display but rather render and send line by line. So I don't think what I'm familiar with as screen tearing the issue. (for example, sometimes I see wrong colors on correct layout, pretty strange). Also, when it's a single line it could stay there on the screen, meaning wrong data reached the frame buffer. |
I'm curious what's going on with the hardware at this point, that's very strange. |
@bjoernQ any ideas what's causing this behavior? |
to be honest it really doesn't make any sense to me. |
@Dominaezzz I tried to reproduce the issue with a simple program so I started with your example and found something really strange. Normaly your example fill the display blue/red. When I build it in release (so Can you check if the same happens with your device? The only thing I could think of to cause this is too fast commands to the display, so I added delays in the example between the initialization send commands, and reduced the freqyency to 5Mjz, but it didn't help. |
Yeah it's black and white for me as well. (sigh) I wonder what could be going wrong here. |
I think it's something to do with the initialization phase, because in my app this doesn't happen. I'm initializing the display before switching to i8080 and from that perspective it works fine. The example code initializes the display also with the i8080 driver. But even when I added delays, also inside the driver code where command data is being sent, it didn't change anything. Are there limitations maybe on how fast internal esp32 registers can be written to? Release is so much faster than non release, paractically graphics application seem to be unusable in non release mode. |
I've figured it out. Worst part is, I was aware of the hardware issue before I made the PR but forgot after I pushed the POC. The LCD_CAM peripheral is impatient. It sends out data even when it's FIFO is empty, which means it ends up sending out garbage when the FIFO is empty. Due to this fact, even though I start the DMA transfer before starting the peripheral like so.... self.start_write_bytes_dma(ptr as _, len * size_of::<P::Word>())?;
self.start_send(); ...the DMA doesn't have enough time to fill up the peripheral's FIFO before it wants to send data, which means it sends out a little bit of garbage before sending out the actual data. The overhead of debug mode was sufficient to provide this delay but release mode means there's not enough of a delay. This would explain why the initialization is broken and why you occasionally get some random white lines when you draw. |
Or maybe not. It's now hit or miss with a delay between the DMA start and the LCD_CAM start. |
Interesting, do you know how they handle that in the non-rust esp-idf? Maybe they have the delay in their code there? Is there a way of checking on the FIFO when it's full enough and not relying on a delay? |
esp-idf uses I'm not aware of a way to check the FIFO, maybe the maintainers have a clue. |
There doesn't seem to be a way to inspect the FIFO, at least there is nothing I can see in the documentation we have. I think we should just go ahead and use |
Is it Anyway, I tried adding a delay using this function and in some other way between |
This is a driver for the I8080 mode of the LCD part of the LCD_CAM peripheral on the ESP32S3 (and eventually ESP32P4).
This was tested on a WT32-SC01 Plus.
TODO:
- [ ] Add support for continuous DMA mode.Will do in a separate PR. The peripheral doesn't wait for FIFO.- [ ] Consider using mipidsi for the example.Will do in a separate PR. I need to make a PR to mipidsi first for this display.Must
errors
orwarnings
.cargo fmt
was run.CHANGELOG.md
in the proper section.Nice to have