20_基于FPGA驱动LCD12864显示图片

20_基于FPGA驱动LCD12864显示图片

实验原理

LCD12864图片显示原理

LCD12864的驱动原理和时序可以参考上一节"LCD12864显示字符",LCD12864显示图片的关键点就是要弄懂一个问题:LCD12864中每一像素点要如何去点亮或者是熄灭。

LCD12864的显示屏简图如下:

20_基于FPGA驱动LCD12864显示图片

    屏幕分为上下半屏,分别为128*32个像素点。而上下分别的控制是通过选择地址进行控制上下半屏的显示。

将屏幕分为:64行*128列,显示顺序为从左上到右下。

显示的驱动框架如下:

20_基于FPGA驱动LCD12864显示图片

分频模块:

将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

显示控制状态机:

    主要分为三个部分:

  1. LCD12864初始化,设置显示模式。
  2. ROM地址的生成
  3. 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取模软件,

20_基于FPGA驱动LCD12864显示图片

  1. 打开要显示的图片,图片的大小是128*64像素,可以选择实验提供的两张图片:

    20_基于FPGA驱动LCD12864显示图片20_基于FPGA驱动LCD12864显示图片

20_基于FPGA驱动LCD12864显示图片

3、设置取模的参数:

20_基于FPGA驱动LCD12864显示图片

4、设置完成之后就点击保存:20_基于FPGA驱动LCD12864显示图片

5、接下来就用BmpToMif.exe软件将.bin文件转换为.Mif文件

20_基于FPGA驱动LCD12864显示图片

打开刚刚生成的.bin文件,设置字长为8

20_基于FPGA驱动LCD12864显示图片

6、点击生成MIF文件

20_基于FPGA驱动LCD12864显示图片

20_基于FPGA驱动LCD12864显示图片

打开MIF文件,查看数据是否正确:

 

硬件原理图

20_基于FPGA驱动LCD12864显示图片

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所有,切勿用于商业! 若引用资料、代码、图片、文字等等请注明出处,谢谢!

   

每日推送不同科技解读,原创深耕解读当下科技,敬请关注微信公众号"科乎"。

20_基于FPGA驱动LCD12864显示图片

上一篇:13_基于FPGA的液晶1602显示


下一篇:PyTorch入门程序