基于M9K块配置ROM的LCD12864图片显示实验

时间:2021-07-08 19:50:10

先上传三张图片在说

基于M9K块配置ROM的LCD12864图片显示实验                            基于M9K块配置ROM的LCD12864图片显示实验

由于串口传输速度较慢,故此实验是在“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:

基于M9K块配置ROM的LCD12864图片显示实验基于M9K块配置ROM的LCD12864图片显示实验

基于M9K块配置ROM的LCD12864图片显示实验基于M9K块配置ROM的LCD12864图片显示实验

基于M9K块配置ROM的LCD12864图片显示实验

基于M9K块配置ROM的LCD12864图片显示实验

在工程目录下可看到多了“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时(第行),垂直地址(Y)得重新从80开始计数,水平地址(X)得从88开始。故程序中对add_cnt地址进行判断,每当加16次就重新设定垂直地址,i是对行计数。

基于M9K块配置ROM的LCD12864图片显示实验

说了那么多,不知道有没说清楚,就这样吧,代码实现如下:

代码1:

 module LCD12864(
//input
sys_clk,
rst_n, //output
lcd_rs,
lcd_rw,
lcd_en,
lcd_data,
lcd_psb
);
input sys_clk;// 50MHZ
input rst_n; output lcd_rs;//H:data L:command
output lcd_rw;//H:read module L:write module
output lcd_en;//H active
output [:] lcd_data;
output lcd_psb;//H:parallel module L:SPI module
wire [:] dis_data; /***************************************************/
parameter T3MS = 'd149_999;
parameter NUM_64 = 'd63;
parameter IDLE = 'd0,
INIT_FUN_SET1 = 'd1,
INIT_FUN_SET2 = 'd2,
INIT_DISPLAY = 'd3,
INIT_CLEAR = 'd4,
SET_DDRAM_Y = 'd5,
SET_DDRAM_X = 'd6,
WRITE_DATA = 'd7,
STOP = 'd8;
/***************************************************/
//产生周期为6MS的lcd_clk给LCD
reg [:] cnt;
reg lcd_clk;
always @(posedge sys_clk or negedge rst_n)
if(!rst_n) begin
cnt <= 'd0;
lcd_clk <= 'b0;
end
else if(cnt == T3MS)begin
cnt <= 'd0;
lcd_clk <= ~lcd_clk;
end
else
cnt <= cnt + 'b1;
/***************************************************/
reg lcd_rs;
reg [:] state;
reg [:] add_cnt;
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n)
lcd_rs <= 'b0;
else if(state == WRITE_DATA)
lcd_rs <= 'b1; //写数据模式
else
lcd_rs <= 'b0; //写命令模式
/***************************************************/
reg [:] lcd_data;
reg en;
reg [:] i;
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n) begin
state <= IDLE;
lcd_data <= 'hzz;
en <= 'b1;
add_cnt <= 'd0;
i <= 'd0;
end
else
case(state)
IDLE:
begin
state <= INIT_FUN_SET1;
lcd_data <= 'hzz;
en <= 'b1;
end INIT_FUN_SET1:
begin
lcd_data <= 'h36; //CL= 1--8位,扩充指令RE=1,绘图G=1
state <= INIT_FUN_SET2;
end INIT_FUN_SET2:
begin
lcd_data <= 'h36; //CL= 1--8位,扩充指令RE=1,绘图G=1
state <= INIT_DISPLAY;
end INIT_DISPLAY:
begin
lcd_data <= 'h3e; //CL= 1--8位,扩充指令RE=1,绘图G=1
state <= INIT_CLEAR;
end
//其实上面没必要写那么多次数,这是之前并口写的,懒得去掉,故多写几次36
INIT_CLEAR:
begin
lcd_data <= 'h01; //清屏
state <= SET_DDRAM_Y;
end SET_DDRAM_Y: // 先设置垂直地址
begin
if(i <= 'd31)
lcd_data <= 'h80 + i;//80H~9fH
else
lcd_data <= 'h80 + (i-5'd32); //80H~9fH */ state <= SET_DDRAM_X;
end SET_DDRAM_X: //后设置水平地址
begin
if(i <= 'd31)
lcd_data <= 'h80; //80H
else
lcd_data <= 'h88; //88H state <= WRITE_DATA;
end WRITE_DATA:
begin
lcd_data <= dis_data;
add_cnt <= add_cnt + 'b1; if(add_cnt[:] == 'hf/*(add_cnt + 1'b1) % 'd16 == 10'd0*/)begin //计算行
i <= i + 'b1;
if(i == NUM_64)
state <= STOP;
else
state <= SET_DDRAM_Y;
end
else
state <= WRITE_DATA;
end STOP:
begin
en <= 'b0;//显示完了,lcd_e就一直拉为低
state <= STOP;
end default: state <= IDLE;
endcase
/***************************************************/
assign lcd_rw = 'b0;//只有写模式
assign lcd_psb = 'b1;//并口模式
assign lcd_en = en ? lcd_clk : 'b0;
/***************************************************/
//ROM
lcd_rom u1(
.address(add_cnt),
.clock(sys_clk),
.q(dis_data)
);
/***************************************************/
endmodule

其实代码1是有错误的,图片下半部分显示是乱码,检查程序好久都没发现(说明检查的还是不够仔细啊),也没有报错,最终通过仿真查看得知错误在哪,在仿真时要等好长时间后才能看得到哦。

代码2:

 module LCD12864(
//input
sys_clk,
rst_n, //output
lcd_rs,
lcd_rw,
lcd_en,
lcd_data,
lcd_psb
);
input sys_clk;// 50MHZ
input rst_n; output lcd_rs;//H:data L:command
output lcd_rw;//H:read module L:write module
output lcd_en;//H active
output [:] lcd_data;
output lcd_psb;//H:parallel module L:SPI module
wire [:] dis_data; /***************************************************/
`define const_32 'd32
`define const_63 'd63
/***************************************************/
parameter T3MS = 'd149_999;
parameter IDLE = 'd0,
INIT_FUN_SET1 = 'd1,
INIT_FUN_SET2 = 'd2,
INIT_DISPLAY = 'd3,
INIT_CLEAR = 'd4,
SET_DDRAM_Y = 'd5,
SET_DDRAM_X = 'd6,
WRITE_DATA = 'd7,
STOP = 'd8;
/***************************************************/
//产生周期为6MS的lcd_clk给LCD
reg [:] cnt;
reg lcd_clk;
always @(posedge sys_clk or negedge rst_n)
if(!rst_n) begin
cnt <= 'd0;
lcd_clk <= 'b0;
end
else if(cnt == T3MS)begin
cnt <= 'd0;
lcd_clk <= ~lcd_clk;
end
else
cnt <= cnt + 'b1;
/***************************************************/
reg lcd_rs;
reg [:] state;
reg [:] add_cnt;
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n)
lcd_rs <= 'b0;
else if(state == WRITE_DATA)
lcd_rs <= 'b1; //写数据模式
else
lcd_rs <= 'b0; //写命令模式
/***************************************************/
reg [:] lcd_data;
reg en;
reg [:] i;
always @(posedge lcd_clk or negedge rst_n)
if(!rst_n) begin
state <= IDLE;
lcd_data <= 'hzz;
en <= 'b1;
add_cnt <= 'd0;
i <= 'd0;
end
else
case(state)
IDLE:
begin
state <= INIT_FUN_SET1;
lcd_data <= 'hzz;
en <= 'b1;
end INIT_FUN_SET1:
begin
lcd_data <= 'h36; //CL= 1--8位,扩充指令RE=1,绘图G=1
state <= INIT_FUN_SET2;
end INIT_FUN_SET2:
begin
lcd_data <= 'h36; //CL= 1--8位,扩充指令RE=1,绘图G=1
state <= INIT_DISPLAY;
end INIT_DISPLAY:
begin
lcd_data <= 'h3e; //CL= 1--8位,扩充指令RE=1,绘图G=1
state <= INIT_CLEAR;
end
//其实上面没必要写那么多次数,这是之前并口写的,懒得去掉,故多写几次36
INIT_CLEAR:
begin
lcd_data <= 'h01; //清屏
state <= SET_DDRAM_Y;
end SET_DDRAM_Y: // 先设置垂直地址
begin
if(i < `const_32)
lcd_data <= 'h80 + i;//80H~9fH
else
lcd_data <= 'h80 + (i - `const_32); //80H~9fH/ state <= SET_DDRAM_X;
end SET_DDRAM_X: //后设置水平地址
begin
if(i < `const_32)
lcd_data <= 'h80; //80H
else
lcd_data <= 'h88; //88H state <= WRITE_DATA;
end WRITE_DATA:
begin
lcd_data <= dis_data;
add_cnt <= add_cnt + 'b1; if(add_cnt[:] == 'hf/*(add_cnt + 1'b1) % 'd16 == 10'd0*/)begin //计算行
i <= i + 'b1;
if(i == `const_63)
state <= STOP;
else
state <= SET_DDRAM_Y;
end
else
state <= WRITE_DATA;
end STOP:
begin
en <= 'b0;//显示完了,lcd_e就一直拉为低
state <= STOP;
end default: state <= IDLE;
endcase
/***************************************************/
assign lcd_rw = 'b0;//只有写模式
assign lcd_psb = 'b1;//并口模式
assign lcd_en = en ? lcd_clk : 'b0;
/***************************************************/
//ROM
lcd_rom u1(
.address(add_cnt),
.clock(sys_clk),
.q(dis_data)
);
/***************************************************/
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的实验将结束。