本次使用的硬件設備為野火的霸道V2開發(fā)板,顯示器控制芯片型號為ILI9341,實際型號為ST7789V。在編寫代碼時參考的是ILI9341數(shù)據(jù)手冊,二者差別不大,都是240*320分辨率。
1. 簡介
????????ILI9341是一個用于TFT液晶顯示的單芯片控制驅(qū)動器,具有262144色的240RGB x 320像素顯示解決方案。ILI9341支持8/9/16/18位數(shù)據(jù)總線的MCU接口,6/16/18位數(shù)據(jù)總線RGB接口以及3/4線的SPI接口。移動圖像區(qū)域可以通過窗口地址功能再內(nèi)部GRAM來指定。指定的窗口區(qū)域可以選擇性地更新,因此,可以在圖像區(qū)域同時獨立的顯示移動圖像。
系統(tǒng)接口:
? ? ? ? 8080-Ⅰ/8080-Ⅱ系列MCU的8/9/16/18位接口。
? ? ? ? 圖形控制的6/16/18位RGB接口。
? ? ? ? 3/4線的SPI接口。
????????在控制方式上,一般采用16位控制方式(RGB565)??稍斪xILI9341數(shù)據(jù)手冊,在3AH寄存器中可進行配置。
?2. 引腳連接
LCD顯示屏處的排針:
?開發(fā)板上對應的LCD接口:(FSMC_8080模式)
?對應引腳連接:
控制引腳 | ||
CS | PG12 | FSMC_NE4 |
#RS | PE2 |
FSMC_A23
|
#WR | PD5 | FSMC_NWE |
#RD | PD4 |
FSMC_NOE
|
復位和背光引腳 | ||
RES | PG11 | 復位 |
#BK | PG6 | 背光 |
數(shù)據(jù)引腳 | ||
DB0 | PD14 | FSMC_D0 |
DB1 | PD15 | FSMC_D1 |
DB2 | PD0 | FSMC_D2 |
DB3 | PD1 | FSMC_D3 |
DB4 | PE7 | FSMC_D4 |
DB5 | PE8 | FSMC_D5 |
DB6 | PE9 | FSMC_D6 |
DB7 | PE10 | FSMC_D7 |
DB8 | PE11 | FSMC_D8 |
DB9 | PE12 | FSMC_D9 |
DB10 | PE13 | FSMC_D10 |
DB11 | PE14 | FSMC_D11 |
DB12 | PE15 | FSMC_D12 |
DB13 | PD8 | FSMC_D13 |
DB14 | PD9 | FSMC_D14 |
DB15 | PD10 | FSMC_D15 |
????????在引腳連接時,特地將LCD的控制引腳和數(shù)據(jù)引腳與MCU的FSMC外設連接,在使用FSMC模擬8080時序時,這些引腳便可交由FSMC控制,只需將FSMC配置好就可以了。當然,也可使用模擬SPI對這些引腳進行控制。所以在編寫代碼時,除了讀寫接口函數(shù)配置不同以外,兩種控制方式的其他帶啊嗎都可相同。
3. FSMC與“8080”
如果說你問我:你怎么知道FSMC可以模擬8080?
我只能回答:我也是聽別人說的。
首先我們先對比8080與FSMC二者時序的異同。

?注:①寫命令;②寫數(shù)據(jù)。

LCD 8080時序 | FSMC 寫NOR | ||
#CS | 片選 | #NEx | 片選 |
RDX | 讀使能 | #NOE | 讀使能 |
WRX | 寫使能 | #NWE | 寫使能 |
D/CX | 數(shù)據(jù)#命令 | A[25:0] | 地址線 |
D[17:0] | 數(shù)據(jù)引腳 | D[15:0] | 數(shù)據(jù)引腳 |
如圖可見,LCD的8080時序與FSMC寫NOR(模式B)時序近乎相同,數(shù)據(jù)引腳選用16位模式。
不同的則是FSMC沒有數(shù)據(jù)命令選擇引腳,只有地址線,我們可選擇地址線中的一根地址線充當數(shù)據(jù)命令控制引腳即可(0表示命令模式,1表示數(shù)據(jù)模式)。因此,只要配置好FSMC,便可模擬8080時序驅(qū)動LCD屏幕實現(xiàn)數(shù)據(jù)顯示。
3.1 FSMC設備地址

?????????如圖所示,NOR/PSRAM的地址范圍為 0x60000000~0x6FFFFFFF。NOR/PSRAM又分為4個存儲塊,如下圖所示,存儲塊的選擇由地址的26和27位控制。
60000000H二進制表示為:0110 0000 0000 0000 0000 0000 0000 0000
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [27:26]↑↑
HADDR[27:26]=00表示選擇了NOR/PSRAM 1,即起始地址為60000000H
HADDR[27:26]=01表示選擇了NOR/PSRAM 2,即起始地址為64000000H
HADDR[27:26]=10表示選擇了NOR/PSRAM 3,即起始地址為68000000H
HADDR[27:26]=11表示選擇了NOR/PSRAM 4,即起始地址為6C000000H
注意:NOR存儲區(qū)劃分了四個區(qū)并有四個專用的片選FSMC_NE[4:1]
那么就以為著我選擇NOR/PSRAM 1就需要使用FSMC_NE1對應的片選線。
?外部存儲地址:地址位對應地址線
?????????對于控制LCD屏而言,我們采取的數(shù)據(jù)寬度為16位即【RGB565】,地址線FSMC_A[24:0]對應著存儲器地址HADDR[25:1]。假設我們使用FSMC_A0地址線作為數(shù)據(jù)命令控制線,選擇的存儲塊為NOR/PSRAM 1 時,地址設置為0x60000000,地址線A0上的電平輸出為低電平,表示命令模式;地址設置為0x60000002,地址線A0上的電平輸出為高電平,表示數(shù)據(jù)模式。
? ? ? ? 而本次使用的開發(fā)板與8080數(shù)據(jù)命令引腳連接的引腳為PE2(FSMC_A23),對應地址位為HADDR[24]。片選引腳為FSMC_NE4,HADDR[27:26]=11。存儲塊為NOR/PSRAM 4 ,令地址位的第24位為0時表示命令模式,即0x6C000000;數(shù)據(jù)模式:0x6D000000。
?????????當我們在該地址上寫入數(shù)據(jù)時,F(xiàn)SMC便會控制數(shù)據(jù)線輸出對應的數(shù)據(jù),讀取數(shù)據(jù)時,也可直接讀取對應的地址即可。
3.2 FSMC-NOR/PSRAM配置
API(接口函數(shù)):
void FSMC_NORSRAMInit(FSMC_NORSRAMInitTypeDef* FSMC_NORSRAMInitStruct)
FSMC_NORSRAMInitTypeDef 初始化結(jié)構(gòu)體
結(jié)構(gòu)體原型:
/**
* @brief FSMC NOR/SRAM Init structure definition
*/
typedef struct
{
uint32_t FSMC_Bank; //選擇控制存儲塊
uint32_t FSMC_DataAddressMux; //地址總線與數(shù)據(jù)總線是否復用
uint32_t FSMC_MemoryType; //存儲器類型
uint32_t FSMC_MemoryDataWidth; //設置存儲器數(shù)據(jù)寬度
uint32_t FSMC_BurstAccessMode; //設置是否支持突發(fā)訪問模式
uint32_t FSMC_AsynchronousWait; //設置同步等待傳輸時的等待信號
uint32_t FSMC_WaitSignalPolarity;//設置等待信號極性
uint32_t FSMC_WrapMode; //設置是否支持對齊的突發(fā)模式
uint32_t FSMC_WaitSignalActive; //配置等待信號在等待前有效還是等待期間有效
uint32_t FSMC_WriteOperation; //設置寫使能
uint32_t FSMC_WaitSignal; //設置等待狀態(tài)插入使能
uint32_t FSMC_ExtendedMode; //設置擴展模式使能
uint32_t FSMC_WriteBurst; //設置突發(fā)模式使能
/*當不使用擴展模式時,本參數(shù)用于配置讀寫時序,否則用于配置讀時序*/
FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadWriteTimingStruct;
/*當使用擴展模式時,本參數(shù)用于配置寫時序*/
FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;
}FSMC_NORSRAMInitTypeDef;
FSMC_NORSRAMTimingInitTypeDef
時序結(jié)構(gòu)體:
/**
* @brief Timing parameters For NOR/SRAM Banks
*/
typedef struct
{
uint32_t FSMC_AddressSetupTime; //地址建立時間
uint32_t FSMC_AddressHoldTime; //地址保持時間
uint32_t FSMC_DataSetupTime; //數(shù)據(jù)建立時間
uint32_t FSMC_BusTurnAroundDuration; //總線轉(zhuǎn)換周期
uint32_t FSMC_CLKDivision; //時鐘分頻因子(異步模式下無效)
uint32_t FSMC_DataLatency; //數(shù)據(jù)延遲時間(異步模式下無效)
uint32_t FSMC_AccessMode; //設置訪問模式
}FSMC_NORSRAMTimingInitTypeDef;
3.3 配置FSMC
時鐘和中斷優(yōu)先級的配置都在main.c中做統(tǒng)一配置。
引腳配置:除了RES和BK引腳以為,其他引腳都配置為復用輸出模式。這里為了減少代碼行數(shù),就直接使用16進制代替GPIO_Pin。
void ILI9341_GPIO_Config(void)
{
//復位和背光引腳:通用推挽輸出
GPIO_InitTypeDef ILI9341_GPIO;
ILI9341_GPIO.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_11;
ILI9341_GPIO.GPIO_Mode = GPIO_Mode_Out_PP;
ILI9341_GPIO.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOG,&ILI9341_GPIO);
//數(shù)據(jù)引腳和控制引腳:復用推挽輸出
//GPIOD
ILI9341_GPIO.GPIO_Pin = 0xC733;
ILI9341_GPIO.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOD,&ILI9341_GPIO);
//GPIOE
ILI9341_GPIO.GPIO_Pin = 0xFF84;//PE 2、7~15
GPIO_Init(GPIOE,&ILI9341_GPIO);
//GPIOG
ILI9341_GPIO.GPIO_Pin = GPIO_Pin_12;
GPIO_Init(GPIOG,&ILI9341_GPIO);
}
FSMC配置:此處就不做過多解釋,詳情參考STM32F10x用戶手冊。
void ILI9341_FSMC_Config(void)
{
FSMC_NORSRAMDeInit(FSMC_Bank1_NORSRAM4); //復位存儲塊NOR/PSRAM 4
FSMC_NORSRAMInitTypeDef ili9341_FSMC={0}; //NOR初始化結(jié)構(gòu)體
/*時序配置*/
FSMC_NORSRAMTimingInitTypeDef FSMC_ReadWrite_Timing={0};//時序結(jié)構(gòu)體
FSMC_ReadWrite_Timing.FSMC_AddressSetupTime = 0x01;//地址建立時間
FSMC_ReadWrite_Timing.FSMC_DataSetupTime = 0x04;//數(shù)據(jù)建立時間
FSMC_ReadWrite_Timing.FSMC_AccessMode = FSMC_AccessMode_B;//訪問模式:模式B
/*以下配置與模式B無關(guān)*/
FSMC_ReadWrite_Timing.FSMC_AddressHoldTime = 0x00;//地址保持時間
//僅適用于總線復用模式的NOR閃存操作
FSMC_ReadWrite_Timing.FSMC_BusTurnAroundDuration = 0x00;//總線轉(zhuǎn)換周期
//在訪問異步NOR閃存、SRAM或ROM時,這個參數(shù)不起作用
FSMC_ReadWrite_Timing.FSMC_CLKDivision = 0x00;//時鐘分頻因子
FSMC_ReadWrite_Timing.FSMC_DataLatency = 0x00;//數(shù)據(jù)延遲時間
/*NOR初始化配置*/
ili9341_FSMC.FSMC_Bank = FSMC_Bank1_NORSRAM4; // NOR/PSRAM 4
ili9341_FSMC.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;//地址數(shù)據(jù)總線不復用
ili9341_FSMC.FSMC_MemoryType = FSMC_MemoryType_NOR;
ili9341_FSMC.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
ili9341_FSMC.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;//同步突發(fā)模式
ili9341_FSMC.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable;//不使能等待
ili9341_FSMC.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
ili9341_FSMC.FSMC_WrapMode = FSMC_WrapMode_Disable;//不支持對齊突發(fā)模式
ili9341_FSMC.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;//等待信號在等待前有效
ili9341_FSMC.FSMC_WriteOperation = FSMC_WriteOperation_Enable;//寫使能
ili9341_FSMC.FSMC_WaitSignal = FSMC_WaitSignal_Disable;//不使能等待狀態(tài)插入
ili9341_FSMC.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;//不使能擴展模式
ili9341_FSMC.FSMC_WriteBurst = FSMC_WriteBurst_Disable;//不使能寫突發(fā)模式
ili9341_FSMC.FSMC_ReadWriteTimingStruct = &FSMC_ReadWrite_Timing;
ili9341_FSMC.FSMC_WriteTimingStruct = &FSMC_ReadWrite_Timing;
FSMC_NORSRAMInit(&ili9341_FSMC);
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4,ENABLE);//使能FSMC
}
????????接下來就是寫數(shù)據(jù)、寫命令和讀數(shù)據(jù)函數(shù)。由于使用了FSMC外設,所以讀寫數(shù)據(jù)都可直接對地址操作。這里我定義的地址為:
#define FSMC_ADDR_CMD()?? ? *(volatile uint16_t *)0x6C000000
#define FSMC_ADDR_DATA()? ? *(volatile uint16_t *)0x6D000000
改地址為32位地址,因為我們讀取的數(shù)據(jù)位數(shù)為16位,所以做了個地址對齊并使用volatile對這段地址進行防止被優(yōu)化。再取個*表示值,可讀取和改變這個值。
/*
\brief: 寫指令
\param: cmd: ili9341控制指令
\retval: none
*/
void ILI9341_WriteCmd(uint16_t cmd)
{
FSMC_ADDR_CMD() = cmd;
}
/*
\brief: 寫數(shù)據(jù)
\param: data: 寫入的數(shù)據(jù)
\retval: none
*/
void ILI9341_WriteData(uint16_t data)
{
FSMC_ADDR_DATA() = data;
}
/*
\brief: 讀數(shù)據(jù)
\param: none
\retval: none
*/
uint16_t ILI9341_ReadData(void)
{
return FSMC_ADDR_DATA();
}
在此,對FSMC的操作已經(jīng)結(jié)束,重要的就是用到這三個函數(shù)對ILI9341進行讀寫操作,換言之,使用SPI也是用到讀寫函數(shù)。
4. 4線SPI
此處先略。
5. LCD配置
????????驅(qū)動LCD屏的關(guān)鍵是在屏幕任意位置畫一個點,相對于OLED的畫點只是一個位表示亮和不亮,這里的畫點,一個點表示一個16位的RGB像素點。
5.1 獲取LCD顯示屏ID
? ? ? ? ?讀取ID指令為04H,在未對屏幕進行任何配置前,可用該指令驗證編寫好的讀寫函數(shù)是否可行。
/*
\brief: 讀顯示ID信息
\param: none
\retval: ID信息
*/
uint16_t Read_LCD_ID(void)
{
uint16_t id=0;
ILI9341_WriteCmd(0x04);//讀顯示ID信息
ILI9341_ReadData();
ILI9341_ReadData();//LCD制造商ID
id = ILI9341_ReadData();//驅(qū)動版文號ID
id <<= 8;
id |= (ILI9341_ReadData()&0x00FF);//驅(qū)動ID
return id;
}
????????讀取ID信息,首先需要使用發(fā)送命令函數(shù)發(fā)送指令0x04,隨后直接讀取ID信息。這里我只需要后兩個ID數(shù)據(jù),前兩個字節(jié)數(shù)據(jù)就不做保存。ILI9341的16位ID號為9341,ST7789V的ID號為8552。
根據(jù)ID號配置LCD屏初始化序列,當然,在知道自己所用LCD型號時不需要根據(jù)ID配置。
/*
\brief: ILI9341初始化序列配置(寄存器配置)
\param: none
\retval: none
*/
void ILI9341_InitSequence(void)
{
if(Read_LCD_ID() == 0x9341)
{
/* Power control B (CFh) */
ILI9341_WriteCmd ( 0xCF );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x81 );
ILI9341_WriteData ( 0x30 );
/* Power on sequence control (EDh) */
ILI9341_WriteCmd ( 0xED );
ILI9341_WriteData ( 0x64 );
ILI9341_WriteData ( 0x03 );
ILI9341_WriteData ( 0x12 );
ILI9341_WriteData ( 0x81 );
/* Driver timing control A (E8h) */
ILI9341_WriteCmd ( 0xE8 );
ILI9341_WriteData ( 0x85 );
ILI9341_WriteData ( 0x10 );
ILI9341_WriteData ( 0x78 );
/* Power control A (CBh) */
ILI9341_WriteCmd ( 0xCB );
ILI9341_WriteData ( 0x39 );
ILI9341_WriteData ( 0x2C );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x34 );
//ILI9341_WriteData ( 0x02 );
ILI9341_WriteData ( 0x06 ); //原來是0x02改為0x06可防止液晶顯示白屏時有條紋的情況
/* Pump ratio control (F7h) */
ILI9341_WriteCmd ( 0xF7 );
ILI9341_WriteData ( 0x20 );
/* Driver timing control B */
ILI9341_WriteCmd ( 0xEA );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x00 );
/* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
ILI9341_WriteCmd ( 0xB1 );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x1B );
/* Display Function Control (B6h) */
ILI9341_WriteCmd ( 0xB6 );
ILI9341_WriteData ( 0x0A );
ILI9341_WriteData ( 0xA2 );
/* Power Control 1 (C0h) */
ILI9341_WriteCmd ( 0xC0 );
ILI9341_WriteData ( 0x35 );
/* Power Control 2 (C1h) */
ILI9341_WriteCmd ( 0xC1 );
ILI9341_WriteData ( 0x11 );
/* VCOM Control 1 (C5h) */
ILI9341_WriteCmd ( 0xC5 );
ILI9341_WriteData ( 0x45 );
ILI9341_WriteData ( 0x45 );
/* VCOM Control 2 (C7h) */
ILI9341_WriteCmd ( 0xC7 );
ILI9341_WriteData ( 0xA2 );
/* Enable 3G (F2h) */
ILI9341_WriteCmd ( 0xF2 );
ILI9341_WriteData ( 0x00 );
/* Gamma Set (26h) */
ILI9341_WriteCmd ( 0x26 );
ILI9341_WriteData ( 0x01 );
/* Positive Gamma Correction */
ILI9341_WriteCmd ( 0xE0 ); //Set Gamma
ILI9341_WriteData ( 0x0F );
ILI9341_WriteData ( 0x26 );
ILI9341_WriteData ( 0x24 );
ILI9341_WriteData ( 0x0B );
ILI9341_WriteData ( 0x0E );
ILI9341_WriteData ( 0x09 );
ILI9341_WriteData ( 0x54 );
ILI9341_WriteData ( 0xA8 );
ILI9341_WriteData ( 0x46 );
ILI9341_WriteData ( 0x0C );
ILI9341_WriteData ( 0x17 );
ILI9341_WriteData ( 0x09 );
ILI9341_WriteData ( 0x0F );
ILI9341_WriteData ( 0x07 );
ILI9341_WriteData ( 0x00 );
/* Negative Gamma Correction (E1h) */
ILI9341_WriteCmd ( 0XE1 ); //Set Gamma
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x19 );
ILI9341_WriteData ( 0x1B );
ILI9341_WriteData ( 0x04 );
ILI9341_WriteData ( 0x10 );
ILI9341_WriteData ( 0x07 );
ILI9341_WriteData ( 0x2A );
ILI9341_WriteData ( 0x47 );
ILI9341_WriteData ( 0x39 );
ILI9341_WriteData ( 0x03 );
ILI9341_WriteData ( 0x06 );
ILI9341_WriteData ( 0x06 );
ILI9341_WriteData ( 0x30 );
ILI9341_WriteData ( 0x38 );
ILI9341_WriteData ( 0x0F );
/* memory access control set */
ILI9341_WriteCmd ( 0x36 );
ILI9341_WriteData ( 0xC8 ); /*豎屏 左上角到 (起點)到右下角 (終點)掃描方式*/
/* column address control set */
ILI9341_WriteCmd ( 0x2A );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0xEF );
/* page address control set */
ILI9341_WriteCmd ( 0x2B );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x01 );
ILI9341_WriteData ( 0x3F );
/* Pixel Format Set (3Ah) */
ILI9341_WriteCmd ( 0x3a );
ILI9341_WriteData ( 0x55 );
/* Sleep Out (11h) */
ILI9341_WriteCmd ( 0x11 );
Delay_ms(120);
/* Display ON (29h) */
ILI9341_WriteCmd ( 0x29 );
}
if(Read_LCD_ID() == 0x8552)
{
/* Power control B (CFh) */
ILI9341_WriteCmd ( 0xCF );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0xC1 );
ILI9341_WriteData ( 0x30 );
/* Power on sequence control (EDh) */
ILI9341_WriteCmd ( 0xED );
ILI9341_WriteData ( 0x64 );
ILI9341_WriteData ( 0x03 );
ILI9341_WriteData ( 0x12 );
ILI9341_WriteData ( 0x81 );
/* Driver timing control A (E8h) */
ILI9341_WriteCmd ( 0xE8 );
ILI9341_WriteData ( 0x85 );
ILI9341_WriteData ( 0x10 );
ILI9341_WriteData ( 0x78 );
/* Power control A (CBh) */
ILI9341_WriteCmd ( 0xCB );
ILI9341_WriteData ( 0x39 );
ILI9341_WriteData ( 0x2C );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x34 );
ILI9341_WriteData ( 0x02 );
/* Pump ratio control (F7h) */
ILI9341_WriteCmd ( 0xF7 );
ILI9341_WriteData ( 0x20 );
/* Driver timing control B */
ILI9341_WriteCmd ( 0xEA );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x00 );
/* Power Control 1 (C0h) */
ILI9341_WriteCmd ( 0xC0 ); //Power control
ILI9341_WriteData ( 0x21 ); //VRH[5:0]
/* Power Control 2 (C1h) */
ILI9341_WriteCmd ( 0xC1 ); //Power control
ILI9341_WriteData ( 0x11 ); //SAP[2:0];BT[3:0]
/* VCOM Control 1 (C5h) */
ILI9341_WriteCmd ( 0xC5 );
ILI9341_WriteData ( 0x2D );
ILI9341_WriteData ( 0x33 );
/* VCOM Control 2 (C7h) */
// ILI9341_WriteCmd ( 0xC7 );
// ILI9341_WriteData ( 0XC0 );
/* memory access control set */
ILI9341_WriteCmd ( 0x36 ); //Memory Access Control
ILI9341_WriteData ( 0x00 ); /*豎屏 左上角到 (起點)到右下角 (終點)掃描方式*/
ILI9341_WriteCmd(0x3A);
ILI9341_WriteData(0x55);
/* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
ILI9341_WriteCmd ( 0xB1 );
ILI9341_WriteData ( 0x00 );
ILI9341_WriteData ( 0x17 );
/* Display Function Control (B6h) */
ILI9341_WriteCmd ( 0xB6 );
ILI9341_WriteData ( 0x0A );
ILI9341_WriteData ( 0xA2 );
ILI9341_WriteCmd(0xF6);
ILI9341_WriteData(0x01);
ILI9341_WriteData(0x30);
/* Enable 3G (F2h) */
ILI9341_WriteCmd ( 0xF2 );
ILI9341_WriteData ( 0x00 );
/* Gamma Set (26h) */
ILI9341_WriteCmd ( 0x26 );
ILI9341_WriteData ( 0x01 );
/* Positive Gamma Correction */
ILI9341_WriteCmd(0xe0); //Positive gamma
ILI9341_WriteData(0xd0);
ILI9341_WriteData(0x00);
ILI9341_WriteData(0x02);
ILI9341_WriteData(0x07);
ILI9341_WriteData(0x0b);
ILI9341_WriteData(0x1a);
ILI9341_WriteData(0x31);
ILI9341_WriteData(0x54);
ILI9341_WriteData(0x40);
ILI9341_WriteData(0x29);
ILI9341_WriteData(0x12);
ILI9341_WriteData(0x12);
ILI9341_WriteData(0x12);
ILI9341_WriteData(0x17);
/* Negative Gamma Correction (E1h) */
ILI9341_WriteCmd(0xe1); //Negative gamma
ILI9341_WriteData(0xd0);
ILI9341_WriteData(0x00);
ILI9341_WriteData(0x02);
ILI9341_WriteData(0x07);
ILI9341_WriteData(0x05);
ILI9341_WriteData(0x25);
ILI9341_WriteData(0x2d);
ILI9341_WriteData(0x44);
ILI9341_WriteData(0x45);
ILI9341_WriteData(0x1c);
ILI9341_WriteData(0x18);
ILI9341_WriteData(0x16);
ILI9341_WriteData(0x1c);
ILI9341_WriteData(0x1d);
/* Sleep Out (11h) */
ILI9341_WriteCmd ( 0x11 ); //Exit Sleep
Delay_ms(120);
/* Display ON (29h) */
ILI9341_WriteCmd ( 0x29 ); //Display on
ILI9341_WriteCmd(0x2c);
}
}
為了快速使用,直接復制官方提供的初始化序列。也可自行查看寄存器進行配置。
5.2 初始化LCD
/*
\brief: ILI9341驅(qū)動LCD屏初始化配置
\param: none
\retval: none
*/
void ILI9341_LCD_InitConfig(void)
{
ILI9341_GPIO_Config(); //引腳配置
ILI9341_FSMC_Config(); //FSMC外設配置
//復位
ILI9341_RES(Bit_RESET); //開始復位
Delay_ms(5);
ILI9341_RES(Bit_SET); //結(jié)束復位
Delay_ms(5);
ILI9341_InitSequence(); //配置初始化序列
ILI9341_BK(Bit_RESET); //打開背光
}
(1)配置GPIO工作模式;
(2)配置FSMC外設;
(3)復位LCD;
(4)配置LCD初始化序列;
(5)打開背光。
5.3 畫點函數(shù)(重要)
? ? ? ? 在控制LCD顯示時,沒有配置LCD存儲器的掃描方式,即保持初始化序列中的配置(從上到下,從左到右),(0,0)為屏幕左上角的頂點。
5.3.1 設置坐標
?????????SC[15:0]設置起始列地址;EC[15:0]設置結(jié)束列地址。列地址可以設置一個范圍,也可設置為指定一列。設置行地址(2BH)也是如此。
/*
\brief: 設置坐標
\param: x: 橫坐標
y: 列坐標
\retval: none
*/
void LCD_SetCoord(uint16_t x,uint16_t y)
{
ILI9341_WriteCmd(0x2A);//設置列地址
//起始地址
ILI9341_WriteData(x>>8);//高位
ILI9341_WriteData(x&0xFF);
//結(jié)束地址(與起始地址相同)
ILI9341_WriteData(x>>8);//高位
ILI9341_WriteData(x&0xFF);
ILI9341_WriteCmd(0x2B);//設置頁地址
//起始地址
ILI9341_WriteData(y>>8);//高位
ILI9341_WriteData(y&0xFF);
//結(jié)束地址(與起始地址相同)
ILI9341_WriteData(y>>8);//高位
ILI9341_WriteData(y&0xFF);
}
? ? ? ? 在設置行列地址時,將起始地址和結(jié)束地址設置為同一只,行列交叉的點便是設置的坐標點。
5.3.2 畫點函數(shù)
? ? ? ? 在畫一個點前,首先需要給定一個坐標點,然后再把像素點畫上去。當然,不能直接使用寫數(shù)據(jù)函數(shù)直接把像素點畫上去,還得發(fā)送寫存儲器指令。
/*
\brief: 畫點函數(shù)(在屏幕的任意位置畫一個像素)
\param: x: 橫坐標
y: 列坐標
colour: 顏色(RGB565)
\retval: none
*/
void LCD_DrawDot(uint16_t x,uint16_t y,uint16_t colour)
{
if(x+1>LCD_WIDTH || y+1>LCD_HIGH) return ;//超出屏幕范圍
LCD_SetCoord(x,y);//設置坐標
ILI9341_WriteCmd(0x2C);
ILI9341_WriteData(colour);//繪制一個像素點
}
5.4 清屏函數(shù)
? ? ? ? 將屏幕清成一個顏色,這里使用的畫點函數(shù),將屏幕上的240x320個像素點逐一畫上一個點。
void LCD_Clear(uint16_t colour)
{
uint16_t i,j;
for(i=0;i<LCD_WIDTH;i++)
{
for(j=0;j<LCD_HIGH;j++)
{
LCD_DrawDot(i,j,colour);
}
}
}
????????當然,這個清屏函數(shù)會很慢,再調(diào)用這個函數(shù)清屏時,會重復發(fā)送設置坐標函數(shù)和寫儲存器指令??煲稽c的方法就是在設置行列坐標時,直接將坐標設置為一整個屏幕范圍,再發(fā)送一次寫存儲器指令,然后直接寫像素點到屏幕上即可??墒∪シ磸驮O置坐標的時間。
void LCD_Clear(uint16_t colour)
{
uint16_t i,j;
ILI9341_WriteCmd(0x2A);//設置列地址
//起始地址
ILI9341_WriteData(0>>8);//高位
ILI9341_WriteData(0&0xFF);
//結(jié)束地址
ILI9341_WriteData(239>>8);//高位
ILI9341_WriteData(239&0xFF);
ILI9341_WriteCmd(0x2B);//設置頁地址
//起始地址
ILI9341_WriteData(0>>8);//高位
ILI9341_WriteData(0&0xFF);
//結(jié)束地址
ILI9341_WriteData(319>>8);//高位
ILI9341_WriteData(319&0xFF);
ILI9341_WriteCmd(0x2C);//寫存儲
for(i=0;i<LCD_WIDTH;i++)
{
for(j=0;j<LCD_HIGH;j++)
{
ILI9341_WriteData(colour);//繪制一個像素點
}
}
}
6. 顯示字符串
? ? ? ? 畫點函數(shù)編寫完成,便可根據(jù)該函數(shù)封裝各種顯示函數(shù),這里我先編寫顯示字符串函數(shù)用于測試。隨后可編寫顯示漢字,一直畫直線畫園等函數(shù)。
6.1 取模
? ? ? ? ?在編寫顯示函數(shù)前,先制作ascii碼字庫,然后根據(jù)字庫的顯示配置編寫顯示函數(shù)。這里采取的陽碼格式,1為點亮(1畫一個點,0不畫點);高位在前行列式,先在一列中畫一個字節(jié)像素點,高位在最前面,隨后繪制下一列。
6.2 顯示字符
/*
\brief: 在屏幕任意位置顯示字符(行列式,高位在前)
\param: x:橫坐標,y:縱坐標
w:字符寬度 h字符高度 colour:顯示顏色
\retval: none
*/
void LCD_DisplayChar(uint16_t x,uint16_t y,uint16_t w,uint8_t h,uint8_t c,uint16_t colour)
{
uint16_t x0=x;//記錄初始位置
uint16_t y0=y;
uint8_t temp;
uint16_t i,j;
for(i=0;i<(w*h/8);i++)//計算字節(jié)數(shù)
{
//從字庫里讀取一字節(jié)
switch(w)
{
case 8:temp = ascii_8x16[c-' '][i];break;
case 16:break;
default :temp = ascii_8x16[c-' '][i];break;
}
for(j=0;j<8;j++)
{
if(temp & 0x80) //高位在前
{
LCD_DrawDot(x,y,colour);
}
temp <<=1;
y++;
}
x++;
if(x-x0==w)
{
y0 += 8;
x = x0;
}
y = y0;
}
}
? ? ? ? 例:一個8x16的字符生成的字庫為16個字節(jié),排布順序為行列式高位在前,陽碼。所以我們在編寫函數(shù)時可按照分析字節(jié)的方式對一個坐標的判斷是否繪制,繪制完一個字節(jié)表示畫完一列的8個像素,可以再對下一列進行畫點。同時判斷畫的列數(shù)是否等于字符寬度:這里字符寬度為8,高度16,從開始畫點開始一共畫了8列8個字節(jié)時,再從該字符的起始橫坐標開始,縱坐標向下偏移一個字節(jié)再畫剩下的8個字節(jié)。對不同的字符大小,可根據(jù)判斷字符大小偏移坐標。
(1)使用x0,y0保存起始坐標,防止坐標偏移以后找不到了。
(2)定義一個uint8_t類型的變量temp保存要繪制的一個字節(jié)數(shù)據(jù)。
(3)定義變量i用于記錄字節(jié)數(shù),j用于記錄字節(jié)位。
(4)首先根據(jù)字符寬度選擇對應的字庫,ascii的寬度是高的一半,c-' '就是該字符在數(shù)組中的位置,用temp保存字節(jié)用于顯示。
(5)取模時高位在前,先對高位進行判斷,為真則畫一個點,縱坐標+1,接下來判斷下一位,直到一個字節(jié)都畫完。
(6)橫坐標+1,并判斷畫的橫坐標是否與寬度相同。不相同則將縱坐標恢復到起始值,相同則需將橫坐標恢復至起始值并改變縱坐標的起始值(向下偏移一個字節(jié)),在將縱坐標恢復至改變的起始站。反復將所有一個像素點的所有字節(jié)都畫完即可。
調(diào)用該函數(shù)就可以在屏幕任意位置繪制帶顏色的字符,字符大小可改變,需要自己將ascii碼取模,保存到工程中在用temp去取即可。
6.3 顯示字符串
????????這個函數(shù)就比較簡單了,當我們可以在屏幕上繪制一個字符時,就可以在此基礎上繪制多個。
void LCD_DisplayString(uint16_t x,uint16_t y,uint16_t w,uint8_t h,uint8_t *pstr,uint16_t colour)
{
uint8_t *p=pstr;
while(*p != '\0')
{
LCD_DisplayChar(x,y,w,h,*p,colour);
x += w;
p++;//取下一個字符數(shù)據(jù)
}
}
這里傳入的是一個uint8_t *pstr,可以理解為一個字符串的首地址??梢愿鶕?jù)這個首地址訪問到字符串中的所有字符,在調(diào)用顯示字符函數(shù)足以顯示即可。
(1)定義一個指針指向這個字符串的首地址,一般不要直接使用傳入的首地址,因為在下面的指針偏移中會改變指針的指向。
(2)字符串以‘\0’結(jié)束,只要等于這個值可以理解為字符串已經(jīng)全部繪制完成。
(3)繪制一個字符串,橫坐標偏移一個字符寬度,防止下一個字符與當前字符重合。
由于屏幕寬度有限,當縱坐標超過屏幕范圍,將字符將不會顯示到屏幕上,可修改函數(shù),在屏幕剩余橫坐標不足顯示一個字符時換一行顯示。
7. 顯示
????????在屏幕上顯示"hello world !",字體顏色紅色,屏幕背景顏色綠色。
int main(void)
{
CLOCK_Config(); //配置外設時鐘
NVIC_Config(); //中斷優(yōu)先級配置
ILI9341_LCD_InitConfig();
LCD_Clear(YELLOW);//將屏幕清為綠色
LCD_DisplayString(10,0,8,16,(uint8_t *)"hello world !",RED);//從(10,0)坐標開始顯示字符串
while(1)
{
}
}
LCD顯示效果:

?8. 顯示中文
? ? ? ? 由于STM32的能存儲的數(shù)據(jù)有限,可將幾個漢字取模保存到flash中,無法保存一個漢字字庫,下篇將講述從外部flash中讀取數(shù)據(jù)至LCD顯示。
附件:ILI9341顯示屏驅(qū)動工程
鏈接:https://pan.baidu.com/s/1R919i2Lh0lL-YUvKrh96sg?pwd=1234?
提取碼:1234文章來源:http://www.zghlxwxcb.cn/news/detail-661195.html
2023/07/15文章來源地址http://www.zghlxwxcb.cn/news/detail-661195.html
到了這里,關(guān)于【STM32篇】驅(qū)動LCD顯示屏的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!