STemWin如何启用Multiple Buffering功能
1. Multiple Buffering的工作原理
多缓冲是一种使用多个帧缓冲器的方法。其基本原理如下:在启用多个缓冲器的情况下,由显示控制
器所使用的前置缓冲器(front buffer)会在屏幕上产生图像,同时,一个或多个后置缓冲器(back buffers)则用于绘图操作。绘图操
作完成后,后置缓冲器成为可见的前置缓冲器。
如果使用两个缓冲器 (即一个前置缓冲器和一个后置缓冲器),通常称之为 “双缓冲”;如果使用两
个后置缓冲器和一个前置缓冲器,则称之为 “三缓冲”。
由于多缓冲方法使用多个帧缓冲器,因此,即便绘图操作仍在进行中,屏幕画面也是完全渲染的结果。
启动绘图过程时,前置缓冲器的当前内容会被复制到一个后置缓冲器中。在该操作完成后,所有绘图
操作只对该后置缓冲器起作用。绘图操作完成后,后置缓冲器成为前置缓冲器。
如果要使后置缓冲器
成为可见的前置缓冲器,通常只需修改显示控制器的帧缓冲器起始地址寄存器即可。
可以认为,显示器的持续刷新是通过显示控制器的应用程序得以实现的。每秒60次。每个周期完成
之后有一个垂直同步信号,通常称之为VSYNC信号。使后置缓冲器成为前置缓冲器的最佳时机是该
信号出现之时。如果不考虑VSYNC信号,则可能产生撕裂效果(如图1.1所示):
图1.1 撕裂效果
2. 开发环境
硬件:STM32F429Discovery开发板,主板芯片是STM32F429ZI,外部64Mb的SDRAM,LCD是LG的LD050WV1。
软件:基于ST官方提供的STemWin_Library_V1.0.0固件库和例程进行移植,操作系统是FreeRTOS,emWin版本为5.22。
3. 如何启用
Multiple Buffering
3.1Multiple Buffering功能的工作流程
图3.1 Multiple Buffering的工作流程
如图3.1所示,
Multiple Buffering机制的工作顺序为:在
void
LCD_X_Config
(
void
)时进行配置;调用GUI_MULTIBUF_Begin()或GUI_MULTIBUF_BeginEx()对绘图进行缓冲
,此时emWin会将front buffer的内容拷贝到back buffer中
;进行绘图工作,此时所有的绘图工作都将在back buffer中进行;调用GUI_MULTIBUF_End()或GUI_MULTIBUF_EndEX()结束缓冲,此时emWin会将back buffer切换到front。
3.2 官方代码修改、实现以及实现原理解说
主要修改STemWinLibrary555_4x9i目录下的
GUIDRV_stm32f429i_discovery.c文件。由于该文件已经对Multiple Buffering功能进行了支持,所以重点修改此处即可:
//
//
Buffers
/
VScreens
//
#define
NUM_BUFFERS
2
//
Number
of
multiple
buffers
to
be
used
即使用两个buffer,也就是双缓冲。后续的内容官方代码已经写好了,这里指进行介绍说明,同时捋清emWin进行双缓冲功能的工作流程。
开启双缓冲后,在
void
LCD_X_Config
(
void
)函数中,预编译条件被打开:
#if
(
NUM_BUFFERS
>
1)
for
(i
=
0;
i
<
GUI_NUM_LAYERS;
i++)
{
GUI_MULTIBUF_ConfigEx(i,
NUM_BUFFERS);
}
#endif
这是对多缓冲功能进行配置,由于
NUM_BUFFERS
的值是
2
,所以多缓冲功能被打开。
前面提及开启双缓冲后,所有的绘图操作将在back buffer里进行,为了确保绘图的正确性,emWin需要一个copy函数将front buffer中的数据首先拷贝到back buffer中,然后再进行绘图。ST官方代码使用了用户自定义的buffer copy程序,这样可使使用DMA进行内存拷贝,以提高工作效率。使用自定义buffer copy程序,需要在
void
LCD_X_Config
(
void
)函数中添加下面内容:
LCD_SetDevFunc(i,
LCD_DEVFUNC_COPYBUFFER,
(
void(*)(
void))_LCD_CopyBuffer);
下面是官方给出的“
_LCD_CopyBuffer”函数
代码:
/*********************************************************************
*
*
_LCD_CopyBuffer
*/
static
void
_LCD_CopyBuffer(
int
LayerIndex,
int
IndexSrc,
int
IndexDst)
{
U32
BufferSize,
AddrSrc,
AddrDst;
BufferSize
=
_GetBufferSize(LayerIndex);
AddrSrc
=
_aAddr[LayerIndex]
+
BufferSize
*
IndexSrc;
AddrDst
=
_aAddr[LayerIndex]
+
BufferSize
*
IndexDst;
_DMA_Copy(LayerIndex,
(
void
*)AddrSrc,
(
void
*)AddrDst,
_axSize[LayerIndex],
_aySize
[
LayerIndex
],
0
);
}
在绘制完图形后,用户调用
GUI_MULTIBUF_End()函数,此时emWin将back buffer切换到前台显示出来。这个切换动作最好在LCD进行第一行的行扫描时进行,否则容易出现撕裂效果。因此这段代码被写在LTDC的中断处理程序中。
/*********************************************************************
*
*
LTDC_ISR_Handler
*
*
Purpose:
*
End-Of-Frame-Interrupt
for
managing
multiple
buffering
*/
void
LTDC_ISR_Handler(
void)
{
U32
Addr;
int
i;
LTDC->
ICR
=
(
U32)
LTDC_IER_LIE;
for
(i
=
0;
i
<
GUI_NUM_LAYERS;
i++)
{
if
(
_aPendingBuffer
[
i
]
>=
0
)
{
//
//
Calculate
address
of
buffer
to
be
used
as
visible
frame
buffer
//
Addr
=
_aAddr
[
i
]
+
_axSize
[
i
]
*
_aySize
[
i
]
*
_aPendingBuffer
[
i
]
*
_aBytesPerPixels
[
i
];
//
//
Store
address
into
SFR
//
_apLayer
[
i
]->
CFBAR
&=
~(
LTDC_LxCFBAR_CFBADD
);
_apLayer
[
i
]->
CFBAR
=
Addr
;
//
//
Reload
configuration
//
LTDC_ReloadConfig
(
LTDC_SRCR_IMR
);
//
//
Tell
emWin
that
buffer
is
used
//
GUI_MULTIBUF_ConfirmEx
(
i
,
_aPendingBuffer
[
i
]);
//
//
Clear
pending
buffer
flag
of
layer
//
_aBufferIndex
[
i
]
=
_aPendingBuffer
[
i
];
_aPendingBuffer
[
i
]
=
-
1
;
}
}
}
从以上程序可以看出,back buffer到front buffer的切换,只是将back buffer的首地址赋值给了LTDC的CFBAR寄存器。这样的切换时很迅速的,因此用户看不到图像的绘制过程,避免了由绘图工作带来的屏幕闪烁问题。
那么,emWin如何知道何时需要进行buffer的切换工作呢?实际上,当用户调用
GUI_MULTIBUF_End()等函数的时候,会发出
LCD_X_SHOWBUFFER
信号给“
LCD_X_DisplayDriver”函数。
int
LCD_X_DisplayDriver(
unsigned
LayerIndex,
unsigned
Cmd,
void
*
pData)
{
switch
(Cmd)
{
case
LCD_X_SHOWBUFFER
:
{
//
//
Required
if
multiple
buffers
are
used.
The
'Index'
element
of
p
contains
the
buffer
index.
//
LCD_X_SHOWBUFFER_INFO
*
p;
p
=
(
LCD_X_SHOWBUFFER_INFO
*)pData;
_aPendingBuffer[LayerIndex]
=
p->
Index;
break;
}
在需要进行buffer切换时,传入的p->Index为1,这样,在“LTDC_ISR_Handler”函数里就满足了“_aPendingBuffer[i] >= 0”的条件。
3.3 官方代码中存在的bug
如果按上述方法进行修改和配置,并启用
Multiple Buffering功能,那你可能发现,屏幕上能够出现文字,但是却没有方块、水平直线和竖直直线。如果仿真调试,会发现,其实矩形
、水平直线和竖直直线被画在了front buffer里。
这是由于官方示例代码中存在一个bug,导致用户自定义的“
LCD_DEVFUNC_FILLRECT
”功能函数使用了错误地址。该示例代码中配置使用了自定义“
LCD_DEVFUNC_FILLRECT
”函数:
if
(_GetPixelformat(i)
<=
LTDC_Pixelformat_ARGB4444)
{
LCD_SetDevFunc(i,
LCD_DEVFUNC_FILLRECT,
(
void(*)(
void))_LCD_FillRect);
}
而“
_LCD_FillRect"
函数的实现如下:
/*********************************************************************
*
*
_LCD_FillRect
*/
static
void
_LCD_FillRect(
int
LayerIndex,
int
x0,
int
y0,
int
x1,
int
y1,
U32
PixelIndex)
{
U32
BufferSize,
AddrDst;
int
xSize,
ySize;
if
(GUI_GetDrawMode()
==
GUI_DM_XOR)
{
LCD_SetDevFunc
(
LayerIndex
,
LCD_DEVFUNC_FILLRECT
,
NULL
);
LCD_FillRect
(
x0
,
y0
,
x1
,
y1
);
LCD_SetDevFunc
(
LayerIndex
,
LCD_DEVFUNC_FILLRECT
,
(
void
(*)
(
void
))
_LCD_FillRect
);
}
else
{
xSize
=
x1
-
x0
+
1
;
ySize
=
y1
-
y0
+
1
;
BufferSize
=
_GetBufferSize
(
LayerIndex
);
AddrDst
=
_aAddr
[
LayerIndex
]
+
BufferSize
*
_aBufferIndex
[
LayerIndex
]
+
(
y0
*
_axSize
[
LayerIndex
]
+
x0
)
*
_aBytesPerPixels
[
LayerIndex
];
_DMA_Fill
(
LayerIndex
,
(
void
*)
AddrDst
,
xSize
,
ySize
,
_axSize
[
LayerIndex
]
-
xSize
,
PixelIndex
);
}
}
该代码将在绘制矩形、水平直线和竖直直线时进行调用,将绘制的图形拷贝到相应的buffer中。
如果对该处代码进行调试,会发现“AddrDst”的值始终都是front buffer的值。而在启用双缓冲时,“AddrDst”应该指向back buffer。进一步会发现“_aBufferIndex[LayerIndex]”的值是错误的。这是因为不应该在LTDC的中断服务程序中对"_aBufferIndex[LayerIndex]"进行赋值。因为中断服务程序中的赋值只有在用户调用GUI_MULTIBUF_End()时才执行,这个时候绘图工作都已经完成了。正确的方式应该在GUI_MULTIBUF_Begin()后立即对"_aBufferIndex[LayerIndex]"进行赋值。
那么,应该在代码的何处进行赋值呢?答案是“_LCD_CopyBuffer”函数,因为调用GUI_MULTIBUF_Begin()后,emWin会调用该函数将front buffer中的内容拷贝的back buffer,然后才绘图。
因此,修改后的代码为:
/*********************************************************************
*
*
_LCD_CopyBuffer
*/
static
void
_LCD_CopyBuffer(
int
LayerIndex,
int
IndexSrc,
int
IndexDst)
{
U32
BufferSize,
AddrSrc,
AddrDst;
BufferSize
=
_GetBufferSize(LayerIndex);
AddrSrc
=
_aAddr[LayerIndex]
+
BufferSize
*
IndexSrc;
AddrDst
=
_aAddr[LayerIndex]
+
BufferSize
*
IndexDst;
_aBufferIndex[LayerIndex]
=
IndexDst;
_DMA_Copy(LayerIndex,
(
void
*)AddrSrc,
(
void
*)AddrDst,
_axSize[LayerIndex],
_aySize[LayerIndex],
0);
}
/*********************************************************************
*
*
LTDC_ISR_Handler
*
*
Purpose:
*
End-Of-Frame-Interrupt
for
managing
multiple
buffering
*/
void
LTDC_ISR_Handler(
void)
{
U32
Addr;
int
i;
LTDC->
ICR
=
(
U32)
LTDC_IER_LIE;
for
(i
=
0;
i
<
GUI_NUM_LAYERS;
i++)
{
if
(
_aPendingBuffer
[
i
]
>=
0
)
{
//
//
Calculate
address
of
buffer
to
be
used
as
visible
frame
buffer
//
Addr
=
_aAddr[i]
+
_axSize[i]
*
_aySize[i]
*
_aPendingBuffer[i]
*
_aBytesPerPixels[i];
//
//
Store
address
into
SFR
//
//
_apLayer[i]->CFBAR
&=
~(LTDC_LxCFBAR_CFBADD);
// 没发现这句有什么用,一起注掉了吧!
_apLayer
[
i
]->
CFBAR
=
Addr
;
//
//
Reload
configuration
//
LTDC_ReloadConfig
(
LTDC_SRCR_IMR
);
//
//
Tell
emWin
that
buffer
is
used
//
GUI_MULTIBUF_ConfirmEx
(
i
,
_aPendingBuffer
[
i
]);
//
//
Clear
pending
buffer
flag
of
layer
//
_aPendingBuffer
[
i
]
=
-
1
;
}
}
}
这个bug在 STemWin_Library_V1.0.1中仍然存在。但是1.0.1的官方例程中没有使用Multiple Buffering功能,而1.0.0中的demo明显使用了该功能(如图3.2):
图3.2 官方例程明显开启了Multiple Buffering功能
看起来似乎ST方面发现了Multiple Buffering功能不好用,但是没有从根本上解决问题,所以官方例程就不使用该功能了。这种治标不治本的处理方式,让我对st的工作态度表示怀疑。
这个bug在 STemWin_Library_V1.0.1中仍然存在。但是1.0.1的官方例程中没有使用Multiple Buffering功能,而1.0.0中的demo明显使用了该功能(如图3.2):
图3.2 官方例程明显开启了Multiple Buffering功能