[GUI] ESP32(idf)驱动3.5寸SPI-TFT屏移植LittleVGL

一、展示成果

博客上传图片限制在5M内,视频转 GIF 帧率压缩太严重了,还得再次压缩才小于5M,效果完全失真了
小伙伴们有什么好的工具或方法解决吗

gif 图上应该看不出什么区别。但是ESP32 用60MHz的 spi 驱动跑 lvgl,其卡顿比 stm32 spi 驱动跑 lvgl 明显好很多,,毕竟stm32 spi 也达不到 60MHz嘛

  • 主控:ESP32
  • 开发工具:esp- idf-v4.3
  • LCD 4.3寸 ILI9488
  • 温度传感器:K型热电偶+MAX6675
  • GUI:little VGL v8.1.0
  • 简单的跑个 little VGL 例程而已,下一步再搞点好玩的

ESP32

60MHz SPI 驱动 little VGL v8.1 效果
在这里插入图片描述
在这里插入图片描述

STM32

SPI驱动 little VGL v7.8效果
stm32 spi LCD

二、LCD驱动调试

老样子,直接上代码
// 头文件

#ifndef __SLIM_LCD_H__
#define __SLIM_LCD_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "lvgl/lvgl.h"
//*************************************************************************************************
//		引脚描述
//	CS		--> 15
//	SCK		-->	14
//	SDO		-->	12
//	SDI		-->	13
//	RESET 	-->	
//	DC/RS	-->	
//	LED		-->	
//	T_CLK	--> 
//	T_CS	--> 
//	T_DIN	--> 
//	T_DO	--> 
//	T_IRQ	--> 
//*************************************************************************************************
#ifdef CONFIG_IDF_TARGET_ESP32
#define LCD_SPI     HSPI_HOST
#define GPIO_HANDSHAKE  2
#define PIN_LCD_MISO    12
#define PIN_LCD_MOSI    13
#define PIN_LCD_CLK     14
#define PIN_LCD_CS      15
#define PIN_LCD_RST     25
#define PIN_LCD_DC      26
#define PIN_LCD_LED     27
#endif

//LCD parameter
typedef struct{										    
	uint16_t	width;	//LCD width
	uint16_t	height;	//LCD height
	uint16_t	id;		//LCD ID
	uint8_t		dir;	//0:vertical / 1:Horizontal
	uint16_t	wramcmd;//start write GRAM command
	uint16_t	setxcmd;//setting x axis
	uint16_t	setycmd;//setting y axis	 
}_lcd_dev; 	

extern _lcd_dev lcddev;	
#define USE_HORIZONTAL      1   //0:0 / 1:90 / 2:180 / 3:270
#define USER_BUFF_LEN       1440    //3*480

//画笔颜色
#define WHITE       0xFFFF
#define BLACK      	0x0000	  
#define BLUE       	0x001F  
#define BRED        0XF81F
#define GRED		0XFFE0
#define GBLUE		0X07FF
#define RED         0xF800
#define MAGENTA     0xF81F
#define GREEN       0x07E0
#define CYAN        0x7FFF
#define YELLOW      0xFFE0
#define BROWN		0XBC40	//棕色
#define BRRED		0XFC07	//棕红色
#define GRAY		0X8430	//灰色
//GUI颜色

#define DARKBLUE	0X01CF	//深蓝色
#define LIGHTBLUE	0X7D7C	//浅蓝色  
#define GRAYBLUE	0X5458	//灰蓝色
//以上三色为 PANEL颜色 
 
#define LIGHTGREEN	0X841F	//灰绿色
#define LIGHTGRAY	0XEF5B	//浅灰色(PANNEL)
#define LGRAY		0XC618	//浅灰色(PANNEL),窗体背景色

#define LGRAYBLUE	0XA651 //浅灰蓝色(中间层颜色)
#define LBBLUE		0X2B12 //浅棕蓝色(选择条目的反色)

//brush color and background color
#define POINT_COLOR	0x0000
#define BACK_COLOR	0xFFFF

enum{
	REG = 0,
	DATA,
};
enum{
	OFF = 0,
	ON,
};
//==================================================================================	  
//LCD size
#define LCD_W 320
#define LCD_H 480

void LCD_Init(void);
void LCD_SetCursor(uint16_t Xpos, uint16_t Ypos);
void LCD_Refresh(uint16_t Color);
void LCD_Flush(uint16_t xStart, uint16_t yStart, uint16_t xEnd, uint16_t yEnd, uint16_t *buff);
void LCD_Disp_Flush(const lv_area_t * area, lv_color_t * color_p);

#endif
/**
  *************************************************************************************************
  * @file    : slim_lcd.c
  * @author  : slim 
  * @version : V0.0.1
  * @date    : 2021.07.11
  * @brief   : LCD moduler for ILI9488 IC driver
  *************************************************************************************************
  */
#include "slim_lcd.h"


#define RGB_R(n)	(n>>8)&0xF8
#define RGB_G(n)	(n>>3)&0xFC
#define RGB_B(n)	n<<3

_lcd_dev lcddev;
static spi_device_handle_t spi_handle;
uint8_t GRAM_BUFF[USER_BUFF_LEN] = {0};

//===========================================================================
// 静态函数声明
static void _spi_init(void);
static void _lcd_spi_pre_transfer_callback(spi_transaction_t *trans);
static void _lcd_cmd(uint8_t reg);
static void _lcd_data(uint8_t *buff, uint16_t len);
static void _byte_read(uint8_t *buff, uint16_t len);
static uint32_t _lcd_get_id(void);
static void _lcd_init(void);
static void _lcd_led_set(uint8_t state);
static void _lcd_direction(uint8_t direction);
static void _lcd_write_data_16bit(uint16_t rgb_data);
static void _lcd_SetWindows(uint16_t xStar, uint16_t yStar,uint16_t xEnd,uint16_t yEnd);
static void _lcd_clear(uint16_t color);
//============================================================================
// spi/lcd 初始化、读、写
//============================================================================
static void _spi_init(void){
    esp_err_t ret;
	spi_bus_config_t buscfg = {
        .miso_io_num = PIN_LCD_MISO,
        .mosi_io_num = PIN_LCD_MOSI,
        .sclk_io_num = PIN_LCD_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 4096, //非DMA:64byte,,DMA:4096Byte
        .flags = SPICOMMON_BUSFLAG_MASTER,  //初始化检查SPI主机模式是否成功
    };
    //Configuration for the SPI device on the other side of the bus
    spi_device_interface_config_t devcfg = {
        .command_bits = 0,
        .address_bits = 0,
        .dummy_bits = 0,
        .clock_speed_hz = 60*1000000,
        .duty_cycle_pos = 0,        //50% duty cycle
        .mode = 0,
        .spics_io_num = PIN_LCD_CS,
        .cs_ena_posttrans = 1,      //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK
        .queue_size = 3,
        .pre_cb = _lcd_spi_pre_transfer_callback,   //Specify pre-transfer callback to handle D/C line
    };
    //Initialize the SPI bus and add the device we want to send stuff to.
    ret=spi_bus_initialize(LCD_SPI, &buscfg, SPI_DMA_CH1);    //总线初始化
    assert(ret==ESP_OK);
    ret=spi_bus_add_device(LCD_SPI, &devcfg, &spi_handle);
    assert(ret==ESP_OK);
}
static void _lcd_spi_pre_transfer_callback(spi_transaction_t *trans){
    uint32_t dc = (uint32_t)trans->user;
    gpio_set_level(PIN_LCD_DC, dc);
}
// LCD写寄存器
static void _lcd_cmd(uint8_t reg){
    esp_err_t ret;
    spi_transaction_t trans;
    memset(&trans, 0, sizeof(trans));
    trans.length = 8;       //Command is 8 bits
    trans.tx_buffer = &reg;
    trans.user = (void*)0;  //reg
    ret=spi_device_polling_transmit(spi_handle, &trans);   //Transmit!
    assert(ret==ESP_OK);//Should have had no issues.
}
// LCD写数据
static void _lcd_data(uint8_t *buff, uint16_t len){
    esp_err_t ret;
    spi_transaction_t trans;
    memset(&trans, 0, sizeof(trans));
    trans.length = len*8;
    trans.tx_buffer = buff;
    trans.user = (void*)1;  //data
    ret=spi_device_polling_transmit(spi_handle, &trans);   //Transmit!
    assert(ret==ESP_OK);//Should have had no issues.
}
static void _byte_read(uint8_t *buff, uint16_t len){
    spi_transaction_t trans;
    memset(&trans, 0, sizeof(trans));
    trans.length=len*8;
    // trans.flags = SPI_TRANS_USE_RXDATA;
    trans.tx_buffer = NULL;
    trans.rx_buffer = buff;
    trans.user = (void*)1;
    esp_err_t ret = spi_device_polling_transmit(spi_handle, &trans);
    assert( ret == ESP_OK );
}
// 读取LCD id
static uint32_t _lcd_get_id(void){
    uint8_t tmp_id[4] = {0};
    _lcd_cmd(0xD3);
    _byte_read(tmp_id, 4);
    return (tmp_id[0]<<24 | tmp_id[1]<<16 | tmp_id[2]<<8 | tmp_id[3]);
}

// 初始化LCD
static void _lcd_init(void){
    _spi_init();
    //Initialize non-SPI GPIOs
    gpio_set_direction(PIN_LCD_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_LCD_RST, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_LCD_LED, GPIO_MODE_OUTPUT);

    //Reset the display
    gpio_set_level(PIN_LCD_RST, 0);
    vTaskDelay(100 / portTICK_RATE_MS);
    gpio_set_level(PIN_LCD_RST, 1);
    vTaskDelay(100 / portTICK_RATE_MS);

    _lcd_cmd(0xF7);
    _lcd_data((uint8_t[]){0xA9, 0x51, 0x2C, 0x82}, 4);
    _lcd_cmd(0xC0);
    _lcd_data((uint8_t[]){0x11, 0x09}, 2);
    _lcd_cmd(0xC1);
    _lcd_data((uint8_t[]){0x41}, 1);
    _lcd_cmd(0xC1);
    _lcd_data((uint8_t[]){0x41}, 1);
    _lcd_cmd(0XC5);
    _lcd_data((uint8_t[]){0x00, 0x0A, 0x80}, 3);
    _lcd_cmd(0xB1);
    _lcd_data((uint8_t[]){0xB0, 0x11}, 2);
    _lcd_cmd(0xB4);
    _lcd_data((uint8_t[]){0x02}, 1);
    _lcd_cmd(0xB6);
    _lcd_data((uint8_t[]){0x02, 0x42}, 2);
    _lcd_cmd(0xB7);
    _lcd_data((uint8_t[]){0xc6}, 1);
    _lcd_cmd(0xBE);
    _lcd_data((uint8_t[]){0x00, 0x04}, 2);
    _lcd_cmd(0xE9);
    _lcd_data((uint8_t[]){0x00}, 1);
    _lcd_cmd(0x36);
    _lcd_data((uint8_t[]){(1<<3)|(0<<7)|(1<<6)|(1<<5)}, 1);
    _lcd_cmd(0x3A);
    _lcd_data((uint8_t[]){0x66}, 1);
    _lcd_cmd(0xE0);
    _lcd_data((uint8_t[]){0x00, 0x07, 0x10, 0x09, 0x17, 0x0B, 0x41, 0x89, 0x4B, 0x0A, 0x0C, 0x0E, 0x18, 0x1B, 0x0F}, 15);
    _lcd_cmd(0XE1);
    _lcd_data((uint8_t[]){0x00, 0x17, 0x1A, 0x04, 0x0E, 0x06, 0x2F, 0x45, 0x43, 0x02, 0x0A, 0x09, 0x32, 0x36, 0x0F}, 15);
    _lcd_cmd(0x11);
    vTaskDelay(120 / portTICK_RATE_MS);
    _lcd_cmd(0x29);

	printf("==== LCD ID: %X ====\n",_lcd_get_id());
	_lcd_direction(USE_HORIZONTAL);	//setting direction
	_lcd_led_set(ON);
	_lcd_clear(GREEN);	//clear screen
}
// 背光灯设置
static void _lcd_led_set(uint8_t state){
    if(state){
        gpio_set_level(PIN_LCD_LED, 1);
    }else{
        gpio_set_level(PIN_LCD_LED, 0);
    }
}
// lcd显示方向设置
static void _lcd_direction(uint8_t direction){
	switch(direction){		  
		case 0:						 	 		
			lcddev.width=LCD_W;
			lcddev.height=LCD_H;
            _lcd_cmd(0x36);
            _lcd_data((uint8_t[]){(1<<3)|(0<<6)|(0<<7)}, 1);//BGR==1,MY==0,MX==0,MV==0
		break;
		case 1:
			lcddev.width=LCD_H;
			lcddev.height=LCD_W;
            _lcd_cmd(0x36);
			_lcd_data((uint8_t[]){(1<<3)|(0<<7)|(1<<6)|(1<<5)}, 1);//BGR==1,MY==1,MX==0,MV==1
		break;
		case 2:						 	 		
			lcddev.width=LCD_W;
			lcddev.height=LCD_H;
            _lcd_cmd(0x36);
			_lcd_data((uint8_t[]){(1<<3)|(1<<6)|(1<<7)}, 1);//BGR==1,MY==0,MX==0,MV==0
		break;
		case 3:
			lcddev.width=LCD_H;
			lcddev.height=LCD_W;
            _lcd_cmd(0x36);
			_lcd_data((uint8_t[]){(1<<3)|(1<<7)|(1<<5)}, 1);//BGR==1,MY==1,MX==0,MV==1
		break;	
		default:break;
	}		
}
// 写一个RGB像素点
static void _lcd_write_data_16bit(uint16_t rgb_data){
    //18Bit	
	uint8_t rgb_r = (rgb_data>>8)&0xF8;	//RED
	uint8_t rgb_g = (rgb_data>>3)&0xFC;	//GREEN
	uint8_t rgb_b = rgb_data<<3;		//BLUE
    _lcd_data((uint8_t[]){rgb_r, rgb_g, rgb_b}, 3);
}
static void _lcd_SetWindows(uint16_t xStar, uint16_t yStar,uint16_t xEnd,uint16_t yEnd){
	lcddev.setxcmd=0x2A;
	lcddev.setycmd=0x2B;
	lcddev.wramcmd=0x2C;
    _lcd_cmd(lcddev.setxcmd);
    _lcd_data((uint8_t[]){xStar>>8, 0x00FF&xStar, xEnd>>8, 0x00FF&xEnd}, 4);
    
    _lcd_cmd(lcddev.setycmd);	
	_lcd_data((uint8_t[]){yStar>>8, 0x00FF&yStar, yEnd>>8, 0x00FF&yEnd}, 4);

	_lcd_cmd(lcddev.wramcmd);   //start writing GRAM			
}
//清屏
static void _lcd_clear(uint16_t color){
	uint32_t i = 0;

    memset(GRAM_BUFF, 0, USER_BUFF_LEN);
	_lcd_SetWindows(0,0,lcddev.width-1,lcddev.height-1);

    for(i=0;i<USER_BUFF_LEN;){
        GRAM_BUFF[i] = RGB_R(color);
        GRAM_BUFF[i+1] = RGB_G(color);
        GRAM_BUFF[i+2] = RGB_B(color);
        i += 3;
    }
    for(i=0;i<(lcddev.height*lcddev.width*3)/USER_BUFF_LEN;i++){
        _lcd_data(GRAM_BUFF, USER_BUFF_LEN);
    }
}
//============================================================================
// lcd 初始化、读、写
//============================================================================
void LCD_Init(void){
    _lcd_init();
}
void LCD_SetCursor(uint16_t Xpos, uint16_t Ypos){	  	    			
	_lcd_SetWindows(Xpos,Ypos,Xpos+1,Ypos+1);	
}
void LCD_Refresh(uint16_t color){
    _lcd_clear(color);
}

void LCD_Disp_Flush(const lv_area_t * area, lv_color_t * color_p){
    int32_t cnt = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * 3;
    uint16_t i = 0;
    uint16_t index = 0;
    uint16_t numTime = 0;   //多少组GRAM_BUFF
    uint16_t numSurplus = 0;//最后剩下的

    if(cnt < 0){
        printf("parameter setting error!!!\n");
        return;
    }

    _lcd_SetWindows(area->x1, area->y1, area->x2, area->y2);

    // printf("CNT  %d\n\n\n",cnt);
    if(cnt <= USER_BUFF_LEN){
        memset(GRAM_BUFF, 0, USER_BUFF_LEN);
        for(i=0;i<cnt;){
            GRAM_BUFF[i] = RGB_R(color_p->full);
            GRAM_BUFF[i+1] = RGB_G(color_p->full);
            GRAM_BUFF[i+2] = RGB_B(color_p->full);
            i += 3;
            color_p++;
        }
        _lcd_data(GRAM_BUFF, cnt);
    }else{
        numTime = cnt/USER_BUFF_LEN;
        numSurplus = cnt%USER_BUFF_LEN;
        printf("NumTime:%d  NumSurplus:%d\n\n", numTime, numSurplus);
        for(uint16_t j=0;j<numTime;j++){
            // index = j*USER_BUFF_LEN;
            memset(GRAM_BUFF, 0, USER_BUFF_LEN);
            for(i=0;i<USER_BUFF_LEN;){
                GRAM_BUFF[i] = RGB_R(color_p->full);
                GRAM_BUFF[i+1] = RGB_G(color_p->full);
                GRAM_BUFF[i+2] = RGB_B(color_p->full);
                i += 3;
                color_p++;
            }
            _lcd_data(GRAM_BUFF, USER_BUFF_LEN);
        }
        if(numSurplus > 0){
            // index = cnt-numSurplus;
            memset(GRAM_BUFF, 0, USER_BUFF_LEN);
            for(i=0;i<=numSurplus;){
                GRAM_BUFF[i] = RGB_R(color_p->full);
                GRAM_BUFF[i+1] = RGB_G(color_p->full);
                GRAM_BUFF[i+2] = RGB_B(color_p->full);
                i += 3;
                color_p++;
            }
            _lcd_data(GRAM_BUFF, numSurplus);
        }
    }
}

ESP32 的spi 驱动还是挺有意思的
ESP32共有 3 组SPI,这里用的是 HSPI,另一个 VSPI 用于驱动MAX6675 读取温度
ESP32 的 SPI 在传输前和传输完成时可设置回调
transaction_cb_t pre_cb; /**< Callback to be called before a transmission is started.
transaction_cb_t post_cb; /**< Callback to be called after a transmission has completed.,这里就是用的在传输前设置 LCD D/C的 IO 来设置传输的是命令还是数据。
还有个妙用:就是当 SPI 接口不够用时,可以用该回调实现多个 IO 做软CS 片选来复用出多个 SPI。

三、LVGL v8移植

little VGL下载 请点击 转到这篇博客查看

  1. 将下载的 lvgl 移动到 esp-idf-v4.3/components 下
  2. 设置 lvgl 目录下的 lvgl.h 和 lv_conf.h,按需配置即可
  3. 实现 lv_port_displv_port_indevlv_port_fs

port 为接口文件,其中lv_port_disp/lv_port_indev/lv_port_fs 分别对应 LCD 屏绘制,触摸、鼠标、编码器等输入设备,还有文件系统,这里只使能lv_port_disp

// lv_port_disp.c部分函数

void lv_port_disp_init(void){
   disp_init();

   static lv_disp_draw_buf_t draw_buf_dsc_1;
   static lv_color_t buf_1[LV_HOR_RES_MAX * 10];                          /*A buffer for 10 rows*/
   lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, LV_HOR_RES_MAX * 10);   /*Initialize the display buffer*/

   static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
   lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

   disp_drv.hor_res = 480;
   disp_drv.ver_res = 320;
   disp_drv.flush_cb = disp_flush;
   disp_drv.draw_buf = &draw_buf_dsc_1;

   lv_disp_drv_register(&disp_drv);
}

static void disp_init(void){
   LCD_Init();
}

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p){
   LCD_Disp_Flush(area, color_p);
   lv_disp_flush_ready(disp_drv);
}

四、LVGL v8测试线程

// 创建 lvgl 相关的线程

......
	xSta = xTaskCreate(Thread_lvgl, /* 任务函数指针 */
                "LVGL Thread",   /* 任务名:调试使用 */
                15 * 1024,       /* 栈深 */
                NULL,       /* 任务参数 */
                2,          /* 优先级. */
                lvgl_task_handle);  /* 任务 handle */
    if(pdPASS == xSta){
        // vTaskStartScheduler();   //不需要
        printf("create lvgl thread successful!!!\n");
    }else{
        printf("create lvgl thread failed!!!\n");
        return -1;
    }
......

// lvgl线程

lv_tick_inc(50) 最好用定时器去实现周期调用

void Thread_lvgl(void *arg){
    lv_init();
    lv_port_disp_init();        // 显示器初始化
//    lv_port_indev_init();       // 输入设备初始化(如果没有实现就注释掉)
//    lv_port_fs_init();          // 文件系统设备初始化(如果没有实现就注释掉)

	slim_index();		//小控件测试
    while(1){
        lv_tick_inc(50);
		lv_task_handler();
        vTaskDelay(10 / portTICK_PERIOD_MS);
    }
}

//上测试控件

little VGL v8.1 跟 v7.8 的控件设置、style设置还是有很大不同的哈,从v7.8转过来的,还得先适应适应

static void slim_index(void){
    lv_obj_t *scr = lv_scr_act(); //获取屏幕对象
	
	//彩色标签
#if 1
    static lv_style_t label_styles;
    lv_style_init(&label_styles);
    lv_style_set_anim_speed(&label_styles, 50);
    lv_style_set_text_font(&label_styles, &lv_font_montserrat_20);

	lv_obj_t * label1 = lv_label_create(scr);
    lv_label_set_recolor(label1, true);
    lv_label_set_long_mode(label1, LV_LABEL_LONG_SCROLL_CIRCULAR); /*Circular scroll*/
    lv_obj_set_width(label1, 120);
    // lv_obj_add_style(label1, LV_LABEL_PART_MAIN, &label_styles);
    lv_label_set_text(label1, "#ff0000 Hello# #00ff00 world ! slim su.#");
    lv_obj_align(label1, LV_ALIGN_TOP_LEFT, 20, 5);
    lv_obj_add_style(label1, &label_styles, 0);
	// lv_label_set_anim_speed(label1, 80);
#endif

	//圆弧
#if 1
	static lv_style_t style_arc1_bg, style_arc1_indic, style_arc1_knob;
	lv_style_init(&style_arc1_bg);
	lv_style_set_arc_width(&style_arc1_bg, 10);
	lv_style_set_arc_color(&style_arc1_bg, lv_color_make(0, 255, 0));
	lv_style_init(&style_arc1_indic);
	lv_style_set_arc_width(&style_arc1_indic, 10);
	lv_style_set_arc_color(&style_arc1_indic, lv_color_make(255, 0, 0));
    lv_style_init(&style_arc1_knob);
    lv_style_set_bg_color(&style_arc1_knob, lv_color_make(255, 0, 0));
    lv_style_set_bg_opa(&style_arc1_knob, LV_OPA_0);
	
	lv_obj_t *arc1 = lv_arc_create(scr);
	lv_obj_set_size(arc1, 100, 100);
	lv_obj_set_pos(arc1, 5, 50);
	lv_arc_set_bg_angles(arc1, 0, 360);
	lv_arc_set_angles(arc1, 0, 90);
	lv_obj_add_style(arc1, &style_arc1_bg, LV_PART_MAIN);
	lv_obj_add_style(arc1, &style_arc1_indic, LV_PART_INDICATOR);
    lv_obj_add_style(arc1, &style_arc1_knob, LV_PART_KNOB);
	
	static lv_style_t style_arc2_bg, style_arc2_indic, style_arc2_knob;
	lv_style_init(&style_arc2_bg);
	lv_style_set_arc_width(&style_arc2_bg, 15);
	lv_style_set_arc_color(&style_arc2_bg, lv_color_make(0xC0, 0xC0, 0xC0));
	lv_style_init(&style_arc2_indic);
	lv_style_set_arc_width(&style_arc2_indic, 15);
	lv_style_set_arc_color(&style_arc2_indic, lv_color_make(0x80, 0x00, 0x80));
    lv_style_init(&style_arc2_knob);
    lv_style_set_bg_color(&style_arc2_knob, lv_color_make(0x80, 0x00, 0x80));
    lv_style_set_bg_opa(&style_arc2_knob, LV_OPA_0);
	
	lv_obj_t *arc2 = lv_arc_create(scr);
	lv_obj_set_size(arc2, 100, 100);
	lv_obj_set_pos(arc2, 120, 50);
	lv_arc_set_bg_angles(arc2, 0, 360);
	lv_arc_set_angles(arc2, 90, 270);
	lv_obj_add_style(arc2, &style_arc2_bg, LV_PART_MAIN);
	lv_obj_add_style(arc2, &style_arc2_indic, LV_PART_INDICATOR);
    lv_obj_add_style(arc2, &style_arc2_knob, LV_PART_KNOB);

	lv_obj_t *label2 = lv_label_create(arc2);	 // 在Arc控件上创建一个标签
	lv_obj_align(label2, LV_ALIGN_CENTER, 0, 0); // 对齐到Arc控件中心
	lv_label_set_text(label2, "2");				 // 设置标签文本
//	lv_obj_set_event_cb(arc2, arc_event_handler);
#endif

	//进度条
#if 1
    static lv_style_t style_bar1_bg, style_bar1_indic, style_bar_label1;
	lv_style_init(&style_bar_label1);
	lv_style_set_text_font(&style_bar_label1, &lv_font_montserrat_18);

    lv_style_init(&style_bar1_bg);
    lv_style_set_bg_opa(&style_bar1_bg, LV_OPA_COVER);
    lv_style_set_bg_color(&style_bar1_bg, lv_color_make(0xFF, 0xFF, 0x00));

    lv_style_init(&style_bar1_indic);
    lv_style_set_bg_opa(&style_bar1_indic, LV_OPA_COVER);
    lv_style_set_bg_color(&style_bar1_indic, lv_palette_main(LV_PALETTE_RED));
    lv_style_set_bg_grad_color(&style_bar1_indic, lv_palette_main(LV_PALETTE_BLUE));
    lv_style_set_bg_grad_dir(&style_bar1_indic, LV_GRAD_DIR_VER);

    bar1 = lv_bar_create(scr);
    lv_obj_add_style(bar1, &style_bar1_bg, LV_PART_MAIN);
    lv_obj_add_style(bar1, &style_bar1_indic, LV_PART_INDICATOR);
    lv_obj_set_size(bar1, 20, 200);
    lv_obj_align(bar1, LV_ALIGN_TOP_RIGHT, -30, 50);
    lv_bar_set_range(bar1, 0, 100);

	label_bar1 = lv_label_create(scr);
	lv_obj_add_style(label_bar1, &style_bar_label1, LV_PART_MAIN);
	lv_obj_align_to(label_bar1, bar1, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); 
	lv_label_set_text(label_bar1, "80%");

    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_exec_cb(&a, set_temp);
    lv_anim_set_time(&a, 8000);
    lv_anim_set_playback_time(&a, 1000);
    lv_anim_set_var(&a, bar1);
    lv_anim_set_values(&a, 0, 100);
    lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
    lv_anim_start(&a);
#endif

	//复选框
#if 1
	static lv_style_t style_checkbox1;
	lv_style_init(&style_checkbox1);
	lv_style_set_text_font(&style_checkbox1, &lv_font_montserrat_18);
	lv_style_set_bg_opa(&style_checkbox1, LV_OPA_100);
	lv_style_set_bg_color(&style_checkbox1, lv_color_make(0xFF, 0x00, 0x00));

	lv_obj_t *checkbox1 = lv_checkbox_create(scr);
	lv_obj_set_width(checkbox1, 130);
	lv_obj_set_pos(checkbox1, 250, 150);
	lv_obj_add_style(checkbox1, &style_checkbox1, LV_PART_MAIN);
	lv_checkbox_set_text(checkbox1, "CheckBox");
#endif

	//滑块
#if 0
	slider1 = lv_slider_create(scr, NULL);
	lv_obj_set_size(slider1, 100, 10);
	lv_slider_set_range(slider1, 0, 100);
	lv_slider_set_anim_time(slider1, 200);
	lv_bar_set_value(slider1, 20, LV_ANIM_ON);
	lv_obj_align(slider1, scr, LV_ALIGN_IN_TOP_RIGHT, -20, 20); 
//	lv_obj_set_event_cb(slider1, arc_event_handler);
#endif

	//按钮
#if 1
	lv_obj_t *btn1 = lv_btn_create(scr);
	lv_obj_set_pos(btn1, 250, 50);
	lv_obj_set_size(btn1, 120, 40);
	lv_obj_t *label_btn1 = lv_label_create(scr);
	lv_obj_align_to(label_btn1, btn1, LV_ALIGN_CENTER, 0, 0); 
	lv_label_set_text(label_btn1, "key1");
//	lv_obj_set_event_cb(btn1, arc_event_handler);
	
	btn2 = lv_btn_create(scr);
	lv_obj_set_pos(btn2, 250, 220);
	lv_obj_set_size(btn2, 80, 40);
    lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE);
	// lv_btn_set_checkable(btn2, true);
	// lv_btn_set_state(btn2, true);
	lv_obj_t *label_btn2 = lv_label_create(scr);
	lv_obj_align_to(label_btn2, btn2, LV_ALIGN_CENTER, 0, 0); 
	lv_label_set_text(label_btn2, "key2");
//	lv_obj_set_event_cb(btn2, arc_event_handler);
#endif 

	//预加载
#if 1
	static lv_style_t style_spinner1_bg, style_spinner1_indic;
	lv_style_init(&style_spinner1_bg);
	lv_style_set_bg_opa(&style_spinner1_bg, LV_OPA_100);
	lv_style_set_arc_width(&style_spinner1_bg, 15);
	lv_style_set_arc_color(&style_spinner1_bg, lv_color_make(0xFF, 0x00, 0xFF));
	lv_style_init(&style_spinner1_indic);
	lv_style_set_bg_opa(&style_spinner1_indic, LV_OPA_100);
	lv_style_set_arc_width(&style_spinner1_indic, 15);
	lv_style_set_arc_color(&style_spinner1_indic, lv_color_make(0x00, 0x00, 0x80));

    lv_obj_t * spinner1 = lv_spinner_create(scr, 2000, 60);
    lv_obj_set_size(spinner1, 80, 80);
	lv_obj_set_pos(spinner1, 10, 180);
    lv_obj_add_style(spinner1, &style_spinner1_bg, LV_PART_MAIN);
    lv_obj_add_style(spinner1, &style_spinner1_indic, LV_PART_INDICATOR);

    static lv_style_t style_spinner2_bg, style_spinner2_indic;
	lv_style_init(&style_spinner2_bg);
	lv_style_set_arc_width(&style_spinner2_bg, 0);
	lv_style_init(&style_spinner2_indic);
	lv_style_set_arc_width(&style_spinner2_indic, 8);
	lv_style_set_arc_color(&style_spinner2_indic, lv_color_make(0x80, 0x00, 0x00));

	
	lv_obj_t *spinner2 = lv_spinner_create(scr, 2000, 120);
	lv_obj_set_size(spinner2, 60, 60);
	lv_obj_set_pos(spinner2, 120, 180);
	lv_obj_add_style(spinner2, &style_spinner2_bg, LV_PART_MAIN);
	lv_obj_add_style(spinner2, &style_spinner2_indic, LV_PART_INDICATOR);
#endif	
	
	//线条
#if 1
	static lv_style_t style_line1;
	lv_style_init(&style_line1);
	lv_style_set_line_width(&style_line1, 10);
	lv_style_set_line_color(&style_line1, lv_color_make(255, 100, 100));
	lv_style_set_line_rounded(&style_line1, true); //圆角
		
	static lv_point_t line_points[] = {{180, 8},{460,40}};
	lv_obj_t *line1 = lv_line_create(scr);
	lv_obj_add_style(line1, &style_line1, LV_PART_MAIN);
	lv_line_set_points(line1, line_points, 2);
	lv_obj_set_pos(line1, 0, 0);	//点的坐标以此为原点
#endif
	
	//led
#if 1
	static lv_style_t style_led1;
	lv_style_init(&style_led1);
	lv_style_set_bg_color(&style_led1, lv_color_make(0xff, 0xff, 0x00));
	
	led1 = lv_led_create(scr);
	lv_obj_set_pos(led1, 350, 215);
	lv_obj_set_size(led1, 50, 50);
	lv_led_off(led1);
	lv_led_set_color(led1, lv_palette_main(LV_PALETTE_YELLOW));
#endif

    //文本域
#if 1
    txt1 = lv_textarea_create(scr);
    lv_obj_set_size(txt1, 380, 30);
    lv_obj_set_pos(txt1, 50, 280);
    // lv_textarea_set_one_line(txt1, true);
    lv_textarea_set_text(txt1, "MAX6675 detect Tempuration: --.-C");
#endif
}

//这里就是测试用的,实际项目不能这么写咯
static void set_temp(void * bar, int32_t temp){
    char str[10] = {0};
    float temp_val = 0;

    lv_bar_set_value(bar, temp, LV_ANIM_ON);
    if(0 == temp%10){
        sprintf(str, "%d%%", temp);
        lv_label_set_text(label_bar1, str);
    }
    if((led1 != NULL) && (0 == temp%50) && (txt1 != NULL)){
        lv_led_toggle(led1);

        max6675_get_temp(&temp_val);
        sprintf(str, "%2.1fC", temp_val);
        lv_textarea_del_char(txt1);
        lv_textarea_del_char(txt1);
        lv_textarea_del_char(txt1);
        lv_textarea_del_char(txt1);
        lv_textarea_del_char(txt1);
        lv_textarea_add_text(txt1, str);
    }
}

差不多就是这样咯

想玩下语音控制,小伙伴们有木有好的 ESP 离线语音开发方案 或 开发板推荐吖

版权声明:本文为CSDN博主「slimmm」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/slimmm/article/details/119975059

一、展示成果

博客上传图片限制在5M内,视频转 GIF 帧率压缩太严重了,还得再次压缩才小于5M,效果完全失真了
小伙伴们有什么好的工具或方法解决吗

gif 图上应该看不出什么区别。但是ESP32 用60MHz的 spi 驱动跑 lvgl,其卡顿比 stm32 spi 驱动跑 lvgl 明显好很多,,毕竟stm32 spi 也达不到 60MHz嘛

  • 主控:ESP32
  • 开发工具:esp- idf-v4.3
  • LCD 4.3寸 ILI9488
  • 温度传感器:K型热电偶+MAX6675
  • GUI:little VGL v8.1.0
  • 简单的跑个 little VGL 例程而已,下一步再搞点好玩的

ESP32

60MHz SPI 驱动 little VGL v8.1 效果
在这里插入图片描述
在这里插入图片描述

STM32

SPI驱动 little VGL v7.8效果
stm32 spi LCD

二、LCD驱动调试

老样子,直接上代码
// 头文件

#ifndef __SLIM_LCD_H__
#define __SLIM_LCD_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "lvgl/lvgl.h"
//*************************************************************************************************
//		引脚描述
//	CS		--> 15
//	SCK		-->	14
//	SDO		-->	12
//	SDI		-->	13
//	RESET 	-->	
//	DC/RS	-->	
//	LED		-->	
//	T_CLK	--> 
//	T_CS	--> 
//	T_DIN	--> 
//	T_DO	--> 
//	T_IRQ	--> 
//*************************************************************************************************
#ifdef CONFIG_IDF_TARGET_ESP32
#define LCD_SPI     HSPI_HOST
#define GPIO_HANDSHAKE  2
#define PIN_LCD_MISO    12
#define PIN_LCD_MOSI    13
#define PIN_LCD_CLK     14
#define PIN_LCD_CS      15
#define PIN_LCD_RST     25
#define PIN_LCD_DC      26
#define PIN_LCD_LED     27
#endif

//LCD parameter
typedef struct{										    
	uint16_t	width;	//LCD width
	uint16_t	height;	//LCD height
	uint16_t	id;		//LCD ID
	uint8_t		dir;	//0:vertical / 1:Horizontal
	uint16_t	wramcmd;//start write GRAM command
	uint16_t	setxcmd;//setting x axis
	uint16_t	setycmd;//setting y axis	 
}_lcd_dev; 	

extern _lcd_dev lcddev;	
#define USE_HORIZONTAL      1   //0:0 / 1:90 / 2:180 / 3:270
#define USER_BUFF_LEN       1440    //3*480

//画笔颜色
#define WHITE       0xFFFF
#define BLACK      	0x0000	  
#define BLUE       	0x001F  
#define BRED        0XF81F
#define GRED		0XFFE0
#define GBLUE		0X07FF
#define RED         0xF800
#define MAGENTA     0xF81F
#define GREEN       0x07E0
#define CYAN        0x7FFF
#define YELLOW      0xFFE0
#define BROWN		0XBC40	//棕色
#define BRRED		0XFC07	//棕红色
#define GRAY		0X8430	//灰色
//GUI颜色

#define DARKBLUE	0X01CF	//深蓝色
#define LIGHTBLUE	0X7D7C	//浅蓝色  
#define GRAYBLUE	0X5458	//灰蓝色
//以上三色为 PANEL颜色 
 
#define LIGHTGREEN	0X841F	//灰绿色
#define LIGHTGRAY	0XEF5B	//浅灰色(PANNEL)
#define LGRAY		0XC618	//浅灰色(PANNEL),窗体背景色

#define LGRAYBLUE	0XA651 //浅灰蓝色(中间层颜色)
#define LBBLUE		0X2B12 //浅棕蓝色(选择条目的反色)

//brush color and background color
#define POINT_COLOR	0x0000
#define BACK_COLOR	0xFFFF

enum{
	REG = 0,
	DATA,
};
enum{
	OFF = 0,
	ON,
};
//==================================================================================	  
//LCD size
#define LCD_W 320
#define LCD_H 480

void LCD_Init(void);
void LCD_SetCursor(uint16_t Xpos, uint16_t Ypos);
void LCD_Refresh(uint16_t Color);
void LCD_Flush(uint16_t xStart, uint16_t yStart, uint16_t xEnd, uint16_t yEnd, uint16_t *buff);
void LCD_Disp_Flush(const lv_area_t * area, lv_color_t * color_p);

#endif
/**
  *************************************************************************************************
  * @file    : slim_lcd.c
  * @author  : slim 
  * @version : V0.0.1
  * @date    : 2021.07.11
  * @brief   : LCD moduler for ILI9488 IC driver
  *************************************************************************************************
  */
#include "slim_lcd.h"


#define RGB_R(n)	(n>>8)&0xF8
#define RGB_G(n)	(n>>3)&0xFC
#define RGB_B(n)	n<<3

_lcd_dev lcddev;
static spi_device_handle_t spi_handle;
uint8_t GRAM_BUFF[USER_BUFF_LEN] = {0};

//===========================================================================
// 静态函数声明
static void _spi_init(void);
static void _lcd_spi_pre_transfer_callback(spi_transaction_t *trans);
static void _lcd_cmd(uint8_t reg);
static void _lcd_data(uint8_t *buff, uint16_t len);
static void _byte_read(uint8_t *buff, uint16_t len);
static uint32_t _lcd_get_id(void);
static void _lcd_init(void);
static void _lcd_led_set(uint8_t state);
static void _lcd_direction(uint8_t direction);
static void _lcd_write_data_16bit(uint16_t rgb_data);
static void _lcd_SetWindows(uint16_t xStar, uint16_t yStar,uint16_t xEnd,uint16_t yEnd);
static void _lcd_clear(uint16_t color);
//============================================================================
// spi/lcd 初始化、读、写
//============================================================================
static void _spi_init(void){
    esp_err_t ret;
	spi_bus_config_t buscfg = {
        .miso_io_num = PIN_LCD_MISO,
        .mosi_io_num = PIN_LCD_MOSI,
        .sclk_io_num = PIN_LCD_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 4096, //非DMA:64byte,,DMA:4096Byte
        .flags = SPICOMMON_BUSFLAG_MASTER,  //初始化检查SPI主机模式是否成功
    };
    //Configuration for the SPI device on the other side of the bus
    spi_device_interface_config_t devcfg = {
        .command_bits = 0,
        .address_bits = 0,
        .dummy_bits = 0,
        .clock_speed_hz = 60*1000000,
        .duty_cycle_pos = 0,        //50% duty cycle
        .mode = 0,
        .spics_io_num = PIN_LCD_CS,
        .cs_ena_posttrans = 1,      //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK
        .queue_size = 3,
        .pre_cb = _lcd_spi_pre_transfer_callback,   //Specify pre-transfer callback to handle D/C line
    };
    //Initialize the SPI bus and add the device we want to send stuff to.
    ret=spi_bus_initialize(LCD_SPI, &buscfg, SPI_DMA_CH1);    //总线初始化
    assert(ret==ESP_OK);
    ret=spi_bus_add_device(LCD_SPI, &devcfg, &spi_handle);
    assert(ret==ESP_OK);
}
static void _lcd_spi_pre_transfer_callback(spi_transaction_t *trans){
    uint32_t dc = (uint32_t)trans->user;
    gpio_set_level(PIN_LCD_DC, dc);
}
// LCD写寄存器
static void _lcd_cmd(uint8_t reg){
    esp_err_t ret;
    spi_transaction_t trans;
    memset(&trans, 0, sizeof(trans));
    trans.length = 8;       //Command is 8 bits
    trans.tx_buffer = &reg;
    trans.user = (void*)0;  //reg
    ret=spi_device_polling_transmit(spi_handle, &trans);   //Transmit!
    assert(ret==ESP_OK);//Should have had no issues.
}
// LCD写数据
static void _lcd_data(uint8_t *buff, uint16_t len){
    esp_err_t ret;
    spi_transaction_t trans;
    memset(&trans, 0, sizeof(trans));
    trans.length = len*8;
    trans.tx_buffer = buff;
    trans.user = (void*)1;  //data
    ret=spi_device_polling_transmit(spi_handle, &trans);   //Transmit!
    assert(ret==ESP_OK);//Should have had no issues.
}
static void _byte_read(uint8_t *buff, uint16_t len){
    spi_transaction_t trans;
    memset(&trans, 0, sizeof(trans));
    trans.length=len*8;
    // trans.flags = SPI_TRANS_USE_RXDATA;
    trans.tx_buffer = NULL;
    trans.rx_buffer = buff;
    trans.user = (void*)1;
    esp_err_t ret = spi_device_polling_transmit(spi_handle, &trans);
    assert( ret == ESP_OK );
}
// 读取LCD id
static uint32_t _lcd_get_id(void){
    uint8_t tmp_id[4] = {0};
    _lcd_cmd(0xD3);
    _byte_read(tmp_id, 4);
    return (tmp_id[0]<<24 | tmp_id[1]<<16 | tmp_id[2]<<8 | tmp_id[3]);
}

// 初始化LCD
static void _lcd_init(void){
    _spi_init();
    //Initialize non-SPI GPIOs
    gpio_set_direction(PIN_LCD_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_LCD_RST, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_LCD_LED, GPIO_MODE_OUTPUT);

    //Reset the display
    gpio_set_level(PIN_LCD_RST, 0);
    vTaskDelay(100 / portTICK_RATE_MS);
    gpio_set_level(PIN_LCD_RST, 1);
    vTaskDelay(100 / portTICK_RATE_MS);

    _lcd_cmd(0xF7);
    _lcd_data((uint8_t[]){0xA9, 0x51, 0x2C, 0x82}, 4);
    _lcd_cmd(0xC0);
    _lcd_data((uint8_t[]){0x11, 0x09}, 2);
    _lcd_cmd(0xC1);
    _lcd_data((uint8_t[]){0x41}, 1);
    _lcd_cmd(0xC1);
    _lcd_data((uint8_t[]){0x41}, 1);
    _lcd_cmd(0XC5);
    _lcd_data((uint8_t[]){0x00, 0x0A, 0x80}, 3);
    _lcd_cmd(0xB1);
    _lcd_data((uint8_t[]){0xB0, 0x11}, 2);
    _lcd_cmd(0xB4);
    _lcd_data((uint8_t[]){0x02}, 1);
    _lcd_cmd(0xB6);
    _lcd_data((uint8_t[]){0x02, 0x42}, 2);
    _lcd_cmd(0xB7);
    _lcd_data((uint8_t[]){0xc6}, 1);
    _lcd_cmd(0xBE);
    _lcd_data((uint8_t[]){0x00, 0x04}, 2);
    _lcd_cmd(0xE9);
    _lcd_data((uint8_t[]){0x00}, 1);
    _lcd_cmd(0x36);
    _lcd_data((uint8_t[]){(1<<3)|(0<<7)|(1<<6)|(1<<5)}, 1);
    _lcd_cmd(0x3A);
    _lcd_data((uint8_t[]){0x66}, 1);
    _lcd_cmd(0xE0);
    _lcd_data((uint8_t[]){0x00, 0x07, 0x10, 0x09, 0x17, 0x0B, 0x41, 0x89, 0x4B, 0x0A, 0x0C, 0x0E, 0x18, 0x1B, 0x0F}, 15);
    _lcd_cmd(0XE1);
    _lcd_data((uint8_t[]){0x00, 0x17, 0x1A, 0x04, 0x0E, 0x06, 0x2F, 0x45, 0x43, 0x02, 0x0A, 0x09, 0x32, 0x36, 0x0F}, 15);
    _lcd_cmd(0x11);
    vTaskDelay(120 / portTICK_RATE_MS);
    _lcd_cmd(0x29);

	printf("==== LCD ID: %X ====\n",_lcd_get_id());
	_lcd_direction(USE_HORIZONTAL);	//setting direction
	_lcd_led_set(ON);
	_lcd_clear(GREEN);	//clear screen
}
// 背光灯设置
static void _lcd_led_set(uint8_t state){
    if(state){
        gpio_set_level(PIN_LCD_LED, 1);
    }else{
        gpio_set_level(PIN_LCD_LED, 0);
    }
}
// lcd显示方向设置
static void _lcd_direction(uint8_t direction){
	switch(direction){		  
		case 0:						 	 		
			lcddev.width=LCD_W;
			lcddev.height=LCD_H;
            _lcd_cmd(0x36);
            _lcd_data((uint8_t[]){(1<<3)|(0<<6)|(0<<7)}, 1);//BGR==1,MY==0,MX==0,MV==0
		break;
		case 1:
			lcddev.width=LCD_H;
			lcddev.height=LCD_W;
            _lcd_cmd(0x36);
			_lcd_data((uint8_t[]){(1<<3)|(0<<7)|(1<<6)|(1<<5)}, 1);//BGR==1,MY==1,MX==0,MV==1
		break;
		case 2:						 	 		
			lcddev.width=LCD_W;
			lcddev.height=LCD_H;
            _lcd_cmd(0x36);
			_lcd_data((uint8_t[]){(1<<3)|(1<<6)|(1<<7)}, 1);//BGR==1,MY==0,MX==0,MV==0
		break;
		case 3:
			lcddev.width=LCD_H;
			lcddev.height=LCD_W;
            _lcd_cmd(0x36);
			_lcd_data((uint8_t[]){(1<<3)|(1<<7)|(1<<5)}, 1);//BGR==1,MY==1,MX==0,MV==1
		break;	
		default:break;
	}		
}
// 写一个RGB像素点
static void _lcd_write_data_16bit(uint16_t rgb_data){
    //18Bit	
	uint8_t rgb_r = (rgb_data>>8)&0xF8;	//RED
	uint8_t rgb_g = (rgb_data>>3)&0xFC;	//GREEN
	uint8_t rgb_b = rgb_data<<3;		//BLUE
    _lcd_data((uint8_t[]){rgb_r, rgb_g, rgb_b}, 3);
}
static void _lcd_SetWindows(uint16_t xStar, uint16_t yStar,uint16_t xEnd,uint16_t yEnd){
	lcddev.setxcmd=0x2A;
	lcddev.setycmd=0x2B;
	lcddev.wramcmd=0x2C;
    _lcd_cmd(lcddev.setxcmd);
    _lcd_data((uint8_t[]){xStar>>8, 0x00FF&xStar, xEnd>>8, 0x00FF&xEnd}, 4);
    
    _lcd_cmd(lcddev.setycmd);	
	_lcd_data((uint8_t[]){yStar>>8, 0x00FF&yStar, yEnd>>8, 0x00FF&yEnd}, 4);

	_lcd_cmd(lcddev.wramcmd);   //start writing GRAM			
}
//清屏
static void _lcd_clear(uint16_t color){
	uint32_t i = 0;

    memset(GRAM_BUFF, 0, USER_BUFF_LEN);
	_lcd_SetWindows(0,0,lcddev.width-1,lcddev.height-1);

    for(i=0;i<USER_BUFF_LEN;){
        GRAM_BUFF[i] = RGB_R(color);
        GRAM_BUFF[i+1] = RGB_G(color);
        GRAM_BUFF[i+2] = RGB_B(color);
        i += 3;
    }
    for(i=0;i<(lcddev.height*lcddev.width*3)/USER_BUFF_LEN;i++){
        _lcd_data(GRAM_BUFF, USER_BUFF_LEN);
    }
}
//============================================================================
// lcd 初始化、读、写
//============================================================================
void LCD_Init(void){
    _lcd_init();
}
void LCD_SetCursor(uint16_t Xpos, uint16_t Ypos){	  	    			
	_lcd_SetWindows(Xpos,Ypos,Xpos+1,Ypos+1);	
}
void LCD_Refresh(uint16_t color){
    _lcd_clear(color);
}

void LCD_Disp_Flush(const lv_area_t * area, lv_color_t * color_p){
    int32_t cnt = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * 3;
    uint16_t i = 0;
    uint16_t index = 0;
    uint16_t numTime = 0;   //多少组GRAM_BUFF
    uint16_t numSurplus = 0;//最后剩下的

    if(cnt < 0){
        printf("parameter setting error!!!\n");
        return;
    }

    _lcd_SetWindows(area->x1, area->y1, area->x2, area->y2);

    // printf("CNT  %d\n\n\n",cnt);
    if(cnt <= USER_BUFF_LEN){
        memset(GRAM_BUFF, 0, USER_BUFF_LEN);
        for(i=0;i<cnt;){
            GRAM_BUFF[i] = RGB_R(color_p->full);
            GRAM_BUFF[i+1] = RGB_G(color_p->full);
            GRAM_BUFF[i+2] = RGB_B(color_p->full);
            i += 3;
            color_p++;
        }
        _lcd_data(GRAM_BUFF, cnt);
    }else{
        numTime = cnt/USER_BUFF_LEN;
        numSurplus = cnt%USER_BUFF_LEN;
        printf("NumTime:%d  NumSurplus:%d\n\n", numTime, numSurplus);
        for(uint16_t j=0;j<numTime;j++){
            // index = j*USER_BUFF_LEN;
            memset(GRAM_BUFF, 0, USER_BUFF_LEN);
            for(i=0;i<USER_BUFF_LEN;){
                GRAM_BUFF[i] = RGB_R(color_p->full);
                GRAM_BUFF[i+1] = RGB_G(color_p->full);
                GRAM_BUFF[i+2] = RGB_B(color_p->full);
                i += 3;
                color_p++;
            }
            _lcd_data(GRAM_BUFF, USER_BUFF_LEN);
        }
        if(numSurplus > 0){
            // index = cnt-numSurplus;
            memset(GRAM_BUFF, 0, USER_BUFF_LEN);
            for(i=0;i<=numSurplus;){
                GRAM_BUFF[i] = RGB_R(color_p->full);
                GRAM_BUFF[i+1] = RGB_G(color_p->full);
                GRAM_BUFF[i+2] = RGB_B(color_p->full);
                i += 3;
                color_p++;
            }
            _lcd_data(GRAM_BUFF, numSurplus);
        }
    }
}

ESP32 的spi 驱动还是挺有意思的
ESP32共有 3 组SPI,这里用的是 HSPI,另一个 VSPI 用于驱动MAX6675 读取温度
ESP32 的 SPI 在传输前和传输完成时可设置回调
transaction_cb_t pre_cb; /**< Callback to be called before a transmission is started.
transaction_cb_t post_cb; /**< Callback to be called after a transmission has completed.,这里就是用的在传输前设置 LCD D/C的 IO 来设置传输的是命令还是数据。
还有个妙用:就是当 SPI 接口不够用时,可以用该回调实现多个 IO 做软CS 片选来复用出多个 SPI。

三、LVGL v8移植

little VGL下载 请点击 转到这篇博客查看

  1. 将下载的 lvgl 移动到 esp-idf-v4.3/components 下
  2. 设置 lvgl 目录下的 lvgl.h 和 lv_conf.h,按需配置即可
  3. 实现 lv_port_displv_port_indevlv_port_fs

port 为接口文件,其中lv_port_disp/lv_port_indev/lv_port_fs 分别对应 LCD 屏绘制,触摸、鼠标、编码器等输入设备,还有文件系统,这里只使能lv_port_disp

// lv_port_disp.c部分函数

void lv_port_disp_init(void){
   disp_init();

   static lv_disp_draw_buf_t draw_buf_dsc_1;
   static lv_color_t buf_1[LV_HOR_RES_MAX * 10];                          /*A buffer for 10 rows*/
   lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, LV_HOR_RES_MAX * 10);   /*Initialize the display buffer*/

   static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
   lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

   disp_drv.hor_res = 480;
   disp_drv.ver_res = 320;
   disp_drv.flush_cb = disp_flush;
   disp_drv.draw_buf = &draw_buf_dsc_1;

   lv_disp_drv_register(&disp_drv);
}

static void disp_init(void){
   LCD_Init();
}

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p){
   LCD_Disp_Flush(area, color_p);
   lv_disp_flush_ready(disp_drv);
}

四、LVGL v8测试线程

// 创建 lvgl 相关的线程

......
	xSta = xTaskCreate(Thread_lvgl, /* 任务函数指针 */
                "LVGL Thread",   /* 任务名:调试使用 */
                15 * 1024,       /* 栈深 */
                NULL,       /* 任务参数 */
                2,          /* 优先级. */
                lvgl_task_handle);  /* 任务 handle */
    if(pdPASS == xSta){
        // vTaskStartScheduler();   //不需要
        printf("create lvgl thread successful!!!\n");
    }else{
        printf("create lvgl thread failed!!!\n");
        return -1;
    }
......

// lvgl线程

lv_tick_inc(50) 最好用定时器去实现周期调用

void Thread_lvgl(void *arg){
    lv_init();
    lv_port_disp_init();        // 显示器初始化
//    lv_port_indev_init();       // 输入设备初始化(如果没有实现就注释掉)
//    lv_port_fs_init();          // 文件系统设备初始化(如果没有实现就注释掉)

	slim_index();		//小控件测试
    while(1){
        lv_tick_inc(50);
		lv_task_handler();
        vTaskDelay(10 / portTICK_PERIOD_MS);
    }
}

//上测试控件

little VGL v8.1 跟 v7.8 的控件设置、style设置还是有很大不同的哈,从v7.8转过来的,还得先适应适应

static void slim_index(void){
    lv_obj_t *scr = lv_scr_act(); //获取屏幕对象
	
	//彩色标签
#if 1
    static lv_style_t label_styles;
    lv_style_init(&label_styles);
    lv_style_set_anim_speed(&label_styles, 50);
    lv_style_set_text_font(&label_styles, &lv_font_montserrat_20);

	lv_obj_t * label1 = lv_label_create(scr);
    lv_label_set_recolor(label1, true);
    lv_label_set_long_mode(label1, LV_LABEL_LONG_SCROLL_CIRCULAR); /*Circular scroll*/
    lv_obj_set_width(label1, 120);
    // lv_obj_add_style(label1, LV_LABEL_PART_MAIN, &label_styles);
    lv_label_set_text(label1, "#ff0000 Hello# #00ff00 world ! slim su.#");
    lv_obj_align(label1, LV_ALIGN_TOP_LEFT, 20, 5);
    lv_obj_add_style(label1, &label_styles, 0);
	// lv_label_set_anim_speed(label1, 80);
#endif

	//圆弧
#if 1
	static lv_style_t style_arc1_bg, style_arc1_indic, style_arc1_knob;
	lv_style_init(&style_arc1_bg);
	lv_style_set_arc_width(&style_arc1_bg, 10);
	lv_style_set_arc_color(&style_arc1_bg, lv_color_make(0, 255, 0));
	lv_style_init(&style_arc1_indic);
	lv_style_set_arc_width(&style_arc1_indic, 10);
	lv_style_set_arc_color(&style_arc1_indic, lv_color_make(255, 0, 0));
    lv_style_init(&style_arc1_knob);
    lv_style_set_bg_color(&style_arc1_knob, lv_color_make(255, 0, 0));
    lv_style_set_bg_opa(&style_arc1_knob, LV_OPA_0);
	
	lv_obj_t *arc1 = lv_arc_create(scr);
	lv_obj_set_size(arc1, 100, 100);
	lv_obj_set_pos(arc1, 5, 50);
	lv_arc_set_bg_angles(arc1, 0, 360);
	lv_arc_set_angles(arc1, 0, 90);
	lv_obj_add_style(arc1, &style_arc1_bg, LV_PART_MAIN);
	lv_obj_add_style(arc1, &style_arc1_indic, LV_PART_INDICATOR);
    lv_obj_add_style(arc1, &style_arc1_knob, LV_PART_KNOB);
	
	static lv_style_t style_arc2_bg, style_arc2_indic, style_arc2_knob;
	lv_style_init(&style_arc2_bg);
	lv_style_set_arc_width(&style_arc2_bg, 15);
	lv_style_set_arc_color(&style_arc2_bg, lv_color_make(0xC0, 0xC0, 0xC0));
	lv_style_init(&style_arc2_indic);
	lv_style_set_arc_width(&style_arc2_indic, 15);
	lv_style_set_arc_color(&style_arc2_indic, lv_color_make(0x80, 0x00, 0x80));
    lv_style_init(&style_arc2_knob);
    lv_style_set_bg_color(&style_arc2_knob, lv_color_make(0x80, 0x00, 0x80));
    lv_style_set_bg_opa(&style_arc2_knob, LV_OPA_0);
	
	lv_obj_t *arc2 = lv_arc_create(scr);
	lv_obj_set_size(arc2, 100, 100);
	lv_obj_set_pos(arc2, 120, 50);
	lv_arc_set_bg_angles(arc2, 0, 360);
	lv_arc_set_angles(arc2, 90, 270);
	lv_obj_add_style(arc2, &style_arc2_bg, LV_PART_MAIN);
	lv_obj_add_style(arc2, &style_arc2_indic, LV_PART_INDICATOR);
    lv_obj_add_style(arc2, &style_arc2_knob, LV_PART_KNOB);

	lv_obj_t *label2 = lv_label_create(arc2);	 // 在Arc控件上创建一个标签
	lv_obj_align(label2, LV_ALIGN_CENTER, 0, 0); // 对齐到Arc控件中心
	lv_label_set_text(label2, "2");				 // 设置标签文本
//	lv_obj_set_event_cb(arc2, arc_event_handler);
#endif

	//进度条
#if 1
    static lv_style_t style_bar1_bg, style_bar1_indic, style_bar_label1;
	lv_style_init(&style_bar_label1);
	lv_style_set_text_font(&style_bar_label1, &lv_font_montserrat_18);

    lv_style_init(&style_bar1_bg);
    lv_style_set_bg_opa(&style_bar1_bg, LV_OPA_COVER);
    lv_style_set_bg_color(&style_bar1_bg, lv_color_make(0xFF, 0xFF, 0x00));

    lv_style_init(&style_bar1_indic);
    lv_style_set_bg_opa(&style_bar1_indic, LV_OPA_COVER);
    lv_style_set_bg_color(&style_bar1_indic, lv_palette_main(LV_PALETTE_RED));
    lv_style_set_bg_grad_color(&style_bar1_indic, lv_palette_main(LV_PALETTE_BLUE));
    lv_style_set_bg_grad_dir(&style_bar1_indic, LV_GRAD_DIR_VER);

    bar1 = lv_bar_create(scr);
    lv_obj_add_style(bar1, &style_bar1_bg, LV_PART_MAIN);
    lv_obj_add_style(bar1, &style_bar1_indic, LV_PART_INDICATOR);
    lv_obj_set_size(bar1, 20, 200);
    lv_obj_align(bar1, LV_ALIGN_TOP_RIGHT, -30, 50);
    lv_bar_set_range(bar1, 0, 100);

	label_bar1 = lv_label_create(scr);
	lv_obj_add_style(label_bar1, &style_bar_label1, LV_PART_MAIN);
	lv_obj_align_to(label_bar1, bar1, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); 
	lv_label_set_text(label_bar1, "80%");

    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_exec_cb(&a, set_temp);
    lv_anim_set_time(&a, 8000);
    lv_anim_set_playback_time(&a, 1000);
    lv_anim_set_var(&a, bar1);
    lv_anim_set_values(&a, 0, 100);
    lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
    lv_anim_start(&a);
#endif

	//复选框
#if 1
	static lv_style_t style_checkbox1;
	lv_style_init(&style_checkbox1);
	lv_style_set_text_font(&style_checkbox1, &lv_font_montserrat_18);
	lv_style_set_bg_opa(&style_checkbox1, LV_OPA_100);
	lv_style_set_bg_color(&style_checkbox1, lv_color_make(0xFF, 0x00, 0x00));

	lv_obj_t *checkbox1 = lv_checkbox_create(scr);
	lv_obj_set_width(checkbox1, 130);
	lv_obj_set_pos(checkbox1, 250, 150);
	lv_obj_add_style(checkbox1, &style_checkbox1, LV_PART_MAIN);
	lv_checkbox_set_text(checkbox1, "CheckBox");
#endif

	//滑块
#if 0
	slider1 = lv_slider_create(scr, NULL);
	lv_obj_set_size(slider1, 100, 10);
	lv_slider_set_range(slider1, 0, 100);
	lv_slider_set_anim_time(slider1, 200);
	lv_bar_set_value(slider1, 20, LV_ANIM_ON);
	lv_obj_align(slider1, scr, LV_ALIGN_IN_TOP_RIGHT, -20, 20); 
//	lv_obj_set_event_cb(slider1, arc_event_handler);
#endif

	//按钮
#if 1
	lv_obj_t *btn1 = lv_btn_create(scr);
	lv_obj_set_pos(btn1, 250, 50);
	lv_obj_set_size(btn1, 120, 40);
	lv_obj_t *label_btn1 = lv_label_create(scr);
	lv_obj_align_to(label_btn1, btn1, LV_ALIGN_CENTER, 0, 0); 
	lv_label_set_text(label_btn1, "key1");
//	lv_obj_set_event_cb(btn1, arc_event_handler);
	
	btn2 = lv_btn_create(scr);
	lv_obj_set_pos(btn2, 250, 220);
	lv_obj_set_size(btn2, 80, 40);
    lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE);
	// lv_btn_set_checkable(btn2, true);
	// lv_btn_set_state(btn2, true);
	lv_obj_t *label_btn2 = lv_label_create(scr);
	lv_obj_align_to(label_btn2, btn2, LV_ALIGN_CENTER, 0, 0); 
	lv_label_set_text(label_btn2, "key2");
//	lv_obj_set_event_cb(btn2, arc_event_handler);
#endif 

	//预加载
#if 1
	static lv_style_t style_spinner1_bg, style_spinner1_indic;
	lv_style_init(&style_spinner1_bg);
	lv_style_set_bg_opa(&style_spinner1_bg, LV_OPA_100);
	lv_style_set_arc_width(&style_spinner1_bg, 15);
	lv_style_set_arc_color(&style_spinner1_bg, lv_color_make(0xFF, 0x00, 0xFF));
	lv_style_init(&style_spinner1_indic);
	lv_style_set_bg_opa(&style_spinner1_indic, LV_OPA_100);
	lv_style_set_arc_width(&style_spinner1_indic, 15);
	lv_style_set_arc_color(&style_spinner1_indic, lv_color_make(0x00, 0x00, 0x80));

    lv_obj_t * spinner1 = lv_spinner_create(scr, 2000, 60);
    lv_obj_set_size(spinner1, 80, 80);
	lv_obj_set_pos(spinner1, 10, 180);
    lv_obj_add_style(spinner1, &style_spinner1_bg, LV_PART_MAIN);
    lv_obj_add_style(spinner1, &style_spinner1_indic, LV_PART_INDICATOR);

    static lv_style_t style_spinner2_bg, style_spinner2_indic;
	lv_style_init(&style_spinner2_bg);
	lv_style_set_arc_width(&style_spinner2_bg, 0);
	lv_style_init(&style_spinner2_indic);
	lv_style_set_arc_width(&style_spinner2_indic, 8);
	lv_style_set_arc_color(&style_spinner2_indic, lv_color_make(0x80, 0x00, 0x00));

	
	lv_obj_t *spinner2 = lv_spinner_create(scr, 2000, 120);
	lv_obj_set_size(spinner2, 60, 60);
	lv_obj_set_pos(spinner2, 120, 180);
	lv_obj_add_style(spinner2, &style_spinner2_bg, LV_PART_MAIN);
	lv_obj_add_style(spinner2, &style_spinner2_indic, LV_PART_INDICATOR);
#endif	
	
	//线条
#if 1
	static lv_style_t style_line1;
	lv_style_init(&style_line1);
	lv_style_set_line_width(&style_line1, 10);
	lv_style_set_line_color(&style_line1, lv_color_make(255, 100, 100));
	lv_style_set_line_rounded(&style_line1, true); //圆角
		
	static lv_point_t line_points[] = {{180, 8},{460,40}};
	lv_obj_t *line1 = lv_line_create(scr);
	lv_obj_add_style(line1, &style_line1, LV_PART_MAIN);
	lv_line_set_points(line1, line_points, 2);
	lv_obj_set_pos(line1, 0, 0);	//点的坐标以此为原点
#endif
	
	//led
#if 1
	static lv_style_t style_led1;
	lv_style_init(&style_led1);
	lv_style_set_bg_color(&style_led1, lv_color_make(0xff, 0xff, 0x00));
	
	led1 = lv_led_create(scr);
	lv_obj_set_pos(led1, 350, 215);
	lv_obj_set_size(led1, 50, 50);
	lv_led_off(led1);
	lv_led_set_color(led1, lv_palette_main(LV_PALETTE_YELLOW));
#endif

    //文本域
#if 1
    txt1 = lv_textarea_create(scr);
    lv_obj_set_size(txt1, 380, 30);
    lv_obj_set_pos(txt1, 50, 280);
    // lv_textarea_set_one_line(txt1, true);
    lv_textarea_set_text(txt1, "MAX6675 detect Tempuration: --.-C");
#endif
}

//这里就是测试用的,实际项目不能这么写咯
static void set_temp(void * bar, int32_t temp){
    char str[10] = {0};
    float temp_val = 0;

    lv_bar_set_value(bar, temp, LV_ANIM_ON);
    if(0 == temp%10){
        sprintf(str, "%d%%", temp);
        lv_label_set_text(label_bar1, str);
    }
    if((led1 != NULL) && (0 == temp%50) && (txt1 != NULL)){
        lv_led_toggle(led1);

        max6675_get_temp(&temp_val);
        sprintf(str, "%2.1fC", temp_val);
        lv_textarea_del_char(txt1);
        lv_textarea_del_char(txt1);
        lv_textarea_del_char(txt1);
        lv_textarea_del_char(txt1);
        lv_textarea_del_char(txt1);
        lv_textarea_add_text(txt1, str);
    }
}

差不多就是这样咯

想玩下语音控制,小伙伴们有木有好的 ESP 离线语音开发方案 或 开发板推荐吖

版权声明:本文为CSDN博主「slimmm」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/slimmm/article/details/119975059

生成海报
点赞 0

slimmm

我还没有学会写个人说明!

暂无评论

相关推荐

[GUI] ESP32(idf)驱动3.5寸SPI-TFT屏移植LittleVGL

一、展示成果 博客上传图片限制在5M内,视频转 GIF 帧率压缩太严重了,还得再次压缩才小于5M,效果完全失真了 小伙伴们有什么好的工具或方法解决吗 gif 图上应该看不出什么区别。但是ESP32 用