进阶项目(6)LCD12864液晶屏幕设计讲解
写在前面的话
液晶(LCD)显示具有功耗低、体积小、重量轻、超薄等许多其他显示器无法比拟的优点,近几年被广泛应用于FPGA控制的智能仪器、仪表和低功耗的电子产品中。LCD可分为段位式LCD、字符式LCD和点阵式LCD。其中段位式LCD和字符式LCD只能用于字符和数字的简单显示,不能满足图像曲线和汉字显示的要求;而点阵式LCD不仅可以显示字符、数字,还可以显示各种图形、曲线及汉字,并且可以实现屏幕上下左右滚动、动画功能、分区开窗口、反转、闪烁等功能,用途十分广泛。
基本概念
LCD12864 是一种具有4 位/8 位并行、2 线或3 线串行多种接口方式,内部含有国标一级、二级简体 、中文字库的点阵图形液晶显示模块;其显示分辨率为128×64, 内置8192 个16*16 点汉字,和128 个16*8 点ASCII 字符集,利用该模块灵活的接口方式和简单方便的操作指令,可构成全中文人机交互图形界面。可以显示8×4 行16×16 点 阵的汉字,也可完成图形显示。低电压低功耗是其又一显著特点。由该模块构成的液晶显示方案与同类型的图形点阵液晶显示模块相比,不论硬件电路结构或显示程序都要简洁得多,且该模块的价格也略低于相同点阵的图形液晶模块。
硬件电路结构
我们开发板上所使用的液晶为晶联讯生产的JLX12864G-13903型液晶显示器,可以显示128列*64行点阵单色图片,或显示8个/行*4行16*16点阵的汉字,或显示16个/行*8行8*8点阵的英文、数字、符号。
该液晶的通信方式可以通过不同的硬件设置,配置成串行和并行两种不同的通信方式。在这里,为了节约IO资源,我们将其配置成串行通信方式,具体电路结构如下:
由电路图可以看出,我们需要关心的其实也就只有五条线,那么接下来就让我们梳理一下这几条信号线的具体意义。
管脚接口 |
说明 |
12864_cs |
片选信号,低电平有效 |
12864_res |
复位信号,低电平有效 |
12864_rs |
寄存器选择信号,高为数据寄存器,低为指令寄存器 |
12864_sck |
串行时钟线 |
12864_sda |
串行数据线 |
官方代码解析
我们明白了定义的信号之后,接下来看一下它的时序:
注:此处的A0即为我们的12864_rs信号,SI是我们定义的数据线12864_sda,只是表示方式不同而已。
由时序图可以得出,只要我们的代码逻辑能符合上述时序要求,就可以顺利地发送数据,可是点亮这个LCD,需要哪些步骤呢?官方的资料提供了一些例程,可惜这些例程都是C语言版本的,接下来,就让我们这些“门外汉”慢慢品读吧。
C程序如下:
/* Test program for JLX12864G-139,串行接口 Driver IC is:ST7565R(or competible) Programmed by Ken,Dec.24,2010 JLX electronic Co.,ltd, http://www.jlxlcd.cn;http://www.jlxlcd.com.cn */ #include <reg51.H> sbit cs1=P1^1; sbit reset=P1^0; sbit rs=P3^0; sbit sclk=P3^1; sbit sid=P3^2; void transfer_data(int data1); void transfer_command(int data1); char code graphic1[]; char code graphic2[]; char code graphic3[]; char code graphic4[]; char code graphic5[]; void Delay(int i); void Delay1(int i); void disp_grap(char *dp); void initial_lcd(); void clear_screen(); void waitkey(); //===============main program=================== void main(void) { int i,j,k; initial_lcd(); while(1) { clear_screen(); //clear all dots disp_grap(graphic1); //display a picture of 128*64 dots waitkey(); disp_grap(graphic2); //display a picture of 128*64 dots waitkey(); disp_grap(graphic3); //display a picture of 128*64 dots waitkey(); disp_grap(graphic4); //display a picture of 128*64 dots waitkey(); disp_grap(graphic5); //display a picture of 128*64 dots waitkey(); } } /*LCD 初始化*/ void initial_lcd() { reset=0; /*低电平复位*/ Delay(20); reset=1; /*复位完毕*/ Delay(20); transfer_command(0xe2); /*软复位*/ Delay(5); transfer_command(0x2c); /*升压步聚 1*/ Delay(5); transfer_command(0x2e); /*升压步聚 2*/ Delay(5); transfer_command(0x2f); /*升压步聚 3*/ Delay(5); transfer_command(0x23); /*粗调对比度,可设置范围 0x20~0x27*/ transfer_command(0x81); /*微调对比度*/ transfer_command(0x1a); /*微调对比度的值,可设置范围 0x00~0x3f*/ transfer_command(0xa2); /*1/9 偏压比(bias)*/ transfer_command(0xc0); /*行扫描顺序:从上到下*/ transfer_command(0xa1); /*列扫描顺序:从左到右*/ transfer_command(0xaf); /*开显示*/ } //===============clear all dot martrics============= void clear_screen() { unsigned char i,j; for(i=0;i<9;i++) { cs1=0; transfer_command(0xb0+i); transfer_command(0x10); transfer_command(0x00); for(j=0;j<132;j++) { transfer_data(0x00); } } } //==================display a piture of 128*64 dots================ void disp_grap(char *dp) { int i,j; for(i=0;i<8;i++) { cs1=0; transfer_command(0xb0+i); //set page address, transfer_command(0x10); transfer_command(0x00); for(j=0;j<128;j++) { transfer_data(*dp); dp++; } } } //=============transfer command to LCM=============== void transfer_command(int data1) { char i; cs1=0; rs=0; for(i=0;i<8;i++) { sclk=0; if(data1&0x80) sid=1; else sid=0; Delay1(5); sclk=1; Delay1(5); data1=data1<<=1; } } //-----------transfer data to LCM--------------- void transfer_data(int data1) { char i; cs1=0; rs=1; for(i=0;i<8;i++) { sclk=0; if(data1&0x80) sid=1; else sid=0; sclk=1; data1=data1<<=1; } } //=============delay time===================== void Delay(int i) { int j,k; for(j=0;j<i;j++) for(k=0;k<990;k++); } //=============delay time===================== void Delay1(int i) { int j,k; for(j=0;j<i;j++) for(k=0;k<10;k++); } //--------------wait a switch,jump out if P2.0 get a signal"0"------------------ void waitkey() { repeat: if (P2&0x01) goto repeat; else Delay(1); if (P2&0x01) goto repeat; else; } char code graphic1[]={ /*-- 调入了一幅图像:D:\Backup\我的文档\My Pictures\12864-139 英文.bmp --*/ /*-- 宽度 x 高度=128x64 --*/ 0xFF,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0xF1,0x01,0xF1,0x01,0x01,0x01,0x01,0x01,0x11,0x61,0x81,0x61,0x11,0x01,0x01,0x21,0xF1,0x01,0x01,0x01,0x61,0x11,0x11,0x11,0xE1,0x01,0x61,0x91,0x91,0x91,0x61,0x01,0xC1,0xA1,0x91,0x91,0x21,0x01,0x01,0xC1,0x21,0xF1,0x01,0x01,0xE1,0x11,0x11,0x11,0x61,0x01,0x81,0x81,0x81,0x81,0x81,0x01,0x01,0x21}; |
对于我们学硬件的人来说,这样的C代码或许有些挑战,但是不要紧,接下来就让我们慢慢来分析它的main函数,定能找到关键线索。Main函数如下:
void main(void) { int i,j,k; initial_lcd(); while(1) { clear_screen(); //clear all dots disp_grap(graphic1); //display a picture of 128*64 dots waitkey(); disp_grap(graphic2); //display a picture of 128*64 dots waitkey(); disp_grap(graphic3); //display a picture of 128*64 dots waitkey(); disp_grap(graphic4); //display a picture of 128*64 dots waitkey(); disp_grap(graphic5); //display a picture of 128*64 dots waitkey(); } } |
由此可以看出,main函数开始执行的时候,首先要经过一个叫做initial_lcd的函数,然后进入循环主体,每隔一段时间打印一幅图像。那么接下来,就让我们细细品读这个initial_lcd函数的构成。代码如下:
/*LCD 初始化*/ void initial_lcd() { reset=0; /*低电平复位*/ Delay(20); reset=1; /*复位完毕*/ Delay(20); transfer_command(0xe2); /*软复位*/ Delay(5); transfer_command(0x2c); /*升压步聚 1*/ Delay(5); transfer_command(0x2e); /*升压步聚 2*/ Delay(5); transfer_command(0x2f); /*升压步聚 3*/ Delay(5); transfer_command(0x23); /*粗调对比度,可设置范围 0x20~0x27*/ transfer_command(0x81); /*微调对比度*/ transfer_command(0x1a); /*微调对比度的值,可设置范围 0x00~0x3f*/ transfer_command(0xa2); /*1/9 偏压比(bias)*/ transfer_command(0xc0); /*行扫描顺序:从上到下*/ transfer_command(0xa1); /*列扫描顺序:从左到右*/ transfer_command(0xaf); /*开显示*/ } |
由此可以看出,所谓LCD初始化就是首先拉低硬件复位信号12864_res一段时间再放开,等待复位结束以后,开始执行下面的一条条指令:
- 软件复位
- 升压步骤一
- 升压步骤二
- 升压步骤三
- 粗调对比度,可设置范围0x20~0x27
- 微调对比度
- 微调对比度的值,可设置范围0x00~0x3f
- 1/9偏压比(bias)
- 行扫描顺序:从上到下
10.列扫描顺序:从左到右
11.起始行位置:从第一行开始
12.开显示
现在的问题主要就是指令是如何被执行的呢?接下来再让我们解析transfer_command这个函数。代码如下:
void transfer_command(int data1) { char i; cs1=0; rs=0; for(i=0;i<8;i++) { sclk=0; if(data1&0x80) sid=1; else sid=0; Delay1(5); sclk=1; Delay1(5); data1=data1<<=1; } } |
仔细分析代码,不难发现这个函数的作用就是拉低片选信号,并拉低12864_rs信号(选择指令寄存器),然后通过移位操作,将参数data1发送到12864_sda串行数据总线。
总结:分析上文可知,我们如果要用FPGA实现对12864的控制,首先需要做的就是先硬件复位12864一段时间,然后按照C例程的顺序,发送上述指令集,等12864初始化完毕就可以开始发送图像数据。
接下来,我们开始分析如何发送有效数据,例程代码如下:
//=============display a piture of 128*64 dots===== void disp_grap(char *dp) { int i,j; for(i=0;i<8;i++) { cs1=0; transfer_command(0xb0+i); //set page address, transfer_command(0x10); transfer_command(0x00); for(j=0;j<128;j++) { transfer_data(*dp); dp++; } }} |
这里面涉及到了另外一个函数—transfer_data,那么就让我们继续一探究竟,其代码如下:
//-----------transfer data to LCM--------------- void transfer_data(int data1) { char i; cs1=0; rs=1; for(i=0;i<8;i++) { sclk=0; if(data1&0x80) sid=1; else sid=0; sclk=1; data1=data1<<=1; } } |
可以看出,函数transfer_data也只是通过并串转换将数据发送到串行数据总线,在发送数据的过程中必须将12864_rs置为高电平(选择数据寄存器),返回到前一级函数disp_grap,可以看出在发送数据之前,还必须首先发送三组指令:
transfer_command(0xb0+i); //set page address,
transfer_command(0x10);
transfer_command(0x00);
梳理上文,可以总结如下:
如果想要点亮液晶,必须先发送一系列的指令集,对LCD进行初始化。初始化完毕以后,可以开始发送数据,但每发一组数据之前还必须先发送三组指令,设置页面显示地址,最后才是发送数据。
项目需求
我们本次的设计任务是写入 1024 个相同的八位数,在屏幕上显示黑白条纹,系统架构设计如下
系统架构
1.时钟分频模块(PLL)
我们发送数据的时序要求需要满足一些特定的参数,将频率降低到一定值,就可以满足这些时序参数。具体参数值请查阅官方手册,我们这里分频时钟输出为5MHz。
2.指令控制模块(LCD_control)
如前文所述,想要点亮LCD,首先需要进行LCD初始化,发送特定的指令集,等初始化结束以后,再开始发送具体的显示数据,这些过程都需要紧密配合,所以我们把这部分的控制作为一个单独的模块来实现,端口及其意义如下:
端口名 |
端口说明 |
clk |
系统时钟5Mhz |
rst_n |
系统低电平复位信号 |
cnt_shift |
控制状态跳转信号 |
shift_en |
控制数据发送模块开始进行并串转换 |
data |
输出待发送的指令或数据 |
rs_control |
输出LCD寄存器选择信号 |
res_control |
输出LCD复位信号 |
3.数据发送模块(send_data)
这个模块主要是输出LCD12864的各种控制信号,端口及其意义如下:
端口名 |
端口说明 |
clk |
系统时钟5Mhz |
rst_n |
系统低电平复位信号 |
cnt_shift |
输出给指令控制模块的状态跳转信号 |
shift_en |
输入的并串转换的标志信号 |
data |
输入的待发送的指令或数据 |
rs_control |
输入LCD寄存器选择信号 |
res_control |
输入LCD复位信号 |
cs_12864 |
输出LCD片选信号 |
rs_12864 |
输出LCD寄存器选择信号 |
res_12864 |
输出LCD复位信号 |
sck_12864 |
输出LCD时钟信号 |
sda_12864 |
输出LCD串行数据线 |
代码解释
LCD_control模块代码如下
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function : LCD控制模块 *****************************************************/ 001 module LCD_control( 002 //系统输入 003 clk, //系统5Mhz时钟输入 004 rst_n, //低电平复位信号 005 cnt_shift, //发送数据模块反馈的移位计数器信号 006 //系统输出 007 data, //待发送数据 008 rse_contral, //LCD复位信号 009 rs_contral, //LCD寄存器选择 010 shift_en //发送数据控制信号 011 ); 012 //--------------------系统输入-------------------- 013 input clk; //系统5Mhz时钟输入 014 input rst_n; //低电平复位信号 015 input [3:0]cnt_shift; //发送数据模块反馈的移位计数器信号 016 //----------------------系统输出---------------------- 017 output reg [7:0]data; //待发送数据 018 output reg rse_contral; //LCD复位信号 019 output reg rs_contral; //LCD寄存器选择 020 output reg shift_en; //发送数据控制信号 021 //-------------------寄存器定义-------------------- 022 reg [9:0]state; //状态机状态寄存器 023 reg [3:0]cnt; //延时计数器 024 reg [3:0]i; //行计数 025 reg [9:0]j; //列计数 026 027 always @(posedge clk or negedge rst_n) 028 begin 029 if(!rst_n) 030 begin 031 data<=8'h00; //待发送数据或指令初始化 032 state<=0; 033 cnt<=0; 034 i<=0; 035 j<=0; 036 rs_contral<=0; //选择指令寄存器 037 shift_en<=0; 038 rse_contral<=0; 039 end 040 else 041 begin case(state) 042 //---------------LCD初始化--------------------------------- 043 0:begin//上电延时至少1us 044 if(cnt<8) 045 begin 046 cnt<=cnt+1'b1; 047 rse_contral<=0; 048 end 049 else 050 begin 051 cnt<=0; 052 state<=1; 053 rse_contral<=1; 054 /*软件复位指令准备*/ data<=8'he2; 055 end 056 end 057 1:begin/*软复位*/ 058 rs_contral<=0; //选择指令寄存器 059 if(cnt_shift<8) 060 shift_en<=1; 061 else 062 begin 063 if(cnt_shift==8)//发送完毕 064 begin 065 state<=2; 066 shift_en<=0; 067 /*升压步聚1指令准备*/ data<=8'h2c; 068 end 069 end 070 end 071 2:begin/*升压步聚1*/ 072 if(cnt_shift<8) 073 shift_en<=1; 074 else 075 begin 076 if(cnt_shift==8)//发送完毕 077 begin 078 state<=3; 079 shift_en<=0; 080 /*升压步聚2指令准备*/ data<=8'h2e; 081 end 082 end 083 end 084 3:begin/*升压步聚2*/ 085 if(cnt_shift<8) 086 shift_en<=1; 087 else 088 begin 089 if(cnt_shift==8)//发送完毕 090 begin 091 state<=4; 092 /*升压步聚3指令准备*/ data<=8'h2f; 093 shift_en<=0; 094 end 095 end 096 end 097 4:begin /*升压步聚3*/ 098 if(cnt_shift<8) 099 shift_en<=1; 100 else 101 begin 102 if(cnt_shift==8)//发送完毕 103 begin 104 state<=5; 105 shift_en<=0; 106 /*粗调对比度指令准备*/ data<=8'h23; 107 end 108 end 109 end 110 5:begin/*粗调对比度,可设置范围0x20~0x27*/ 111 if(cnt_shift<8) 112 shift_en<=1; 113 else 114 begin 115 if(cnt_shift==8)//发送完毕 116 begin 117 state<=6; 118 shift_en<=0; 119 /*微调对比度指令准备*/ data<=8'h81; 120 end 121 end 122 end 123 6:begin/*微调对比度*/ 124 if(cnt_shift<8) 125 shift_en<=1; 126 else 127 begin 128 if(cnt_shift==8)//发送完毕 129 begin 130 state<=7; 131 shift_en<=0; 132 /*微调对比度的值指令准备*/ data<=8'h1a; 133 end 134 end 135 end 136 7:begin/*微调对比度的值,可设置范围0x00~0x3f*/ 137 if(cnt_shift<8) 138 shift_en<=1; 139 else 140 begin 141 if(cnt_shift==8)//发送完毕 142 begin 143 state<=8; 144 shift_en<=0; 145 /*1/9偏压比(bias)指令准备*/ data<=8'ha2; 146 end 147 end 148 end 149 8:begin/*1/9偏压比(bias)*/ 150 if(cnt_shift<8) 151 shift_en<=1; 152 else 153 begin 154 if(cnt_shift==8)//发送完毕 155 begin 156 state<=9; 157 shift_en<=0; 158 /*行扫描顺序指令准备*/ data<=8'hc0; 159 end 160 end 161 end 162 9:begin/*行扫描顺序:从上到下*/ 163 if(cnt_shift<8) 164 shift_en<=1; 165 else 166 begin 167 if(cnt_shift==8)//发送完毕 168 begin 169 state<=10; 170 shift_en<=0; 171 /*列扫描顺序指令准备*/ data<=8'ha1; 172 end 173 end 174 end 175 10:begin/*列扫描顺序:从左到右*/ 176 if(cnt_shift<8) 177 shift_en<=1; 178 else 179 begin 180 if(cnt_shift==8)//发送完毕 181 begin 182 state<=11; 183 shift_en<=0; 184 /*起始行位置指令准备*/ data<=8'h40; 185 end 186 end 187 end 188 11:begin /*起始行位置:从第一行开始*/ 189 if(cnt_shift<8) 190 shift_en<=1; 191 else 192 begin 193 if(cnt_shift==8)//发送完毕 194 begin 195 state<=12; 196 shift_en<=0; 197 /*开显示指令准备*/ data<=8'haf; 198 end 199 end 200 end 201 12:begin /*开显示*/ 202 if(cnt_shift<8) 203 shift_en<=1; 204 else 205 begin 206 if(cnt_shift==8)//发送完毕 207 begin 208 state<=13; 209 shift_en<=0; 210 /*显示页地址位第一页指令准备*/ data<=8'hb0; 211 end 212 end 213 end 214 //--------------------写图像数据到LCD------------------ 215 13:begin 216 if(i<8) //行计数器小于8,继续写数据 217 begin 218 if(cnt_shift<8) 219 shift_en<=1; 220 else 221 begin 222 if(cnt_shift==8)//发送完毕 223 begin 224 state<=14; 225 shift_en<=0; 226 data<=8'h10; //设置列高地址 227 end 228 end 229 end 230 else //行计数器大于8,跳到状态17 231 begin 232 state<=17; 233 end 234 end 235 14:begin 236 if(cnt_shift<8) 237 shift_en<=1; 238 else 239 begin 240 if(cnt_shift==8)//发送完毕 241 begin 242 state<=15; 243 shift_en<=0; 244 data<=8'h00; //设置列低地址 245 end 246 end 247 end 248 15:begin 249 if(cnt_shift<8) 250 shift_en<=1; 251 else 252 begin 253 if(cnt_shift==8)//发送完毕 254 begin 255 state<=16; 256 shift_en<=0; 257 /*待发送的数据*/ data<=8'b00001111; 258 end 259 end 260 end 261 16:begin 262 if(j<128) 263 begin 264 rs_contral<=1; //选择数据寄存器 265 if(cnt_shift<8) 266 shift_en<=1; 267 else begin 268 if(cnt_shift==8)//发送完毕 269 begin 270 j<=j+1'b1; 271 /*写完128列,页计数器加1*/ if(j==127) 272 i<=i+1'b1; 273 shift_en<=0; 274 end 275 end 276 end 277 else 278 begin 279 j<=0; 280 /*选择指令寄存器*/ rs_contral<=0; 281 state<=13; 282 /* 页地址加1*/ data<=8'hb0+i; 283 end 284 end 285 17:begin 286 shift_en<=0; 287 end 288 default:; 289 endcase 290 end 291 end 292 293 endmodule |
这个模块是LCD的控制模块,对LCD进行初始化和发送待显示的数据。
从第215行开始到结束,作用就是把图像数据写到12864进行显示,首先设置页地址为8’hb0(第一页),然后是设置行地址的高位和低位(第一行第一列),最后发送数据,发送数据的时候一定要把指令寄存器拉高,我们有一个列计数器j和页计数器i,当一行数据写满128列的时候,在开始写下一页,写完8页之后,跳到状态17即可。
发送数据模块
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function : LCD输出数据模块 *****************************************************/ 01 module send_data( 02 //系统输入 03 clk,//系统5Mhz时钟输入 04 rst_n,//低电平复位信号 05 rse_contral,//LCD复位控制信号 06 rs_contral,//LCD寄存器选择控制信号 07 shift_en,//移位控制信号 08 data,//待发送数据 09 //系统输出 10 cs_12864,//LCD片选信号 11 rse_12864,//LCD复位信号 12 rs_12864,//LCD寄存器选择信号 13 sck_12864,//LCD串行时钟 14 sda_12864,//LCD串行数据总线 15 cnt_shift//LCD移位计数器 16 ); 17 //------------------------系统输入-------------------- 18 input clk;//系统5Mhz时钟输入 19 input rst_n;//低电平复位信号 20 input rse_contral;//LCD复位控制信号 21 input rs_contral;//LCD寄存器选择控制信号 22 input shift_en;//移位控制信号 23 input [7:0]data;//待发送数据 24 //------------------------系统输出------------------- 25 output rs_12864;//LCD寄存器选择信号 26 output rse_12864;//LCD复位信号 27 output reg cs_12864;//LCD片选信号 28 output reg sck_12864;//LCD串行时钟 29 output reg sda_12864;//LCD串行数据总线 30 output reg [3:0]cnt_shift;//LCD移位计数器 31 //------------------------寄存器定义------------------ 32 reg [7:0]data_reg;//待发送数据寄存器 33 //------------------------输出量赋值------------------ 34 assign rse_12864=rse_contral; 35 assign rs_12864=rs_contral; 36 //---------------产生LCD时钟sck_12864---------------- 37 always@(negedge clk or negedge rst_n) 38 begin 39 if(!rst_n) 40 begin 41 sck_12864<=0;//sck_12864赋初值 42 end 43 else 44 begin 45 sck_12864<=~sck_12864;//sck_12864自取反 46 end 47 end 48 //------------------------发送数据逻辑--------------- 49 always@(posedge clk or negedge rst_n) 50 begin 51 if(!rst_n) 52 begin 53 cs_12864<=1; //复位期间关闭片选信号 54 sda_12864<=0; //串行数据总线sda_12864赋初值 55 cnt_shift<=0; //移位计数器赋初值 56 data_reg<=data;//将数据存储到数据寄存器 57 end 58 else 59 begin 60 if(cnt_shift==0)//前一组数据发送完毕 61 data_reg<=data;//寄存器数据更新,准备下一次发送 62 63 if(shift_en)//发送数据使能信号打开 64 cs_12864<=0;//打开片选信号 65 //在sck_12864低电平期间,如果接收到发送 66 //请求信号shift_en,则开始进行并串转换 67 if((cnt_shift<8)&&(!sck_12864)&&(shift_en)) 68 begin 69 //将data_reg每一位赋值给sda_12864 70 sda_12864<=data_reg[7]; 71 cnt_shift<=cnt_shift+1'b1;//移位寄存器计数 72 //控制数据循环移位 73 data_reg<={data_reg[6:0],data_reg[7]}; 74 end 75 else 76 if(cnt_shift==8)//移位结束,即并串转换结束 77 begin 78 cs_12864<=1;//片选信号关闭 79 cnt_shift<=0;//计数器清零 80 end 81 end 82 end 83 endmodule |
第37~47行输出12864时钟,第49行到结束是发送数据的控制逻辑,第60~61行是当移位计数器cnt_shift计数到0也就是发送完一组数据之后数据寄存器data_reg接收新数据,第63~64行当接收到shift_en信号是高电平的时候,使能12864的片选信号,第67~74行在sck_12864低电平期间,如果接收到发送请求信号shift_en,则开始进行并串转换输出数据,第76~80行当每一次数据发送完毕之后,计数器清零,并关闭片选信号。
顶层模块代码
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * E_mail : zxopenwjf@126.com * The module function : LCD顶层模块 *****************************************************/ 01 module LCD_12864 ( 02 //系统输入 03 clk_sys,//系统50M时钟 04 rst_n,//系统复位 05 //系统输出 06 cs_12864,//LCD片选信号 07 rse_12864,//LCD复位信号 08 rs_12864,//LCD寄存器选择信号 09 sck_12864,//LCD串行时钟 10 sda_12864//LCD串行数据总线 11 ); 12 //------------------------系统输入------------------------ 13 input clk_sys;//系统50M时钟 14 input rst_n;//系统复位 15 //------------------------系统输出------------------------ 16 output cs_12864; //LCD片选信号 17 output rse_12864;//LCD复位信号 18 output rs_12864; //LCD寄存器选择信号 19 output sck_12864;//LCD串行时钟 20 output sda_12864;//LCD串行数据总线 21 //------------------------内部连线------------------------ 22 wire [7:0]data; 23 wire clk; 24 wire [3:0]cnt_shift; 25 wire rse_contral; 26 wire shift_en; 27 //-----------------------模块实例化----------------------- 28 PLL PLL( 29 .inclk0(clk_sys), 30 .c0(clk) 31 ); 32 33 LCD_control LCD_control( 34 .clk(clk), 35 .rst_n(rst_n), 36 .data(data), 37 .cnt_shift(cnt_shift), 38 .rse_contral(rse_contral), 39 .shift_en(shift_en), 40 .rs_contral(rs_contral) 41 ); 42 43 send_data send_data( .clk(clk), 44 .rst_n(rst_n), 45 .rse_contral(rse_contral), 46 .data(data), 47 .rs_contral(rs_contral), 48 .shift_en(shift_en), 49 .cnt_shift(cnt_shift), 50 .cs_12864(cs_12864), 51 .rse_12864(rse_12864), 52 .rs_12864(rs_12864), 53 .sck_12864(sck_12864), 54 .sda_12864(sda_12864) 55 ); 56 57 endmodule |
综合编译以后,我们可以查看RTL视图,查看电路综合结果和预想是否一致,调用RTL视图如下:
由此可以看到电路综合出的结果和我们预先设计的框架相同。接下来我们编写测试代码,用来验证我们设计的正确性
测试模块代码
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * E_mail : zxopenwjf@126.com * The module function : LCD顶层模块 *****************************************************/ 01 `timescale 1ns/1ps 02 module tb; 03 //------------------------系统输入------------------------ 04 reg clk_sys;//系统50M时钟 05 reg rst_n;//系统复位 06 //------------------------系统输出------------------------ 07 wire cs_12864; //LCD片选信号 08 wire rse_12864;//LCD复位信号 09 wire rs_12864; //LCD寄存器选择信号 10 wire sck_12864;//LCD串行时钟 11 wire sda_12864;//LCD串行数据总线 12 //------------------------产生测试激励------------------------ 13 initial 14 begin 15 clk_sys=0; 16 rst_n=0; 17 #1000.1 rst_n=1; 18 end 19 20 always #10 clk_sys=~clk_sys;//50MHZ时钟 21 22 //------------------------实例化被测试模块------------------------ 23 LCD_12864 LCD_12864( 24 .clk_sys(clk_sys), 25 .rst_n(rst_n), 26 27 .cs_12864(cs_12864), 28 .rse_12864(rse_12864), 29 .rs_12864(rs_12864), 30 .sck_12864(sck_12864), 31 .sda_12864(sda_12864) 32 ); 33 endmodule |
仿真分析
查看上述仿真波形可知,各条指令按照顺序通过send_data模块被有序输出。将代码下载到开发板既可以看到对应的正确显示结果。