ESP-IDF初探:使用OLED显示任意图片
0. 前言
虽然ESP32也支持Arduino框架且Arduino使用起来十分方便,但是因为我最近在研究和CSI有关的东西,所以还是需要基于ESP-IDF来开发。这篇文章会介绍怎样在WSL下搭建ESP-IDF的环境,并点亮我手上的一个OLED屏幕,然后我会在另一个坑里介绍怎样提取ESP32的CSI信息。
1. WSL里的VSCode+ESP-IDF环境配置
安装依赖
ESP-IDF把配置、编译、烧录的动作都用Python打包了一层,方便在VSCode里进行调试,所以首先要安装包括Python虚拟环境在内的各种依赖。
sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
安装ESP-IDF扩展
安装完依赖后详见官方文档的步骤在VSCode里配置ESP-IDF扩展(ESP-IDF Extension for VS Code)。步骤包括:
安装VSCode的ESP-IDF Extension
安装特定版本的ESP-IDF框架,本文用的是5.4.1
安装完毕后可以看到侧边栏的ESP-IDF包含大量的工具,COMMANDS下的东西其实都是用Python打包过的指令,在VSCode的底部也有对应的小按钮。比较常用的有:
ESP-IDF Select Flash Method: 选择烧录方式UART/DFU/JTAG
Select Port to Use: 选择串口
Set Espressif Device Target: 选择开发板硬件
SDK Configuration Editor: 配置环境变量,包括pin脚设置
Build Project: 编译
Flash Device: 烧录
Monitor Device: 查看串口输出
Advanced - Show Examples:查看例程
把串口共享至WSL
我手上的这块开发板通过CP2102 UART转USB连接到电脑的串口。串口默认是连接到Windows的,为了能在WSL里烧录程序,需要把串口共享给WSL。首先在Windows下安装usbipd,然后用管理员模式打开命令行:
usbipd list
usbipd bind --busid 1-1
usbipd attach --wsl --busid 1-1
#如果要断开共享
usbipd detach --busid 1-1
usbipd unbind --busid 1-1
在WSL里可以用这条指令确认串口是否已经连到WSL:
ls /dev/ttyUSB*
例程烧录
以最简单的例程blink为例,烧录例程的步骤是:
在例程里找到blink,在某个目录下创建这个项目
设置硬件版本
在SDK Configuration Editor里配置GPIO口
编译
设置串口号
烧录
芯片重启后通过串口查看状态
我用的是搭载了ESP-WROOM-32模组的ESP32 DEVKIT V1,pin脚连接方式如下图所示,LED用的pin脚是GPIO2。成功烧录后运行效果会是LED灯闪烁,串口持续输出切换LED状态的信息。
2. 点亮OLED屏幕
当初买这个模组时还顺便买了一个128*64像素的SSD1306的小OLED屏幕,正好可以用ESP-IDF把它点亮。
硬件连接
如前面的pin脚图所示,ESP32自带一个I2C模块。但是这个ESP-IDF提供的i2c_oled
例程是用软件实现的I2C,所以其实任何两个GPIO都可以用来当SDA和SCL。只需连好后在i2c_oled_example_main.c
里配置所用pin脚即可。
直接烧录,OLED顺利点亮,效果是滚动显示Hello Espressif, Hello LVGL这行字。
软件架构
ESP-IDF里一些可复用的模块称为组件(Component),IDF自带一个Component Manager用来管理各种组件和依赖。在main组件和各个子组件所在目录下都有一个idf_component.yml
,里面写清楚了这个组件有哪些依赖。例如,i2c_oled
的main的依赖是:
dependencies:
lvgl/lvgl: "8.3.0"
esp_lcd_sh1107: "^1"
esp_lvgl_port: "^1"
其中,lvgl是一个开源的图形库,用来在嵌入式系统中绘制各种图形。esp_lcd_sh1107是oled屏幕的驱动库。esp_lvgl_port是把lvgl移植到esp32用的驱动库。
进一步分析代码发现,i2c_oled
例程中,用来点亮OLED的关键函数是这一段:
void example_lvgl_demo_ui(lv_disp_t *disp)
{
lv_obj_t *scr = lv_disp_get_scr_act(disp);
lv_obj_t *label = lv_label_create(scr);
lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
/* Circular scroll */
lv_label_set_text(label, "Hello Espressif, Hello LVGL.");
/* Size of the screen (if you use rotation 90 or 270, please set disp->driver->ver_res) */
}
其中lv_label_set_text
是在lvgl/src/widgets
下面被定义的,可以认为label是widgets的一种。同理,image也是widgets的一种,那么调用和image有关的函数就可以显示图片了。
3. 显示自定义图片
lvgl文档
根据lvgl的文档,
You can store images in two places
as a variable in internal memory (RAM or ROM)
as a file
ESP32的另一个例程,i80_controller
的文档也对这两种方式进行了解释。以文件形式储存还需要外接的SD卡和相应驱动,所以这里我选择直接把图片转成二进制编译进去。
生成图片
i80_controller
目录下有一个esp_logo.c的,是二进制格式的Espressif的logo的例子。
const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMAGE_ESP_LOGO uint8_t esp_logo_map[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
/* More binary data omitted */
};
const lv_image_dsc_t esp_logo = {
.header.cf = LV_COLOR_FORMAT_RGB565A8,
.header.magic = LV_IMAGE_HEADER_MAGIC,
.header.w = 96,
.header.h = 96,
.data_size = 9216 * 3,
.data = esp_logo_map,
};
其中,esp_logo_map是图片每个像素的数据,esp_logo是一个image对象,包含了像素数据、颜色格式、长宽等信息。
如lvgl的文档中所写,有一个在线转换器可以用来把图片文件转成二进制。因为i2c_oled
用的lvgl是8.3.0版本,所以用转换器的时候也需要选择LVGL v8。因为这款OLED屏幕是单色的,所以图片的颜色格式选的是LV_IMG_CF_INDEXED_1_8BIT
。转换完成后会得到一个.c文件,放到i2c_oled目录下即可。
显示图片
lvgl的文档里写了,
The simplest way to use an image in LVGL is to display it with an lv_img object: If the image was converted with the online converter, you should use LV_IMG_DECLARE(my_icon_dsc) to declare the image in the file where you want to use it.
所以,最终lvgl_demo_ui.c
里的显示图片用的代码如下:
static lv_obj_t *icon = NULL;
LV_IMG_DECLARE(oled_image)
void example_lvgl_demo_ui(lv_disp_t *disp)
{
icon = lv_img_create(lv_scr_act());
lv_img_set_src(icon, &oled_image);
}
烧录后最终效果如图。