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


    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

    显示控制状态机:

        主要分为三个部分:

    1. LCD12864初始化,设置显示模式。
    2. ROM地址的生成
    3. LCD12864行列地址的控制

    只要解决了这三个问题,LCD12864的显示控制就基本解决了。下面我们一一讲解:

        在显示过程中,行数有64行,每一行有128列,这里我们将这128列分为16个部分,每一部分为8列,这样128*64就变成了16*64,这样显示的最小元素就从一个像素点变成了1*8个像素点。这样就总共需要16*64=1024个数据单元,每个数据单元的位宽为8。这样我们就可以很方便读出数据进行显示了。这里需要创建一个深度为1024,位宽为8ROM,然后将我们要显示的图片数据保存在这个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取模软件,

    1. 打开要显示的图片,图片的大小是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所有,切勿用于商业! 若引用资料、代码、图片、文字等等请注明出处,谢谢!

       

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

  • 相关阅读:
    C#多线程开发中如何更新UI界面控件内容
    C#中Invoke的用法(转)
    while loop, for loop
    basic bash learning 1
    Some useful link for leaning linux shell
    How to Adding ExtendReport in test framework
    如何解决Extent report 无法加载CSS样式 的问题
    Capturing Screenshots
    WebDriver switching to new window
    Data Driven Testing
  • 原文地址:https://www.cnblogs.com/logic3/p/15915930.html
Copyright © 2020-2023  润新知