學習筆記:12864液晶模組的詳細使用

Airbnb發表於2015-04-17

學習筆記:12864液晶模組的詳細使用

 (2012-10-17 09:23:32)
備註:這篇文章是對12864操作的具體介紹,僅限剛接觸12864的新手,大神請拍磚,文章寫的較散,建議先參考12864手冊及控制驅動器ST7920英文手冊有個初步理解之後再閱讀該篇文章,將會有更深的認識。強烈建議閱讀ST7920英文手冊,細節內容裡面有詳細介紹,中文的12864也是從中譯過來的。

本文分三個步驟介紹12864的內部資源原理,指令集詳細講解,以及應用例子。

對12864的所有操作概括起來有4種:
1)、讀忙狀態(同時讀出指標地址內容),初始化之後每次對12864的讀寫均要進行忙檢測。
2)、寫命令:所有的命令可以檢視指令表,後續講解指令的詳細用法。寫地址也是寫指令。
3)、寫資料:操作物件有DDRAM、CGRAM、GDRAM。
4)、讀資料:操作物件也是DDRAM、CGRAM、GDRAM。
 
學習筆記:12864液晶模組的詳細使用


對12864的學習首相要了解其內部資源,知道了它裡面有哪些東西,你就可以更加方便的使用它。

先介紹幾個英文的名字:
DDRAM:(Data Display Ram),資料顯示RAM,往裡面寫啥,螢幕就會顯示啥。
CGROM:(Character Generation ROM),字元發生ROM。裡面儲存了中文漢字的字模,也稱作中文字型檔,編碼方式有GB2312(中文簡體)和BIG5(中文繁體)。筆者使用的是育鬆電子的QC12864B,講解以此為例。
CGRAM:(Character Generation RAM),字元發生RAM,,12864內部提供了64×2B的CGRAM,可用於使用者自定義4個16×16字元,每個字元佔用32個位元組。
GDRAM:(Graphic Display RAM):圖形顯示RAM,這一塊區域用於繪圖,往裡面寫啥,螢幕就會顯示啥,它與DDRAM的區別在於,往DDRAM中寫的資料是字元的編碼,字元的顯示先是在CGROM中找到字模,然後對映到螢幕上,而往GDRAM中寫的資料時圖形的點陣資訊,每個點用1bit來儲存其顯示與否。
HCGROM:(Half height Character Generation ROM):半寬字元發生器,就是字母與數字,也就是ASCII碼。
至於ICON RAM(IRAM):貌似市場上的12864沒有該項功能,筆者也沒有找到它的應用資料,所以不作介紹。

下面就圍繞著上面列舉的這列資源展開對12864的講解:
DDRAM:
    筆者使用的這塊12864內部有4行×32位元組的DDRAM空間。但是某一時刻,螢幕只能顯示2行×32位元組的空間,那麼剩餘的這些空間呢?它們可以用於快取,在實現卷屏顯示時這些空間就派上用場了。
    DDRAM結構如下所示:
80H、81H、82H、83H、84H、85H、86H、87H88H、89H、8AH、8BH、8CH、8DH、8EH、8FH
90H、91H、92H、93H、94H、95H、96H、97H98H、99H、9AH、9BH、9CH、9DH、9EH、9FH
A0H、A1H、A2H、A3H、A4H、A5H、A6H、A7HA8H、A9H、AAH、ABH、ACH、ADH、AEH、AFH
B0H、B1H、B2H、B3H、B4H、B5H、B6H、B7HB8H、B9H、BAH、BBH、BCH、BDH、BEH、BFH

地址與螢幕顯示對應關係如下:
第一行:80H、81H、82H、83H、84H、85H、86H、87H
第二行:90H、91H、92H、93H、94H、95H、96H、97H 
第三行:88H、89H、8AH、8BH、8CH、8DH、8EH、8FH
第四行:98H、99H、9AH、9BH、9CH、9DH、9EH、9FH

說明:紅色部分的資料歸上半屏顯示,綠色部分的資料歸下半屏顯示。一般我們用於顯示字元使用的是上面兩行的空間,也就是80H~8FH,90H~9FH,每個地址的空間是2個位元組,也就是1個字,所以可以用於儲存字元編碼的空間總共是128位元組。因為每個漢字的編碼是2個位元組,所以每個地址需要使用2個位元組來儲存一個漢字。當然如果將2個位元組拆開來使用也可以,那就是顯示2個半寬字元。
    DDRAM內部儲存的資料是字元的編碼,可以寫入的編碼有ASCII碼、GB2312碼、BIG5碼。筆者使用的12864字型檔貌似不太全,字元“數”都無法顯示,而是顯示其他字元。如果顯示長篇漢字文章就不太適合吧。
    DDRAM資料讀寫:
    所有的資料讀寫都是先送地址,然後進行讀寫。對DDRAM寫資料時,確保在基本指令集下(使用指令0x30開啟),然後寫入地址,之後連續寫入兩個位元組的資料。讀資料時,在基本指令集下先寫地址,然後假讀一次,之後再連續讀2個位元組的資料,讀完之後地址指標自動加一,跳到下一個字,若需要讀下一個字的內容,只需再執行連續讀2個位元組的資料。這裡的假讀需要注意,不光是讀CGRAM需要假讀,讀其他的GDRAM、DDRAM都需要先假讀一次,之後的讀才是真讀,假讀就是讀一次資料,但不儲存該資料,也就是說送地址之後第一次讀的資料時錯誤的,之後的資料才是正確的。(dummy為假讀)
學習筆記:12864液晶模組的詳細使用

    關於編碼在DDRAM中的儲存需要說明事項如下:
    1)、每次對DDRAM的操作單位是一個字,也就是2個位元組,當往DDRAM寫入資料時,首先寫地址,然後連續送入2個位元組的資料,先送高位元組資料,再送低位元組資料。讀資料時也是如此,先寫地址,然後讀出高位元組資料,再讀出低位元組資料(讀資料時注意先假讀一次)。
    2)、顯示ASCII碼半寬字元時,往每個地址送入2個位元組的ASCII編碼,對應螢幕上的位置就會顯示2個半寬字元,左邊的為高位元組字元,右邊的為低位元組字元。
    3)、顯示漢字時,漢字編碼的2個位元組必須儲存在同一地址空間中,不能分開放在2個地址存放,否則顯示的就不是你想要的字元。每個字中的2個位元組自動結合查詢字模並顯示字元。所以,如果我們往一個地址中寫入的是一個漢字的2位元組編碼就會正確顯示該字元,編碼高位元組存放在前一地址低位元組,編碼低位元組存放在後一地址高位元組,顯然他們就不會結合查詢字模,而是與各地址相應位元組結合查詢字模。
學習筆記:12864液晶模組的詳細使用

    4)、因為控制器ST7920提供了4個自定義字元,所以這4個自定義字元也是可以顯示出來的,同樣這4個自定義字元也是採用編碼的方式,但是這4個字元的編碼是固定的,分別是0000H,0002H,0004H,0006H。如下圖所示:
學習筆記:12864液晶模組的詳細使用

上圖只是把2個字元的CGRAM空間畫出來,後續還有2個字元。可以看到每個字元都有16行16列,每一行使用2個位元組,因此一個字元佔用的空間是32位元組,地址是6位的,4個字元的地址分別是:00H~0FH、10H~1FH、20H~2FH、30H~3FH。編碼使用2個位元組,可以看到有2個位是任意的,說明其實這4個字元的編碼可以有多個,只是我們常用前面列舉的4個編碼。

CGRAM: (資料讀寫)
    CGRAM的結構就是上面所示了,這裡再補充一些讀寫CGRAM的內容,讀寫之前先寫地址,寫CGRAM的指令為0x40+地址。但是我們寫地址時只需要寫第一行的地址,例如第一個字元就是0x40+00H,然後連續寫入2個位元組的資料,之後地址指標會自動加一,跳到下一行的地址,然後再寫入2個位元組的資料。其實程式設計實現就是寫入地址,然後連續寫入32個位元組的資料。讀資料也是先寫首地址,然後假讀一次,接著連續讀32個位元組的資料。

GDRAM:(繪圖顯示RAM)
繪圖RAM的空間結構如下圖所示:
學習筆記:12864液晶模組的詳細使用

這些都是點陣,繪圖RAM就是給這些點陣置1或置0,可以看到其實它本來是32行×256列的,但是分成了上下兩屏顯示,每個點對應了螢幕上的一個點。要使用繪圖功能需要開啟擴充套件指令。然後寫地址,再讀寫資料。
    GDRAM的讀寫:
    首先說明對GDRAM的操作基本單位是一個字,也就是2個位元組,就是說讀寫GDRAM時一次最少寫2個位元組,一次最少讀2個位元組。
    寫資料:先開啟擴充套件指令集(0x36),然後送地址,這裡的地址與DDRAM中的略有不同,DDRAM中的地址只有一個,那就是字地址。而GDRAM中的地址有2個,分別是字地址(列地址/水平地址X)和位地址(行地址/垂直地址Y),上圖中的垂直地址就是00H~31H,水平地址就是00H~15H,寫地址時先寫垂直地址(行地址)再寫水平地址(列地址),也就是連續寫入兩個地址,然後再連續寫入2個位元組的資料。如圖中所示,左邊為高位元組右邊為低位元組。為1的點被描黑,為0的點則顯示空白。這裡列舉個寫地址的例子:寫GDRAM地址指令是0x80+地址。被加上的地址就是上面列舉的X和Y,假設我們要寫第一行的2個位元組,那麼寫入地址就是0x00H(寫行地址)然後寫0x80H(列地址),之後才連續寫入2個位元組的資料(先高位元組後低位元組)。再如寫螢幕右下角的2個位元組,先寫行地址0x9F(0x80+32),再寫列地址0x8F(0x80+15),然後連續寫入2個位元組的資料。程式設計中寫地址函式中直接用引數(0x+32),而不必自己相加。
    讀資料:先開啟擴充套件指令集,然後寫行地址、寫列地址,假讀一次,再連續讀2位元組的資料(先高位元組後低位元組)。

讀寫時序:
讀寫時序圖如下:(上圖為寫,下圖為讀)
學習筆記:12864液晶模組的詳細使用

學習筆記:12864液晶模組的詳細使用

時序圖中的訊號引腳就是12864主要的引腳,分別是:
RS:命令/資料暫存器選擇端
WR:讀寫控制端
E:使能端
DB7~DB0:資料端
 
所有對12864的操作都是圍繞著幾根引腳展開的。包括寫命令、寫資料、讀資料、讀狀態就是通過這些引腳的高低電平搭配來實現的。

根據時序圖可以編寫相應的寫命令函式、寫資料函式、讀資料函式、讀狀態函式。需要的注意的是有效資料出現的那段時間Tc必須合適,不能太短,否則會造成讀寫失敗。
 
給出幾個函式示例:
//忙檢測,若忙則等待,最長等待時間為60ms
void busychk_12864(void){
 unsigned int timeout = 0;
 E_12864 = 0;
 RS_12864 = 0;
 RW_12864 = 1;
 E_12864 = 1;
 while((IO_12864 & 0x80) && ++timeout != 0);  //忙狀態檢測,等待超時時間為60ms
 E_12864 = 0;
}
 
//寫命令子程式
void wrtcom_12864(unsigned char com){
 busychk_12864();
 E_12864 = 0; 
 RS_12864 = 0;
 RW_12864 = 0;
 IO_12864 = com;
 E_12864 = 1;
 delay_12864(50);    //50us使能延時!!!注意這裡,如果是較快的CPU應該延時久一些
 E_12864 = 0;  
}
 
//讀資料子程式
unsigned char reddat_12864(void){
 unsigned char temp;
 busychk_12864();
 E_12864 = 0;
 IO_12864 = 0xff;  //IO口置高電平,讀引腳
 RS_12864 = 1;
 RW_12864 = 1;
 E_12864 = 1;
 delay_12864(50);    //使能延時!!!注意這裡,如果是較快的CPU應該延時久一些
 temp = IO_12864;
 
 return temp; 
}
 
//寫資料子程式
void wrtdat_12864(unsigned char dat){
 busychk_12864();
 E_12864 = 0;
 RS_12864 = 1;
 RW_12864 = 0;
 E_12864 = 1;
 IO_12864 = dat;
 delay_12864(50);    //使能延時!!!注意這裡,如果是較快的CPU應該延時久一些
 E_12864 = 0;  
}
 
其中,忙檢測是必要的,當BF=1時,表示內部正在進行有關的操作,即處於忙狀態。在BF變回0之前ST7920不會接受任何指令。MCU必須檢測BF以確定ST7920內部操作是否完成,然後才能再傳送指令。也可以用延時來替代忙檢測,但是需要延時足夠的時間。盲檢測實際就是讀內部的狀態暫存器,該暫存器最高位(D7)為忙標誌BF,剩餘7位為地址指標的內容,所以進行盲檢測實際上也把地址指標中的地址讀出來了。
 
指令集:
指令集分為基本指令集和擴充套件指令集,使用相應的指令集必須先寫相應指令表明後續指令均為該類指令。如使用基本指令集時,寫指令(0x30),需要使用擴充套件指令集時寫指令(0x34)切換到擴充套件指令集。
 
一)基本指令集(RE=0):(使用擴充套件指令集先寫指令0x30,這使得RE=0)
 
清屏指令(0x01):往DDRAM寫滿0x20,指標地址寫0x00。表現在螢幕就是顯示空白。
回車指令(0x02/0x03):地址指標內容寫0x00.
進入模式0 0 0 0 0 1 I/D S:設定讀寫資料之後游標、顯示移位的方向。內部有2個可程式設計位,I/D表示讀寫一個字元後資料指標是加一還是減一。I/D=1指標加一,I/D=0指標減一。S=1開啟整屏移動。
S I/D= H H,螢幕每次左移一個字元。
S I/D= H L ,螢幕每次右移一個字元。
但是平時不開啟螢幕移動,這裡說明一個概念,就是螢幕移動,實際試驗中若開啟了螢幕移動你會發生顯示是灰常怪異的,說明如下:由於DDRAM的結構是下方表所示:
上半屏                                             下半屏
80H、81H、82H、83H、84H、85H、86H、87H88H、89H、8AH、8BH、8CH、8DH、8EH、8FH
90H、91H、92H、93H、94H、95H、96H、97H98H、99H、9AH、9BH、9CH、9DH、9EH、9FH
A0H、A1H、A2H、A3H、A4H、A5H、A6H、A7HA8H、A9H、AAH、ABH、ACH、ADH、AEH、AFH
B0H、B1H、B2H、B3H、B4H、B5H、B6H、B7HB8H、B9H、BAH、BBH、BCH、BDH、BEH、BFH
在未開啟屏移時,螢幕是以表格第一列作為參考起點,然後前8列歸上半屏顯示,後8列歸下半屏顯示。如果此時向左屏移一個字元,那麼DDRAM內容與顯示對映關係變為:
80H81H、82H、83H、84H、85H、86H、87H88H89H、8AH、8BH、8CH、8DH、8EH、8FH
90H91H、92H、93H、94H、95H、96H、97H98H99H、9AH、9BH、9CH、9DH、9EH、9FH
A0H、A1H、A2H、A3H、A4H、A5H、A6H、A7HA8H、A9H、AAH、ABH、ACH、ADH、AEH、AFH
B0H、B1H、B2H、B3H、B4H、B5H、B6H、B7HB8H、B9H、BAH、BBH、BCH、BDH、BEH、BFH
可以看到實際上原來第三第四行開始的字元跑到了第一行第二行的末尾,整個DDRAM的結構就是一種迴圈的結構,發生屏移時DDRAM與顯示對映關係不斷在改變。但是這不太符合我們的閱讀習慣,所以如果需要使用該項功能還需程式設計校正之。
顯示、游標、閃爍開關0 0 0 0 0 0 1 D C B:
D=1: 顯示開(Display) C=1: 游標開(Cursor) B=1: 游標位置閃爍開(Blink)。為0則為關。
游標顯示移位控制0 0 0 1 S/C R/L X X
 
學習筆記:12864液晶模組的詳細使用
說明:
LL:這時僅僅是將地址指標AC的值減1。在螢幕上表現是游標左移一個字元。
LH:這時僅僅是將地址指標AC的值加1。在螢幕上表現是游標右移一個字元。
HL:AC指標不變,向左屏移一個字元。這是DDRAM結構迴圈左移,80H接在8FH後面,90H接在9FH後面。這與上面講的屏移是一樣的。
HH:AC指標不變,向右屏移一個字元。這是DDRAM結構迴圈右移,80H接在8FH後面,90H接在9FH後面。
功能設定:0 0 1 DL X RE X X:(切換基本指令集與擴充套件指令集)
DL=1表示8為介面,DL=0表示4為介面。
RE=1表示開啟擴充套件指令,RE=0表示使用基本指令。
開啟基本指令則設定為0x30,開啟擴充套件指令則設定為0x34。
CGRAM地址設定:0x40+地址。地址範圍是00H~3FH。前提是SR=0,即允許設定IRAM和CGRAM地址!!!
DDRAM地址設定:只有字地址。如下表所示。(注意DDRAM地址有4行×16字)如下所示:
80H、81H、82H、83H、84H、85H、86H、87H88H、89H、8AH、8BH、8CH、8DH、8EH、8FH
90H、91H、92H、93H、94H、95H、96H、97H98H、99H、9AH、9BH、9CH、9DH、9EH、9FH
A0H、A1H、A2H、A3H、A4H、A5H、A6H、A7HA8H、A9H、AAH、ABH、ACH、ADH、AEH、AFH
B0H、B1H、B2H、B3H、B4H、B5H、B6H、B7HB8H、B9H、BAH、BBH、BCH、BDH、BEH、BFH
所以某一時刻只能顯示其中的2行。只有捲動顯示才能將另兩行的資料顯示出來。
讀忙標誌(地址):同時忙標誌和地址讀出來。忙狀態時,ST7920不會接受任何指令。按照時序圖將RS置0,RW置1,然後讀取狀態暫存器。
寫RAM(DDRAM/CGRAM/GDRAM):寫了控制邏輯(函式wrtcom_12864(地址);)之後,直接送資料(wrtdat_12864)。寫完後地址指標根據進入模式中的設定加一或減一。寫資料前先寫地址,而寫地址本身是一個寫地址命令,然後再寫資料。
讀RAM(DDRAM/CGRAM/GDRAM):記得先假讀一次,後面的才是真讀,假讀之後不需要再假讀了,除非重設了地址。
 
二)擴充套件指令集(RE=1):(使用擴充套件指令集先寫指令0x34,這使得RE=1)
 
待機模式:0x01,不影響DDRAM,所以跟清屏指令不同,任何指令可以結束待機模式。
捲動地址/IRAM地址允許設定:0 0 0 0 0 0 1 SR:
SR=1:允許設定垂直捲動地址。SR=0:允許設定IRAM和CGRAM地址。
設定捲動/IRAM地址:0x40+地址。(捲動地址為行地址,即縱向地址).
這裡講解捲動,捲動就是上下滾屏,實現螢幕的垂直滾動。
捲動地址:地址範圍為0x00~0x63,共64行卷動地址其實就是垂直地址。每一個地址代表著DDRAM中的一行的畫素點。捲動一次就是把該行所有點移到上半屏和下半螢幕最上方。
80H、81H、82H、83H、84H、85H、86H、87H88H、89H、8AH、8BH、8CH、8DH、8EH、8FH
90H、91H、92H、93H、94H、95H、96H、97H98H、99H、9AH、9BH、9CH、9DH、9EH、9FH
A0H、A1H、A2H、A3H、A4H、A5H、A6H、A7HA8H、A9H、AAH、ABH、ACH、ADH、AEH、AFH
B0H、B1H、B2H、B3H、B4H、B5H、B6H、B7HB8H、B9H、BAH、BBH、BCH、BDH、BEH、BFH
還是DDRAM的結構圖,需要注意的是卷屏是分上半屏捲動和下半屏捲動,兩屏之間沒有關係,也就是DDRAM中左邊紅色部分在上半屏滾動,右邊綠色部分在下半屏滾動。
B0H、B1H、B2H、B3H、B4H、B5H、B6H、B7H 的下一行是
80H、81H、82H、83H、84H、85H、86H、87H
也就是說左邊是一個上下相接的迴圈結構。同理右邊也是上下相接的迴圈結構。左邊記憶體中的字元上下滾動。右邊記憶體中的字元上下滾動,兩者木有關係。
要開啟捲動,首先開啟擴充套件指令集,然後允許捲動地址設定,再設定捲動地址。
wrtcom_12864(0x34);              //開啟擴充套件指令
wrtcom_12864(0x03);              //允許輸入捲動地址
wrtcom_12864(0x40 + 地址    //設定捲動地址
wrtcom_12864(0x30);              //回到基本指令
要實現全屏滾動,就必須使用迴圈不斷地修改捲動地址。從00~63如此迴圈,但遺憾的是這也不符合我們的閱讀習慣,後續的應用的中將講解全屏滾動的實現方法。這裡只是把卷動原理講清楚。
反白顯示:0 0 0 0 0 1 R1 R0:
R1、R0初始化的值為00。選擇1~4任一行反白顯示並可決定是否反白。
如何開啟反白顯示:首先開啟擴充套件指令(0x34),然後設定選中某一行設定反白顯示(0x04+R1R0)。00為第一行,01為第二行,10為第三行,11為第四行。需要說明的是,這裡的行是指DDRAM所有記憶體的行,而不是顯示的行,螢幕只顯示2行。
所以如果我們開啟第3第4行的反白顯示,不捲動我們是看不到效果的。
同時,如果我們開啟第1行反白顯示,那麼在螢幕中第1行第3行都會反白顯示,第2行則對應螢幕第2第4行,這一點需要注意。
如何關閉反白顯示:只需在此寫一次地址即可關閉,也就說,第一次寫第一開啟反白,第二次寫相同的地址關閉反白顯示。
wrtcom_12864(0x34);  //反白顯示試驗
wrtcom_12864(0x04);  //開啟反白顯示
delay_12864(60000);  //延時
delay_12864(60000);  //延時
wrtcom_12864(0x04); //關閉反白顯示
wrtcom_12864(0x30);  //開啟基本指令集
擴充套件功能設定:0x36設定繪圖顯示開。
當GDRAM寫完了之後,寫0x36則螢幕顯示你所繪製的圖形。
0 0 0 0 1 DL x RE G x (RE=1擴充套件指令,G=1開繪圖顯示,DL=1表示8為介面
設定GDRAM地址:繪圖時,需要將GDRAM的地址寫入地址指標中,然後才能寫入資料。連續寫入兩個位元組,第一個為行地址(Y),第二個為列地址(X)。
需要注意的是:寫了資料之後,地址指標會自動加一(以字為單位),當到達該行的行尾時,指標下一次加一會使得地址指標跳回該行行首,也就說如果地址值為8FH時,下一次它就是80H(以第一行為例)。指標地址在本行之間迴圈。
指令介紹完
再講下初始化過程,根據ST7920的手冊提供的初始化步驟就可以了。
學習筆記:12864液晶模組的詳細使用
學習筆記:12864液晶模組的詳細使用
初始化函式如下:
//延時子程式
void delay_12864(unsigned int del){
 unsigned int i;
 for(i = 0; i < del; i++){; }
}
 
//初始化12864子函式
void initial_12864(void){
 delay_12864(40000);
 RST_12864 = 1;
 RST_12864 = 0;    //復位
 delay_12864(500);
 RST_12864 = 1;
 wrtcom_12864(0x30);   //設定為基本指令集動作
 delay_12864(100);
 wrtcom_12864(0x30);   //設定為基本指令集動作
 delay_12864(37);
 wrtcom_12864(0x08);   //設定顯示、游標、閃爍全關。
 delay_12864(100);
 wrtcom_12864(0x01);   //清屏,並且DDRAM資料指標清零
 delay_12864(100000);
 wrtcom_12864(0x06);      //進入模式設定
}
 
應用部分:
這裡講解12864的幾個典型應用:
1)、自編字元建立以及顯示
2)、GDRAM的繪製及顯示
3)、全屏捲動的實現方法
 
 
1)、自編字元建立以及顯示
先明確的要點,12864具有4個自編字元,每個字元的編碼為0000H、0002H、0004H、0006H,4個自定義字元的CGRAM地址分別為00H~0FH、10H~1FH、20H~2FH、30H~3FH。
我們以第3個字元為例:
在這裡先把整個原始檔的巨集定義以及各子函式貼出:
#include <reg52.h>
#define IO_12864  P0
sbit RS_12864 = P2^5;
sbit RW_12864 = P2^6;
sbit E_12864 = P2^7;
sbit RST_12864 = P2^2;
//忙檢測,若忙則等待,最長等待時間為60ms
void busychk_12864(void){
 unsigned int timeout = 0;
 E_12864 = 0;
 RS_12864 = 0;
 RW_12864 = 1;
 E_12864 = 1;
 while((IO_12864 & 0x80) && ++timeout != 0);  //忙狀態檢測,等待超時時間為60ms
 E_12864 = 0;
}
 
//寫命令子程式
void wrtcom_12864(unsigned char com){
 busychk_12864();
 E_12864 = 0; 
 RS_12864 = 0;
 RW_12864 = 0;
 IO_12864 = com;
 E_12864 = 1;
 delay_12864(50);    //使能延時!!!注意這裡,如果是較快的CPU應該延時久一些
 E_12864 = 0;  
}
 
//讀資料子程式
unsigned char reddat_12864(void){
 unsigned char temp;
 busychk_12864();
 E_12864 = 0;
 IO_12864 = 0xff;  //IO口置高電平,讀引腳
 RS_12864 = 1;
 RW_12864 = 1;
 E_12864 = 1;
 delay_12864(50);    //使能延時!!!注意這裡,如果是較快的CPU應該延時久一些
 temp = IO_12864;
 
 return temp; 
}
 
//寫資料子程式
void wrtdat_12864(unsigned char dat){
 busychk_12864();
 E_12864 = 0;
 RS_12864 = 1;
 RW_12864 = 0;
 E_12864 = 1;
 IO_12864 = dat;
 delay_12864(50);    //使能延時!!!注意這裡,如果是較快的CPU應該延時久一些
 E_12864 = 0;  
}
 
//初始化12864子函式
void initial_12864(void){
 delay_12864(40000);
 RST_12864 = 1;
 RST_12864 = 0;    //復位
 delay_12864(500);
 RST_12864 = 1;
 wrtcom_12864(0x30);   //設定為基本指令集動作
 delay_12864(100);
 wrtcom_12864(0x30);   //設定為基本指令集動作
 delay_12864(37);
 wrtcom_12864(0x08);   //設定顯示、游標、閃爍全關。
 delay_12864(100);
 wrtcom_12864(0x01);   //清屏,並且DDRAM資料指標清零
 delay_12864(100000);
 wrtcom_12864(0x06);   //進入模式設定
 wrtcom_12864(0x0c);   //開顯示
 
}
 
以上函式定義在main()函式之前,我們在主函式中編寫程式:
void main(){
  unsigned char i,*addr;
  unsigned char defchar[] = {0x08,0x10,0x08,0x10,0x08,0x10,0x7F,0xFE,0x20,0x04,0x12,0x48,0x08,0x10,0x05,0xA0,0x02,0x40,0x01,0x80,0x01,0x80,0x07,0xE0,0x09,0x90,0x11,0x88,0x11,0x88,0x11,0x88};    //自定義字元,這裡是筆者畫的一個小機器人。
 
  delay_12864(100);      //啟動延時
  initial_12864();       //初始化12864
  addr = defchar;
  wrtcom_12864(0x40+0x20); //寫CGRAM首行地址
  for(i = 0; i < 32; i++){
    wrtdat_12864(*addr++);   
  }
  wrtcom_12864(0x80);     //在第一行第一個字元出顯示自定義字元
  wrtdat_12864(0x00);     //寫第三個自定義字元編碼的高位元組
  wrtdat_12864(0x04);     //寫第三個自定義字元編碼的低位元組
 
  while(1);
 
}
執行程式就可以看到第一個字元處出現一個小機器人了。
 
2)、GDRAM的繪製及顯示
先明確的要點,GDRAM是32行×16字。寫資料之前必須先送行地址,然後送列地址。讀寫的基本操作單元是字(2個位元組)。讀寫完一個字後地址指標在本行自動加一,到達行末則返回行首地址(地址迴圈)。
我們這裡先以一個畫點函式函式為例,然後再根據畫點函式寫一個繪製矩形的函式
先建一個座標左上角為(0,0),右下角為(63,127)。
畫點原理:由於GDRAM的讀寫基本操作單元是字,那麼我們需要畫一個點但是又不改變其他點的內容,那麼需要把該點所處的字中的2個位元組均讀出,然後再單獨修改我們需要畫的那個點(其他位保持不變),最後把該字再寫回去。
因此,涉及的操作有先讀GDRAM,再寫GDRAM,再顯示GDRAM。
在寫主函式之前先寫幾個子函式,說明其作用:
void clnGDR_12864(void)  //清空GDRAM
void drawdot_12864(unsigned char y,unsigned char x,unsigned char type) //畫點子函式
 
為什麼要清空GDRAM呢,因為指令集中沒有GDRAM清空指令,而我們往裡寫了什麼它就會一直儲存著,所以我們畫點之前先清空GDRAM,其實清空GDRAM就是不斷往裡寫0x00。
 
//清空GDRAM,總共就是寫1KB的0x00。
void clnGDR_12864(void){
  unsigned char j,k;
  wrtcom_12864(0x34);      //在寫GDRAM的地址之前一定要開啟擴充指令集
                           //否則地址寫不進去!!
  for( j = 0 ; j < 32 ; j++ )
  {
   
    wrtcom_12864(0x80 + j) ;    //寫Y 座標
    wrtcom_12864(0x80) ;        //寫X 座標
  
    for( k = 0 ; k < 32 ; k++ ) //寫一整行資料
    {
     wrtdat_12864( 0x00 );
    }
  }  
}
 
//畫點函式,左上角為參考點(0,0)
//右下角為(63,127),點座標形式為(行座標,列座標)
//引數type用於設定畫黑點、白點或取反(黑變白,白變黑)
//type = 0為白色,1 為黑色,2為取反
void drawdot_12864(unsigned char y,unsigned char x,unsigned char type){
  
 unsigned char X,Y,k;  //X儲存行地址,Y儲存列地址
                       //k儲存點在字中的位置從左至右為0~15
 unsigned char DH,DL;  //存放讀出資料的高位元組和低位元組
 
 if(y >= 0 && y <= 63 && x >= 0 && x <= 127) {
  
  if(y < 32){     //演算法:確定所畫點的地址行與列地址
   X = 0x80 + (x >> 4);
   Y = 0x80 + y; 
  }else{
   X = 0x88 + (x >> 4);
   Y = 0x80 + (y - 32);
  }
       
  wrtcom_12864(0x34);  //開啟擴充套件指令,關閉繪圖顯示
  wrtcom_12864(Y);  //寫入所確定的點的行位地址  
  wrtcom_12864(X);   //寫入所確定的點的列字地址
 
  DH = reddat_12864(); //假讀
  DH = reddat_12864();    //讀高位元組
  DL = reddat_12864(); //讀低位元組
 
  k = x % 16;          //餘數為點在字中的位置
 
  //畫點
  switch(type){     //畫點型別,1黑或0白或2取反
   
   case 0:
        if(k < 8){   //點在高位元組
      DH &= ~(0x01 << (7 - k));  //修改該點同時保持其他位不變
     }else{          //點在低位元組
      DL &= ~(0x01 << (7 - (k % 8)));  //修改該點同時保持其他位不變
     }
     break;
   case 1:
     if(k < 8){
      DH |= (0x01 << (7 - k));  //修改該點同時保持其他位不變
     }else{
      DL |= (0x01 << (7 - (k % 8))); //修改該點同時保持其他位不變
     }
     break;
   case 2:
     if(k < 8){
      DH ^= (0x01 << (7 - k));  //修改該點同時保持其他位不變
     }else{
      DL ^= (0x01 << (7 - (k % 8)));   //修改該點同時保持其他位不變
     }
     break;
   default:
     break;  
  }
  
  wrtcom_12864(Y);  //寫行位地址
  wrtcom_12864(X);     //寫列字地址
 
  wrtdat_12864(DH);  //將高位元組資料寫回
  wrtdat_12864(DL);  //將低位元組資料寫回
 
  wrtcom_12864(0x30);  //轉回普通指令
 }
}
 
下面編寫主函式,這就簡單了,如下:
 
void main(void){
 delay_12864(1000);
 initial_12864();
 clnGDR_12864();           //清空GDRAM
 drawdot_12864(20,50,1);   //畫點
 wrtcom_12864(0x36);       //開繪圖顯示
 while(1);
}
 
程式執行後相應位置出現了一個黑點,手機壞了,拍不了照,不然就貼下照片。
然後根據畫點函式,擴充套件一個畫矩形的函式吧:
 
//畫矩形子函式,引數為(點1行座標,點1列座標,
//點2行座標,點2列座標,線條顏色(0為白,1為黑,2對原色取反))           
void drawrec_12864(unsigned char y1,unsigned char x1,unsigned char y2,unsigned char x2,unsigned char type){
 
 unsigned char largex,largey,smallx,smally;  //將兩點橫縱座標按大小儲存
 unsigned char i;
 if(x1 > x2){
  largex = x1;
  smallx = x2;
 }else{
  largex = x2;
  smallx = x1;
 }
 
 if(y1 > y2){
  largey = y1;
  smally = y2;
 }else{
  largey = y2;
  smally = y1;
 }
 
//以下繪製4條矩形邊框
 for(i = smallx; i < largex; i++){
  drawdot_12864(largey,i,type); 
 }
 for(i = largey; i > smally; i--){
  drawdot_12864(i,largex,type);
 }
 for(i = largex; i > smallx; i--){
  drawdot_12864(smally,i,type);
 }
 for(i = smally; i < largey; i++){
  drawdot_12864(i,smallx,type);
 }
 
 wrtcom_12864(0x30);     //返回普通指令  
}
 
主函式為:
void main(void){
 delay_12864(1000);
 initial_12864();
 clnGDR_12864();                  //清空GDRAM
 drawrec_12864(20,50,30,120,1);   //畫矩形
 wrtcom_12864(0x36);              //開繪圖顯示
 while(1);
}
 
關於GDRAM的操作就到這吧,下面講解下12864全屏捲動的實現方法。
 
3)、12864全屏捲動的實現方法
首先需要明確的要點:
DDRAM的結構如下:
80H、81H、82H、83H、84H、85H、86H、87H88H、89H、8AH、8BH、8CH、8DH、8EH、8FH
90H、91H、92H、93H、94H、95H、96H、97H98H、99H、9AH、9BH、9CH、9DH、9EH、9FH
A0H、A1H、A2H、A3H、A4H、A5H、A6H、A7HA8H、A9H、AAH、ABH、ACH、ADH、AEH、AFH
B0H、B1H、B2H、B3H、B4H、B5H、B6H、B7HB8H、B9H、BAH、BBH、BCH、BDH、BEH、BFH
卷屏是分上下屏個各自捲動的,上半屏捲動左邊紅色區域的內容,下半屏捲動右邊綠色區域的內容。
 
為了實現全屏捲動顯示,必須使用拼接的方法實現。
筆者花了幾個小時研究了下演算法,然後第二天實現了。現講述如下:
細心觀察DDRAM的結構發現,如果在捲動過程中,在同一時刻螢幕顯示的內容最多涉及3行DDRAM的內容,而另一行是沒有顯示的,那麼這一行就是用來快取的資料的。
當螢幕顯示如下2行時開始捲動(一):
80H、81H、82H、83H、84H、85H、86H、87H88H、89H、8AH、8BH、8CH、8DH、8EH、8FH
90H、91H、92H、93H、94H、95H、96H、97H98H、99H、9AH、9BH、9CH、9DH、9EH、9FH
則螢幕同時出現以下3行DDRAM內容(二):
80H、81H、82H、83H、84H、85H、86H、87H88H、89H、8AH、8BH、8CH、8DH、8EH、8FH
90H、91H、92H、93H、94H、95H、96H、97H98H、99H、9AH、9BH、9CH、9DH、9EH、9FH
A0H、A1H、A2H、A3H、A4H、A5H、A6H、A7HA8H、A9H、AAH、ABH、ACH、ADH、AEH、AFH
需要注意的是,左邊是上半屏顯示,右邊是下半屏顯示。
在程式的開始處往DDRAM對應區域填寫如下內容:
第一行字元   第三行字元-->  開始顯示
第二行字元   第四行字元
第三行字元   第五行字元-->  即將顯示
第四行字元   第六行字元
這樣在開始捲動之後,就可以實現拼接的效果了。當捲動了16次之後,也就是第一行字元已經移出螢幕,螢幕顯示的DDRAM如下:
第一行字元   第三行字元
第二行字元   第四行字元
第三行字元   第五行字元
第四行字元   第六行字元
此時,螢幕接著滾動,顯示內容涉及3行的DDRAM,如下:
第一行字元   第三行字元-->  已顯示完畢
第二行字元   第四行字元
第三行字元   第五行字元
第四行字元   第六行字元-->  即將顯示
第一行DDRAM是空餘的,下次就該往第一行寫資料,寫完後DDRAM內容如下:
第五行字元   第七行字元
第二行字元   第四行字元
第三行字元   第五行字元
第四行字元   第六行字元
經過又一次的16次卷屏之後螢幕顯示內容如下:
第五行字元   第七行字元-->  即將顯示
第二行字元   第四行字元-->  顯示完畢
第三行字元   第五行字元
第四行字元   第六行字元
然後接下來又捲動16次,筆者的演算法是,在每一次捲動後寫一個字到顯示完畢的那一行中,卷完16次,顯示完畢的那一行也就寫完了。然後接下來的16次捲動又寫剛剛顯示完畢的那一行,而剛被寫完的那一行將在後面16次捲動中顯示。
原理就是如此,然後從中提取出規律,設計出演算法,並程式設計實現:
下面是程式實現:
void main(void){
 unsigned char code ser[] = {"一一一一一一一一二二二二二二二二叄叄叄叄叄叄叄叄四四四四四四四四中國中國中國中國"}; //這是要顯示的字串
//沒有檢測換行符功能,只能顯示一長串的漢字或一串ASCII碼字元。
 unsigned char i,addr,flag,hang,over,*ptdat;
//addr用於儲存寫入地址
//flag儲存捲動地址,名字沒取好!
//hang儲存下一行要寫入資料的行號(1~4)
//over記錄寫入的空字元數
//ptdat儲存字串的指標
 
 delay_12864(1000);
 initial_12864();
 
 ptdat = ser;
 over = 0;    //寫入空字元數
 
//這裡先把前面DDRAM中的前3行的字元資料寫入
//如果字元不足<=4行,那麼不捲動,之後字元>4行才捲動
//一直到末行顯示完畢則停止捲動
 wrtcom_12864(0x80); //寫螢幕第一行字元
 for(i = 0; i < 16; i++){
  if(*ptdat != '\0'){
   wrtdat_12864(*ptdat++);
  }else{
   wrtdat_12864(0x20);
   over++;
  }
 }
 wrtcom_12864(0x90); //寫螢幕第二行字元
 for(i = 0; i < 16; i++){
  if(*ptdat != '\0'){
   wrtdat_12864(*ptdat++);
  }else{
   wrtdat_12864(0x20);
   over++;
  }
 }
 wrtcom_12864(0x88);//寫螢幕第三行字元
 for(i = 0; i < 16; i++){
  if(*ptdat != '\0'){
   wrtdat_12864(*ptdat++);
  }else{
   wrtdat_12864(0x20);
   over++;
  }
 }
 wrtcom_12864(0x98);//寫螢幕第四行字元
 for(i = 0; i < 16; i++){
  if(*ptdat != '\0'){
   wrtdat_12864(*ptdat++);
  }else{
   wrtdat_12864(0x20);
   over++;
  }
 }
 ptdat = ptdat - 32;
 wrtcom_12864(0xa0); //寫DDRAM第3行資料
 for(i = 0; i < 16; i++){
  if(*ptdat != '\0'){
   wrtdat_12864(*ptdat++);
  }else{
   wrtdat_12864(0x20);
   over++;
  }
 }
 ptdat = ptdat + 16;
 for(i = 0; i < 16; i++){
  if(*ptdat != '\0'){
   wrtdat_12864(*ptdat++);
  }else{
   wrtdat_12864(0x20);
   over++;
  }
 }
//前面的程式碼是往DDRAM中寫如下內容:
//第一行字元   第三行字元
//第二行字元   第四行字元
//第三行字元   第五行字元
//如果寫第5行時全為空,說明字元剛好4行,不捲動。
//此時第5行寫入16個0x20空字元,over用於記錄空字元個數。
//如果不足4行,則前面也將會寫入空字元,此時寫完了3行DDRAM後
//over的值必大於16,而只要over>15,就不捲動
 
 wrtcom_12864(0x0c);   //開顯示
 if(over > 15){;}   //顯示字元不足4行,不捲動
 else      //顯示字元大於4行,開啟捲動
 {
  hang = 4;  //接下來要寫DDRAM第4行資料
  flag = 0x01; //初始捲動地址為1
 
  while(1){
 
   switch(hang){   //設定寫入DDRAM的地址
    
    case 1: addr = 0x80; break;  //往地址變數中寫第一行首地址
    case 2: addr = 0x90; break;  //往地址變數中寫第二行首地址
    case 3: addr = 0xa0; break;  //往地址變數中寫第三行首地址
    case 4: addr = 0xb0; break;  //往地址變數中寫第四行首地址
   }
 
   switch(hang){   //指出下一次要寫的行地址
    
    case 1: hang = 2; break;//第1行寫完了,下一行要寫第2行
    case 2: hang = 3; break;//第2行寫完了,下一行要寫第3行
    case 3: hang = 4; break;//第3行寫完了,下一行要寫第4行
    case 4: hang = 1; break;//第4行寫完了,下一行要寫第1行  
   }

//後續程式碼為往每一行寫資料,捲動一次寫一個字。

   ptdat = ptdat - 32;
   for(i = 0; i < 8; i++){  //寫一行中的前8個字元
    wrtcom_12864(0x34);     //開啟擴充套件指令
    wrtcom_12864(0x03);     //允許輸入捲動地址
    wrtcom_12864(0x40 + flag++);  //設定捲動地址
    wrtcom_12864(0x30);     //回到基本指令
    wrtcom_12864(addr + i);
    delay_12864(20000);
 
    if(*ptdat != '\0'){
     wrtdat_12864(*ptdat++);   //寫入高位元組
    }else{
     wrtdat_12864(0x20);    //字串結束則寫入空字元
    }         
            
    if(*ptdat != '\0'){
     wrtdat_12864(*ptdat++);   //寫入低位元組
    }else{
     wrtdat_12864(0x20);    //字串結束則寫入空字元
    }
 
   }
 
   ptdat = ptdat + 16;
   for(i = 8; i < 16; i++){  //寫一行中的後8個字元
    wrtcom_12864(0x34);     //開啟擴充套件指令
    wrtcom_12864(0x03);     //允許輸入捲動地址
    if(flag == 64){flag = 0;}
    wrtcom_12864(0x40 + flag);   //設定捲動地址
    flag++;
    wrtcom_12864(0x30);     //回到基本指令
    wrtcom_12864(addr + i);
    delay_12864(20000);
 
    if(*ptdat != '\0'){
     wrtdat_12864(*ptdat++);   //寫入高位元組
    }else{
     over++;       //寫完最後一行字元,需要再捲動16次才能顯示出來。
     wrtdat_12864(0x20);      //字串結束則寫入空字元  
    }    

相關文章