话不多说先上图
前言
在做这个实验的时候在网上找了许多资料,都是关于使用单片机驱动LCD显示,确实用单片机驱动是要简单不少,记得在FPGA学习交流群里问问题的时候,被前辈指教,说给我最好的指教便是别在玩这个了,多看看关于FPGA方面的书籍,比做这个单片机做的东西价值强多了。现在想来确实,自从学习FPGA以来,看过的书没有多少,只是想做个什么了,就在网上找找例程,照抄下来,把算法推理一遍,下个板子实现了,便以为自己会了懂了,要是自己在写一个便问题百出。那么菜鸟始终是菜鸟。自己根本没有掌握FPGA的设计思想和优势,用着FPGA做着单片机的东西,这种东西练练手就足够了,所以,做完这个实验LCD就到此为止了,我也要好好想想该如何进行下面的学习了。在这个上面我还是花费了不少时间,所以写一篇博文总结记录想,回头看的时候,以警醒自己。
摘要
做完这个实验我最大的感触是一定要会读数据手册。拿到元件(LCD12864),上面有16个的引脚,先要搞清楚每个引脚的功能,这时候就一定要会读数据手册。我个qc12864b的中文手册琢磨了很久才看懂了一些简单的操作指令,包括在手册上要提取出来,元件的驱动时序,扫描时钟,引脚定义,操作指令,功能描述等。
LCD显示原理
扫描时钟
从手册上可以读出,qc12864b(这是我所使用LCD12864的型号)的扫描时钟介于470—590khz之间,最适为530khz,为了方便分频,所以取500khz。
引脚说明
VSS 电源地,0v ——接开发板GND就行
VDD 电源正极(5v,3.3v)
VO 液晶显示偏压,调整对比度
RS RS=1时,当mpu进行读模块操作,指向地址寄存器
当mpu进行写模块操作,指向指令寄存器
RS=0时,无论进行读/写操作,都指向数据寄存器
R/W 高电平读操作,低电平写操作
E 使能信号,高电平读取信息,下降沿执行命令
DB0 数据总线第0位
DB1 数据总线第1位
DB2 数据总线第2位
DB3 数据总线第3位
DB4 数据总线第4位
DB5 数据总线第5位
DB6 数据总线第6位
DB7 数据总线第7位
PSB 串并控制端口,H为并行,L为串行,直接接5v
RST 液晶复位端口,低电平有效
A 背光正极输入,调整电压大小可以调整亮度
K 背光负极输入,一般直接接地
指令说明
具体指令说明,这里就不一一列出,可查阅qc12864b中文数据手册,我在阅读这一部分的时候耗费了很长时间,这些指令是驱动LCD显示文字与图片的核心,所以必须要掌握。
并口显示时序
Verilog实现方案
从时序图可以看出在使能信号的一个周期内可以完成写入数据操作,扫描时钟与使能信号周期相同即可。
状态转移图
状态机初始化位置 IDLE lcd_data = 8’hzz
功能设计 SETFUNCTION lcd_data = 8’h36//扩充指令绘图显示
显示设置 SWITCHMODE lcd_data = 8’h0c//显示状态
清屏 CLEAR lcd_data = 8’h01//清零指令
设置Y坐标 SETDDRAM_Y
设置X坐标 SETDDRAM_X
写入数据 WRITERAM
结束 STOP
绘图指令下的坐标图
代码如下
1 module lcd_qc12864b( 2 input mclk, 3 input rst_n, 4 output reg lcd_rs,//H读写数据,L指令 5 output lcd_rw, //高电平读操作,低电平写操作 6 output lcd_e,//使能信号高电平有效 7 output reg [7:0]lcd_data,//八位数据总线 8 output psb,//串并控制端口,H为并行,L为串行,直接接5v 9 output lcd_rst//液晶的复位端口,低电平有效 10 ); 11 12 reg lcd_clk;//需要500khz频率的时钟 13 reg [7:0] state;//状态机寄存器 14 reg [23:0] cnt;//分频驱动12864计数器 15 reg flag = 1'b1;//显示完成标志 16 reg [9:0] char_cnt;// 17 wire [7:0] data_disp;//一个字节是八位,一个英文字符是一个字节,中文是俩个字节 18 reg [5:0] cnt1;//行计数 19 20 parameter T500KHZ=24'd49999; 21 22 //分频500khz时钟 23 always @(posedge mclk or negedge rst_n) 24 begin 25 if(!rst_n)begin 26 lcd_clk <= 1'b0; 27 cnt <= 24'd0; 28 end 29 else if(cnt == T500KHZ)begin 30 cnt <= 24'd0; 31 lcd_clk <= ~lcd_clk; 32 end 33 else 34 cnt <= cnt + 1'd1; 35 end 36 37 //状态机8个状态设置 38 parameter IDLE = 8'b00_000_000,//初始状态 39 SETFUNCTION = 8'b00_000_001,//功能设置 40 SWITCHMODE = 8'b00_000_010,//设置显示开和光标闪烁关闭 41 CLEAR = 8'b00_000_100,//清屏操作 42 SETDDRAM_Y = 8'b00_010_000,//设置y轴坐标 43 SETDDRAM_X = 8'b00_100_000,//设置x轴坐标 44 WRITERAM = 8'b01_000_000,//写设置写入寄存器 45 STOP = 8'b10_000_000;//LCD操作停止,释放其控制 46 47 //三段式书写状态机 48 always @(posedge lcd_clk or negedge rst_n) 49 begin 50 if(!rst_n) 51 lcd_rs <= 1'b0; 52 else begin 53 if(state == WRITERAM) 54 lcd_rs <= 1'b1;//写数据模式 55 else 56 lcd_rs <= 1'b0;//写命令模式 57 end 58 end 59 60 //lcd_rst始终为高电平,psb始终为低电平即可 61 assign lcd_rst = 1'b1; 62 assign psb = 1'b1; 63 assign lcd_rw = 1'b0;//只是写操作,不需要读操作 64 assign lcd_e = (flag==1)?lcd_clk:1'b0;//使能信号与液晶时钟同步 65 66 always @(posedge lcd_clk or negedge rst_n) 67 begin 68 if(!rst_n)begin 69 cnt1<= 1'b0; 70 state <= IDLE; 71 lcd_data <= 8'hzz; 72 char_cnt <= 6'd0; 73 end 74 else begin 75 case(state) 76 IDLE:begin 77 state <= SETFUNCTION;//功能设置,8-bit+基本指令集0x30 78 lcd_data <= 8'h36; 79 end 80 81 SETFUNCTION:begin 82 state <= SWITCHMODE;//设置显示开和光标闪烁关闭 83 lcd_data <= 8'h36; 84 end 85 86 SWITCHMODE:begin 87 state <= CLEAR; 88 lcd_data <= 8'h3e;//显示设置,全显示开,光标和闪烁关 89 end 90 CLEAR:begin//清屏操作 91 state <= SETDDRAM_Y;//点设置 92 lcd_data <= 8'h01;//清屏 93 end 94 SETDDRAM_Y:begin 95 if(cnt1< 32) 96 lcd_data <= 8'h80 + cnt1;//80H~9fH 97 else 98 lcd_data <= 8'h80 + (cnt1- 32); //80H~9fH 99 100 state <= SETDDRAM_X; 101 end 102 SETDDRAM_X:begin 103 if(cnt1< 32) 104 lcd_data <= 8'h80; //80H 105 else 106 lcd_data <= 8'h88; //88H 107 state <= WRITERAM; 108 end 109 WRITERAM:begin 110 lcd_data <= data_disp; 111 char_cnt <= char_cnt + 1'b1; 112 if(char_cnt[3:0] == 4'hf)begin //计算行 113 cnt1<= cnt1+ 1'b1; 114 if(cnt1== 63) 115 state <= STOP; 116 else 117 state <= SETDDRAM_Y; 118 end 119 else 120 state <= WRITERAM; 121 end 122 STOP:begin 123 flag <= 1'b0; 124 state <= STOP;//LCD操作停止,释放其控制 125 end 126 default: state <= IDLE;//回到初始状态 127 endcase 128 end 129 end 130 131 // ROM 132 rom U1_rom ( 133 .clka(mclk), 134 .addra(char_cnt), 135 .douta(data_disp) 136 ); 137 138 endmodule
配置ROM
有分布式ROM/ROM和块ROM/RAM,这里配置的是块ROM/RAM。这两种方式具体我也不是了解很深,以后再深入学习。
该实验最让我头疼的是调用ROM,第一次接触IP核有许多地方都完全不懂。用LCD(带中文字库)显示文字的时候,可以直接输入文字的十六进制数值,设置显示地址坐标即可,12864显示原理点阵控制点的亮灭来实现,但是如果要显示图片的话一个个输入难免太过麻烦,这个时候调用ROM就方便许多。找一张或做一张像素为128x64的单色图片,使用取模软件,按c51的方式取模。取模出来的数据为十六进制,行8个,64个。
配置块RAM/ROM时,要加载的是.coe文件,所以需要将取模的十六进制数据保存到.coe文件中。最开始我一直在找如何能直接将图片取模出来的数据转化成.coe文件,试了很多方法都失败了,最后发现完全可以自己按文件格式编辑一个即可,最终文件保存格式如下。
将图片取模出来的每个数据前的ox去掉,这只是C语言中16进制的表示形式,文件第一行的MEMORY_INITIALIZATION_RADIX=16;表示数据全都为16进制数,这里的16可以换成8、2、10都行。
打开ISE建立工程后,新建文件,选择IP(CORE Generator & Architecture Wizard),填写文件名,next
找到Memories & Storage Elements单击,选择RAMs & ROMs单击,选择Block Memory Generator 7.3,next,finish。
上面直接next,下面这里选Single Port ROM,next
这里要填写数据位宽和深度,位宽是你所需要输出的数据位宽,LCD有8个数据位,故这里定义位宽为8,深度即有多少个这样的数据,从取模出来的数据看,易得共有1024个数据位。我使用的basys2开发板使用的是Xilinx Spantan3E—100TQ144 FPGA包含73728位的块RAM/ROM。因此,使用的块RAM的最大容量为9216字节或4608个16位字。从这里来看,是完全够用的。
Next,加载.coe数据文件,勾选Load Init File 加载.coe文件,我这里是在桌面上保存着,直接找到选择就好。
然后一直点击next直至最后一个界面,最后点击Generate,生成rom.xco。生成的IP核在工程目录下的ipcore_dir文件中。
将rom.v文件打开端口定义如下,直接在顶层文件对其实例化,如代码所示
最后可直接仿真,新建tb文件查看仿真传输数据是否正确。由波形图可得在功能设定指令传输完毕后,进入读数据状态,所读入的数据与文件中数据相符,一行数据传输完毕进入下一行,继续读入数据,仿真图结果正确。下板子也正确。
最开始学习FPGA,最容易出现的就是两个问题,一个是把verilog当C语言来写,另一个就是把FPGA当单片机来用。前者我已经有了改善,后者我还需要继续努力。
转载请注明出处:NingHeChuan(宁河川)
个人微信订阅号:NingHeChuan
如果你想及时收到个人撰写的博文推送,可以扫描左边二维码(或者长按识别二维码)关注个人微信订阅号
知乎ID:NingHeChuan
微博ID:NingHeChuan