STemWin如何启用Multiple Buffering功能

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的工作态度表示怀疑。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lionchan187

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值