FPGA笔记(八)-驱动12864

FPGA笔记(八)-驱动12864

最近俩天由于要求所致,必须马上要会使用FPGA驱动12864和驱动矩阵键盘,虽然之前用C51做过这类驱动,毕竟不一样,所以时间上也是很匆忙。通过各种网上找资料和实验,今天算是完成了LCD12864的驱动的学习。赶紧做个笔记巩固一下!


要驱动12864首先得它的datasheet,看明白主要的几个问题:

1、LCD的时钟频率所在范围。(FPGA一般都是20M、50M及以上晶振,太大导致LCD无法响应过来)下图为RT12864-s液晶的所需时钟频率

FPGA笔记(八)-驱动12864_第1张图片

范围为470KHZ-590KHZ。(如果小了的话也没关系,只不过是显示的时候一个字一个字的显示出来,而不是人眼察觉不到地整篇出现)

2、整个的工作流程。(初始化->写数,从而可以用状态机来描述)


我记得在单片机里驱动液晶的话,是这样的

把写命令和写数据的指令的时序和RW、RS、EN设置好,方便后续调用

void write_data(int data)

{

。。。。

。。。。

}

void write_com(int com)

{

。。。。

}

//设置好初始化的指令

void initialLCD_12864()

{

write_com(0x80);

。。。。。等等一些列的初始化指令

}


FPGA驱动液晶显示,也差不多,一样是得先初始化后再进行写数字,只不过是用状态机来实现这一顺序的过程,啥也不用多说,看程序吧。

module Lcd12864(//针对型号为RT12864-S
						input Sys_Clk,
						input Rst,
						output reg LCD_RS,
						output LCD_RW,
						output LCD_E,
						output reg [7:0]LCD_Data
				//		output PSB//串并控制端口,H为并行,L为串行,直接接5v
				//		output LCD_Rst,//液晶的复位端口,低电平有效
				//由于端口不够,暂时让其悬空
						);
				reg LCD_Clk;
				reg[7:0] state;//状态机寄存器
				reg [23:0] cnt;//计数器
				reg flag;//显示完成标志
				reg[5:0]char_cnt;
				reg[7:0]data_disp;//一个字节是八位,一个英文字符是一个字节,中文是俩个字节
				parameter T500KHZ=24'd49999;
			//	parameter T500KHZ=24'd24_999_999;//测试低频现象
			//	parameter T500KHZ=24'd24_9;//仿真专用
				//首先先对系统频率分频,液晶所需频率不用这么高,但是是多少呢?由datasheet可得出该液晶的最大频率是590kHZ
				//典型值为530KH,为了好算取个值500KHZ,50M/500KHZ=10HZ,计数5M/2变化一次方向
				always@(posedge Sys_Clk or negedge Rst)
				begin
						if(!Rst)
							begin
									cnt<=24'd0;
									LCD_Clk<=1'b0;
							end
						else  if(cnt==T500KHZ)
									begin
										cnt<=24'd0;
										LCD_Clk<=~LCD_Clk;
									end
								else
									cnt<=cnt+1'b1;
				end
				
				
				//state machine description,8个状态 只要用把位二进制就可以全部表示
				parameter IDLE=8'b00_000_000;//初始状态
				parameter SETFUNCTION=8'b00_000_001;//功能设置,8-bit+基本指令集0x30
			//	parameter SETFUNCTION2=8'b00_000_010;//同上
				parameter SWITCHMODE=8'b00_000_100;//设置显示开和光标闪烁关闭
				parameter CLEAR=8'b00_001_000;//清屏操作
				parameter SETMODE=8'b00_010_000;//点设置
				parameter SETDDRAM=8'b00_100_000;//起始行设置
				parameter WRITERAM=8'b01_000_000;//写设置
				parameter STOP=8'b10_000_000;//LCD操作停止,释放其控制
			
				initial
				begin
					cnt=24'd0;
					state=IDLE;
					flag=1'b1;
					LCD_Clk=1'b10;
					char_cnt=6'd0;
					data_disp=8'd32;
				end
				
				//设置好RS、RW、E
				always@(posedge LCD_Clk or negedge Rst)
				begin
						if(!Rst)
							LCD_RS<=1'b0;
						else	if(state==WRITERAM)
									LCD_RS<=1'b1;
								else
									LCD_RS<=1'b0;
				end
				
				//如果定义了LCD_Rst俩个端口的话,可做如下设置
				//assign LCD_Rst=1'b1;
				//assign PSB=1'b1;
				assign LCD_RW=1'b0;//只是写操作,不需要读操作
				assign LCD_E=(flag==1)?LCD_Clk:1'b0;//使能信号与液晶时钟同步
				
				//descible the state machine
				always@(posedge LCD_Clk or negedge Rst)
				begin
						if(!Rst)
							begin
									state<=IDLE;
									LCD_Data<=8'bzz_zzz_zzz;
							end
						else
								begin
										case(state)
												IDLE:
														begin
																state<=SETFUNCTION;
																LCD_Data<=8'h30;
														end
												SETFUNCTION:
														begin
																state<=SWITCHMODE;
															//	state<=SETFUNCTION2;
																LCD_Data<=8'h30;
														end
										//		SETFUNCTION2:
											//			begin	
											//					state<=SWITCHMODE;
											//					LCD_Data<=8'h30;
											//			end
												SWITCHMODE:
														begin
																state<=CLEAR;
																LCD_Data<=8'h0c;//显示设置,全显示开,光标和闪烁关
														end
												CLEAR:
														begin
																state<=SETMODE;
																LCD_Data<=8'h01;//清屏、
														end
												SETMODE:
														begin
																state<=SETDDRAM;
																LCD_Data<=8'h06;//点设置,光标右移,地址加一,整体不动
														end
												SETDDRAM://设置起始位置
														begin
																state<=WRITERAM;
																if(char_cnt==6'd0)
																	LCD_Data<=8'h80;//line1
																else
																	LCD_Data<=9'h90;//line2
														end
												WRITERAM:
														begin
																if(char_cnt<=6'd11)
																	begin
																			char_cnt<=char_cnt+1'b1;
																			LCD_Data<=data_disp;
																			if(char_cnt==6'd11)
																				state<=SETDDRAM;//第一行写完后从新返回设置地址
																			else
																				state<=WRITERAM;
																	end
																else if(char_cnt>=6'd12&&char_cnt<=6'd25)
																			begin
																				if(char_cnt==6'd25)
																					begin
																							state<=STOP;
																							char_cnt=6'd0;
																							flag<=1'b0;
																					end
																				else
																					begin	
																						LCD_Data<=data_disp;//不管到没到第25个都要继续写数
																						state<=WRITERAM;
																						char_cnt<=char_cnt+1'b1;
																					end
																					
																			end
															
														
														end
											STOP:		state<=STOP;
											default: state<=IDLE;
									endcase
							end			
				end
				
				always@(char_cnt)
				begin
						case(char_cnt)
							6'd0:data_disp="G";
							6'd1:data_disp="U";
							6'd2:data_disp="X";
							6'd3:data_disp="I";
							6'd4:data_disp="A";
							6'd5:data_disp="N";
							6'd6:data_disp="Y";
							6'd7:data_disp="I";
							6'd8:data_disp="L";
							6'd9:data_disp="C";
							6'd10:data_disp="D";
							6'd11:data_disp="!";
							
							6'd12:data_disp="H";
							6'd13:data_disp="E";
							6'd14:data_disp="L";
							6'd15:data_disp="L";
							6'd16:data_disp="O";
							6'd17:data_disp="E";
							6'd18:data_disp="V";
							6'd19:data_disp="E";
							6'd20:data_disp="R";
							6'd21:data_disp="Y";
							6'd22:data_disp="O";
							6'd23:data_disp="N";
							6'd24:data_disp="E";
					//		default:data_disp=8'h32;
						endcase
						
				end
				
endmodule

我所使用的12864型号为RT12864-S,各个引脚功能如下

FPGA笔记(八)-驱动12864_第2张图片

所以可以清楚的看到RS、RW、EN等引脚的使用方法

FPGA笔记(八)-驱动12864_第3张图片

我从这个时序图看到了只有当RS=H,RW=L,在一个使能周期内完成数据的读取,也就是说使能信号是一个周期信号

对于程序中的assign LCD_E=(flag==1)?LCD_Clk:1'b0;//使能信号与液晶时钟同步。


程序中来看状态机大概就是这样一个流程(开始->设置8bit控制+基本指令集(0x30)->设置全显示开、光标关、闪烁关(0x0c)->清屏(0x01)->设置输入方式,数据读写后,光标右移,地址加一,整体画面不动(0x06)->设置写数的起始地址(0x80、0x90、0x88,0x98)->写数(对应ASCI来写字符)->结束),我就懒得去画图了,凑合看吧,饿死我了!

最后再想,既然初始化都是顺序执行,为什么还要状态机,在一个begin-end内不就完成了初始化吗?(如果你这么想,那么请看上述时序图,不管是写数还是写命令都是在一个使能周期内完成的,而这个周期是有时间限制的,而不是俩句程序语句之间的时间周期)

看一下仿真图就更加清晰明了了。任何操作都是一步一步来的,一个使能周期内只能写一次数据或命令

FPGA笔记(八)-驱动12864_第4张图片

对应这看这些二进制数(三个使能周期的00110000(0x30)(分别是initial+SETFUNCTION+SETFUNCTION2),而后是00001100(0x0c)依次一下。。。。),时序上面完全符合。


其实我很想知道为什么要俩个SETFUNCTION状态,只不过是多了一个周期的0x30指令而已,于是我把SETFUNCTION2这个状态去掉了,看看情况,结果液晶一样能正常显示出数!如网上所写的程序,说第二个SETFUNCTION2给的数(0x30)是清屏操作,很明显看datasheet里的基本指令集所写更本不是什么清屏指令,只是指明8-bit控制和指令集是基本指令集

FPGA笔记(八)-驱动12864_第5张图片

一样是可以的,所以学习不是拿来主义,多问一句为什么,可以让我学到更多的东西,更希望把它变为自己的东西。而不是下次还得抄袭。

你可能感兴趣的:(FPGA)