本帖最后由 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头文件:
然后再定义一个计数器:
在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存在差异,编译的时候会有提示。
后续将会继续使用彩色显示屏来进行测试,并继续给大家分享。 |