在網上找了許久,發現FPGA用串口驅動LCD12864程序很少,基本上沒有。剛開始竊喜,中間郁悶,最后還是高興,為什么這樣說呢!頭一回在沒有參考程序的情況下,完全是照時序圖寫(自信),中間調試過程遇到一點小插曲(郁悶),后來搞定(高興),也算是對這段時間學習FPGA的一個能力檢測吧。廢話少說,趕緊步入正題。
首先來看一下串口模式的幾個重要管腳:
1、lcd_cs(PIN4),使能信號,高有效(有的資料上寫着低有效,高低我都試過,確認是低有效),定義output。
2、lcd_sid(PIN5),數據傳輸線,相當於I2C的SBDA數據傳輸線,可定義雙向,這里僅只有寫,所以定義output。
3、lcd_scl(PIN6),數據傳輸的時鍾,相當於I2C的SBCL時鍾線,定義output,這里注意,很多資料上沒說這頻率多少合適,而有的資料上寫的是高脈沖和低脈沖不能小於800ns或其他數據,一開始本以為這個時鍾會比並口時鍾會快一些,參考“verilog hdl的那些事兒”中,頻率設置100KHZ,他的是ST7566P,我的是ST7920,同一個系列,頻率應該會差不多吧,以上條件完全讓我把頻率設置在100KHZ,那么這個實驗注定要失敗,我就是栽倒在這,中間調試時,把頻率往小降,往大增(就是沒想到要那么慢的時鍾),結果就是不行,要么出現亂碼,要么不顯示,后來干脆就把這頻率設置成與並口一樣的時鍾,周期6MS(當然這個頻率不是最佳值,到底多少只有自己去試試了),喔、喔,終於正確的顯示了,高興阿。
4、PSB(PIN15),串口模式,可以直接接地,程序中是設置為低,定義output。
管腳都清楚了,那么接下來看時序圖,是很重要的一步:在發送數據前,CS要為高,發送數據是先連續發送5個1,用來啟動一個周期,此時傳輸計數被重置,並且串行傳輸被同步。緊接的兩個位指定傳輸方向(RW,確定讀還是寫)和傳輸性質(RS,確定是命令寄存器還是數據寄存器),最后的第八位是一個“0”,一個啟動字節發送完畢,那么緊接着發送數據或是指令。一個數據或指令要分為兩個字節來處理,先發高4bit,緊接着連續發送4個0,在發送低4bit,又緊接着發送4個0,也就是說完整發送完一個數據或命令,lcd_scl得打24個節拍。由於本實驗也是只寫不讀,所以得避開LCD的忙狀態,所以每次發送完一個數據或是命令后,在打一個節拍(25個),本程序中一個節拍的時間(6MS)足夠避開LCD的忙狀態。這里也要注意,當把所有數據處理完之后,lcd_scl得關閉,也就是拉低,否則,屏會一直刷新,覆蓋掉原有的內容,最終全屏成亂碼。
OK,時序搞定,那么可以利用狀態機完成,代碼實現
LCD12864_SPI.v

1 module LCD12864_SPI( 2 //input 3 sys_clk, 4 rst_n, 5 6 //output 7 lcd_cs, 8 lcd_sid, 9 lcd_scl, 10 lcd_psb 11 ); 12 input sys_clk;//50MHZ 13 input rst_n; 14 15 output lcd_cs; //enable,H active 16 output lcd_sid; //SPI data 17 output lcd_scl; //SPI clk 18 output lcd_psb; ////H:parallel module L:SPI module 19 /***************************************************/ 20 assign lcd_psb = 1'b0;//串口模式 21 /***************************************************/ 22 parameter T3MS = 18'd149_999; 23 parameter IDLE = 4'd0, //准備狀態 24 SEND_1 = 4'd1, //連續發送5個1 25 SNED_RW = 4'd2, //是寫還是讀 26 SEND_RS = 4'd3, //是指令還是數據 27 SEND_0 = 4'd4, //發送一個0,代表啟動字節結束 28 SEND_DATA_H = 4'd5, //發送數據的高4bit 29 SEND_FOUR_0 = 4'd6, //連續發送四個0 30 SEND_DATA_L = 4'd7, //發送數據的低4bit 31 DELAY = 4'd8, //延時一個lcd_clk周期,避開LCD的忙狀態 32 STOP = 4'd9; //結束 33 /***************************************************/ 34 ////產生周期為6MS的lcd_clk給LCD 35 reg [17:0] cnt; 36 reg lcd_clk; 37 always @(posedge sys_clk or negedge rst_n) 38 if(!rst_n) begin 39 cnt <= 18'd0; 40 lcd_clk <= 1'b0; 41 end 42 else if(cnt == T3MS)begin 43 cnt <= 18'd0; 44 lcd_clk <= ~lcd_clk; 45 end 46 else 47 cnt <= cnt + 1'b1; 48 /***************************************************/ 49 assign lcd_scl = en ? lcd_clk : 1'b0; //發送完之后,必須得停止,否則屏一直刷新,最后成全屏亂碼 50 /***************************************************/ 51 //在下降沿設置數據或命令 52 reg lcd_sid; 53 reg lcd_cs; 54 reg [2:0] i; 55 reg [2:0] j; 56 reg [6:0] num; 57 reg [3:0] state; 58 reg [7:0] dis_data; 59 reg flag; //用來標志一個dis_data數據發送完 60 reg en; //lcd_scl的使能信號 61 always @(posedge lcd_clk or negedge rst_n) 62 if(!rst_n) begin 63 lcd_sid <= 1'bz; 64 lcd_cs <= 1'b0; 65 i <= 3'd0; 66 j <= 3'd0; 67 num <= 7'd0; 68 state <= IDLE; 69 flag <= 1'b0; 70 en <= 1'b1; 71 end 72 else 73 case(state) 74 75 IDLE: 76 begin 77 lcd_cs <= 1'b1; 78 state <= SEND_1; 79 end 80 81 SEND_1://發送5個1 82 begin 83 i <= i + 1'b1; 84 lcd_sid <= 1'b1; 85 if(i == 3'd4) begin 86 i <= 3'd0; 87 state <= SNED_RW; 88 end 89 else 90 state <= SEND_1; 91 end 92 93 SNED_RW: 94 begin 95 lcd_sid <= 1'b0;//寫 96 state <= SEND_RS; 97 end 98 99 SEND_RS: 100 begin 101 state <= SEND_0; 102 if((num <= 7'd5) || (num == 7'd18) 103 || (num == 7'd25) || (num == 7'd34) 104 || (num == 7'd41) || (num == 7'd42) 105 || (num == 7'd75)) 106 lcd_sid <= 1'b0;//命令 107 else 108 lcd_sid <= 1'b1;//數據 109 end 110 111 SEND_0: 112 begin 113 lcd_sid <= 1'b0;//一個啟動字節結束 114 state <= SEND_DATA_H; 115 end 116 117 SEND_DATA_H: //進入發送高4bit 118 begin 119 j <= j + 1'b1; 120 lcd_sid <= dis_data[7-j]; 121 if(j == 3'd3) 122 state <= SEND_FOUR_0; 123 else 124 state <= SEND_DATA_H; 125 end 126 127 SEND_FOUR_0: //進入連續發送四個0 128 begin 129 i <= i + 1'b1; 130 lcd_sid <= 1'b0; 131 if(i == 3'd3) begin 132 i <= 3'd0; 133 if(flag) begin 134 num <= num + 1'b1; 135 flag <= 1'b0; 136 state <= DELAY; 137 end 138 else 139 state <= SEND_DATA_L; 140 end 141 else 142 state <= SEND_FOUR_0; 143 end 144 145 SEND_DATA_L: //進入連續發送低4bit 146 begin 147 j <= j + 1'b1; 148 lcd_sid <= dis_data[7-j]; 149 if(j == 3'd7) begin 150 j <= 3'd0; 151 flag <= 1'b1; //標志一個字節發送完 152 state <= SEND_FOUR_0; 153 end 154 else 155 state <= SEND_DATA_L; 156 end 157 158 DELAY: //延時,避開LCD的忙狀態 159 if(num <= 7'd77) 160 state <= SEND_1; 161 else 162 state <= STOP; 163 164 STOP: 165 begin 166 lcd_cs <= 1'b0; 167 en <= 1'b0; 168 state <= STOP; 169 end 170 171 endcase 172 /***************************************************/ 173 always @(num) 174 case(num) 175 7'd0 : dis_data = 8'h30;//功能設定 176 7'd1 : dis_data = 8'h30;//功能設定 177 7'd2 : dis_data = 8'h0c;//顯示設定 178 7'd3 : dis_data = 8'h01;//清屏 179 7'd4 : dis_data = 8'h06;//進入設定點 180 7'd5 : dis_data = 8'h81;//設置DDRAM地址 181 //歡迎訪問博客 182 7'd6 : dis_data = 8'hbb; 183 7'd7 : dis_data = 8'hb6; 184 7'd8 : dis_data = 8'hd3; 185 7'd9 : dis_data = 8'had; 186 7'd10 : dis_data = 8'hb7; 187 7'd11 : dis_data = 8'hc3; 188 7'd12 : dis_data = 8'hce; 189 7'd13 : dis_data = 8'hca; 190 7'd14 : dis_data = 8'hb2; 191 7'd15 : dis_data = 8'ha9; 192 7'd16 : dis_data = 8'hbf; 193 7'd17 : dis_data = 8'hcd; 194 //2line 顯示 文少清 195 7'd18 : dis_data = 8'h92;//設置DDRAM地址 196 7'd19 : dis_data = 8'hce; 197 7'd20 : dis_data = 8'hc4; 198 7'd21 : dis_data = 8'hc9; 199 7'd22 : dis_data = 8'hd9; 200 7'd23 : dis_data = 8'hC7; 201 7'd24 : dis_data = 8'he5; 202 //3line 顯示 LCD12864 203 7'd25 : dis_data = 8'h8a;//設置DDRAM命令 204 7'd26 : dis_data = "L"; 205 7'd27 : dis_data = "C"; 206 7'd28 : dis_data = "D"; 207 7'd29 : dis_data = "1"; 208 7'd30 : dis_data = "2"; 209 7'd31 : dis_data = "8"; 210 7'd32 : dis_data = "6"; 211 7'd33 : dis_data = "4"; 212 //4Line顯示 謝謝! 213 7'd34 : dis_data = 8'h9d;//設置DDRAM地址 214 7'd35 : dis_data = 8'hd0; 215 7'd36 : dis_data = 8'hbb; 216 7'd37 : dis_data = 8'hd0; 217 7'd38 : dis_data = 8'hbb; 218 7'd39 : dis_data = 8'ha3; 219 7'd40 : dis_data = 8'ha1; 220 //4Line顯示喇叭 221 7'd41 : dis_data = 8'h30;//功能設定 222 7'd42 : dis_data = 8'h40;//設定CGRAM字符的位置 223 7'd43 : dis_data = 8'h00; 224 7'd44 : dis_data = 8'h39; 225 7'd45 : dis_data = 8'h00; 226 7'd46 : dis_data = 8'h6a; 227 7'd47 : dis_data = 8'h00; 228 7'd48 : dis_data = 8'ha8; 229 7'd49 : dis_data = 8'h01; 230 7'd50 : dis_data = 8'h29; 231 7'd51 : dis_data = 8'h7e; 232 7'd52 : dis_data = 8'h2a; 233 7'd53 : dis_data = 8'hfc; 234 7'd54 : dis_data = 8'h28; 235 7'd55 : dis_data = 8'hfc; 236 7'd56 : dis_data = 8'h29; 237 7'd57 : dis_data = 8'hcc; 238 7'd58 : dis_data = 8'h2a; 239 7'd59 : dis_data = 8'hcc; 240 7'd60 : dis_data = 8'h28; 241 7'd61 : dis_data = 8'hfc; 242 7'd62 : dis_data = 8'h29; 243 7'd63 : dis_data = 8'hfc; 244 7'd64 : dis_data = 8'h2a; 245 7'd65 : dis_data = 8'h7e; 246 7'd66 : dis_data = 8'h28; 247 7'd67 : dis_data = 8'h01; 248 7'd68 : dis_data = 8'h29; 249 7'd69 : dis_data = 8'h00; 250 7'd70 : dis_data = 8'haa; 251 7'd71 : dis_data = 8'h00; 252 7'd72 : dis_data = 8'h68; 253 7'd73 : dis_data = 8'h00; 254 7'd74 : dis_data = 8'h38; 255 7'd75 : dis_data = 8'h99;//設置DDRAM地址 256 7'd76 : dis_data = 8'h00;//設置自定義顯示字符編碼 高8bit 是數據不是命令 257 7'd77 : dis_data = 8'h00;//設置自定義顯示字符編碼 低8bit 258 default: dis_data = 8'h00; 259 endcase 260 261 endmodule
顯示效果:這次把星星月亮換成一個小喇叭