历史上的今天
今天是:2025年08月13日(星期三)
2021年08月13日 | STM32CubeMX | 42 - 使用DMA2D加速显存数据传输
2021-08-13 来源:eefocus
一、使用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"
1.
在main函数中的sdram初始化函数之后,添加lcd初始化函数,并使用HAL库自带的systick时间戳测量一次清屏的时间:
/* USER CODE BEGIN 2 */
printf("sdram test by mculover666rn");
SDRAM_Init();
printf("sdram init successrn");
lcd_init();
start_time = HAL_GetTick();
lcd_clear(PINK);
end_time = HAL_GetTick();
printf("lcd clear spend time:%ld msrn", 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);
编译,下载,结果如下图:
上一篇:mac下搭建stm32开发环境
史海拾趣
|
2. 印刷电子 能快速印刷出多个导体/绝缘体或半导体层以形成电路的技术,可望催生比目前采用传统制程生产之IC成本更低芯片。通常印刷半导体意味着使用性能与硅大不相同 的有机材料,甚至所生产之组件尺寸也能超越硅材料的 ...… 查看全部问答> |
|
我想在cygwin下编译基于目标cpu i960的交叉编译工具链 在编译bootrap gcc的时候遇到一个libgcc1.a的问题 mv libgcc1.a libgcc1.cross || (echo You must find a way to make libgcc1.a; false) mv: cannot stat `libgcc1.a\': No such file or d ...… 查看全部问答> |
|
我这几天用笔记本串口连接开发板(之前用台式机正常),但是在超级终端下输入显示乱码,而在dnw下输入显示正常。开发板像超级终端输出显示正常。 怎么才能在超级终端下正常输入呢,请指教,谢谢!… 查看全部问答> |
|
Pocket PC怎样通过activesync连接到局域网? 我已经安装好Activesync4.2,Pocket PC通过USB线连接到我的电脑上,连接成功.我的电脑是连在局域网上的,怎样将Pocket PC连到局域网上呢?… 查看全部问答> |
|
本帖最后由 jameswangsynnex 于 2015-3-3 19:54 编辑 用头戴式耳机,尤其是小型耳机听音乐,总感到音乐味不够足,在低频段的效果更差。因此用本机增强耳机的低频特性,并采用立体声反相合成的办法,加上内藏简易矩阵环绕声电路,能获得强劲的低音 ...… 查看全部问答> |
|
求一个基于MSP430F149或F449芯片的LCD键盘显示系统的完整C程序 求一个基于MSP430F149或F449芯片的LCD键盘显示系统的完整C程序 说明:本人刚开始学习MSP430,想用MSP430做一个键盘LCD显示控制系统 求一个完整的C程序,来学习和模仿下。 控制系统要求:1,键盘是3*4列的行列式扫描键盘。LCD最好用truely(信利公 ...… 查看全部问答> |




