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 库