olikraus / u8g2

U8glib library for monochrome displays, version 2
Other
5.2k stars 1.06k forks source link

CH1120 OLED driver support #2496

Open RSE-dev opened 3 months ago

RSE-dev commented 3 months ago

I have encountered an issue with the newer CH1120 driver IC, which seems to be replacing the SH1108. The OLED module looks absolutely the same, has the same pinout (SPI connection) and same resolution (128x160) Despite their similarities, the CH1120 does not function correctly with the existing SH1108 code in the library. I'm using STM32CubeIDE, the display module is connected through the SPI. The module based on SH1108 with the same setup works perfectly

I attempted to modify the existing code:

Unfortunately, changes I made didn't lead to the working module, only displaying "the snow". But the display reacts for the setting the contrast, rotation and u8g2_SetPowerSave

CH1120_snow

Please find here the datasheet for the OLED driver IC and for the OLED display based on it WEA160128BWPP3N00000.pdf CH1120 V0.9.pdf

olikraus commented 2 months ago

It looks like that the CH1120 is a graylevel display, while SH1108 is a pure monochrome display. At least CH1120 needs to be configured as a monochrome display, see chapter 6.7.3 of the datasheet.

Did you compare the command table of both display? I think there are not so many similarities... Anyhow I will follow your apprach and create a copy of the SH1108 code...

olikraus commented 2 months ago

I tookover the init sequence from the display datasheet. It is very much different to the SH1108. I really wonder that anything was visible with the SH1108 constructor.

I have created beta 2.35.25 with constructor U8G2_CH1120_128X160_1_4W_HW_SPI and friends

Maybe you can test this new constructor. I assume still a lot of work is required.

You can download the latest U8g2 beta release from here: https://github.com/olikraus/U8g2_Arduino/archive/master.zip Arduino IDE:

  1. Remove the existing U8g2_Arduino library (https://stackoverflow.com/questions/16752806/how-do-i-remove-a-library-from-the-arduino-environment)
  2. Install the U8g2_Arduino Zip file via Arduino IDE, add zip library menu (https://www.arduino.cc/en/Guide/Libraries).

PlatformIO: platformio.ini (https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-deps) should include

lib_deps =
  u8g2=https://github.com/olikraus/U8g2_Arduino/archive/master.zip
RSE-dev commented 2 months ago

I tested the new constructor. Unfortunately, the result is the same (or maybe just a little different image of the snow)

Moreover, I have to add that the testing is running on two different platforms:

For the future iterations, I'll run the code on both platforms to provide more information to you

olikraus commented 2 months ago

Somehow it looks like that no data is transfered.

RSE-dev commented 2 months ago

I tested the code with two displays. The modules are absolutely the same except for the driver IC. The pinout of the modules is the same, so I'm just swapping the modules and flashing the new code to the Arduino Mega.

photo_2024-09-05_09-13-32

The code is pretty basic, please find it here with comments

#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

// U8G2_CH1120_128X160_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 42, /* dc=*/ 44, /* reset=*/ 46);     //doesn't work with CH1120 based display
// U8G2_CH1120_128X160_2_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 42, /* dc=*/ 44, /* reset=*/ 46);     //doesn't work with CH1120 based display
// U8G2_CH1120_128X160_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 42, /* dc=*/ 44, /* reset=*/ 46);     //doesn't work with CH1120 based display

// U8G2_SH1108_128X160_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 42, /* dc=*/ 44, /* reset=*/ 46);     //works fine with SH1108 based display
// U8G2_SH1108_128X160_2_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 42, /* dc=*/ 44, /* reset=*/ 46);     //works fine with SH1108 based display
U8G2_SH1108_128X160_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 42, /* dc=*/ 44, /* reset=*/ 46);    //works fine with SH1108 based display

char buf[16];

void setup(void) {
  u8g2.begin();  
}

void loop(void) {
  ltoa(millis(),buf,10);
  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_ncenB10_tr);
    u8g2.drawStr(0,24,"Hello World!");
    u8g2.drawStr(0,50,buf);
  } while ( u8g2.nextPage() );
  delay(1000);
}

As the pinout of the modules is the same and the code works fine with the SH1108-based display, I assume, the connection is correct.

olikraus commented 2 months ago

Ok, i found a remark in the datasheet and fixed the code based on this. Can you test the new beta (2.35.26) again ?

/ You can download the latest U8g2 beta release from here: https://github.com/olikraus/U8g2_Arduino/archive/master.zip Arduino IDE:

  1. Remove the existing U8g2_Arduino library (https://stackoverflow.com/questions/16752806/how-do-i-remove-a-library-from-the-arduino-environment)
  2. Install the U8g2_Arduino Zip file via Arduino IDE, add zip library menu (https://www.arduino.cc/en/Guide/Libraries).

PlatformIO: platformio.ini (https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-deps) should include

lib_deps =
  u8g2=https://github.com/olikraus/U8g2_Arduino/archive/master.zip
RSE-dev commented 2 months ago

I tested the updated code. Unfortunately, there are no changes. On Arduino Mega - just a black screen On STM32 - a "snow" image that reacts to the screen content update by blinking its part

olikraus commented 2 months ago

I am running out of ideas. Where did you buy that display?

RSE-dev commented 2 months ago

I got it from TELEREX as a product sample. Suspecting the module is damaged, I ordered one more to check how it works. As soon as I have them received it, I will repeat the tests and let you know

RSE-dev commented 2 months ago

I started the display after the modifying the initial sequence and the adding the U8X8_MSG_DISPLAY_DRAW_TILE functionality.

The max frequency of SPI for this controller is around 1MHz

Again, there are some artifacts on the display

#include "u8x8.h"
/* 
 code copyied from sh1107
 ch1120: 160x160 controller from Sino Wealth
 */

static const uint8_t u8x8_d_ch1120_noname_powersave0_seq[] = { U8X8_START_TRANSFER(), /* enable chip, delay is part of the transfer start */
U8X8_C(0x0af), /* display on */
U8X8_END_TRANSFER(), /* disable chip */
U8X8_END() /* end of sequence */
};

static const uint8_t u8x8_d_ch1120_noname_powersave1_seq[] = { U8X8_START_TRANSFER(), /* enable chip, delay is part of the transfer start */
U8X8_C(0x0ae), /* display off */
U8X8_END_TRANSFER(), /* disable chip */
U8X8_END() /* end of sequence */
};

static const uint8_t u8x8_d_ch1120_160x160_noname_powersave0_seq[] = { U8X8_START_TRANSFER(), /* enable chip, delay is part of the transfer start */
U8X8_C(0x0a0), /* segment remap a0/a1*/
U8X8_C(0x0c0), /* c0: scan dir normal, c8: reverse */
U8X8_END_TRANSFER(), /* disable chip */
U8X8_END() /* end of sequence */
};

static const uint8_t u8x8_d_ch1120_160x160_noname_powersave1_seq[] = { U8X8_START_TRANSFER(), /* enable chip, delay is part of the transfer start */
U8X8_C(0x0a1), /* segment remap a0/a1*/
U8X8_C(0x0c8), /* c0: scan dir normal, c8: reverse */
U8X8_END_TRANSFER(), /* disable chip */
U8X8_END() /* end of sequence */
};

static uint8_t u8x8_d_ch1120_generic(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
    uint8_t x, c, y;
    uint8_t *ptr;
    switch (msg) {
    /* handled by the calling function
     case U8X8_MSG_DISPLAY_SETUP_MEMORY:
     u8x8_d_helper_display_setup_memory(u8x8, &u8x8_ch1120_64x128_noname_display_info);
     break;
     */
    /* handled by the calling function
     case U8X8_MSG_DISPLAY_INIT:
     u8x8_d_helper_display_init(u8x8);
     u8x8_cad_SendSequence(u8x8, u8x8_d_ch1120_64x128_noname_init_seq);
     break;
     */
    case U8X8_MSG_DISPLAY_SET_POWER_SAVE:
        if (arg_int == 0)
            u8x8_cad_SendSequence(u8x8, u8x8_d_ch1120_noname_powersave0_seq);
        else
            u8x8_cad_SendSequence(u8x8, u8x8_d_ch1120_noname_powersave1_seq);
        break;
    case U8X8_MSG_DISPLAY_SET_FLIP_MODE:
        if (arg_int == 0) {
            u8x8_cad_SendSequence(u8x8, u8x8_d_ch1120_160x160_noname_powersave0_seq);
            u8x8->x_offset = u8x8->display_info->default_x_offset;
        } else {
            u8x8_cad_SendSequence(u8x8, u8x8_d_ch1120_160x160_noname_powersave1_seq);
            u8x8->x_offset = u8x8->display_info->flipmode_x_offset;
        }
        break;
#ifdef U8X8_WITH_SET_CONTRAST
    case U8X8_MSG_DISPLAY_SET_CONTRAST:
        u8x8_cad_StartTransfer(u8x8);
        u8x8_cad_SendCmd(u8x8, 0x081);
        u8x8_cad_SendArg(u8x8, arg_int); /* ch1120 has range from 0 to 255 */
        u8x8_cad_EndTransfer(u8x8);
        break;
#endif
    case U8X8_MSG_DISPLAY_DRAW_TILE:
        u8x8_cad_StartTransfer(u8x8);
        x = ((u8x8_tile_t*) arg_ptr)->x_pos;
        y = (((u8x8_tile_t*) arg_ptr)->y_pos);
        c = ((u8x8_tile_t*) arg_ptr)->cnt; /* number of tiles */
        ptr = ((u8x8_tile_t*) arg_ptr)->tile_ptr; /* data ptr to the tiles */
        uint8_t buf[64]; //buffer in bytes for the row = number of tiles*8columns/2 as one byte holds two greyscale pixels

        for (uint8_t row = 0; row < 8; row++) {
            for (uint8_t col = 0; col < c*8; col++) {
                uint8_t *lptr = ptr + col;
                uint8_t bit_value = (*lptr >> row) & 0x01;
                uint8_t four_bit_value = (bit_value == 1) ? 0xF : 0x0;
                buf[col / 2] = (buf[col / 2] << 4) | four_bit_value;
            }
            u8x8_cad_SendCmd(u8x8, 0x021); /* set column start/stop address */
            u8x8_cad_SendArg(u8x8, 0x00);
            u8x8_cad_SendArg(u8x8, 0x3F);

            u8x8_cad_SendCmd(u8x8, 0x022); /* set row start/stop address */
            u8x8_cad_SendArg(u8x8, y * 8 + row);
            u8x8_cad_SendArg(u8x8, y * 8 + row + 1);
            u8x8_cad_SendData(u8x8, 64, buf);
        }
        return u8x8_cad_EndTransfer(u8x8);
        break;
    default:
        return 0;
    }
    return 1;
}

static const u8x8_display_info_t u8x8_ch1120_160x160_noname_display_info =
{
  /* chip_enable_level = */ 0,
  /* chip_disable_level = */ 1,

  /* post_chip_enable_wait_ns = */ 60,
  /* pre_chip_disable_wait_ns = */ 120,
  /* reset_pulse_width_ms = */ 100,     /* ch1120: 3 us */
  /* post_reset_wait_ms = */ 100, /* sometimes OLEDs need much longer setup time */
  /* sda_setup_time_ns = */ 100,        /* ch1120: 100ns */
  /* sck_pulse_width_ns = */ 100,   /* ch1120: 100ns */
  /* sck_clock_hz = */ 4000000UL,   /* since Arduino 1.6.0, the SPI bus speed in Hz. Should be  1000000000/sck_pulse_width_ns */
  /* spi_mode = */ 0,       /* active high, rising edge */
  /* i2c_bus_clock_100kHz = */ 4,
  /* data_setup_time_ns = */ 40,
  /* write_pulse_width_ns = */ 150, /* ch1120: cycle time is 300ns, so use 300/2 = 150 */
  /* tile_width = */ 20,
  /* tile_height = */ 20,
  /* default_x_offset = */ 0,
  /* flipmode_x_offset = */ 0,
  /* pixel_width = */ 160,
  /* pixel_height = */ 160
};

static const uint8_t u8x8_d_ch1120_128x160_noname_init_seq[] = {

U8X8_START_TRANSFER(), /* enable chip, delay is part of the transfer start */

U8X8_C(0x0ae), /* display off */
U8X8_CAA(0x21, 0x00, 0x3f), /* Set Column Start/End Address of Display RAM, mono mode */
U8X8_CAA(0x22, 0x00, 0x9f), /* Set Row Start/End Address of Display RAM, mono mode */
U8X8_CA(0x0a2, 0x000), /* display start */
U8X8_CA(0x081, 0x08f), /* set contrast control */
U8X8_CA(0x0ac, 0x000), /* mono mode, or 0x003???*/
U8X8_CA(0x020, 0x000), /* addressing mode */
U8X8_C(0x0a0), /* scan direction */
U8X8_C(0x0C8), /* scan direction */
U8X8_CA(0x0a3, 0x001), /* display rotation */
U8X8_C(0x0a4), /* Disable Entire Display OFF/ON */
U8X8_C(0x0a6), /* Set Normal/Reverse Display */
U8X8_CA(0x0a8, 0x07f), /* multiplex ratio */
U8X8_CA(0x0d3, 0x010), /* display offset */
U8X8_CA(0x0d5, 0x01f), /* clock divide ratio and oscillator frequency */
U8X8_CA(0x048, 0x002), /*  */
U8X8_CA(0x093, 0x002), /*  */
U8X8_CA(0x0d8, 0x001), /*  */
U8X8_CA(0x049, 0x006), /*  */
U8X8_CA(0x0d9, 0x00f), /*  */
U8X8_CA(0x094, 0x01f), /*  */
U8X8_CA(0x04b, 0x004), /*  */
U8X8_CA(0x0da, 0x000), /*  */
U8X8_CA(0x0db, 0x040), /* VSEGM Deselect Level */
U8X8_CA(0x0ad, 0x002), /*  */
U8X8_C(0x0aF), /* display on */
U8X8_END_TRANSFER(), /* disable chip */
U8X8_END() /* end of sequence */
};

static const u8x8_display_info_t u8x8_ch1120_128x160_noname_display_info = {
/* chip_enable_level = */0,
/* chip_disable_level = */1,

/* post_chip_enable_wait_ns = */20,
/* pre_chip_disable_wait_ns = */20,
/* reset_pulse_width_ms = */100, /* ch1120: 3 us */
/* post_reset_wait_ms = */100, /* sometimes OLEDs need much longer setup time */
/* sda_setup_time_ns = */100, /* ch1120: 100ns */
/* sck_pulse_width_ns = */100, /* ch1120: 100ns */
/* sck_clock_hz = */1000000UL, /* since Arduino 1.6.0, the SPI bus speed in Hz. Should be  1000000000/sck_pulse_width_ns */
/* spi_mode = */0, /* active high, rising edge */
/* i2c_bus_clock_100kHz = */4,
/* data_setup_time_ns = */40,
/* write_pulse_width_ns = */40, /* ch1120: cycle time is 300ns, so use 300/2 = 150 */
/* tile_width = */16,
/* tile_height = */20,
/* default_x_offset = */0,
/* flipmode_x_offset = */0,
/* pixel_width = */128,
/* pixel_height = */160 };

uint8_t u8x8_d_ch1120_128x160(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {

    if (u8x8_d_ch1120_generic(u8x8, msg, arg_int, arg_ptr) != 0)
        return 1;

    switch (msg) {
    case U8X8_MSG_DISPLAY_INIT:
        u8x8_d_helper_display_init(u8x8);
        u8x8_cad_SendSequence(u8x8, u8x8_d_ch1120_128x160_noname_init_seq);
        break;
    case U8X8_MSG_DISPLAY_SETUP_MEMORY:
        u8x8_d_helper_display_setup_memory(u8x8, &u8x8_ch1120_128x160_noname_display_info);
        break;
    default:
        return u8x8_d_ch1120_generic(u8x8, msg, arg_int, arg_ptr);
    }
    return 1;
}

image

olikraus commented 2 months ago

hmmm looks good..., however I am not sure how to help at the moment.