单片机开发---ESP32S3移植lvgl+触摸屏

书接上文

《单片机开发—ESP32-S3模块上手》

本章内容

熟悉一下ESP32S3的开发,修改范例程序的lvgl,使之能够匹配现在的显示屏。
具体工作大概为通过SPI接口连接一块SPI串口屏幕,并且适配lvgl,最后加上触摸屏作为输入。
单片机开发---ESP32S3移植lvgl+触摸屏_第1张图片

屏幕

用了这块SPI屏幕,带触摸和SD卡插槽。下面有显示部分引脚功能说明
单片机开发---ESP32S3移植lvgl+触摸屏_第2张图片

软件工程

工程有一个lvgl的demo程序,位于Espressif\frameworks\esp-idf-v4.4.3\examples\peripherals\lcd\lvgl路径下,不过默认是支持并口8080的方式进行显示的。
所以要改为SPI方式驱动,进行屏幕显示。原理很简单,开头加上屏幕初始化,显示的时候,将内容输入到显示屏即可。
单片机开发---ESP32S3移植lvgl+触摸屏_第3张图片

相关重点

屏幕,触摸和移植。

屏幕驱动

代码参考Espressif\frameworks\esp-idf-v4.4.3\examples\peripherals\spi_master\lcd\main下面对于spi lcd的控制。
引脚配置如下
单片机开发---ESP32S3移植lvgl+触摸屏_第4张图片
注意s3能用的spi有两个SPI2和SPI3,SPI2通常引脚固定,速度快,SPI3可以选择复用GPIO进行配置,速度稍慢,后面的触摸屏会用到。

屏幕SPI配置

屏幕驱动函数中,注意这里的配置
单片机开发---ESP32S3移植lvgl+触摸屏_第5张图片

时钟频率可以选择最高到80Mhz,不过到了80M,我的屏幕就失真了,可能是屏幕自身不支持这么大的速率。
单片机开发---ESP32S3移植lvgl+触摸屏_第6张图片

并行绘制

例子中注意这个宏定义,注释意思就是

为了加快传输速度,每个SPI传输都会发送一组显示的线。 此定义指定数量。更多意味着更多的内存使用,
但设置/完成传输的开销较小。确保240可以除以这个。

//To speed up transfers, every SPI transfer sends a bunch of lines. This define specifies how many. More means more memory use,
//but less overhead for setting up / finishing transfers. Make sure 240 is dividable by this.
#define PARALLEL_LINES 40

就是将一副图片,按照line的方式进行显示,每次传输40行数据。

众所周知,ili9341芯片,是支持直接写入显存的,大概意思就是,屏幕有一块显存,对应了每个像素点,通过2A和2B命令,配置好写入位置,
2A表示修改列范围
单片机开发---ESP32S3移植lvgl+触摸屏_第7张图片
2B表示修改行范围
单片机开发---ESP32S3移植lvgl+触摸屏_第8张图片
再执行2C命令,就可以直接写入显存数据了。

这个位置数据都是矩形,那么我们就可以以40行图像作为一个矩形,写入显存。

那么问题来了,既然有这么大的显存。那么我就要一帧一帧的数据写入可以吗?

答案是不可以,将PARALLEL_LINES调整超过50之后,就只能显示一部分画面了,因为SPI的一次传输,是有限制的

#define LCD_SPI_MAX_DATA_SIZE (SPI_LL_DATA_MAX_BIT_LEN / 8)
#define SPI_LL_DATA_MAX_BIT_LEN (1 << 18)

最多传输32768个字节,对于宽度320像素的屏幕,每行是320*2个字节,所以经过计算,每次最多传输
51行。

那么在小于51中,最大被240整除的数,那自然就是48了,所以设置为48,是效率最高的时候。
单片机开发---ESP32S3移植lvgl+触摸屏_第9张图片

完整驱动

参考spi驱动lcd,将其封装为一个文件,对外提供一个初始化及绘图函数即可

/*
lcd_ili9341.c
  */

#include 
#include 
#include 

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_system.h"

static const char *TAG = "lcd_ili9341";


#define LCD_HOST    SPI2_HOST

#define PIN_NUM_MISO SPI2_IOMUX_PIN_NUM_MISO
#define PIN_NUM_MOSI SPI2_IOMUX_PIN_NUM_MOSI
#define PIN_NUM_CLK  SPI2_IOMUX_PIN_NUM_CLK
#define PIN_NUM_CS   SPI2_IOMUX_PIN_NUM_CS

#define PIN_NUM_DC   9
#define PIN_NUM_RST  4
#define PIN_NUM_BCKL 5

#define PARALLEL_LINES 48

/*
 The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct.
*/
typedef struct 
{
    uint8_t cmd;
    uint8_t data[16];
    uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
} lcd_init_cmd_t;

typedef enum
{
    LCD_TYPE_ILI = 1,
    LCD_TYPE_ST,
    LCD_TYPE_MAX,
} type_lcd_t;

//Place data into DRAM. Constant data gets placed into DROM by default, which is not accessible by DMA.
DRAM_ATTR static const lcd_init_cmd_t st_init_cmds[]={
    /* Memory Data Access Control, MX=MV=1, MY=ML=MH=0, RGB=0 */
    {0x36, {(1<<5)|(1<<6)}, 1},
    /* Interface Pixel Format, 16bits/pixel for RGB/MCU interface */
    {0x3A, {0x55}, 1},
    /* Porch Setting */
    {0xB2, {0x0c, 0x0c, 0x00, 0x33, 0x33}, 5},
    /* Gate Control, Vgh=13.65V, Vgl=-10.43V */
    {0xB7, {0x45}, 1},
    /* VCOM Setting, VCOM=1.175V */
    {0xBB, {0x2B}, 1},
    /* LCM Control, XOR: BGR, MX, MH */
    {0xC0, {0x2C}, 1},
    /* VDV and VRH Command Enable, enable=1 */
    {0xC2, {0x01, 0xff}, 2},
    /* VRH Set, Vap=4.4+... */
    {0xC3, {0x11}, 1},
    /* VDV Set, VDV=0 */
    {0xC4, {0x20}, 1},
    /* Frame Rate Control, 60Hz, inversion=0 */
    {0xC6, {0x0f}, 1},
    /* Power Control 1, AVDD=6.8V, AVCL=-4.8V, VDDS=2.3V */
    {0xD0, {0xA4, 0xA1}, 1},
    /* Positive Voltage Gamma Control */
    {0xE0, {0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D, 0x37, 0x43, 0x47, 0x09, 0x15, 0x12, 0x16, 0x19}, 14},
    /* Negative Voltage Gamma Control */
    {0xE1, {0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19}, 14},
    /* Sleep Out */
    {0x11, {0}, 0x80},
    /* Display On */
    {0x29, {0}, 0x80},
    {0, {0}, 0xff}
};

DRAM_ATTR static const lcd_init_cmd_t ili_init_cmds[]={
    /* Power contorl B, power control = 0, DC_ENA = 1 */
    {0xCF, {0x00, 0x83, 0X30}, 3},
    /* Power on sequence control,
     * cp1 keeps 1 frame, 1st frame enable
     * vcl = 0, ddvdh=3, vgh=1, vgl=2
     * DDVDH_ENH=1
     */
    {0xED, {0x64, 0x03, 0X12, 0X81}, 4},
    /* Driver timing control A,
     * non-overlap=default +1
     * EQ=default - 1, CR=default
     * pre-charge=default - 1
     */
    {0xE8, {0x85, 0x01, 0x79}, 3},
    /* Power control A, Vcore=1.6V, DDVDH=5.6V */
    {0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5},
    /* Pump ratio control, DDVDH=2xVCl */
    {0xF7, {0x20}, 1},
    /* Driver timing control, all=0 unit */
    {0xEA, {0x00, 0x00}, 2},
    /* Power control 1, GVDD=4.75V */
    {0xC0, {0x26}, 1},
    /* Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 */
    {0xC1, {0x11}, 1},
    /* VCOM control 1, VCOMH=4.025V, VCOML=-0.950V */
    {0xC5, {0x35, 0x3E}, 2},
    /* VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 */
    {0xC7, {0xBE}, 1},
    /* Memory access contorl, MX=MY=0, MV=1, ML=0, BGR=1, MH=0 */
    {0x36, {0x28}, 1},
    /* Pixel format, 16bits/pixel for RGB/MCU interface */
    {0x3A, {0x55}, 1},
    /* Frame rate control, f=fosc, 70Hz fps */
    {0xB1, {0x00, 0x1B}, 2},
    /* Enable 3G, disabled */
    {0xF2, {0x08}, 1},
    /* Gamma set, curve 1 */
    {0x26, {0x01}, 1},
    /* Positive gamma correction */
    {0xE0, {0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00}, 15},
    /* Negative gamma correction */
    {0XE1, {0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F}, 15},
    /* Column address set, SC=0, EC=0xEF */
    {0x2A, {0x00, 0x00, 0x00, 0xEF}, 4},
    /* Page address set, SP=0, EP=0x013F */
    {0x2B, {0x00, 0x00, 0x01, 0x3f}, 4},
    /* Memory write */
    {0x2C, {0}, 0},
    /* Entry mode set, Low vol detect disabled, normal display */
    {0xB7, {0x07}, 1},
    /* Display function control */
    {0xB6, {0x0A, 0x82, 0x27, 0x00}, 4},
    /* Sleep out */
    {0x11, {0}, 0x80},
    /* Display on */
    {0x29, {0}, 0x80},
    {0, {0}, 0xff},
};

static void lcd_spi_pre_transfer_callback(spi_transaction_t *t);

void lcd_spi_init(spi_device_handle_t* spi)
{
	esp_err_t ret;
	spi_bus_config_t buscfg=
	{
		.miso_io_num=PIN_NUM_MISO,
		.mosi_io_num=PIN_NUM_MOSI,
		.sclk_io_num=PIN_NUM_CLK,
		.quadwp_io_num=-1,
		.quadhd_io_num=-1,
		.max_transfer_sz=PARALLEL_LINES*320*2+8
	};
	
	spi_device_interface_config_t devcfg=
	{
		.clock_speed_hz=SPI_MASTER_FREQ_40M,	//26*1000*1000, 		  //Clock out at 26 MHz
		.mode=0,								//SPI mode 0
		.spics_io_num=PIN_NUM_CS,				//CS pin
		.queue_size=7,							//We want to be able to queue 7 transactions at a time
		.pre_cb=lcd_spi_pre_transfer_callback,	//Specify pre-transfer callback to handle D/C line
	};

	//Initialize the SPI bus
	ret=spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO);
	ESP_ERROR_CHECK(ret);

	//Attach the LCD to the SPI bus
	ret=spi_bus_add_device(LCD_HOST, &devcfg, spi);
	ESP_ERROR_CHECK(ret);
}

/* Send a command to the LCD. Uses spi_device_polling_transmit, which waits
 * until the transfer is complete.
 *
 * Since command transactions are usually small, they are handled in polling
 * mode for higher speed. The overhead of interrupt transactions is more than
 * just waiting for the transaction to complete.
 */
static void lcd_cmd(spi_device_handle_t spi, const uint8_t cmd)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length=8;                     //Command is 8 bits
    t.tx_buffer=&cmd;               //The data is the cmd itself
    t.user=(void*)0;                //D/C needs to be set to 0
    ret=spi_device_polling_transmit(spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.
}

/* Send data to the LCD. Uses spi_device_polling_transmit, which waits until the
 * transfer is complete.
 *
 * Since data transactions are usually small, they are handled in polling
 * mode for higher speed. The overhead of interrupt transactions is more than
 * just waiting for the transaction to complete.
 */
static void lcd_data(spi_device_handle_t spi, const uint8_t *data, int len)
{
    esp_err_t ret;
    spi_transaction_t t;
    if (len==0) return;             //no need to send anything
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length=len*8;                 //Len is in bytes, transaction length is in bits.
    t.tx_buffer=data;               //Data
    t.user=(void*)1;                //D/C needs to be set to 1
    ret=spi_device_polling_transmit(spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.
}

//This function is called (in irq context!) just before a transmission starts. It will
//set the D/C line to the value indicated in the user field.
static void lcd_spi_pre_transfer_callback(spi_transaction_t *t)
{
    int dc=(int)t->user;
    gpio_set_level(PIN_NUM_DC, dc);
	
}

static uint32_t lcd_get_id(spi_device_handle_t spi)
{
    //get_id cmd
    lcd_cmd(spi, 0x04);

    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length=8*3;
    t.flags = SPI_TRANS_USE_RXDATA;
    t.user = (void*)1;

    esp_err_t ret = spi_device_polling_transmit(spi, &t);
    assert( ret == ESP_OK );

    return *(uint32_t*)t.rx_data;
}

//Initialize the display
void lcd_init(spi_device_handle_t spi)
{
    int cmd=0;
    const lcd_init_cmd_t* lcd_init_cmds;

    //Initialize non-SPI GPIOs
    gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_BCKL, GPIO_MODE_OUTPUT);

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

    //detect LCD type
    uint32_t lcd_id = lcd_get_id(spi);
    int lcd_detected_type = 0;
    int lcd_type =LCD_TYPE_ILI;

    printf("LCD ID: %08X\n", lcd_id);
    if ( lcd_id == 0 ) 
	{
        //zero, ili
        lcd_detected_type = LCD_TYPE_ILI;
        printf("ILI9341 detected.\n");
    } 
	else 
	{
        // none-zero, ST
        lcd_detected_type = LCD_TYPE_ST;
        printf("ST7789V detected.\n");
    }

#ifdef CONFIG_LCD_TYPE_AUTO
    lcd_type = lcd_detected_type;
#elif defined( CONFIG_LCD_TYPE_ST7789V )
    printf("kconfig: force CONFIG_LCD_TYPE_ST7789V.\n");
    lcd_type = LCD_TYPE_ST;
#elif defined( CONFIG_LCD_TYPE_ILI9341 )
    printf("kconfig: force CONFIG_LCD_TYPE_ILI9341.\n");
    lcd_type = LCD_TYPE_ILI;
#endif
    if ( lcd_type == LCD_TYPE_ST ) 
	{
        printf("LCD ST7789V initialization.\n");
        lcd_init_cmds = st_init_cmds;
    } 
	else 
	{
        printf("LCD ILI9341 initialization.\n");
        lcd_init_cmds = ili_init_cmds;
    }

    //Send all the commands
    while (lcd_init_cmds[cmd].databytes!=0xff)
	{
        lcd_cmd(spi, lcd_init_cmds[cmd].cmd);
        lcd_data(spi, lcd_init_cmds[cmd].data, lcd_init_cmds[cmd].databytes&0x1F);
        if (lcd_init_cmds[cmd].databytes&0x80) 
		{
            vTaskDelay(100 / portTICK_RATE_MS);
        }
        cmd++;
    }

    ///Enable backlight
    gpio_set_level(PIN_NUM_BCKL, 1);
}


/* To send a set of lines we have to send a command, 2 data bytes, another command, 2 more data bytes and another command
 * before sending the line data itself; a total of 6 transactions. (We can't put all of this in just one transaction
 * because the D/C line needs to be toggled in the middle.)
 * This routine queues these commands up as interrupt transactions so they get
 * sent faster (compared to calling spi_device_transmit several times), and at
 * the mean while the lines for next transactions can get calculated.
 */
void send_lines(spi_device_handle_t spi, int x1pos,int x2pos,int y1pos,int y2pos, uint16_t *linedata,int length)
{
    esp_err_t ret;
    int x;
    //Transaction descriptors. Declared static so they're not allocated on the stack; we need this memory even when this
    //function is finished because the SPI driver needs access to it even while we're already calculating the next line.
    static spi_transaction_t trans[6];

    //In theory, it's better to initialize trans and data only once and hang on to the initialized
    //variables. We allocate them on the stack, so we need to re-init them each call.
    for (x=0; x<6; x++) 
	{
        memset(&trans[x], 0, sizeof(spi_transaction_t));
        if ((x&1)==0) 
		{
            //Even transfers are commands
            trans[x].length=8;
            trans[x].user=(void*)0;
        } 
		else 
		{
            //Odd transfers are data
            trans[x].length=8*4;
            trans[x].user=(void*)1;
        }
        trans[x].flags=SPI_TRANS_USE_TXDATA;
    }
    trans[0].tx_data[0]=0x2A;           //Column Address Set
    trans[1].tx_data[0]=x1pos>>8;              //Start Col High
    trans[1].tx_data[1]=x1pos&0xff;              //Start Col Low
    trans[1].tx_data[2]=x2pos>>8;       //End Col High
    trans[1].tx_data[3]=x2pos&0xff;     //End Col Low
   
    trans[2].tx_data[0]=0x2B;           //Page address set
    trans[3].tx_data[0]=y1pos>>8;        //Start page high
    trans[3].tx_data[1]=y1pos&0xff;      //start page low
    trans[3].tx_data[2]=y2pos>>8;    //end page high
    trans[3].tx_data[3]=y2pos&0xff;  //end page low
   
    trans[4].tx_data[0]=0x2C;           //memory write
    trans[5].tx_buffer=linedata;        //finally send the line data

    trans[5].length=length*8;          //Data length, in bits
    trans[5].flags=0; //undo SPI_TRANS_USE_TXDATA flag

    //Queue all transactions.
    for (x=0; x<6; x++) 
	{
        ret=spi_device_queue_trans(spi, &trans[x], portMAX_DELAY);
        assert(ret==ESP_OK);
    }

    //When we are here, the SPI driver is busy (in the background) getting the transactions sent. That happens
    //mostly using DMA, so the CPU doesn't have much to do here. We're not going to wait for the transaction to
    //finish because we may as well spend the time calculating the next line. When that is done, we can call
    //send_line_finish, which will wait for the transfers to be done and check their status.
}

void send_line_finish(spi_device_handle_t spi)
{
    spi_transaction_t *rtrans;
    esp_err_t ret;
    //Wait for all 6 transactions to be done and get back the results.
    for (int x=0; x<6; x++) 
	{
        ret=spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY);
        assert(ret==ESP_OK);
        //We could inspect rtrans now if we received any info back. The LCD is treated as write-only, though.
    }
}

效果测试

单片机开发---ESP32S3移植lvgl+触摸屏_第10张图片
经过测试,例子大概在每秒23帧左右。也还算可以了,毕竟电影才每秒24帧。

触摸屏使用

这个触摸屏也是SPI接口,引脚说明
单片机开发---ESP32S3移植lvgl+触摸屏_第11张图片

触摸屏引脚定义

这次用SPI3,采用复用GPIO的方式使用,只是在配置上有所区别,使用来是一样的。

单片机开发---ESP32S3移植lvgl+触摸屏_第12张图片
不过注意,这里有个坑,后续会填上。

单片机开发---ESP32S3移植lvgl+触摸屏_第13张图片

触摸屏SPI配置

这块也是研究很久才调试通过了。单片机开发---ESP32S3移植lvgl+触摸屏_第14张图片
这里的配置要重点看一下,尤其是devcfg中,关于命令和地址长度的配置,否则无法正常驱动触摸屏。

还有速率,不适合太高,否则读取数据太快,反而读不到。
单片机开发---ESP32S3移植lvgl+触摸屏_第15张图片

XPT2046的使用

简单来说,就是当IRQ引脚电压为高的时候,就表示有触摸操作,随后读取xy模拟坐标,最后通过边界值,计算出物理坐标。

单片机开发---ESP32S3移植lvgl+触摸屏_第16张图片
根据不同的屏幕大小,模拟坐标变化范围不一样,所以计算坐标时候的数值也不一样。举个例子
我用的屏幕

  1. 得到左上角和右下角的模拟量坐标XY极限值
    x=1960,y=1871
    x=152,y=110

  2. 转换模拟坐标值范围
    x坐标:1960~152 -->1808~0
    y坐标:1871~110 --> 1761~0

  3. 计算斜率,即计算出每个像素对应多少单位模拟量
    x坐标的斜率: 1808/320=5.65
    y坐标的斜率: 1761/240=7.3375

  4. 得到实际的像素坐标
    x坐标: 320-(模拟量-152)/5.65
    y坐标: 240-(模拟量-110)/7.3375

后续可能需要增加一些去抖动。

触摸屏完整驱动

/*
 touch.c
 */

#include 
#include 
#include 

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_system.h"

static const char *TAG = "touch";


#define TOUCH_HOST    SPI3_HOST

#define TOUCH_MISO 15 
#define TOUCH_MOSI 16
#define TOUCH_CLK  17
#define TOUCH_CS   18
#define TOUCH_IRQ  37

static spi_device_handle_t touch_spi;



void touch_spi_init(void)
{
	esp_err_t ret;
	spi_bus_config_t buscfg=
	{
		.miso_io_num=TOUCH_MISO,
		.mosi_io_num=TOUCH_MOSI,
		.sclk_io_num=TOUCH_CLK,
		.quadwp_io_num=-1,
		.quadhd_io_num=-1,
		//.max_transfer_sz=PARALLEL_LINES*320*2+8
		.flags = SPICOMMON_BUSFLAG_GPIO_PINS
	};
	
	spi_device_interface_config_t devcfg=
	{
		.command_bits=8,
		.address_bits=0,
		.clock_speed_hz=1000*1000,//SPI_MASTER_FREQ_10M,	//26*1000*1000, 		  //Clock out at 26 MHz
		.mode=0,								//SPI mode 0
		.spics_io_num=TOUCH_CS,				//CS pin
		.queue_size=7,							//We want to be able to queue 7 transactions at a time
		//.pre_cb=lcd_spi_pre_transfer_callback,	//Specify pre-transfer callback to handle D/C line
	};

	//Initialize the SPI bus
	ret=spi_bus_initialize(TOUCH_HOST, &buscfg, SPI_DMA_CH_AUTO);
	ESP_ERROR_CHECK(ret);

	//Attach the LCD to the SPI bus
	ret=spi_bus_add_device(TOUCH_HOST, &devcfg,&touch_spi);
	ESP_ERROR_CHECK(ret);

    gpio_set_direction(TOUCH_IRQ, GPIO_MODE_INPUT);
}


/*
函数功能:  读2个字节
说明: 读取16位数据,最低4位数据无效,有效数据是高12位

*/
static uint16_t XPT2046_ReadData(uint8_t cmd)
{

	uint16_t data=0;

	spi_transaction_t t = 
	{
	.cmd = cmd,
	.length=16,
	.rxlength = 16,
	.flags = SPI_TRANS_USE_RXDATA,
	};
    esp_err_t ret = spi_device_polling_transmit(touch_spi, &t);
    assert( ret == ESP_OK );

	data=(t.rx_data[0]<<4)+(t.rx_data[1]>>4);
    return data;


}


/*
XPT2046的命令:

10010000  :测试Y的坐标 0x90
11010000  :测试X的坐标 0xD0

返回值:  0表示没有读取到坐标,1表示读取到当前坐标

//1. 得到左上角和右下角的坐标XY极限值
  x=1960,y=1871
  x=152,y=110

//2. 转换坐标值
  x坐标:1960~152 -->1808~0
  y坐标:1871~110 -->  1761~0

//3. 计算斜率
  x坐标的斜率: 1808/320=5.65
  y坐标的斜率: 1761/240=7.3375

//4. 得到实际的像素坐标
  x坐标:  320-(模拟量-152)/5.5
  y坐标:  240-(模拟量-110)/7.3375
*/
uint8_t touch_ReadXY(uint16_t* x,uint16_t* y)
{
	if(gpio_get_level(TOUCH_IRQ)==0)
	{
        vTaskDelay(pdMS_TO_TICKS(10));
		if(gpio_get_level(TOUCH_IRQ)==0)
		{
			vTaskDelay(pdMS_TO_TICKS(10));
			/*1. 得到物理坐标*/
			int x0=XPT2046_ReadData(0x90);
			int y0=XPT2046_ReadData(0xD0);
		
			//printf("%d,%d\n",x0,y0);		
			/*2. 得到像素坐标*/
			*x=320-(x0-152)/5.65;
			*y=240-(y0-110)/7.3375;
			//ESP_LOGI(TAG, "原始 x:%d,y:%d ,转换后 x:%d,y:%d",x0,y0,*x,*y);
			
			return 1;
		}
		else
			return 0;
	}
	else
		return 0;
}


LVGL修改

demo中默认支持的是 并口,所以要修改为通过SPI方式显示
单片机开发---ESP32S3移植lvgl+触摸屏_第17张图片
在显示回调 ,调用见面封装的ili9341显示接口。
单片机开发---ESP32S3移植lvgl+触摸屏_第18张图片
触摸设备同样需要注册
在这里插入图片描述
回调函数中,进行坐标读取。单片机开发---ESP32S3移植lvgl+触摸屏_第19张图片
这里调动额select模块测试了一下,触摸还算可以
单片机开发---ESP32S3移植lvgl+触摸屏_第20张图片

过程问题

随后在性能提升的时候,配置SPI PSRAM的时候,一旦开启PSRAM,就 出现启动卡主的问题,因为之前一直这样开启,也没问题,一度以为自己的模块坏了。
单片机开发---ESP32S3移植lvgl+触摸屏_第21张图片

后来想起来这个关键信息

单片机开发---ESP32S3移植lvgl+触摸屏_第22张图片
原来是37引脚的问题,所以修改了一下触摸引脚IRQ,问题得到了解决。
再次记录一下完整启动打印

ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3808,len:0x1664
load:0x403c9700,len:0xbb8
load:0x403cc700,len:0x2e8c
entry 0x403c9954
I (24) boot: ESP-IDF v4.4.3-dirty 2nd stage bootloader
I (25) boot: compile time 11:10:38
I (25) boot: chip revision: 0
I (27) boot.esp32s3: Boot SPI Speed : 80MHz
I (32) boot.esp32s3: SPI Mode       : DIO
I (36) boot.esp32s3: SPI Flash Size : 16MB
I (41) boot: Enabling RNG early entropy source...
I (47) boot: Partition Table:
I (50) boot: ## Label            Usage          Type ST Offset   Length
I (57) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (65) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (72) boot:  2 factory          factory app      00 00 00010000 00100000
I (80) boot: End of partition table
I (84) esp_image: segment 0: paddr=00010020 vaddr=3c050020 size=0df08h ( 57096) map
I (103) esp_image: segment 1: paddr=0001df30 vaddr=3fc93730 size=020e8h (  8424) load
I (105) esp_image: segment 2: paddr=00020020 vaddr=42000020 size=435e0h (275936) map
I (159) esp_image: segment 3: paddr=00063608 vaddr=3fc95818 size=00ce0h (  3296) load
I (160) esp_image: segment 4: paddr=000642f0 vaddr=40374000 size=0f730h ( 63280) load
I (178) esp_image: segment 5: paddr=00073a28 vaddr=50000000 size=00010h (    16) load
I (185) boot: Loaded app from partition at offset 0x10000
I (185) boot: Disabling RNG early entropy source...
I (199) opi psram: vendor id : 0x0d (AP)
I (199) opi psram: dev id    : 0x02 (generation 3)
I (199) opi psram: density   : 0x03 (64 Mbit)
I (203) opi psram: good-die  : 0x01 (Pass)
I (207) opi psram: Latency   : 0x01 (Fixed)
I (212) opi psram: VCC       : 0x01 (3V)
I (217) opi psram: SRF       : 0x01 (Fast Refresh)
I (222) opi psram: BurstType : 0x01 (Hybrid Wrap)
I (228) opi psram: BurstLen  : 0x01 (32 Byte)
I (233) opi psram: Readlatency  : 0x02 (10 cycles@Fixed)
I (239) opi psram: DriveStrength: 0x00 (1/1)
W (244) PSRAM: DO NOT USE FOR MASS PRODUCTION! Timing parameters will be updated in future IDF version.
I (254) spiram: Found 64MBit SPI RAM device
I (259) spiram: SPI RAM mode: sram 80m
I (263) spiram: PSRAM initialized, cache is in normal (1-core) mode.
I (270) cpu_start: Pro cpu up.
I (274) cpu_start: Starting app cpu, entry point is 0x40375328
0x40375328: call_start_cpu1 at E:/esp32_new_tools/Espressif/frameworks/esp-idf-v4.4.3/components/esp_system/port/cpu_start.c:148

I (0) cpu_start: App cpu up.
I (700) spiram: SPI SRAM memory test OK
I (709) cpu_start: Pro cpu start user code
I (709) cpu_start: cpu freq: 240000000
I (709) cpu_start: Application information:
I (712) cpu_start: Project name:     lcd_lvgl
I (717) cpu_start: App version:      1
I (721) cpu_start: Compile time:     Jan 30 2023 11:09:49
I (727) cpu_start: ELF file SHA256:  07e942fc9d6734b0...
I (733) cpu_start: ESP-IDF:          v4.4.3-dirty
I (739) heap_init: Initializing. RAM available for dynamic allocation:
I (746) heap_init: At 3FC9F478 len 0004A298 (296 KiB): D/IRAM
I (752) heap_init: At 3FCE9710 len 00005724 (21 KiB): STACK/DRAM
I (759) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM
I (765) heap_init: At 600FE000 len 00002000 (8 KiB): RTCRAM
I (772) spiram: Adding pool of 8192K of external SPI memory to heap allocator
I (780) spi_flash: detected chip: gd
I (784) spi_flash: flash io: dio
I (788) sleep: Configure to isolate all GPIO pins in sleep state
I (794) sleep: Enable automatic switching of GPIO sleep configuration
I (802) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (822) spiram: Reserving pool of 32K of internal memory for DMA/internal allocations
LCD ID: 00000000
ILI9341 detected.
LCD ILI9341 initialization.
I (1222) s3_lvgl: Initialize LVGL library
I (1222) s3_lvgl: Register display driver to LVGL
I (1222) s3_lvgl: Install LVGL tick timer
I (1222) s3_lvgl: Display LVGL animation

只想说一句。如果不能用,你引出来干嘛?让人看着着急吗?

单片机开发---ESP32S3移植lvgl+触摸屏_第23张图片

代码下载及参考

完整代码下载
使用方法:
首先拷贝Espressif\frameworks\esp-idf-v4.4.3\examples\peripherals\lcd\lvgl作为工程,
然后将下载内容复制进去,即可完成编译,

其实不太推荐,还是需要自己学习一遍。然后参考前面的问题,进行自己实践更明白。

参考资料
《STM32 入门开发:编写 XPT2046 电阻触摸屏驱动 (模拟 SPI)》

单片机开发---ESP32S3移植lvgl+触摸屏_第24张图片

结束语

昨天最大的news大概就是某失踪很久的同学,被发现在校外不远 树林中,一个拉网搜索过的地方,其实感觉最大的可能,就是不是第一现场,100多天,不可能没人看到。所以,真相还没有浮出水面。而且感觉看到的,不一定就是真相,毕竟这么久都没有出来结果。
单片机开发---ESP32S3移植lvgl+触摸屏_第25张图片
就像现在的推送机制的存在,你爱看的内容,认同的内容,就会越来越多出现在你的视野里,久而久之,你认为世界就应该是这样,一旦出现了一些不同的声音,你就会认为是少数,是另类,甚至是极端。这就是信息茧房。我们学校的教育也是一样,教育仅仅是一种福利,因为目前的教育带有浓重的儒家思想,偏向于使人遵守规矩,服从管理……
所以为了保持清醒,要多想多思考,并且要保持理性。
单片机开发---ESP32S3移植lvgl+触摸屏_第26张图片

你可能感兴趣的:(单片机,C语言典型代码,操作系统,单片机,嵌入式硬件,esp32,lvgl,触摸屏)