STM32 的图形加速器 DMA2D

STM32 的图形加速器 DMA2D

1. 背景

​ 在实际使用 LTDC 控制器控制液晶屏时,配置好的显存地址写入要显示的像素数据,LTDC 就会把这些数据从显存中搬运到液晶面板进行显示。实际上要显示的数据量非常的大,我们常常以纯软件的方式填充显存(指定那个位置要显示什么颜色),这样非常影响绘图速度,因此我们希望能用 DMA 来操作,针对这个需求,STM32 专门定制了 DMA2D 外设,它可以用于快速绘制矩形、执行、分层数据混合、数据复制以及进行图像数据格式转换,可以把它理解为图形专用的 DMA。

2. DMA2D 工作模式

  • 寄存器到存储器(此模式通常同于清屏,将寄存器中保存的颜色值搬运到显存中的某个位置,或者整个显存)
  • 存储器到存储器(将内存中的数据搬到显存中)
  • 存储器到存储区并执行像素格式转换
  • 存储器到存储器并执行像素格式转换和混合

3. DMA2D 控制

​ 通过对 DMA2D 控制器寄存(DMA2D_CR)配置 DMA2D 控制器,用户可以执行下列操作:

  • 选择工作模式
  • 使能/禁止 DMA2D 中断
  • 启动/挂起/中止进行中的数据传输

4. DMA2D 前景层 FIFO 和背景层 FIFO

​ DMA2D 前景层(FG) FIFO 和 背景(BG)FIFO 获取要处理的输入数据。这些 FIFO 根据相应像素格式转换器(PFC)中定义的颜色格式获取像素。通过如下一组寄存器对他们进行编程:

  • DMA2D 前景层存储地址寄存器(DMA2D_FGMAR 此寄存器存的是前景层数据地址)

  • DMA2D 前景层偏移寄存器(DMA2D_FGOR )

  • DMA2D 背景层存储地址寄存器(DMA2D_BGMAR )

  • DMA2D 背景层偏移寄存器

    DMA2D 在寄存器到存储器模式下工作时,不激活任何 FIFO。

    DMA2D 在存储器到存储器模式下工作时(没有像素格式转换和混合操作时),仅激活FG FIFO,并将其作用为缓冲区。

    DMA2D 在存储器到存储器模式下工作时并支持像素格式转换时(无混合操作),不会激活
    BG FIFO。

5. DMA2D 工作原理

5.1 register to memory

​ 说了那么多理论,我们看一下实际上 DMA2D 的优势到底在那里。当我们想要在 LCD 显示屏的中间画上几个矩形时,或者清屏时,都是使用的纯软件循环填充framnbuffer 的空间比如:

    for(y = y0; y <= y1; y++)
    {
        for(x = x0; x <= x1; x++)
			framebuffer[y][x] = color; 
    }

这样的代码非常耗时,根据两个坐标(x0,y0),(x1,y1) 两个坐标,确定一个平面之后,两层循环填充 frambuffer。当我们清屏时,我们可能想到用 memset 函数去填充 frambuffer,当然这样是明智的选择,也是可行的。但是,如果不想清楚整块 frambuffer(操作 frambuffer 就相当于操作 LCD屏幕了)呢?memset 只适用于连续的地址空间。比如下面这种情况在这里插入图片描述
我只想要蓝色这块区域填充成红色,就不能简单的使用 memset(frambuffer,0xf800, sizeof(frambuffer)) (像素格式RGB565)了吧。因为此块区域地址不连续,因此只能使用上述循环大法,确定左上角坐标,与右下角坐标,循环填充 frambuffer 中的这块地址。

让我们看看 DMA2D 是怎么干的。
在这里插入图片描述
首先我们给出这块地址的首地址,也就是红色方块的地址。由于地址不连续,只想画出蓝色区域,因此告诉 DMA2D 填充完成一行后,需要跨过多少个格子才开始画下一行(注意:一个格子对应一个像素点)。比如,蓝色的一行有6个格子,因此需要跨过18、19、20、21 这四个格子之后才开始继续填充。跨越的像素个数有一个简单的计算方式,即一行像素的总个数减去要这一行要显示的像素个数,对应上图为 10 — 6 = 4。

因此画出上面的区域我们可以这样配置 DMA2D:

由于仅仅只是矩形的填充,我们将 DMA2D 配置在 寄存器到存储区模式下

DMA2D->CR   = (0x3UL << 16);

然后,告诉 DMA2D 要填充的frambuffer 地址和填充的矩形区域的宽高

DMA2D->OMAR = (uint32_t)(&frambuffer[x][y]);
DMA2D->NLR = (uint32_t)(width << 16) | (uint16_t)height;

紧接着告诉画一行要跳过多少像素,由输出偏移寄存器控制,用于确定下一行的起始地址

DMA2D->ORR = LCD_PIXEL_WIDTH - Width

然后告诉DMA2D 这块区域需要的颜色以及颜色格式

DMA2D->OCOLR = color
DMA2D->OPFCCR = 0x2;// RGB565

现在可以启动 DMA2D 传输了,只需要将 CR 寄存器的 bit0 置1即可,传输结束时,此位由硬件清0,因此,还可利用此位判断是否传输完成

DMA2D->CR |= 0x1;
while(DMA2D->CR & 0x1);//等待传输完成

将上面代码整理并封装成 api,,由于是最基本的填充函数,因此将会经常调用到,所以我们将设置为内联函数,减少函数调用的开销

static inline void dma2d_fill( void * fb, uint32_t width, uint32_t height, uint32_t lineoffect, uint32_t pixelformat,  uint32_t color) {

    /* DMA2D配置 */  
    DMA2D->CR      = 0x00030000UL;	// 配置为寄存器到储存器模式
	DMA2D->OCOLR   = color;        // 设置填充使用的颜色
    DMA2D->OMAR    = (uint32_t)fb; // 填充区域的起始内存地址
    DMA2D->OOR     = lineoffect;  // 行偏移,即跳过的像素(像素为单位)
    DMA2D->OPFCCR  = pixelformat;  // 设置颜色格式
    DMA2D->NLR     = (uint32_t)(width << 16) | (uint16_t)height;    // 设置填充区域的宽和高,单位是像素
    /* 启动传输 */
    DMA2D->CR   |= DMA2D_CR_START;   
    /* 等待DMA2D传输完成 */
    while (DMA2D->CR & DMA2D_CR_START) {} 
}

/* 填充颜色,此处我的 fb 是uint8类型的,由于采用 RGB565格式存储像素点,因此,一个像素占用两个字节,因此下面地址偏移乘了2
 * 也可强转位 uint16 然后就不用乘 2 了
 * w :待填充区域的宽度
 * h :待填充区域的高度
*/
void fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color){
    void* distfb = &framebuffer[2*(y*800 + x)];
    dma2d_fill(distfb, w, h, 800 - w, LTDC_PIXEL_FORMAT_RGB565, color);
}

int dma2d_file_rect_test(void)
{
	fill_rect(0, 0, 800, 480, 0x0000);//清屏
	fill_rect(300, 80, 20, 280, 0x001f);
	fill_rect(340, 100, 20, 200, 0x001f);
	fill_rect(380, 40, 20, 260, 0x001f);
	fill_rect(420, 60, 20, 240, 0x001f);
	fill_rect(260, 300, 240, 1, 0x0000);
    return 0;
}
MSH_CMD_EXPORT(dma2d_file_rect_test,dma2d_file_rect_test);//导出到 shell 命令

运行结果如下:

在这里插入图片描述

5.2 memory to memory

DMA2D 还可以工作在 memory to memory 模式下,此模式可用于将图片数据搬运到 fb 中指定的地址中去,因此我们做如下配置

DMA2D->CR = (0 << 16);

然后我们得告诉我的图片放到了内存中的那里,以及要将图片搬到我的那里,因此做如下配置

DMA2D->BGMAR = (uint32_t)srcaddr;
DMA2D->OMAR    = (uint32_t)dstaddr; // 目标地址
DMA2D->FGOR    = offlinesrc;     // 源数据偏移(像素)
DMA2D->OOR     = offlinedst;     // 目标地址偏移(像素)
DMA2D->FGPFCCR = pixelformat;  //颜色格式
DMA2D->NLR     = (uint32_t)(width << 16) | (uint16_t)height;// 图片的宽高

将其封装成 API

static void dma2d_memcopy(uint32_t pixelformat, void * psrc, void * pdst, int width, int height, int offLinesrc, int offLinedst)
{
    
    DMA2D->CR      = 0x00000000UL;
    DMA2D->FGMAR   = (uint32_t)psrc;
    DMA2D->OMAR    = (uint32_t)pdst;
    DMA2D->FGOR    = offLinesrc;
    DMA2D->OOR     = offLinedst;
    DMA2D->FGPFCCR = pixelformat;
    DMA2D->NLR     = (uint32_t)(width << 16) | (uint16_t)height;

    DMA2D->CR   |= DMA2D_CR_START;

    while (DMA2D->CR & DMA2D_CR_START) {}
}
/*
 * sky_animation_mask1 为原图片地址
 * _lcd.front_buf 为显存
 * 原图片地址不偏移
 * 目的地址偏移
*/
int dma2d_m2m(void)
{
    dma2d_memcopy(LTDC_PIXEL_FORMAT_RGB565,sky_animation_mask1,_lcd.front_buf,LOGO1_W,LOGO1_H,0,800-LOGO1_W);
}
MSH_CMD_EXPORT(dma2d_m2m,dma2d_m2m);

执行效果如下:

请添加图片描述

如果想移动图片的位置,可以操作 fb 地址,这里给的目的地址就是 fb 的首地址,也就是左上角。

5.3 memory to mrmory 图片混合

​ 通过将两张图片混合,并且设置图片透明度,可以达到图片渐变的效果,因此需要将 DMA2D 设置在待混合的模式下

DMA2D->CR = (0x2 << 16);

​ 然后设置背景、背景偏移、前景、前景偏移、目的地址

DMA2D->BGMAR = (uint32_t)bgaddr;//背景 src addr
DMA2D->BGOR  = offsetlineBg;// 背景偏移
DMA2D->FGMAR = (uint32_t)ggaddr;// 前景 src addr
DMA2D->FGOR  = offsetlinefg;// 前景偏移
DMA2D->OMAR = dstaddr;// 目的地址
DMA2D->OOR = offlinedist; // 输出偏移,行便宜将添加到行末尾,用于确认下一行的起始地址
DMA2D->NLR     = (uint32_t)(width << 16) | (uint16_t)height;

由于两幅图片需要混合,因此需要设置前景层图片透明度,这里我们需要设置FGPFCCR 寄存器

DMA2D->FGPFCCR = (opa << 24) | (1 << 16) | (pixelformat << 0)// 将第16位置1,强制图片透明度为opa
DMA2D->BGPFCCR = pixelformat; // 设置背景颜色格式
DMA2D->OPFCCR = pixelformat; // 设置输出颜色格式

将面的步骤整理成 api 如下:

void dma2d_mixcolorsbulk(void* pfg, void* pbg, void* pdst,
        uint32_t offlinefg, uint32_t offlinebg, uint32_t offlinedist,
        uint16_t width, uint16_t height,
        uint32_t pixelformat, uint8_t opa) { // opa 透明度

    DMA2D->CR    = 0x00020000UL;                

    DMA2D->FGMAR = (uint32_t)pfg;               
    DMA2D->BGMAR = (uint32_t)pbg;               
    DMA2D->OMAR  = (uint32_t)pdst;              

    DMA2D->FGOR  = offlinefg;                   
    DMA2D->BGOR  = offlinebg;                   
    DMA2D->OOR   = offlinedist;                 

    DMA2D->NLR = (uint32_t)(width << 16) | (uint16_t)height; 
    
    DMA2D->FGPFCCR = pixelformat | (1UL << 16) | ((uint32_t)opa << 24);            

    DMA2D->BGPFCCR = pixelformat;               
    DMA2D->OPFCCR  = pixelformat;                

    DMA2D->CR   |= DMA2D_CR_START;

    
    while (DMA2D->CR & DMA2D_CR_START) {}
}

int dma2d_test(void)
{ dma2d_mixcolorsbulk(sky_animation_mask,sky_animation_mask1,&_lcd.front_buf[_lcd.lcd_info.width*200+400],0,0,800-LOGO_W,LOGO1_W,LOGO1_H,LTDC_PIXEL_FORMAT_RGB565,128);
}
MSH_CMD_EXPORT(dma2d_test,dma2d_test);

效果如下:
在这里插入图片描述

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值