在这里插入图片描述

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

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐