Lvgl9.3 + ssd1306 0.96" oled + mcu

Description

I try to port LVGL9.3 to my AT32F403AVGT7 development board which has an 0.96’’ OLED display screen and the screen has a SSD1306 driver IC.

But i have not achieved success until now. I post two xxx.c files: lv_port_disp_template.c and ssd1306.c. I want to know whether this function(static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)) is correct or not.

Code to reproduce

//lv_port_disp_template.c
#ifndef MY_DISP_HOR_RES
    #define MY_DISP_HOR_RES    128
#endif

#ifndef MY_DISP_VER_RES
    #define MY_DISP_VER_RES    64
#endif

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();

    /*------------------------------------
     * Create a display and set a flush_cb
     * -----------------------------------*/
    lv_display_t * disp = lv_display_create(MY_DISP_HOR_RES, MY_DISP_VER_RES);
    lv_display_set_color_format(disp, LV_COLOR_FORMAT_I1);
    lv_display_set_flush_cb(disp, disp_flush);
                                           
    LV_ATTRIBUTE_MEM_ALIGN
    static uint8_t buf_1[MY_DISP_HOR_RES * MY_DISP_VER_RES / 8 + 8];
    lv_display_set_buffers(disp, buf_1, NULL, sizeof(buf_1), LV_DISPLAY_RENDER_MODE_FULL);

}

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

/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
    ssd1306_init();
}

static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)
{
    px_map += 8;

    lv_draw_sw_i1_convert_to_vtiled(
        px_map,                                                 
        (MY_DISP_HOR_RES * MY_DISP_VER_RES) / 8,                
        MY_DISP_HOR_RES,                                         
        MY_DISP_VER_RES,                                        
        ssd1306_buffer,                                        
        sizeof(ssd1306_buffer),                                 
        false                                                  
    );

    ssd1306_flush();
    
    lv_display_flush_ready(disp_drv);   
}



//ssd1306.c
uint8_t ssd1306_buffer[SSD1306_WIDTH * SSD1306_HEIGHT / 8] = {0};

static void send_cmd(uint8_t code) {
    // Function to send command to SSD1306
    // This function should be implemented based on your specific hardware interface
    
    i2c_start_generate(I2C1);
    while(i2c_flag_get(I2C1,I2C_STARTF_FLAG)!= SET);
    
    i2c_7bit_address_send(I2C1,SSD1306_IIC_ADDR,I2C_DIRECTION_TRANSMIT);
    while(i2c_flag_get(I2C1,I2C_ADDR7F_FLAG)!= SET);
    i2c_flag_clear(I2C1,I2C_ADDR7F_FLAG);
    
    i2c_data_send(I2C1,0x00);
    i2c_data_send(I2C1,code);
    while(i2c_flag_get(I2C1,I2C_TDC_FLAG)!= SET);
    
    i2c_stop_generate(I2C1);
     
}

static void send_data(uint8_t data) {
    // Function to send data to SSD1306
    // This function should be implemented based on your specific hardware interface
    
    i2c_start_generate(I2C1);
    while(i2c_flag_get(I2C1,I2C_STARTF_FLAG)!= SET);
    
    i2c_7bit_address_send(I2C1,SSD1306_IIC_ADDR,I2C_DIRECTION_TRANSMIT);
    while(i2c_flag_get(I2C1,I2C_ADDR7F_FLAG)!= SET);
    i2c_flag_clear(I2C1,I2C_ADDR7F_FLAG);
    
    i2c_data_send(I2C1,0x40);
    i2c_data_send(I2C1,data);
    while(i2c_flag_get(I2C1,I2C_TDC_FLAG)!= SET);
    
    i2c_stop_generate(I2C1);

}

void ssd1306_flush(void) {
    // Send the display buffer to the SSD1306
    for (uint8_t page = 0; page < 8; page++) {
        send_cmd(0xB0 + page); // Set page address
        send_cmd(0x00); // Set lower column address
        send_cmd(0x10); // Set higher column address

        for (uint16_t col = 0; col < SSD1306_WIDTH; col++) {
            send_data(ssd1306_buffer[col + (page * SSD1306_WIDTH)]);
        }
    }
}


void ssd1306_clear(void) {
    // Clear the display buffer
    for (uint16_t i = 0; i < sizeof(ssd1306_buffer); i++) {
        ssd1306_buffer[i] = 0x00;
    }
}

void ssd1306_draw_pixel(uint8_t x, uint8_t y, uint8_t color) {
    if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) {
        return; // Out of bounds
    }
    
    if (color) {
        ssd1306_buffer[x + (y / 8) * SSD1306_WIDTH] |= (1 << (y % 8));
    } else {
        ssd1306_buffer[x + (y / 8) * SSD1306_WIDTH] &= ~(1 << (y % 8));
    }
}

void ssd1306_init(void) {
    // Initialize the SSD1306 display
    send_cmd(0xAE); // Display off
    send_cmd(0xD5); // Set display clock divide ratio/oscillator frequency
    send_cmd(0x80); // Set divide ratio
    send_cmd(0xA8); // Set multiplex ratio
    send_cmd(0x3F); // 1/64 duty
    send_cmd(0xD3); // Set display offset
    send_cmd(0x00); // No offset
    send_cmd(0x40); // Set start line to 0
    send_cmd(0x8D); // Charge pump setting
    send_cmd(0x14); // Enable charge pump
    send_cmd(0x20); // Memory addressing mode
    send_cmd(0x02); // Page addressing mode
    send_cmd(0xA1); // Segment remap (column address 127 is mapped to SEG0)
    send_cmd(0xC8); // COM output scan direction (remap mode)
    send_cmd(0xDA); // Set COM pins hardware configuration
    send_cmd(0x12);
    send_cmd(0x81); // Contrast control
    send_cmd(0xCF);
    send_cmd(0xD9); // Pre-charge period
    send_cmd(0xF1);
    send_cmd(0xDB); // VCOMH deselect level
    send_cmd(0x30);
    send_cmd(0xA4); // Entire display ON (resume to RAM content display)
    send_cmd(0xA6); // Normal display (not inverted)
    send_cmd(0xAF); // Display ON

    ssd1306_clear();
    ssd1306_flush();
}

Screenshot and/or video

Why not debug your code ??? Is your OLED unreset etc ?

Thank you for your reply. I have successed to port lvgl to my board. I show my porting code below because i wish it will be helpful if someone uses an OLED screen drived by SSD1306.

The correct and critical snippets at the lv_port_disp_template.c file:

#ifndef MY_DISP_HOR_RES
    #define MY_DISP_HOR_RES    128
#endif

#ifndef MY_DISP_VER_RES
    #define MY_DISP_VER_RES    64
#endif
void lv_port_disp_init(void)
{
    LV_ATTRIBUTE_MEM_ALIGN static uint8_t buf_1[MY_DISP_HOR_RES * MY_DISP_VER_RES / 8 + 8];
   
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();

    /*------------------------------------
     * Create a display and set a flush_cb
     * -----------------------------------*/
    lv_display_t * disp = lv_display_create(MY_DISP_HOR_RES, MY_DISP_VER_RES);
    lv_display_set_flush_cb(disp, disp_flush);                                
    lv_display_set_buffers(disp, buf_1, NULL, sizeof(buf_1), LV_DISPLAY_RENDER_MODE_FULL);
}
static void disp_init(void)
{
    ssd1306_init();
}
static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)
{
    uint16_t i,j = 0;

    px_map += 8;

    // //print the content of px_map
    // printf("转换前的px_map\n");
    // for(i = 0; i < MY_DISP_HOR_RES * MY_DISP_VER_RES / 8; i++) {
    //     for(j = 0; j < 8; j++) {
    //         if(px_map[i] & (1 << (7-j))) {
    //             printf("1");
    //         } else {
    //             printf("0");
    //         }
    //     }
    // }

    for(i = 0; i <  MY_DISP_HOR_RES * MY_DISP_VER_RES / 8; i++) {
        for(j = 0; j < 8; j++) {
            if(px_map[i] & (1 << (7-j))) {
                ssd1306_draw_pixel((i * 8 + j) % MY_DISP_HOR_RES, (i * 8 + j) / MY_DISP_HOR_RES, true);
            } else {
                ssd1306_draw_pixel((i * 8 + j) % MY_DISP_HOR_RES, (i * 8 + j) / MY_DISP_HOR_RES, false);
            }
        }
        
    }
        
    ssd1306_flush();
    
    lv_display_flush_ready(disp_drv);   
}

Relevent code snippets at my SSD1306 driver file oled_096_ssd1306.c:

uint8_t ssd1306_buffer[SSD1306_WIDTH * SSD1306_HEIGHT / 8] = {0};
void ssd1306_draw_pixel(uint8_t x, uint8_t y, uint8_t color) {
    if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) {
        return; // Out of bounds
    }
    
    if (color) {
        ssd1306_buffer[x + (y / 8) * SSD1306_WIDTH] |= (1 << (y % 8));
    } else {
        ssd1306_buffer[x + (y / 8) * SSD1306_WIDTH] &= ~(1 << (y % 8));
    }
}
void ssd1306_flush(void) {
    // Send the display buffer to the SSD1306
    for (uint8_t page = 0; page < 8; page++) {
        send_cmd(0xB0 + page); // Set page address
        send_cmd(0x00); // Set lower column address
        send_cmd(0x10); // Set higher column address

        for (uint16_t col = 0; col < SSD1306_WIDTH; col++) {
            send_data(ssd1306_buffer[col + (page * SSD1306_WIDTH)]);
        }
    }
}
void ssd1306_init(void) {
    //Because of space limitation, i didn't show it. It just send some initiatial instructions
}

My GUI code, just for test:

void my_gui_ui(void)
{
    // 1. 创建新屏幕对象
    lv_obj_t * screen = lv_obj_create(NULL);
    
    // 2. 设置屏幕为全屏(移除默认边距)
    lv_obj_set_size(screen, 128, 64);
    lv_obj_set_style_pad_all(screen, 0, 0);
    lv_obj_set_style_border_width(screen, 0, 0);
    
    // 3. 设置单色屏专用样式
    lv_obj_set_style_bg_color(screen, lv_color_black(), 0);
    
    // 4. 创建标签
    lv_obj_t * label = lv_label_create(screen);
    lv_label_set_text(label, LV_SYMBOL_OK);
    
    // 5. 设置标签样式(高对比度)
    lv_obj_set_style_text_color(label, lv_color_white(), 0);
    lv_obj_set_style_text_font(label, &lv_font_montserrat_16, 0);
    
    // 6. 居中标签
    lv_obj_center(label);
    
    // 7. 加载屏幕
    lv_scr_load(screen);
    
}

The operation phenomenon is as follows:

Note:

1.The lv_draw_sw_i1_convert_to_vtiled() function may not suitable for SSD1306 OLED. So i wrote code that transit data pointed by px_map to a suitable format at fluch_cb(), because the data format for SSD1306 is a column-wise (vtiled) buffer layout.

2.This code in flush_cb() can print the content of px_map because i found some errors(May be) at the Monochrome Displays part of LVGL9.3 documentation, of course, you must have your own printf function:

    // //print the content of px_map
    // printf("转换前的px_map\n");
    // for(i = 0; i < MY_DISP_HOR_RES * MY_DISP_VER_RES / 8; i++) {
    //     for(j = 0; j < 8; j++) {
    //         if(px_map[i] & (1 << (7-j))) {
    //             printf("1");
    //         } else {
    //             printf("0");
    //         }
    //     }
    // }