基于STEP FPGA的SPI RGB液晶屏显示驱动

本节将和大家一起使用FPGA驱动底板上的1.8寸RGB液晶屏实现图片显示功能。

硬件说明


我们的STEP-BaseBoard底板上集成了1.8寸彩色液晶屏TFT_LCD模块,大家可以驱动LCD显示文字、图片或动态的波形。


首先了解一下液晶屏模块,相关资料下载:https://pan.baidu.com/s/1bp6AYsR
框图如下:
原理图如下:
原理图中的器件U1为液晶屏,液晶屏为1.8寸,128RGB160像素,串行总线(SPI),液晶屏集成了ST7735S的驱动器,处理器与ST7735S通信完成液晶屏的显示控制
ST7735S为132RGB x 162像素点 262K 控制器/驱动器,芯片可以直接跟外部处理器连接,支持串行SPI通信和8/9/16/18位并行通信(本液晶屏集成ST7735S时没有留并行接口,所以只能使用串行通信),详细参数请参考数据手册:st7735s_datasheet.pdf

Verilog代码


// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: LCD_RGB
// 
// Author: Step
// 
// Description: Drive TFT_RGB_LCD_1.8 to display
// 
// Web: www.stepfpga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.1     |2016/10/30   |Initial ver
// --------------------------------------------------------------------
module LCD_RGB #
(
	parameter LCD_W = 8'd132,			//液晶屏像素宽度
	parameter LCD_H = 8'd162			//液晶屏像素高度
)
(
	input				clk_in,			//12MHz系统时钟
	input				rst_n_in,		//系统复位,低有效
 
	output	reg			ram_lcd_clk_en,	//RAM时钟使能
	output	reg	[7:0]	ram_lcd_addr,	//RAM地址信号
	input		[131:0]	ram_lcd_data,	//RAM数据信号
 
	output	reg			lcd_rst_n_out,	//LCD液晶屏复位
	output	reg			lcd_bl_out,		//LCD背光控制
	output	reg			lcd_dc_out,		//LCD数据指令控制
	output	reg			lcd_clk_out,	//LCD时钟信号
	output	reg			lcd_data_out	//LCD数据信号
);
 
	localparam			INIT_DEPTH = 16'd73; //LCD初始化的命令及数据的数量
 
	localparam			RED		=	16'hf800;	//红色
	localparam			GREEN	=	16'h07e0;	//绿色
	localparam			BLUE	=	16'h001f;	//蓝色
	localparam			BLACK	=	16'h0000;	//黑色
	localparam			WHITE	=	16'hffff;	//白色
	localparam			YELLOW	=	16'hffe0;	//黄色
 
	localparam			IDLE	=	3'd0;
	localparam			MAIN	=	3'd1;
	localparam			INIT	=	3'd2;
	localparam			SCAN	=	3'd3;
	localparam			WRITE	=	3'd4;
	localparam			DELAY	=	3'd5;
 
	localparam			LOW		=	1'b0;
	localparam			HIGH	=	1'b1;
 
	//assign	lcd_bl_out = HIGH;				// backlight active high level
 
	wire		[15:0]	color_t	=	YELLOW;		//顶层色为黄色
	wire		[15:0]	color_b	=	BLACK;		//背景色为黑色
 
	reg			[7:0]	x_cnt;
	reg			[7:0]	y_cnt;
	reg			[131:0]	ram_data_r;
 
	reg			[8:0]	data_reg;				//
	reg			[8:0]	reg_setxy	[10:0];
	reg			[8:0]	reg_init	[72:0];
	reg			[2:0]	cnt_main;
	reg			[2:0]	cnt_init;
	reg			[2:0]	cnt_scan;
	reg			[5:0]	cnt_write;
	reg			[15:0]	cnt_delay;
	reg			[15:0]	num_delay;
	reg			[15:0]	cnt;
	reg					high_word;
	reg			[2:0] 	state = IDLE;
	reg			[2:0] 	state_back = IDLE;
	always@(posedge clk_in or negedge rst_n_in) begin
		if(!rst_n_in) begin
			x_cnt <= 8'd0;
			y_cnt <= 8'd0;
			ram_lcd_clk_en <= 1'b0;
			ram_lcd_addr <= 8'd0;
			cnt_main <= 3'd0;
			cnt_init <= 3'd0;
			cnt_scan <= 3'd0;
			cnt_write <= 6'd0;
			cnt_delay <= 16'd0;
			num_delay <= 16'd50;
			cnt <= 16'd0;
			high_word <= 1'b1;
			lcd_bl_out <= LOW;
			state <= IDLE;
			state_back <= IDLE;
		end else begin
			case(state)
				IDLE:begin
						x_cnt <= 8'd0;
						y_cnt <= 8'd0;
						ram_lcd_clk_en <= 1'b0;
						ram_lcd_addr <= 8'd0;
						cnt_main <= 3'd0;
						cnt_init <= 3'd0;
						cnt_scan <= 3'd0;
						cnt_write <= 6'd0;
						cnt_delay <= 16'd0;
						num_delay <= 16'd50;
						cnt <= 16'd0;
						high_word <= 1'b1;
						state <= MAIN;
						state_back <= MAIN;
					end
				MAIN:begin
						case(cnt_main)	//MAIN状态
							3'd0:	begin state <= INIT; cnt_main <= cnt_main + 1'b1; end
							3'd1:	begin state <= SCAN; cnt_main <= cnt_main + 1'b1; end
							3'd2:	begin cnt_main <= 1'b1; end
							default: state <= IDLE;
						endcase
					end
				INIT:begin	//初始化状态
						case(cnt_init)
							3'd0:	begin lcd_rst_n_out <= 1'b0; cnt_init <= cnt_init + 1'b1; end	//复位有效
							3'd1:	begin num_delay <= 16'd3000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	//延时
							3'd2:	begin lcd_rst_n_out <= 1'b1; cnt_init <= cnt_init + 1'b1; end	//复位恢复
							3'd3:	begin num_delay <= 16'd3000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	//延时
							3'd4:	begin 
										if(cnt>=INIT_DEPTH) begin	//当73条指令及数据发出后,配置完成
											cnt <= 16'd0;
											cnt_init <= cnt_init + 1'b1;
										end else begin
											data_reg <= reg_init[cnt];	
											if(cnt==16'd0) num_delay <= 16'd50000; //第一条指令需要较长延时
											else num_delay <= 16'd50;
											cnt <= cnt + 16'd1;
											state <= WRITE;
											state_back <= INIT;
										end
									end
							3'd5:	begin cnt_init <= 1'b0; state <= MAIN; end	//初始化完成,返回MAIN状态
							default: state <= IDLE;
						endcase
					end
				SCAN:begin	//刷屏状态,从RAM中读取数据刷屏
						case(cnt_scan)
							3'd0:	begin //确定刷屏的区域坐标,这里为全屏
										if(cnt >= 11) begin	//
											cnt <= 16'd0;
											cnt_scan <= cnt_scan + 1'b1;
										end else begin
											data_reg <= reg_setxy[cnt];
											cnt <= cnt + 16'd1;
											num_delay <= 16'd50;
											state <= WRITE;
											state_back <= SCAN;
										end
									end
							3'd1:	begin ram_lcd_clk_en <= HIGH; ram_lcd_addr <= y_cnt; cnt_scan <= cnt_scan + 1'b1; end	//RAM时钟使能
							3'd2:	begin cnt_scan <= cnt_scan + 1'b1; end	//延时一个时钟
							3'd3:	begin ram_lcd_clk_en <= LOW; ram_data_r <= ram_lcd_data; cnt_scan <= cnt_scan + 1'b1; end	//读取RAM数据,同时关闭RAM时钟使能
							3'd4:	begin //每个像素点需要16bit的数据,SPI每次传8bit,两次分别传送高8位和低8位
										if(x_cnt>=LCD_W) begin	//当一个数据(一行屏幕)写完后,
											x_cnt <= 8'd0;	
											if(y_cnt>=LCD_H) begin y_cnt <= 8'd0; cnt_scan <= cnt_scan + 1'b1; end	//如果是最后一行就跳出循环
											else begin y_cnt <= y_cnt + 1'b1; cnt_scan <= 3'd1; end		//否则跳转至RAM时钟使能,循环刷屏
										end else begin
											if(high_word) data_reg <= {1'b1,(ram_data_r[x_cnt]? color_t[15:8]:color_b[15:8])};	//根据相应bit的状态判定显示顶层色或背景色,根据high_word的状态判定写高8位或低8位
											else begin data_reg <= {1'b1,(ram_data_r[x_cnt]? color_t[7:0]:color_b[7:0])}; x_cnt <= x_cnt + 1'b1; end	//根据相应bit的状态判定显示顶层色或背景色,根据high_word的状态判定写高8位或低8位,同时指向下一个bit
											high_word <= ~high_word;	//high_word的状态翻转
											num_delay <= 16'd50;	//设定延时时间
											state <= WRITE;	//跳转至WRITE状态
											state_back <= SCAN;	//执行完WRITE及DELAY操作后返回SCAN状态
										end
									end
							3'd5:	begin cnt_scan <= 1'b0; lcd_bl_out <= HIGH; state <= MAIN; end
							default: state <= IDLE;
						endcase
					end
				WRITE:begin	//WRITE状态,将数据按照SPI时序发送给屏幕
						if(cnt_write >= 6'd17) cnt_write <= 1'b0;
						else cnt_write <= cnt_write + 1'b1;
						case(cnt_write)
							6'd0:	begin lcd_dc_out <= data_reg[8]; end	//9位数据最高位为命令数据控制位
							6'd1:	begin lcd_clk_out <= LOW; lcd_data_out <= data_reg[7]; end	//先发高位数据
							6'd2:	begin lcd_clk_out <= HIGH; end
							6'd3:	begin lcd_clk_out <= LOW; lcd_data_out <= data_reg[6]; end
							6'd4:	begin lcd_clk_out <= HIGH; end
							6'd5:	begin lcd_clk_out <= LOW; lcd_data_out <= data_reg[5]; end
							6'd6:	begin lcd_clk_out <= HIGH; end
							6'd7:	begin lcd_clk_out <= LOW; lcd_data_out <= data_reg[4]; end
							6'd8:	begin lcd_clk_out <= HIGH; end
							6'd9:	begin lcd_clk_out <= LOW; lcd_data_out <= data_reg[3]; end
							6'd10:	begin lcd_clk_out <= HIGH; end
							6'd11:	begin lcd_clk_out <= LOW; lcd_data_out <= data_reg[2]; end
							6'd12:	begin lcd_clk_out <= HIGH; end
							6'd13:	begin lcd_clk_out <= LOW; lcd_data_out <= data_reg[1]; end
							6'd14:	begin lcd_clk_out <= HIGH; end
							6'd15:	begin lcd_clk_out <= LOW; lcd_data_out <= data_reg[0]; end	//后发低位数据
							6'd16:	begin lcd_clk_out <= HIGH; end
							6'd17:	begin lcd_clk_out <= LOW; state <= DELAY; end	//
							default: state <= IDLE;
						endcase
					end
				DELAY:begin	//延时状态
						if(cnt_delay >= num_delay) begin
							cnt_delay <= 16'd0;
							state <= state_back; 
						end else cnt_delay <= cnt_delay + 1'b1;
					end
				default:state <= IDLE;
			endcase
		end
	end
 
	// data for setxy
	initial	//设定显示区域指令及数据
		begin
			reg_setxy[0]	=	{1'b0,8'h2a};
			reg_setxy[1]	=	{1'b1,8'h00};
			reg_setxy[2]	=	{1'b1,8'h00};
			reg_setxy[3]	=	{1'b1,8'h00};
			reg_setxy[4]	=	{1'b1,LCD_W-1};
			reg_setxy[5]	=	{1'b0,8'h2b};
			reg_setxy[6]	=	{1'b1,8'h00};
			reg_setxy[7]	=	{1'b1,8'h00};
			reg_setxy[8]	=	{1'b1,8'h00};
			reg_setxy[9]	=	{1'b1,LCD_H-1};
			reg_setxy[10]	=	{1'b0,8'h2c};
		end
 
	// data for init
	initial	//LCD初始化的命令及数据
		begin
			reg_init[0]		=	{1'b0,8'h11}; 
			reg_init[1]		=	{1'b0,8'hb1}; 
			reg_init[2]		=	{1'b1,8'h05}; 
			reg_init[3]		=	{1'b1,8'h3c}; 
			reg_init[4]		=	{1'b1,8'h3c}; 
			reg_init[5]		=	{1'b0,8'hb2}; 
			reg_init[6]		=	{1'b1,8'h05}; 
			reg_init[7]		=	{1'b1,8'h3c}; 
			reg_init[8]		=	{1'b1,8'h3c}; 
			reg_init[9]		=	{1'b0,8'hb3}; 
			reg_init[10]	=	{1'b1,8'h05}; 
			reg_init[11]	=	{1'b1,8'h3c}; 
			reg_init[12]	=	{1'b1,8'h3c}; 
			reg_init[13]	=	{1'b1,8'h05}; 
			reg_init[14]	=	{1'b1,8'h3c}; 
			reg_init[15]	=	{1'b1,8'h3c}; 
			reg_init[16]	=	{1'b0,8'hb4}; 
			reg_init[17]	=	{1'b1,8'h03}; 
			reg_init[18]	=	{1'b0,8'hc0}; 
			reg_init[19]	=	{1'b1,8'h28}; 
			reg_init[20]	=	{1'b1,8'h08}; 
			reg_init[21]	=	{1'b1,8'h04}; 
			reg_init[22]	=	{1'b0,8'hc1}; 
			reg_init[23]	=	{1'b1,8'hc0}; 
			reg_init[24]	=	{1'b0,8'hc2}; 
			reg_init[25]	=	{1'b1,8'h0d}; 
			reg_init[26]	=	{1'b1,8'h00}; 
			reg_init[27]	=	{1'b0,8'hc3}; 
			reg_init[28]	=	{1'b1,8'h8d}; 
			reg_init[29]	=	{1'b1,8'h2a}; 
			reg_init[30]	=	{1'b0,8'hc4}; 
			reg_init[31]	=	{1'b1,8'h8d}; 
			reg_init[32]	=	{1'b1,8'hee}; 
			reg_init[32]	=	{1'b0,8'hc5}; 
			reg_init[33]	=	{1'b1,8'h1a}; 
			reg_init[34]	=	{1'b0,8'h36}; 
			reg_init[35]	=	{1'b1,8'hc0}; 
			reg_init[36]	=	{1'b0,8'he0}; 
			reg_init[37]	=	{1'b1,8'h04}; 
			reg_init[38]	=	{1'b1,8'h22}; 
			reg_init[39]	=	{1'b1,8'h07}; 
			reg_init[40]	=	{1'b1,8'h0a}; 
			reg_init[41]	=	{1'b1,8'h2e}; 
			reg_init[42]	=	{1'b1,8'h30}; 
			reg_init[43]	=	{1'b1,8'h25}; 
			reg_init[44]	=	{1'b1,8'h2a}; 
			reg_init[45]	=	{1'b1,8'h28}; 
			reg_init[46]	=	{1'b1,8'h26}; 
			reg_init[47]	=	{1'b1,8'h2e}; 
			reg_init[48]	=	{1'b1,8'h3a}; 
			reg_init[49]	=	{1'b1,8'h00}; 
			reg_init[50]	=	{1'b1,8'h01}; 
			reg_init[51]	=	{1'b1,8'h03}; 
			reg_init[52]	=	{1'b1,8'h13}; 
			reg_init[53]	=	{1'b0,8'he1}; 
			reg_init[54]	=	{1'b1,8'h04}; 
			reg_init[55]	=	{1'b1,8'h16}; 
			reg_init[56]	=	{1'b1,8'h06}; 
			reg_init[57]	=	{1'b1,8'h0d}; 
			reg_init[58]	=	{1'b1,8'h2d}; 
			reg_init[59]	=	{1'b1,8'h26}; 
			reg_init[60]	=	{1'b1,8'h23}; 
			reg_init[61]	=	{1'b1,8'h27}; 
			reg_init[62]	=	{1'b1,8'h27}; 
			reg_init[63]	=	{1'b1,8'h25}; 
			reg_init[64]	=	{1'b1,8'h2d}; 
			reg_init[65]	=	{1'b1,8'h3b}; 
			reg_init[66]	=	{1'b1,8'h00}; 
			reg_init[67]	=	{1'b1,8'h01}; 
			reg_init[68]	=	{1'b1,8'h04}; 
			reg_init[69]	=	{1'b1,8'h13}; 
			reg_init[70]	=	{1'b0,8'h3a}; 
			reg_init[71]	=	{1'b1,8'h05}; 
			reg_init[72]	=	{1'b0,8'h29}; 
 
		end
 
endmodule

小结


本节主要为大家讲解了1.8寸RGB液晶屏图片显示的框架,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。
如果你对Diamond软件的使用不了解,请参考这里:Diamond的使用

相关资料



使用STEP-MXO2第二代的1.8寸RGB液晶屏显示驱动程序: 后续会有下载连接 待更新
使用STEP-MAX10的1.8寸RGB液晶屏显示驱动程序: 后续会有下载连接 待更新