RP2040点亮LCD屏幕并移植LVGL
基于FreeRTOS系统,用RP2040驱动LCD屏幕,通过DMA实现高刷,并移植LVGL
准备材料
RP2040
LCD屏幕 通信方式:SPI 驱动IC型号:ST7798 分辨率:240*240
Raspberry Pi Pico SDK raspberrypi/pico-sdk (github.com)
FreeRTOS Release V11.1.0 · FreeRTOS/FreeRTOS-Kernel (github.com)
lvgl lvgl/lvgl: Embedded graphics library to create beautiful UIs for any MCU, MPU and display type. (github.com)
编写LCD驱动
lcd_st7798.h
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "hardware/dma.h"
void LCD_Init(void);
void Fill(uint x1,uint y1,uint x2,uint y2,uint16_t color);
void Draw(uint x,uint y,uint16_t* graph,uint width,uint height);
/*LCD控制器的寄存器定义*/
#define SLPOUT 0x11 /*Sleep out*/
#define INVON 0x21 /*Display inversion on*/
#define DISPON 0x29 /*Display on*/
#define CASET 0x2A /*Set X address*/
#define RASET 0x2B /*Set Y address*/
#define RAMWR 0x2C /*Write data*/
#define MADCTL 0x36 /*Memory data access control*/
#define COLMOD 0x3A /*Interface pixel format*/
#define PORCTRL 0xB2 /*Porch control*/
#define GCTRL 0xB7 /*Gate control*/
#define VCOMS 0xBB /*VCOM setting*/
#define LCMCTRL 0xC0 /*LCM control*/
#define VDVVRHEN 0xC2 /*VDV and VRH Command Enable*/
#define VRHS 0xC3 /*VRH Set*/
#define VDVSET 0xC4 /*VDV Setting*/
#define FRCTR2 0xC6 /*FR Control2*/
#define PWCTRL1 0xD0 /*Power Control 1*/
#define PVGAMCTRL 0xE0 /*Positive Voltage Gamma Control*/
#define NVGAMCTRL 0xE1 /*Negative Voltage Gamma Control*/
lcd_st7798.c
#include "lcd_st7798.h"
/*屏幕尺寸*/
#define _LCD_W 240
#define _LCD_H 240
/*
SPI控制器及引脚
有的硬件设计会直接将LCD的CS引脚拉低到GND
这样的设计,设置 _LCD_CSN -1
*/
#define _LCD_SPI spi0
#define _LCD_RES 17
#define _LCD_SCK 18
#define _LCD_DAT 19
#define _LCD_CSN -1
#define _LCD_DCN 16
/*SPI时钟速度*/
#define _LCD_FRQ 1000 * 1000 * 60UL
/*SPI使用的DMA*/
static uint dma;
/*LCD 设置参数*/
static const uint8_t DATA_PORCTRL[] = {0x0C,0x0C,0x00,0x33,0x33};
static const uint8_t DATA_PWCTRL1[] = {0xA4,0xA1};
static const uint8_t DATA_PVGAMCTRL[] = {0xD0,0x04,0x0D,0x11,0x13,0x2B,0x3F,0x54,0x4C,0x18,0x0D,0x0B,0x1F,0x23};
static const uint8_t DATA_NVGAMCTRL[] = {0xD0,0x04,0x0C,0x11,0x13,0x2C,0x3F,0x44,0x51,0x2F,0x1F,0x1F,0x20,0x23};
/*通过CPU传输1字节*/
static void singleWriteCPU8(uint8_t addr,uint8_t data,int cmdOnly);
/*通过CPU传输多字节(8bit)*/
static void burstWriteCPU8(uint8_t addr,uint8_t* data,uint8_t length);
/*通过CPU传输多字节(16bit)*/
static void burstWriteCPU16(uint8_t addr,uint16_t* data,uint16_t length);
/*通过DMA传输多字节*/
static void burstWriteDMA16(uint8_t addr,uint16_t* data,uint16_t length,int inc);
/*设置VRAM写入位置*/
static void setMemoryAddress(uint16_t xs,uint16_t ys,uint16_t xe,uint16_t ye);
/*通过DMA传输16bit*/
static void xferDMA16(const uint16_t* addr,uint32_t length);
/*LCD初始化*/
void LCD_Init(void){
/*
gpio 初始化
*/
gpio_init(_LCD_RES);
#ifdef _LCD_CSN
#if _LCD_CSN != -1
gpio_init(_LCD_CSN);
#endif
#endif
gpio_init(_LCD_DCN);
gpio_set_dir(_LCD_RES,true);
#ifdef _LCD_CSN
#if _LCD_CSN != -1
gpio_set_dir(_LCD_CSN,true);
#endif
#endif
gpio_set_dir(_LCD_DCN,true);
gpio_set_function(_LCD_SCK,GPIO_FUNC_SPI);
gpio_set_function(_LCD_DAT,GPIO_FUNC_SPI);
spi_init(_LCD_SPI,_LCD_FRQ);
/*
LCD控制器初始化
硬件复位后设置参数
*/
gpio_put(_LCD_RES,false);
sleep_ms(100);
gpio_put(_LCD_RES,true);
singleWriteCPU8(MADCTL,0x00,0);
singleWriteCPU8(COLMOD,0x05,0);
burstWriteCPU8(PORCTRL,DATA_PORCTRL,sizeof(DATA_PORCTRL));
singleWriteCPU8(GCTRL,0x35,0);
singleWriteCPU8(VCOMS,0x19,0);
singleWriteCPU8(LCMCTRL,0x2C,0);
singleWriteCPU8(VDVVRHEN,0x01,0);
singleWriteCPU8(VRHS,0x12,0);
singleWriteCPU8(VDVSET,0x20,0);
singleWriteCPU8(FRCTR2,0x0F,0);
burstWriteCPU8(PWCTRL1,DATA_PWCTRL1,sizeof(DATA_PWCTRL1));
burstWriteCPU8(PVGAMCTRL,DATA_PVGAMCTRL,sizeof(DATA_PVGAMCTRL));
burstWriteCPU8(NVGAMCTRL,DATA_NVGAMCTRL,sizeof(DATA_NVGAMCTRL));
singleWriteCPU8(INVON,0,1);
singleWriteCPU8(SLPOUT,0,1);
sleep_ms(120);
singleWriteCPU8(DISPON,0,1);
setMemoryAddress(0,0,_LCD_W - 1,_LCD_H - 1);
dma = dma_claim_unused_channel(true);
return;
}
/*
矩形填充
x1,y1 : 要绘制的矩形左上角的顶点坐标
x2,y2 : 要绘制的矩形右下+1的顶点坐标
color : 要绘制的矩形颜色
*/
void Fill(uint x1,uint y1,uint x2,uint y2,uint16_t color){
int width = x2 - x1;
int height = y2 - y1;
setMemoryAddress(x1,y1,x2 - 1,y2 -1);
burstWriteDMA16(RAMWR,&color,width * height,0);
return;
}
/*
绘制图形
x,y : 绘制图形的左上顶点坐标
graph : 要绘制的图形的头指针
width : 图形宽度
height : 图形高度
*/
void Draw(uint x,uint y,uint16_t* graph,uint width,uint height){
setMemoryAddress(x,y,(x+width-1),(y+height-1));
burstWriteDMA16(RAMWR,graph,width*height,1);
return;
}
/*
VRAM写入位置设置
xs,ys : 起点坐标
xe,ye : 终点坐标
*/
static void setMemoryAddress(uint16_t xs,uint16_t ys,uint16_t xe,uint16_t ye){
/*
CASET(2Ah) Column Address Set
RASET(2Bh) Row Address Set
XS[7:0],XE[7:0],YS[7:0],YE[7:0]はそれぞれを含む
例:
XS = 0,XE = 10の場合幅は11ピクセルとなる
YS,YEについても同様
*/
uint16_t DATA_CASET[] = {
xs,
xe,
};
uint16_t DATA_RASET[] = {
ys,
ye
};
burstWriteDMA16(0x2A,DATA_CASET,2,1);
burstWriteDMA16(0x2B,DATA_RASET,2,1);
}
/*
CPU传输1字节
addr : 目标寄存器地址
data : 要发送的数据
cmdOnly : 要发送的数据是不是LCD控制指令(例如软件复位)
*/
static void singleWriteCPU8(uint8_t addr,uint8_t data,int cmdOnly){
spi_set_format(_LCD_SPI,8,true,true,SPI_MSB_FIRST);
//gpio_put(_LCD_CSN,false);
gpio_put(_LCD_DCN,false);
spi_write_blocking(_LCD_SPI,&addr,1);
if(cmdOnly){
return;;
}
gpio_put(_LCD_DCN,true);
spi_write_blocking(_LCD_SPI,&data,1);
}
/*
多字节CPU传输(8bit)
addr : 目标寄存器地址
data : 要发送的数据的头指针
length : 数据长度
*/
static void burstWriteCPU8(uint8_t addr,uint8_t* data,uint8_t length){
spi_set_format(_LCD_SPI,8,true,true,SPI_MSB_FIRST);
#ifdef _LCD_CSN
#if _LCD_CSN != -1
gpio_put(_LCD_CSN,false);
#endif
#endif
gpio_put(_LCD_DCN,false);
spi_write_blocking(_LCD_SPI,&addr,1);
gpio_put(_LCD_DCN,true);
spi_write_blocking(_LCD_SPI,data,length);
#ifdef _LCD_CSN
#if _LCD_CSN != -1
gpio_put(_LCD_CSN,true);
#endif
#endif
return;
}
/*
多字节CPU传输(16位)
addr : 目标寄存器地址
data : 要发送的数据的头指针
length : 数据长度
*/
static void burstWriteCPU16(uint8_t addr,uint16_t* data,uint16_t length){
spi_set_format(_LCD_SPI,8,true,true,SPI_MSB_FIRST);
#ifdef _LCD_CSN
#if _LCD_CSN != -1
gpio_put(_LCD_CSN,false);
#endif
#endif
gpio_put(_LCD_DCN,false);
spi_write_blocking(_LCD_SPI,&addr,1);
gpio_put(_LCD_DCN,true);
spi_set_format(_LCD_SPI,16,true,true,SPI_MSB_FIRST);
spi_write16_blocking(_LCD_SPI,data,length);
#ifdef _LCD_CSN
#if _LCD_CSN != -1
gpio_put(_LCD_CSN,true);
#endif
#endif
return;
}
/*
多字节DMA传输(16位)
addr : 目标寄存器地址
data : 要发送的数据的头指针
length : 数据长度
inc : 是否将发送数据的地址递增
*/
static void burstWriteDMA16(uint8_t addr,uint16_t* data,uint16_t length,int inc){
dma_channel_config c = dma_channel_get_default_config(dma);
singleWriteCPU8(addr,0x00,1);
spi_set_format(_LCD_SPI,16,true,true,SPI_MSB_FIRST);
gpio_put(_LCD_DCN,true);
channel_config_set_transfer_data_size(&c, DMA_SIZE_16);
channel_config_set_dreq(&c, spi_get_dreq(_LCD_SPI, true));
channel_config_set_read_increment(&c, !(inc == 0));
dma_channel_configure(dma, &c,
&spi_get_hw(_LCD_SPI)->dr,
data,
length,
true);
//等待DMA完成传输
dma_channel_wait_for_finish_blocking(dma);
//等待SPI传输结束,即使DMA传输结束,SPI传输也不一定结束,
while(spi_is_busy(_LCD_SPI)){};
return;
}
在src
目录下的CMakeLists.txt
中添加
add_executable(${ProjectName}
lcd_st7789.c
)
移植lvgl
获取lvgl源码,使用V8.4版本。Release Release v8.4.0 · lvgl/lvgl (github.com)
将lvgl源码解压到工程目录下的
lib
文件夹下将lvgl源码文件夹下的
lv_conf_template.h
复制拷贝到lvgl源码文件夹的同级目录下,也就是工程目录下的lib
文件夹下,并重命名为lv_conf.h
在工程目录下的
CMakeLists.txt
中添加
SET(LVGL_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib/lvgl)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib)
include_directories(${LVGL_PATH})
add_subdirectory(${LVGL_PATH})
在
src
目录下的CMakeLists.txt
中添加
target_link_libraries(${ProjectName}
lvgl
)
配置lvgl_conf.h
/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 20 /*[ms]*/
/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 20 /*[ms]*/
/*1: Show CPU usage and FPS count*/
#define LV_USE_PERF_MONITOR 1
#if LV_USE_PERF_MONITOR
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif
/*1: Show the used memory and the memory fragmentation
* Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 1
#if LV_USE_MEM_MONITOR
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
#endif
在main.c完善lvgl所需要的硬件驱动接口
#define LCD_W 240
static lv_disp_drv_t disp_drv;
static lv_disp_draw_buf_t lv_draw_buf;
static lv_color_t lv_color_buf1[LCD_W * 20];
static lv_color_t lv_color_buf2[LCD_W * 20];
static lv_indev_drv_t indev_drv;
static lv_indev_t * indev_encoder;
lv_group_t *indev_group;
//lvgl刷新回调函数
static void my_disp_flush( lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p )
{
uint32_t w = (area->x2 - area->x1+1 );
uint32_t h = (area->y2 - area->y1+1 );
Draw(area->x1, area->y1,(uint16_t *)&color_p->full, w, h);
lv_disp_flush_ready(disp);
}
//lvgl初始化
void user_lvgl_init()
{
lv_init();
/*
初始化显示缓冲区 使用双buffer缓存区
一个缓冲区用于显示,另一个用于绘制
*/
lv_disp_draw_buf_init( &lv_draw_buf, lv_color_buf1, lv_color_buf2, LCD_W * 20 );
//初始化LCD显示驱动
lv_disp_drv_init(&disp_drv);
//注册LCD刷新回调函数,将缓存区中的像素数据刷新到实际的显示屏上。
disp_drv.flush_cb = my_disp_flush;
//注册缓存区
disp_drv.draw_buf = &lv_draw_buf;
disp_drv.hor_res = LCD_W;
disp_drv.ver_res = LCD_W;
disp_drv.direct_mode = 0;
//注册LCD显示驱动
lv_disp_drv_register(&disp_drv);
/*
注册输入设备
*/
// lv_indev_drv_init(&indev_drv);
// indev_drv.type = LV_INDEV_TYPE_ENCODER;
// indev_drv.read_cb = encoder_read;
// indev_encoder = lv_indev_drv_register(&indev_drv);
// indev_group = lv_group_create();
// lv_group_set_default(indev_group);
// lv_indev_set_group(indev_encoder, indev_group);
}
开启FreeRTOS开启滴答钩子函数,为lvgl提供心跳,
lv_tick_inc()
来更新 lvgl 的时间计数器
#define configUSE_TICK_HOOK 1
void vApplicationTickHook()
{
lv_tick_inc(1);
}
测试lvgl
static void anim_x_cb(void * var, int32_t v)
{
lv_obj_set_x(var, v);
}
static void anim_size_cb(void * var, int32_t v)
{
lv_obj_set_size(var, v, v);
}
void user_lv_example(void)
{
lv_obj_t * label2 = lv_label_create(lv_scr_act());
lv_label_set_long_mode(label2, LV_LABEL_LONG_SCROLL_CIRCULAR); /*Circular scroll*/
lv_obj_set_width(label2, 100);
lv_label_set_text(label2, "hello hello hello");
lv_obj_align(label2, LV_ALIGN_CENTER, 0, -90);
lv_obj_t * obj = lv_obj_create(lv_scr_act());
//lv_obj_remove_style()
lv_obj_set_style_bg_color(obj, lv_palette_main(LV_PALETTE_RED), 0);
lv_obj_set_style_radius(obj, LV_RADIUS_CIRCLE, 0);
lv_obj_align(obj, LV_ALIGN_LEFT_MID, 10, -30);
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, obj);
lv_anim_set_values(&a, 10, 50);
lv_anim_set_time(&a, 1000);
lv_anim_set_playback_delay(&a, 100);
lv_anim_set_playback_time(&a, 300);
lv_anim_set_repeat_delay(&a, 500);
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out);
lv_anim_set_exec_cb(&a, anim_size_cb);
lv_anim_start(&a);
lv_anim_set_exec_cb(&a, anim_x_cb);
lv_anim_set_values(&a, 10, 180);
lv_anim_start(&a);
lv_obj_t * spinner = lv_spinner_create(lv_scr_act(), 1000, 60);
lv_obj_set_size(spinner, 60, 60);
lv_obj_align(spinner, LV_ALIGN_CENTER, 0, 40);
}
void lvgl_task()
{
while (1)
{
lv_task_handler();
vTaskDelay(pdMS_TO_TICKS(5));
}
}
int main()
{
stdio_init_all();
set_sys_clock_khz(240000, true);
user_lvgl_init();
user_lv_example();
TaskHandle_t led_task_Handle = NULL;
TaskHandle_t lvgl_task_Handle = NULL;
xTaskCreate(led_task, "LED_Task", 256, NULL, 1, &led_task_Handle);
xTaskCreate(lvgl_task, "lvgl_task", 10*1024, NULL, tskIDLE_PRIORITY + 8, &lvgl_task_Handle);
UBaseType_t task1_CoreAffinityMask = (1 << 0);
UBaseType_t task0_CoreAffinityMask = (1 << 1);
vTaskCoreAffinitySet(led_task_Handle, task1_CoreAffinityMask);
vTaskCoreAffinitySet(lvgl_task_Handle, task0_CoreAffinityMask);
vTaskStartScheduler();
while(1){};
}
评论区