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

Add support for SSD1680 e-paper controller #210

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

AramVartanyan
Copy link

The driver module is based on the construction of IL3820 (SSD1608), which is very similar from command point of view.
It is tested on ESP32-S2 MCU and GoodDisplay GDEY029T94.

The driver is based on the construction of IL3820 (SSD1608), which is very similar from command point of view.
It is tested on ESP32-S2 MCU and GoodDisplay GDEY029T94.

Choose a reason for hiding this comment

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

Hi! Any chance you could share the setup necessary for LVGL? The display is filled with random dots (landscape or portrait) and the label is shown as a black strip while the text appears ghosted (and inverted) on another portion of the display. Thanks!

Copy link
Author

@AramVartanyan AramVartanyan Sep 17, 2023

Choose a reason for hiding this comment

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

Hi,
I had the same issue at first.

This is my default lvl config, which should make it work:

# LVGL configuration
#

CONFIG_LV_USE_USER_DATA=y

CONFIG_LV_COLOR_DEPTH_1=y
CONFIG_LV_THEME_MONO=y
CONFIG_LV_THEME_DEFAULT_INIT_MONO=y
CONFIG_LV_DISP_DEF_REFR_PERIOD=500

CONFIG_LV_TFT_DISPLAY_USER_CONTROLLER_SSD1680=y
CONFIG_LV_HOR_RES_MAX=128
CONFIG_LV_VER_RES_MAX=296
CONFIG_LV_DPI=112
CONFIG_LV_DISPLAY_ORIENTATION_LANDSCAPE=y

#
# Display Pin Assignments
#
CONFIG_LV_DISP_SPI_MOSI=38
# CONFIG_LV_DISPLAY_USE_SPI_MISO is not set
CONFIG_LV_DISP_SPI_CLK=37
CONFIG_LV_DISPLAY_USE_SPI_CS=y
CONFIG_LV_DISP_SPI_CS=36
CONFIG_LV_DISPLAY_USE_DC=y
CONFIG_LV_DISP_PIN_DC=35
CONFIG_LV_DISP_USE_RST=y
CONFIG_LV_DISP_PIN_RST=34
CONFIG_LV_DISP_PIN_BUSY=33

CONFIG_LV_DISP_BACKLIGHT_OFF=y

Unfortunately I do not have the display anymore.

Copy link

@glina126 glina126 Sep 18, 2023

Choose a reason for hiding this comment

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

Thanks for the quick reply! Fyi (and for anyone that stumbles on this), in the end, the LVGL buffer needed to be manually set up as it does not cover the entire screen and thus it was rendering only bits and pieces. Also, here is a fixed snipped for landscape mode (you have it backwards :)

    // mirrored index 
    uint16_t mirrored_idx = (EPD_PANEL_HEIGHT - x) + ((y >> 3) * EPD_PANEL_HEIGHT);
    byte_index = x + ((y >> 3) * EPD_PANEL_HEIGHT);
    bit_index  = y & 0x7;
    // 1 means white, 0 means black
    // note that the bit index is inverted in place
    if (color.full == 0) {
        BIT_SET(buf[mirrored_idx - 1], 7 - bit_index);
    } else {
        BIT_CLEAR(buf[mirrored_idx - 1], 7 - bit_index);
    }

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for fixing this.
That part was very blurry to me. Obviously I haven't understood it.

Is there a way for you to test it with the fix?
(Or you already have?!)

Thanks to Damian Glinojecki
@glina126
Copy link

I need to clean up my base, but once I am done, I can do a pr. This is a weekend/night project so it will take some time.

@FilipSzkandera
Copy link

The driver module is based on the construction of IL3820 (SSD1608), which is very similar from command point of view.
It is tested on ESP32-S2 MCU and GoodDisplay GDEY029T94.

Do you have some example code with the initialisation process for the SSD1680? I've been able to drive a classic LCD display with ESP & LVGL and thankfully with your PR been able to compile it for an e-paper, but I'm having trouble displaying anything.

@glina126
Copy link

glina126 commented Sep 19, 2023

@FilipSzkandera

when initializing lv_disp_buf_init (I am on v7.11 -- need to update.. I know) I had to manually specify the buffer size to WIDTH * HEIGHT of my display (296 * 128). I only did this to ensure the entire display gets updated every time. The ssd1608 driver flush function assumes this today. Also, I need to unwind this properly, but.. the screen needs to be set as rotated = 1 and yet set as Landscape. I will unwind this math before doing a pr and ensure its correct.

@FilipSzkandera
Copy link

@glina126

Hmm, okay. I'm using LVGL v8.3.0 and esp-idf v5 so I had to update few more things, but merging this PR with this one #227 allowed me to compile it all, though nothing ever showed up on the display.

And what about lv_disp_drv_init()? I had it for my lcd display. (Sorry, I'm still learning LVGL) :)

@AramVartanyan
Copy link
Author

@FilipSzkandera check if this example works for you:

https://github.com/AramVartanyan/lvgl_test

@FilipSzkandera
Copy link

@FilipSzkandera check if this example works for you:

https://github.com/AramVartanyan/lvgl_test

Yes! Thank you very much, this really helped a lot!
I had to do some modifications in order to run it with LVGL 8.3.0 like lv_disp_buf_init -> lv_disp_draw_buf_init and so on, but otherwise exactly what I needed.

@FilipSzkandera
Copy link

@AramVartanyan

My code now renders what I want on my e-ink display, however whenever I change anything (i.e. update any part of the screen, change focus, etc) the display won't refresh. It will fill a small portion of the screen with a random garbage, but never goes through the whole refreshing process nor will it change anything else. Have you stumbled upon this at any time?

@AramVartanyan
Copy link
Author

@FilipSzkandera

Unfortunately no. The issue could be related again to the portrait / landscape mode.

However there is something you can try out.
The first display flush will be full. For the next 5 flushes only partial image refreshes will be done. The seventh will be full again.
Try to refresh the display at least 7 times and tell me about the result.

Thanks

@FilipSzkandera
Copy link

FilipSzkandera commented Oct 1, 2023

@AramVartanyan

Oh! yeah, after x changes the display will appear to be updated correctly! I was not able to confirm that there are 7 needed display updates (I'm not sure when lvgl will force an update) but it is after some amount of button interactions that will change a display contents somehow.

Could you please point me somewhere where I could look next? I've done a lot of research and I have ran out of ideas, but should I try to debug your code, or do you think this could be related to something else?
Very appreciating your help, thank you very much.

@AramVartanyan
Copy link
Author

AramVartanyan commented Oct 1, 2023

Good. I believe that the issue should be in the partial flush area. However I lost my display and I cannot test it.

Possible workaround could be to avoid the condition and to allow full refresh every time.
This could be easily done by setting EPD_PARTIAL_CNT to zero, directly in ssd1680.c.

@FilipSzkandera
Copy link

Thank you very much, I'll go through it in upcoming few days and report back if I find anything.

@FilipSzkandera
Copy link

FilipSzkandera commented Oct 3, 2023

@AramVartanyan

Ok, now I have dissected every line of your partial-update routine, tried playing with numbers, etc.
Setting EPD_PARTIAL_CNT to zero works well, but it is not a great solution in some applications.

This change IS NOT a solution, but with this setup, the screen at least appear to be partially changing and I cen see the change happening. But the whole page has offset, the numbers are really faded and funky.

        //update partial
        // ssd1680_hw_reset();
        ssd1680_write_cmd(SSD1680_CMD_BWF_CTRL, ssd1680_border_part, 1);
        ssd1680_set_window(0, EPD_PANEL_WIDTH - 1, 0, EPD_PANEL_HEIGHT - 1);
        ssd1680_set_cursor(0, EPD_PANEL_HEIGHT - 1);
        
        ssd1680_send_cmd(SSD1680_CMD_WRITE1_RAM);
        for(size_t row = 0; row <= (EPD_PANEL_HEIGHT - 1); row++) {
            ssd1680_send_data(buffer, linelen);
            buffer += SSD1680_COLUMNS; //(128/8)x296 = 4736
        }

        ssd1680_update_display(true);
        partial_counter--;
    }

    // ssd1680_deep_sleep();

This does eliminate a lot of the "partial update" mechanics, because it updates almost the whole screen.

If you would be willing to, I can send you some screenshots, I think with your knowledge and experience you could just see my obvious mistake that I am not seeing (or maybe a bug in the code) :)
Thanks

@AramVartanyan
Copy link
Author

@FilipSzkandera
It will be really hard for me to debug it, since I do not have the display anymore.
However, I have tested the partial update with LVGL v7.11 and it was working fine with my setup. This was one of my main goals.
The only issue was with the Landscape mode, but I hope it is fixed now.
Is it a big effort for you to test the driver with v7.11?
If you experience the same issue, than I would try to debug it with your help. But I would prefere to find another way for communication and not to spam here.
For sure I would need to see screenshots, code, settings, hardware connections etc.

@FilipSzkandera
Copy link

FilipSzkandera commented Oct 29, 2023

@AramVartanyan
I think I found the bug. I have to experiment a bit more and then I'll come back.

@FilipSzkandera
Copy link

This should be the fix, but I need to run it for longer to be for sure.
buffer = (uint8_t *) color_map; - is very important, my guess is that it did not really update RAM2 with correct data and caused really strange artefacts all over the screen.
ssd1680_init(); - was moved inside the full refresh cycle as it also messed up the partial refresh, hardware reset is all there is needed for PR.
I could not get partial screen to update just a part of the screen, so it updates the whole screen. Might fix later, but it is at least working for me.

...
if (!partial_counter) {
        ssd1680_init();
        ESP_LOGD(TAG, "Refreshing in FULL");
        ssd1680_send_cmd(SSD1680_CMD_WRITE1_RAM);
        
        for(size_t row = 0; row <= (EPD_PANEL_HEIGHT - 1); row++){
            ssd1680_send_data(buffer, linelen);
            buffer += SSD1680_COLUMNS;
        }

        buffer = (uint8_t *) color_map;
        
        ssd1680_send_cmd(SSD1680_CMD_WRITE2_RAM);
        for(size_t row = 0; row <= (EPD_PANEL_HEIGHT - 1); row++){
            ssd1680_send_data(buffer, linelen);
            buffer += SSD1680_COLUMNS;
        }
        ssd1680_update_display(false);
        partial_counter = EPD_PARTIAL_CNT;
    } else {
        //update partial
        ssd1680_hw_reset();
        ssd1680_write_cmd(SSD1680_CMD_BWF_CTRL, ssd1680_border_init, 1);

        // ssd1680_set_window(area->x1, area->x2, area->y1, area->y2);
        // ssd1680_set_window(0, EPD_PANEL_WIDTH - 1, 0, EPD_PANEL_HEIGHT - 1);
        // ssd1680_set_cursor(0, EPD_PANEL_HEIGHT - 1);
        ssd1680_set_cursor(x_addr_counter, y_addr_counter);

        ssd1680_send_cmd(SSD1680_CMD_WRITE1_RAM);
        for(size_t row = 0; row <= (EPD_PANEL_HEIGHT - 1); row++) {
            ssd1680_send_data(buffer, linelen);
            buffer += SSD1680_COLUMNS; //(128/8)x296 = 4736
        }

        ssd1680_update_display(true);
        partial_counter--;
    }

@AramVartanyan
Copy link
Author

@FilipSzkandera Thank you for working to fix the issue.

The initialization is required no matter if it is a partial or a full refresh. "ssd1680_init();" should stay before the "if(!partial_counter)...".
Also I have noticed that for some reason (I do not remember why) I placed a condition for the HW reset in the initialization. Please remove this condition and test again. The HW reset is always required.

And finally you can try to switch the SSD1680_DATA_ENTRY_XIYIY with SSD1680_DATA_ENTRY_XIYDX for the ssd1680_scan_mode.
Honestly I am not sure how this option is influencing the work of ssd1680, but there are 8 Data entry sequence modes and I have not tested them. It could be related to the partial refresh issue.

@4lphac
Copy link

4lphac commented Jul 26, 2024

Hello, @FilipSzkandera did you succeed in making your display work with partial update with ESP-IDF 5 and LVGL 8? I'm in the process of porting a little project from arduino+epd2 to ESP-IDF & LVGL, and I'd like to start with the latest stable version of all components. Thanks!

@FilipSzkandera
Copy link

Yes I did; kinda.
I could only get the partial refresh working on a whole display. When specifying a smaller refresh window (ssd1680_set_window and ssd1680_set_cursor) It always did something unexpected for me and after few hours, I was convinced that mine did not supported that. And the partial refresh is still working, so that was fine for my use case. But maybe I'm wrong.

To address the previous discussion here, I did end up with this code for ssd1680_flush :

void ssd1680_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
    /* Each byte holds the data of 8 pixels, linelen is the number of bytes
     * we need to cover a line of the display. */

    size_t linelen = EPD_PANEL_WIDTH / 8; //SSD1680_COLUMNS = (EPD_PANEL_WIDTH / 8)
    uint8_t *buffer = (uint8_t *) color_map;

    if (!partial_counter) {
        ssd1680_init();
        ssd1680_set_cursor(area->y1, area->x1);

        ssd1680_send_cmd(SSD1680_CMD_WRITE1_RAM);
        for(size_t row = 0; row <= (EPD_PANEL_HEIGHT - 1); row++){
            ssd1680_send_data(buffer, linelen);
            buffer += SSD1680_COLUMNS;
        }

        buffer = (uint8_t *) color_map;
        
        ssd1680_send_cmd(SSD1680_CMD_WRITE2_RAM);
        for(size_t row = 0; row <= (EPD_PANEL_HEIGHT - 1); row++){
            ssd1680_send_data(buffer, linelen);
            buffer += SSD1680_COLUMNS;
        }
        ssd1680_update_display(false);
        partial_counter = EPD_PARTIAL_CNT;
    } else {
        ssd1680_hw_reset();
        ssd1680_write_cmd(SSD1680_CMD_BWF_CTRL, ssd1680_border_init, 1);
        
        ssd1680_set_window(area->y1, area->y2, area->x1, area->x2);
        ssd1680_set_cursor(area->y1, area->x1);

        ssd1680_send_cmd(SSD1680_CMD_WRITE1_RAM);

        buffer += (area->x1+8) * area->y1 / 8;

        int new_line_len = ((area->y2 - area->y1 + 1)) / 8;

        for(size_t row = 0; row <= ((area->x2 - area->x1)); row++) {
            ssd1680_send_data(buffer, new_line_len);
            buffer += new_line_len; //(128/8)x296 = 4736
        }

        ssd1680_update_display(true);
        partial_counter--;
    }
    ssd1680_deep_sleep();
    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing */
    lv_disp_flush_ready(drv);
    
}

Then I had to set LVGL to always refresh the full screen, otherwise this callback won't have the correct data. And it is working great for me ever since.

@4lphac
Copy link

4lphac commented Jul 28, 2024

No luck, I merged contributes from both driver repositories like you did for both IDF V5- LVGL v8 compatibility and SSD1680 support, but @AramVartanyan test project brings up only a garbled output (pins are set correctly since they work on arduino+epd2). How did you set "LVGL to always refresh the full screen"? @FilipSzkandera . Would you share your setup? drivers and test project?

@AramVartanyan
Copy link
Author

No luck, I merged contributes from both driver repositories like you did for both IDF V5- LVGL v8 compatibility and SSD1680 support, but @AramVartanyan test project brings up only a garbled output (pins are set correctly since they work on arduino+epd2). How did you set "LVGL to always refresh the full screen"?

Unfortunately the test example works only on LVGL v7.11. I have tried to run v8, but this repository does not support it. It will take lot of effort to make it work.

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.

4 participants