【相关课程推荐】 课程名称:梦翼师兄FPGA培训(视频加板卡),手把手带你入门FPGA
写在前面的话 液晶(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>
***it cs1=P1^1;
***it reset=P1^0;
***it rs=P3^0;
***it sclk=P3^1;
***it 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 Pictures12864-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模块被有序输出。将代码下载到开发板既可以看到对应的正确显示结果。
|