1)实验平台:正点原子开拓者FPGA 开发板
2)摘自《开拓者 Nios II开发指南》关注官方微信号公众号,获取更多资料:正点原子
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/index.html
![427ed3c889c8a9ea5650ef138edb607e.png](https://i-blog.csdnimg.cn/blog_migrate/21d35ab17e6c462ccd2c737ad934aab1.jpeg)
第十章MCU TFT-LCD图片显示实验
TFT LCD是Thin Film Transistor Liquid Crystal Display的缩写,即薄膜场效应晶体
管液晶显示器,即每个液晶像素点都是由集成在像素点后面的薄膜晶体管来驱动,这样不仅
提高了显示屏的响应速度,同时还可以精确控制显示色阶,做到高速度、高彩色保真度、高
亮度、高对比度和高分辨率。TFT LCD能够低电压驱动,具有功耗低,使用寿命长等特点。其
显示应用范围覆盖了从1英寸至40英寸范围内的所有显示器以及投影大平面,是全尺寸显示终
端。另外TFT LCD的环保特性好,无辐射、无闪烁,是设计用户友好型图形界面的优良载体。
本章我们将使用Nios II驱动TFT LCD的显示。
本章包括以下几个部分:
10.1 简介
10.2 实验任务
10.3 硬件设计
10.4 软件设计
10.5 下载验证
简介
ATK-4.3’TFTLCD是ALIENTEK推出的一款高性能4.3寸电容触摸屏模块。该模块屏幕分辨
率为800*480,16位真彩显示,采用Intel8080接口(MCU接口),该芯片自带GRAM,无需外加
驱动器,因而任何单片机,都可以轻易驱动。此外ALIENTEK还推出了一款具有相同接口和分
辨率的TFTLCD——ALIENTEK第二代7寸TFTLCD——ATK-7’ TFTLCD-V2.2,ATK-7’TFTLCD V2
模块具有屏幕分辨率高(800*480),支持16/18/24位真彩显示、支持8/9/12/16位数据格
式、支持开窗显示等特色。当然了,ALIENTEK也提供了3.5寸和2.8寸的TFTLCD,以满足不同
应用的需求。
虽然这些不同尺寸的TFTLCD的LCD驱动器芯片不同(关于各尺寸的TFTLCD的详细介绍可参
见提供的增值资料部分),如7寸的ATK-7’ TFTLCD-V2.2采用的是SSD1963、4.3寸的ATK-
4.3’TFTLCD采用的是NT35510等,但都采用Intel8080接口。Intel8080接口是一种并行接口
协议,由Intel公司提出,被广泛应用于各类液晶显示器。下面我们了解一下Intel8080接口
的操作时序。
为了更好的了解Intel8080接口,我们先来看一下TFTLCD模块的接口原理图,如下图所
示:
图10.1.1 TFTLCD模块接口原理图
各引脚的详细描述如下表所示:
表格10.1.1 TFTLCD模块接口引脚功能描述
![07369c5d1504bbd1f0d5dc3b7bd7d323.png](https://i-blog.csdnimg.cn/blog_migrate/5f6553d58e10d6476abb9f8dca902d8a.jpeg)
在Intel8080并口模式下,LCD驱动需要用到的信号线如下:
CS:LCD片选信号。
WR:向LCD写入数据。
RD:从LCD读取数据。
D[15:0]:16位双向数据线(RGB565)。
RST:硬复位LCD。
RS:命令/数据标志(0,读写命令;1,读写数据)。
除了以上信号,我们一般还需要用到这2个信号:RST和BL_CTR,其中RST是液晶的硬复位
脚,低电平有效,用于复位TFTLCD的驱动芯片如NT35510,实现液晶复位,在每次初始化之
前,我们建议先执行硬复位,再做初始化。BL_CTR则是背光控制引脚,高电平有效,即高电平时点亮背光,另外可以用PWM控制BL_CTR脚,从而控制背光的亮度。
Intel8080并口读/写的过程为:拉低片选CS,选中驱动芯片,并根据要写入/读取的数据
的类型,设置RS为高(数据)/低(命令),然后我们根据是读数据还是写数据设置RD/WR为
低,即当写数据时,WR设为低电平,RD保持高电平,当读数据时,RD设为低电平,WR保持高
电平,然后:当WR由低到高时,TFTLCD锁存数据。写时序的时序图如下:
![700a0ed193658f7f99c7f9c4608c6c37.png](https://i-blog.csdnimg.cn/blog_migrate/da1b368cf67b10f309ab58e43a3af6c3.jpeg)
图10.1.2 8080 并口写时序图
从上图可以看到,在写数据时,读信号RD保持高电平,RS根据写数据的类型设置为高或
低电平(高:数据,低:命令),当WR为低电平时,往D0~D15写数据,当WR由低电平变成高
电平时,TFTLCD锁存数据。
读时序的时序图如下:
![e4bff6d45d169d1df3a4c0c8a5f6cf7e.png](https://i-blog.csdnimg.cn/blog_migrate/94121e218e6a2efb88950f89f7d92964.jpeg)
图 10.1.3 8080 并口读时序图
从上图可以看到,在读数据时,写信号WR保持高电平,当RD为低电平时,TFTLCD控制数
据总线D0~D15,当RD由低电平变成高电平时,主机读取数据。
Intel8080接口方式下,控制脚的信号状态所对应的功能如下表所示:
图 10.1.4 控制脚信号状态功能表
![40e20ed23e6199e7d28270f3053265ff.png](https://i-blog.csdnimg.cn/blog_migrate/56b3b533f45bdc588b097c90aeb1a7a1.jpeg)
在Intel8080接口下读数据操作的时候,我们有时候(例如读显存的时候)需要一个假读
命(Dummy Read),以使得微控制器的操作频率和显存的操作频率相匹配。在读取真正的数
据之前,由一个的假读的过程。这里的假读,其实就是第一个读到的字节丢弃不要,从第二
个开始,才是我们真正要读的数据。一个典型的读显存的时序图,如下图所示:
![7c55bb90e176a1e5c90241e0a8b57791.png](https://i-blog.csdnimg.cn/blog_migrate/d164596db8b4cc9a3ada04bf9b8dfad6.jpeg)
图10.1.5 读显存时序图
可以看到,在发送了列地址之后,开始读数据,第一个是Dummy Read,也就是假读,我
们从第二个开始,才算是真正有效的数据
现在我们以ATK-4.3’TFTLCD为例,看一下Intel8080总线读写时序的具体参数,如下图
所示:
![fd956d2f1934d21144401a19e2f10d06.png](https://i-blog.csdnimg.cn/blog_migrate/50a180fe918b92419410433a25c2c14d.jpeg)
图10.1.6 Intel总线读写时序
图中各时间参数见表10.1.3 所示:
![030d0535ff1e4d44a7f2b4db2d0b7bc4.png](https://i-blog.csdnimg.cn/blog_migrate/fbb1d7034ef0401bb63bd8b0e26f899d.jpeg)
图 10.1.7 Intel8080 并口读写时间参数
从上表可以看出,模块的写周期是非常快的,只需要33ns即可,理论上最大速度可以达
到:3030W像素每秒,即刷屏速度可以达到每秒钟78.9 帧。模块的读取速度相对较慢:读ID
(RD(ID))周期是160ns,读显存周期是400ns(RD(FM))。
并行接口模式就介绍到这里,现在我们以4.3寸的ATK-4.3’TFTLCD的LCD驱动芯片
NT35510为例进行介绍,其它的驱动芯片基本都类似,我们就不详细阐述了。
NT35510自带LCD GRAM(480*864*3字节),并且最高支持24位颜色深度(1600万色),
不过,我们一般使用16位颜色深度(65K色),RGB565格式,这样,在16位模式下,可以达到
最快的速度。
在16位模式下,NT35510采用RGB565格式存储颜色数据,此时NT35510的低16位数据总线
(高8位没有用到)与MCU(这里指Nios II)的16位数据线以及24位LCD GRAM的对应关系如下
表所示:
![b566d980bc3c0bdfcfccd7d4c484eb14.png](https://i-blog.csdnimg.cn/blog_migrate/704d9636f1a8cdc4ef664e265fe7cd63.jpeg)
图10.1.8 16位总线与24位GRAM对应关系
从上表可以看出,NT35510的24位GRAM与16位RGB565的对应关系,其实就是分别将高位的
R、G、B数据,搬运到低位做填充,“凑成”24位,再显示。
MCU的16位数据中,最低5位代表蓝色,中间6位为绿色,最高5位为红色。数值越大,表
示该颜色越深。另外,特别注意NT35510的指令是16位宽,数据除了GRAM读写的时候是16位
宽,其它都是8位宽的(高8位无效),这个和ILI9320等驱动器不一样,必须加以注意。
接下来,我们介绍一下NT35510的几个重要命令,因为NT35510的命令很多,我们这里就
不全部介绍了,有兴趣的读者可以查看NT35510的datasheet。里面对这些命令有详细的介
绍。我们将介绍:0XDA00,0XDB00,0XDC00,0X3600,0X2A00~0X2A03,0X2B00~0X2B03,
0X2C00,0X2E00 等14条指令。
首先来看指令:0XDA00,0XDB00,0XDC00,这三条指令是读ID1,ID2,ID3指令,也就是
用于读取LCD控制器的ID,该指令如下表所示
图10.1.9 读ID指令描述
![bb553145bc360754f51bb7aa21a42075.png](https://i-blog.csdnimg.cn/blog_migrate/b664eebb344dedc9026ab7e49fc76e4b.jpeg)
![8742813b218ec6fd07245392afa36a54.png](https://i-blog.csdnimg.cn/blog_migrate/d52d75e8d06047f966f5754b271f2736.jpeg)
从上表可以看出,LCD读ID,总共由3个指令(0XDA00、0XDB00和0XDC00)构成,每个指
令输出一个参数,每个ID以8位数据(即指令后的参数)的形式输出(高8位固定为0),不过
这里输出的ID,并不包含5510这样的字样,仅有指令0XDB00会输出ID:0X80,其他两个指令
读到的ID都是0。将3个指令的输出,组合在一起,可以得到NT35510的ID为:0X8000。
通过这个ID,即可判别所用的LCD驱动器是什么型号,这样,我们的代码,就可以根据控
制器的型号去执行对应驱动IC的初始化代码,从而兼容不同驱动IC的屏,使得一个代码支持
多款LCD。
接下来看指令:0X3600,这是存储访问控制指令,可以控制NT35510存储器的读写方向,
简单的说,就是在连续写GRAM的时候,可以控制GRAM指针的增长方向,从而控制显示方式
(读GRAM也是一样)。该指令如表所示:
图10.1.10 0X3600 指令描述
![8f8a156695c1a898995c4fd1210a4bdd.png](https://i-blog.csdnimg.cn/blog_migrate/5b85b05ec6d437b3a02d5641606502da.jpeg)
从上表可以看出,0X3600指令后面,紧跟一个参数,这里我们主要关注:MY、MX、MV这三个位,
通过这三个位的设置,我们可以控制整个 NT35510 的全部扫描方向,如下表所示:
图10.1.11 MY、MX、MV设置与LCD扫描方向关系表
![c7da49ced4756058f24fd6d56b2278cf.png](https://i-blog.csdnimg.cn/blog_migrate/b4dc465390cad5818d6edcc1af03f237.jpeg)
![9cd1674a6535a770bd610e8d9b0e569a.png](https://i-blog.csdnimg.cn/blog_migrate/1b4dd75884d90eacad13808bb225fe8f.jpeg)
这样,我们在利用 NT35510 显示内容的时候,就有很大灵活性了,比如显示 BMP 图片,BMP
解码数据,就是从图片的左下角开始,慢慢显示到右上角,如果设置 TFTLCD扫描方向为从左
到右,从下到上,那么我们只需要设置一次坐标,然后就不停的往LCD填充颜色数据即可,这
样可以大大提高显示速度。
接下来看指令:0X2A00~0X2A03,这几个是列地址设置指令,在从左到右,从上到下的扫
描方式(默认)下面,该指令用于设置横坐标(x 坐标),该指令如下 表 所示:
图10.1.12 0X2A00~0X2A03指令描述
![0c5e238d1fe0cf0b859450ddc2b6ee42.png](https://i-blog.csdnimg.cn/blog_migrate/8bf3c556f25e7cbfe11444b69a7d2ed4.jpeg)
在默认扫描方式时,这4个指令用于设置x坐标,每条指令带有1个参数,实际上总共就是
2个坐标值:SC和EC(SC和EC都是16位的,由2个8位组成),即列地址的起始值和结束值,SC
必须小于等于EC,且0≤SC/EC≤479。一般在设置x坐标的时候,我们只需要0X2A00和0X2A01
两条指令即可,也就是设置SC即可,因为如果EC没有变化,我们只需要设置一次即可(在初
始化NT35510的时候设置),从而提高速度。
与0X2A00~0X2A03指令类似,指令:0X2B00~0X2B03,是页地址设置指令,在从左到右,
从上到下的扫描方式(默认)下面,该指令用于设置纵坐标(y坐标)。该指令如下 表 所
示:
图10.1.13 0X2B00~0X2B03指令描述
![c049d14bbc86a942baf3a09d557a6068.png](https://i-blog.csdnimg.cn/blog_migrate/c37977f4c10122bcb111b3d0cbf3128a.jpeg)
在默认扫描方式时,这4个指令用于设置y坐标,每条指令带有1个参数,实际上总共就是
2个坐标值:SP和EP(SP和EP都是16位的,由2个8位组成),即页地址的起始值和结束值,SP
必须小于等于EP,且0≤SP/EP≤799。一般在设置y坐标的时候,我们只需要带0X2B00和
0X2B01两条指令即可,也就是设置SP即可,因为如果EP没有变化,我们只需要设置一次即可
(在初始化NT35510的时候设置),从而提高速度。
接下来看指令:0X2C00,该指令是写GRAM指令,在发送该指令之后,我们便可以往LCD的
GRAM里面写入颜色数据了,该指令支持连续写,指令描述如下表所示:
图10.1.14 0X2C00指令描述
![e1fd39a051d7312319f86e078d5d0b95.png](https://i-blog.csdnimg.cn/blog_migrate/cec0822b45e7351615e878a7a9b411d9.jpeg)
从上表可知,在收到指令 0X2C00之后,数据有效位宽变为16位,我们可以连续
写入LCD GRAM值,而GRAM的地址将根据MY/MX/MV设置的扫描方向进行自增。例如:假
设设置的是从左到右,从上到下的扫描方式,那么设置好起始坐标(通过SC,SP设
置)后,每写入一个颜色值,GRAM地址将会自动自增1(SC++),如果碰到EC,则回
到 SC,同时SP++,一直到坐标:EC,EP结束,其间无需再次设置的坐标,从而大大
提高写入速度。
最后,来看看指令:0X2E00,该指令是读 GRAM 指令,用于读取 NT35510 的显存(GRAM),该指令在NT35510的数据手册上面的描述是有误的,真实的输出情况如
下表所示:
表10.1.11 0X2E00指令描述
![ec262d5dd070d07eea19ce7677c655ba.png](https://i-blog.csdnimg.cn/blog_migrate/2984cffb6a6aee8d73fcdac7a75d9f92.jpeg)
该指令用于读取GRAM,如上表所示,NT35510在收到该指令后,第一次输出的是dummy数
据,也就是无效的数据,第二次开始,读取到的才是有效的GRAM数据(从坐标:SC,SP开
始),输出规律为:每个颜色分量占8个位,一次输出2个颜色分量。比如:第一次输出是
R1G1,随后的规律为:B1R2→G2B2→R3G3→B3R4→G4B4→R5G5...以此类推。如果我们只需要
读取一个点的颜色值,那么只需要接收到参数3即可,如果要连续读取(利用GRAM地址自增,
方法同上),那么就按照上述规律去接收颜色数据。
以上,就是操作NT35510常用的几个指令,通过这几个指令,我们便可以很好的控制
NT35510 显示我们所要显示的内容了。
![0c46157327ad90fc7f385e656c84dda1.png](https://i-blog.csdnimg.cn/blog_migrate/d024905a2a4abfaf1b4b0c6fa2940907.jpeg)
图10.1.15 TFTLCD使用流程
任何LCD,使用流程都可以简单的用以上流程图表示。其中硬复位和初始化序列,只需要
执行一次即可。而画点流程就是:设置坐标→写GRAM指令→写入颜色数据,然后在LCD 上
面,我们就可以看到对应的点显示我们写入的颜色了。读点流程为:设置坐标→读GRAM指令
→读取颜色数据,这样就可以获取到对应点的颜色数据了。
以上只是最简单的操作,也是最常用的操作,有了这些操作,一般就可以正常使用
TFTLCD了。接下来,我们开始利用Nios II驱动TFTLCD并使其显示。
实验任务
本章我们使用Nios II驱动TFTLCD,并使其显示图片,图片上叠加字符显示。
硬件设计
本章实验的硬件框架如下图所示:
![3f53de3e4d9db6b5111b29c5c12b3392.png](https://i-blog.csdnimg.cn/blog_migrate/17c8409cf2b482aa905dd293d8a234bf.jpeg)
图10.3.1 TFTLCD显示实验的硬件框架图
顶层代码如下:
1 module qsys_lcd(
2 //module clock
3 input sys_clk , //系统时钟,50Mhz
4 input sys_rst_n , //系统复位,低电平有效
5
6 //SDRAM interface
7 output sdram_clk , //SDRAM 芯片时钟
8 output sdram_cke , //SDRAM 时钟有效
9 output sdram_cs_n , //SDRAM 片选
10 output sdram_ras_n, //SDRAM 行有效
11 output sdram_cas_n, //SDRAM 列有效
12 output sdram_we_n , //SDRAM 写有效
13 output [ 1:0] sdram_ba , //SDRAM Bank地址
14 output [12:0] sdram_addr , //SDRAM 行/列地址
15 inout [15:0] sdram_data , //SDRAM 数据
16 output [ 1:0] sdram_dqm , //SDRAM 数据掩码
17
18 //EPCS FLASH interface
19 output epcs_dclk , // EPCS 时钟信号
20 output epcs_sce , // EPCS 片选信号
21 output epcs_sdo , // EPCS 数据输出信号
22 input epcs_data0, // EPCS 数据输入信号
23
24 //MCU LCD interface
25 inout [15:0] mlcd_data , // LCD 数据信号
26 output mlcd_bl , // LCD 背光信号
27 output mlcd_cs_n , // LCD 片选信号
28 output mlcd_wr_n , // LCD 写信号
29 output mlcd_rd_n , // LCD 读信号
30 output mlcd_rs , // LCD 命令/数据信号
31 output mlcd_rst_n // LCD 复位信号
32 //user interface
33
34 );
35
36 //wire define
37 wire clk_100m; //SDRAM 控制器时钟
38 wire rst_n ; //系统复位信号
39 wire locked ; //PLL输出稳定标志
40
41 //*****************************************************
42 //** main code
43 //*****************************************************
44
45 //待PLL输出稳定之后,停止系统复位
46 assign rst_n = sys_rst_n & locked;
47
48 //例化PLL
49 pll_clk u_pll_clk(
50 .areset (~sys_rst_n),
51 .inclk0 (sys_clk ),
52 .c0 (clk_100m ),
53 .c1 (sdram_clk ),
54 .locked (locked )
55 );
56
57 //例化Nios2系统模块
58 nios2os u_nios2os (
59 .clk_clk (clk_100m ), // 时钟100M
60 .reset_reset_n (rst_n ), // 复位信号
61 .sdram_addr (sdram_addr ), // SDRAM 行/列地址
62 .sdram_ba (sdram_ba ), // SDRAM Bank地址
63 .sdram_cas_n (sdram_cas_n), // SDRAM 列有效
64 .sdram_cke (sdram_cke ), // SDRAM 时钟有效
65 .sdram_cs_n (sdram_cs_n ), // SDRAM 片选
66 .sdram_dq (sdram_data ), // SDRAM 数据
67 .sdram_dqm (sdram_dqm ), // SDRAM 数据掩码
68 .sdram_ras_n (sdram_ras_n), // SDRAM 行有效
69 .sdram_we_n (sdram_we_n ), // SDRAM 写有效
70 .epcs_dclk (epcs_dclk ), // EPCS 时钟信号
71 .epcs_sce (epcs_sce ), // EPCS 片选信号
72 .epcs_sdo (epcs_sdo ), // EPCS 数据输出信号
73 .epcs_data0 (epcs_data0 ), // EPCS 数据输入信号
74 .mlcd_data_export (mlcd_data ), // LCD 数据信号
75 .mlcd_cs_n_export (mlcd_cs_n ), // LCD 片选信号
76 .mlcd_wr_n_export (mlcd_wr_n ), // LCD 写信号
77 .mlcd_rd_n_export (mlcd_rd_n ), // LCD 读信号
78 .mlcd_rst_n_export(mlcd_rst_n ), // LCD 复位信号
79 .mlcd_rs_export (mlcd_rs ), // LCD 命令/数据信号
80 .mlcd_bl_export (mlcd_bl ) // LCD 背光信号
81 );
82
83 endmodule
顶层代码主要实现PLL和Nios II系统模块的例化。
软件设计
创建好软件工程后,我们将hello_world.c更改为qsys_lcd.c,并在qsyslcd的应用工程
下新建一个文件夹,命名为drive,里面存放TFTLCD的驱动代码文件和相应的字体文件,文件
夹结构如下图所示:
![d76f39b5d674099acc9b01582ab5bc1d.png](https://i-blog.csdnimg.cn/blog_migrate/a783a5ef3f7160efbaae247061692bf6.jpeg)
图 10.4.1 文件结构
其中font.h是由取模软件“PCtoLCD2002”生成的不同字体大小的ASCII码字模文件,除
了支持0~9的10个数字字符和26个英文字母的大小写ASCII字符集外,还支持如下字符
集: !"#$%&'()*+,-./:;<=>?@ []^_`{|}~,能显示四种字符点阵大小:12*12、16*16、
24*24和32*32。mculcd.h是TFTLCD驱动文件的头文件,mculcd.c是TFTLCD驱动文件的c文件。
由于这两个文件的代码较多,我们这里就不贴出来了,只针对几个重要的函数进行讲解。
我们先介绍一下mculcd.h里面的一个重要结构体:
//LCD重要参数集
typedef struct
{
u16 width; //LCD 宽度
u16 height; //LCD 高度
u16 id ; //LCD ID
u8 dir; //横屏还是竖屏控制:0,竖屏;1,横屏
u16 wramcmd; //开始写gram指令
u16 setxcmd; //设置x坐标指令
u16 setycmd; //设置y坐标指令
}_lcd_dev;
//LCD参数
_lcd_dev lcddev; //管理LCD重要参数
该结构体用于保存一些TFTLCD重要参数信息,比如TFTLCD的长宽、LCD ID(驱动IC型
号)、LCD横竖屏状态等,这个结构体虽然占用了十几个字节的内存,但是却可以让我们的驱
动函数支持不同尺寸的LCD,同时可以实现LCD横竖屏切换等重要功能,所以还是利大于弊
的。有了以上了解,下面我们开始介绍mculcd.c里面的一些重要函数。
//LCD延迟函数,单位毫秒
void delay_ms(u32 n)
{
usleep(n*1000);
}
//LCD写命令
void LCD_WR_CMD(u16 Cmd)
{
IOWR_ALTERA_AVALON_PIO_DIRECTION(MLCD_DATA_BASE,0xFFFF); // 设置DATA PIO为输出
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_RS_BASE,0); // 拉低RS
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_RD_N_BASE,1); // RD设为高电平
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_WR_N_BASE,0); // 拉低WR
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_DATA_BASE,Cmd); // 往DATA端口写命令
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_WR_N_BASE,1); // 拉高WR
}
//LCD写数据
void LCD_WR_DATA(u16 Data)
{
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_RS_BASE,1);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_RD_N_BASE,1);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_WR_N_BASE,0);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_DATA_BASE,Data);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_WR_N_BASE,1);
}
//LCD读数据
u16 LCD_RD_DATA()
{
u16 read_data = 0;
IOWR_ALTERA_AVALON_PIO_DIRECTION(MLCD_DATA_BASE,0x0000); // 设置DATA PIO为输入
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_RS_BASE,1);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_RD_N_BASE,0);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_WR_N_BASE,1);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_RD_N_BASE,1);
read_data = IORD_ALTERA_AVALON_PIO_DATA(MLCD_DATA_BASE);
return read_data;
}
//LCD写寄存器数据
void LCD_WriteReg(u16 LCD_Reg,u16 LCD_RegValue)
{
LCD_WR_CMD(LCD_Reg); // 写入的寄存器
LCD_WR_DATA(LCD_RegValue); // 写入的数据
}
//LCD读寄存器
u16 LCD_ReadReg(u16 LCD_Reg)
{
LCD_WR_CMD(LCD_Reg); // 要读取的寄存器
usleep(5); // 延时5us
return LCD_RD_DATA(); // 返回读取的数据
}
//开始写GRAM
void LCD_WriteRAM_Prepare(void)
{
LCD_WR_CMD(lcddev.wramcmd);
}
//LCD写GRAM
//RGB_Code:颜色值
void LCD_WriteRAM(u16 RGB_Code)
{
LCD_WR_DATA(RGB_Code); //写十六位GRAM
}
从该代码中我们可以看出,写命令和读写数据完全符合我们的时序图。需要注意的是,
由于DATA PIO为双向的,所以在读写命令或数据之前需要先设置其方向,否则读写不成功。
接下来我们介绍的是画点函数。该函数实现代码如下:
//画点
//x,y:坐标
//POINT_COLOR:此点的颜色
void LCD_DrawPoint(u16 x,u16 y)
{
LCD_SetCursor(x,y); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
LCD_WR_DATA(POINT_COLOR);
}
该函数实现比较简单,就是先设置坐标,然后往坐标写颜色。其中POINT_COLOR是我们定
义的一个全局变量,用于存放画笔颜色,顺带介绍一下另外一个全局变量:BACK_COLOR,该
变量代表LCD的背景色。LCD_DrawPoint函数虽然简单,但是至关重要,其他几乎所有上层函
数,都是通过调用这个函数实现的。
有了画点函数,自然就可以用画点函数显示字符串。显示字符串函数如下:
//显示字符串
//x,y:起点坐标
//width,height:区域大小
//size:字体大小
//*p:字符串起始地址
//mode:叠加方式(1)还是非叠加方式(0)
void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 mode,u8 *p)
{
u8 x0=x;
width+=x;
height+=y;
while((*p<='~')&&(*p>=' ')) { //判断是不是非法字符!
if(x>=width) {
x=x0;
y+=size;
}
if(y>=height)
break;//退出
LCD_ShowChar(x,y,*p,size,mode);
x+=size/2;
p++;
}
}
字符串显示函数可以设置显示的位置x、y,和显示字体的大小size,还可以以叠加方式
显示,或者以非叠加方式显示。叠加方式显示多用于在显示的图片上再显示字符。非叠加方
式一般用于普通的显示。
TFTLCD最大的用处是显示图片,自然需要介绍一下图片显示函数,函数如下:
//x,y:起点坐标
//size:图片大小
//LCD显示图片
void LCD_DisplayPic(u16 x,u16 y,u32 size,const u8 *pic)
{
u32 i;
LCD_SetCursor(x,y);
LCD_WriteRAM_Prepare(); //开始写入GRAM
for(i=0; i < size; i++)
LCD_WR_DATA(pic[i*2]<<8 | pic[i*2+1]);
}
这里假设图片经转换后得到的头文件数据类型是char类型,为8位,因为一个像素需要16
位(RGB565),所以需要合并两个8位为16,如果图片经转换后得到的头文件数据类型是16位
的,LCD_WR_DATA(pic[i*2]<<8 | pic[i*2+1])应改为LCD_WR_DATA(pic[i])。
最后,我们再介绍一下TFTLCD模块的初始化函数MCULCD_Init,该函数先复位TFTLCD,然
后读取TFTLCD控制器(驱动芯片)的型号,根据控制IC的型号执行不同的初始化代码,由于
该函数代码较长,我们摘录部分如下:
//TFTLCD初始化
void MCULCD_Init(void)
{
//TFTLCD 复位
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_RST_N_BASE,0);
delay_ms(100);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_RST_N_BASE,1);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_CS_N_BASE,1);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_RD_N_BASE,1);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_WR_N_BASE,1);
IOWR_ALTERA_AVALON_PIO_DATA(MLCD_CS_N_BASE,0);
delay_ms(50);
//尝试9341 ID的读取
LCD_WR_CMD(0XD3);
lcddev.id=LCD_RD_DATA(); //dummy read
lcddev.id=LCD_RD_DATA(); //读到0X00
lcddev.id=LCD_RD_DATA(); //读取93
lcddev.id<<=8;
lcddev.id|=LCD_RD_DATA(); //读取41
if(lcddev.id!=0X9341) //非9341,尝试看看是不是NT35310
……
主函数部分的代码如下:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include "./drive/mculcd.h"
4 #include "pic01.h" //显示的图片
5
6 int main()
7 {
8 MCULCD_Init();
9 POINT_COLOR=MLCD_RED;
10
11 LCD_DisplayPic(0,0,lcddev.width * lcddev.height,gImage_pic01);
12
13 LCD_ShowString(30,50,300,30,24,1,"Welcome to PIONEER FPGA");
14 LCD_ShowString(30,80,400,30,24,1,"This is a TFT LCD test application");
15 LCD_ShowString(30,110,200,30,24,1,"ATOM@ALIENTEK");
16 LCD_ShowString(30,140,200,30,24,1,"2018/10/10");
17
18 return 0;
19 }
“pic01.h”是由Image2Lcd软件转换得到的图片文件,该软件我们在《开拓者FPGA开发
指南》的第四十章 SD卡图片显示实验(VGA显示)中已介绍其使用,这里我们再简单的介绍
下使用步骤,按下图箭头指示的方向,首先打开选择一幅分辨率为800*480的jpg或bmp格式的
图片,图片加载进来之后,在工具界面左侧设置输出数据类型为“C语言数据(*.c)”,扫描
模式为“垂直扫描”,输出灰度为“16位真彩色”,最大宽度和高度分别为“800”和
“480”,选中“自右至左扫描”和“高位在前(MSB First)”。设置完成后在菜单栏中点
击“保存”,并在弹出的界面中选择C file(*.c;*.h)文件的保存路径并输入文件名,后缀
为.h,我们这里设置文件名为pic01.h。
![85dd929f2f6a342eff61c9b47eb9f18f.png](https://i-blog.csdnimg.cn/blog_migrate/00fc06ceb5c0bcdde4c38b5934f5ef29.jpeg)
图 10.4.2 图片转换设置
代码第17行的gImage_pic01是pic01.h中的数组,如下图所示:
![a9cb9fdc972c26060d0c7ddf58b3acd0.png](https://i-blog.csdnimg.cn/blog_migrate/789b2095110de7da495fefcba5b54b4e.jpeg)
图 10.4.3 转换后得到的图片数组
这里的字符显示我们设置为叠加模式,当然了也可以设置为非叠加模式,叠加模式在图
片上显示字符效果好。
下载验证
讲完了软件工程,接下来我们就将该实验下载至我们的开拓者开发板进行验证。
首先我们将4.3寸的ATK-4.3’TFTLCD与开发板上的MCU TFT LCD接口连接。再将下载器一
端连电脑,另一端与开发板上对应端口连接,最后连接电源线并打开电源开关。开拓者开发
板实物图如下所示:
![a1578ada962d30216e2b95257d4b3e84.png](https://i-blog.csdnimg.cn/blog_migrate/3a3f4af0fe5ebacc22f873140ea8fbd0.jpeg)
图 10.5.1 开发板实物图
接下来我们下载程序,验证TFT LCD显示功能。
我们在Quartus II软件中将qsys_lcd.sof文件下载至我们的开拓者开发板,
qsys_lcd.sof下载完成后,我们还需要在Nios II SBT for Eclipse软件中将qsys_lcd.elf文
件下载至我们的开拓者开发板,qsys_lcd.elf下载完成以后,我们的C程序将会执行在我们的
开拓者开发板上,运行结果如下图所示。
![f1e4f94abee15120e4a409b821fdda9c.png](https://i-blog.csdnimg.cn/blog_migrate/afa75b6527f9d8fcc1cb3be71c0d547a.jpeg)
图 10.5.2 实验结果图
图片上叠加显示我们写入的字符。至此,我们的TFT LCD显示实验就完成了。