乐鑫的IDF软件写的真的是单片机领域的linux级别,手上正好有一块ESP-WROVER-KIT开发板,自带一块320*240分辨率的TFT液晶屏,虽然ESP32不是为这类显示屏而生,但是人家主频性能好啊,DMA加速一下,跑个简单的界面no problem的,我又不用它来播放视频。
LittlevGL的官方其实已经移植了一个版本,但是在写法上没有充分利用好dma的优势,这里仿照着乐鑫的example写了一个chart demo
上图
上代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_freertos_hooks.h"
#include "lvgl.h"
#define PIN_NUM_MISO 25
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK 19
#define PIN_NUM_CS 22
#define PIN_NUM_DC 21
#define PIN_NUM_RST 18
#define PIN_NUM_BCKL 5
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;
static spi_device_handle_t spi;
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_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_transmit(spi, &t); //Transmit!
assert(ret == ESP_OK); //Should have had no issues.
}
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_transmit(spi, &t); //Transmit!
assert(ret == ESP_OK); //Should have had no issues.
}
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_transmit(spi, &t);
assert(ret == ESP_OK);
return *(uint32_t *)t.rx_data;
}
static void my_disp_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t *color_map)
{
esp_err_t ret;
spi_transaction_t trans[6];
for (int 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;
}
uint32_t size = (x2 - x1 + 1) * (y2 - y1 + 1);
uint8_t *color_u8 = (uint8_t *)color_map;
for (uint32_t i = 0; i < size * 2; i += 2)
{
color_u8[i] ^= color_u8[i + 1];
color_u8[i + 1] ^= color_u8[i];
color_u8[i] ^= color_u8[i + 1];
}
trans[0].tx_data[0] = 0x2A; //Column Address Set
trans[1].tx_data[0] = (x1 >> 8) & 0xFF; //Start Col High
trans[1].tx_data[1] = x1 & 0xFF; //Start Col Low
trans[1].tx_data[2] = (x2 >> 8) & 0xFF; //End Col High
trans[1].tx_data[3] = x2 & 0xFF; //End Col Low
trans[2].tx_data[0] = 0x2B; //Page address set
trans[3].tx_data[0] = (y1 >> 8) & 0xFF; //Start page high
trans[3].tx_data[1] = y1 & 0xFF; //start page low
trans[3].tx_data[2] = (y2 >> 8) & 0xFF; //end page high
trans[3].tx_data[3] = y2 & 0xFF; //end page low
trans[4].tx_data[0] = 0x2C; //memory write
trans[5].tx_buffer = color_map;
trans[5].flags = 0;
trans[5].length = size * 2 * 8;
//Queue all transactions.
for (int x = 0; x < 6; x++)
{
ret = spi_device_queue_trans(spi, &trans[x], portMAX_DELAY);
assert(ret == ESP_OK);
}
spi_transaction_t *rtrans;
for (int x = 0; x < 6; x++)
{
ret = spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY);
assert(ret == ESP_OK);
}
lv_flush_ready();
}
static void spi_spec_init()
{
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 = 12800};
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 26 * 1000 * 1000,
.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(HSPI_HOST, &buscfg, 1);
ESP_ERROR_CHECK(ret);
//Attach the LCD to the SPI bus
ret = spi_bus_add_device(HSPI_HOST, &devcfg, &spi);
ESP_ERROR_CHECK(ret);
}
static 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_PERIOD_MS);
gpio_set_level(PIN_NUM_RST, 1);
vTaskDelay(100 / portTICK_PERIOD_MS);
//detect LCD type
uint32_t lcd_id = lcd_get_id(spi);
int lcd_detected_type = 0;
int lcd_type;
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");
}
lcd_type = lcd_detected_type;
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_PERIOD_MS);
}
cmd++;
}
///Enable backlight
gpio_set_level(PIN_NUM_BCKL, 0);
}
static void lv_tick_task(void)
{
lv_tick_inc(1);
}
static lv_obj_t *chart = NULL;
static lv_chart_series_t *series = NULL;
static void gui_init(void)
{
//Initialize LittlevGL Library
lv_init();
//Register display driver
lv_disp_drv_t disp;
lv_disp_drv_init(&disp);
disp.disp_flush = my_disp_flush;
lv_disp_drv_register(&disp);
//Bind littlevgl tick counter to FreeRTOS
esp_register_freertos_tick_hook(lv_tick_task);
//Create a new Screen
lv_obj_t *scr = lv_obj_create(NULL, NULL);
lv_scr_load(scr);
//Use alien theme
lv_theme_t *th = lv_theme_alien_init(100, NULL);
lv_theme_set_current(th);
chart = lv_chart_create(scr, NULL);
lv_obj_set_size(chart, 300, 200);
lv_obj_align(chart, NULL, LV_ALIGN_CENTER, 0, 0);
lv_chart_set_type(chart, LV_CHART_TYPE_POINT | LV_CHART_TYPE_LINE);
lv_chart_set_series_opa(chart, LV_OPA_70);
lv_chart_set_series_width(chart, 4);
lv_chart_set_range(chart, 0, 100);
series = lv_chart_add_series(chart, LV_COLOR_RED);
}
static void gui_turbo_task(void *pvParameter)
{
while (1)
{
vTaskDelay(100 / portTICK_PERIOD_MS);
lv_task_handler();
}
}
static void user_task(void *pvParameter)
{
uint8_t value = 0;
while (1)
{
value = esp_random() % 100;
lv_chart_set_next(chart, series, value);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main()
{
spi_spec_init();
lcd_init(spi);
gui_init();
xTaskCreate(
gui_turbo_task, //Task Function
"gui_turbo_task", //Task Name
2048, //Stack Depth
NULL, //Parameters
1, //Priority
NULL); //Task Handler
xTaskCreate(
user_task, //Task Function
"user_task", //Task Name
512, //Stack Depth
NULL, //Parameters
1, //Priority
NULL); //Task Handler
}
离线
厉害了,学习!
离线
感谢分享!收藏了
离线
感谢分享!收藏了
离线
厉害!屏是SPI接口的吗?刷一帧,速度快不?
离线
厉害!屏是SPI接口的吗?刷一帧,速度快不?
1. 是SPI接口的屏幕
2. 跑demo的时候没有发现卡顿
离线
谢谢分享,楼主能分享下整个工程吗?谢谢啦。
离线
在mircopython里有大神移植过LittlevGL没?
离线
感谢分享!收藏了
离线
楼主,跑一下lv_demo_benchmark 看看FPS有多少
离线
学习了,谢谢分享
离线
在mircopython里有大神移植过LittlevGL没?
我之前移植过mircopython下对littlevgl编程很鸡肋,不太灵活,而且很多问题
离线
现在lvgl官方推出的esp32_helper库怎么说呢,感觉自己写比较轻量吧,它写的有点庞杂了。其实它要是支持分立下载能更好,毕竟需要适配的设备就一个,不需要其他驱动
离线
esp32S3光是启用PSram我都折腾了好久 DMA还没试过
离线