1. Multiple Buffering的工作原理
多緩沖是一種使用多個(gè)幀緩沖器的方法。其基本原理如下:在啟用多個(gè)緩沖器的情況下,,由顯示控制器所使用的前置緩沖器(front buffer)會(huì)在屏幕上產(chǎn)生圖像,,同時(shí),一個(gè)或多個(gè)后置緩沖器(back buffers)則用于繪圖操作,。繪圖操作完成后,,后置緩沖器成為可見的前置緩沖器。
如果使用兩個(gè)緩沖器 (即一個(gè)前置緩沖器和一個(gè)后置緩沖器),,通常稱之為 “雙緩沖”,;如果使用兩個(gè)后置緩沖器和一個(gè)前置緩沖器,則稱之為 “三緩沖”,。
由于多緩沖方法使用多個(gè)幀緩沖器,,因此,即便繪圖操作仍在進(jìn)行中,,屏幕畫面也是完全渲染的結(jié)果,。啟動(dòng)繪圖過程時(shí),前置緩沖器的當(dāng)前內(nèi)容會(huì)被復(fù)制到一個(gè)后置緩沖器中,。在該操作完成后,,所有繪圖操作只對(duì)該后置緩沖器起作用。繪圖操作完成后,,后置緩沖器成為前置緩沖器,。如果要使后置緩沖器成為可見的前置緩沖器,通常只需修改顯示控制器的幀緩沖器起始地址寄存器即可,。
可以認(rèn)為,,顯示器的持續(xù)刷新是通過顯示控制器的應(yīng)用程序得以實(shí)現(xiàn)的。每秒60次,。每個(gè)周期完成之后有一個(gè)垂直同步信號(hào),通常稱之為VSYNC信號(hào),。使后置緩沖器成為前置緩沖器的最佳時(shí)機(jī)是該信號(hào)出現(xiàn)之時(shí),。如果不考慮VSYNC信號(hào),則可能產(chǎn)生撕裂效果(如圖1.1所示):
圖1.1 撕裂效果
2. 開發(fā)環(huán)境
硬件:STM32F429Discovery開發(fā)板,,主板芯片是STM32F429ZI,,外部64Mb的SDRAM,LCD是LG的LD050WV1,。
軟件:基于ST官方提供的STemWin_Library_V1.0.0固件庫和例程進(jìn)行移植,,操作系統(tǒng)是FreeRTOS,emWin版本為5.22,。
3. 如何啟用Multiple Buffering
?
3.1Multiple Buffering功能的工作流程
?
圖3.1 Multiple Buffering的工作流程
如圖3.1所示,,Multiple Buffering機(jī)制的工作順序?yàn)椋涸?/span>void LCD_X_Config(void)時(shí)進(jìn)行配置,;調(diào)用GUI_MULTIBUF_Begin()或GUI_MULTIBUF_BeginEx()對(duì)繪圖進(jìn)行緩沖,此時(shí)emWin會(huì)將front buffer的內(nèi)容拷貝到back buffer中,;進(jìn)行繪圖工作,,此時(shí)所有的繪圖工作都將在back buffer中進(jìn)行;調(diào)用GUI_MULTIBUF_End()或GUI_MULTIBUF_EndEX()結(jié)束緩沖,,此時(shí)emWin會(huì)將back buffer切換到front,。
3.2 官方代碼修改、實(shí)現(xiàn)以及實(shí)現(xiàn)原理解說
?主要修改STemWinLibrary555_4x9i目錄下的GUIDRV_stm32f429i_discovery.c文件,。由于該文件已經(jīng)對(duì)Multiple Buffering功能進(jìn)行了支持,,所以重點(diǎn)修改此處即可:
//
// Buffers / VScreens
//
#define NUM_BUFFERS 2 // Number of multiple buffers to be used
即使用兩個(gè)buffer,也就是雙緩沖,。后續(xù)的內(nèi)容官方代碼已經(jīng)寫好了,,這里指進(jìn)行介紹說明,同時(shí)捋清e(cuò)mWin進(jìn)行雙緩沖功能的工作流程,。
開啟雙緩沖后,,在void LCD_X_Config(void)函數(shù)中,預(yù)編譯條件被打開:
#if (NUM_BUFFERS > 1)
for (i = 0; i < GUI_NUM_LAYERS; i++) {
GUI_MULTIBUF_ConfigEx(i, NUM_BUFFERS);
}
#endif
這是對(duì)多緩沖功能進(jìn)行配置,,由于NUM_BUFFERS的值是2,,所以多緩沖功能被打開。
前面提及開啟雙緩沖后,,所有的繪圖操作將在back buffer里進(jìn)行,,為了確保繪圖的正確性,emWin需要一個(gè)copy函數(shù)將front buffer中的數(shù)據(jù)首先拷貝到back buffer中,,然后再進(jìn)行繪圖,。ST官方代碼使用了用戶自定義的buffer copy程序,這樣可使使用DMA進(jìn)行內(nèi)存拷貝,,以提高工作效率,。使用自定義buffer copy程序,需要在void LCD_X_Config(void)函數(shù)中添加下面內(nèi)容:
LCD_SetDevFunc(i, LCD_DEVFUNC_COPYBUFFER, (void(*)(void))_LCD_CopyBuffer);
下面是官方給出的“_LCD_CopyBuffer”函數(shù)代碼:
/*********************************************************************
*
* _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);
}
在繪制完圖形后,,用戶調(diào)用GUI_MULTIBUF_End()函數(shù),,此時(shí)emWin將back buffer切換到前臺(tái)顯示出來。這個(gè)切換動(dòng)作最好在LCD進(jìn)行第一行的行掃描時(shí)進(jìn)行,,否則容易出現(xiàn)撕裂效果,。因此這段代碼被寫在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寄存器。這樣的切換時(shí)很迅速的,,因此用戶看不到圖像的繪制過程,,避免了由繪圖工作帶來的屏幕閃爍問題,。
那么,emWin如何知道何時(shí)需要進(jìn)行buffer的切換工作呢,?實(shí)際上,,當(dāng)用戶調(diào)用GUI_MULTIBUF_End()等函數(shù)的時(shí)候,會(huì)發(fā)出LCD_X_SHOWBUFFER信號(hào)給“LCD_X_DisplayDriver”函數(shù),。
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;
}
在需要進(jìn)行buffer切換時(shí),,傳入的p->Index為1,這樣,,在“LTDC_ISR_Handler”函數(shù)里就滿足了“_aPendingBuffer[i] >= 0”的條件,。
3.3 官方代碼中存在的bug
如果按上述方法進(jìn)行修改和配置,并啟用Multiple Buffering功能,,那你可能發(fā)現(xiàn),,屏幕上能夠出現(xiàn)文字,但是卻沒有方塊,、水平直線和豎直直線,。如果仿真調(diào)試,會(huì)發(fā)現(xiàn),,其實(shí)矩形,、水平直線和豎直直線被畫在了front buffer里。
這是由于官方示例代碼中存在一個(gè)bug,,導(dǎo)致用戶自定義的“LCD_DEVFUNC_FILLRECT”功能函數(shù)使用了錯(cuò)誤地址,。該示例代碼中配置使用了自定義“LCD_DEVFUNC_FILLRECT”函數(shù):
if (_GetPixelformat(i) <= LTDC_Pixelformat_ARGB4444) {
LCD_SetDevFunc(i, LCD_DEVFUNC_FILLRECT, (void(*)(void))_LCD_FillRect);
}
而“ _LCD_FillRect"函數(shù)的實(shí)現(xiàn)如下:
/*********************************************************************
*
* _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);
}
}
該代碼將在繪制矩形、水平直線和豎直直線時(shí)進(jìn)行調(diào)用,,將繪制的圖形拷貝到相應(yīng)的buffer中,。如果對(duì)該處代碼進(jìn)行調(diào)試,會(huì)發(fā)現(xiàn)“AddrDst”的值始終都是front buffer的值,。而在啟用雙緩沖時(shí),,“AddrDst”應(yīng)該指向back buffer。進(jìn)一步會(huì)發(fā)現(xiàn)“_aBufferIndex[LayerIndex]”的值是錯(cuò)誤的,。這是因?yàn)椴粦?yīng)該在LTDC的中斷服務(wù)程序中對(duì)"_aBufferIndex[LayerIndex]"進(jìn)行賦值,。因?yàn)橹袛喾?wù)程序中的賦值只有在用戶調(diào)用GUI_MULTIBUF_End()時(shí)才執(zhí)行,這個(gè)時(shí)候繪圖工作都已經(jīng)完成了,。正確的方式應(yīng)該在GUI_MULTIBUF_Begin()后立即對(duì)"_aBufferIndex[LayerIndex]"進(jìn)行賦值。
那么,,應(yīng)該在代碼的何處進(jìn)行賦值呢,?答案是“_LCD_CopyBuffer”函數(shù),因?yàn)檎{(diào)用GUI_MULTIBUF_Begin()后,,emWin會(huì)調(diào)用該函數(shù)將front buffer中的內(nèi)容拷貝的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); // 沒發(fā)現(xiàn)這句有什么用,,一起注掉了吧,!
_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;
}
}
} 圖3.2 官方例程明顯開啟了Multiple Buffering功能 看起來似乎ST方面發(fā)現(xiàn)了Multiple Buffering功能不好用,,但是沒有從根本上解決問題,所以官方例程就不使用該功能了,。這種治標(biāo)不治本的處理方式,,讓我對(duì)st的工作態(tài)度表示懷疑。 |
|