20_基于FPGA驱动LCD12864显示图片
实验原理
LCD12864图片显示原理
LCD12864的驱动原理和时序可以参考上一节"LCD12864显示字符",LCD12864显示图片的关键点就是要弄懂一个问题:LCD12864中每一像素点要如何去点亮或者是熄灭。
LCD12864的显示屏简图如下:
屏幕分为上下半屏,分别为128*32个像素点。而上下分别的控制是通过选择地址进行控制上下半屏的显示。
将屏幕分为:64行*128列,显示顺序为从左上到右下。
显示的驱动框架如下:
分频模块:
将50M的时钟信号进行简单的16分频,输出大概1.5M时钟信号,给显示控制模块。
/******************时钟分频**********************/ always @(posedge clk or negedge rst) begin if(!rst) div_cnt <= 15'd0; else if(div_cnt==15'h4000) begin div_cnt <= 15'd0; clk_div<=~clk_div; end else div_cnt <= div_cnt+ 1'b1; end |
显示控制状态机:
主要分为三个部分:
- LCD12864初始化,设置显示模式。
- ROM地址的生成
- LCD12864行列地址的控制
只要解决了这三个问题,LCD12864的显示控制就基本解决了。下面我们一一讲解:
在显示过程中,行数有64行,每一行有128列,这里我们将这128列分为16个部分,每一部分为8列,这样128*64就变成了16*64,这样显示的最小元素就从一个像素点变成了1*8个像素点。这样就总共需要16*64=1024个数据单元,每个数据单元的位宽为8。这样我们就可以很方便读出数据进行显示了。这里需要创建一个深度为1024,位宽为8的ROM,然后将我们要显示的图片数据保存在这个ROM中,然后通过控制ROM的地址,将显示屏上的每一个元素的对应数据读出送到数据线上。
在知道了数据从哪里来到数据到哪里去之后,接下来就是要解决一个重要问题:ROM的寻址。这里将显示屏分为:行(X)和列(Y)。该工程中采用一个地址计数器进行地址的计数,由于显示屏分为上下半屏,各个屏显示的数据量为512,所以这里就可以用该地址计数器的最高位来控制上下半屏的驱动,当最高位为0时,控制上半屏,为1时控制下半屏。
在设置LCD122864的寄存器的时候将DDRAM的地址计数器(AC)设置为自动增加,这样在每写如一个元素数据之后,LCD12864的行地址就会自动加一,显示的位置就会跳到下一个,就不需要不断往LCD12864里送地址了,只需在每写入一行后,切换列地址。
对应代码段如下:
/****************设定图形显示区X轴地址*****************/ wr_x_addr_1: begin next_state <= wr_x_addr_2; lcd12864_en <= 1'b1; lcd12864_data <= {4'd8,cnt[9],3'd0};//上半屏:0x80;下半屏:0x88 end
wr_x_addr_2: begin next_state <= wr_data_1; lcd12864_data <= {4'd8,cnt[9],3'd0}; end |
同时,将地址计数器送入ROM的地址线,这样就可以对ROM进行寻址。等等,这里只是控制好了X地址,而Y地址的控制是通过每完成显示一行,即送出了16个数据块,Y的地址便加一,然后更新到LCD12864上,由于Y地址是逢16进一的,所以就可以用地址计数器的第8位~第4位对LCD进行列控制。
对应代码段如下:
/********************写图片数据到LCD显存***********************/ wr_y_addr_1: //设定图形显示区Y轴地址 begin next_state <= wr_y_addr_2; lcd12864_data <= {3'b100,cnt[8:4]};//设置列地址(y) lcd12864_en <= 1'b1; end
wr_y_addr_2: begin next_state <= wr_x_addr_1; lcd12864_data <= {3'b100,cnt[8:4]}; end |
在写完64行之后(其实就是一帧)整个显示屏就驱动完成了,最后再转到初始状态不断刷新屏幕。
对应代码段如下:
/****************写数据到图形显示区*****************/ wr_data_1: begin next_state <= wr_data_2; lcd12864_rs <= 1'b1; lcd12864_en <= 1'b1; lcd12864_data <= data; end
wr_data_2://写数据 begin lcd12864_rs<=1'b1; lcd12864_data <= data; if(cnt[3:0] == 4'hf)//写完一行数据(16个) begin if(cnt[9:4] == 7'h3f)//写完一屏数据(64行) next_state <= idle;//写完一屏的数据后重新跳转到初始状态 else next_state <= wr_y_addr_1;//每写完一行数据,重新写入行地址 end else next_state <= wr_data_1;//每写完一行数据,重新写入行地址,地址会自动加一 end |
图片取模与MIF文件的生成
1、打开Image2Lcd取模软件,
- 打开要显示的图片,图片的大小是128*64像素,可以选择实验提供的两张图片:
3、设置取模的参数:
4、设置完成之后就点击保存:
5、接下来就用BmpToMif.exe软件将.bin文件转换为.Mif文件
打开刚刚生成的.bin文件,设置字长为8
6、点击生成MIF文件
打开MIF文件,查看数据是否正确:
硬件原理图
LCD12864的接口与LCD1602接口进行复用,所以使用时需要将PSB置高,作为背光电源,滑动变阻器用来设置背光灯的亮度。
实验代码
/********************************版权声明************************************** ** 大西瓜团队 ** **----------------------------文件信息-------------------------- ** 文件名称: lcd12864.v ** 创建日期: ** 功能描述:实现LCD显示功能 ** 硬件平台:大西瓜第三代开发板,http://daxiguafpga.taobao.com ** 版权声明:本代码属个人知识产权,本代码仅供交流学习. **---------------------------修改文件的相关信息---------------- ** 修改人: ** 修改日期: ** 修改内容: *******************************************************************************/ module LCD12864(clk,rst,lcd12864_rs,lcd12864_rw,lcd12864_en,lcd12864_data,psb); input clk; //系统时钟 input rst; //复位信号 output lcd12864_rs; //1:数据模式;0:指令模式 output lcd12864_rw; //1:读操作;0:写操作 output lcd12864_en; //使能信号,写操作时在下降沿将数据送出;读操作时保持高电平 output psb; output[7:0] lcd12864_data; //LCD数据总线
reg lcd12864_rs; reg lcd12864_en; reg[7:0] lcd12864_data;
reg[3:0] state; //状态机 reg[3:0] next_state; reg[14:0] div_cnt; //分频计数器 reg[9:0] cnt; //写操作计数器 reg cnt_rst; //写操作计数器复位信号 wire[7:0] data; //要显示的数据
reg clk_div; //分频时钟 /********************状态机参数*********************/ parameter idle = 4'b0000, setbase_1 = 4'b0001, setmode_1 = 4'b0010, setcurs_1 = 4'b0111, setexte_1 = 4'b0100, setexte_2 = 4'b1100, wr_y_addr_1 = 4'b1101, wr_y_addr_2 = 4'b1111, wr_x_addr_1 = 4'b1110, wr_x_addr_2 = 4'b1010, wr_data_1 = 4'b1011, wr_data_2 = 4'b1001;
assign lcd12864_rw = 1'b0; //对LCD始终为写操作 assign psb=1'b1; //开背光灯 /******************时钟分频**********************/ always @(posedge clk or negedge rst) begin if(!rst) div_cnt <= 15'd0; else if(div_cnt==15'h4000) begin div_cnt <= 15'd0; clk_div<=~clk_div; end else div_cnt <= div_cnt+ 1'b1; end /**************状态机转向*********/ always @(posedge clk_div or negedge rst) begin if(!rst) state <= idle; else state <= next_state; end /***************************************************************/ always @(posedge clk_div) begin if(cnt_rst)//ROM寻址复位,表示已写入一帧数据 cnt <= 10'd0; else if(state == wr_data_2)//每写入一字节数据,地址加一 cnt <= cnt + 1'b1; end /**********************状态机逻辑*******************************/ always @(state or cnt or data) begin lcd12864_rs <= 1'b0; lcd12864_en <= 1'b0; cnt_rst <= 1'b0; lcd12864_data <= 8'h0; case(state) idle: begin next_state <= setbase_1; end /************************初使化LCD*****************************/ /******************设置为基本指令操作***************/ setbase_1: begin next_state <= setmode_1; lcd12864_data <= 8'h30; lcd12864_en <= 1'b1; end
/*************设定DDRAM 的地址计数器(AC)自动增加********************/ setmode_1: begin next_state <= setcurs_1; lcd12864_data <= 8'h06; lcd12864_en <= 1'b1; end
/**************开显示,关光标,不闪*******************/ setcurs_1: begin next_state <= setexte_1; lcd12864_data <= 8'h0c; lcd12864_en <= 1'b1; end /**************扩充指令操作*******************/ setexte_1: begin next_state<=setexte_2; lcd12864_data <= 8'h36; lcd12864_en <= 1'b1; end
setexte_2: begin next_state <= wr_y_addr_1; lcd12864_data <= 8'h36; cnt_rst <= 1'b1;//图片数据帧复位标志 end
/********************写图片数据到LCD显存***********************/ wr_y_addr_1: //设定图形显示区Y轴地址 begin next_state <= wr_y_addr_2; lcd12864_data <= {3'b100,cnt[8:4]};//设置列地址(y) lcd12864_en <= 1'b1; end
wr_y_addr_2: begin next_state <= wr_x_addr_1; lcd12864_data <= {3'b100,cnt[8:4]}; end /****************设定图形显示区X轴地址*****************/ wr_x_addr_1: begin next_state <= wr_x_addr_2; lcd12864_en <= 1'b1; lcd12864_data <= {4'd8,cnt[9],3'd0};//上半屏:0x80;下半屏:0x88 end
wr_x_addr_2: begin next_state <= wr_data_1; lcd12864_data <= {4'd8,cnt[9],3'd0}; end /****************写数据到图形显示区*****************/ wr_data_1: begin next_state <= wr_data_2; lcd12864_rs <= 1'b1; lcd12864_en <= 1'b1; lcd12864_data <= data; end
wr_data_2://写数据 begin lcd12864_rs<=1'b1; lcd12864_data <= data; if(cnt[3:0] == 4'hf)//写完一行数据(16个) begin if(cnt[9:4] == 7'h3f)//写完一屏数据(64行) next_state <= idle;//写完一屏的数据后重新跳转到初始状态 else next_state <= wr_y_addr_1;//每写完一行数据,重新写入行地址 end else next_state <= wr_data_1;//每写完一行数据,重新写入行地址,地址会自动加一 end
default: next_state<=idle;//跳转到初始状态 endcase end /***************************************************************/ /******************调用ROM(图片数据)*****************************/ rom imagerom(.address (cnt),.clock (clk),.q (data)); endmodule |
实验操作
实验效果
大西瓜FPGA-->https://daxiguafpga.taobao.com
配套开发板:https://item.taobao.com/item.htm?spm=a1z10.1-c.w4004-24211932856.3.489d7241aCjspB&id=633897209972
博客资料、代码、图片、文字等属大西瓜FPGA所有,切勿用于商业! 若引用资料、代码、图片、文字等等请注明出处,谢谢!
每日推送不同科技解读,原创深耕解读当下科技,敬请关注微信公众号"科乎"。