STM32驅動ILI9341控制器控制TFTLCD顯示
一、用STM32控制TFTLCD顯示的編程方法,在編程驅動TFTLCD液晶顯示器之前,我們先熟悉以下概念:
1、色彩深度,這是一個與TFTLCD顯存對應的概念;所謂色彩深度就是每個像素點需要多少位的RGB
數(shù)據(jù)表示該點的顏色信息。注意,不同的TFTLCD顯示器的RGB的對應關系不一樣,這個可以在LCD
控制芯片手冊中找到答案。
例: 某LCD顯示支持8、16、24位RGB,這些位數(shù)是指該像素點顏色由8、16、24位RGB構成,但是
RGB三種顏色各占的位數(shù)可以查看數(shù)據(jù)手冊。
2、TFTLCD的操作分為兩種:
A、對控制寄存器的讀寫操作(即程序員將要操作LCD顯存寄存器的地址設置成可讀或者可寫)。
B、對顯存寄存器的讀寫操作(即讀寫LCD顯存寄存器)。
3、TFTLCD有一個索引寄存器,對控制寄存器操作前,需要對索引寄存器進行定入操作,用以指明
寄存器讀寫是針對那個寄存器的,具體操作步驟如下:
RS為低電平狀態(tài)下,寫入兩個字節(jié)的數(shù)據(jù),第一個字節(jié)為零,第二個字節(jié)為寄存器索引值。
RS為高電平狀態(tài)下,讀取兩個字節(jié)數(shù)據(jù),第一個字節(jié)為高八位,第二個字節(jié)為低八位。
二、實驗平臺STM32F103RCT6與ILI9341 TFTLCD驅動模塊
硬件采用 16 位的并方式與外部連接,之所以不采用 8 位的方式,是因為彩屏的數(shù)據(jù)量比較大,
尤其在顯示圖片的時候,如果用 8 位數(shù)據(jù)線,就會比 16 位方式慢一倍以上,我們當然希望速
度越快越好,所以我們選擇 16 位的80 并口。有如下一些信號線:
CS:TFTLCD 片選信號。
WR:向 TFTLCD 寫入數(shù)據(jù)。
RD:從 TFTLCD 讀取數(shù)據(jù)。
D[15:0]:16 位雙向數(shù)據(jù)線。
RST:硬復位 TFTLCD。
RS:命令/數(shù)據(jù)標志(0,讀寫命令;1,讀寫數(shù)據(jù))。
在 16 位模式下,ILI9341 采用 RGB565 格式存儲顏色數(shù)據(jù),接下來看一下ILI9341 的幾個重要命令
1、0XD3,用于讀取 LCD 控制器的 ID。
2、0X36,這是存儲訪問控制指令,可以控制 ILI9341 存儲器的讀寫方向,簡單的說,就是在連續(xù)寫
GRAM 的時候,可以控制 GRAM 指針的增長方向,從而控制顯示方式。
3、0X2A,這是列地址設置指令,在從左到右,從上到下的掃描方式(默認)下面,該指令用于設置
橫坐標(x 坐標)。
4、0X2B,是頁地址設置指令,在從左到右,從上到下的掃描方式(默認)下面,該指令用于設置縱
坐標(y 坐標)。
5、0X2C,該指令是寫 GRAM 指令,在發(fā)送該指令之后,我們便可以往 LCD的 GRAM 里面寫入顏色
數(shù)據(jù)了,該指令支持連續(xù)寫,在收到指令 0X2C 之后,數(shù)據(jù)有效位寬變?yōu)?16 位,我們可以連續(xù)寫入
LCDGRAM 值,而 GRAM 的地址將根據(jù) MY/MX/MV 設置的掃描方向進行自增。
6、0X2E, 該指令是讀 GRAM 指令,用于讀取 ILI9341 的顯存(GRAM)。
三、軟件編程
lcd.h 里面的一個重要結構體:
//LCD重要參數(shù)集
typedef struct
{
u16 width;//LCD 寬度
u16 height;//LCD 高度
u16 id;//LCD ID
u8 dir;//橫屏還是豎屏控制:0,豎屏;1,橫屏。
u16wramcmd;//開始寫gram指令
u16 setxcmd;//設置x坐標指令
u16 setycmd;//設置y坐標指令
}_lcd_dev;
//LCD參數(shù)
extern _lcd_dev lcddev; //管理LCD重要參數(shù)
該結構體用于保存一些 LCD 重要參數(shù)信息,比如 LCD 的長寬、LCD ID(驅動 IC 型號)、
LCD 橫豎屏狀態(tài)等,這個結構體雖然占用了 14 個字節(jié)的內存,但是卻可以讓我們的驅動函數(shù)
支持不同尺寸的 LCD,同時可以實現(xiàn) LCD 橫豎屏切換等重要功能,所以還是利大于弊的。有
了以上了解,下面我們開始介紹 ILI93xx.c 里面的一些重要函數(shù)。
第一個是 LCD_WR_DATA 函數(shù),該函數(shù)在 lcd.h 里面,通過宏定義的方式申明。該函數(shù)通
過 80 并口向 LCD 模塊寫入一個 16 位的數(shù)據(jù),使用頻率是最高的,這里我們采用了宏定義的方
式,以提高速度。其代碼如下
//寫數(shù)據(jù)函數(shù)
#define LCD_WR_DATA(data){
LCD_RS_SET;
LCD_CS_CLR;
DATAOUT(data);
LCD_WR_CLR;
LCD_WR_SET;
LCD_CS_SET;
}
//寫數(shù)據(jù)函數(shù)
//可以替代LCD_WR_DATAX宏,拿時間換空間.
//data:寄存器值
void LCD_WR_DATAX(u16 data)
{
LCD_RS_SET;
LCD_CS_CLR;
DATAOUT(data);
LCD_WR_CLR;
LCD_WR_SET;
LCD_CS_SET;
}
第三個是 LCD_WR_REG 函數(shù),該函數(shù)是通過 8080 并口向 LCD 模塊寫入寄存器命令,因
為該函數(shù)使用頻率不是很高,我們不采用宏定義來做(宏定義占用 FLASH 較多),通過 LCD_RS
來標記是寫入命令(LCD_RS=0)還是數(shù)據(jù)(LCD_RS=1)。該函數(shù)代碼如下:
//寫寄存器函數(shù)
//data:寄存器值
void LCD_WR_REG(u16 data)
{
LCD_RS_CLR;//寫地址
LCD_CS_CLR;
DATAOUT(data);
LCD_WR_CLR;
LCD_WR_SET;
LCD_CS_SET;
}
既然有寫寄存器命令函數(shù),那就有讀寄存器數(shù)據(jù)函數(shù)。接下來介紹 LCD_RD_DATA 函數(shù),
該函數(shù)用來讀取 LCD 控制器的寄存器數(shù)據(jù)(非 GRAM 數(shù)據(jù)),該函數(shù)代碼如下:
//讀LCD數(shù)據(jù)
//返回值:讀到的值
u16 LCD_RD_DATA(void)
{
u16 t;
GPIOB->CRL=0X88888888; //PB0-7 上拉輸入
GPIOB->CRH=0X88888888; //PB8-15 上拉輸入
GPIOB->ODR=0X0000; //全部輸出0
LCD_RS_SET;
LCD_CS_CLR;
//讀取數(shù)據(jù)(讀寄存器時,并不需要讀2次)
LCD_RD_CLR;
if(lcddev.id==0X8989)delay_us(2);//FOR 8989,延時2us
t=DATAIN;
LCD_RD_SET;
LCD_CS_SET;
GPIOB->CRL=0X33333333; //PB0-7 上拉輸出
GPIOB->CRH=0X33333333; //PB8-15 上拉輸出
GPIOB->ODR=0XFFFF; //全部輸出高
return t;
}
以上 4 個函數(shù),用于實現(xiàn) LCD 基本的讀寫操作,接下來,我們介紹 2 個 LCD 寄存器操作
的函數(shù),LCD_WriteReg 和 LCD_ReadReg,這兩個函數(shù)代碼如下:
//寫寄存器
//寫寄存器
//LCD_Reg:寄存器編號
//LCD_RegValue:要寫入的值
void LCD_WriteReg(u16 LCD_Reg,u16 LCD_RegValue)
{
LCD_WR_REG(LCD_Reg);
LCD_WR_DATA(LCD_RegValue);
}
//讀寄存器
//LCD_Reg:寄存器編號
//返回值:讀到的值
u16 LCD_ReadReg(u16 LCD_Reg)
{
LCD_WR_REG(LCD_Reg); //寫入要讀的寄存器號
return LCD_RD_DATA();
}
這兩個函數(shù)函數(shù)十分簡單,LCD_WriteReg 用于向 LCD 指定寄存器寫入指定數(shù)據(jù),而
LCD_ReadReg 則用于讀取指定寄存器的數(shù)據(jù),這兩個函數(shù),都只帶一個參數(shù)/返回值,所以,
在有多個參數(shù)操作(讀取/寫入)的時候,就不適合用這兩個函數(shù)了,得另外實現(xiàn)。
第七個要介紹的函數(shù)是坐標設置函數(shù),該函數(shù)代碼如下:
//設置光標位置
//Xpos:橫坐標
//Ypos:縱坐標
void LCD_SetCursor(u16 Xpos, u16 Ypos)
{
if(lcddev.id==0X9341||lcddev.id==0X5310)
{
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(Xpos>>8);
LCD_WR_DATA(Xpos&0XFF);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(Ypos>>8);
LCD_WR_DATA(Ypos&0XFF);
}else if(lcddev.id==0X6804)
{
if(lcddev.dir==1)Xpos=lcddev.width-1-Xpos;//橫屏時處理
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(Xpos>>8);
LCD_WR_DATA(Xpos&0XFF);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(Ypos>>8);
LCD_WR_DATA(Ypos&0XFF);
}else if(lcddev.id==0X5510)
{
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(Xpos>>8);
LCD_WR_REG(lcddev.setxcmd+1);
LCD_WR_DATA(Xpos&0XFF);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(Ypos>>8);
LCD_WR_REG(lcddev.setycmd+1);
LCD_WR_DATA(Ypos&0XFF);
}else
{
if(lcddev.dir==1) Xpos=lcddev.width-1-Xpos;//橫屏其實就是調轉x,y坐標
LCD_WriteReg(lcddev.setxcmd, Xpos);
LCD_WriteReg(lcddev.setycmd, Ypos);
}
}
該函數(shù)實現(xiàn)將 LCD 的當前操作點設置到指定坐標(x,y)。因為不同 LCD 的設置方式不一定
完全一樣,所以代碼里面有好幾個判斷,對不同的驅動 IC 進行不同的設置。
接下來我們介紹第八個函數(shù):畫點函數(shù)。該函數(shù)實現(xiàn)代碼如下:
//畫點
//x,y:坐標
//POINT_COLOR:此點的顏色
void LCD_DrawPoint(u16 x,u16 y)
{
LCD_SetCursor(x,y);//設置光標位置
LCD_WriteRAM_Prepare();//開始寫入GRAM
LCD_WR_DATA(POINT_COLOR);
}
該函數(shù)實現(xiàn)比較簡單,就是先設置坐標,然后往坐標寫顏色。其中 POINT_COLOR 是我們
定義的一個全局變量,用于存放畫筆顏色,順帶介紹一下另外一個全局變量: BACK_COLOR,
該變量代表 LCD 的背景色。LCD_DrawPoint 函數(shù)雖然簡單,但是至關重要,其他幾乎所有上
層函數(shù),都是通過調用這個函數(shù)實現(xiàn)的。
有了畫點,當然還需要有讀點的函數(shù),第九個介紹的函數(shù)就是讀點函數(shù),用于讀取 LCD
的 GRAM, 這里說明一下,為什么 OLED 模塊沒做讀 GRAM 的函數(shù),而這里做了。因為 OLED
模塊是單色的,所需要全部 GRAM 也就 1K 個字節(jié),而 TFTLCD 模塊為彩色的,點數(shù)也比 OLED
模塊多很多,以 16 位色計算, 一款 320×240 的液晶,需要 320×240×2 個字節(jié)來存儲顏色值,
也就是也需要 150K 字節(jié),這對任何一款單片機來說,都不是一個小數(shù)目了。而且我們在圖形
疊加的時候,可以先讀回原來的值,然后寫入新的值,在完成疊加后,我們又恢復原來的值。
這樣在做一些簡單菜單的時候,是很有用的。這里我們讀取 TFTLCD 模塊數(shù)據(jù)的函數(shù)為
LCD_ReadPoint,該函數(shù)直接返回讀到的 GRAM 值。該函數(shù)使用之前要先設置讀取的 GRAM
地址,通過 LCD_SetCursor 函數(shù)來實現(xiàn)。LCD_ReadPoint 的代碼如下:
//讀取個某點的顏色值
//x,y:坐標
//返回值:此點的顏色
u16 LCD_ReadPoint(u16 x,u16 y)
{
u16 r,g,b;
if(x>=lcddev.width||y>=lcddev.height)return 0;//超過了范圍,直接返回
LCD_SetCursor(x,y);
if(lcddev.id==0X9341||lcddev.id==0X6804||lcddev.id==0X5310)LCD_WR_REG(0X2E);//9341/6804/5310發(fā)送讀GRAM指令
else if(lcddev.id==0X5510)LCD_WR_REG(0X2E00);//5510 發(fā)送讀GRAM指令
else LCD_WR_REG(R34); //其他IC發(fā)送讀GRAM指令
GPIOB->CRL=0X88888888; //PB0-7 上拉輸入
GPIOB->CRH=0X88888888; //PB8-15 上拉輸入
GPIOB->ODR=0XFFFF; //全部輸出高
LCD_RS_SET;
LCD_CS_CLR;
//讀取數(shù)據(jù)(讀GRAM時,第一次為假讀)
LCD_RD_CLR;
delay_us(1);//延時1us
LCD_RD_SET;
//dummy READ
LCD_RD_CLR;
delay_us(1);//延時1us
r=DATAIN; //實際坐標顏色
LCD_RD_SET;
if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)//9341/NT35310/NT35510要分2次讀出
{
LCD_RD_CLR;
b=DATAIN;//讀取藍色值
LCD_RD_SET;
g=r&0XFF;//對于9341,第一次讀取的是RG的值,R在前,G在后,各占8位
g<<=8;
}else if(lcddev.id==0X6804)
{
LCD_RD_CLR;
LCD_RD_SET;
r=DATAIN;//6804第二次讀取的才是真實值
}
LCD_CS_SET;
GPIOB->CRL=0X33333333;//PB0-7 上拉輸出
GPIOB->CRH=0X33333333;//PB8-15 上拉輸出
GPIOB->ODR=0XFFFF; //全部輸出高
if(lcddev.id==0X9325||lcddev.id==0X4535||lcddev.id==0X4531||lcddev.id==0X8989||lcddev.id==0XB505)





