(十二)ESP32-S3之lvgl移植(基于st7789v+cst816)
·

st7789_driver.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_commands.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_err.h"
#include "esp_log.h"
#include "st7789_driver.h"
#include "lvgl/lvgl.h"
#define LCD_SPI_HOST SPI2_HOST
static const char* TAG = "st7789";
//lcd操作句柄
static esp_lcd_panel_io_handle_t lcd_io_handle = NULL;
//刷新完成回调函数
static lcd_flush_done_cb s_flush_done_cb = NULL;
//背光GPIO
static gpio_num_t s_bl_gpio = -1;
static bool notify_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
if(s_flush_done_cb)
s_flush_done_cb(user_ctx);
return false;
}
/** st7789初始化
* @param st7789_cfg_t 接口参数
* @return 成功或失败
*/
esp_err_t st7789_driver_hw_init(st7789_cfg_t* cfg)
{
//初始化SPI
spi_bus_config_t buscfg = {
.sclk_io_num = cfg->clk, //SCLK引脚
.mosi_io_num = cfg->mosi, //MOSI引脚
.miso_io_num = -1,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.flags = SPICOMMON_BUSFLAG_MASTER , //SPI主模式
.max_transfer_sz = cfg->width * 40 * sizeof(uint16_t), //DMA单次传输最大字节,最大32768
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
s_flush_done_cb = cfg->done_cb; //设置刷新完成回调函数
s_bl_gpio = cfg->bl; //设置背光GPIO
//初始化GPIO(BL)
gpio_config_t bl_gpio_cfg =
{
.pull_up_en = GPIO_PULLUP_DISABLE, //禁止上拉
.pull_down_en = GPIO_PULLDOWN_DISABLE, //禁止下拉
.mode = GPIO_MODE_OUTPUT, //输出模式
.intr_type = GPIO_INTR_DISABLE, //禁止中断
.pin_bit_mask = (1<<cfg->bl) //GPIO脚
};
gpio_config(&bl_gpio_cfg);
//初始化复位脚
if(cfg->rst > 0)
{
gpio_config_t rst_gpio_cfg =
{
.pull_up_en = GPIO_PULLUP_DISABLE, //禁止上拉
.pull_down_en = GPIO_PULLDOWN_DISABLE, //禁止下拉
.mode = GPIO_MODE_OUTPUT, //输出模式
.intr_type = GPIO_INTR_DISABLE, //禁止中断
.pin_bit_mask = (1<<cfg->rst) //GPIO脚
};
gpio_config(&rst_gpio_cfg);
}
//创建基于spi的lcd操作句柄
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = cfg->dc, //DC引脚
.cs_gpio_num = cfg->cs, //CS引脚
.pclk_hz = cfg->spi_fre, //SPI时钟频率
.lcd_cmd_bits = 8, //命令长度
.lcd_param_bits = 8, //参数长度
.spi_mode = 0, //使用SPI0模式
.trans_queue_depth = 10, //表示可以缓存的spi传输事务队列深度
.on_color_trans_done = notify_flush_ready, //刷新完成回调函数
.user_ctx = cfg->cb_param, //回调函数参数
.flags = { // 以下为 SPI 时序的相关参数,需根据 LCD 驱动 IC 的数据手册以及硬件的配置确定
.sio_mode = 0, // 通过一根数据线(MOSI)读写数据,0: Interface I 型,1: Interface II 型
},
};
// Attach the LCD to the SPI bus
ESP_LOGI(TAG,"create esp_lcd_new_panel");
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, &lcd_io_handle));
//硬件复位
if(cfg->rst > 0)
{
gpio_set_level(cfg->rst,0);
vTaskDelay(pdMS_TO_TICKS(20));
gpio_set_level(cfg->rst,1);
vTaskDelay(pdMS_TO_TICKS(20));
}
/*向LCD写入初始化命令*/
esp_lcd_panel_io_tx_param(lcd_io_handle,LCD_CMD_SWRESET,NULL,0); //软件复位
vTaskDelay(pdMS_TO_TICKS(150));
esp_lcd_panel_io_tx_param(lcd_io_handle,LCD_CMD_SLPOUT,NULL,0); //退出休眠模式
vTaskDelay(pdMS_TO_TICKS(200));
esp_lcd_panel_io_tx_param(lcd_io_handle,LCD_CMD_COLMOD,(uint8_t[]) {0x55,}, 1); //选择RGB数据格式,0x55:RGB565,0x66:RGB666
esp_lcd_panel_io_tx_param(lcd_io_handle, 0xb0, (uint8_t[]) {0x00, 0xF0},2);
esp_lcd_panel_io_tx_param(lcd_io_handle,LCD_CMD_INVON,NULL,0); //颜色翻转
esp_lcd_panel_io_tx_param(lcd_io_handle,LCD_CMD_NORON,NULL,0); //普通显示模式
uint8_t spin_type = 0;
switch(cfg->spin)
{
case 0:
spin_type = 0x00; //不旋转
break;
case 1:
spin_type = 0x60; //顺时针90
break;
case 2:
spin_type = 0xC0; //180
break;
case 3:
spin_type = 0xA0; //顺时针270,(逆时针90)
break;
default:break;
}
esp_lcd_panel_io_tx_param(lcd_io_handle,LCD_CMD_MADCTL,(uint8_t[]) {spin_type,}, 1); //屏旋转方向
vTaskDelay(pdMS_TO_TICKS(150));
esp_lcd_panel_io_tx_param(lcd_io_handle,LCD_CMD_DISPON,NULL,0); //打开显示
vTaskDelay(pdMS_TO_TICKS(300));
return ESP_OK;
}
/** st7789写入显示数据
* @param x1,x2,y1,y2:显示区域
* @return 无
*/
void st7789_flush(int x1,int x2,int y1,int y2,void *data)
{
// define an area of frame memory where MCU can access
if(x2 <= x1 || y2 <= y1)
{
if(s_flush_done_cb)
s_flush_done_cb(NULL);
return;
}
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_CASET, (uint8_t[]) {
(x1 >> 8) & 0xFF,
x1 & 0xFF,
((x2 - 1) >> 8) & 0xFF,
(x2 - 1) & 0xFF,
}, 4);
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_RASET, (uint8_t[]) {
(y1 >> 8) & 0xFF,
y1 & 0xFF,
((y2 - 1) >> 8) & 0xFF,
(y2 - 1) & 0xFF,
}, 4);
// transfer frame buffer
size_t len = (x2 - x1) * (y2 - y1) * 2;
esp_lcd_panel_io_tx_color(lcd_io_handle, LCD_CMD_RAMWR, data, len);
return ;
}
/** 控制背光
* @param enable 是否使能背光
* @return 无
*/
void st7789_lcd_backlight(bool enable)
{
if(enable)
{
gpio_set_level(s_bl_gpio,1);
}
else
{
gpio_set_level(s_bl_gpio,0);
}
}
st7789_driver.h
#ifndef _ST7789_DRIVER_H_
#define _ST7789_DRIVER_H_
#include "driver/gpio.h"
#include "esp_err.h"
typedef void(*lcd_flush_done_cb)(void* param);
typedef struct
{
gpio_num_t mosi; //数据
gpio_num_t clk; //时钟
gpio_num_t cs; //片选
gpio_num_t dc; //命令
gpio_num_t rst; //复位
gpio_num_t bl; //背光
uint32_t spi_fre; //spi总线速率
uint16_t width; //宽
uint16_t height; //长
uint8_t spin; //选择方向(0不旋转,1顺时针旋转90, 2旋转180,3顺时针旋转270)
lcd_flush_done_cb done_cb; //数据传输完成回调函数
void* cb_param; //回调函数参数
}st7789_cfg_t;
/** st7789初始化
* @param st7789_cfg_t 接口参数
* @return 成功或失败
*/
esp_err_t st7789_driver_hw_init(st7789_cfg_t* cfg);
/** st7789写入显示数据
* @param x1,x2,y1,y2:显示区域
* @return 无
*/
void st7789_flush(int x1,int x2,int y1,int y2,void *data);
/** 控制背光
* @param enable 是否使能背光
* @return 无
*/
void st7789_lcd_backlight(bool enable);
#endif
cst816t_driver.c
#include "cst816t_driver.h"
#include "driver/i2c.h"
//#include "driver/i2c_master.h"
#include "esp_log.h"
#define TOUCH_I2C_PORT I2C_NUM_0
#define CST816T_ADDR 0x15
static const char *TAG = "cst816t";
//边界值
static uint16_t s_usLimitX = 0;
static uint16_t s_usLimitY = 0;
static esp_err_t i2c_read(uint8_t slave_addr, uint8_t register_addr, uint8_t read_len,uint8_t *data_buf);
/** CST816T初始化
* @param cfg 配置
* @return err
*/
esp_err_t cst816t_init(cst816t_cfg_t* cfg)
{
int i2c_master_port = TOUCH_I2C_PORT;
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = cfg->sda,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = cfg->scl,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = cfg->fre,
};
s_usLimitX = cfg->x_limit;
s_usLimitY = cfg->y_limit;
ESP_ERROR_CHECK(i2c_param_config(i2c_master_port, &conf));
ESP_ERROR_CHECK(i2c_driver_install(i2c_master_port, conf.mode, 0, 0, 0));
uint8_t data_buf;
i2c_read(CST816T_ADDR, 0xA7, 1,&data_buf);
ESP_LOGI(TAG, "\tChip ID: 0x%02x", data_buf);
i2c_read(CST816T_ADDR, 0xA9, 1,&data_buf);
ESP_LOGI(TAG, "\tFirmware version: 0x%02x", data_buf);
i2c_read(CST816T_ADDR, 0xAA, 1,&data_buf);
ESP_LOGI(TAG, "\tFactory ID: 0x%02x", data_buf);
return ESP_OK;
}
/** 读取坐标值
* @param x x坐标
* @param y y坐标
* @param state 松手状态 0,松手 1按下
* @return 无
*/
void cst816t_read(int16_t *x,int16_t *y,int *state)
{
uint8_t data_x[2]; // 2 bytesX
uint8_t data_y[2]; // 2 bytesY
uint8_t touch_pnt_cnt = 0; // Number of detected touch points
static int16_t last_x = 0; // 12bit pixel value
static int16_t last_y = 0; // 12bit pixel value
i2c_read(CST816T_ADDR, 0x02,1, &touch_pnt_cnt);
if (touch_pnt_cnt != 1) { // ignore no touch & multi touch
*x = last_x;
*y = last_y;
*state = 0;
return;
}
//读取X坐标
i2c_read(CST816T_ADDR,0x03,2,data_x);
//读取Y坐标
i2c_read(CST816T_ADDR,0x05,2,data_y);
int16_t current_x = ((data_x[0] & 0x0F) << 8) | (data_x[1] & 0xFF);
int16_t current_y = ((data_y[0] & 0x0F) << 8) | (data_y[1] & 0xFF);
if(last_x != current_x || current_y != last_y)
{
last_x = current_x;
last_y = current_y;
//ESP_LOGI(TAG,"touch x:%d,y:%d",last_x,last_y);
}
if(last_x >= s_usLimitX)
last_x = s_usLimitX - 1;
if(last_y >= s_usLimitY)
last_y = s_usLimitY - 1;
*x = last_x;
*y = last_y;
*state = 1;
}
/** 根据寄存器地址读取N字节
* @param slave_addr 器件地址
* @param register_addr 寄存器地址
* @param read_len 要读取的数据长度
* @param data_buf 数据
* @return err
*/
static esp_err_t i2c_read(uint8_t slave_addr, uint8_t register_addr, uint8_t read_len,uint8_t *data_buf)
{
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
if(!i2c_cmd)
{
ESP_LOGE(TAG, "Error i2c_cmd creat fail!");
return ESP_FAIL;
}
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, (slave_addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(i2c_cmd, register_addr, I2C_MASTER_ACK);
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, (slave_addr << 1) | I2C_MASTER_READ, true);
for(int i = 0;i < read_len;i++)
{
if(i == read_len - 1)
i2c_master_read_byte(i2c_cmd, &data_buf[i], I2C_MASTER_NACK);
else
i2c_master_read_byte(i2c_cmd, &data_buf[i], I2C_MASTER_ACK);
}
i2c_master_stop(i2c_cmd);
esp_err_t ret = i2c_master_cmd_begin(TOUCH_I2C_PORT, i2c_cmd, pdMS_TO_TICKS(1000));
i2c_cmd_link_delete(i2c_cmd);
return ret;
}
cst816t_driver.h
#ifndef _CST816T_DRIVER_H_
#define _CST816T_DRIVER_H_
#include "driver/gpio.h"
#include "esp_err.h"
//CST816T 触摸IC驱动
typedef struct
{
gpio_num_t scl; //SCL管脚
gpio_num_t sda; //SDA管脚
uint32_t fre; //I2C速率
uint16_t x_limit; //X方向触摸边界
uint16_t y_limit; //y方向触摸边界
}cst816t_cfg_t;
/** CST816T初始化
* @param cfg 配置
* @return err
*/
esp_err_t cst816t_init(cst816t_cfg_t* cfg);
/** 读取坐标值
* @param x x坐标
* @param y y坐标
* @param state 松手状态 0,松手 1按下
* @return 无
*/
void cst816t_read(int16_t *x,int16_t *y,int *state);
#endif
main.c
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "lv_port.h"
#include "lvgl.h"
#include "lv_demos.h"
#include "st7789_driver.h"
// 主函数
void app_main(void)
{
lv_port_init(); //初始化LVGL
lv_demo_widgets(); //初始化控件demo程序
//lv_demo_stress();
st7789_lcd_backlight(true); //打开背光
while(1)
{
vTaskDelay(pdMS_TO_TICKS(10));
lv_task_handler(); //LVGL循环处理
}
}
lv_port.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "driver/gpio.h"
#include "lv_port.h"
#include "lvgl.h"
#include "st7789_driver.h"
#include "cst816t_driver.h"
static lv_disp_drv_t disp_drv;
static const char *TAG = "lv_port";
#define LCD_WIDTH 240
#define LCD_HEIGHT 280
/**
* @brief LVGL定时器时钟
*
* @param pvParam 无用
*/
static void lv_tick_inc_cb(void *data)
{
uint32_t tick_inc_period_ms = *((uint32_t *) data);
lv_tick_inc(tick_inc_period_ms);
}
/**
* @brief 通知LVGL写入数据完毕
*/
static void lv_port_flush_ready(void* param)
{
lv_disp_flush_ready(&disp_drv);
/* portYIELD_FROM_ISR (true) or not (false). */
}
/**
* @brief 写入显示数据
*
* @param disp_drv 对应的显示器
* @param area 显示区域
* @param color_p 显示数据
*/
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
(void) disp_drv;
// 当spin=0时应移除X轴偏移
st7789_flush(area->x1 /* + 20 */, area->x2 + 1 /* + 20 */, area->y1+20, area->y2 + 1+20, color_p);
}
/**
* @brief 注册LVGL显示驱动
*
*/
static void lv_port_disp_init(void)
{
static lv_disp_draw_buf_t draw_buf_dsc;
size_t disp_buf_height = 40;
/* 必须从内部RAM分配显存,这样刷新速度快 */
lv_color_t *p_disp_buf1 = heap_caps_malloc(LCD_WIDTH * disp_buf_height * sizeof(lv_color_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
lv_color_t *p_disp_buf2 = heap_caps_malloc(LCD_WIDTH * disp_buf_height * sizeof(lv_color_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
ESP_LOGI(TAG, "Try allocate two %u * %u display buffer, size:%u Byte", LCD_WIDTH, disp_buf_height, LCD_WIDTH * disp_buf_height * sizeof(lv_color_t) * 2);
if (NULL == p_disp_buf1 || NULL == p_disp_buf2) {
ESP_LOGE(TAG, "No memory for LVGL display buffer");
esp_system_abort("Memory allocation failed");
}
/* 初始化显示缓存 */
lv_disp_draw_buf_init(&draw_buf_dsc, p_disp_buf1, p_disp_buf2, LCD_WIDTH * disp_buf_height);
/* 初始化显示驱动 */
lv_disp_drv_init(&disp_drv);
/*设置水平和垂直宽度*/
disp_drv.hor_res = LCD_WIDTH; //水平宽度
disp_drv.ver_res = LCD_HEIGHT; //垂直宽度
/* 设置刷新数据函数 */
disp_drv.flush_cb = disp_flush;
/*设置显示缓存*/
disp_drv.draw_buf = &draw_buf_dsc;
/*注册显示驱动*/
lv_disp_drv_register(&disp_drv);
}
/**
* @brief 获取触摸坐标
*
* @param indev_drv 触摸驱动
* @param data 数据
* @return 无
*/
void IRAM_ATTR indev_read(struct _lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
int16_t x,y;
int state;
cst816t_read(&x,&y,&state);
//data->point.x = y;
data->point.x = x;
if(x == 0)
x = 1;
//data->point.y = LCD_HEIGHT - x;
data->point.y = y;
data->state = state;
}
/**
* @brief 注册LVGL输入驱动
*
* @return esp_err_t
*/
static esp_err_t lv_port_indev_init(void)
{
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = indev_read;
lv_indev_drv_register(&indev_drv);
return ESP_OK;
}
/**
* @brief 创建LVGL定时器
*
* @return esp_err_t
*/
static esp_err_t lv_port_tick_init(void)
{
static uint32_t tick_inc_period_ms = 5;
const esp_timer_create_args_t periodic_timer_args = {
.callback = lv_tick_inc_cb,
.name = "",
.arg = &tick_inc_period_ms,
.dispatch_method = ESP_TIMER_TASK,
.skip_unhandled_events = true,
};
esp_timer_handle_t periodic_timer;
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, tick_inc_period_ms * 1000));
return ESP_OK;
}
/**
* @brief LCD接口初始化
*
* @return NULL
*/
static void lcd_init(void)
{
st7789_cfg_t st7789_config;
st7789_config.mosi = GPIO_NUM_19;
st7789_config.clk = GPIO_NUM_18;
st7789_config.cs = GPIO_NUM_5;
st7789_config.dc = GPIO_NUM_17;
st7789_config.rst = GPIO_NUM_21;
st7789_config.bl = GPIO_NUM_26;
st7789_config.spi_fre = 40*1000*1000; //SPI时钟频率
st7789_config.width = LCD_WIDTH; //屏宽
st7789_config.height = LCD_HEIGHT; //屏高
st7789_config.spin = 0; //顺时针旋转90度
st7789_config.done_cb = lv_port_flush_ready; //数据写入完成回调函数
st7789_config.cb_param = &disp_drv; //回调函数参数
st7789_driver_hw_init(&st7789_config);
}
/**
* @brief 触摸芯片初始化
*
* @return NULL
*/
static void tp_init(void)
{
cst816t_cfg_t cst816t_config;
cst816t_config.sda = GPIO_NUM_43;
cst816t_config.scl = GPIO_NUM_42;
//cst816t_config.x_limit = LCD_HEIGHT; //由于屏幕显示旋转了90°,X和Y触摸需要调转
//cst816t_config.y_limit = LCD_WIDTH;
cst816t_config.x_limit = LCD_WIDTH;
cst816t_config.y_limit = LCD_HEIGHT;
cst816t_config.fre = 200*1000;
cst816t_init(&cst816t_config);
}
esp_err_t lv_port_init(void)
{
/* 初始化LVGL库 */
lv_init();
/*lcd接口初始化*/
lcd_init();
/* 注册显示驱动 */
lv_port_disp_init();
/*触摸芯片初始化*/
tp_init();
/* 注册输入驱动*/
lv_port_indev_init();
/* 初始化LVGL定时器 */
lv_port_tick_init();
return ESP_OK;
}
lv_port.h
/**
* @file lv_port.h
* @brief
* @version 0.1
* @date 2021-06-08
*
* @copyright Copyright 2021 Espressif Systems (Shanghai) Co. Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Init LVGL GUI library
*
* @return
* - ESP_OK: Success
*/
esp_err_t lv_port_init(void);
#ifdef __cplusplus
}
#endif
更多推荐



所有评论(0)