【器件型號】
單片機採用STM32F429IG,運行頻率爲180MHz,外部晶振HSE的頻率爲25MHz。
開發板採用外部32MB的SDRAM內存作顯存。顯示屏分辨率爲800×480,顏色格式爲RGB565,每個像素佔2個字節(顯示半透明位圖時,位圖每像素佔3個字節)。顯存佔用內存的大小爲800×480×2=768000字節=750KB。
7寸LCD液晶屏與單片機的連接電路如下圖所示。這個液晶屏通過LTDC接口和I²C接口與單片機連接。LTDC用於傳輸圖像數據,I²C用於傳輸觸摸信息。圖中的RESET、T_PEN、T_MISO和T_CS引腳爲空引腳。
名稱 | 描述 | I/O口 |
---|---|---|
LCD_BL | 液晶屏顯示開關(高電平開顯示) | PB5 |
LCD_DE | LTDC數據使能信號(低電平有效) | PF10 |
LCD_VSYNC | LTDC垂直同步信號 | PI9 |
LCD_HSYNC | LTDC水平同步信號 | PI10 |
LCD_CLK | LTDC時鐘信號 | PG7 |
LCD_R3~7 | LTDC紅色分量值(5位) | PH9~12, PG6 |
LCD_G2~7 | LTDC綠色分量值(6位) | PH13~15, PI0~2 |
LCD_B3~7 | LTDC藍色分量值(5位) | PG11, PI4~7 |
T_SCK | I²C總線時鐘 | PH6 |
T_MOSI | I²C總線數據 |
PI3 |
SDRAM與單片機的連接電路如下圖所示。這些線全部爲FMC的SDRAM信號線。
【測試程序】
程序下載地址:https://pan.baidu.com/s/1Hpiy-RMqErWjy3VtyPKolA(提取碼:fxmr)
#include <GUI.h>
#include <stdio.h>
#include <stm32f4xx.h>
#include "common.h"
#include "images.h"
#include "ARKLCD7.h"
#include "W9825G6KH.h"
static void display_image(void)
{
GUI_BITMAP bmp;
bmp.XSize = IMAGE_EXCLAMATION_WIDTH; // 寬度
bmp.YSize = IMAGE_EXCLAMATION_HEIGHT; // 高度
bmp.BytesPerLine = 3 * bmp.XSize; // 每行字節數
bmp.BitsPerPixel = 24; // 每個像素點字節數
bmp.pData = image_exclamation; // 位圖數據
bmp.pPal = NULL; // 調色板
bmp.pMethods = GUI_DRAW_BMPAM565; // 數據格式: ARGB565
GUI_DrawBitmap(&bmp, (SCREEN_WIDTH - IMAGE_EXCLAMATION_WIDTH) / 2, 30);
}
int main(void)
{
char str[60];
int i, ret;
ARKLCD7_TouchInfo touch;
GUI_MEMDEV_Handle memdev;
GUI_RECT rect;
HAL_Init();
clock_init();
usart_init(115200);
printf("STM32F429IG ARKLCD7\n");
printf("SystemCoreClock=%u\n", SystemCoreClock);
W9825G6KH_Init(); // 初始化SDRAM內存
ARKLCD7_Init(); // 初始化液晶屏
GUI_Init();
GUI_SetBkColor(GUI_MAGENTA); // 背景設爲淺紫色
GUI_Clear(); // 清屏
// 打開緩衝功能, 繪圖先在內存裏面完成, 繪製好了之後再一次性顯示出來, 避免閃爍
memdev = GUI_MEMDEV_Create(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
GUI_MEMDEV_Select(memdev);
display_image(); // 繪製位圖
GUI_MEMDEV_CopyToLCD(memdev); // 圖像繪製完成後, 調用這個函數顯示出來
GUI_SetFont(GUI_FONT_32B_ASCII); // 設置字體
GUI_SetTextMode(GUI_TM_TRANS); // 文字背景透明
while (1)
{
ret = ARKLCD7_ReadTouchInfo(&touch); // 讀取觸摸信息
if (ret == 0)
{
// 顯示系統時間
GUI_SetColor(GUI_RED);
rect.x0 = 0;
rect.y0 = 200;
rect.x1 = SCREEN_WIDTH;
rect.y1 = 235;
snprintf(str, sizeof(str), "Ticks: %u", HAL_GetTick());
GUI_ClearRectEx(&rect);
GUI_DispStringInRect(str, &rect, GUI_TA_HCENTER | GUI_TA_VCENTER);
// 顯示觸摸點的個數
rect.y0 += 35;
rect.y1 += 35;
snprintf(str, sizeof(str), "Point count: %d", touch.count);
GUI_ClearRectEx(&rect);
GUI_DispStringInRect(str, &rect, GUI_TA_HCENTER | GUI_TA_VCENTER);
// 顯示觸摸點的座標
GUI_SetColor(GUI_BLUE);
for (i = 0; i < 5; i++)
{
rect.y0 += 35;
rect.y1 += 35;
GUI_ClearRectEx(&rect);
if (i < touch.count)
{
snprintf(str, sizeof(str), "Point %d: (%u, %u)", i + 1, touch.points[i].x, touch.points[i].y);
GUI_DispStringInRect(str, &rect, GUI_TA_HCENTER | GUI_TA_VCENTER);
}
}
GUI_MEMDEV_CopyToLCD(memdev);
}
HAL_Delay(50);
}
}
其中用到的半透明圖像保存在單片機的內部Flash中,大小約爲60KB:
#ifndef __IMAGES_H
#define __IMAGES_H
#define IMAGE_EXCLAMATION_WIDTH 154
#define IMAGE_EXCLAMATION_HEIGHT 134
extern const unsigned char image_exclamation[61908];
#endif
【程序運行效果】
背景色爲淺紫色,上方顯示一張半透明圖片,下方顯示毫秒計數器的當前值(ticks),以及觸摸點的個數和座標。
【液晶屏驅動程序】
LCD顯示屏的初始化由ARKLCD7.c完成。初始化液晶屏調用的是ARKLCD7_Init函數,這個函數會先初始化液晶屏的GPIO口,然後配置好LTDC。
ARKLCD7這個液晶屏的LTDC_R7、LTDC_G7和LTDC_B7(PG6、PI2和PI7)引腳上接了外部上下拉電阻,用來標識這個液晶屏的分辨率(具體看正點原子的液晶屏手冊)。本項目使用的液晶屏的分辨率爲800×480,顏色爲16位RGB565,每個像素佔用兩個字節,所以需要的顯存是800×480×2字節,即750KB。
HAL_RCCEx_PeriphCLKConfig配置的是LTDC的頻率,也就是顯示器的刷新率。使用外部SDRAM作顯存時,時鐘頻率不能太高,否則顯示器無法顯示圖像。還要注意不能超過SDRAM的最大讀寫速率。考慮了這些因素後,程序中將刷新率設置爲30MHz。計算方式爲PLLSAIN÷PLLSAIR÷PLLSAIDivR=360÷6÷2=30。最開始設置的頻率是33MHz(也就是正點原子例程上面的頻率),後來發現往SDRAM上通過memcpy放置大尺寸圖像時,液晶屏抖動很厲害,刷新率降低到後30MHz問題就解決了。值得注意的是,板子上還接了一個NAND Flash存儲器,這個存儲器的讀取速度能達到6MB/s。但是由於NAND Flash和SDRAM共用了FMC的總線,所以用SDRAM作顯存時,NAND Flash的讀取速度不能太快,否則會因爲總線佔用時間過長,LTDC沒有足夠的機會讀取SDRAM,導致液晶屏圖像劇烈抖動。讀取NAND Flash時,要注意每一條指令發出去後都要delay延時一下,delay函數中的for循環至少要循環1000次,這樣液晶屏就不會發生抖動。
防止液晶屏抖動,還要注意儘量減少SDRAM的併發讀寫操作。例如,在FreeRTOS系統環境下,當一個任務在調用GUI_DrawBitmap函數往SDRAM上繪製位圖,另一個任務在用FATFS的f_read函數將W25Q256存儲器中存儲的文件加載到SDRAM上。此時加上LTDC的液晶屏顯示(LTDC每時每刻都在不停地讀取SDRAM,並將顯存中的數據發給液晶屏顯示),就有3個併發的SDRAM操作。這時液晶屏圖像就會發生抖動。要解決這個問題,可以設法減少SDRAM併發操作的個數。調用FreeRTOS系統的xSemaphoreCreateRecursiveMutex函數建立一個互斥量。進行GUI_DrawBitmap操作前先用xSemaphoreTakeRecursive鎖住這個互斥量,完了之後用xSemaphoreGiveRecursive解鎖。FATFS底層的diskio讀取數據塊的函數也加上這個互斥鎖。這樣同時的併發操作數只有2個,就能解決液晶屏抖動的問題。
void ARKLCD7_Init(void)
{
uint8_t screen_id = 0;
GPIO_InitTypeDef gpio;
LTDC_LayerCfgTypeDef layer;
RCC_PeriphCLKInitTypeDef clk = {0};
__HAL_RCC_CRC_CLK_ENABLE(); // STemWin GUI_Init前必須打開CRC
__HAL_RCC_DMA2D_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOI_CLK_ENABLE();
/* 先讀取ID */
gpio.Mode = GPIO_MODE_INPUT;
gpio.Pin = GPIO_PIN_6; // LTDC_R7
gpio.Pull = GPIO_PULLUP;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOG, &gpio);
gpio.Pin = GPIO_PIN_2 | GPIO_PIN_7; // LTDC_G7, LTDC_B7
HAL_GPIO_Init(GPIOI, &gpio);
if (HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_6) == GPIO_PIN_SET)
screen_id |= 1;
if (HAL_GPIO_ReadPin(GPIOI, GPIO_PIN_2) == GPIO_PIN_SET)
screen_id |= 2;
if (HAL_GPIO_ReadPin(GPIOI, GPIO_PIN_7) == GPIO_PIN_SET)
screen_id |= 4;
printf("Screen ID: %u\n", screen_id);
/* 初始化Screen引腳 */
// PB5: 顯示控制 (LCD_BL)
gpio.Mode = GPIO_MODE_OUTPUT_PP;
gpio.Pin = GPIO_PIN_5;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &gpio);
// PH6: SCL, PI3: SDA
SCL_1;
SDA_1;
gpio.Mode = GPIO_MODE_OUTPUT_OD;
gpio.Pin = GPIO_PIN_6;
gpio.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOH, &gpio);
gpio.Pin = GPIO_PIN_3;
HAL_GPIO_Init(GPIOI, &gpio);
// PF10: LTDC_DE
gpio.Alternate = GPIO_AF14_LTDC;
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Pin = GPIO_PIN_10;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOF, &gpio);
// PG6: LTDC_R7, PG7: LTDC_CLK, PG11: LTDC_B3
gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_11;
HAL_GPIO_Init(GPIOG, &gpio);
// PH9~12: LTDC_R3~6, PH13~15: LTDC_G2~4
gpio.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOH, &gpio);
// PI0~2: LTDC_G5~7, PI4~7: LTDC_B4~7, PI9: LTDC_VSYNC, PI10: LTDC_HSYNC
gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_9 | GPIO_PIN_10;
HAL_GPIO_Init(GPIOI, &gpio);
/* 初始化LTDC */
// 使用外部SRAM作顯存時, 時鐘頻率不能太高
clk.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
clk.PLLSAI.PLLSAIN = 360;
clk.PLLSAI.PLLSAIR = 6;
clk.PLLSAIDivR = RCC_PLLSAIDIVR_2;
HAL_RCCEx_PeriphCLKConfig(&clk);
__HAL_RCC_LTDC_CLK_ENABLE();
HAL_NVIC_EnableIRQ(LTDC_IRQn);
hltdc.Instance = LTDC;
hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL;
hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL;
hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL;
hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
hltdc.Init.HorizontalSync = SCREEN_HSW - 1;
hltdc.Init.VerticalSync = SCREEN_VSW - 1;
hltdc.Init.AccumulatedHBP = SCREEN_HSW + SCREEN_HBP - 1;
hltdc.Init.AccumulatedVBP = SCREEN_VSW + SCREEN_VBP - 1;
hltdc.Init.AccumulatedActiveW = SCREEN_HSW + SCREEN_HBP + SCREEN_WIDTH - 1;
hltdc.Init.AccumulatedActiveH = SCREEN_VSW + SCREEN_VBP + SCREEN_HEIGHT - 1;
hltdc.Init.TotalWidth = SCREEN_HSW + SCREEN_HBP + SCREEN_WIDTH + SCREEN_HFP - 1;
hltdc.Init.TotalHeigh = SCREEN_VSW + SCREEN_VBP + SCREEN_HEIGHT + SCREEN_VFP - 1;
hltdc.Init.Backcolor.Red = 0;
hltdc.Init.Backcolor.Green = 0;
hltdc.Init.Backcolor.Blue = 0;
HAL_LTDC_Init(&hltdc);
layer.WindowX0 = 0;
layer.WindowX1 = SCREEN_WIDTH;
layer.WindowY0 = 0;
layer.WindowY1 = SCREEN_HEIGHT;
layer.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
layer.Alpha = 255;
layer.Alpha0 = 0;
layer.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
layer.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
layer.FBStartAdress = (uint32_t)screen_buffer[0];
layer.ImageWidth = SCREEN_WIDTH;
layer.ImageHeight = SCREEN_HEIGHT;
layer.Backcolor.Red = 0;
layer.Backcolor.Green = 0;
layer.Backcolor.Blue = 0;
HAL_LTDC_ConfigLayer(&hltdc, &layer, 0);
hdma2d.Instance = DMA2D;
}
配置完成後,三維數組screen_buffer就是液晶屏所顯示的內容,格式是screen_buffer[緩衝區編號][y座標][x座標]。將PB5引腳拉高,打開液晶屏顯示。
ARKLCD7_Enable函數通過LCD_BL(PB5)引腳控制液晶屏是否顯示圖像。設爲高電平時開顯示。但是要注意,LTDC至少要傳輸圖像150ms後才能開顯示,否則開顯示瞬間會出現一道白線(正點原子本身的例程就有這個bug) 。
void ARKLCD7_Enable(int enabled)
{
if (enabled)
{
// 開顯示前必須確保LTDC已經打開並開始傳輸圖像
printf("Screen is enabled!\n");
HAL_Delay(150); // 延時, 讓LTDC傳輸一段時間圖像但不顯示, 這樣可以防止開顯示屏的瞬間出現白線
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); // 開顯示
}
else
{
printf("Screen is disabled!\n");
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET); // 關顯示
}
}
開發板使用的SDRAM是W9825G6KH,容量爲32MB。SDRAM的初始化操作由W9825G6KH.c完成。首先系統啓動時會在main函數中調用W9825G6KH_Init函數,這個函數主要是初始化SDRAM的GPIO端口(HAL_GPIO_Init),配置好STM32的FMC(HAL_SDRAM_Init),然後發送SDRAM初始化命令(HAL_SDRAM_SendCommand)。初始化完畢後,可通過0xc0000000~0xc1ffffff地址訪問SDRAM內存。
SDRAM內存的初始化程序如下:
#include <stm32f4xx.h>
#include "W9825G6KH.h"
SDRAM_HandleTypeDef hsdram;
void W9825G6KH_Init(void)
{
FMC_SDRAM_CommandTypeDef cmd;
FMC_SDRAM_TimingTypeDef timing;
GPIO_InitTypeDef gpio;
__HAL_RCC_FMC_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/* 初始化SDRAM引腳 */
// PC0: FMC_SDNWE, PC2: FMC_SDNE0, PC3: FMC_SDCKE0
gpio.Alternate = GPIO_AF12_FMC;
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Pin = GPIO_PIN_0 | GPIO_PIN_2 | GPIO_PIN_3;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOC, &gpio);
// PD0~1: FMC_D2~3, PD8~10: FMC_D13~15, PD14~15: FMC_D0~1
gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOD, &gpio);
// PE0~1: FMC_NBL0~1, PE7~15: FMC_D4~12
gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOE, &gpio);
// PF0~5: FMC_A0~5, PF11: FMC_SDNRAS, PF12~15: FMC_A6~9
gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOF, &gpio);
// PG0~2: FMC_A10~12, PG4~5: FMC_BA0~1, PG8: FMC_SDCLK, PG15: FMC_SDNCAS
gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOG, &gpio);
/* 初始化SDRAM */
hsdram.Instance = FMC_Bank5_6;
hsdram.Init.SDBank = FMC_SDRAM_BANK1;
hsdram.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;
hsdram.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13;
hsdram.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;
hsdram.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
hsdram.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;
hsdram.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
hsdram.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
hsdram.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
hsdram.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0;
timing.LoadToActiveDelay = 1;
timing.ExitSelfRefreshDelay = 1;
timing.SelfRefreshTime = 6;
timing.RowCycleDelay = 4;
timing.WriteRecoveryTime = 1;
timing.RPDelay = 1;
timing.RCDDelay = 1;
HAL_SDRAM_Init(&hsdram, &timing);
cmd.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
cmd.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
cmd.AutoRefreshNumber = 1; // 避免assert_failed
cmd.ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(&hsdram, &cmd, HAL_MAX_DELAY);
HAL_Delay(1);
// Precharge All
cmd.CommandMode = FMC_SDRAM_CMD_PALL;
HAL_SDRAM_SendCommand(&hsdram, &cmd, HAL_MAX_DELAY);
cmd.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
cmd.AutoRefreshNumber = 8;
HAL_SDRAM_SendCommand(&hsdram, &cmd, HAL_MAX_DELAY);
// Load Mode Register
// CAS latency: 3, Write mode: burst read and single write
cmd.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
cmd.ModeRegisterDefinition = 0x230; // 參閱10.4 Mode Register Set Cycle
HAL_SDRAM_SendCommand(&hsdram, &cmd, HAL_MAX_DELAY);
HAL_SDRAM_ProgramRefreshRate(&hsdram, 683);
}
液晶屏的I²C接口專門用來傳輸觸控信息,設備地址爲0x70。由於正點原子的板子的觸控引腳沒有接到STM32的I²C引腳上,因此只能GPIO模擬I²C。ARKLCD7_ReadTouchInfo就是通過模擬I²C讀取觸控信息的函數,讀出來的寄存器值是保存到ARKLCD7_TouchInfo結構體裏面的。其中count成員代表當前一共有多少個觸控點,points數組保存了每個觸控點的座標信息。比如,第一個觸控點的X座標是info.points[0].x & ARKLCD7_X,Y座標是info.points[0].y & ARKLCD7_Y。其中ARKLCD7_X和ARKLCD7_Y都等於0xfff。之所以要&0xfff,是因爲保存x、y座標的寄存器裏面還保存了座標以外的其他信息。
通過I²C讀取觸控信息的代碼如下:
int ARKLCD7_ReadTouchInfo(ARKLCD7_TouchInfo *info)
{
int i, ret;
ret = ARKLCD7_Read(0, info, sizeof(ARKLCD7_TouchInfo));
if (ret != sizeof(ARKLCD7_TouchInfo))
return -1;
if (info->count > ARKLCD7_MAX_POINTS)
return -1;
for (i = 0; i < info->count; i++)
{
info->points[i].x = htons(info->points[i].x) & ARKLCD7_X;
info->points[i].y = htons(info->points[i].y) & ARKLCD7_Y;
}
return 0;
}
【STemWin移植】
液晶屏採用STemWin圖形庫來繪製圖形,如繪製直線、矩形、圓等圖形。STemWin是不開放源代碼的庫,所以只有編譯好的庫文件,沒有源代碼。STemWin可在ST官網HAL庫文件包中的STM32Cube_FW_F4_V1.24.0/Middlewares/ST/STemWin文件夾中找到。裸機環境下采用的庫文件是STemWin_CM4_wc16.a。如果有操作系統,應該選擇STemWin_CM4_OS_wc16.a。添加到Keil工程後,要將文件屬性選擇爲Library file,而不是彙編語言的源文件。
將inc文件夾全部複製出來。Config文件夾中只需要複製GUIConf.c/h、LCDConf_Lin.c/h。最後將GUI_X.c也添加到工程中。
這些文件中,只有GUIConf.c和LCDConf_Lin.c需要修改。
在GUIConf.c中,將aMemory修改到外部SDRAM內存裏面(地址0xc0000000)。這塊內存用於圖形庫的圖形處理,分配的大小由GUI_NUMBYTES宏決定。將數組的類型定義爲uint32_t而不是uint8_t,可以保證數組的首地址能夠被4整除。
static U32 aMemory[GUI_NUMBYTES / 4] __attribute__((at(0xc0000000)));
LCDConf_Lin.c用於將STemWin庫和LCD液晶屏綁定起來,裏面最重要的語句是LCD_SetVRAMAddrEx(0, (void *)VRAM_ADDR),其中VRAM_ADDR就是screen_buffer,也就是顯存的地址。設定了這個地址之後,所有的圖形操作都是往這個顯存裏面寫。以下還有一些其他的參數。
屏幕分辨率:
#define XSIZE_PHYS 800
#define YSIZE_PHYS 480
顏色格式:RGB565
#define COLOR_CONVERSION GUICC_M565
驅動類型:
#define DISPLAY_DRIVER GUIDRV_LIN_16
顯存地址:
#define VRAM_ADDR screen_buffer
重寫一些圖形操作函數(可利用DMA2D硬件加速):
//
// Set custom functions for several operations to optimize native processes
//
LCD_SetDevFunc(0, LCD_DEVFUNC_COPYBUFFER, (ARKLCD7_Function)ARKLCD7_CopyBuffer);
LCD_SetDevFunc(0, LCD_DEVFUNC_COPYRECT, (ARKLCD7_Function)ARKLCD7_CopyRect);
LCD_SetDevFunc(0, LCD_DEVFUNC_FILLRECT, (ARKLCD7_Function)ARKLCD7_FillRect);
//LCD_SetDevFunc(0, LCD_DEVFUNC_DRAWBMP_8BPP, (void(*)(void))CUSTOM_LCD_DrawBitmap8bpp);
//LCD_SetDevFunc(0, LCD_DEVFUNC_DRAWBMP_16BPP, (void(*)(void))CUSTOM_LCD_DrawBitmap16bpp);
GUI_MEMDEV_SetDrawMemdev16bppFunc(ARKLCD7_CopyRectFromMemdev);
還有就是LCD_X_DisplayDriver函數裏面,和我們的驅動程序綁定。
GUI_Init函數初始化圖形庫,執行這個函數前必須要調用__HAL_RCC_CRC_CLK_ENABLE函數打開STM32自帶的CRC功能,否則程序會卡死。打開CRC是在ARKLCD7_Init裏面進行的。之後就可以用圖形庫提供的各種API來繪製圖形。
同時繪製多個圖形時,爲了防止看到繪製的中間過程產生的屏幕閃爍,可以在內存中創建一個GUI_MEMDEV,用GUI_MEMDEV_Select選中,在內存中繪製好所有的內容後,再調用GUI_MEMDEV_CopyToLCD一次性顯示出來。GUI_MEMDEV還可以用於保存位圖的繪製結果,比如位圖的縮放或旋轉結果,要用的時候直接顯示出來,不用再繪製一遍。
MEMDEV能夠消除多個圖像繪製步驟中產生的屏幕閃爍,但是不能消除拖動圖片時屏幕上產生的撕裂現象(tearing effect)。MULTIBUF(多緩衝)方式可以解決這個問題。MULTIBUF需要配合LTDC的垂直同步中斷,將畫好的內容從內存轉移到液晶屏上的操作必須在此中斷產生的瞬間完成。圖像繪製好了要顯示時,調用ARKLCD7_ShowBuffer函數,函數中用HAL_LTDC_ProgramLineEvent開中斷,中斷產生後,在HAL_LTDC_LineEventCallback函數中用HAL_LTDC_SetAddress直接將顯存的地址修改到繪製好的內存地址上,避免複製操作,然後調用GUI_MULTIBUF_Confirm函數通知STemWin圖像已完成顯示。