Problem with animation on SSD1306 monochrome display


Having problems with drawing animations, the object appears on parts of the screen that it shouldn’t.

What MCU/Processor/Board and compiler are you using?

Cypress CY8C5888AXI-LP096 mcu, FreeSoC2 board from Sparkfun. Compiler is ARM GCC 5.4 2016 q2.

What do you want to achieve?

For now I’m trying to make an arc that acts as a loader appear as it should on screen. The code for this example is already given in Littlev documentation.

What have you tried so far?

Everything went well with Hello world example and with drawing simple arc which would mean the drivers for SSD1306 are ok, so I just tried the code for simple bar and preloader afterwards to try some animation and didn’t expect any problems since everything worked nicely.
Also to mention that the screen is initialized in page addressing mode.

Code to reproduce

Here is a snippet of a code from main.c

int main(void)
    CyGlobalIntEnable; /* Enable global interrupts. */

    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    I2C_Oled_Start();   /*  Start the component */
    lv_disp_init(DISPLAY_ADDRESS);  /*  Initialize the screen   */
    static uint8 gbuf[DISP_BUF_SIZE];
    static lv_disp_buf_t disp_buf;
    lv_disp_buf_init( &disp_buf, gbuf, NULL, LV_HOR_RES_MAX*8 );
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init( &disp_drv );
    disp_drv.buffer = &disp_buf;
    disp_drv.flush_cb = flush_cb;
    disp_drv.set_px_cb = set_pix_cb;
    disp_drv.rounder_cb = rounder_cb;
    lv_disp_drv_register( &disp_drv );

    lv_theme_mono_init(0, NULL);
    lv_theme_set_current( lv_theme_get_mono() );

/*Create a style for the Preloader*/
    static lv_style_t style;
    lv_style_copy(&style, &lv_style_plain);
    style.line.width = 1;                         /*10 px thick arc*/
    style.line.color = LV_COLOR_BLACK;       /*Blueish arc color*/

    style.body.border.color = LV_COLOR_WHITE; /*Gray background color*/
    style.body.border.width = 10;
    style.body.padding.left = 0;

    /*Create a Preloader object*/
    lv_obj_t * preload = lv_preload_create(lv_scr_act(), NULL);
    lv_obj_set_size(preload, 50, 50);
    lv_obj_align(preload, NULL, LV_ALIGN_CENTER, 0, 0);
    lv_preload_set_style(preload, LV_PRELOAD_STYLE_MAIN, &style);

If there is a need I’ll post the code of set_pix_cb(), flush_cb() and rounder() functions as well.

Screenshot and/or video


I hope that I described the problem accurately enough, and I apologize in advance if I left out something that might be important.

It looks like a bug within your display driver. Please post the code for the three functions you mentioned.

Hi @embeddedt, thanks for answering
Here are the functions

 #define BIT_SET(x,y)    ((x) |= (1u<<(y)))          /*  Turn the pixel on   */
#define BIT_CLEAR(x,y)  ((x) &= ~(1u<<(y)))         /*  Turn the pixel off  */

/*  Function for turnng the pixel on or off */
void set_pix_cb( struct _disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa )
    uint16 byte_index = (( y/8 ) * buf_w) + x;
    uint8  bit_index = y & 0x07;
    // == 0 inverts, so we get blue on black
   switch (color.full)

    case 1:
    BIT_CLEAR( buf[byte_index], bit_index );
    case 0:
    BIT_SET( buf[byte_index], bit_index );

/*  Function for flushing the content from the buffer on the screen */    
void flush_cb( lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p )
    uint8 row1 = area -> y1 >> 3;
    uint8 row2 = area-> y2 >> 3;
    uint8 * buf = (uint8 *) color_p;
  for(uint8 row =row1; row <= row2; row++)
      /*    Function that moves the pointer to page address(row) and column address goes in this for loop */
    I2C_Oled_MasterSendStart(i2caddr, 0);
    I2C_Oled_MasterWriteByte(0x00); /* Send command byte */
    oledStartSend( row, area ->x1);     /*  That's the function    */
    I2C_Oled_MasterSendStart(i2caddr, 0);
    I2C_Oled_MasterWriteByte(0x40); /* Send data byte(s) */
    for(uint16 x = area -> x1; x <= area -> x2; x++)
    {   /*  Sending row lines  */
/*  Inform the graphics library that you are ready with the flushing    */

  /* Round the area that needs to be refreshed/flushed considering vertical pixel alignment */
void rounder_cb(lv_disp_drv_t * disp_driv, lv_area_t * area )
    area -> y1 = (area -> y1 & (~0x07));
    area -> y2 = (area -> y2 & (~0x07)) + 7;

static void oledStartSend( uint8 row, uint8 col)
    I2C_Oled_MasterWriteByte( 0xB0 | row);
    I2C_Oled_MasterWriteByte( 0x00 | ((col >> 4) & 0xF) );

I also included static void oledStartSend( uint8 row, uint8 col) function, and forgot to mention that the screen uses I2C interface for communication with mcu.

It looked good at first look, but probably there is minor typo in it.
I suggest checking you driver without lvgl first. Try to

  • set on pixel
  • draw a rectangle

Hi @kisvegabor,
I have already tried drivers(so to speak) for setting pixel on and off and drawing lines and rectangles using Adafruit’s library for SDS1306.
Function for setpx is nearly identical as the one that is used here, and functions for drawing lines and rectangles are written so they serve great for testing if driver is written correctly.

Thing that was different is that SSD1306 was initialized in horizontal addressing mode, and after every pixel draw, or line or rectangle drawing, screen would be erased, and if anything would be drawn after, it would be done as if the drawing was done on the empty screen.
When I used drivers for Hello world example, or to draw anything that stays on the screen, then everything worked fine with lvgl.
Problem started happening when I tried to use animations from lvgl and my guess is that it has something to do with page addressing mode where I’m probably not setting the page and column addresses properly(which I am realizing while typing this post).

I’ll try to write static void oledStartSend( uint8 row, uint8 col) function again, and maybe that will solve my problem.
Thanks for the patience :slightly_smiling_face:

I suggest trying something very simple:

    lv_obj_t * label = lv_lable_create(lv_scr_act(), NULL);
    lv_label_set_text(label, "a");

    lv_obj_set_pos(label, 20, 0);
    lv_obj_set_pos(label, 0, 20);
    lv_obj_set_pos(label, 20, 20);

I have tried what was suggested and everything worked as it should. That is, all three examples. But when I program it with animation(preloader example), and after that I program it with one of this examples with screen having the power all the time, the screen gets stuck with some leftover image after which I have to unplug the board because reset of mcu doesn’t do the job of resetting the screen controller, and plug it back to get the wanted example displayed.

It’s strange that your display gets stuck. It can be sign of a problem.

Anyway, what do experience with this label scrolling animation?

    lv_obj_t * label = lv_label_create(lv_scr_act(), NULL);
    lv_label_set_long_mode(label, LV_LABEL_LONG_SROLL_CIRC);
    lv_obj_set_width(label, 40);
    lv_label_set_text(label, "A long text to scroll!");

    //Try with and without alignment.
    lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, 0);  

I have found the problem. It was my bad writing of static void oledStartSend( uint8 row, uint8 col) function where setting the lower and higher column start address was done wrong.

Here is the corrected version of the function

static void oledStartSend( uint8 row, uint8 col)
    I2C_Oled_MasterWriteByte( 0xB0 | row );
    I2C_Oled_MasterWriteByte( 0x00 | (col & 0xF) );         /*  Column lower nibble  */
    I2C_Oled_MasterWriteByte( 0x10 | ((col >> 4) & 0xF) );  /*  Column higher nibble  */

Now everything works nicely


This was probably because the address pointers values were incorrect, and those values were saved and not reset because the module was still powered on while the mcu was programmed resulting with mcu not writing into display data RAM and module was displaying the image that was left from previous program.

Thanks for help :smiley:

Glad to hear that you’ve found the issue!