STM32CubeMX | 42 - 使用DMA2D加速显存数据传输

Posted Mculover666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32CubeMX | 42 - 使用DMA2D加速显存数据传输相关的知识,希望对你有一定的参考价值。

一、使用CPU搬运数据到显存

在上一篇文章中讲述了如何配置 LTDC 驱动 RGB 屏幕:STM32CubeMX | 41-使用LTDC驱动TFT-LCD屏幕(RGB屏)

本节中我们接着上一节的实验,讲述如何使用 DMA2D 实现打点、画线、填充等函数,只需要单层全屏即可,修改LTDC层配置如下:

1. 编写lcd驱动头文件

创建lcd_rgb_ltdc_drv.h文件,存放关于操作LCD屏幕的一些宏定义配置和函数定义:

#ifndef _LCD_RGB_LTDC_DRV_H_
#define _LCD_RGB_LTDC_DRV_H_

#include "ltdc.h"

/**
 * @brief   Windows size on lcd.
*/
#define LCD_WIDTH       1024
#define LCD_HEIGHT      600

/**
 * @brief   Backlight control pin of lcd.
*/
#define LCD_BL_GPIO_PORT    GPIOB
#define LCD_BL_GPIO_PIN     GPIO_PIN_5

/**
 * @brief   start address of lcd framebuffer.
*/
#define LCD_FRAME_BUFFER    0xc0000000

/**
 * @brief   color
 * @note    rgb565   
*/
#define BLACK   0x0000
#define BLUE    0x001F
#define GREEN   0x07E0
#define GBLUE   0X07FF
#define GRAY    0X8430
#define BROWN   0XBC40
#define RED     0xF800
#define PINK    0XF81F
#define BRRED   0XFC07
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

/**
 * @brief       Control the lcd backlight.
 * @param[in]   brightness  the value of lcd backlight.
 * @return      None
*/
void lcd_backlight_control(uint8_t bightness);

/**
 * @brief       LCD initialization.
 * @param       None
 * @return      None
*/
void lcd_init(void);

/**
 * @brief       Clear lcd.
 * @param[in]   color   rgb565.
 * @return      None
*/
void lcd_clear(uint16_t color);

#endif /* _LCD_RGB_LTDC_DRV_H_ */

2. lcd驱动实现

创建lcd_rgb_ltdc_drv.c文件,存放关于操作LCD屏幕的函数实现。

首先是背光控制实现,应该使用pwm实现背光调节,本文中为了方便直接使用GPIO控制:

void lcd_backlight_control(uint8_t bightness)
{
    // todo: use pwm to control backlight

    if (bightness) {    
        // turn on the backlight
        HAL_GPIO_WritePin(LCD_BL_GPIO_PORT, LCD_BL_GPIO_PIN, GPIO_PIN_SET);
    } else {            
        // turn off the backlight
        HAL_GPIO_WritePin(LCD_BL_GPIO_PORT, LCD_BL_GPIO_PIN, GPIO_PIN_RESET);
    }
}

接着实现lcd清屏函数,使用CPU(循环)搬运数据到显存中

void lcd_clear(uint16_t color)
{
    uint16_t *ptr = (uint16_t*)LCD_FRAME_BUFFER;
    uint32_t i = 0;

    while (i++ < LCD_WIDTH*LCD_HEIGHT) {
        *(ptr+i) = color;
    }
}

最后实现lcd初始化函数,先刷显存,然后开背光,防止设备上电时屏幕闪烁:

void lcd_init()
{
    lcd_clear(BLACK);
    lcd_backlight_control(255);
}

3. lcd单次清屏时间测试

在main.c中包含驱动头文件:

#include "lcd_rgb_ltdc_drv.h"

在main函数中的sdram初始化函数之后,添加lcd初始化函数,并使用HAL库自带的systick时间戳测量一次清屏的时间:

/* USER CODE BEGIN 2 */
printf("sdram test by mculover666\\r\\n");
SDRAM_Init();
printf("sdram init success\\r\\n");

lcd_init();

start_time = HAL_GetTick();
lcd_clear(PINK);
end_time = HAL_GetTick();

printf("lcd clear spend time:%ld ms\\r\\n", end_time - start_time);
/* USER CODE END 2 */

编译、运行,在串口助手可以看到使用CPU搬运数据到显存中,在-Og优化等级下单次清屏需要 155 ms左右,在-O0优化等级下单次清屏需要321ms左右:

二、使用DMA2D加速显存数据搬运

1. DMA2D

在STM32中,DMA2D外设专门用来给LCD显示加速,有LTDC外设的型号中,通常也会配套有DMA2D。

DMA2D外设主要提供了两个功能:

  • DMA数据搬运:支持从寄存器到存储器、存储器到存储器两种模式,快速高效,并且不占用cpu资源
  • 2D图形加速:支持快速格式转换和混合;

本文中主要使用到DMA2D外设的数据搬运功能,使用起来也是比较简单。

2. 开启DMA2D


重新生成工程,cubemx会自动生成并调用dma2d初始化函数,完成dma2d外设时钟使能以及dma2d传输模式配置。

3. 使用DMA2D实现lcd清屏函数

在lcd驱动头文件中添加一个宏定义,用于控制是否使能DMA2D:

/**
 * @brief   whether use dma2d to transfer data to lcd framebuffer.
*/
#define USE_DMA2D_EN        1

接着对DMA2D传输操作进行封装,编写一个DMA2D传输函数:

static void dma2d_transfer_data_r2m(uint32_t *addr, uint32_t xSize, uint32_t ySize, uint32_t offsetLine, uint16_t color)
{
    DMA2D->CR = DMA2D_R2M;   // dma2d mode: register to memory.
    DMA2D->OPFCCR = DMA2D_OUTPUT_RGB565;

    DMA2D->OCOLR = color;
    DMA2D->OMAR = (uint32_t)addr;
    DMA2D->OOR = offsetLine;
    DMA2D->NLR = (uint32_t)(xSize << 16) | (uint16_t)ySize;
    
    DMA2D->CR |= DMA2D_CR_START;
    while (DMA2D->CR & DMA2D_CR_START);
}

利用此DMA2D传输函数,重新添加清屏函数的实现:

void lcd_clear(uint16_t color)
{
#if USE_DMA2D_EN
    dma2d_transfer_data_r2m((uint32_t *)LCD_FRAME_BUFFER, LCD_WIDTH, LCD_HEIGHT, 0, color);
#else
    uint16_t *ptr = (uint16_t*)LCD_FRAME_BUFFER;
    uint32_t i = 0;

    while (i++ < LCD_WIDTH*LCD_HEIGHT) {
        *(ptr+i) = color;
    }
#endif /* USE_DMA2D_EN */
}

上层测试代码不变,确保控制dma2d的宏使能:

#define USE_DMA2D_EN        1

编译、下载,在串口助手中查看清屏一次所需时间:

可以看到,刷屏一次只需31ms即可,并且在使用dma2d传输数据的情况下,数据传输时间和编译优化等级无关

三、LCD基本功能实现

LCD基本功能包括打点、读点、画线、绘图等函数。

1. 打点函数

打点函数的核心是计算当前用户给出的坐标位置在显存中的位置,两种实现如下:

void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color)
{
    uint32_t pos;
    uint16_t *ptr;

    // check position.
    if (x > LCD_WIDTH || y > LCD_HEIGHT) {
        return;
    }

    // calculate the position offset in framebuffer.
    pos = x + y*LCD_WIDTH;
    ptr = (uint16_t*)LCD_FRAME_BUFFER;

    // modify the framebuffer.
#if USE_DMA2D_EN
    dma2d_transfer_data_r2m((uint32_t *)(ptr+pos), 1, 1, 0, color);
#else
    *(ptr+pos) = color;
#endif /* USE_DMA2D_EN */
}

2. 读点函数实现

读点函数实现的核心也是计算出用户给出的坐标位置在显存中的位置:

uint16_t lcd_read_point(uint16_t x, uint16_t y)
{
    uint32_t pos;
    uint16_t *ptr, data;

    // check position.
    if (x > LCD_WIDTH || y > LCD_HEIGHT) {
        return 0;
    }

    // calculate the position offset in framebuffer.
    pos = x + y*LCD_WIDTH;
    ptr = (uint16_t*)LCD_FRAME_BUFFER;

    // read the framebuffer.
    data = *(ptr+pos);

    return data;
}

3. 画线、画矩形、画圆

这三个功能都是基于打点函数,使用 Bresenham 算法,代码篇幅过多,如有兴趣可直接查看本篇源码。

4. 测试

在main函数中添加测试代码:

lcd_draw_line(0, 0, 1024, 600, GREEN);
lcd_draw_line(0, 300, 1024, 300, RED);
lcd_draw_line(512, 0, 512, 600, BLUE);
lcd_draw_line(1024, 0, 0, 600, YELLOW);

lcd_draw_rect(256, 150, 1024-256, 600-150, PINK);

编译,下载,结果如下图:

以上是关于STM32CubeMX | 42 - 使用DMA2D加速显存数据传输的主要内容,如果未能解决你的问题,请参考以下文章

STM32CubeMX学习笔记(42)——ETH接口+LwIP协议栈使用(静态IP)

STM32CubeMX stm32F4 CAN 使用步骤--全文复制粘贴

STM32F429DISC开发板SDRAM(IS42S16400J)实验—基于STM32cubeMX HAL库

在 Atollic TrueStudio、STM32CubeMX 中导入 C 库

ThreadX GUIX上手之STM32F429 DMA2D加速

STM32 DMA1和DMA2通道一览表