**这是本文档旧的修订版!**
=====图片显示系统设计=====
实验任务
- 任务:基于 STEP-MAX10M08核心板 和 STEP BaseBoard V3.0底板 完成图片显示系统设计并观察调试结果
- 要求:将小脚丫的Logo转换成单色图片数据,驱动底板上1.8寸彩色液晶屏显示出来
- 解析:将单色图片的数据存储到rom中,驱动1.8寸将图片刷到液晶屏上。
实验目的
扩展板卡上集成了1.8寸彩色液晶屏TFT_LCD模块,大家可以驱动LCD显示文字、图片或动态的波形。本实验主要学习1.8寸串行彩色液晶屏的驱动设计,然后将小脚丫Logo处理显示,完成图片显示系统的总体设计。
- 了解1.8寸串行采样液晶屏的原理及驱动设计
- 完成图片显示系统设计实现
设计框图
实验原理
液晶屏介绍
查看底板上集成的1.8寸串行彩色液晶屏规格书,屏幕采用ST7735S的驱动芯片,接下来我们主要根据ST7735S的芯片手册来了解其工作原理和驱动方法。
ST7735S为132RGB x 162像素点 262K 控制器/驱动器,芯片可以直接跟外部处理器连接,支持串行SPI通信和8/9/16/18位并行通信(本液晶屏集成ST7735S时没有留并行接口,所以只能使用串行通信),详细参数请参考数据手册。
ST7735S支持不同位宽的并行通信格式。
在控制器给屏幕刷屏时,根据MV、MX、MY的配置支持8种不同方向的刷屏模式。
支持大量功能指令,部分系统功能指令列表如下
更多的内容这里就不一一介绍了,感兴趣的同学可以详细阅读ST7735S芯片手册。
液晶屏硬件连接
STEP BaseBoard V3.0底板上的1.8寸串行彩色液晶屏模块电路,其电路图如下:
底板上的1.8寸串行彩色液晶屏电路和VGA显示电路复用部分FPGA管脚,两者不能同时使用,当使用1.8寸串行彩色液晶屏时,DISPSEL信号置高,驱动1.8寸串行彩色液晶屏使能同时点亮背光,DISP2~ DISP_5分别对应RESET、D/C、SDA、SCK管脚,最后FPGA驱动1.8寸液晶屏完成屏显示控制即可。
液晶屏驱动设计
要驱动液晶屏需要先了解液晶屏的驱动流程,可以从液晶屏驱动芯片ST7735S的芯片手册上获取,也可以到网上找找有没有别人使用同类液晶屏的案例,或者向卖方问问有没有相关资料提供,这里我们找到了一个用51单片机驱动的程序例程,例程仅供参考,需要根据例程中的配置到芯片手册中查找确认,不可以直接套用。
首先完成液晶屏初始化操作,51程序流程如下:
<code c>
void ST7735LAIBAO177INITIAL ()
{
———–ST7735R Reset Sequence—————-
RES =1; delay (1); Delay 1ms
RES =0; delay (1); Delay 1ms
RES =1; delay (120); Delay 120ms
———-End ST7735R Reset Sequence ————
LCDWriteCommand(0x11); Sleep out
delay(120); Delay 120ms
———ST7735S Frame Rate——————-
LCDWriteCommand(0xB1);
LCDWriteData(0x05); LCDWriteData(0x3C); LCDWriteData(0x3C);
LCDWriteCommand(0xB2);
LCDWriteData(0x05); LCDWriteData(0x3C); LCDWriteData(0x3C);
LCDWriteCommand(0xB3);
LCDWriteData(0x05); LCDWriteData(0x3C); LCDWriteData(0x3C);
LCDWriteData(0x05); LCDWriteData(0x3C); LCDWriteData(0x3C);
———–End ST7735S Frame Rate—————
LCDWriteCommand(0xB4); Dot inversion
LCDWriteData(0x03);
———–ST7735S Power Sequence—————
LCDWriteCommand(0xC0);
LCDWriteData(0x28); LCDWriteData(0x08); LCDWriteData(0x04);
LCDWriteCommand(0xC1);
LCDWriteData(0XC0);
LCDWriteCommand(0xC2);
LCDWriteData(0x0D); LCDWriteData(0x00);
LCDWriteCommand(0xC3);
LCDWriteData(0x8D); LCDWriteData(0x2A);
LCDWriteCommand(0xC4);
LCDWriteData(0x8D); LCDWriteData(0xEE);
———-End ST7735S Power Sequence———-
LCDWriteCommand(0xC5); VCOM
LCDWriteData(0x18); 1a
LCDWriteCommand(0x36); MX, MY, RGB mode
LCDWriteData(0xC0);
———–ST7735S Gamma Sequence———–
LCDWriteCommand(0xE0);
LCDWriteData(0x04); LCDWriteData(0x22); LCDWriteData(0x07);
LCDWriteData(0x0A); LCDWriteData(0x2E); LCDWriteData(0x30);
LCDWriteData(0x25); LCDWriteData(0x2A); LCDWriteData(0x28);
LCDWriteData(0x26); LCDWriteData(0x2E); LCDWriteData(0x3A);
LCDWriteData(0x00); LCDWriteData(0x01); LCDWriteData(0x03);
LCDWriteData(0x13);
LCDWriteCommand(0xE1);
LCDWriteData(0x04); LCDWriteData(0x16); LCDWriteData(0x06);
LCDWriteData(0x0D); LCDWriteData(0x2D); LCDWriteData(0x26);
LCDWriteData(0x23); LCDWriteData(0x27); LCDWriteData(0x27);
LCDWriteData(0x25); LCDWriteData(0x2D); LCDWriteData(0x3B);
LCDWriteData(0x00); LCDWriteData(0x01); LCDWriteData(0x04);
LCDWriteData(0x13);
————End ST7735S Gamma Sequence———-
LCDWriteCommand(0x3A); 65k mode
LCDWriteData(0x05);
LCD_WriteCommand(0x29); Display on
}
<\code>
创建存储器,将初始化过程中写的所有指令和数据存储,同时存储的还有指令或数据标志,例如初始化第1条指令为8'h11,我们增加最高位1‘b0组成9位位宽数据。存储器部分指令和数据如下:
<code verilog>
initial begin LCD初始化的命令及数据
reginit[ 0] = {1'b0,8'h11}; 最高位为0,表示低8位为指令
reginit[ 1] = {1'b0,8'hb1};
reginit[ 2] = {1'b1,8'h05}; 最高位为1,表示低8位为数据
reginit[ 3] = {1'b1,8'h3c};
reg_init[ 4] = {1'b1,8'h3c};
<\code>
从51例程中可以看到,整个初始化过程都在给液晶屏写指令或数据,通过查看写指令或写数据的时序发现,唯一不同的就是对A0(对应底板液晶屏模块中的D/C信号)的控制,程序实现如下:
<code c>
void LCD_WriteXXX(uint dat)
{ int i;
A0=0; 写指令,如果写数据 A0=1;
CSB=0; 液晶屏使能
for(i=0;i<8;i++)
{
if(dat &0x80) SDA=1;
else SDA=0;
SCL=0; SCL=1;
dat «=1;
}
CSB=1;
}
<\code>
FPGA驱动液晶屏的设计使用状态机完成,将写数据与写指令的SPI时序整合成一个状态,另加一位指令数据控制位,程序实现如下:
<code verilog>
WRITE:begin WRITE状态,将数据按照SPI时序发送给屏幕
if(cntwrite >= 6'd17) cntwrite ⇐ 1'b0;
else cntwrite ⇐ cntwrite + 1'b1;
case(cntwrite)
6'd0: begin lcddc ⇐ datareg[8]; end 9位数据最高位为命令数据控制位
6'd1: begin lcdclk ⇐ LOW; lcddin ⇐ datareg[7]; end 先发高位数据
6'd2: begin lcdclk ⇐ HIGH; end
6'd3: begin lcdclk ⇐ LOW; lcddin ⇐ datareg[6]; end
6'd4: begin lcdclk ⇐ HIGH; end
6'd5: begin lcdclk ⇐ LOW; lcddin ⇐ datareg[5]; end
6'd6: begin lcdclk ⇐ HIGH; end
6'd7: begin lcdclk ⇐ LOW; lcddin ⇐ datareg[4]; end
6'd8: begin lcdclk ⇐ HIGH; end
6'd9: begin lcdclk ⇐ LOW; lcddin ⇐ datareg[3]; end
6'd10: begin lcdclk ⇐ HIGH; end
6'd11: begin lcdclk ⇐ LOW; lcddin ⇐ datareg[2]; end
6'd12: begin lcdclk ⇐ HIGH; end
6'd13: begin lcdclk ⇐ LOW; lcddin ⇐ datareg[1]; end
6'd14: begin lcdclk ⇐ HIGH; end
6'd15: begin lcdclk ⇐ LOW; lcddin ⇐ datareg[0]; end 后发低位数据
6'd16: begin lcdclk ⇐ HIGH; end
6'd17: begin lcdclk ⇐ LOW; state ⇐ DELAY; end
default: state ⇐ IDLE;
endcase
end
<\code>
初始化指令和数据都放到存储器中了,数据写入的SPI串行时序也已经设计成了一个状态,初始化状态只需要在复位后将存储器中的指令或数据通过WRITE状态发送给液晶屏,程序实现如下:
<code verilog>
INIT:begin 初始化状态
if(cntinit==3'd4) begin
if(cnt==INITDEPTH) cntinit ⇐ 1'b0;
else cntinit ⇐ cntinit;
end else cntinit ⇐ cntinit + 1'b1;
case(cntinit)
3'd0: lcdres ⇐ 1'b0; 复位有效
3'd1: begin numdelay⇐16'd3000; state⇐DELAY; stateback⇐INIT; end
3'd2: lcdres ⇐ 1'b1; 复位恢复
3'd3: begin numdelay⇐16'd3000; state⇐DELAY; stateback⇐INIT; end
3'd4: if(cnt>=INITDEPTH) begin 当62条指令及数据发出后,配置完成
cnt ⇐ 16'd0; state ⇐ MAIN;
end else begin
cnt ⇐ cnt + 16'd1; datareg ⇐ reginit[cnt];
if(cnt==16'd0) numdelay ⇐ 16'd50000; 第一条指令需要较长延时
else numdelay ⇐ 16'd50;
state ⇐ WRITE; stateback ⇐ INIT;
end
default: state ⇐ IDLE;
endcase
end
<\code>
初始化完成,进入刷屏状态,刷屏数据写入前首先进行区域坐标的定位,然后刷写数据,图片采用单色显示,图片ram中每位数表示一个液晶屏一个像素点的亮还是灭,彩色液晶屏本实验采用16bit格式,即需要16bit数据决定像素的颜色,16bit数据分两次发送,最终从ram模块中获取的数据每位数据都要转换成16bit的数据,0转换成背景色对应的数据,1转换成顶层色对应的数据,程序实现如下:
<code verilog>
SCAN:begin 刷屏状态,从RAM中读取数据刷屏
case(cntscan)
3'd0: if(cnt >= 11) begin 确定刷屏的区域坐标,这里为全屏
cnt ⇐ 16'd0;
cntscan ⇐ cntscan + 1'b1;
end else begin
cnt ⇐ cnt + 16'd1;
datareg ⇐ regsetxy[cnt];
numdelay ⇐ 16'd50;
state ⇐ WRITE; stateback ⇐ SCAN;
end
3'd1: begin ramclken⇐HIGH;ramaddr⇐ycnt;cntscan⇐cntscan+1'b1; end
3'd2: begin cntscan ⇐ cntscan + 1'b1; end 延时一个时钟
3'd3: begin ramclken⇐LOW;ramdatar⇐ramdata;cntscan⇐cntscan+1'b1; end
3'd4: begin 每个像素点需要16bit的数据,SPI每次传8bit,两次分别传送高8位和低8位
if(xcnt>=LCDW) begin 当一个数据(一行屏幕)写完后,
xcnt ⇐ 8'd0;
if(ycnt>=LCDH) begin ycnt ⇐ 8'd0; cntscan ⇐ cntscan + 1'b1; end 如果是最后一行就跳出循环
else begin ycnt ⇐ ycnt + 1'b1; cntscan ⇐ 3'd1; end 否则跳转至RAM时钟使能,循环刷屏
end else begin
if(highword)
根据相应bit的状态判定显示顶层色或背景色,根据highword的状态判定写高8位或低8位
datareg ⇐ {1'b1,(ramdatar[xcnt]? colort[15:8]:colorb[15:8])};
else begin
datareg ⇐ {1'b1,(ramdatar[xcnt]? colort[7:0]:colorb[7:0])};
xcnt ⇐ xcnt + 1'b1;
end
highword ⇐ ~highword; highword的状态翻转
numdelay ⇐ 16'd50; 设定延时时间
state ⇐ WRITE; 跳转至WRITE状态
stateback ⇐ SCAN; 执行完WRITE及DELAY操作后返回SCAN状态
end
end
3'd5: begin cntscan ⇐ 1'b0; state ⇐ MAIN; end
default: state ⇐ IDLE;
endcase
end
<\code>
===系统总体实现===
液晶屏驱动模块的数据来源于图片数据的ram模块,这些数据由图片取模得到,使用图片取模软件,将图片载入软件,输出数据类型选择C语言数组,根据液晶屏驱动实际情况配置对应的扫描模式,输出灰度选择单色,调整最大宽度和高度符合液晶屏要求,最后点击保存生成需要的文件。
打开生成的文件,数据格式如下,是C语言的格式
<code c>
const unsigned char gImage_11[1990] = { 0X10,0X01,0X00,0X80,0X00,0X7C,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X07,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
<\code>
使用编辑器的查找替换功能,将数据处理成下图格式
<code verilog>
132'h00000000000000000000000000000000,
132'h00000000000000000000000000000000,
132'h00000000000000000000000000000000,
132'h0000000000000000F800000000000000,
132'h0000000000000007FF00000000000000,
<\code>
创建ram模块,将图片数据初始化到ram中,程序实现图下:
<code verilog>
module LCD_RAM (input wire [7:0] Address, output reg [131:0] Q);
always @ (*)
case(Address)
8'd0 : Q = 132'h00000000000000000000000000000000;
8'd1 : Q = 132'h00000000000000000000000000000000;
8'd2 : Q = 132'h00000000000000000000000000000000;
8'd3 : Q = 132'h0000000000000000F800000000000000;
8'd4 : Q = 132'h0000000000000007FF00000000000000;
<\code>
存储图片数据的ram本实验采用分布式ram搭建,前面波形信号发生器实验中讲过ram IP核的例化及使用方法,有兴趣的同学可以自己尝试一下。
在顶层模块中,将两个模块例化并连接,最终完成图片显示系统的总体设计。
综合后的设计框图如下:
====实验步骤====
- 双击打开Quartus Prime工具软件;
- 新建工程:File → New Project Wizard(工程命名,工程目录选择,设备型号选择,EDA工具选择);
- 新建文件:File → New → Verilog HDL File,键入设计代码并保存;
- 设计综合:双击Tasks窗口页面下的Analysis & Synthesis对代码进行综合;
- 管脚约束:Assignments → Assignment Editor,根据项目需求分配管脚;
- 设计编译:双击Tasks窗口页面下的Compile Design对设计进行整体编译并生成配置文件;
- 程序烧录:点击Tools → Programmer打开配置工具,Program进行下载;
- 观察设计运行结果。
====实验现象====