侧边栏壁纸
博主头像
李振洋的博客博主等级

歌颂动荡的青春

  • 累计撰写 38 篇文章
  • 累计创建 6 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

RP2040点亮LCD屏幕并移植LVGL

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

  1. 获取lvgl源码,使用V8.4版本。Release Release v8.4.0 · lvgl/lvgl (github.com)

  1. 将lvgl源码解压到工程目录下的lib文件夹下

  2. 将lvgl源码文件夹下的lv_conf_template.h 复制拷贝到lvgl源码文件夹的同级目录下,也就是工程目录下的lib文件夹下,并重命名为lv_conf.h

  3. 在工程目录下的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})
  1. src目录下的CMakeLists.txt 中添加

target_link_libraries(${ProjectName} 
    lvgl
    )
  1. 配置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
  1. 在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);

}
  1. 开启FreeRTOS开启滴答钩子函数,为lvgl提供心跳,lv_tick_inc() 来更新 lvgl 的时间计数器

#define configUSE_TICK_HOOK                     1

void vApplicationTickHook()
{
	lv_tick_inc(1);
}
  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){};
}

0

评论区