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

CH1120 OLED driver support #2496

Open
RSE-dev opened this issue Aug 30, 2024 · 11 comments
Open

CH1120 OLED driver support #2496

RSE-dev opened this issue Aug 30, 2024 · 11 comments
Milestone

Comments

@RSE-dev
Copy link

RSE-dev commented Aug 30, 2024

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:

  • declared the new function in u8g2.h
    void u8g2_Setup_ch1120_128x160_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb);
  • created the new function in u8g2_d_setup.c
void u8g2_Setup_ch1120_128x160_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
	{
	uint8_t tile_buf_height;
	uint8_t *buf;
	u8g2_SetupDisplay(u8g2, u8x8_d_ch1120_128x160, u8x8_cad_001, byte_cb, gpio_and_delay_cb);
	buf = u8g2_m_16_20_f(&tile_buf_height);
	u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation);
	}
  • declared the new function in u8x8.h
    uint8_t u8x8_d_ch1120_128x160(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
  • created the file u8x8_d_ch1120.c (the clone of the u8x8_d_sh1108.c with adopted functions names and modified init_seq and display_info)
  • the init_seq was modified according to the manufacturers datasheet and rocemmendations:
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(0x021,0x000,0x01F), 	// Set Column Start/End Address of Display RAM for Mono mode
U8X8_CAA(0x022,0x000,0x04F), 	// Set Row Start/End Address of Display RAM for Mono mode
U8X8_CA(0x0a2, 0x000), 			// Set Display Start Line
U8X8_CA(0x081, 0x02f), 			// The Contrast Control Mode Set
U8X8_CA(0x0ac, 0x001), 			// Set Grayscale/Mono display mode ->Mono mode
U8X8_CA(0x020, 0x000),		    // Set Memory addressing mode?????
U8X8_C(0x0a0),					// Set Segment Re-map
U8X8_C(0x0c8),					// Set Common Output Scan Direction
U8X8_CA(0x0a3, 0x000),  		// Set Display Rotation 90* - NO
U8X8_C(0x0a4), 					// Disable Entire Display OFF/ON
U8X8_C(0x0a6), 					// Set Normal/Reverse Display
U8X8_CA(0x0a8, 0x07f), 			// Set Multiplex Ratio
U8X8_CA(0x0d3, 0x010), 			// Set Display Offset
U8X8_CA(0x0d5, 0x01f), 			// Set Display Divide Ratio/Oscillator Frequency Mode Set
U8X8_CAAAAA(0x048, 0x002, 0x093, 0x002, 0x0d8, 0x001),// Dis-charge Period
U8X8_CAAAAA(0x049, 0x006, 0x0d9, 0x00f, 0x094, 0x01f),// Pre-charge Period
U8X8_CA(0x04b, 0x004),			// Pre-charge strength
U8X8_CA(0x0da, 0x000), 			// Set SEG pads hardware configuration
U8X8_CA(0x0db, 0x040), 			// VCOM Deselect Level
U8X8_CA(0x0ad, 0x002), 			// External or internal IREF Set
//U8X8_C(0x0af),
U8X8_END_TRANSFER(),			 /* disable chip */
U8X8_END() 						/* end of sequence */
};
  • the display_info was modified according to the manufacturer's datasheet:
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 = */12, /* ch1120: 12 us tcsh */
/* pre_chip_disable_wait_ns = */45, /* ch1120: 12 us tcss */
/* reset_pulse_width_ms = */1, /* ch1120: 10 us */
/* post_reset_wait_ms = */1,
/* sda_setup_time_ns = */20, /* ch1120: 20ns tsds */
/* 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, /* ch1120: 40 ns tds8 */
/* write_pulse_width_ns = */150, /* ch1120: tCCLW */
/* tile_width = */16,
/* tile_height = */20,
/* default_x_offset = */16,
/* flipmode_x_offset = */16,
/* pixel_width = */128,
/* pixel_height = */160 };

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 olikraus added this to the 2.35 milestone Aug 30, 2024
@olikraus
Copy link
Owner

olikraus commented Sep 3, 2024

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 added a commit that referenced this issue Sep 3, 2024
olikraus added a commit that referenced this issue Sep 3, 2024
olikraus added a commit that referenced this issue Sep 3, 2024
@olikraus
Copy link
Owner

olikraus commented Sep 3, 2024

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

RSE-dev commented Sep 4, 2024

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:

  • Arduino mega2560, and its native IDE (the code execution gives only the black screen)
  • STM32 with corresponding adaptations of the lib (the code execution gives a "snow" image)
    CH1120_snow_2

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

@olikraus
Copy link
Owner

olikraus commented Sep 4, 2024

Somehow it looks like that no data is transfered.

  • How does the code look like (for Arduino IDE)?
  • Which is the exact constructor + arguments?
  • How is the display connected?
  • For SPI: Can you confirm that the CD/A0 line is connected correctly?

@RSE-dev
Copy link
Author

RSE-dev commented Sep 5, 2024

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
Copy link
Owner

olikraus commented Sep 7, 2024

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

RSE-dev commented Sep 9, 2024

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
Copy link
Owner

olikraus commented Sep 9, 2024

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

@olikraus olikraus modified the milestones: 2.35, 2.36 Sep 11, 2024
@RSE-dev
Copy link
Author

RSE-dev commented Sep 12, 2024

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

RSE-dev commented Sep 24, 2024

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
Copy link
Owner

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

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

No branches or pull requests

2 participants