目标
配置vscode + platformIO环境,在该环境下为ESP32单片机适配LVGL图像库以及keypad输入设备,驱动液晶显示器。
1.环境配置
1.1安装python解释器
安装[python解释器 这边用的是3.7.1](链接:https://pan.baidu.com/s/1OloK0seytAjSclvIqtui4g
提取码:zdzd ) ,不安装vscode创建platformIO工程可能会报错
安装解释器时勾选上添加变量。安装完成后,将解释器的地址添加到全局变量。
完成后可以在命令行里用pip list指令测试一下,不报错就是安装成功了
添加系统变量
测试是不是装成功了
1.2安装vscode
官网下载 或百度云提取码:zdzd
直接安装 “下一步 下一步 我接受” ,第一次打开会问是不是安装中文字体再重启点击”是“再打开就中文了。
1.3安装platformIO插件
右下角有安装进度,一般比较慢,经常卡在中间不动。搞个梯子会快很多。如果卡在core很久很久,可以试试关闭防火墙。
安装的过程中如果提示没有找到python3.6,可以按照指引填写在这儿
1.4创建工程
选择完成后点击下面的finish,创建也许会花很长很长时间(可能2天搞不完),因为服务器在外面。用梯子会快很多(我实测1h左右,也许是梯子不是很稳的原因)。如果没梯子可以用手机4G开个热点据说会快很多。
完成找到刚刚保存项目的地址打开文件夹,可以看到src目录下主函数的入口
由于每次新建项目花费的时间比较长,可以将这个空项目备份一下。以后再有项目的时候,直接复制这个空项目,然后在这个空项目上操作。
在main.cpp里写个程序测试一下,插上esp32下载时如果出现upload问题可能是没有安装串口驱动
2. 显示驱动库的移植
2.1下载TFT_eSPI库
下载完成后可以在这个json文件里看到保存的地址
根据个人喜欢可以将去剪切到lib文件夹下,目录会自动识别自动修改的
2.2 修改TFT_eSPI库
打开setup.h文件,选择硬件驱动的型号取消屏蔽
选择屏幕的宽高,取消屏蔽
找到修改ESP32 引脚的地方 修改引脚的编号
2.3 测试TFT_eSPI是否适配成功
复制example文件夹下的例子到main里,看看能不能正常显示,再开头加一个ardunio.h的头文件,编译下载后屏幕会显示彩虹色
3.安装LVGL库
lvgl是图像库,可以方便的绘制按钮,日历,文本框等图形,也可以方便的接入按钮按下的事件函数,支持css布局。详情可阅读百问网LVGL中文手册。 LVGL大更新的版本并不向低版本兼容,7.X版本与8.X版本的很多接口不一样,需要根据版本阅读相应的手册。
3.1 下载LVGL库
下载LVGL 8.x版本,并且复制粘贴到lib文件夹下,方法同TFT_eSPI
3.2 修改LVGL库适配硬件
复制lv_conf_template.h粘贴到同一个目录下,文件名修改为lv_conf.h
打开.h文件中的宏 根据实际修改颜色的深度
3.3 测试一下lvgl库
初始化LVGL 并在屏幕上显示个红色的按钮。 各种接口和组件可以参考手册。
#include <Arduino.h>
#include <TFT_eSPI.h>
#include <lvgl.h>
/*Change to your screen resolution*/
static const uint32_t screenWidth = 160;
static const uint32_t screenHeight = 128;
TFT_eSPI tft = TFT_eSPI(); /* TFT instance */
static lv_disp_draw_buf_t disp_buf;
static lv_color_t buf[screenWidth * 10];
//extern const lv_img_dsc_t picQiudayeda;
#if USE_LV_LOG != 0
/* Serial debugging */
void my_print(lv_log_level_t level, const char *file, uint32_t line, const char *dsc)
{
Serial.printf("%s@%d->%s\r\n", file, line, dsc);
Serial.flush();
}
#endif
/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
tft.startWrite();
tft.setAddrWindow(area->x1, area->y1, w, h);
tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
tft.endWrite();
lv_disp_flush_ready(disp);
}
void setup()
{
Serial.begin(115200);
lv_init();
#if USE_LV_LOG != 0
lv_log_register_print_cb(my_print); /* register print function for debugging */
#endif
tft.begin(); /* TFT init */
tft.setRotation(1); /* Landscape orientation */
lv_disp_draw_buf_init(&disp_buf, buf, NULL, screenWidth * 10);
/*Initialize the display*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &disp_buf;
lv_disp_drv_register(&disp_drv);
//画一个按钮
lv_obj_t *obj = lv_btn_create(lv_scr_act());
static lv_style_t style;
lv_style_set_bg_color( &style, lv_color_hex(0xff0000));
lv_style_set_width(&style, 60);
lv_style_set_height(&style, 35);
lv_obj_add_style(obj, &style, LV_PART_MAIN );
lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0 );
}
void loop()
{
lv_timer_handler(); /* let the GUI do its work */
// lv_task_handler();
// lv_tick_inc(5); 有输入设备的时候打开
delay(5);
}
3.4添加按键(keypad)组件
没有触摸屏的时候,按键组件作为输入可以切换选中按钮等小部件,也可以按下按钮 或者 让进度条的值变大变小。
复制这两个文件到src目录下
将.c .h文件修改成lv_port_indev
打开.h文件 修改内容, 打开宏 修改标签 修改lvgl头文件include的地址
修改.c文件 打开宏 修改include的地址
.c文件写了四种支持的输入设备的类型,保留想要保留的就行,其余的都删除
根据注释将整个.c文件中与keypad无关的都删除(只保留想要的输入设备)
lv_port_indev_init 是初始化
keypad_read 是回掉函数 lvgl会自动循环这个函数,判断按键的按下情况
keypad_get_key 按键读取的函数 这个函数在keypad_read里会赋值给act_key变量,表征按键状态的变化
删除之后
/**
* @file lv_port_indev.c
*
*/
/*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1
/*********************
* INCLUDES
*********************/
#include "lv_port_indev.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void keypad_init(void);
static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static uint32_t keypad_get_key(void);
/**********************
* STATIC VARIABLES
**********************/
lv_indev_t * indev_keypad;
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_port_indev_init(void)
{
/**
* Here you will find example implementation of input devices supported by LittelvGL:
* - Touchpad
* - Mouse (with cursor support)
* - Keypad (supports GUI usage only with key)
* - Encoder (supports GUI usage only with: left, right, push)
* - Button (external buttons to press points on the screen)
*
* The `..._read()` function are only examples.
* You should shape them according to your hardware
*/
static lv_indev_drv_t indev_drv;
/*------------------
* Keypad
* -----------------*/
/*Initialize your keypad or keyboard if you have*/
keypad_init();
/*Register a keypad input device*/
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_KEYPAD;
indev_drv.read_cb = keypad_read;
indev_keypad = lv_indev_drv_register(&indev_drv);
/*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
*add objects to the group with `lv_group_add_obj(group, obj)`
*and assign this input device to group to navigate in it:
*`lv_indev_set_group(indev_keypad, group);`*/
}
/**********************
* STATIC FUNCTIONS
**********************/
/*------------------
* Keypad
* -----------------*/
/*Initialize your keypad*/
static void keypad_init(void)
{
/*Your code comes here*/
}
static void mouse_get_xy(lv_coord_t * x, lv_coord_t * y)//这个不要删除 在init里调用了 善良
{
/*Your code comes here*/
(*x) = 0;
(*y) = 0;
}
/*Will be called by the library to read the mouse*/
static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static uint32_t last_key = 0;
/*Get the current x and y coordinates*/
mouse_get_xy(&data->point.x, &data->point.y);
/*Get whether the a key is pressed and save the pressed key*/
uint32_t act_key = keypad_get_key();
if(act_key != 0) {
data->state = LV_INDEV_STATE_PR;
/*Translate the keys to LVGL control characters according to your key definitions*/
switch(act_key) {
case 1:
act_key = LV_KEY_NEXT;
break;
case 2:
act_key = LV_KEY_PREV;
break;
case 3:
act_key = LV_KEY_LEFT;
break;
case 4:
act_key = LV_KEY_RIGHT;
break;
case 5:
act_key = LV_KEY_ENTER;
break;
}
last_key = act_key;
} else {
data->state = LV_INDEV_STATE_REL;
}
data->key = last_key;
}
/*Get the currently being pressed key. 0 if no key is pressed*/
static uint32_t keypad_get_key(void)
{
/*Your code comes here*/
return 0;
}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
3.5主函数里初始化keypad
1.在setup里调用 lv_port_indev_init()函数,初始化设备 lv_port_indev_init不在main文件里,故需要在.h里声明
2.在loop里添加 lv_tick_inc(5); 让系统知道5ms到了,没有这个不回掉keypad_read函数的 (这一步很重要)
3.indev_keypad是kepad的句柄 在main函数里需要用所以也需要在.h里声明
4.修改lv_inport_indec.c里的keypad_get_key函数,可以直接在这个里面判断按键是否按下,根据不同的按键return不同的值
也可以在这个返回一个全局变量,按键的判断或者全局变量的改变放在主函数。推荐使用后者耦合性低,方便使用onebutton等按键判断库。
src下lv_port_indev.h lv_port_indev.c main.cpp三个文件c代码最后如下
lv_port_indev.h
/**
* @file lv_port_indev_templ.h
*
*/
/*Copy this file as "lv_port_indev.h" and set this value to "1" to enable content*/
#if 1
#ifndef LV_PORT_INDEV_H
#define LV_PORT_INDEV_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lvgl.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
void lv_port_indev_init(void);
lv_indev_t * indev_keypad;
uint32_t key_state;
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_PORT_INDEV_TEMPL_H*/
#endif /*Disable/Enable content*/
lv_port_indev.c
/**
* @file lv_port_indev.c
*
*/
/*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1
/*********************
* INCLUDES
*********************/
#include "lv_port_indev.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void keypad_init(void);
static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static uint32_t keypad_get_key(void);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_port_indev_init(void)
{
/**
* Here you will find example implementation of input devices supported by LittelvGL:
* - Touchpad
* - Mouse (with cursor support)
* - Keypad (supports GUI usage only with key)
* - Encoder (supports GUI usage only with: left, right, push)
* - Button (external buttons to press points on the screen)
*
* The `..._read()` function are only examples.
* You should shape them according to your hardware
*/
static lv_indev_drv_t indev_drv;
/*------------------
* Keypad
* -----------------*/
/*Initialize your keypad or keyboard if you have*/
keypad_init();
/*Register a keypad input device*/
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_KEYPAD;
indev_drv.read_cb = keypad_read;
indev_keypad = lv_indev_drv_register(&indev_drv);
/*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
*add objects to the group with `lv_group_add_obj(group, obj)`
*and assign this input device to group to navigate in it:
*`lv_indev_set_group(indev_keypad, group);`*/
}
/**********************
* STATIC FUNCTIONS
**********************/
/*------------------
* Keypad
* -----------------*/
/*Initialize your keypad*/
static void keypad_init(void)
{
/*Your code comes here*/
}
static void mouse_get_xy(lv_coord_t * x, lv_coord_t * y)
{
/*Your code comes here*/
(*x) = 0;
(*y) = 0;
}
/*Will be called by the library to read the mouse*/
static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static uint32_t last_key = 0;
/*Get the current x and y coordinates*/
mouse_get_xy(&data->point.x, &data->point.y);
/*Get whether the a key is pressed and save the pressed key*/
uint32_t act_key = keypad_get_key();
if(act_key != 0) {
data->state = LV_INDEV_STATE_PR;
/*Translate the keys to LVGL control characters according to your key definitions*/
switch(act_key) {
case 1:
act_key = LV_KEY_NEXT;
break;
case 2:
act_key = LV_KEY_PREV;
break;
case 3:
act_key = LV_KEY_LEFT;
break;
case 4:
act_key = LV_KEY_RIGHT;
break;
case 5:
act_key = LV_KEY_ENTER;
break;
}
last_key = act_key;
} else {
data->state = LV_INDEV_STATE_REL;
}
data->key = last_key;
}
/*Get the currently being pressed key. 0 if no key is pressed*/
static uint32_t keypad_get_key(void)
{
/*Your code comes here*/
return key_state;
//return 0;
}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
main.cpp
#include <Arduino.h>
#include <TFT_eSPI.h>
#include <lvgl.h>
#include <lv_port_indev.h>
/*Change to your screen resolution*/
static const uint32_t screenWidth = 160;
static const uint32_t screenHeight = 128;
TFT_eSPI tft = TFT_eSPI(); /* TFT instance */
static lv_disp_draw_buf_t disp_buf;
static lv_color_t buf[screenWidth * 10];
//extern const lv_img_dsc_t picQiudayeda;
#if USE_LV_LOG != 0
/* Serial debugging */
void my_print(lv_log_level_t level, const char *file, uint32_t line, const char *dsc)
{
Serial.printf("%s@%d->%s\r\n", file, line, dsc);
Serial.flush();
}
#endif
/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
tft.startWrite();
tft.setAddrWindow(area->x1, area->y1, w, h);
tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
tft.endWrite();
lv_disp_flush_ready(disp);
}
void keyInit()//配置输入
{
pinMode(0, INPUT_PULLUP);
pinMode(32, INPUT_PULLUP);
pinMode(12, INPUT_PULLUP);
pinMode(33, INPUT_PULLUP);
}
uint32_t keyScan()//扫描
{
if(digitalRead(0)==0)
{
Serial.println("LV_KEY_NEXT");
return LV_KEY_NEXT;
}
if(digitalRead(32)==0)
{
Serial.println("LV_KEY_PREV");
return LV_KEY_PREV;
}
if(digitalRead(12)==0)
{
Serial.println("LV_KEY_ENTER");
return LV_KEY_ENTER;
}
if(digitalRead(33)==0)
{
Serial.println("LV_KEY_LEFT");
return LV_KEY_LEFT;
}
return 0;
}
void setup()
{
Serial.begin(115200);
keyInit();
lv_init();
#if USE_LV_LOG != 0
lv_log_register_print_cb(my_print); /* register print function for debugging */
#endif
tft.begin(); /* TFT init */
tft.setRotation(1); /* Landscape orientation */
lv_disp_draw_buf_init(&disp_buf, buf, NULL, screenWidth * 10);
/*Initialize the display*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &disp_buf;
lv_disp_drv_register(&disp_drv);
lv_port_indev_init();//输入设备初始化
lv_group_t *group = lv_group_create();
lv_obj_t *obj = lv_btn_create(lv_scr_act());
lv_obj_t *obj2 = lv_btn_create(lv_scr_act());
static lv_style_t style;
lv_style_set_bg_color( &style, lv_color_hex(0xff0000));
lv_style_set_width(&style, 60);
lv_style_set_height(&style, 35);
lv_obj_add_style(obj, &style, LV_PART_MAIN );
lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0 );
lv_obj_add_style(obj2, &style, LV_PART_MAIN | LV_STATE_FOCUSED | LV_STATE_PRESSED );
lv_obj_align_to(obj2, obj, LV_ALIGN_OUT_TOP_MID, 0, -5);
lv_group_add_obj(group, obj);
lv_group_add_obj(group, obj2);
lv_indev_set_group(indev_keypad, group);
}
void loop()
{
key_state = keyScan();
lv_timer_handler(); /* let the GUI do its work */
lv_task_handler();
lv_tick_inc(5); //有输入设备的时候打开
delay(5);
}
4.squareline所见即所得
squareline官网 可以拖拽生成UI和事件可以导出界面和事件,目前不是特别成熟,可在拖拽生成UI后再修改,手动添加事件等。利用该软件可以快速的设计出UI,所见即所得。