# ESP-IDF初探:使用OLED显示任意图片 ## 0. 前言 虽然ESP32也支持Arduino框架且Arduino使用起来十分方便,但是因为我最近在研究和CSI有关的东西,所以还是需要基于ESP-IDF来开发。这篇文章会介绍怎样在WSL下搭建ESP-IDF的环境,并点亮我手上的一个OLED屏幕,然后我会在[另一个坑](csi_esp.html)里介绍怎样提取ESP32的CSI信息。 --- ## 1. WSL里的VSCode+ESP-IDF环境配置 ### 安装依赖 ESP-IDF把配置、编译、烧录的动作都用Python打包了一层,方便在VSCode里进行调试,所以首先要安装包括Python虚拟环境在内的各种依赖。 ```bash 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](https://github.com/espressif/vscode-esp-idf-extension/blob/master/README.md))。步骤包括: * 安装VSCode的ESP-IDF Extension * 安装特定版本的ESP-IDF框架,本文用的是5.4.1 ![IDF工具](espidf_examples.png) 安装完毕后可以看到侧边栏的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,然后用管理员模式打开命令行: ``` bash 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: ``` bash ls /dev/ttyUSB* ``` ### 例程烧录 以最简单的例程blink为例,烧录例程的步骤是: 1. 在例程里找到blink,在某个目录下创建这个项目 2. 设置硬件版本 3. 在SDK Configuration Editor里配置GPIO口 4. 编译 5. 设置串口号 6. 烧录 7. 芯片重启后通过串口查看状态 我用的是搭载了ESP-WROOM-32模组的ESP32 DEVKIT V1,pin脚连接方式如[下图](https://www.circuitstate.com/pinouts/doit-esp32-devkit-v1-wifi-development-board-pinout-diagram-and-reference/)所示,LED用的pin脚是GPIO2。成功烧录后运行效果会是LED灯闪烁,串口持续输出切换LED状态的信息。 ![pinmap](esp32_pinmap.png) --- ## 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的依赖是: ```yml 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的关键函数是这一段: ```c 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的[文档](https://docs.lvgl.io/8.3/overview/image.html), >You can store images in two places >* as a variable in internal memory (RAM or ROM) >* as a file ESP32的[另一个例程](https://github.com/espressif/esp-idf/tree/v5.4.1/examples/peripherals/lcd/i80_controller),`i80_controller`的文档也对这两种方式进行了解释。以文件形式储存还需要外接的SD卡和相应驱动,所以这里我选择直接把图片转成二进制编译进去。 ### 生成图片 `i80_controller`目录下有一个esp_logo.c的,是二进制格式的Espressif的logo的例子。 ```c 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的文档中所写,有一个[在线转换器](https://lvgl.io/tools/imageconverter)可以用来把图片文件转成二进制。因为`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`里的显示图片用的代码如下: ```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); } ``` 烧录后最终效果如图。 ![显示自定义图片](oled_on.jpg) --- ## 参考资料 1. [Standard Toolchain Setup for Linux and macOS](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/linux-macos-setup.html) 2. [ESP-IDF Extension for VS Code](https://github.com/espressif/vscode-esp-idf-extension/blob/master/README.md) 3. [一步到位!在 WSL2 中玩转 Windows 串口](https://blog.csdn.net/MakerElf/article/details/144325977) 4. [DOIT ESP32 DevKit V1 Wi-Fi Development Board – Pinout Diagram & Arduino Reference](https://www.circuitstate.com/pinouts/doit-esp32-devkit-v1-wifi-development-board-pinout-diagram-and-reference/) 5. [IDF Component Manager](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/tools/idf-component-manager.html) 6. [Working with ESP-IDF Components in VS Code](https://www.phippselectronics.com/how-to-add-a-component-in-esp-idf-in-vscode/) 7. [LVGL Images](https://docs.lvgl.io/8.3/overview/image.html) 8. [Image Converter](https://lvgl.io/tools/imageconverter) 9. [i80 LCD LVGL porting example](https://github.com/espressif/esp-idf/tree/v5.4.1/examples/peripherals/lcd/i80_controller) 10. [LVGL图片转换器](https://lvgl.io/tools/imageconverter)