Display drivers to share (sharp mip, uc1610)

Hi

i’m using sharp mip (LS027B7DH01) an uc1610 (ea dogxl160) based displays for my project and have managed them to work with littlevGL (V5.3) but have some questions.

  • sharp mip (LS027B7DH01 400x240 monochrome) :
/**
 * @file SHARP_MIP.c
 *
 */

/*-------------------------------------------------------------------------------------------------
 *  SHARP memory in pixel monochrome display series
 *      LS012B7DD01 (184x38  pixels.)
 *      LS013B7DH03 (128x128 pixels.)
 *      LS013B7DH05 (144x168 pixels.)
 *      LS027B7DH01 (400x240 pixels.) (tested)
 *      LS032B7DD02 (336x536 pixels.)
 *      LS044Q7DH01 (320x240 pixels.)
 *
 *  These displays need periodic com inversion, there are two ways :
 * 		- software com inversion when writing a frame on screen
 * 		  memory : define SHARP_MIP_SOFT_COM_INVERSION 1 and
 * 		  set EXTMODE display pin LOW
 * 		- hardware com inversion with EXTCOMIN display pin :
 * 		  define SHARP_MIP_SOFT_COM_INVERSION 0,
 * 		  set EXTMODE display pin HIGH and handle
 * 		  EXTCOMIN waveform (for example with mcu pwm output),
 * 		  see datasheet pages 8-12 for details
 *
 *  VDB size : (LV_VER_RES / X) * (2 + LV_HOR_RES / 8) + 2 bytes, structure :
 * 	    [FRAME_HEADER (1 byte)] [GATE_ADDR (1 byte )] [LINE_DATA (LV_HOR_RES / 8 bytes)]	1st  line
 *	    [DUMMY        (1 byte)] [GATE_ADDR (1 byte )] [LINE_DATA (LV_HOR_RES / 8 bytes)]	2nd  line
 *	    ...........................................................................................
 *      [DUMMY        (1 byte)] [GATE_ADDR (1 byte )] [LINE_DATA (LV_HOR_RES / 8 bytes)]	last line
 *      [DUMMY                             (2 bytes)]
 *
 *  Since extra bytes (dummy, addresses, header) are stored in VDB, we need to use
 *  an "external" VDB. Configuration in lv_conf becomes :
 *  	#define LV_VDB_SIZE         ((LV_VER_RES / X) * LV_HOR_RES)
 *  	#define LV_VDB_PX_BPP       1
 *  	#define LV_VDB_ADR          LV_VDB_ADR_INV
 *  and before lv_init() we must call lv_vdb_set_adr(our_vdb_address, NULL)
 *-----------------------------------------------------------------------------------------------*/

/*********************
 *      INCLUDES
 *********************/

#include "SHARP_MIP.h"

#if USE_SHARP_MIP

#include <stdbool.h>
#include "../lvgl/lv_core/lv_vdb.h"
#include LV_DRV_DISP_INCLUDE
#include LV_DRV_DELAY_INCLUDE

/*********************
 *      DEFINES
 *********************/

#define SHARP_MIP_HEADER				      0
#define SHARP_MIP_UPDATE_RAM_FLAG		  (1U << 7U)	/* (M0) Mode flag : H -> update memory, L -> maintain memory */
#define SHARP_MIP_COM_INVERSION_FLAG	(1U << 6U)	/* (M1) Frame inversion flag : relevant when EXTMODE = L,    */
													                        /* 		  H -> outputs VCOM = H, L -> outputs VCOM = L         */
#define SHARP_MIP_CLEAR_SCREEN_FLAG	  (1U << 5U)  /* (M2) All clear flag : H -> clear all pixels               */

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/

/**********************
 *  STATIC VARIABLES
 **********************/

#if SHARP_MIP_SOFT_COM_INVERSION
static bool_t com_output_state = false;
#endif

/**********************
 *      MACROS
 **********************/

/*
 * Return the VDB byte index corresponding to the pixel
 * relatives coordinates (x, y) in the area.
 * The area is rounded to a whole screen line.
 */
#define BUFIDX(x, y)			(((x) >> 3U) + ((y) * (2U + (SHARP_MIP_HOR_RES >> 3U))) + 2U)

/*
 * Return the byte bitmask of a pixel bit corresponding
 * to VDB arrangement (8 pixels per byte on lines).
 */
#define PIXIDX(x)			    SHARP_MIP_REV_BYTE(1U << ((x) & 7U))

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void sharp_mip_init(void) {
	sharp_mip_init_board();        /*Function defined in our LV_DRV_DISP_INCLUDE*/
	sharp_mip_display_enable();    /*Function defined in our LV_DRV_DISP_INCLUDE*/
	/* These displays have nothing to initialize */
}


void sharp_mip_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p) {
	(void) x1;
	(void) x2;
  
  /*Return if the area is out the screen*/
  if(y2 < 0) return;
  if(y1 > SHARP_MIP_VER_RES - 1) return;

  /*Truncate the area to the screen*/
  int32_t act_y1 = y1 < 0 ? 0 : y1;
  int32_t act_y2 = y2 > SHARP_MIP_VER_RES - 1 ? SHARP_MIP_VER_RES - 1 : y2;

  /*Buffer address & size*/
	uint8_t * buf      = (uint8_t *) color_p;                          /*Get the buffer address*/
	uint16_t  buf_h    = (act_y2 - act_y1 + 1);                        /*Number of buffer lines*/
	uint16_t  buf_size = buf_h * (2 + SHARP_MIP_HOR_RES / 8) + 2;      /*Buffer size in bytes  */

	/* Set lines to flush dummy byte & gate address in VDB*/
	for(uint16_t act_y = 0 ; act_y < buf_h ; act_y++) {
		buf[BUFIDX(0, act_y) - 1] = SHARP_MIP_REV_BYTE((act_y1 + act_y + 1));
		buf[BUFIDX(0, act_y) - 2] = 0;
	}

	/* Set last dummy two bytes in VDB */
	buf[BUFIDX(0, buf_h) - 1] = 0;
	buf[BUFIDX(0, buf_h) - 2] = 0;

	/* Set frame header in VDB */
	buf[0] = SHARP_MIP_HEADER            |
           SHARP_MIP_UPDATE_RAM_FLAG;
#if SHARP_MIP_SOFT_COM_INVERSION
	if (com_output_state) {
		com_output_state = false;
	} else {
		buf[0] |= SHARP_MIP_COM_INVERSION_FLAG;
		com_output_state = true;
	}
#endif

	/* Write the frame on display memory */
	LV_DRV_DISP_SPI_CS(1);
	LV_DRV_DISP_SPI_WR_ARRAY(buf, buf_size);
	LV_DRV_DISP_SPI_CS(0);

  lv_flush_ready();
}

void sharp_mip_vdb_wr(uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) {
	(void) buf_w;
	(void) opa;

	if (lv_color_to1(color) != 0) {
		buf[BUFIDX(x, y)] |=  PIXIDX(x);   /*Set VDB pixel bit to 1 for other colors than BLACK*/
	} else {
		buf[BUFIDX(x, y)] &= ~PIXIDX(x);   /*Set VDB pixel bit to 0 for BLACK color*/
	}
}

void sharp_mip_round_cb(lv_area_t *area) {
  /* Round area to a whole line */
  area->x1 = 0;
  area->x2 = SHARP_MIP_HOR_RES - 1;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

#endif

the buffer stores extra bytes to transmit to display controller, is there a way to do that without using an “external” buffer ?

  • UC1610 (ea dogxl160 160x104 4 gray) :
/**
 * @file UC1610.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "UC1610.h"

#if USE_UC1610

#include <stdbool.h>
#include "../lvgl/lv_core/lv_vdb.h"
#include LV_DRV_DISP_INCLUDE
#include LV_DRV_DELAY_INCLUDE

/*********************
 *      DEFINES
 *********************/
#define UC1610_CMD_MODE    0U
#define UC1610_DATA_MODE   1U
#define UC1610_RESET_MODE  0U
#define UC1610_SET_MODE    1U

/* hardware control commands */
#define UC1610_SYSTEM_RESET			            0xE2        /* software reset */
#define UC1610_NOP				                  0xE3
#define UC1610_SET_TEMP_COMP			          0x24		    /* set temperature compensation, default -0.05%/°C */
#define UC1610_SET_PANEL_LOADING		        0x29		    /* set panel loading, default 16~21 nF */
#define UC1610_SET_PUMP_CONTROL		          0x2F		    /* default internal Vlcd (8x pump) */
#define UC1610_SET_LCD_BIAS_RATIO		        0xEB		    /* default 11 */
#define UC1610_SET_VBIAS_POT			          0x81		    /* 1 byte (0~255) to follow setting the contrast, default 0x81 */
#define UC1610_SET_LINE_RATE			          0xA0		    /* default 12,1 Klps */
#define UC1610_SET_DISPLAY_ENABLE		        0xAE		    /* + 1 / 0 : exit sleep mode / entering sleep mode */
#define UC1610_SET_LCD_GRAY_SHADE		        0xD0		    /* default 24% between the two gray shade levels */
#define UC1610_SET_COM_END			            0xF1		    /* set the number of used com electrodes (lines number -1) */

/* ram address control */
#define UC1610_SET_AC				                0x88		    /* set ram address control */
#define UC1610_AC_WA_FLAG			              (1U << 0U)  /* automatic column/page increment wrap around (1 : cycle increment) */
#define UC1610_AC_AIO_FLAG			            (1U << 1U)	/* auto increment order (0/1 : column/page increment first) */
#define UC1610_AC_PID_FLAG			            (1U << 2U)	/* page address auto increment order (0/1 : +1/-1) */

/* set cursor ram address */
#define UC1610_SET_CA_LSB			              0x00		    /* + 4 LSB bits */
#define UC1610_SET_CA_MSB 			            0x10		    /* + 4 MSB bits // MSB + LSB values range : 0~159 */
#define UC1610_SET_PA				                0x60        /* + 5 bits // values range : 0~26 */

/* display control commands */
#define UC1610_SET_FIXED_LINES		          0x90		    /* + 4 bits = 2xFL */
#define UC1610_SET_SCROLL_LINES_LSB		      0x40		    /* + 4 LSB bits scroll up display by N (7 bits) lines */
#define UC1610_SET_SCROLL_LINES_MSB		      0x50		    /* + 3 MSB bits */
#define UC1610_SET_ALL_PIXEL_ON		          0xA4		    /* + 1 / 0 : set all pixel on, reverse */
#define UC1610_SET_INVERSE_DISPLAY		      0xA6		    /* + 1 / 0 : inverse all data stored in ram, reverse */
#define UC1610_SET_MAPPING_CONTROL		      0xC0		    /* control mirorring */
#define UC1610_SET_MAPPING_CONTROL_LC_FLAG	(1U << 0U)
#define UC1610_SET_MAPPING_CONTROL_MX_FLAG	(1U << 1U)
#define UC1610_SET_MAPPING_CONTROL_MY_FLAG	(1U << 2U)

/* window program mode */
#define UC1610_SET_WINDOW_PROGRAM_ENABLE	  0xF8		    /* + 1 / 0 : enable / disable window programming mode, */
  								                                      /* reset before changing boundaries                    */
#define UC1610_SET_WP_STARTING_CA		        0xF4		    /* 1 byte to follow for column address */
#define UC1610_SET_WP_ENDING_CA		          0xF6		    /* 1 byte to follow for column address */
#define UC1610_SET_WP_STARTING_PA		        0xF5		    /* 1 byte to follow for page address   */
#define UC1610_SET_WP_ENDING_PA		          0xF7		    /* 1 byte to follow for page address   */

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/

/**********************
 *  STATIC VARIABLES
 **********************/
static uint8_t cmd_buf[12];

/**********************
 *      MACROS
 **********************/

/* Return the byte bitmask of a pixel color corresponding to VDB arrangement */
#define PIXIDX(y, c)			((c) << (((y) & 3U) << 1U))

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void uc1610_init(void) {
	uc1610_init_board();                               /* provided by "#include LV_DRV_DISP_INCLUDE" */
	LV_DRV_DELAY_MS(12);

	/* initialization sequence */
#if UC1610_INIT_HARD_RST
	LV_DRV_DISP_RST(UC1610_RESET_MODE);                /* hardware reset */
	LV_DRV_DELAY_MS(1);
	LV_DRV_DISP_RST(UC1610_SET_MODE);
#else
	cmd_buf[0]  = UC1610_SYSTEM_RESET;                 /* software reset */
	LV_DRV_DISP_CMD_DATA(UC1610_CMD_MODE);
	LV_DRV_DISP_SPI_CS(0);
	LV_DRV_DISP_SPI_WR_ARRAY(cmd_buf, 1);
	LV_DRV_DISP_SPI_CS(1);
#endif

	LV_DRV_DELAY_MS(2);
	cmd_buf[0]  = UC1610_SET_COM_END;                  /* set com end value */
	cmd_buf[1]  = UC1610_VER_RES - 1;
	cmd_buf[2]  = UC1610_SET_PANEL_LOADING;
	cmd_buf[3]  = UC1610_SET_LCD_BIAS_RATIO;
	cmd_buf[4]  = UC1610_SET_VBIAS_POT;                /* set contrast */
	cmd_buf[5]  = (UC1610_INIT_CONTRAST * 255) / 100;
#if UC1610_TOP_VIEW
	cmd_buf[6]  = UC1610_SET_MAPPING_CONTROL         | /* top view */
			          UC1610_SET_MAPPING_CONTROL_MY_FLAG |
				        UC1610_SET_MAPPING_CONTROL_MX_FLAG;
#else
	cmd_buf[6]  = UC1610_SET_MAPPING_CONTROL;          /* bottom view */
#endif
	cmd_buf[7]  = UC1610_SET_SCROLL_LINES_LSB | 0;     /* set scroll line on line 0 */
	cmd_buf[8]  = UC1610_SET_SCROLL_LINES_MSB | 0;
	cmd_buf[9]  = UC1610_SET_AC | UC1610_AC_WA_FLAG;   /* set auto increment wrap around */
	cmd_buf[10] = UC1610_SET_INVERSE_DISPLAY  | 1;	   /* invert colors to complies lv color system */
	cmd_buf[11] = UC1610_SET_DISPLAY_ENABLE   | 1;     /* turn display on */

	LV_DRV_DISP_CMD_DATA(UC1610_CMD_MODE);
	LV_DRV_DISP_SPI_CS(0);
	LV_DRV_DISP_SPI_WR_ARRAY(cmd_buf, 12);
	LV_DRV_DISP_SPI_CS(1);
}


void uc1610_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p) {
  /*Return if the area is out the screen*/
  if(x2 < 0) return;
  if(y2 < 0) return;
  if(x1 > UC1610_HOR_RES - 1) return;
  if(y1 > UC1610_VER_RES - 1) return;

  /*Truncate the area to the screen*/
  int32_t act_x1 = x1 < 0 ? 0 : x1;
  int32_t act_y1 = y1 < 0 ? 0 : y1;
  int32_t act_x2 = x2 > UC1610_HOR_RES - 1 ? UC1610_HOR_RES - 1 : x2;
  int32_t act_y2 = y2 > UC1610_VER_RES - 1 ? UC1610_VER_RES - 1 : y2;

  /*Buffer address & size*/
  uint8_t * buf      = (uint8_t *) color_p;
	uint16_t  buf_size = (act_x2 - act_x1 + 1) * (((act_y2 - act_y1) >> 2) + 1);

	/*Set display window to fill*/
	cmd_buf[0] = UC1610_SET_WINDOW_PROGRAM_ENABLE | 0;	/* before changing boundaries */
	cmd_buf[1] = UC1610_SET_WP_STARTING_CA;
	cmd_buf[2] = act_x1;
	cmd_buf[3] = UC1610_SET_WP_ENDING_CA;
	cmd_buf[4] = act_x2;
	cmd_buf[5] = UC1610_SET_WP_STARTING_PA;
	cmd_buf[6] = act_y1 >> 2U;
	cmd_buf[7] = UC1610_SET_WP_ENDING_PA;
	cmd_buf[8] = act_y2 >> 2U;
	cmd_buf[9] = UC1610_SET_WINDOW_PROGRAM_ENABLE | 1;	/* entering window programming */

	LV_DRV_DISP_CMD_DATA(UC1610_CMD_MODE);
	LV_DRV_DISP_SPI_CS(0);
	LV_DRV_DISP_SPI_WR_ARRAY(cmd_buf, 10);
	LV_DRV_DISP_SPI_CS(1);

	/*Flush VDB on display memory*/
	LV_DRV_DISP_CMD_DATA(UC1610_DATA_MODE);
	LV_DRV_DISP_SPI_CS(0);
	LV_DRV_DISP_SPI_WR_ARRAY(buf, buf_size);
	LV_DRV_DISP_SPI_CS(1);

  lv_flush_ready();
}

void uc1610_vdb_wr(uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) {
	(void) opa;

	uint16_t idx = x + buf_w * (y >> 2U);

	buf[idx] &= ~PIXIDX(y, 0x03);                 /* reset pixel color */
	buf[idx] |=  PIXIDX(y, lv_color_to2(color));  /* write new color   */
}

void uc1610_round_cb(lv_area_t *area) {
	/*Round y window to display memory page size*/
	area->y1 = (area->y1 & (~0x03));
	area->y2 = (area->y2 & (~0x03)) + 0x03;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

#endif

it’s a 4 gray color display and i had to add 4 gray related in lv_color.h

#elif LV_COLOR_DEPTH == 2
#define LV_COLOR_SIZE           8
typedef union
{
    uint8_t blue  :2;
    uint8_t green :2;
    uint8_t red   :2;
    uint8_t full  :2;
} lv_color2_t;
#elif LV_COLOR_DEPTH == 2
typedef uint8_t lv_color_int_t;
typedef lv_color2_t lv_color_t;

lv_color_to1

#elif LV_COLOR_DEPTH == 2
    if(color.full & 0x2) return 1;
    else return 0;

lv_color_to2

static inline uint8_t lv_color_to2(lv_color_t color)
{
#if LV_COLOR_DEPTH == 1
    return color.full * 3;
#elif LV_COLOR_DEPTH == 2
    return color.full;
#elif LV_COLOR_DEPTH == 8
    return color.full >> 6;
#elif LV_COLOR_DEPTH == 16
    return color.full >> 14;
#elif LV_COLOR_DEPTH == 32
    return color.full >> 30;
#endif
}

lv_color_to8

#elif LV_COLOR_DEPTH == 2
    return color.full * 0x55;

lv_color_to16

#elif LV_COLOR_DEPTH == 2
    return color.full * 0x5555;

lv_color_to32

#elif LV_COLOR_DEPTH == 2
    return color.full * 0x55555555;

and macro to create colors

#elif LV_COLOR_DEPTH == 2
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){(b8 >> 6 | g8 >> 6 | r8 >> 6)})
#elif LV_COLOR_DEPTH == 2
#define LV_COLOR_MAKE(r8, g8, b8) ((lv_color_t){(r8 >> 6 | g8 >> 6 | b8 >> 6)})

I’m not familiar with colors formats so not sure lv_color_toX are right, do you think it’s ok ?
Do you see something to improve ?

I can contribute these drivers if you think they are right.

Thanks

Hi,

Which buffer do you mean exactly?

When you register your display driver you can set a vdb_wr function. If it’s set then it will be used to write the VDB. It gives you the opportunity to store the pixels in any format you need.
Hence in disp_flsuh, you can simply send the pixel without any conversation.

The other solution is to use LV_COLOR_DEPTH 8 and in disp_flush and convert the buffer to the required format.

hi,
and sorry for a little bit confused questions.

For the buffer, i mean VDB (sharp mip) with lv_conf.h configuration :

#define LV_VDB_SIZE         ((LV_VER_RES / 4) * LV_HOR_RES) /*[pix]*/
#define LV_VDB_PX_BPP       1
#define LV_VDB_ADR          LV_VDB_ADR_INV
#define LV_VDB_DOUBLE       0

This display requires a continuous spi transmission for at least 1 line with header, line address and dummy bytes when updating his ram. The solution i found is to embed these extra bytes in VDB, so the effective size is larger than LV_VDB_SIZE :

static uint8_t sharp_vdb[(LV_VER_RES / 4) * (2 + (LV_HOR_RES / 8)) + 2]; /*size -> [bytes]*/

using lv_vdb_set_adr() to set VDB address in littlevGL.

For vdb_wr() and disp_flush(), it’s ok, writing pixels in VDB and flushing displays as expected.

I’ve tried to tweak lv_color.h to support 4 gray with color depth = 2 and what i’m not sure is the accuracy of these modifications (lv_color_toX).

Enven so, drivers work, sharp with mono theme and uc1610 with a tweaked mono.

Some basic photos :
sharp_mip :


uc1610 :

Unfortunately, VDB is required, however its size should be very small with 1 bpp. It seems to be only 3 kB. If it’s still too much you can further lower LV_VDB_SIZE

You can introduce custom color formats, as you did, but a big part of the library relays in the existing RGB color formats. For example, the converted images supports all 8,16 and 32 color formats. If you add a new one, the images won’t be compatible with them.

Actually why is it required to tweak the images? vdb_wr should do the job, don’t it?

Yes, it’s actually not required to tweak the images and as you recommend, it’s more simple and works with vdb_wr :

void uc1610_vdb_wr(uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) {
	(void) opa;

	uint16_t idx = x + buf_w * (y >> 2);

	/* Convert color to depth 2 */
#if LV_COLOR_DEPTH == 1
	uint8_t color2 = color.full * 3;
#else
	uint8_t color2 = color.full >> (LV_COLOR_DEPTH - 2);
#endif

	buf[idx] &= ~PIXIDX(y, 3);       /* reset pixel color */
	buf[idx] |=  PIXIDX(y, color2);  /* write new color   */
}

Thank you

Nice!

If you want to share the drivers feel free to send Pull requests in lv_drivers repo.

However I recommend waiting for the release of v6.0 because there some minor changes in driver’s API or send the drivers to dev-6.0 branch.

Hello, please see related query about this driver