打印
[STM32U5]

【NUCLEO-U5A5ZJ-Q测评】无需移植使用lvgl驱动SSD1306 OLED显示中英文和绘图

[复制链接]
1214|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 HonestQiao 于 2023-12-10 13:22 编辑

这篇文章分享的内容,是在Zephyr系统中,直接使用lvgl驱动 NUCLEO-U5A5ZJ-Q 连接的SSD1306 OLED显示屏,来显示中英文字符,以及绘图。


一、硬件连接
NUCLEO-U5A5ZJ-Q开发板提供了Arduino兼容接口,我手头有之前在Arduino Uno R3上使用的SSD1306 OLED,IIC接口的,可以应用上来。
查看手册,可以得知Arduino兼容接口部分IIC接口的位置:


在开发板上的实际位置如下:


然后,将IIC接口的SSD 1306 OLED连接到Arduino兼容引脚:


一定要注意你所使用的SSD1306 OLED的驱动电压是5V还是3.3V, 不然可能会烧毁。

二、驱动SSD1306 OLED
经过研究Zephyr的文档,发现Zephyr已经内置了LVGL模块,并且还能够用于驱动SSD1306。
下面就是一个简单的实例代码,用于在屏幕上显示Hello World,并且还有一个计数器计数:
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/gpio.h>
#include <lvgl.h>
#include <stdio.h>
#include <string.h>
#include <zephyr/kernel.h>

#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(app);

static uint32_t count;

int main(void)
{
    char count_str[11] = {0};
    const struct device *display_dev;
    lv_obj_t *hello_world_label;
    lv_obj_t *count_label;

    display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
    if (!device_is_ready(display_dev)) {
        LOG_ERR("Device not ready, aborting test");
        return 0;
    }

<blockquote>hello_world_label = lv_label_create(lv_scr_act());


上述的代码逻辑较为简单,简单说明如下:
首先是引入lvgl头文件:
#include <lvgl.h>


然后再定义一个计数器:
static uint32_t count;


在main调用中,先检查设备是否初始化好了:
display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
    if (!device_is_ready(display_dev)) {
        LOG_ERR("Device not ready, aborting test");
        return 0;
    }


再创建2个Lable对象用于显示文字和计数器,并给按键设置好回调。
<blockquote>hello_world_label = lv_label_create(lv_scr_act());


最后,在while循环中,更新计数器的值:
    while (1) {
        if ((count % 100) == 0U) {
            sprintf(count_str, "%d", count/100U);
            lv_label_set_text(count_label, count_str);
        }
        lv_task_handler();
        ++count;
        k_sleep(K_MSEC(10));
    }


需要注意的是,在Zephyr中,没有处理LVGL的自动刷新,需要自己调用 lv_task_handler() 来更新显示内容到屏幕上。


要在工程中使用LVGL模块,还需要在prj.conf中添加下面的配置:
CONFIG_LV_Z_MEM_POOL_SIZE=16384
CONFIG_LV_Z_SHELL=y
CONFIG_MAIN_STACK_SIZE=2048

CONFIG_DISPLAY=y
CONFIG_DISPLAY_LOG_LEVEL_ERR=y

CONFIG_LOG=y
CONFIG_SHELL=y

CONFIG_LVGL=y
CONFIG_LV_MEM_CUSTOM=y
CONFIG_LV_USE_LOG=y
CONFIG_LV_USE_LABEL=y
CONFIG_LV_USE_BTN=y
CONFIG_LV_USE_ARC=y
CONFIG_LV_USE_IMG=y
CONFIG_LV_USE_MONKEY=y
CONFIG_LV_FONT_MONTSERRAT_14=y


编译的时候,需要使用下面的参数:
west build -b nucleo_u5a5zj_q . -- -DSHIELD=ssd1306_128x64


编译烧录后,运行结果如下:


我所使用的SSD 1306 OLED为底色上黄下蓝,所以看到是有颜色的,但实际显示的时候,就是底色能使上面的颜色,显示文本只有黑色一种。

三、图形的绘制
使用LVGL,还可以在SSD1306 OLED上面绘图。
不过需要注意的是,毕竟SSD1306分辨率有限,显示颜色只有单色,所以效果一般,但用于一些基础信息的显示,还是可以的。
下面就演示在SSD1306 OLED上面,根据给定的坐标点,画一条折线:
 lv_obj_clean(lv_scr_act());
    static lv_point_t line_points[] = { {5, 15}, {30, 50}, {60, 20}, {90, 60}, {120, 30} };

    /*Create style*/
    static lv_style_t style_line;
    lv_style_init(&style_line);
    lv_style_set_line_width(&style_line, 1);
    // lv_style_set_line_color(&style_line, lv_palette_main(LV_PALETTE_WHITE));
    lv_style_set_line_rounded(&style_line, true);

    /*Create a line and apply the new style*/
    lv_obj_t * line1;
    line1 = lv_line_create(lv_scr_act());
    lv_line_set_points(line1, line_points, 5);     /*Set the points*/
    lv_obj_add_style(line1, &style_line, 0);
    lv_obj_center(line1);

        lv_task_handler();


在上述代码中,先定义了一个一批坐标到 line_points,然后定义划线的样式 style_line,再使用 lv_line_create 创建 Line对象,使用lv_line_set_points添加要绘制的处理的折线点,以及使用 lv_obj_add_style 设置样式。

最终的显示效果如下:


四、中文的显示
LVGL对Unicode字符的处理提供了很好的处理,包括中文在内。
不过,要显示中文,需要提前准备好专门的字形文件。
我们可以直接把一个ttf字体,转换为字形文件,但是这样包含的中文字符比较多,生成的文件比较大,嵌入式设备不一定都能存放得下,而且运行起来速度也会收到影响。
所以,通常情况下,只会为需要显示的中文字符,生成对应的字形文件。
要给LVGL生成中文字形文件,可以使用lv_font_conv,可以从 https://github.com/lvgl/lv_font_conv 下载。

下面,我要在显示屏上显示"你好,世界!",那么可以试用如下的指令来生成:
lv_font_conv --no-compress --format lvgl --font OPPOSans-M.ttf -o opposans_m_16_2.c --bpp 4 --size 16 -r 0x20-0x7F --symbols 你好,世界!
上述指令的含义,是从 OPPOSans-M.ttf 字体文件中,提取 "你好,世界!" 的字形数据以及ASCII码对应的字形数据,生成16号字体对应的c文件opposans_m_16_2.c,生成后的内容如下:


因为字形数据较多,所以只展示上面一部分。

将生成的字形数据的c文件,放置到项目的src目录下,然后,在代码中,include 之后,添加字体定义:
LV_FONT_DECLARE(opposans_m_16_2);

然后设置一个演示使用中文字体:
static lv_style_t label_style_cn;                                                                                                // 创建一个风格
lv_style_init(&label_style_cn);                                                                                                // 初始化风格
lv_style_set_text_font(&label_style_cn, &opposans_m_16_2);        // 设置字体


再给前面显示的 hello_world_label 添加定义的样式:
lv_label_set_text(hello_world_label, "Hello world!");
lv_obj_align(hello_world_label, LV_ALIGN_CENTER, 0, 0);
lv_obj_add_style(hello_world_label, &label_style_cn, LV_PART_MAIN);                // 应用效果风格


最后,在代码中调用:
while (1) {
        if ((count % 100) == 0U) {
                sprintf(count_str, "%d", count/100U);
                lv_label_set_text(count_label, count_str);

                if (((count / 100) % 2) == 0U) {
                        lv_label_set_text(hello_world_label, "Hello world!");
                } else {
                        lv_label_set_text(hello_world_label, "你好,世界!");
                }
        }
        lv_task_handler();
        ++count;
        k_sleep(K_MSEC(10));
}


编译运行后,实际效果如下:



五、总结
NUCLEO-U5A5ZJ-Q的良好硬件设计,使得开发者可以很方便的连接Arduino兼容的硬件,大大方便了开发工作。
Zephyr对NUCLEO-U5A5ZJ-Q的给力支持,以及提供的LVGL模块支持,能够让开发者聚焦于核心业务的处理,真的是非常的方便。
有一点需要注意的是,Zephyr提供的LVGL版本并不是最新的,所以有极少部分调用,可能与新版本的LVGL存在差异,编译的时候会有提示。
后续将会继续使用彩色显示屏来进行测试,并继续给大家分享。

使用特权

评论回复
沙发
xusiwei1236| | 2023-12-11 10:14 | 只看该作者
帮主牛X

使用特权

评论回复
板凳
AdaMaYun| | 2023-12-13 22:51 | 只看该作者
Zephyr提供的LVGL版本之间的差异有哪些?

使用特权

评论回复
地板
szt1993| | 2023-12-13 22:56 | 只看该作者
LVGL移植需要注意哪些方面呢

使用特权

评论回复
5
HonestQiao|  楼主 | 2023-12-15 17:15 | 只看该作者
AdaMaYun 发表于 2023-12-13 22:51
Zephyr提供的LVGL版本之间的差异有哪些?

和官方的差异很小,实例我都是拿来直接使用的

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

37

主题

94

帖子

2

粉丝