UI for Embedded is always a hassle, find the right MCU, find the right Display, connect the right wires and that's even before writing the first line of code that actually shows anything on the display, drivers, graphic libraries and input libraries can be a pain to use, not to mention a pain to write.
Fortunately, we no longer live in a cave, we have PlatformIO, LVGL and drivers for many of the LCDs available for commercial use and maker community.
I propose a way to start quickly so we can save some of the bring up time for a new setup, configuration is done in the kconfig way, so its really go through the menu, change a setting, compile and test.
There is a getting started for ESP32 on LVGL github, Gabor Kiss-Vamosi wrote a tutorial and we have some documentation, Espressif event got an example repository, but I've discovered my way is a bit more flexible and easier to use once its set-up, since you can develop on the desktop (LVGL refers to it as Simulator) and don't have to upload each revision, plus you don't have to do it, just fork my project and you're good to go. In any case, the full instructions are below, so you can mix and match versions and for me it provided a pretty much consistent experience.
Hardware
There are many kits you can buy, each one with its own quirks but my goal was to test what I had and unfortunately I had none of the kits.
So I got out a perfboard, a few pin headers and a resistor (more on that later) and built my own.
Please note that I have yet to test any of them, so do your own research before purchasing, the following are affiliate links.
ESP32-LCDKit
The ESP32-LCDKit looks like its the most versatile for Graphic development, the schematic is available and you can use which ever ESP32 you want (as long as its a 38 pins)
ESP-WROVER-KIT
Next in line is the ESP-WROVER-KIT, like the previous one, its schematics are also available, but one major drawback is that the kit does not include a touch screen, only a display so its less suitable for interactive UI.
ESP32-S2-Kaluga-1 Kit
WT32-SC01
Source Control
We can't really start a project without source control, how can we track changes? how can we go back to a stable state?
Atlassian has a great cheat sheet and there is built in support in Visual Studio Code.
Lets initialize a new git repo:
git init
I also recommend committing each stage of your setup, it will help you to track changes and find out which code caused the change.PlatformIO
PlatformIO is a development platform that enables writing code in multiple platforms while maintaining a consistent experience.
In this case, we'd like to initialize a new project:
pio init
LVGL
Now that we have an empty project, we'll need to add lvgl to it, so go ahead and extract latest release from https://github.com/lvgl/lvgl/releases into lib/lvgl.
Than take library.json from our example project and copy it into lib/lvgl root, what this library.json file actually does is allow you to select which parts of lvgl gets compiled.
Native / Desktop Drivers
There are multiple ways of working with LVGL but one of the better ways is getting your UI completely disconnected from your business logic and running the UI on your PC, this way its easier to design, debug and verify, on top of it, you can use LVGL's snapshot API to automatically validate your views so they can stay consistent no matter which changes you do.
The way LVGL works on the desktop is by using SDL2.
So first we'll extract the latest source from https://github.com/lvgl/lv_drivers into lib/lv_drivers
Then we'll copy lv_drv_conf_template.h to include/native/lv_drv_conf.h and enable the file (change #if 0 to 1)
Then we'll modify library.json to include SDL dependency, otherwise PlatformIO dependency detection won't work properly due to the way SDL is included through a #DEFINE macro.
"dependencies":[
{
"name":"SDL2"
}
],
And modify library.json to remove an incompatible source file:
"build": {
"srcFilter" : [
"-<display/ILI9341.c>"
]
}
Lastly, we'll update include/native/lv_drv_conf.h in appropriate place and copy our menu configuration keys to SDL configuration keys, otherwise our menu won't control SDL (Desktop) display properly.
#include "lvgl_native_drivers.h"
#define USE_SDL 1
#define SDL_HOR_RES CONFIG_SDL_HOR_RES
#define SDL_VER_RES CONFIG_SDL_VER_RES
Since our native environment will need to use SDL, we should also add SDL2 to lib, there are different libraries for different environments, such as Windows and Linux.ESP32 Hardware Display Drivers
Now that we have our desktop setup, we also want our hardware setup so we can flash our device and see how our design looks on the real hardware.
Please note that the drivers are not always configured ideally, if the colors seems a bit off, you should read the datasheet and make sure everything is configured properly.
Lets start by extracting the latest source from https://github.com/lvgl/lvgl_esp32_drivers into lib/lvgl_esp32_drivers and copy the library.json from this project
Some drivers are not working properly with PlatformIO's scons configuration and needs to be enabled/disabled on a per-file basis, you should look in library.json as an example.
Another thing we want to tell our library.json is which framework it should work with, for example, in our setup we have native and esp32 environments and the esp32 drivers should not be compiled on the native environment since none of Espressif's libraries exist or even needed for desktops.
Then we'll modify lvgl_helper.c to include "lv_conf.h" right after "sdkconfig.h", the vanilla setup assumes your lvgl is part of esp32 components which can make desktop configuration a problem.
#include "sdkconfig.h"
#include "lv_conf.h"
Then we need to add lvgl kconfig script (run_lvgl_kconfig.py) and set its configuration custom_lvgl_kconfig_save_settings, custom_lvgl_kconfig_output_header, custom_lvgl_kconfig_include_headers configuration sections to each relevant environment in platformio.ini
And add lvgl esp32 drivers kconfig script (run_lvgl_esp32_drivers_kconfig.py) and custom_lvgl_esp32_drivers_kconfig_save_settings, custom_lvgl_esp32_drivers_kconfig_output_header configuration section to each relevant environment in platformio.ini
These two scripts and their setting enables platformio.ini to use the target scripts for easy configuration. To see which scripts are installed for each environment:
Now that we've assigned each configuration output to a different folder under include, we should tell platformio to include headers from these folders so each environment will get a different set of configuration files.
LVGL Configuration
LVGL uses configuration files separate from the driver configuration files to configure some aspects of it, such as fonts, widgets, colors and layouts, but we need to tell it where to take the configuration from.
We do it by adding these flags to build_flags for relevant environments in platformio.ini:
-DLV_LVGL_H_INCLUDE_SIMPLE
-DLV_CONF_INCLUDE_SIMPLE
-DLV_CONF_PATH=lv_conf.h
Environment Hardware Abstraction
There is a small library in the demo project called lvgl_hal, it contains the setup for the drivers, obviously different from native to ESP32, you may need to modify it to your environment / programming.
So copy lvgl_hal to your lib folder.
Changing Configuration
Lets start with ESP32 configuration, LVGL can run lean or resource intensive, larger buffers may help with rendering speed, caching images and data can also help, my usual setup is 240Mhz CPU speed and 80Mhz PSRAM speed.
To make these configuration, you'll need to modify ESP32 configuration by running:
pio run -e esp32 -t menuconfig
We can configure ESP32 drivers:
pio run -e esp32 -t lvgl-esp32-drivers-config
pio run -e native -t lvgl-native-drivers-config
And lastly we'll want to configure our lvgl, using the same configuration for both desktop and embedded can help you to find bugs quicker.
pio run -e esp32 -t lvgl-config
pio run -e native -t lvgl-config
Runner
The runner library is intended as an abstraction of the main function, on a desktop its int main(argc,argv), on ESP32 its appmain() and on Arduino its setup() and loop(), instead of writing the same ifdefs everywhere, just copy the runner library, include it in your main file and use it:
MAIN(){
}
Unfortunately my ILI9488 is not configured properly (more on that later)
0 comments:
Post a Comment