【联盛德W803-Pico 试用】LVGL驱动SSD1306OLED 屏幕

发布于 2025-04-01 00:20:38

1. 硬件介绍

1.1 W803-Pico

w803_pico.png
W803-Pico 主控为联盛德 W803 芯片,支持无线 WiFi (IEEE802.11 b/g/n 协议)、蓝牙 BT/BLE4.2 协议。芯片内置高性能 32 位处理器,主频达 240MHz。
内置 2MB Flash 以及 288KB RAM。

MCU 特性

集成 32 位 XT804 处理器,工作频率 240MHz,内置 DSP、浮点运算单元与安全引擎
内置 2MB Flash,288KB RAM
集成 PSRAM 接口,支持最高 64M bit 外置 PSRAM 存储器
集成 10 路 Touch Sensor 触控接口
集成 5 路 UART 高速接口
集成 2 路 12 比特 ADC,最高采样率 1KHz
集成 1 个高速 从 SPI 接口,支持最高 50MHz
集成 1 个 SDIO_HOST 接口,支持 SDIO2.0、SDHC、MMC4.2
集成 1 个 SDIO_DEVICE,支持 SDIO2.0,最高工作频率 200Mbps
集成 1 个 I2C 控制器
集成 GPIO 控制器,最多支持 20 个 GPIO
集成 5 路 PWM 接口
集成 1 路 Duplex I2S 控制器

1.2 SSD1306 OLED 0.96屏幕

OLED.png

  • 分辨率:128x64 像素
  • 显示颜色:单色(白色/蓝色)
  • 接口类型:I2C/SPI(本教程使用I2C接口)
  • 工作电压:3.3V-5V

管脚定义

管脚名称功能说明
VCC3.3V电源输入
GND地线
SCLI2C时钟线
SDAI2C数据线

2. 硬件连接

电路连接.png
W803-Pico ↔ SSD1306接线表

W803-Pico 引脚SSD1306 引脚
3.3VVCC
GNDGND
PA1SCL
PA4SDA

3. 工程配置

打开vscode,打开wm_iot_sdk文件夹,wm_iot_sdk2.x下载地址

工程的搭建和环境配置请参照官网介绍安装测试 W803-Pico 入门指南

3.1 创建工程副本

在VSCODE内终端输入:
cp .\examples\peripheral\i2c -r .\examples\peripheral\i2c_lvgl_ssd1306oled

3.2 menuconfig配置

SDK menuconfig配置
SDK配置.png

4. 驱动代码实现

4.1 SSD1306初始化

void SSD1306_init(void)
{
    m_font_offset = 2;
    
    // 初始化I2C设备
    ssd1306_i2c_device = wm_drv_i2c_init(I2C_CONTROLLER_NAME);
    if (ssd1306_i2c_device == NULL) {
        printf("SSD1306 I2C init error\r\n");
        return;
    }
    
    // 配置I2C设备
    ssd1306_i2c_config.addr = SSD1306_I2C_ADDR;
    ssd1306_i2c_config.speed_hz = SSD1306_I2C_SPEED;
 
    SSD1306_sendCommand(0xAE);            // display off
    SSD1306_sendCommand(0xA6);            // Set Normal Display (default)
    SSD1306_sendCommand(0xAE);            // DISPLAYOFF
    SSD1306_sendCommand(0xD5);            // SETDISPLAYCLOCKDIV
    SSD1306_sendCommand(0x80);            // the suggested ratio 0x80
    SSD1306_sendCommand(0xA8);            // SSD1306_SETMULTIPLEX
    SSD1306_sendCommand(0x3F);
    SSD1306_sendCommand(0xD3);            // SETDISPLAYOFFSET
    SSD1306_sendCommand(0x0);             // no offset
    SSD1306_sendCommand(0x40|0x0);        // SETSTARTLINE
    SSD1306_sendCommand(0x8D);            // CHARGEPUMP
    SSD1306_sendCommand(0x14);
    SSD1306_sendCommand(0x20);            // MEMORYMODE
    SSD1306_sendCommand(0x00);            // 0x0 act like ks0108
    SSD1306_sendCommand(0xA1);            // SEGREMAP   Mirror screen horizontally (A0)
    SSD1306_sendCommand(0xC8);            // COMSCANDEC Rotate screen vertically (C0)
    SSD1306_sendCommand(0xDA);            // 0xDA
    SSD1306_sendCommand(0x12);            // COMSCANDEC
    SSD1306_sendCommand(0x81);            // SETCONTRAST
    SSD1306_sendCommand(0xCF);            //
    SSD1306_sendCommand(0xd9);            // SETPRECHARGE 
    SSD1306_sendCommand(0xF1); 
    SSD1306_sendCommand(0xDB);            // SETVCOMDETECT                
    SSD1306_sendCommand(0x40);
    SSD1306_sendCommand(0xA4);            // DISPLAYALLON_RESUME        
    SSD1306_sendCommand(0xA6);            // NORMALDISPLAY             
    SSD1306_clearDisplay();
    SSD1306_sendCommand(0x2E);            // Stop scroll
    SSD1306_sendCommand(0x20);            // Set Memory Addressing Mode
    SSD1306_sendCommand(0x00);            // Set Memory Addressing Mode ab Horizontal addressing mode
    SSD1306_setFont(font8x8);
}

SSD1306 OLED其他接口

void SSD1306_sendCommand(unsigned char command)
{
    uint8_t sub_addr = SSD1306_Command_Mode;
    
    if (ssd1306_i2c_device == NULL) {
        printf("SSD1306 I2C device not initialized\r\n");
        return;
    }
    
    wm_drv_i2c_write(ssd1306_i2c_device, &ssd1306_i2c_config, &sub_addr, 1, &command, 1);
}
 
void SSD1306_setBrightness(unsigned char Brightness)
{
   SSD1306_sendCommand(SSD1306_Set_Brightness_Cmd);
   SSD1306_sendCommand(Brightness);
}
 
 
void SSD1306_setHorizontalMode()
{
    addressingMode = HORIZONTAL_MODE;
    SSD1306_sendCommand(0x20);                      // set addressing mode
    SSD1306_sendCommand(0x00);                      // set horizontal addressing mode
}
 
void SSD1306_clearDisplay()
{
    unsigned char i, j;
    SSD1306_sendCommand(SSD1306_Display_Off_Cmd);     // display off
    for(j=0; j<8; j++)
    {    
        SSD1306_setTextXY(j, 0);    
        {
            for(i=0; i<16; i++)  // clear all columns
            {
                SSD1306_putChar(' ');    
            }
        }
    }
    SSD1306_sendCommand(SSD1306_Display_On_Cmd);     // display on
    SSD1306_setTextXY(0, 0);    
}
 
void SSD1306_sendData(unsigned char Data)
{
    uint8_t sub_addr = SSD1306_Data_Mode;
    
    if (ssd1306_i2c_device == NULL) {
        printf("SSD1306 I2C device not initialized\r\n");
        return;
    }
    
    wm_drv_i2c_write(ssd1306_i2c_device, &ssd1306_i2c_config, &sub_addr, 1, &Data, 1);
}

4.2 LVGL适配修改

修改wm_lv_port_disp.c

#include "wm_lv_port_disp.h"
#include <stdbool.h>
 
#include "wm_error.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
 
#define LOG_TAG "lvgl_port_oled"
#include "wm_log.h"
 
/*********************
 *      DEFINES
 *********************/
#ifdef MY_DISP_HOR_RES
#undef MY_DISP_HOR_RES
#endif
#ifdef MY_DISP_VER_RES
#undef MY_DISP_VER_RES
#endif
#define MY_DISP_HOR_RES 128   // SSD1306 OLED水平分辨率
#define MY_DISP_VER_RES 64    // SSD1306 OLED垂直分辨率
 
/**********************
 *      TYPEDEFS
 **********************/
typedef struct {
    SemaphoreHandle_t lvgl_sem; /*semaphore to control the timing of next buffer filling*/
} wm_lvgl_ctx_t;
 
/**********************
 *  STATIC PROTOTYPES
 **********************/
static void disp_init(void);
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
 
/**********************
 *  STATIC VARIABLES
 **********************/
static wm_lvgl_ctx_t wm_lvgl_ctx = { 0 };
 
#define LVGL_PORT_BUFF_SIZE (MY_DISP_HOR_RES * MY_DISP_VER_RES) // 1/8 screen resolution
static lv_color_t lvgl_draw_buff1[LVGL_PORT_BUFF_SIZE];
static lv_color_t lvgl_draw_buff2[LVGL_PORT_BUFF_SIZE];
 
/**********************
 *   GLOBAL FUNCTIONS
 **********************/
void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();
    printf("CONFIG_LV_COLOR_DEPTH = %d\n",CONFIG_LV_COLOR_DEPTH);
    /*-----------------------------
     * Create a buffer for drawing
     *----------------------------*/
    static lv_disp_draw_buf_t draw_buf_dsc;
    lv_disp_draw_buf_init(&draw_buf_dsc, lvgl_draw_buff1, lvgl_draw_buff2,
                          LVGL_PORT_BUFF_SIZE); /*Initialize the display buffer*/
 
    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/
    static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);   /*Basic initialization*/
 
    /*Set the resolution of the display*/
    disp_drv.hor_res = MY_DISP_HOR_RES;
    disp_drv.ver_res = MY_DISP_VER_RES;
 
    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;
    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc;
    disp_drv.full_refresh = 1;
    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
        
    wm_log_info("LVGL with SSD1306 OLED display initialized");
}
/**********************
 *   STATIC FUNCTIONS
 **********************/
extern void SSD1306_sendCommand(unsigned char command);
extern void SSD1306_setHorizontalMode();
extern void SSD1306_sendData(unsigned char Data);
/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
    // 创建LVGL使用的信号量
    wm_lvgl_ctx.lvgl_sem = xSemaphoreCreateCounting(1, 0);
    wm_log_info("SSD1306 OLED display initialized");
}
volatile bool disp_flush_enabled = true;
/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_enable_update(void)
{
    disp_flush_enabled = true;
}
/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_disable_update(void)
{
    disp_flush_enabled = false;
}
/*Flush the content of the internal buffer the specific area on the display
 *You can use DMA or any hardware acceleration to do this operation in the background but
 *'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
    // 如果禁用了显示更新,直接返回
    if (!disp_flush_enabled) {
        lv_disp_flush_ready(disp_drv);
        return;
    }
    
    // 计算要更新的区域的宽度和高度
    uint16_t width = area->x2 - area->x1 + 1;
    uint16_t height = area->y2 - area->y1 + 1;
    
    // 由于SSD1306是单色显示屏,我们需要将LVGL的彩色数据转换为单色
    // 这里假设lv_color_t是16位色彩
    uint8_t page_start = area->y1 / 8;
    uint8_t page_end = (area->y2 + 7) / 8;
    
    // 设置水平模式更容易按区域更新
    SSD1306_setHorizontalMode();
    
    // 遍历每一页(每页8个像素行)
    for (uint8_t page = page_start; page < page_end; page++) {
        // 设置OLED显示的起始地址
        SSD1306_sendCommand(0xB0 + page);  // 设置页地址
        SSD1306_sendCommand(0x00 + (area->x1 & 0x0F));  // 设置低位列地址
        SSD1306_sendCommand(0x10 + ((area->x1 >> 4) & 0x0F));  // 设置高位列地址
        
        // 处理这一页的每一列
        for (uint16_t col = 0; col < width; col++) {
            uint8_t data = 0;
            
            // 组合8个垂直像素到一个字节
            for (uint8_t bit = 0; bit < 8; bit++) {
                uint16_t y = page * 8 + bit;
                if (y >= area->y1 && y <= area->y2) {
                    uint16_t index = (y - area->y1) * width + col;
                    // 将LVGL的颜色值转换为单色(简单阈值处理)
                    if (color_p[index].full > 0) {
                        data |= (1 << bit);
                    }
                }
            }
            
            // 发送数据到OLED
            SSD1306_sendData(~data);
        }
    }
    
    // 通知LVGL显示刷新完成
    xSemaphoreGive(wm_lvgl_ctx.lvgl_sem);
    
    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

修改lvgl_user_config.h如下

/**
 * @file lvgl_user_config.h
 * Include all user config setting for LVGL related headers
 */
 
#ifndef LVGL_USER_CONFIG_H
#define LVGL_USER_CONFIG_H
 
#include "wm_drv_tft_lcd_cfg.h"
 
#ifdef __cplusplus
extern "C" {
#endif
 
/*********************
 *      DEFINES
 *********************/
 
/*Indicate the exact LCD device name as defined in the device table and wm_drv_tft_lcd_cfg.h*/
//#define WM_LVGL_LCD_MODULE_NAME WM_CFG_TFT_LCD_DEVICE_NAME
 
/* actual screen width, it must equals to the x-resolution in the wm_drv_tft_lcd_cfg.h if no rotation */
//#define MY_DISP_HOR_RES         ((WM_CFG_TFT_LCD_ROTATION % 2) ? WM_CFG_TFT_LCD_Y_RESOLUTION : WM_CFG_TFT_LCD_X_RESOLUTION)
 
/* actual screen height, it must equals to the y-resolution in the wm_drv_tft_lcd_cfg.h if no rotation */
//#define MY_DISP_VER_RES         ((WM_CFG_TFT_LCD_ROTATION % 2) ? WM_CFG_TFT_LCD_X_RESOLUTION : WM_CFG_TFT_LCD_Y_RESOLUTION)
 
/**********************
 *      TYPEDEFS
 **********************/
 
/**********************
 * GLOBAL PROTOTYPES
 **********************/
 
#ifdef __cplusplus
} /*extern "C"*/
#endif
 
#endif /*LVGL_USER_CONFIG_H*/

主函数测试代码

/*
 * W803平台 SSD1306 OLED驱动测试程序
 */
#include <stdio.h>
#include "W803_SSD1306.h"
#include "wmsdk_config.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "wm_drv_timer.h"
#include "wm_drv_gpio.h"
#include "wm_drv_sdh_spi.h"
#include "wm_utils.h"
 
#include "lvgl.h"
#include "wm_lv_port_disp.h"
 
#define LOG_TAG "lvgl_example"
#include "wm_log.h"
 
#include "wm_log.h"
 
#define WM_LVGL_TASK_STACK             (1024)
#define WM_LVGL_TASK_PRIO              (configMAX_PRIORITIES - 8)
 
/* the controller device name which used by LCD, it must same as the device name defined in device table*/
#define LCD_SPI_CONTROLLER_DEVICE_NAME "sdspi"
 
/* LVGL tick period uint */
#define LV_TICK_PERIOD_MS              (1)
void wm_lv_create_tick(void);
static void wm_lv_task_entry(void *arg);
int main(void)
{
    xTaskCreate(wm_lv_task_entry, "wm_lv_task", WM_LVGL_TASK_STACK, NULL, WM_LVGL_TASK_PRIO, NULL);
    return 0;
}
 
// 创建一个简单的LVGL界面
static void create_demo_ui(void)
{
    // 创建标签
    //lv_obj_t *label = lv_label_create(lv_scr_act());
    //lv_label_set_text(label, "W803 SSD1306");
    //lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 5);
    
    // 创建按钮
    lv_obj_t *btn1 = lv_btn_create(lv_scr_act());
    lv_obj_set_pos(btn1, 24, 0);
    lv_obj_set_size(btn1, 100, 20);
    
    // 简化按钮样式以适应单色显示
    //lv_obj_set_style_radius(btn, 0, 0);               // 移除圆角
    lv_obj_set_style_shadow_width(btn1, 0, 0);         // 移除阴影
    lv_obj_set_style_bg_grad_dir(btn1, LV_GRAD_DIR_NONE, 0); // 移除渐变
    
    // 设置高对比度颜色
    lv_obj_set_style_bg_color(btn1, lv_color_black(), 0);    // 背景设为黑色
    lv_obj_set_style_border_color(btn1, lv_color_white(), 0); // 边框设为白色
    lv_obj_set_style_border_width(btn1, 1, 0);               // 确保边框可见
    
    // 在按钮上创建标签
    lv_obj_t *btn1_label = lv_label_create(btn1);
    lv_label_set_text(btn1_label, "Button1");
    lv_obj_center(btn1_label);
    lv_obj_set_style_text_font(btn1_label, &lv_font_montserrat_12, 0); // 设置为16号字体
    lv_obj_set_style_text_color(btn1_label, lv_color_white(), 0); // 确保文本为白色
    
    // 创建按钮
    lv_obj_t *btn2 = lv_btn_create(lv_scr_act());
    lv_obj_set_pos(btn2, 24, 32);
    lv_obj_set_size(btn2, 100, 20);
    
    // 简化按钮样式以适应单色显示
    lv_obj_set_style_radius(btn2, 0, 0);               // 移除圆角
    lv_obj_set_style_shadow_width(btn2, 0, 0);         // 移除阴影
    lv_obj_set_style_bg_grad_dir(btn2, LV_GRAD_DIR_NONE, 0); // 移除渐变
    
    // 设置高对比度颜色
    lv_obj_set_style_bg_color(btn2, lv_color_white(), 0);    // 背景设为黑色
    lv_obj_set_style_border_color(btn2, lv_color_black(), 0); // 边框设为白色
    lv_obj_set_style_border_width(btn2, 1, 0);               // 确保边框可见
    
    // 在按钮上创建标签
    lv_obj_t *btn2_label = lv_label_create(btn2);
    lv_label_set_text(btn2_label, "Button2");
    lv_obj_set_style_text_font(btn2_label, &lv_font_montserrat_16, 0); // 设置为16号字体
    lv_obj_center(btn2_label);
    lv_obj_set_style_text_color(btn2_label, lv_color_black(), 0); // 确保文本为白色
}
 
#if LV_USE_LOG
void wm_lv_log_cb(const char *buf)
{
    wm_log_info("%s", buf);
}
#endif
 
static void wm_lv_task_entry(void *arg)
{
    //wm_lcd_init();
    printf("wm_lv_task_entry\n");
     // 初始化SSD1306
     SSD1306_init();
     // 清屏并设置亮度
     SSD1306_clearDisplay();
     SSD1306_setBrightness(255); // 最大亮度
     printf("SSD1306_init\n");
#if LV_USE_LOG
    lv_log_register_print_cb(wm_lv_log_cb);
#endif
 
    printf("lv_init\n");
    lv_init();
    printf("wm_lv_create_tick\n");
    wm_lv_create_tick();
    printf("lv_port_disp_init\n");
    lv_port_disp_init();
 
/* Enable one demo scenario in lv_confs.h at a time to prevent conflicts */
#if LV_USE_DEMO_BENCHMARK
    lv_demo_benchmark_set_finished_cb(&on_benchmark_finished);
 
    lv_demo_benchmark_set_max_speed(true);
 
    lv_demo_benchmark();
#endif
 
#if LV_USE_DEMO_STRESS
    lv_demo_stress();
#endif
 
#if LV_USE_DEMO_MUSIC
    lv_demo_music();
#endif
 
#if LV_USE_DEMO_WIDGETS
    lv_demo_widgets();
#endif
    printf("create_demo_ui\n");
    create_demo_ui();
    while (1) {
        lv_task_handler();
        vTaskDelay(pdMS_TO_TICKS(10));
    }
    /* deinit once loop break */
    vTaskDelete(NULL);
}
 
/* LVGL Timer Porting */
static void wm_lv_tick_timer_irq(void *arg)
{
    lv_tick_inc(LV_TICK_PERIOD_MS);
}
 
 
void wm_lv_create_tick(void)
{
    int err                      = 0;
    wm_device_t *timer0_dev      = NULL;
    wm_drv_timer_cfg_t timer_cfg = { 0 };
 
    if (NULL == (timer0_dev = wm_drv_timer_init("timer0"))) {
        wm_log_error("timer init failed");
    }
 
    if (WM_ERR_SUCCESS != (err = wm_drv_timer_register_callback(timer0_dev, wm_lv_tick_timer_irq, timer0_dev))) {
        wm_log_error("timer register err");
    }
 
    timer_cfg.unit        = WM_HAL_TIMER_UNIT_MS;
    timer_cfg.auto_reload = true;
    timer_cfg.period      = LV_TICK_PERIOD_MS;
    wm_drv_timer_start(timer0_dev, timer_cfg);
}
 
/* LVGL Task Related */
#if LV_USE_DEMO_BENCHMARK
static void on_benchmark_finished(void)
{
    disp_enable_update();
}
#endif

5、编译与烧录

5.1 编译

选择mian.c后鼠标右击-》WM_IOD_SDK-》build,编译完成后可在build目录下生成fls烧写文件。

5.2 烧写

使用官方下载工具烧写。如下图:

工具烧写.jpg

6、 运行效果

lvgl驱动OLED显示按钮.png

0 条评论

发布
问题