Problem with SSD1331 SPI OLED module


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

I’m using FreeSoC2 development board that has CY8C5888AXI-LP096 with ARM GCC 5.4-2016-q2 compiler

What do you experience?

The code first stops at lv_task.c 135 line (lv_task_handler address: 0x000022EE), after which it goes to Cm3Start.c line 98 (IntDefaultHandler address: 0x000006D6) where is a definition of a function that goes into an infinite loop when something goes wrong. I do have to mention that when I try the same code with SSD1306 that uses I2C protocol, everything works without problems.
EDIT: Failed to mention that this is with version 6.1, and that it manages to display hello world example on display with version 5.3

What do you expect?

The same behavior as with SSD1306, that is, to display the same picture/animation, only in color of course.

Code to reproduce

I don’t know how much of the code will be of use without the whole project because I don’t know what part of code is a suspect for such behavior, and also MCU uses internal logic blocks for SPI communication that does work fine when it’s used to send commands or data for screen filling.

I can archive and upload the project for more detailed insight, but to view it, PSoC Creator is needed.

Here is the main.c file, and if needed I’ll post more code. flush_cb function is not posted since it is not reached at all.

#include "project.h"
#include "lvgl.h"
#include "lv_conf.h"
#include "ssd1306.h"
#include "SSD1331.h"
//#include "lv_tutorial_hello_world.h"
#include <stdio.h>
#define DISP_BUF_SIZE   (128*64/8)
#define SSD1331_BUFF    (96*64)
#define DISPLAY_ADDRESS 0x3C // ‭0‬+SA0+RW - 0x3C or 0x3D


void Littlev_SysTick_init(void);
void Timer_Routine(void);
static void arc_loader(lv_task_t * t);

int main(void)
    CyGlobalIntEnable; /* Enable global interrupts. */
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    I2C_Oled_Start();   /*  Start the component */
      /*  Initialize the screen   */
//    lv_ssd1306_init(DISPLAY_ADDRESS);
//    static uint8 gbuf[DISP_BUF_SIZE];   /*  SSD1306 version */
    static uint8 gbuf[SSD1331_BUFF];    /*  SSD1331 version */
    static lv_disp_buf_t disp_buf;
//    lv_disp_buf_init( &disp_buf, gbuf, NULL, LV_HOR_RES_MAX*64/8 ); /*  SSD1306 version */
    lv_disp_buf_init( &disp_buf, gbuf, NULL, LV_HOR_RES_MAX*64 );   /*  SSD1331 version */
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init( &disp_drv );
    disp_drv.buffer = &disp_buf;
//    disp_drv.flush_cb = ssd1306_flush_cb;  
    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() );
    /*  Run the example */
//    lv_tutorial_hello_world();
    /*  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, 100);
//    lv_label_set_text(label, "Long text scrolling around");
//    //Try with and without alignment.
//    lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, 0); 
    /*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);

//    /*  Create a play symbol    */
//    lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);
//    lv_label_set_text(label1, LV_SYMBOL_PLAY);
//    lv_obj_align(label1, NULL, LV_ALIGN_IN_RIGHT_MID, 0, 0);
//    /*Create style for the Arcs*/
//  static lv_style_t style;
//  lv_style_copy(&style, &lv_style_plain);
//  style.line.color = LV_COLOR_BLACK;           /*Arc color*/
//  style.line.width = 1;                       /*Arc width*/
//  /*Create an Arc*/
//  lv_obj_t * arc = lv_arc_create(lv_scr_act(), NULL);
//  lv_arc_set_style(arc, LV_ARC_STYLE_MAIN, &style);          /*Use the new style*/
//  lv_arc_set_angles(arc, 0, 360);
//  lv_obj_set_size(arc, 64, 64);
//  lv_obj_align(arc, NULL, LV_ALIGN_CENTER, 0, 0);
//   /*Create style for the Arcs*/
//  static lv_style_t style;
//  lv_style_copy(&style, &lv_style_plain);
//  style.line.color = LV_COLOR_BLACK;           /*Arc color*/
//  style.line.width = 1;                       /*Arc width*/
//  /*Create an Arc*/
//  lv_obj_t * arc = lv_arc_create(lv_scr_act(), NULL);
//  lv_arc_set_angles(arc, 180, 180);
//  lv_arc_set_style(arc, LV_ARC_STYLE_MAIN, &style);
//  lv_obj_set_size(arc, 50, 50);
//  lv_obj_align(arc, NULL, LV_ALIGN_CENTER, 0, 0);
//  /* Create an `lv_task` to update the arc.
//   * Store the `arc` in the user data*/
//  lv_task_create(arc_loader, 20, LV_TASK_PRIO_LOWEST, arc);
//    /* Basic animation tear testing */
//    lv_obj_t * label = lv_label_create(lv_scr_act(), NULL);
//    lv_label_set_text(label, "Ljuba homos");

//    lv_obj_set_pos(label, 20, 0);
//    CyDelay(300);
//    lv_obj_set_pos(label, 0, 20);
//    CyDelay(300);
//    lv_obj_set_pos(label, 0, 22);
//    lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, 0);
}       /*  End of Main function    */

    void Timer_Routine(void)
void Littlev_SysTick_init(void)
    uint8 i ;
// Configure the SysTick timer to generate interrupt every 1 ms
// and start its operation.
 // Find unused callback slot
    for (i = 0; i < CY_SYS_SYST_NUM_OF_CALLBACKS; i++)
        if (CySysTickGetCallback(i) == NULL)
            CySysTickSetCallback(i, Timer_Routine);

static void arc_loader(lv_task_t * t)
    static int16_t a = 0;

    if(a >= 359) a = 359;

    if(a < 180) lv_arc_set_angles(t->user_data, 180-a ,180);
    else lv_arc_set_angles(t->user_data, 540-a ,180);

    if(a == 359) {

Screenshot and/or video

Here are some screenshots from debugger.
Up until this point everything works

and after that this is what happens

Also, if I’m missing the point or subforum please delete or move the topic


So you cannot step into lv_task_exec? Can you step instruction by instruction when it reaches that line and find exactly where the fault happens?

I can step into lv_task_exec and can go through steps until lv_refr.c line 397 which is

/*Do the refreshing from the top object*/
lv_refr_obj_and_children(top_p, &start_mask);

When I try to do the step over the loop happens. I did the step in and went without problems with step over until lv_refr.c line 553. At this line I couldn’t do step in, so I went in disassembly and there I could go five more steps until the line 0x0000B7F0 blx r3 after it definitely goes into infinite loop and I can’t do anything but reset.

Here is the screenshot of disassembly

Looks like obj->design_cb is corrupt (0xFFFFFFFF) when it gets called inside lv_refr_obj. What is the value of r3 before the ldr r3, [r3, #20] instruction runs?

EDIT: Nevermind. That information is already in the screenshot. obj is 0x1FFFA2DC (obviously an invalid number).

Can you trace where the value of obj comes from?

As I can see value of obj comes from top_p on line 459 of lv_refr.c file but here is the screen shot of the moment when

static void lv_refr_obj(lv_obj_t * obj, const lv_area_t * mask_ori_p)

is stepped in

Everything seems to be ok at this point, but when I step over line 517 this is what happens

After stepping in that line I determined that obj gets those values after exiting

lv_opa_t lv_obj_get_opa_scale(const lv_obj_t * obj)

and returning to

static bool lv_obj_design(lv_obj_t * obj, const lv_area_t * mask_p, lv_design_mode_t mode)

EDIT: I think I have found where obj gets it’s values.
It’s in for loop of

static void sw_color_fill(lv_color_t * mem, lv_coord_t mem_width, const lv_area_t * fill_area, lv_color_t color,
                          lv_opa_t opa)
    /*Set all row in vdb to the given color*/
    lv_coord_t row;
    lv_coord_t col;

    lv_disp_t * disp = lv_refr_get_disp_refreshing();
    if(disp->driver.set_px_cb) {
        for(col = fill_area->x1; col <= fill_area->x2; col++) {
            for(row = fill_area->y1; row <= fill_area->y2; row++) {
                disp->driver.set_px_cb(&disp->driver, (uint8_t *)mem, mem_width, col, row, color, opa);
    } else {
        mem += fill_area->y1 * mem_width; /*Go to the first row*/

        /*Run simpler function without opacity*/
        if(opa == LV_OPA_COVER) {

            /*Fill the first row with 'color'*/
            for(col = fill_area->x1; col <= fill_area->x2; col++) {
                mem[col] = color;

            /*Copy the first row to all other rows*/
            lv_color_t * mem_first = &mem[fill_area->x1];
            lv_coord_t copy_size   = (fill_area->x2 - fill_area->x1 + 1) * sizeof(lv_color_t);
            mem += mem_width;

            for(row = fill_area->y1 + 1; row <= fill_area->y2; row++) {
                memcpy(&mem[fill_area->x1], mem_first, copy_size);
                mem += mem_width;
            } //  <-  This is the for loop after which obj gets changed

and it happens when row hits 40 for some reason. Before that it doesn’t change anything. After this I don’t know where to look anymore

Looks like it’s probably a memory corruption issue.

    static uint8 gbuf[SSD1331_BUFF];    /*  SSD1331 version */
    static lv_disp_buf_t disp_buf;
    lv_disp_buf_init( &disp_buf, gbuf, NULL, LV_HOR_RES_MAX*64 );  

This is your code from the first post. I see a couple of issues here which are probably causing the issue you’re seeing.

  1. gbuf should be an lv_color_t array, not a uint8_t array.
  2. The size passed to lv_disp_buf_init should be the same as the size within the braces ([]), because the function expects the number of lv_color_t entries to be passed to it. So you should pass SSD1331_BUFF to lv_disp_buf_init or change the array to use LV_HOR_RES_MAX*64.

This one solved the problem and now everything works nicely.

And about

SSD1331_BUFF and LV_HOR_RES_MAX*64 are actually the same. I used LV_HOR_RES_MAX*64 as it was easier to divide by 8 when using it with SSD1306. Sorry about confusion.

Big thanks for helping out @embeddedt

EDIT: If someone is interested here is the archived project for PSoC Creator LittlevGL(7v0) (1.2 MB)


Hi @ssmc,

Thanks for making your project available, I’m working with the PSoC5LP devkit and a dispay based on the IL9341 controller, it was almost two years since I developed with PSoC and forgot some things that your project remembered me.
I was using a Timer dedicated block to generate the periodic interrupt for lvgl tick but your approach using the SysTick is a lot better because it saves me a Timer block.
Also I forgot editing the value of LV_MEM_SIZE, it must be the same value as the System Stack size.

I will upload my project once it’s working.