先上传三张图片在说
由于串口传输速度较慢,故此实验是在“LCD12864 液晶显示-汉字及自定义显示(并口)”基础上进一步修改而来。在写代码之前还是得先搞清楚每一步的动作,具体步骤如下:
一、先找到一张128*64大小的图片,自己也可以通过系统自带的“画图”工具进行调整,最终保存为"单色图.bmp"格式。最好找一张比较简单的图片。
二、图片通过“字模.EXE”软件提取出数据,总不能像之前那样把一个个数据赋值给dis_data,那工作量太大了,说不定中间还会弄错。可以用一个简单的办法把这些数据放置到FPGA内部自带的ROM中,通过调用在把数据从ROM中提取出来(其实FPGA内部并没有专用的ROM硬件资源,实现ROM的思路是对RAM赋予初值,并保持该初值)。
a、首先新建一个文本,命名为xx.c格式的文件这里是logo.c,打开,在该文件里面定义一个数组.
unsigned char code tab[] = {0x00,0x00.....0x00}; 这个数组大小我们可以算出是1024。
b、打开keli软件,新建一个工程,芯片随便选一个ATxx类型就可以,然后把该文件添加到该工程中,按"Alt + F7",弹出一个“options for target 'target 1'”,选择"output",勾上“Creat Hex file”,点OK,按F7进行编译,编译成功后,可在该工程目录下看到logo.hex文件。
c、用Quartus 软件打开建立的LCD12864工程,按照如下图所示进行操作建立一个ROM:
在工程目录下可看到多了“lcd_rom.v”和“lcd_rom_bb.v”,打开“lcd_rom.v”,复制“lcd_rom (address, clock,q);”到LCD12864.v中,并把相关接口传进去lcd_rom u1(.address(add_cnt), .clock(sys_clk), .q(dis_data)); ,OK,ROM已经加载好了。
三、得知道LCD128*64显示图片的原理
a、图片初始化与汉字初始化有所不同,图片需要用到扩展指令。RE=1,并要把绘图开关打开G=1,这里要设置成0x36就可以了。
b、通过手册都知道,在显示之前,要设置垂直地址(Y)和水平地址(X),要连续发送给LCD的。由于绘图RAM 的地址计数器(AC)只会对水平地址(X )自动加一,当水平地址加0FH(16)次时,会重新设为00H 但并不会对垂直地址做进位自动加一,故当连续写入多个数据时,要判断垂直地址是否需重新设定。如下图,是一张图片所提取的数据(16*64 = 1024),一个数据占8bit,故一行有8*16= 128bit,一列有64行,共128*64。某一bit就是LCD128*64中的某一个点阵。而且对于液晶显示,只要把液晶哪个点阵接上高电平(相应bit置1),哪个点阵就点亮了。垂直地址就要注意了,当垂直地址(Y)加到9f时(第31行),垂直地址(Y)得重新从80开始计数,水平地址(X)得从88开始。故程序中对add_cnt地址进行判断,每当加16次就重新设定垂直地址,i是对行计数。
说了那么多,不知道有没说清楚,就这样吧,代码实现如下:
代码1:
1 module LCD12864( 2 //input 3 sys_clk, 4 rst_n, 5 6 //output 7 lcd_rs, 8 lcd_rw, 9 lcd_en, 10 lcd_data, 11 lcd_psb 12 ); 13 input sys_clk;// 50MHZ 14 input rst_n; 15 16 output lcd_rs;//H:data L:command 17 output lcd_rw;//H:read module L:write module 18 output lcd_en;//H active 19 output [7:0] lcd_data; 20 output lcd_psb;//H:parallel module L:SPI module 21 wire [7:0] dis_data; 22 23 /***************************************************/ 24 parameter T3MS = 18'd149_999; 25 parameter NUM_64 = 6'd63; 26 parameter IDLE = 4'd0, 27 INIT_FUN_SET1 = 4'd1, 28 INIT_FUN_SET2 = 4'd2, 29 INIT_DISPLAY = 4'd3, 30 INIT_CLEAR = 4'd4, 31 SET_DDRAM_Y = 4'd5, 32 SET_DDRAM_X = 4'd6, 33 WRITE_DATA = 4'd7, 34 STOP = 4'd8; 35 /***************************************************/ 36 //产生周期为6MS的lcd_clk给LCD 37 reg [17:0] cnt; 38 reg lcd_clk; 39 always @(posedge sys_clk or negedge rst_n) 40 if(!rst_n) begin 41 cnt <= 18'd0; 42 lcd_clk <= 1'b0; 43 end 44 else if(cnt == T3MS)begin 45 cnt <= 18'd0; 46 lcd_clk <= ~lcd_clk; 47 end 48 else 49 cnt <= cnt + 1'b1; 50 /***************************************************/ 51 reg lcd_rs; 52 reg [3:0] state; 53 reg [9:0] add_cnt; 54 always @(posedge lcd_clk or negedge rst_n) 55 if(!rst_n) 56 lcd_rs <= 1'b0; 57 else if(state == WRITE_DATA) 58 lcd_rs <= 1'b1; //写数据模式 59 else 60 lcd_rs <= 1'b0; //写命令模式 61 /***************************************************/ 62 reg [7:0] lcd_data; 63 reg en; 64 reg [5:0] i; 65 always @(posedge lcd_clk or negedge rst_n) 66 if(!rst_n) begin 67 state <= IDLE; 68 lcd_data <= 8'hzz; 69 en <= 1'b1; 70 add_cnt <= 10'd0; 71 i <= 5'd0; 72 end 73 else 74 case(state) 75 IDLE: 76 begin 77 state <= INIT_FUN_SET1; 78 lcd_data <= 8'hzz; 79 en <= 1'b1; 80 end 81 82 INIT_FUN_SET1: 83 begin 84 lcd_data <= 8'h36; //CL= 1--8位,扩充指令RE=1,绘图G=1 85 state <= INIT_FUN_SET2; 86 end 87 88 INIT_FUN_SET2: 89 begin 90 lcd_data <= 8'h36; //CL= 1--8位,扩充指令RE=1,绘图G=1 91 state <= INIT_DISPLAY; 92 end 93 94 INIT_DISPLAY: 95 begin 96 lcd_data <= 8'h3e; //CL= 1--8位,扩充指令RE=1,绘图G=1 97 state <= INIT_CLEAR; 98 end 99 //其实上面没必要写那么多次数,这是之前并口写的,懒得去掉,故多写几次36 100 INIT_CLEAR: 101 begin 102 lcd_data <= 8'h01; //清屏 103 state <= SET_DDRAM_Y; 104 end 105 106 SET_DDRAM_Y: // 先设置垂直地址 107 begin 108 if(i <= 5'd31) 109 lcd_data <= 8'h80 + i;//80H~9fH 110 else 111 lcd_data <= 8'h80 + (i-5'd32); //80H~9fH */ 112 113 state <= SET_DDRAM_X; 114 end 115 116 SET_DDRAM_X: //后设置水平地址 117 begin 118 if(i <= 5'd31) 119 lcd_data <= 8'h80; //80H 120 else 121 lcd_data <= 8'h88; //88H 122 123 state <= WRITE_DATA; 124 end 125 126 WRITE_DATA: 127 begin 128 lcd_data <= dis_data; 129 add_cnt <= add_cnt + 1'b1; 130 131 if(add_cnt[3:0] == 4'hf/*(add_cnt + 1'b1) % 10'd16 == 10'd0*/)begin //计算行 132 i <= i + 1'b1; 133 if(i == NUM_64) 134 state <= STOP; 135 else 136 state <= SET_DDRAM_Y; 137 end 138 else 139 state <= WRITE_DATA; 140 end 141 142 STOP: 143 begin 144 en <= 1'b0;//显示完了,lcd_e就一直拉为低 145 state <= STOP; 146 end 147 148 default: state <= IDLE; 149 endcase 150 /***************************************************/ 151 assign lcd_rw = 1'b0;//只有写模式 152 assign lcd_psb = 1'b1;//并口模式 153 assign lcd_en = en ? lcd_clk : 1'b0; 154 /***************************************************/ 155 //ROM 156 lcd_rom u1( 157 .address(add_cnt), 158 .clock(sys_clk), 159 .q(dis_data) 160 ); 161 /***************************************************/ 162 endmodule
其实代码1是有错误的,图片下半部分显示是乱码,检查程序好久都没发现(说明检查的还是不够仔细啊),也没有报错,最终通过仿真查看得知错误在哪,在仿真时要等好长时间后才能看得到哦。
代码2:
1 module LCD12864( 2 //input 3 sys_clk, 4 rst_n, 5 6 //output 7 lcd_rs, 8 lcd_rw, 9 lcd_en, 10 lcd_data, 11 lcd_psb 12 ); 13 input sys_clk;// 50MHZ 14 input rst_n; 15 16 output lcd_rs;//H:data L:command 17 output lcd_rw;//H:read module L:write module 18 output lcd_en;//H active 19 output [7:0] lcd_data; 20 output lcd_psb;//H:parallel module L:SPI module 21 wire [7:0] dis_data; 22 23 /***************************************************/ 24 `define const_32 6'd32 25 `define const_63 6'd63 26 /***************************************************/ 27 parameter T3MS = 18'd149_999; 28 parameter IDLE = 4'd0, 29 INIT_FUN_SET1 = 4'd1, 30 INIT_FUN_SET2 = 4'd2, 31 INIT_DISPLAY = 4'd3, 32 INIT_CLEAR = 4'd4, 33 SET_DDRAM_Y = 4'd5, 34 SET_DDRAM_X = 4'd6, 35 WRITE_DATA = 4'd7, 36 STOP = 4'd8; 37 /***************************************************/ 38 //产生周期为6MS的lcd_clk给LCD 39 reg [17:0] cnt; 40 reg lcd_clk; 41 always @(posedge sys_clk or negedge rst_n) 42 if(!rst_n) begin 43 cnt <= 18'd0; 44 lcd_clk <= 1'b0; 45 end 46 else if(cnt == T3MS)begin 47 cnt <= 18'd0; 48 lcd_clk <= ~lcd_clk; 49 end 50 else 51 cnt <= cnt + 1'b1; 52 /***************************************************/ 53 reg lcd_rs; 54 reg [3:0] state; 55 reg [9:0] add_cnt; 56 always @(posedge lcd_clk or negedge rst_n) 57 if(!rst_n) 58 lcd_rs <= 1'b0; 59 else if(state == WRITE_DATA) 60 lcd_rs <= 1'b1; //写数据模式 61 else 62 lcd_rs <= 1'b0; //写命令模式 63 /***************************************************/ 64 reg [7:0] lcd_data; 65 reg en; 66 reg [5:0] i; 67 always @(posedge lcd_clk or negedge rst_n) 68 if(!rst_n) begin 69 state <= IDLE; 70 lcd_data <= 8'hzz; 71 en <= 1'b1; 72 add_cnt <= 10'd0; 73 i <= 6'd0; 74 end 75 else 76 case(state) 77 IDLE: 78 begin 79 state <= INIT_FUN_SET1; 80 lcd_data <= 8'hzz; 81 en <= 1'b1; 82 end 83 84 INIT_FUN_SET1: 85 begin 86 lcd_data <= 8'h36; //CL= 1--8位,扩充指令RE=1,绘图G=1 87 state <= INIT_FUN_SET2; 88 end 89 90 INIT_FUN_SET2: 91 begin 92 lcd_data <= 8'h36; //CL= 1--8位,扩充指令RE=1,绘图G=1 93 state <= INIT_DISPLAY; 94 end 95 96 INIT_DISPLAY: 97 begin 98 lcd_data <= 8'h3e; //CL= 1--8位,扩充指令RE=1,绘图G=1 99 state <= INIT_CLEAR; 100 end 101 //其实上面没必要写那么多次数,这是之前并口写的,懒得去掉,故多写几次36 102 INIT_CLEAR: 103 begin 104 lcd_data <= 8'h01; //清屏 105 state <= SET_DDRAM_Y; 106 end 107 108 SET_DDRAM_Y: // 先设置垂直地址 109 begin 110 if(i < `const_32) 111 lcd_data <= 8'h80 + i;//80H~9fH 112 else 113 lcd_data <= 8'h80 + (i - `const_32); //80H~9fH/ 114 115 state <= SET_DDRAM_X; 116 end 117 118 SET_DDRAM_X: //后设置水平地址 119 begin 120 if(i < `const_32) 121 lcd_data <= 8'h80; //80H 122 else 123 lcd_data <= 8'h88; //88H 124 125 state <= WRITE_DATA; 126 end 127 128 WRITE_DATA: 129 begin 130 lcd_data <= dis_data; 131 add_cnt <= add_cnt + 1'b1; 132 133 if(add_cnt[3:0] == 4'hf/*(add_cnt + 1'b1) % 10'd16 == 10'd0*/)begin //计算行 134 i <= i + 1'b1; 135 if(i == `const_63) 136 state <= STOP; 137 else 138 state <= SET_DDRAM_Y; 139 end 140 else 141 state <= WRITE_DATA; 142 end 143 144 STOP: 145 begin 146 en <= 1'b0;//显示完了,lcd_e就一直拉为低 147 state <= STOP; 148 end 149 150 default: state <= IDLE; 151 endcase 152 /***************************************************/ 153 assign lcd_rw = 1'b0;//只有写模式 154 assign lcd_psb = 1'b1;//并口模式 155 assign lcd_en = en ? lcd_clk : 1'b0; 156 /***************************************************/ 157 //ROM 158 lcd_rom u1( 159 .address(add_cnt), 160 .clock(sys_clk), 161 .q(dis_data) 162 ); 163 /***************************************************/ 164 endmodule
代码2是正确的,代码1错在哪呢,就是i的位宽弄错了,代码2中用宏定义替代数字直接与i的比较。哎,不小心写错了,尤其是软件不提醒也不报警告的那种错误,势必要害了自己啊。。。。。。。
注意:if(add_cnt[3:0] == 4'hf/*(add_cnt + 1'b1) % 10'd16 == 10'd0*/),这两种写法下到板子验证都正确,后面“取于”方式,套用了C语言的方式,建议不采取,一不规范,二也很少看到别人这样写,在群里问别人,说经常写这样不规范的代码,日后势必给自己增加痛苦,面试时会被人家鄙视,果断采取add_cnt[3:0] == 4'hf这种方式。
到此,LCD12864的实验将结束。