硬汉嵌入式论坛

 找回密码
 立即注册
查看: 1994|回复: 11
收起左侧

[emWin] 补齐emwin一项重大短板,小内存单片机使用分段绘制方法研究——Part 3 分段绘制实现方法

[复制链接]

3

主题

17

回帖

26

积分

新手上路

积分
26
发表于 2021-9-7 11:49:50 | 显示全部楼层 |阅读模式
本帖最后由 zysxdg 于 2021-9-7 11:50 编辑

实现分段绘制的基本思想(重点优化 _DrawBitmap() 函数):

示意图

示意图

    对于SPI/I2C接口的TFT,传输数据需要的时间一般大于UI渲染的时间。使用DMA方式发送数据,并且立即开始渲染下一段UI数据,等前一段数据发送完成时,当前段已绘制完成,又可以立即发送,如此循环,相当于渲染和发送同时进行。这种情况下显示帧率基本上能接近数据发送帧率。

实现步骤(重要代码标记蓝色):
先把GUIConfig.h宏定义打开
#define GUI_WINSUPPORT           (1)    // Use Window Manager
#define GUI_SUPPORT_MEMDEV   (1)    // Use Memory Devices
GUIConfig.c文件中根据单片机内存大小设置合适的GUI动态内存

1.实现通过WM自动管理缓存大小。
WM_SetCreateFlags(WM_CF_SHOW | WM_CF_MEMDEV );
GUI_Init();

//创建窗口
...


//初始化缓存并设置缓存大小为GUI剩余缓存的一半,从而保证WM分段数据<缓存大小
hMemBuf = GUI_MEMDEV_CreateEx(0, 0, XSIZE_PHYS, GUI_ALLOC_GetNumFreeBytes()/2/2/XSIZE_PHYS+1, GUI_MEMDEV_HASTRANS);

//进入GUI事件循环
while(1)
{
        GUI_Delay(10);
}

2.用DMA发送方式实现渲染和发送同时进行(GUIDRV_Template.c文件/DMA中断文件)


//刷屏状态全局变量
volatile static int sign = 0;


//设置刷屏状态
void set_flushSign(int val)
{
    if(val == 0)
        sign = 0;
    else
        sign = 1;
}


//获取刷屏状态
int get_flushSign(void)
{
    if(sign == 0)
        return 0;
    else
        return 1;
}


//分段缓存
GUI_MEMDEV_Handle hMemBuf;

static void _DrawBitmap(GUI_DEVICE * pDevice, int x0, int y0,
                        int xSize, int ySize,
                        int BitsPerPixel,
                        int BytesPerLine,
                        const U8 * pData, int Diff,
                        const LCD_PIXELINDEX * pTrans)
{
    int i;
    u16 *pBuf;
    u16 *pTemp;

    switch (BitsPerPixel)
    {
    case 1:
        for (i = 0; i < ySize; i++)
        {
            _DrawBitLine1BPP(pDevice, x0, i + y0, pData, Diff, xSize, pTrans);
            pData += BytesPerLine;
        }
        break;
    case 2:
        for (i = 0; i < ySize; i++)
        {
            _DrawBitLine2BPP(pDevice, x0, i + y0, pData, Diff, xSize, pTrans);
            pData += BytesPerLine;
        }
        break;
    case 4:
        for (i = 0; i < ySize; i++)
        {
            _DrawBitLine4BPP(pDevice, x0, i + y0, pData, Diff, xSize, pTrans);
            pData += BytesPerLine;
        }
        break;
    case 8:
        for (i = 0; i < ySize; i++)
        {
            _DrawBitLine8BPP(pDevice, x0, i + y0, pData, xSize, pTrans);
            pData += BytesPerLine;
        }
        break;
        //
        // Only required for 16bpp color depth of target. Should be removed otherwise.
        //
    case 16:
       if(NULL == hMemBuf)
        {
            //缓存未初始化时使用默认方式
            for (i = 0; i < ySize; i++)
            {
                _DrawBitLine16BPP(pDevice, x0, i + y0, (const U16 *)pData, xSize);
                pData += BytesPerLine;
            }
       }
        else
        {
            //等待上一段数据发送完成
            while(get_flushSign() != 0);
            set_flushSign(1);
            //获取缓存地址
            pBuf = GUI_MEMDEV_GetDataPtr(hMemBuf);
            pTemp = pBuf;
            //将分段绘制完成的数据复制到缓存便于用DMA方式发送
            for (i = 0; i < ySize; i++)
            {
                memcpy(pTemp, (const U16 *)pData, xSize*2);
                pTemp += xSize;
                pData += BytesPerLine;
            }
            //将缓存数据用DMA发送到LCD
            LCD_PartialFlush(x0, y0, xSize, ySize, (uint32_t)pBuf, xSize*2*ySize);
            //立即返回开始渲染下段数据
        }

        break;
        //
        // Only required for 32bpp color depth of target. Should be removed otherwise.
        //
    case 32:
        for (i = 0; i < ySize; i++)
        {
            _DrawBitLine32BPP(pDevice, x0, i + y0, (const U32 *)pData, xSize);
            pData += BytesPerLine;
        }
        break;
    }
}

DMA中断文件:
DMA完成中断
{
    //清除刷屏状态
    set_flushSign(0);
    //其他信号处理
    LCD_CS = 1;
}

3.该方式使用的限制
因为WM才能自动使用分段,所以在窗口WM_PAINT事件之外的地方使用2D绘图函数将无法自动分段绘制,请将所有绘图操作全部放在窗口中进行就可以了。

以上,欢迎讨论。




回复

使用道具 举报

3

主题

338

回帖

347

积分

高级会员

积分
347
发表于 2021-9-7 14:48:04 | 显示全部楼层
如果emwin调用_DrawBitmap时,其像素数量很难预料,因为它是根据运行时可用的空闲gheap确定的。
所以这个中间缓存就很难满足,太大MCU满足不了;太小则所有努力都白费了。
回复

使用道具 举报

3

主题

338

回帖

347

积分

高级会员

积分
347
发表于 2021-9-7 14:50:41 | 显示全部楼层
emWin还有个特点,当memdev内存不足时,就会回退到非memdev方式,此时绘制操作就直接调用底层_drawpixel,_fillrect这些了
回复

使用道具 举报

3

主题

338

回帖

347

积分

高级会员

积分
347
发表于 2021-9-7 14:52:48 | 显示全部楼层
思前想后,感觉这是emWin的硬伤,绕都绕不过去,补也补不完美。
回复

使用道具 举报

3

主题

338

回帖

347

积分

高级会员

积分
347
发表于 2021-9-7 15:08:51 | 显示全部楼层
lvgl也是支持这种双缓存分块模式的,我想想还是学会用lvgl代替emWin更可行一点。
回复

使用道具 举报

3

主题

17

回帖

26

积分

新手上路

积分
26
 楼主| 发表于 2021-9-7 16:43:02 | 显示全部楼层
glory 发表于 2021-9-7 14:48
如果emwin调用_DrawBitmap时,其像素数量很难预料,因为它是根据运行时可用的空闲gheap确定的。
所以这个 ...

只要不动态创建和删除窗口,emwin分配的内存使用率变化波动很小,合理配置GUI_NUMBYTES大小,让分段大小在20行左右(相当于显存总共40行),渲染效率就已经可以了,我实测只要不开Alpha混合,界面基本能流畅运行。
回复

使用道具 举报

3

主题

17

回帖

26

积分

新手上路

积分
26
 楼主| 发表于 2021-9-7 16:46:10 | 显示全部楼层
glory 发表于 2021-9-7 15:08
lvgl也是支持这种双缓存分块模式的,我想想还是学会用lvgl代替emWin更可行一点。

原生支持的当然更好。顺便问下LVGL多语言支持如何,可以显示阿拉伯、泰语这些语言不?
回复

使用道具 举报

3

主题

338

回帖

347

积分

高级会员

积分
347
发表于 2021-9-7 21:38:34 | 显示全部楼层
lvgl支持多国语言,支持字符编码utf-8的。
回复

使用道具 举报

1

主题

8

回帖

11

积分

新手上路

积分
11
发表于 2022-2-18 00:11:38 来自手机 | 显示全部楼层
大兄弟,要是加上TE脚这又如何设计呢?毕竟不知道他啥时候绘制完一帧图片
回复

使用道具 举报

5

主题

166

回帖

181

积分

初级会员

积分
181
发表于 2022-2-19 11:36:24 | 显示全部楼层
我在想复制到缓存的那一段会不会稍微多余了,因为开启WM_CF_MEMDEV标志后就自动分段绘制了,传进_DrawBitmap函数的参数中就有分段的地址,直接拿这个地址给dma应该也行。
不过只是看到这个有这么个想法,还没实际测过。
回复

使用道具 举报

1

主题

8

回帖

11

积分

新手上路

积分
11
发表于 2022-2-21 19:33:54 来自手机 | 显示全部楼层
DX3906 发表于 2022-2-19 11:36
我在想复制到缓存的那一段会不会稍微多余了,因为开启WM_CF_MEMDEV标志后就自动分段绘制了,传进_DrawBitma ...

直接绘制可以,但是就违背了分段的初衷了哈,就因为spi慢,你dma过程中gui又有绘制的话会数据乱了
回复

使用道具 举报

5

主题

166

回帖

181

积分

初级会员

积分
181
发表于 2022-2-23 08:58:02 | 显示全部楼层
huangguimin 发表于 2022-2-21 19:33
直接绘制可以,但是就违背了分段的初衷了哈,就因为spi慢,你dma过程中gui又有绘制的话会数据乱了

我的意思是emwin内部其实是有分段绘制机制的,通过WM_CF_MEMDEV标志开启,然后调用_DrawBitmap函数实现画面从内部的分段缓存写到屏幕的过程,每调用一次就写一个段到屏幕,所以我就在想内部已经分段了,再多拷贝一次会不会有点冗余
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-5-20 10:54 , Processed in 0.380609 second(s), 26 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表