目录
一、制作ESP32S3转接板
1.原理图设计
2. PCB制版布线铺铜
3.输出文件与下单
二、安装VMware ubuntu搭建ESP32S3开发环境
1.安装VMware
2.创建虚拟机
3.安装ESP-IDF
4.在vscode上搭建ESP-IDF
三、Ubuntu上ESP32 MicroPython
1.ESP32 MicroPython固件下载
2.使用esptool刷入固件
四、测试
五、移植LVGL
1.下载LVGL
2.创建屏幕文件及CMake设置
选取器件:ESP32S3+RGB接口 4寸 480*480屏幕
根据480*480屏幕手册设置屏幕的40个引脚
480*480屏幕的属性
检查无误后,点击文件(File)-制造输出里的gerber files
会得到如图所示的对话框:
首先在通用选择,单位选择英寸即可,格式可以选择最高精度2:5
然后选择层,点击左下角绘制层(P),然后选择为(选择使用的),勾选旁边的包括未连接的中间层焊盘,右半边的机械层全部不选。
点击钻孔图层,这一栏的所有项都不勾选。
点击光圈,嵌入的孔径(RS274X)需要勾选
点击高级
设置完成,点击右下角确定。文件会自动生成,等待生成结束。
点击文件-制造输出-gerber files。通用一栏保持不变,点击层,点击绘制层,选择全部去掉,不勾选未连接的中间层焊盘,右侧机械层选择Mechanical 1
点击钻孔图层,选择输出所有的钻孔对,和输出所有的钻孔。
光圈和高级保持和第一次相同。
点击确认后,会自动出现预览文件,直接关闭,不需要保存,这一不是用来定位PCB班上孔的位置。
钻孔文件的输出:
点击文件-制造输出-NC Drill Files
单位和格式的选择和Gerber文件一样,前导和尾数零选择第二项摈弃前导零,坐标位置选择第二项参考相对原点,设置完成后,点击确定。
点击确认后会一次弹出两个确认参数设置对话框,点击确认即可。
把该文件夹压缩上传到嘉立创下单助手即可。
可以去vmware官网下载:vmware官网跳转
安装VMware比较简单,在此不赘述。
需要注意:
到下面图片中的这一个步骤,可以点击许可证,输入密钥就可以使用了,
密钥可以去某度或者其他地方搜索一个拿来用就好
如果直接点击完成的话,可以有试用的时间,也可以使用
VMware安装完成后如下图
点击创建新的虚拟机
这里就按照推荐下一步就行
安装映像:
这里的路径,需要选择一个虚拟机的光盘映像文件,
我这里选择的是ubuntu的,可以去ubuntu下载:官网跳转链接
之后的安装就按照自己需求安装就可以了
参考文档:Standard Toolchain Setup for Linux and macOS - ESP32 - — ESP-IDF Programming Guide latest documentation
在ubuntu上安装需要的工具链
在Terminal终端输入命令:
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
查看python版本号:
python3 --version
下载ESP-IDF:
mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
安装ESP-IDF:
cd esp-idf/
./install.sh all #安装所有版本
. ./export.sh
ESP-IDF已经安装成功
但是当你再一次打开Terminal终端时,输出idf.py命令时会出现
找不到此命令,此时我们需要添加一个环境变量
echo $SHELL #查看我们运行的是什么SHELL
然后我们输入
nano ~/.bashrc
在打开的bashrc文件中任意一行添加
alias get_idf='. $HOME/esp/esp-idf/export.sh'
打开Terminal终端,输入get_idf命令,即可正常运行
编译并上传hello_world到ESP32S3
cd ~/esp
cp -r $IDF_PATH/examples/get-started/hello_world .
cd hello_world/ #进入hello_world文件夹
idf.py set-target ESP32S3 #设置开发板型号
配置flash大小,输入命令
idf.py menuconfig #对ESP32进行配置
选择flasher config
选择flash size
改成4MB即可。
接下来就是编译和烧入
idf.py build #编译
#新开终端
cd /dev
ls ttyUSB* #查看ESP端口号 如果不显示端口号,需要安装对应ESP版本的串口驱动
sudo chmod a+rw /dev/端口号 #设置串口读写权限 注意一定要给权限 不然下面烧入不成功
idf.py flash monitoe -p /dev/端口号 #烧入开发板
以上就是ESP-IDF的安装。
首先我们需要安装一个python 后面需要用到。
python下载官网:python官网下载
下载完成后是个安装包
利用以下代码解压:
tar -zxvf xxxx #xxxx代表你的安装包名称
解压后需要安装一个包:
sudo apt install zlib1g-dev
编译python:
mkdir ~/python #创建一个文件夹,放入编译好的文件
./configure --prefix=xxxxx #xxxxx代表你建的文件夹路径
make #make编译
make install #安装
安装完python后,要记住python的路径,下面配置ESP-IDF需要用到。
vscode官网下载:vscode官网下载
注意下载对应系统的版本
下载完成后打开vscode
在扩展中安装这两个扩展
安装完成后
进入ESP-IDF:Configure ESP-IDF extension 配置
在选择python环境时不能选择前两个,前两个没有权限,此时我们选择第三个,然后把上面安装好的python路径粘贴下面加上/python3即可。
点击install 安装即可。
出现此图片代表安装成功。
下载地址:MicroPython - Python for microcontrollers
进入下载地址后,选择相应型号的开发板
下载 .bin文件,下载完成后就可以开始配置了。
获取PIP
在终端中,安装版本型号输入下列命令。
sudo pacman -S python3-pip #Arch Linux版本
sudo apt-get install python3-pip #Ubuntu
获取esptool
我们需要使用Python的包管理器pip
来获取esptool,在命令行窗口中输入如下命令进行安装:
sudo pip3 install esptool
在此之前,我们需要知道插入到电脑上的ESP32设备,在Linux中的端口号是多少。
Linux通常以ttyUSB
+数字编号的方式为这些设备命名。可以使用如下的命令来查看:
ls -l /dev/ttyUSB*
擦除flash
为了保证固件刷入的成功率,我们先要对ESP32的flash进行清除。代码如下:
sudo esptool.py --port <你的端口号> erase_flash
使用esptool刷入固件
找到第一步下载的ESP32 MicroPython固件的路径,开始使用esptool刷入固件,命令如下:
sudo esptool.py --chip esp32 --port <你的端口号> write_flash -z 0x1000 <你的固件的完整路径>
至此,Ubuntu下 ESP32 MicroPython固件就烧入结束了。
3.安装MicroPython库文件
git clone -b v1.19.1 --single-branch https://github.com/micropython/micropython.git
git完MicroPython库文件后,在micropython-ports文件夹中,复制一个esp32文件夹,重命名为自己的项目名称,我这里取名为facemoo。
在我们复制的文件夹中,新建一个partitions-32MiB.csv文件,里面存放下面代码:
# Notes: the offset of the partition table itself is set in
# $IDF_PATH/components/partition_table/Kconfig.projbuild.
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 0x1F0000,
vfs, data, fat, 0x200000, 0x1E00000,
修改mpconfigport.h文件,修改如下:
接下来就像就可以进行测试了。
首先进入esp-idf文件夹下,在终端输入:
. ./export.sh
打开esp-idf运行环境,进入到facemoo文件夹下,打开终端输入:
idf.py clean #进行清楚
make submodules #如果clean 失败,输入此行代码 之后再次运行idf.py clean
idf.py set-target esp32s3 #设置开发板型号
idf.py menuconfig #打开配置界面进行配置
配置界面如下,首先进入Serial flasher config,进行如下配置。
配置完后,返回上一界面,配置Partition Table,如下图所示:
配置完后,进行Component config配置,如下:
idf.py build #编译
sudo chmod 777 /dev/ttyUSB0 #设置串口权限
idf.py flash monitor -p /dev/ttyUSB0 #烧入和监听
烧入完成后,打开Thonny,会显示如下:
在项目工程中的main文件夹下创建一个名为”idf_component.yml“的文件,在该文件中输入代码:
dependencies:
idf : ">=4.4"
lvgl/lvgl : "~8.2.0"
注意:代码的格式和空格千万不能出错!
该代码的作用是在编译的过程中,下载LVGL,创建完此文件后编译会出现managed_components文件夹
在项目工程目录下创建”driver“文件夹,该文件夹下存放两个屏幕文件:
分别为屏幕的.c文件和.h文件。
.c文件代码如下:
#include "rgb_lcd_480x480.h"
#include
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
// #include "managed_components/lvgl__lvgl/src/lvgl.h"
#include "lvgl.h"
#include
// #include "managed_components/lvgl__lvgl/demos/lv_demos.h"
#include "demos/lv_demos.h"
static const char *TAG = "example";
#define RGB_LCD_PIN_NUM_BK_LIGHT 1
#define RGB_LCD_PIN_NUM_HSYNC 48
#define RGB_LCD_PIN_NUM_VSYNC 45
#define RGB_LCD_PIN_NUM_DE 38
#define RGB_LCD_PIN_NUM_PCLK 2
#define RGB_LCD_PIN_NUM_DATA0 47 // B0
#define RGB_LCD_PIN_NUM_DATA1 21 // B1
#define RGB_LCD_PIN_NUM_DATA2 14 // B2
#define RGB_LCD_PIN_NUM_DATA3 13 // B3
#define RGB_LCD_PIN_NUM_DATA4 12 // B4
#define RGB_LCD_PIN_NUM_DATA5 11 // G0
#define RGB_LCD_PIN_NUM_DATA6 10 // G1
#define RGB_LCD_PIN_NUM_DATA7 9 // G2
#define RGB_LCD_PIN_NUM_DATA8 46 // G3
#define RGB_LCD_PIN_NUM_DATA9 20 // G4
#define RGB_LCD_PIN_NUM_DATA10 19 // G5
#define RGB_LCD_PIN_NUM_DATA11 8 // R0
#define RGB_LCD_PIN_NUM_DATA12 18 // R1
#define RGB_LCD_PIN_NUM_DATA13 17 // R2
#define RGB_LCD_PIN_NUM_DATA14 16 // R3
#define RGB_LCD_PIN_NUM_DATA15 15 // R4
#define RGB_LCD_PIN_NUM_DISP_EN -1
// #define RGB_LCD_PIN_NUM_DISP_EN 39
// The pixel number in horizontal and vertical
#define RGB_LCD_LCD_H_RES 480
#define RGB_LCD_LCD_V_RES 480
#define RGB_LCD_LVGL_TICK_PERIOD_MS 2
#define TOUCH_FT5624_I2C_SDA 6
#define TOUCH_FT5624_I2C_SCL 5
#define TOUCH_FT5624_I2C_INT 7
#define TOUCH_FT5624_I2C_RST 4
#define TOUCH_FT5624_I2C_FREQ_HZ 400000
#define LV_I2C_TOUCH_PORT 0
#define FT5426_I2C_SLAVE_ADDR 0X38 // 设备地址
// I2C读写命令
#define FT_CMD_WR 0X70 // 写命令
#define FT_CMD_RD 0X71 // 读命令
// FT5206 部分寄存器定义
#define FT_DEVIDE_MODE 0x00 // FT5206模式控制寄存器
#define FT_REG_NUM_FINGER 0x02 // 触摸状态寄存器
#define FT_TP1_REG 0X03 // 第一个触摸点数据地址
#define FT_TP2_REG 0X09 // 第二个触摸点数据地址
#define FT_TP3_REG 0X0F // 第三个触摸点数据地址
#define FT_TP4_REG 0X15 // 第四个触摸点数据地址
#define FT_TP5_REG 0X1B // 第五个触摸点数据地址
#define FT_ID_G_LIB_VERSION 0xA1 // 版本
#define FT_ID_G_MODE 0xA4 // FT5206中断模式控制寄存器
#define FT_ID_G_THGROUP 0x80 // 触摸有效值设置寄存器
#define FT_ID_G_PERIODACTIVE 0x88 // 激活状态周期设置寄存器
#define I2C_ADDR_10 (1 << 15)
#define I2C_REG_16 (1 << 31)
#define I2C_NO_REG (1 << 30)
static const uint8_t ACK_CHECK_EN = 1;
typedef struct
{
bool inited;
uint8_t i2c_dev_addr;
} ft5426_status_t;
ft5426_status_t ft5426;
static SemaphoreHandle_t I2C_0_mutex = NULL;
esp_err_t I2C_init();
esp_err_t I2C_read(uint16_t addr, uint32_t reg, uint8_t *buffer, uint16_t size);
esp_err_t I2C_write(uint16_t addr, uint32_t reg, const uint8_t *buffer, uint16_t size);
esp_err_t I2C_lock();
// static esp_err_t I2C_close();
esp_err_t I2C_lock();
esp_err_t I2C_unlock();
esp_err_t I2C_force_unlock();
void i2c_send_address(i2c_cmd_handle_t cmd, uint16_t addr, i2c_rw_t rw)
{
if (addr & I2C_ADDR_10)
{
i2c_master_write_byte(cmd, 0xF0 | ((addr & 0x3FF) >> 7) | rw, ACK_CHECK_EN);
i2c_master_write_byte(cmd, addr & 0xFF, ACK_CHECK_EN);
}
else
{
i2c_master_write_byte(cmd, (addr << 1) | rw, ACK_CHECK_EN);
}
}
void i2c_send_register(i2c_cmd_handle_t cmd, uint32_t reg)
{
if (reg & I2C_REG_16)
{
i2c_master_write_byte(cmd, (reg & 0xFF00) >> 8, ACK_CHECK_EN);
}
i2c_master_write_byte(cmd, reg & 0xFF, ACK_CHECK_EN);
}
/**
* @brief I2C设备初始化
*
* @param port
* @return esp_err_t
*/
esp_err_t I2C_init()
{
esp_err_t ret = ESP_OK;
if (I2C_0_mutex == 0)
{
ESP_LOGI(TAG, "Starting I2C master at port %d.", (int)LV_I2C_TOUCH_PORT);
I2C_0_mutex = xSemaphoreCreateMutex();
i2c_config_t conf = {0};
conf.clk_flags = 0;
conf.sda_io_num = TOUCH_FT5624_I2C_SDA;
conf.scl_io_num = TOUCH_FT5624_I2C_SCL;
conf.sda_pullup_en = GPIO_PULLUP_DISABLE;
conf.scl_pullup_en = conf.sda_pullup_en;
conf.master.clk_speed = TOUCH_FT5624_I2C_FREQ_HZ;
conf.mode = I2C_MODE_MASTER;
ret = i2c_param_config(LV_I2C_TOUCH_PORT, &conf);
ret |= i2c_driver_install(LV_I2C_TOUCH_PORT, conf.mode, 0, 0, 0);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to initialise I2C port %d.", (int)LV_I2C_TOUCH_PORT);
ESP_LOGW(TAG, "If it was already open, we'll use it with whatever settings were used "
"to open it. See I2C Manager README for details.");
}
else
{
ESP_LOGI(TAG, "Initialised port %d (SDA: %d, SCL: %d, speed: %d Hz.)",
LV_I2C_TOUCH_PORT, conf.sda_io_num, conf.scl_io_num, conf.master.clk_speed);
}
}
return ret;
}
esp_err_t I2C_read(uint16_t addr, uint32_t reg, uint8_t *buffer, uint16_t size)
{
esp_err_t result;
TickType_t timeout = 2;
if (I2C_lock() == ESP_OK)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
if (!(reg & I2C_NO_REG))
{
/* When reading specific register set the addr pointer first. */
i2c_master_start(cmd);
i2c_send_address(cmd, addr, I2C_MASTER_WRITE);
i2c_send_register(cmd, reg);
}
/* Read size bytes from the current pointer. */
i2c_master_start(cmd);
i2c_send_address(cmd, addr, I2C_MASTER_READ);
i2c_master_read(cmd, buffer, size, I2C_MASTER_LAST_NACK);
// printf("i2c_master_start: %d, size:%d, buffer: %X, %X\r\n",result, size, buffer[0], buffer[1]);
i2c_master_stop(cmd);
result = i2c_master_cmd_begin(LV_I2C_TOUCH_PORT, cmd, timeout);
i2c_cmd_link_delete(cmd);
I2C_unlock();
}
else
{
ESP_LOGE(TAG, "Lock could not be obtained for port %d.", (int)LV_I2C_TOUCH_PORT);
return ESP_ERR_TIMEOUT;
}
if (result != ESP_OK)
{
ESP_LOGW(TAG, "Touch Error: %d", result);
}
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, size, ESP_LOG_VERBOSE);
return result;
}
esp_err_t I2C_write(uint16_t addr, uint32_t reg, const uint8_t *buffer, uint16_t size)
{
esp_err_t result;
ESP_LOGV(TAG, "Writing port %d, addr 0x%03x, reg 0x%04x", LV_I2C_TOUCH_PORT, addr, reg);
TickType_t timeout = 20;
if (I2C_lock() == ESP_OK)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_send_address(cmd, addr, I2C_MASTER_WRITE);
if (!(reg & I2C_NO_REG))
{
i2c_send_register(cmd, reg);
}
i2c_master_write(cmd, (uint8_t *)buffer, size, ACK_CHECK_EN);
i2c_master_stop(cmd);
result = i2c_master_cmd_begin(LV_I2C_TOUCH_PORT, cmd, timeout);
i2c_cmd_link_delete(cmd);
I2C_unlock();
}
else
{
ESP_LOGE(TAG, "Lock could not be obtained for port %d.", (int)LV_I2C_TOUCH_PORT);
return ESP_ERR_TIMEOUT;
}
if (result != ESP_OK)
{
ESP_LOGW(TAG, "Error: %d", result);
}
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, size, ESP_LOG_VERBOSE);
return result;
}
// static esp_err_t I2C_close() {
// vSemaphoreDelete(I2C_0_mutex);
// I2C_0_mutex= NULL;
// ESP_LOGI(TAG, "Closing I2C master at port 0.");
// return i2c_driver_delete(LV_I2C_TOUCH_PORT);
// }
esp_err_t I2C_lock()
{
ESP_LOGV(TAG, "Mutex lock set for 0.");
TickType_t timeout = 2; // 原设置是20/10
if (xSemaphoreTake(I2C_0_mutex, timeout) == pdTRUE)
{
return ESP_OK;
}
else
{
ESP_LOGE(TAG, "Removing stale mutex lock from port 0.");
I2C_force_unlock();
return (xSemaphoreTake(I2C_0_mutex, timeout) == pdTRUE ? ESP_OK : ESP_FAIL);
}
}
esp_err_t I2C_unlock()
{
ESP_LOGV(TAG, "Mutex lock removed for 0.");
return (xSemaphoreGive(I2C_0_mutex) == pdTRUE) ? ESP_OK : ESP_FAIL;
}
esp_err_t I2C_force_unlock()
{
if (I2C_0_mutex)
{
vSemaphoreDelete(I2C_0_mutex);
}
I2C_0_mutex = xSemaphoreCreateMutex();
return ESP_OK;
}
/**
* @brief I2C读数据
*
* @param register_addr
* @param data_buf
* @param len
* @return esp_err_t
*/
esp_err_t ft5462_i2c_read(uint16_t register_addr, uint8_t *data_buf, uint8_t len)
{
return I2C_read(ft5426.i2c_dev_addr, register_addr, data_buf, len);
}
/**
* @brief I2C写数据
*
* @param register_addr
* @param data_buf
* @param len
* @return esp_err_t
*/
esp_err_t ft5462_i2c_write(uint16_t register_addr, uint8_t *data_buf, uint8_t len)
{
return I2C_write(ft5426.i2c_dev_addr, register_addr, data_buf, len);
}
/**
* @brief 触摸扫描
*
* @param drv
* @param data
* @return true
* @return false
*/
static void touch_driver_read(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
// printf("touch_driver_read\r\n");
uint8_t temp[4];
ft5462_i2c_read(FT_REG_NUM_FINGER, temp, 1);
if (temp[0] != 1)
{
// 如果触点不是1个,或者没有按下的时候
data->point.x = 0;
data->point.y = 0;
data->state = LV_INDEV_STATE_REL;
return;
}
// 如果只有一个触点的时候
ft5462_i2c_read(FT_TP1_REG, temp, 4);
data->point.y = ((uint16_t)(temp[0] & 0X0F) << 8) + temp[1];
data->point.x = ((uint16_t)(temp[2] & 0X0F) << 8) + temp[3];
data->state = LV_INDEV_STATE_PR;
data->continue_reading = false;
return;
}
// static void ft5462_i2c_read_touch_point(uint16_t * x, uint16_t * y)
// {
// uint8_t temp[4];
// ft5462_i2c_read(FT_REG_NUM_FINGER, temp, 1);
// printf("FT_REG_NUM_FINGER: %d \r\n", temp[0]);
// ft5462_i2c_read(FT_TP1_REG, temp, 4);
// *y = ((uint16_t)(temp[0]&0X0F)<<8) + temp[1];
// *x = ((uint16_t)(temp[2]&0X0F)<<8) + temp[3];
// }
/**
* @brief 初始化FT5426
*
* @param dev_addr
*/
void ft5426_init(uint8_t dev_addr)
{
if (!ft5426.inited)
{
I2C_init(dev_addr);
ft5426.i2c_dev_addr = dev_addr;
// 设置RES移交
gpio_config_t gpio_configure = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << TOUCH_FT5624_I2C_RST,
.pull_down_en = 0,
.pull_up_en = 0};
gpio_config(&gpio_configure);
// 复位
gpio_set_level(TOUCH_FT5624_I2C_RST, 0);
vTaskDelay(20 / portTICK_PERIOD_MS);
gpio_set_level(TOUCH_FT5624_I2C_RST, 1);
vTaskDelay(50 / portTICK_PERIOD_MS);
// 读取版本号
uint8_t temp[5];
memset(temp, 0, 5);
ft5462_i2c_read(FT_ID_G_LIB_VERSION, temp, 2);
// 进入正常操作模式
ft5462_i2c_write(FT_DEVIDE_MODE, temp, 1);
// 查询模式
ft5462_i2c_write(FT_ID_G_MODE, temp, 1);
// 设置触摸有效值
temp[0] = 22;
ft5462_i2c_write(FT_ID_G_THGROUP, temp, 1);
// 设置激活周期
temp[0] = 12;
ft5462_i2c_write(FT_ID_G_PERIODACTIVE, temp, 1);
ft5426.inited = true;
// while (1)
// {
// uint16_t x=0,y=0;
// ft5462_i2c_read_touch_point(&x, &y);
// printf("POINT: (%d, %d)\r\n", x, y);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
// }
}
}
/**
* @brief 触摸驱动初始化
*
*/
void RGB_LCD_touch_init()
{
ft5426_init(FT5426_I2C_SLAVE_ADDR);
// 触摸驱动控制
lv_indev_drv_t *touch_drv;
touch_drv = pvPortMalloc(sizeof(lv_indev_drv_t));
lv_indev_drv_init(touch_drv);
touch_drv->type = LV_INDEV_TYPE_POINTER;
touch_drv->read_cb = touch_driver_read;
lv_indev_drv_register(touch_drv);
}
void example_lvgl_demo_ui(lv_obj_t *scr);
/**
* @brief LVGL刷新回调函数
* @param drv LVGL显示驱动对象
* @param area 需要刷新的区域
* @param color_map 颜色映射表
*/
static void rgb_lcd_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)drv->user_data;
// 将缓冲区的内容复制到显示器的特定区域
esp_lcd_panel_draw_bitmap(panel_handle, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map);
// 通知LVGL库,显示器已经刷新完毕
lv_disp_flush_ready(drv);
}
/**
* @brief LVGL延时回调函数
*/
static void rgb_lcd_increase_lvgl_tick(void *arg)
{
// 通知LVGL库,经过了一段时间(2ms)
lv_tick_inc(RGB_LCD_LVGL_TICK_PERIOD_MS);
}
void rgb_lcd_init(void){
static lv_disp_draw_buf_t disp_buf; // 显示缓冲区对象,用于存储绘图数据
static lv_disp_drv_t disp_drv; // 显示配置结构体,其中包含绘图缓冲区对象和显示回调函数
// 设置背光GPIO,这里应该通过PWM控制,或者通过三极管控制
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << RGB_LCD_PIN_NUM_BK_LIGHT};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
// 安装RGB面板驱动
ESP_LOGI(TAG, "Install RGB panel driver");
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // 使用 RGB565 配色方案
.psram_trans_align = 64, // PSRAM 传输对齐
.clk_src = LCD_CLK_SRC_PLL240M, // 设置时钟源
.disp_gpio_num = RGB_LCD_PIN_NUM_DISP_EN, // 设置显示使能引脚
.pclk_gpio_num = RGB_LCD_PIN_NUM_PCLK, // 设置像素时钟引脚
.vsync_gpio_num = RGB_LCD_PIN_NUM_VSYNC, // 设置垂直同步引脚
.hsync_gpio_num = RGB_LCD_PIN_NUM_HSYNC, // 设置水平同步引脚
.de_gpio_num = RGB_LCD_PIN_NUM_DE, // 设置数据使能引脚
.data_gpio_nums = {
// 设置数据引脚
RGB_LCD_PIN_NUM_DATA0, // B0
RGB_LCD_PIN_NUM_DATA1, // B1
RGB_LCD_PIN_NUM_DATA2, // B2
RGB_LCD_PIN_NUM_DATA3, // B3
RGB_LCD_PIN_NUM_DATA4, // B4
RGB_LCD_PIN_NUM_DATA5, // G0
RGB_LCD_PIN_NUM_DATA6, // G1
RGB_LCD_PIN_NUM_DATA7, // G2
RGB_LCD_PIN_NUM_DATA8, // G3
RGB_LCD_PIN_NUM_DATA9, // G4
RGB_LCD_PIN_NUM_DATA10, // G5
RGB_LCD_PIN_NUM_DATA11, // R0
RGB_LCD_PIN_NUM_DATA12, // R1
RGB_LCD_PIN_NUM_DATA13, // R2
RGB_LCD_PIN_NUM_DATA14, // R3
RGB_LCD_PIN_NUM_DATA15, // R4
},
.timings = {
// 设置时序参数
.pclk_hz = 18*1000*1000, // 设置像素时钟频率
.h_res = RGB_LCD_LCD_H_RES, // 设置水平分辨率
.v_res = RGB_LCD_LCD_V_RES, // 设置垂直分辨率
.hsync_back_porch = 0,//43
.hsync_front_porch = 8,
.hsync_pulse_width = 2,
.vsync_back_porch = 0, //15
.vsync_front_porch = 12,//12
.vsync_pulse_width = 10,//10
.flags.pclk_active_neg = false,
},
.flags.fb_in_psram = 1, // allocate frame buffer in PSRAM
};
// 创建RGB面板,并将其句柄保存到 panel_handle 变量中,panel_handle用于传入显示回调函数中,在回调函数中进行显示操作
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
// 初始化RGB面板
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// 打开背光
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(RGB_LCD_PIN_NUM_BK_LIGHT, 1);
// 初始化LVGL库
ESP_LOGI(TAG, "Initialize LVGL library");
lv_init();
// 初始化LVGL显示驱动
// 首先在 SPIRAM 中分配双缓冲区,然后将其传递给 lv_disp_draw_buf_init 函数,以初始化显示缓冲区对象。
// 最后,将显示缓冲区对象传递给 lv_disp_drv_init 函数,以初始化显示驱动对象。
static size_t BUF_SIZE = RGB_LCD_LCD_H_RES * RGB_LCD_LCD_V_RES * sizeof(lv_color_t);
lv_color_t *buf1 = heap_caps_malloc(BUF_SIZE, MALLOC_CAP_SPIRAM);
lv_color_t *buf2 = heap_caps_malloc(BUF_SIZE, MALLOC_CAP_SPIRAM);
// lv_color_t *buf1 = heap_caps_malloc(RGB_LCD_LCD_H_RES * BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
// lv_color_t *buf2 = heap_caps_malloc(RGB_LCD_LCD_H_RES * BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf1);
assert(buf2);
/* 初始化LVGL显示缓冲区
* LVGL 使用双缓冲技术来实现平滑的屏幕刷新。它需要两个显示缓冲区来进行绘制和显示操作。当一个缓冲区用于显示时,另一个缓冲区可以同时进行绘制和更新操作,以提高渲染效率。
* 具体来说,buf1 和 buf2 是两个指针变量,它们分别指向两个不同的显示缓冲区。每个缓冲区都是一块内存区域,用于存储屏幕上每个像素的颜色值或绘图元素的数据。
* 在初始化显示缓冲区对象时,你需要为 buf1 和 buf2 提供相应的内存地址。这些内存地址应该是事先分配和配置好的,以便 LVGL 在运行时可以向这些缓冲区中写入绘制数据。
* 在屏幕刷新时,LVGL 会根据需要在这两个缓冲区之间进行切换,使绘制和显示操作能够同时进行,并实现平滑的屏幕更新效果。
* 需要注意的是,在使用 lv_disp_draw_buf_init 初始化显示缓冲区对象之前,你需要先确保已经正确分配和配置好 buf1 和 buf2 所需的内存空间。
*/
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, RGB_LCD_LCD_H_RES * RGB_LCD_LCD_V_RES);
// uint8_t *img_buf = malloc(RGB_LCD_LCD_H_RES * RGB_LCD_LCD_V_RES * sizeof(lv_color_t));
// lv_disp_draw_buf_init(&disp_buf, img_buf, NULL, RGB_LCD_LCD_H_RES * RGB_LCD_LCD_V_RES);
// ESP_LOGI(TAG, "buf1 @%p, buf2 @%p", buf1, buf2);
// ESP_LOGI(TAG, "disp_buf @%p", disp_buf);
// 初始化LVGL显示驱动
ESP_LOGI(TAG, "Register display driver to LVGL");
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = RGB_LCD_LCD_H_RES; // 设置水平分辨率
disp_drv.ver_res = RGB_LCD_LCD_V_RES; // 设置垂直分辨率
disp_drv.flush_cb = rgb_lcd_lvgl_flush_cb; // 设置刷新回调函数
disp_drv.draw_buf = &disp_buf; // 设置显示缓冲区
disp_drv.user_data = panel_handle; // 设置用户数据,这里设置为RGB面板句柄,用于在回调函数中的显示操作
/* lv_disp_drv_t disp_drv 结构体配置项如下:
* hor_res :水平分辨率
* ver_res :垂直分辨率
* physical_hor_res :物理显示的水平分辨率
* physical_ver_res :物理显示的垂直分辨率
* offset_x :水平偏移量
* offset_y :垂直偏移量
* draw_buf :用于绘制屏幕内容的缓冲区指针
* direct_mode :是否使用以屏幕大小为基础的缓冲区和绘制绝对坐标(默认 是)
* full_refresh :是否总是重新绘制整个屏幕(默认 是)
* sw_rotate :是否使用软件旋转(速度较慢)(默认 是)
* antialiasing :是否启用抗锯齿(默认 是)
* rotated :是否旋转显示屏90度(默认 1,将显示器旋转90度)
* screen_transp :如果屏幕没有固定的不透明背景,则处理透明屏幕(默认 是)
* dpi :显示屏的DPI(每英寸点数)(默认 10)
* flush_cb :将内部缓冲区的内容写入显示屏的回调函数
* rounder_cb :根据显示驱动程序的要求调整无效区域的回调函数
* set_px_cb :根据显示的特殊要求,在缓冲区中设置像素的回调函数
* clear_cb :清除缓冲区的回调函数
* monitor_cb :在刷新周期后调用,用于监控渲染和刷新时间以及刷新的像素数
* wait_cb :在lvgl等待操作完成时定期调用的回调函数
* clean_dcache_cb :在需要清除影响渲染的CPU缓存时调用的回调函数
* drv_update_cb : 当驱动程序参数更新时调用的回调函数
* color_chroma_key :在 CHROMA_KEYED 图像中,该颜色将是透明的
* draw_ctx、draw_ctx_init、draw_ctx_deinit、draw_ctx_size :绘制上下文相关的成员变量
*
* 可以尝试调整 full_refresh、antialiasing 和 sw_rotate 参数,观察屏幕刷新效果。
* 可以尝试调整 dpi 参数,观察屏幕显示效果。
*/
/* 注册显示驱动
* lv_disp_drv_register 是 LittlevGL(LVGL)图形库中的一个函数,用于注册显示驱动器并将其与一个显示设备(屏幕)关联起来。
* 在使用 LVGL 进行图形界面开发时,需要将图形库与底层的硬件显示设备进行连接和协调。lv_disp_drv_register 函数的作用就是创建一个显示驱动器,并将其与一个特定的显示设备进行关联。
* LVGL 操作屏幕的时候,根据该参数进行操作,如果需要显示到屏幕上,将会回调 disp_drv.flush_cb 与硬件进行实际交互,届时会把 disp_drv.user_data 作为参数传入
*/
lv_disp_t *disp = lv_disp_drv_register(&disp_drv);
// 初始化LVGL触摸驱动(IIC)
RGB_LCD_touch_init();
ESP_LOGI(TAG, "Install LVGL tick timer");
/* LVGL的Tick接口(使用esp_timer生成2ms周期性事件),每过2ms更新一次数据
* 在每次调用 lv_tick_inc 函数时,LVGL 会根据传入的时间间隔(period),将内部时钟计数值增加相应的量。
* 这样,LVGL 就可以根据自己的时钟计数来进行事件处理和界面更新,保证了图形界面的实时性和流畅性。
*/
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &rgb_lcd_increase_lvgl_tick,
.name = "lvgl_tick"};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer)); // 创建定时器
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, RGB_LCD_LVGL_TICK_PERIOD_MS * 1000)); // 启动定时器
// 获取当前活动的屏幕(Screen)对象
ESP_LOGI(TAG, "Display LVGL Scatter Chart...");
// lv_obj_t *scr = lv_disp_get_scr_act(disp);
// example_lvgl_demo_ui(lv_disp_get_scr_act(disp));
// example_lvgl_demo_ui(lv_scr_act());
// test();
// lv_demo_music();
// lv_demo_widgets();
ESP_LOGI(TAG, "LV_USE_DEMO_WIDGETS %d", LV_USE_DEMO_WIDGETS);
while (1)
{
// 提高LVGL的任务优先级和/或缩短处理程序周期可以提高性能
vTaskDelay(pdMS_TO_TICKS(10));
/* 在使用 LVGL 进行图形界面开发时,定时器可以用于实现定时刷新、动画效果、延时操作等功能。lv_timer_handler 函数的作用是处理定时器事件,并执行与定时器相关的操作。
*
* 通过调用 lv_timer_handler 函数,可以触发 LVGL 的定时器机制,使得注册的定时器回调函数得到调用。在这些定时器回调函数中,可以编写相应的逻辑代码,实现定时执行特定任务的功能。
* 一般来说,lv_timer_handler 函数会被周期性地调用,以处理过期的定时器事件。在每次调用 lv_timer_handler 时,LVGL 会检查所有已注册的定时器,若有定时器超过设定的时间,则会触发相应的回调函数。
* 可以通过提升任务优先级,或者修改上面的延时时间加速处理,但一般来讲 运行lv_timer_handler的任务的优先级应低于运行“lv_tick_inc”的任务`
*/
lv_timer_handler();
}
}
// static void btn_event_cb(lv_event_t * e)
// {
// printf("click button : %d \r\n", (int)e->user_data);
// }
void example_lvgl_demo_ui(lv_obj_t *scr)
{
// 添加一组按钮
// for(int i=0; i<6; i++){
// lv_obj_t * btn = lv_btn_create(scr);
// lv_obj_set_pos(btn, 15+i*130, 15);
// lv_obj_set_size(btn, 120,120);
// lv_obj_add_event_cb(btn, btn_event_cb,LV_EVENT_CLICKED,(void *)i);
// lv_obj_t *lab =lv_label_create(btn);
// char name[6];
// sprintf(name, "BTN_%d", i);
// lv_label_set_text(lab, name);
// lv_obj_align(lab, LV_ALIGN_CENTER, 0, 0);
// }
// 添加obj
lv_obj_t * obj = lv_obj_create(scr);
lv_obj_set_pos(obj, 10,10);
lv_obj_set_size(obj,120,120);
}
.h文件代码如下:
#pragma once
void rgb_lcd_init(void);
设置完成后,开始进行CMake设置,注意是main文件夹下的CMakeLists.txt文件。
首先要定义要编译的文件,如下:
将屏幕的源文件和头文件加入到编译队列。
除此之外,还有一步:
这样,我们的LVGL就移植完成了,之后根据项目和屏幕的需求,在进行更改就好了。