話不多說先上圖
前言
在做這個實驗的時候在網上找了許多資料,都是關於使用單片機驅動LCD顯示,確實用單片機驅動是要簡單不少,記得在FPGA學習交流群里問問題的時候,被前輩指教,說給我最好的指教便是別在玩這個了,多看看關於FPGA方面的書籍,比做這個單片機做的東西價值強多了。現在想來確實,自從學習FPGA以來,看過的書沒有多少,只是想做個什么了,就在網上找找例程,照抄下來,把算法推理一遍,下個板子實現了,便以為自己會了懂了,要是自己在寫一個便問題百出。那么菜鳥始終是菜鳥。自己根本沒有掌握FPGA的設計思想和優勢,用着FPGA做着單片機的東西,這種東西練練手就足夠了,所以,做完這個實驗LCD就到此為止了,我也要好好想想該如何進行下面的學習了。在這個上面我還是花費了不少時間,所以寫一篇博文總結記錄想,回頭看的時候,以警醒自己。
摘要
做完這個實驗我最大的感觸是一定要會讀數據手冊。拿到元件(LCD12864),上面有16個的引腳,先要搞清楚每個引腳的功能,這時候就一定要會讀數據手冊。我個qc12864b的中文手冊琢磨了很久才看懂了一些簡單的操作指令,包括在手冊上要提取出來,元件的驅動時序,掃描時鍾,引腳定義,操作指令,功能描述等。
LCD顯示原理
掃描時鍾
從手冊上可以讀出,qc12864b(這是我所使用LCD12864的型號)的掃描時鍾介於470—590khz之間,最適為530khz,為了方便分頻,所以取500khz。
引腳說明
VSS 電源地,0v ——接開發板GND就行
VDD 電源正極(5v,3.3v)
VO 液晶顯示偏壓,調整對比度
RS RS=1時,當mpu進行讀模塊操作,指向地址寄存器
當mpu進行寫模塊操作,指向指令寄存器
RS=0時,無論進行讀/寫操作,都指向數據寄存器
R/W 高電平讀操作,低電平寫操作
E 使能信號,高電平讀取信息,下降沿執行命令
DB0 數據總線第0位
DB1 數據總線第1位
DB2 數據總線第2位
DB3 數據總線第3位
DB4 數據總線第4位
DB5 數據總線第5位
DB6 數據總線第6位
DB7 數據總線第7位
PSB 串並控制端口,H為並行,L為串行,直接接5v
RST 液晶復位端口,低電平有效
A 背光正極輸入,調整電壓大小可以調整亮度
K 背光負極輸入,一般直接接地
指令說明
具體指令說明,這里就不一一列出,可查閱qc12864b中文數據手冊,我在閱讀這一部分的時候耗費了很長時間,這些指令是驅動LCD顯示文字與圖片的核心,所以必須要掌握。
並口顯示時序
Verilog實現方案
從時序圖可以看出在使能信號的一個周期內可以完成寫入數據操作,掃描時鍾與使能信號周期相同即可。
狀態轉移圖
狀態機初始化位置 IDLE lcd_data = 8’hzz
功能設計 SETFUNCTION lcd_data = 8’h36//擴充指令繪圖顯示
顯示設置 SWITCHMODE lcd_data = 8’h0c//顯示狀態
清屏 CLEAR lcd_data = 8’h01//清零指令
設置Y坐標 SETDDRAM_Y
設置X坐標 SETDDRAM_X
寫入數據 WRITERAM
結束 STOP
繪圖指令下的坐標圖
代碼如下

1 module lcd_qc12864b( 2 input mclk, 3 input rst_n, 4 output reg lcd_rs,//H讀寫數據,L指令 5 output lcd_rw, //高電平讀操作,低電平寫操作 6 output lcd_e,//使能信號高電平有效 7 output reg [7:0]lcd_data,//八位數據總線 8 output psb,//串並控制端口,H為並行,L為串行,直接接5v 9 output lcd_rst//液晶的復位端口,低電平有效 10 ); 11 12 reg lcd_clk;//需要500khz頻率的時鍾 13 reg [7:0] state;//狀態機寄存器 14 reg [23:0] cnt;//分頻驅動12864計數器 15 reg flag = 1'b1;//顯示完成標志 16 reg [9:0] char_cnt;// 17 wire [7:0] data_disp;//一個字節是八位,一個英文字符是一個字節,中文是倆個字節 18 reg [5:0] cnt1;//行計數 19 20 parameter T500KHZ=24'd49999; 21 22 //分頻500khz時鍾 23 always @(posedge mclk or negedge rst_n) 24 begin 25 if(!rst_n)begin 26 lcd_clk <= 1'b0; 27 cnt <= 24'd0; 28 end 29 else if(cnt == T500KHZ)begin 30 cnt <= 24'd0; 31 lcd_clk <= ~lcd_clk; 32 end 33 else 34 cnt <= cnt + 1'd1; 35 end 36 37 //狀態機8個狀態設置 38 parameter IDLE = 8'b00_000_000,//初始狀態 39 SETFUNCTION = 8'b00_000_001,//功能設置 40 SWITCHMODE = 8'b00_000_010,//設置顯示開和光標閃爍關閉 41 CLEAR = 8'b00_000_100,//清屏操作 42 SETDDRAM_Y = 8'b00_010_000,//設置y軸坐標 43 SETDDRAM_X = 8'b00_100_000,//設置x軸坐標 44 WRITERAM = 8'b01_000_000,//寫設置寫入寄存器 45 STOP = 8'b10_000_000;//LCD操作停止,釋放其控制 46 47 //三段式書寫狀態機 48 always @(posedge lcd_clk or negedge rst_n) 49 begin 50 if(!rst_n) 51 lcd_rs <= 1'b0; 52 else begin 53 if(state == WRITERAM) 54 lcd_rs <= 1'b1;//寫數據模式 55 else 56 lcd_rs <= 1'b0;//寫命令模式 57 end 58 end 59 60 //lcd_rst始終為高電平,psb始終為低電平即可 61 assign lcd_rst = 1'b1; 62 assign psb = 1'b1; 63 assign lcd_rw = 1'b0;//只是寫操作,不需要讀操作 64 assign lcd_e = (flag==1)?lcd_clk:1'b0;//使能信號與液晶時鍾同步 65 66 always @(posedge lcd_clk or negedge rst_n) 67 begin 68 if(!rst_n)begin 69 cnt1<= 1'b0; 70 state <= IDLE; 71 lcd_data <= 8'hzz; 72 char_cnt <= 6'd0; 73 end 74 else begin 75 case(state) 76 IDLE:begin 77 state <= SETFUNCTION;//功能設置,8-bit+基本指令集0x30 78 lcd_data <= 8'h36; 79 end 80 81 SETFUNCTION:begin 82 state <= SWITCHMODE;//設置顯示開和光標閃爍關閉 83 lcd_data <= 8'h36; 84 end 85 86 SWITCHMODE:begin 87 state <= CLEAR; 88 lcd_data <= 8'h3e;//顯示設置,全顯示開,光標和閃爍關 89 end 90 CLEAR:begin//清屏操作 91 state <= SETDDRAM_Y;//點設置 92 lcd_data <= 8'h01;//清屏 93 end 94 SETDDRAM_Y:begin 95 if(cnt1< 32) 96 lcd_data <= 8'h80 + cnt1;//80H~9fH 97 else 98 lcd_data <= 8'h80 + (cnt1- 32); //80H~9fH 99 100 state <= SETDDRAM_X; 101 end 102 SETDDRAM_X:begin 103 if(cnt1< 32) 104 lcd_data <= 8'h80; //80H 105 else 106 lcd_data <= 8'h88; //88H 107 state <= WRITERAM; 108 end 109 WRITERAM:begin 110 lcd_data <= data_disp; 111 char_cnt <= char_cnt + 1'b1; 112 if(char_cnt[3:0] == 4'hf)begin //計算行 113 cnt1<= cnt1+ 1'b1; 114 if(cnt1== 63) 115 state <= STOP; 116 else 117 state <= SETDDRAM_Y; 118 end 119 else 120 state <= WRITERAM; 121 end 122 STOP:begin 123 flag <= 1'b0; 124 state <= STOP;//LCD操作停止,釋放其控制 125 end 126 default: state <= IDLE;//回到初始狀態 127 endcase 128 end 129 end 130 131 // ROM 132 rom U1_rom ( 133 .clka(mclk), 134 .addra(char_cnt), 135 .douta(data_disp) 136 ); 137 138 endmodule
配置ROM
有分布式ROM/ROM和塊ROM/RAM,這里配置的是塊ROM/RAM。這兩種方式具體我也不是了解很深,以后再深入學習。
該實驗最讓我頭疼的是調用ROM,第一次接觸IP核有許多地方都完全不懂。用LCD(帶中文字庫)顯示文字的時候,可以直接輸入文字的十六進制數值,設置顯示地址坐標即可,12864顯示原理點陣控制點的亮滅來實現,但是如果要顯示圖片的話一個個輸入難免太過麻煩,這個時候調用ROM就方便許多。找一張或做一張像素為128x64的單色圖片,使用取模軟件,按c51的方式取模。取模出來的數據為十六進制,行8個,64個。
配置塊RAM/ROM時,要加載的是.coe文件,所以需要將取模的十六進制數據保存到.coe文件中。最開始我一直在找如何能直接將圖片取模出來的數據轉化成.coe文件,試了很多方法都失敗了,最后發現完全可以自己按文件格式編輯一個即可,最終文件保存格式如下。
將圖片取模出來的每個數據前的ox去掉,這只是C語言中16進制的表示形式,文件第一行的MEMORY_INITIALIZATION_RADIX=16;表示數據全都為16進制數,這里的16可以換成8、2、10都行。
打開ISE建立工程后,新建文件,選擇IP(CORE Generator & Architecture Wizard),填寫文件名,next
找到Memories & Storage Elements單擊,選擇RAMs & ROMs單擊,選擇Block Memory Generator 7.3,next,finish。
上面直接next,下面這里選Single Port ROM,next
這里要填寫數據位寬和深度,位寬是你所需要輸出的數據位寬,LCD有8個數據位,故這里定義位寬為8,深度即有多少個這樣的數據,從取模出來的數據看,易得共有1024個數據位。我使用的basys2開發板使用的是Xilinx Spantan3E—100TQ144 FPGA包含73728位的塊RAM/ROM。因此,使用的塊RAM的最大容量為9216字節或4608個16位字。從這里來看,是完全夠用的。
Next,加載.coe數據文件,勾選Load Init File 加載.coe文件,我這里是在桌面上保存着,直接找到選擇就好。
然后一直點擊next直至最后一個界面,最后點擊Generate,生成rom.xco。生成的IP核在工程目錄下的ipcore_dir文件中。
將rom.v文件打開端口定義如下,直接在頂層文件對其實例化,如代碼所示
最后可直接仿真,新建tb文件查看仿真傳輸數據是否正確。由波形圖可得在功能設定指令傳輸完畢后,進入讀數據狀態,所讀入的數據與文件中數據相符,一行數據傳輸完畢進入下一行,繼續讀入數據,仿真圖結果正確。下板子也正確。
最開始學習FPGA,最容易出現的就是兩個問題,一個是把verilog當C語言來寫,另一個就是把FPGA當單片機來用。前者我已經有了改善,后者我還需要繼續努力。
轉載請注明出處:NingHeChuan(寧河川)
個人微信訂閱號:NingHeChuan
如果你想及時收到個人撰寫的博文推送,可以掃描左邊二維碼(或者長按識別二維碼)關注個人微信訂閱號
知乎ID:NingHeChuan
微博ID:NingHeChuan