图33.3.1.1 RGB实验程序流程图
33.3.2 RGB-LCD函数解析
ESP-IDF提供了一套API来配置RGB。要使用此功能,需要导入必要的头文件:
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h"
接下来,作者将介绍一些常用的ESP32-S3中的RGB函数,这些函数的描述及其作用如下:
1,创建RGB对象
该函数通过配置结构体参数的方式将参数以指针的方式传进,该函数原型如下所示:
esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t
*rgb_panel_config,
esp_lcd_panel_handle_t *ret_panel);
该函数的形参描述如下表所示:
参数 | 描述 |
| RGB 配置,指针参数 |
| 返回LCD句柄,指针参数 |
表33.3.2.1 esp_lcd_new_rgb_panel()函数形参描述
该函数的返回值描述,如下表所示:
| |
ESP_ERR_INVALID_ARG | |
ESP_ERR_NO_MEM | |
ESP_ERR_NOT_FOUND | 创建RGB LCD面板失败,因为没有找到一些必需的硬件资源 |
ESP_OK | |
表33.3.2.2 函数esp_lcd_new_rgb_panel ()返回值描述
该函数使用esp_lcd_rgb_panel_config_t类型的结构体变量传入,该结构体的定义如下:
| 成员变量 | |
esp_lcd_rgb_panel_config_t | | |
psram_trans_align: 在PSRAM中分配的帧缓冲区的对齐 | |
| 这里设置为 GPIO_PULLUP_ENABLE,启用内部上拉,避免悬空。 |
| |
| 这里设置为 GPIO_PULLUP_ENABLE,启用内部上拉,避免悬空。 |
| 这里设置为 400000,即 400kHz,是 I2C 的常用速度。 |
| |
| |
| |
| |
.flags.fb_in_psram: 在PSRAM中分配帧缓冲区 | |
| |
表33.3.2.3 esp_lcd_rgb_panel_config_t结构体参数值描述
完成上述结构体参数配置之后,可以将结构传递给 esp_lcd_new_rgb_panel () 函数,用以实例化RGB。
2,复位RGB屏幕
在创建RGB屏幕对象后需要进行RGB屏幕复位,该函数原型如下所示:
esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel);
该函数的形参描述如下表所示:
表33.3.2.4 esp_lcd_panel_reset ()函数形参描述
该函数的返回值描述,如下表所示:
表33.3.2.5 函数esp_lcd_panel_reset ()返回值描述
3,初始化RGB屏幕
通过上两个步骤的配置,可以对屏幕进行初始化了,该函数原型如下所示:
esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel);
该函数的形参描述如下表所示:
表33.3.2.6 esp_lcd_panel_init ()函数形参描述
该函数的返回值描述,如下表所示:
表33.3.2.7 函数esp_lcd_panel_init ()返回值描述
33.3.3 RGB-LCD驱动解析
在IDF版的23_rgb例程中,作者23_rgb\components\BSP路径下新增了一个RGB文件夹,分别用于存放ltdc.c、ltdc.h以及ltdcfont.h这三个文件。其中,ltdc.h和ltdc.h文件负责声明RGB相关的函数和变量并存放了LTDC 字库,而ltdc.c文件则实现了RGB的驱动代码。下面,我们将详细解析这三个文件的实现内容。
1,ltdc.h文件
该文件下定义了屏幕需要用到的一些重要参数,比如引脚定义,RGB颜色的十六进制值以及屏幕横向纵向的方向参数等。
/* RGB_BL */
#define LCD_BL(x) do { x ? \
xl9555_pin_write(LCD_BL_IO, 1): \
xl9555_pin_write(LCD_BL_IO, 0); \
} while(0)
/* RGBLCD引脚 */
#define GPIO_LCD_DE (GPIO_NUM_4)
#define GPIO_LCD_VSYNC (GPIO_NUM_NC)
#define GPIO_LCD_HSYNC (GPIO_NUM_NC)
#define GPIO_LCD_PCLK (GPIO_NUM_5)
#define GPIO_LCD_R3 (GPIO_NUM_45)
#define GPIO_LCD_R4 (GPIO_NUM_48)
#define GPIO_LCD_R5 (GPIO_NUM_47)
#define GPIO_LCD_R6 (GPIO_NUM_21)
#define GPIO_LCD_R7 (GPIO_NUM_14)
#define GPIO_LCD_G2 (GPIO_NUM_10)
#define GPIO_LCD_G3 (GPIO_NUM_9)
#define GPIO_LCD_G4 (GPIO_NUM_46)
#define GPIO_LCD_G5 (GPIO_NUM_3)
#define GPIO_LCD_G6 (GPIO_NUM_8)
#define GPIO_LCD_G7 (GPIO_NUM_18)
#define GPIO_LCD_B3 (GPIO_NUM_17)
#define GPIO_LCD_B4 (GPIO_NUM_16)
#define GPIO_LCD_B5 (GPIO_NUM_15)
#define GPIO_LCD_B6 (GPIO_NUM_7)
#define GPIO_LCD_B7 (GPIO_NUM_6)
/* 常用画笔颜色 */
#define WHITE 0xFFFF /* 白色 */
#define BLACK 0x0000 /* 黑色 */
#define RED 0xF800 /* 红色 */
#define GREEN 0x07E0 /* 绿色 */
#define BLUE 0x001F /* 蓝色 */
#define MAGENTA 0xF81F /* 品红色/紫红色 = BLUE + RED */
#define YELLOW 0xFFE0 /* 黄色 = GREEN + RED */
#define CYAN 0x07FF /* 青色 = GREEN + BLUE */
/* LCD LTDC重要参数集 */
typedef struct
{
/* LTDC面板的宽度,固定参数,不随显示方向改变,如果为0,说明没有任何RGB屏接入 */
uint32_t pwidth;
uint32_t pheight; /* LTDC面板的高度,固定参数,不随显示方向改变 */
uint16_t hsw; /* 水平同步宽度 */
uint16_t vsw; /* 垂直同步宽度 */
uint16_t hbp; /* 水平后廊 */
uint16_t vbp; /* 垂直后廊 */
uint16_t hfp; /* 水平前廊 */
uint16_t vfp; /* 垂直前廊 */
uint8_t activelayer; /* 当前层编号:0/1 */
uint8_t dir; /* 0,竖屏;1,横屏; */
uint16_t id; /* LTDC ID */
uint32_t pclk_hz; /* 设置像素时钟 */
uint16_t width; /* LTDC宽度 */
uint16_t height; /* LTDC高度 */
} _ltdc_dev;
/* LTDC参数 */
extern _ltdc_dev ltdcdev; /* 管理LTDC重要参数 */
extern esp_lcd_panel_handle_t panel_handle;
2,ltdc.c文件
该文件下便是实现RGB屏幕的驱动函数了。由于代码过长,笔者仅整理出部分代码粘贴至此,详细内容请各位读者进入工程界面进行理解与学习。
static const char *TAG = "ltdc";
esp_lcd_panel_handle_t panel_handle = NULL; /* RGBLCD句柄 */
/* 定义portMUX_TYPE类型的自旋锁变量,用于临界区保护 */
static portMUX_TYPE my_spinlock = portMUX_INITIALIZER_UNLOCKED;
uint32_t g_back_color = 0xFFFF; /* 背景色 */
/* 管理LTDC重要参数 */
_ltdc_dev ltdcdev;
/**
* @NOTE 利用LCD RGB线的最高位(R7,G7,B7)来识别面板ID * PG6 = R7(M0); PI2 = G7(M1); PI7 = B7(M2);
* M2:M1:M0
* 0 :0 :0 4.3 寸480*272 RGB屏,ID = 0X4342
* 1 :0 :0 4.3 寸800*480 RGB屏,ID = 0X4384
* @retval 0, 非法;
* 其他, LCD ID
*/
uint16_t ltdc_panelid_read(void)
{
uint8_t idx = 0;
gpio_config_t gpio_init_struct = {0};
gpio_init_struct.intr_type = GPIO_INTR_DISABLE; /* 失能引脚中断 */
gpio_init_struct.mode = GPIO_MODE_INPUT; /* 输入输出模式 */
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; /* 使能上拉 */
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 失能下拉 */
gpio_init_struct.pin_bit_mask = 1ull << GPIO_LCD_B7;
gpio_config(&gpio_init_struct); /* 配置GPIO */
gpio_init_struct.pin_bit_mask = 1ull << GPIO_LCD_R7 || 1ull << GPIO_LCD_G7;
gpio_config(&gpio_init_struct); /* 配置GPIO */
idx = (uint8_t)gpio_get_level(GPIO_LCD_R7); /* 读取M0 */
idx |= (uint8_t)gpio_get_level(GPIO_LCD_G7) << 1; /* 读取M1 */
idx |= (uint8_t)gpio_get_level(GPIO_LCD_B7) << 2; /* 读取M2 */
switch (idx)
{
case 0 :
return 0X4342; /* 4.3寸屏, 480*272分辨率 */
case 4 :
return 0X4384; /* 4.3寸屏, 800*480分辨率 */
default :
return 0;
}
}
/**
* @brief 初始化ltdc
* @param 无
* @retval 无
*/
void ltdc_init(void)
{
panel_handle = NULL;
ltdcdev.id = ltdc_panelid_read(); /* 读取LCD面板ID */
if (ltdcdev.id == 0X4342) /* 4.3寸屏, 480*272 RGB屏 */
{
ltdcdev.pwidth = 480; /* 面板宽度,单位:像素 */
ltdcdev.pheight = 272; /* 面板高度,单位:像素 */
ltdcdev.hsw = 1; /* 水平同步宽度 */
ltdcdev.vsw = 1; /* 垂直同步宽度 */
ltdcdev.hbp = 40; /* 水平后廊 */
ltdcdev.vbp = 8; /* 垂直后廊 */
ltdcdev.hfp = 5; /* 水平前廊 */
ltdcdev.vfp = 8; /* 垂直前廊 */
ltdcdev.pclk_hz = 9 * 1000 * 1000; /* 设置像素时钟 9Mhz */
}
else if (ltdcdev.id == 0X4384)
{
ltdcdev.pwidth = 800; /* 面板宽度,单位:像素 */
ltdcdev.pheight = 480; /* 面板高度,单位:像素 */
ltdcdev.hbp = 88; /* 水平后廊 */
ltdcdev.hfp = 40; /* 水平前廊 */
ltdcdev.hsw = 48; /* 水平同步宽度 */
ltdcdev.vbp = 32; /* 垂直后廊 */
ltdcdev.vfp = 13; /* 垂直前廊 */
ltdcdev.vsw = 3; /* 垂直同步宽度 */
ltdcdev.pclk_hz = 18 * 1000 * 1000; /* 设置像素时钟 18Mhz */
}
/* 配置RGB参数 */
esp_lcd_rgb_panel_config_t panel_config = { /* RGBLCD配置结构体 */
.data_width = 16, /* 数据宽度为16位 */
.psram_trans_align = 64, /* 在PSRAM中分配的缓冲区的对齐 */
.clk_src = LCD_CLK_SRC_PLL160M, /* RGBLCD外设时钟源 */
.disp_gpio_num = GPIO_NUM_NC, /* 用于显示控制信号,不使用设为-1 */
.pclk_gpio_num = GPIO_LCD_PCLK, /* PCLK信号引脚 */
.hsync_gpio_num = GPIO_NUM_NC, /* HSYNC信号引脚,DE模式可不使用 */
.vsync_gpio_num = GPIO_NUM_NC, /* VSYNC信号引脚,DE模式可不使用 */
.de_gpio_num = GPIO_LCD_DE, /* DE信号引脚 */
.data_gpio_nums = { /* 数据线引脚 */
GPIO_LCD_B3, GPIO_LCD_B4, GPIO_LCD_B5, GPIO_LCD_B6, GPIO_LCD_B7,
GPIO_LCD_G2, GPIO_LCD_G3, GPIO_LCD_G4, GPIO_LCD_G5, GPIO_LCD_G6, GPIO_LCD_G7,
GPIO_LCD_R3, GPIO_LCD_R4, GPIO_LCD_R5, GPIO_LCD_R6, GPIO_LCD_R7,
},
.timings = { /* RGBLCD时序参数 */
.pclk_hz = ltdcdev.pclk_hz, /* 像素时钟频率 */
.h_res = ltdcdev.pwidth, /* 水平分辨率,即一行中的像素数 */
.v_res = ltdcdev.pheight, /* 垂直分辨率,即帧中的行数 */
/* 水平后廊,hsync和行活动数据开始之间的PCLK数 */
.hsync_back_porch = ltdcdev.hbp,
/* 水平前廊,活动数据结束和下一个hsync之间的PCLK数 */
.hsync_front_porch = ltdcdev.hfp,
.hsync_pulse_width = ltdcdev.vsw, /* 垂直同步宽度,单位:行数 */
/* 垂直后廊,vsync和帧开始之间的无效行数 */
.vsync_back_porch = ltdcdev.vbp,
/* 垂直前廊,帧结束和下一个vsync之间的无效行数 */
.vsync_front_porch = ltdcdev.vfp,
.vsync_pulse_width = ltdcdev.hsw, /* 水平同步宽度,单位:PCLK周期 */
.flags.pclk_active_neg = true, /* RGB数据在下降沿计时 */
},
.flags.fb_in_psram = true, /* 在PSRAM中分配帧缓冲区 */
/* 解决写spiflash时,抖动问题 */
.bounce_buffer_size_px = (ltdcdev.id == 0X4384) ? 480 * 10 : 272 * 10,
};
/* 创建RGB对象 */
esp_lcd_new_rgb_panel(&panel_config, &panel_handle);
/* 复位RGB屏 */
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
/* 初始化RGB */
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
/* 设置横屏 */
ltdc_display_dir(1);
/* 清除屏幕为颜色 */
ltdc_clear(WHITE);
/* 打开背光 */
LCD_BL(1);
}
/**
* @brief 清除屏幕
* @param color:清除的颜色
* @retval 无
*/
void ltdc_clear(uint16_t color)
{
uint16_t *buffer = heap_caps_malloc(ltdcdev.width * sizeof(uint16_t), MALLOC_CAP_INTERNAL |
MALLOC_CAP_8BIT);
if (NULL == buffer)
{
ESP_LOGE(TAG, "Memory for bitmap is not enough");
}
else
{
for (uint16_t i = 0; i < ltdcdev.width; i++)
{
buffer = color;
}
for (uint16_t y = 0; y < ltdcdev.height; y++)
{/*使用taskENTER_CRITICAL()和taskEXIT_CRITICAL()保护画点过程,禁止任务调度*/
taskENTER_CRITICAL(&my_spinlock); /* 屏蔽中断 */
esp_lcd_panel_draw_bitmap(panel_handle,
0,
y,
ltdcdev.width,
y + 1,
buffer);
taskEXIT_CRITICAL(&my_spinlock); /* 重新使能中断 */
}
heap_caps_free(buffer);
}
}
33.3.4 CMakeLists.txt文件
打开本实验BSP下的CMakeLists.txt文件,其内容如下所示:
set(src_dirs
IIC
LED
XL9555
RGBLCD)
set(include_dirs
IIC
LED
XL9555
RGBLCD)
set(requires
driver
esp_lcd
esp_common
log)
idf_component_register(SRC_DIRS ${src_dirs}
INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
上述的红色标注部分驱动需要由开发者自行添加,以确保RGB屏幕驱动能够顺利集成到构建系统中。这一步骤是必不可少的,它确保了RGB驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。
33.3.5 实验应用代码
打开main/main.c文件,该文件定义了工程入口函数,名为app_main。该函数代码如下。
i2c_obj_t i2c0_master;
/**
* @brief 程序入口
* @param 无
* @retval 无
*/
void app_main(void)
{
uint8_t x = 0;
uint8_t lcd_id[12];
esp_err_t ret;
ret = nvs_flash_init(); /* 初始化NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
led_init(); /* 初始化LED */
i2c0_master = iic_init(I2C_NUM_0); /* 初始化IIC0 */
xl9555_init(i2c0_master); /* IO扩展芯片初始化 */
ltdc_init(); /* RGB屏初始化 */
sprintf((char *)lcd_id, "LCD ID:%04X", ltdcdev.id);/*将ID打印到lcd_id数组*/
while (1)
{
switch (x)
{
case 0:
ltdc_clear(WHITE);
break;
case 1:
ltdc_clear(BLACK);
break;
case 2:
ltdc_clear(BLUE);
break;
case 3:
ltdc_clear(RED);
break;
case 4:
ltdc_clear(MAGENTA);
break;
case 5:
ltdc_clear(GREEN);
break;
case 6:
ltdc_clear(CYAN);
break;
case 7:
ltdc_clear(YELLOW);
break;
}
ltdc_show_string(10, 40, 240, 32, 32, "ESP32S3", RED);
ltdc_show_string(10, 80, 240, 24, 24, "LTDC TEST", RED);
ltdc_show_string(10, 110, 240, 16, 16, "ATOM@ALIENTEK", RED);
ltdc_show_string(10, 130, 240, 16, 16, (char *)lcd_id, RED);/* LCD ID */
x++;
if (x == 8)
{
x = 0;
}
vTaskDelay(1000);
}
}
main函数功能主要是显示一些固定的字符,字体大小包括32*16、24*12、16*8和12*6四种,同时显示RGB-LCD驱动IC的型号,然后不停的切换背景颜色,每1s切换一次。其中我们用到一个sprintf的函数,该函数用法同printf,只是sprintf把打印内容输出到指定的内存区间上,最终在死循环中通过ltdc_show_strinig函数进行屏幕显示,sprintf的详细用法,请百度学习。
33.4 下载验证
在初始化完RGB-LCD屏幕后,便在RGB-LCD上显示一些本实验的相关信息,随后便每间隔1000毫秒就更换一次RGB-LCD屏幕显示的背景色。