STM32任意IO模拟8080时序驱动TFTLCD屏

本篇硬件平台STM32F103ZET6、TFTLCD屏采用2.8寸、320*240分辨率、16bitRGB、37pin、ILI9341驱动。

本来准备使用STM32F429平台的,因为它有LTDC,顺便将LCD控制的方式都实验一遍,可是接杜邦线比较麻烦,而且数据会受影响,实验没有成功,所以只能用手头的STM32F103ZET暂替。后面会再次用F4实验。写这篇博客的目的是为了通过stm32普通IO模拟8080协议去操控TFT彩屏,数据D[15:0]端口可以不是同一个GPIO_PORT,可以使GPIOA/GPIOB/GPIOC等端口任意PIN脚作为数据D[15:0]端口。

一、8080并口协议

要是用IO模拟的方式驱动LCD,首先要了解80并口协议,因为IO模拟需要遵守这个协议才可以正常驱动LCD。

LCD屏常用的时序为8080时序和6800时序, 6800总线又叫做摩托罗拉总线、8080时序也叫做英特尔总线。
Intel 总线的控制线有四根,RD 写使能, WR 读使能, ALE 地址锁存, CS 片选。而 moto 总线只有 三根,R/W 读/写,ALE 地址锁存,CE 片使能。8080总线存在许多接口8/9/16/18位接口

1、8080模式:
LCD控制以及传输数据所需要的的管脚列表

管脚名称功能描述
CS片选信号线
RS(D/I)数据/命令选择管脚(1:数据读写,0:命令读写)
WRMPU向LCD写入数据控制线
RDMPU从LCD读出数据控制线
DB[15:0]16位双向数据线
RST硬复位LCD信号
BLLCD背光控制信号
IM0IM0=0时为16bit数据总线,IM0=1时为8bit数据总线

2、8080并口读/写的过程:
(1)读取数据:

伪代码:
1、CS为低
2、RS为高(数据)
3、在RD的上升沿,读取数据线上的数据(D[15:0]),
4、WR为高
5、CS为高,结束一组数据读取

LCD_CS = 0;		//开始片选  
LCD_RS = 1;		//读数据
LCD_WR = 1;		//禁止写
LCD_RD = 0;		//上升沿读数据
data = DATAIN();//读取数据
LCD_RD = 1;		//上升沿读数据
LCD_CS = 1;		//结束片选  

读数据时序图:
在这里插入图片描述
(2)写入数据:
伪代码:
1、CS为低
2、RS为高(数据)
3、在WR的上升沿,使数据写入到 驱动 IC 里面
4、RD为高
5、CS为高,结束一组数据读取

LCD_CS = 0;			//开始片选  
LCD_RS = 1;			//写数据
LCD_RD = 1;			//禁止读
DATAOUT(Data);		//输出数据
LCD_WR = 0;			//写入开始
LCD_WR = 1;			//写入结束
LCD_CS = 0;			//结束片选  

**8080时序读写过程**

二、ILI9341

这款LCD驱动芯片我相信大家都很熟悉,具体情况可以翻阅ILI9341数据手册。我在网上看到卖9341的屏幕有37pin和40pin的。原子的使用37pin,野火的使用40pin。下面是我找到管脚定义图:
37pin:
在这里插入图片描述
40pin
在这里插入图片描述
我们通过对比可以看到40pin比37pin多了模式选择(IM),以及支持串口信号SPI。

三、GPIO口的配置

对于stm32,我们使用IO模拟8080驱动LCD屏幕时,除了CS、WR、RD、RS、RST、BL控制引脚,可以根据自己需要,定义任意IO去控制。对于数据端口DB[15:0],建议使用同一个GPIO端口使用,因为操作方便(当然后面也提供数据端口也使用任意IO控制的方法)。

接下来是GPIO配置部分:

1、DB[15:0]使用同一个GPIO口。

static void ILI9341_GPIO_Config ( void )
{
	GPIO_InitTypeDef GPIO_InitStructure;
	/* 使能复用IO时钟*/
	//	RCC_APB2PeriphClockCmd ( RCC_APB2Periph_AFIO, ENABLE );
	//复位引脚直接使用NRST,开发板复位的时候会使液晶复位
	/* 使能对应相应管脚时钟*/
	RCC_APB2PeriphClockCmd (/*控制信号*/
							ILI9341_CS_CLK|ILI9341_DC_CLK|ILI9341_WR_CLK|
							ILI9341_RD_CLK	|ILI9341_BK_CLK|
							/*数据信号*/
							ILI9341_DATA_CLK, ENABLE );
	
	//开启SWD,失能JTAG (部分PB引脚用在了jtag接口,改成SWD接口就不会有干扰)
	//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable , ENABLE);	
	/* 配置液晶相对应的数据线,PORT-D0~D15 */	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode =  GPIO_Mode_Out_PP;

	GPIO_InitStructure.GPIO_Pin = ILI9341_DATA_PIN;
	GPIO_Init ( ILI9341_DATA_PORT, &GPIO_InitStructure );	
		/* 配置液晶相对应的控制线
	 * 读   			:LCD-RD
	 * 写   			:LCD-WR
	 * 片选   			:LCD-CS
	 * 数据/命令 		:LCD-DC
	 */
	GPIO_InitStructure.GPIO_Pin = ILI9341_RD_PIN; 
	GPIO_Init (ILI9341_RD_PORT, & GPIO_InitStructure );
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_WR_PIN; 
	GPIO_Init (ILI9341_WR_PORT, & GPIO_InitStructure );
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_CS_PIN; 
	GPIO_Init ( ILI9341_CS_PORT, & GPIO_InitStructure );  
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_DC_PIN; 
	GPIO_Init ( ILI9341_DC_PORT, & GPIO_InitStructure );
			
	/* 配置LCD背光控制管脚BK*/	
	GPIO_InitStructure.GPIO_Pin = ILI9341_BK_PIN; 
	GPIO_Init ( ILI9341_BK_PORT, &GPIO_InitStructure );

在这里插入图片描述
配置的时候我们要注意,如果数据端口使用的是GPIOB(PB3、PB4),我们要禁用JTAG。对于STM32F103,我们需要将上面代码注释部分还原。如果不使用GPIOB端口,使用其他GPIO,我们则无需改动,注释保留。

2、DB[15:0]使用不同的GPIO口。

宏定义方式,便于移植

static void ILI9341_GPIO_Config ( void )
{
	GPIO_InitTypeDef GPIO_InitStructure;
		RCC_APB2PeriphClockCmd ( 	
								/*控制信号*/
								ILI9341_CS_CLK|ILI9341_DC_CLK|ILI9341_WR_CLK|
								ILI9341_RD_CLK|ILI9341_BK_CLK|
								/*数据信号*/
								ILI9341_D0_CLK|ILI9341_D1_CLK|ILI9341_D2_CLK|
								ILI9341_D3_CLK|ILI9341_D4_CLK|ILI9341_D5_CLK|
								ILI9341_D6_CLK|ILI9341_D7_CLK|ILI9341_D8_CLK|
								ILI9341_D9_CLK|ILI9341_D10_CLK|ILI9341_D11_CLK|
								ILI9341_D12_CLK|ILI9341_D13_CLK|ILI9341_D14_CLK|
								ILI9341_D15_CLK, ENABLE );
								
	/* 配置液晶相对应的数据线,PORT-D0~D15 */	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode =  GPIO_Mode_Out_PP;
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_D0_PIN;
	GPIO_Init ( ILI9341_D0_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D1_PIN;
	GPIO_Init ( ILI9341_D1_PORT, &GPIO_InitStructure );
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_D2_PIN;
	GPIO_Init ( ILI9341_D2_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D3_PIN;
	GPIO_Init ( ILI9341_D3_PORT, &GPIO_InitStructure );	

	GPIO_InitStructure.GPIO_Pin = ILI9341_D4_PIN;
	GPIO_Init ( ILI9341_D4_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D5_PIN;
	GPIO_Init ( ILI9341_D5_PORT, &GPIO_InitStructure );

	GPIO_InitStructure.GPIO_Pin = ILI9341_D6_PIN;
	GPIO_Init ( ILI9341_D6_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D7_PIN;
	GPIO_Init ( ILI9341_D7_PORT, &GPIO_InitStructure );

	GPIO_InitStructure.GPIO_Pin = ILI9341_D8_PIN;
	GPIO_Init ( ILI9341_D8_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D9_PIN;
	GPIO_Init ( ILI9341_D9_PORT, &GPIO_InitStructure );

	GPIO_InitStructure.GPIO_Pin = ILI9341_D10_PIN;
	GPIO_Init ( ILI9341_D10_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D11_PIN;
	GPIO_Init ( ILI9341_D11_PORT, &GPIO_InitStructure );

	GPIO_InitStructure.GPIO_Pin = ILI9341_D12_PIN;
	GPIO_Init ( ILI9341_D12_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D13_PIN;
	GPIO_Init ( ILI9341_D13_PORT, &GPIO_InitStructure );
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_D14_PIN;
	GPIO_Init ( ILI9341_D14_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D15_PIN;
	GPIO_Init ( ILI9341_D15_PORT, &GPIO_InitStructure );	
	/* 配置液晶相对应的控制线
	 * 读   			:LCD-RD
	 * 写   			:LCD-WR
	 * 片选   		:LCD-CS
	 * 数据/命令 	:LCD-DC
	 */
	GPIO_InitStructure.GPIO_Pin = ILI9341_RD_PIN; 
	GPIO_Init (ILI9341_RD_PORT, & GPIO_InitStructure );
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_WR_PIN; 
	GPIO_Init (ILI9341_WR_PORT, & GPIO_InitStructure );
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_CS_PIN; 
	GPIO_Init ( ILI9341_CS_PORT, & GPIO_InitStructure );  
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_DC_PIN; 
	GPIO_Init ( ILI9341_DC_PORT, & GPIO_InitStructure );
			
	/* 配置LCD背光控制管脚BK*/	
	GPIO_InitStructure.GPIO_Pin = ILI9341_BK_PIN; 
	GPIO_Init ( ILI9341_BK_PORT, &GPIO_InitStructure );
}

四、读写函数实现

1、写数据函数:

/**
  * @brief  向ILI9341写入数据
  * @param  usData :要写入的数据
  * @retval 无
  */	
__inline void ILI9341_Write_Data ( uint16_t usData )
{
	ILI9341_CS_CLR;//开始片选      
	ILI9341_DC_SET;//写数据
	ILI9341_RD_SET;//禁止读
	DATAOUT(usData);//输出数据
	ILI9341_WR_CLR;//写入开始
	ILI9341_WR_SET;//写入结束
	ILI9341_CS_SET;//结束片选   
	
}

2、写命令函数:

/**
  * @brief  向ILI9341写入命令
  * @param  usCmd :要写入的命令(表寄存器地址)
  * @retval 无
  */	
__inline void ILI9341_Write_Cmd ( uint16_t usCmd )
{
	ILI9341_CS_CLR;//开始片选      
	ILI9341_DC_CLR;//写命令
	ILI9341_RD_SET;//禁止读
	DATAOUT(usCmd);//输出命令
	ILI9341_WR_CLR;//写入开始
	ILI9341_WR_SET;//写入结束
	ILI9341_CS_SET;//结束片选        	
}

3、读数据函数:

/**
  * @brief  从ILI9341读取数据
  * @param  无
  * @retval 读取到的数据
  */	
__inline uint16_t ILI9341_Read_Data ( void )
{
	uint16_t data;
	
#if	 	 IL9341_DATA_USE_ONEPORT == 1	
	
 	ILI9341_DATA_PORT->CRL=0X88888888; //上拉输入
	ILI9341_DATA_PORT->CRH=0X88888888; //上拉输入
	ILI9341_DATA_PORT->ODR=0X0000;     //全部输出0

#elif  IL9341_DATA_USE_ANYPORT == 1
	GPIO_SET_DATA_OUT(GPIO_Set_Mode_IN);
	DATAOUT(0X0000);
#endif		
	
	ILI9341_DC_SET;
	ILI9341_WR_SET;

	ILI9341_CS_CLR;
	//读取数据
	ILI9341_RD_CLR;    
    
	data = DATAIN();  
	ILI9341_RD_SET;
	ILI9341_CS_SET; 

#if	 	 IL9341_DATA_USE_ONEPORT == 1	
	
	ILI9341_DATA_PORT->CRL=0X33333333; // 上拉输出
	ILI9341_DATA_PORT->CRH=0X33333333; // 上拉输出
	ILI9341_DATA_PORT->ODR=0XFFFF;    //全部输出高

#elif  IL9341_DATA_USE_ANYPORT == 1
	GPIO_SET_DATA_OUT(GPIO_Set_Mode_OUT);
	DATAOUT(0XFFFF);
#endif	
	return data;  
}

这里就需要说一说细节点了。可以看到我这里用了两个宏,IL9341_DATA_USE_ONEPORT 、IL9341_DATA_USE_ANYPORT分别对应于使用同一个GPIO端口和任意GPIO端口。其实GPIO初始化时也是这么定义的,只是为了便于区分,就此分开编写,后面工程中有体现。

对于使用同一个GPIO总共16个引脚,正好对应DB[15:0],所以DATAOUT()/DATAIN()函数对应如下所示:

1、数据输出DATAOUT():

//数据线输入输出
#define DATAOUT(x) 		ILI9341_DATA_PORT->ODR=x; //数据输出
#define DATAIN()     	ILI9341_DATA_PORT->IDR;   //数据输入	

那么我如果使用不同的GPIO端口作为DB[15:0]数据线,肯定就不能使用这种方法了。这个时候就需要使用如下:

//使用宏定义方式,或者直接定义为一个DATAOUT函数。
#if 1
//空间换时间
//位带操作,与一个缺点,不能根据宏定义更改,操作时,需要对这个部分单独带入对应GPIO
#define DATAOUT(x)	\
{\
	D0_W =  (x>>0&0x0001);\
	D1_W =  (x>>1&0x0001);\
	D2_W =  (x>>2&0x0001);\
	D3_W =  (x>>3&0x0001);\
	D4_W =  (x>>4&0x0001);\
	D5_W =  (x>>5&0x0001);\
	D6_W =  (x>>6&0x0001);\
	D7_W =  (x>>7&0x0001);\
	D8_W =  (x>>8&0x0001);\
	D9_W =  (x>>9&0x0001);\
	D10_W = (x>>10&0x0001);\
	D11_W = (x>>11&0x0001);\
	D12_W = (x>>12&0x0001);\
	D13_W = (x>>13&0x0001);\
	D14_W = (x>>14&0x0001);\
	D15_W = (x>>15&0x0001);\
}
#endif

//调用库函数实现,可以解决直接修改宏定义全局修改,不必像位带操作,针对每个GPIO带入
//比起使用未位带操作满了大概800ms,肉眼可见
#if 0
void DATAOUT(unsigned int x)
{
//	ILI9341_D0_WRITE =  (x>>0&0x0001)&ILI9341_D0_PIN;
//	ILI9341_D1_WRITE =  (x>>1&0x0001)&ILI9341_D1_PIN;
//	ILI9341_D2_WRITE =  (x>>2&0x0001)&ILI9341_D2_PIN;
//	ILI9341_D3_WRITE =  (x>>3&0x0001)&ILI9341_D3_PIN;
//	ILI9341_D4_WRITE =  (x>>4&0x0001)&ILI9341_D4_PIN;
//	ILI9341_D5_WRITE =  (x>>5&0x0001)&ILI9341_D5_PIN;
//	ILI9341_D6_WRITE =  (x>>6&0x0001)&ILI9341_D6_PIN;
//	ILI9341_D7_WRITE =  (x>>7&0x0001)&ILI9341_D7_PIN;
//	ILI9341_D8_WRITE =  (x>>8&0x0001)&ILI9341_D8_PIN;
//	ILI9341_D9_WRITE =  (x>>9&0x0001)&ILI9341_D9_PIN;
//	ILI9341_D10_WRITE = (x>>10&0x0001)&ILI9341_D10_PIN;
//	ILI9341_D11_WRITE = (x>>11&0x0001)&ILI9341_D11_PIN;
//	ILI9341_D12_WRITE = (x>>12&0x0001)&ILI9341_D12_PIN;
//	ILI9341_D13_WRITE = (x>>13&0x0001)&ILI9341_D13_PIN;
//	ILI9341_D14_WRITE = (x>>14&0x0001)&ILI9341_D14_PIN;
//	ILI9341_D15_WRITE = (x>>15&0x0001)&ILI9341_D15_PIN;	
//--------------------------------------------------
//-------------------------------------------
	GPIO_WriteBit(ILI9341_D0_PORT,ILI9341_D0_PIN,(BitAction)(x>>0&0x0001));
	GPIO_WriteBit(ILI9341_D1_PORT,ILI9341_D1_PIN,(BitAction)(x>>1&0x0001));
	GPIO_WriteBit(ILI9341_D2_PORT,ILI9341_D2_PIN,(BitAction)(x>>2&0x0001));
	GPIO_WriteBit(ILI9341_D3_PORT,ILI9341_D3_PIN,(BitAction)(x>>3&0x0001));
	GPIO_WriteBit(ILI9341_D4_PORT,ILI9341_D4_PIN,(BitAction)(x>>4&0x0001));
	GPIO_WriteBit(ILI9341_D5_PORT,ILI9341_D5_PIN,(BitAction)(x>>5&0x0001));
	GPIO_WriteBit(ILI9341_D6_PORT,ILI9341_D6_PIN,(BitAction)(x>>6&0x0001));
	GPIO_WriteBit(ILI9341_D7_PORT,ILI9341_D7_PIN,(BitAction)(x>>7&0x0001));
	GPIO_WriteBit(ILI9341_D8_PORT,ILI9341_D8_PIN,(BitAction)(x>>8&0x0001));
	GPIO_WriteBit(ILI9341_D9_PORT,ILI9341_D9_PIN,(BitAction)(x>>9&0x0001));
	GPIO_WriteBit(ILI9341_D10_PORT,ILI9341_D10_PIN,(BitAction)(x>>10&0x0001));
	GPIO_WriteBit(ILI9341_D11_PORT,ILI9341_D11_PIN,(BitAction)(x>>11&0x0001));
	GPIO_WriteBit(ILI9341_D12_PORT,ILI9341_D12_PIN,(BitAction)(x>>12&0x0001));
	GPIO_WriteBit(ILI9341_D13_PORT,ILI9341_D13_PIN,(BitAction)(x>>13&0x0001));
	GPIO_WriteBit(ILI9341_D14_PORT,ILI9341_D14_PIN,(BitAction)(x>>14&0x0001));
	GPIO_WriteBit(ILI9341_D15_PORT,ILI9341_D15_PIN,(BitAction)(x>>15&0x0001));
}
#endif

2、数据输入DATAIN():

#if 1
unsigned short DATAIN(void)
{
    volatile unsigned short data = 0;
    data |= D15_R;data <<= 1;
    data |= D14_R;data <<= 1;
    data |= D13_R;data <<= 1;
    data |= D12_R;data <<= 1;
    data |= D11_R;data <<= 1;
    data |= D10_R;data <<= 1;
    data |= D9_R;data <<= 1;
    data |= D8_R;data <<= 1;
    data |= D7_R;data <<= 1;
    data |= D6_R;data <<= 1;
	data |= D5_R;data <<= 1;
    data |= D4_R;data <<= 1;
    data |= D3_R;data <<= 1;
    data |= D2_R;data <<= 1;
    data |= D1_R;data <<= 1;
    data |= D0_R;

    return data;
}
#endif 

注意:在 ILI9341_Read_Data()函数中,需要切换数据端口的GPIO的模式。 开始读取时需要切换为输入模式,进行读取数据,读取完数据之后,再切换为输出模式,便于后续操作。

五、功能函数验证及实现

经过上述的配置,现在我们需要知道是否可以正常的读写。所以我们采用读取ILI9341ID号的方式来验证是否正常。

/**
 * @brief  ILI9341读取芯片ID函数,可用于测试底层的读写函数
 * @param  无
 * @retval 正常时返回值为 0x9341 
 */
uint16_t ILI9341_Read_ID(void)
{
	uint16_t id = 0;
	
	ILI9341_Write_Cmd(0xD3);
	ILI9341_Read_Data();
	ILI9341_Read_Data();
	id = ILI9341_Read_Data();
	id<<=8;
	id|=ILI9341_Read_Data();
	
	return id;
	
}

在这里插入图片描述
经过验证,上述读写函数、IO配置等都是正常的。接下来就是,画点、画线等函数了。这些驱动部分可以参考野火或者原子的亦或自己编写都可以。下面贴上野火的。以便大家不用打开工程即可查阅。

1、设置ILI9341的屏幕方向显示

/**
 * @brief  设置ILI9341的GRAM的扫描方向 
 * @param  ucOption :选择GRAM的扫描方向 
 *     @arg 0-7 :参数可选值为0-7这八个方向
 *
 *	!!!其中0、3、5、6 模式适合从左至右显示文字,
 *				不推荐使用其它模式显示文字	其它模式显示文字会有镜像效果			
 *		
 *	其中0、2、4、6 模式的X方向像素为240,Y方向像素为320
 *	其中1、3、5、7 模式下X方向像素为320,Y方向像素为240
 *
 *	其中 6 模式为大部分液晶例程的默认显示方向
 *	其中 3 模式为摄像头例程使用的方向
 *	其中 0 模式为BMP图片显示例程使用的方向
 *
 * @retval 无
 * @note  坐标图例:A表示向上,V表示向下,<表示向左,>表示向右
					X表示X轴,Y表示Y轴

------------------------------------------------------------
模式0:				.		模式1:		.	模式2:			.	模式3:					
					A		.					A		.		A					.		A									
					|		.					|		.		|					.		|							
					Y		.					X		.		Y					.		X					
					0		.					1		.		2					.		3					
	<--- X0 o		.	<----Y1	o		.		o 2X--->  .		o 3Y--->	
------------------------------------------------------------	
模式4:				.	模式5:			.	模式6:			.	模式7:					
	<--- X4 o		.	<--- Y5 o		.		o 6X--->  .		o 7Y--->	
					4		.					5		.		6					.		7	
					Y		.					X		.		Y					.		X						
					|		.					|		.		|					.		|							
					V		.					V		.		V					.		V		
---------------------------------------------------------				
											 LCD屏示例
								|-----------------|
								|			野火Logo		|
								|									|
								|									|
								|									|
								|									|
								|									|
								|									|
								|									|
								|									|
								|-----------------|
								屏幕正面(宽240,高320)

 *******************************************************/
void ILI9341_GramScan ( uint8_t ucOption )
{	
	//参数检查,只可输入0-7
	if(ucOption >7 )
		return;
	
	//根据模式更新LCD_SCAN_MODE的值,主要用于触摸屏选择计算参数
	LCD_SCAN_MODE = ucOption;
	
	//根据模式更新XY方向的像素宽度
	if(ucOption%2 == 0)	
	{
		//0 2 4 6模式下X方向像素宽度为240,Y方向为320
		LCD_X_LENGTH = ILI9341_LESS_PIXEL;
		LCD_Y_LENGTH =	ILI9341_MORE_PIXEL;
	}
	else				
	{
		//1 3 5 7模式下X方向像素宽度为320,Y方向为240
		LCD_X_LENGTH = ILI9341_MORE_PIXEL;
		LCD_Y_LENGTH =	ILI9341_LESS_PIXEL; 
	}

	//0x36命令参数的高3位可用于设置GRAM扫描方向	
	ILI9341_Write_Cmd ( 0x36 ); 
	ILI9341_Write_Data ( 0x08 |(ucOption<<5));//根据ucOption的值设置LCD参数,共0-7种模式
	ILI9341_Write_Cmd ( CMD_SetCoordinateX ); 
	ILI9341_Write_Data ( 0x00 );		/* x 起始坐标高8位 */
	ILI9341_Write_Data ( 0x00 );		/* x 起始坐标低8位 */
	ILI9341_Write_Data ( ((LCD_X_LENGTH-1)>>8)&0xFF ); /* x 结束坐标高8位 */	
	ILI9341_Write_Data ( (LCD_X_LENGTH-1)&0xFF );				/* x 结束坐标低8位 */

	ILI9341_Write_Cmd ( CMD_SetCoordinateY ); 
	ILI9341_Write_Data ( 0x00 );		/* y 起始坐标高8位 */
	ILI9341_Write_Data ( 0x00 );		/* y 起始坐标低8位 */
	ILI9341_Write_Data ( ((LCD_Y_LENGTH-1)>>8)&0xFF );	/* y 结束坐标高8位 */	 
	ILI9341_Write_Data ( (LCD_Y_LENGTH-1)&0xFF );				/* y 结束坐标低8位 */

	/* write gram start */
	ILI9341_Write_Cmd ( CMD_SetPixel );	
}

2、开窗函数

/**
 * @brief  在ILI9341显示器上开辟一个窗口
 * @param  usX :在特定扫描方向下窗口的起点X坐标
 * @param  usY :在特定扫描方向下窗口的起点Y坐标
 * @param  usWidth :窗口的宽度
 * @param  usHeight :窗口的高度
 * @retval 无
 */
void ILI9341_OpenWindow ( uint16_t usX, uint16_t usY, uint16_t usWidth, uint16_t usHeight )
{	
	ILI9341_Write_Cmd ( CMD_SetCoordinateX ); 				 /* 设置X坐标 */
	ILI9341_Write_Data ( usX >> 8  );	 /* 先高8位,然后低8位 */
	ILI9341_Write_Data ( usX & 0xff  );	 /* 设置起始点和结束点*/
	ILI9341_Write_Data ( ( usX + usWidth - 1 ) >> 8  );
	ILI9341_Write_Data ( ( usX + usWidth - 1 ) & 0xff  );

	ILI9341_Write_Cmd ( CMD_SetCoordinateY ); 			     /* 设置Y坐标*/
	ILI9341_Write_Data ( usY >> 8  );
	ILI9341_Write_Data ( usY & 0xff  );
	ILI9341_Write_Data ( ( usY + usHeight - 1 ) >> 8 );
	ILI9341_Write_Data ( ( usY + usHeight - 1) & 0xff );
	
}

3、光标设置

/**
 * @brief  设定ILI9341的光标坐标
 * @param  usX :在特定扫描方向下光标的X坐标
 * @param  usY :在特定扫描方向下光标的Y坐标
 * @retval 无
 */
static void ILI9341_SetCursor ( uint16_t usX, uint16_t usY )	
{
	ILI9341_OpenWindow ( usX, usY, 1, 1 );
}

4、像素点填充

/**
 * @brief  在ILI9341显示器上以某一颜色填充像素点
 * @param  ulAmout_Point :要填充颜色的像素点的总数目
 * @param  usColor :颜色
 * @retval 无
 */
static __inline void ILI9341_FillColor ( uint32_t ulAmout_Point, uint16_t usColor )
{
	uint32_t i = 0;
	
	
	/* memory write */
	ILI9341_Write_Cmd ( CMD_SetPixel );	
		
	for ( i = 0; i < ulAmout_Point; i ++ )
		ILI9341_Write_Data ( usColor );
}

5、清屏函数

/**
 * @brief  对ILI9341显示器的某一窗口以某种颜色进行清屏
 * @param  usX :在特定扫描方向下窗口的起点X坐标
 * @param  usY :在特定扫描方向下窗口的起点Y坐标
 * @param  usWidth :窗口的宽度
 * @param  usHeight :窗口的高度
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_Clear ( uint16_t usX, uint16_t usY, uint16_t usWidth, uint16_t usHeight )
{
	ILI9341_OpenWindow ( usX, usY, usWidth, usHeight );

	ILI9341_FillColor ( usWidth * usHeight, CurrentBackColor );		
	
}

6、对ILI9341显示器的某一点以某种颜色进行填充

/**
 * @brief  对ILI9341显示器的某一点以某种颜色进行填充
 * @param  usX :在特定扫描方向下该点的X坐标
 * @param  usY :在特定扫描方向下该点的Y坐标
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_SetPointPixel ( uint16_t usX, uint16_t usY )	
{	
	if ( ( usX < LCD_X_LENGTH ) && ( usY < LCD_Y_LENGTH ) )
  {
		ILI9341_SetCursor ( usX, usY );
		
		ILI9341_FillColor ( 1, CurrentTextColor );
	}
	
}

7、读取ILI9341 GRAN 的一个像素数据

/**
 * @brief  读取ILI9341 GRAN 的一个像素数据
 * @param  无
 * @retval 像素数据
 */
static uint16_t ILI9341_Read_PixelData ( void )	
{	
	uint16_t usR=0, usG=0, usB=0 ;

	
	ILI9341_Write_Cmd ( 0x2E );   /* 读数据 */
	
	usR = ILI9341_Read_Data (); 	/*FIRST READ OUT DUMMY DATA*/
	
	usR = ILI9341_Read_Data ();  	/*READ OUT RED DATA  */
	usB = ILI9341_Read_Data ();  	/*READ OUT BLUE DATA*/
	usG = ILI9341_Read_Data ();  	/*READ OUT GREEN DATA*/	
	
  return ( ( ( usR >> 11 ) << 11 ) | ( ( usG >> 10 ) << 5 ) | ( usB >> 11 ) );
	
}

8、获取 ILI9341 显示器上某一个坐标点的像素数据

/**
 * @brief  
 * @param  usX :在特定扫描方向下该点的X坐标
 * @param  usY :在特定扫描方向下该点的Y坐标
 * @retval 像素数据
 */
uint16_t ILI9341_GetPointPixel ( uint16_t usX, uint16_t usY )
{ 
	uint16_t usPixelData;

	
	ILI9341_SetCursor ( usX, usY );
	
	usPixelData = ILI9341_Read_PixelData ();
	
	return usPixelData;
	
}

9、Bresenham 算法画线函数

/**
 * @brief  在 ILI9341 显示器上使用 Bresenham 算法画线段 
 * @param  usX1 :在特定扫描方向下线段的一个端点X坐标
 * @param  usY1 :在特定扫描方向下线段的一个端点Y坐标
 * @param  usX2 :在特定扫描方向下线段的另一个端点X坐标
 * @param  usY2 :在特定扫描方向下线段的另一个端点Y坐标
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DrawLine ( uint16_t usX1, uint16_t usY1, uint16_t usX2, uint16_t usY2 )
{
	uint16_t us; 
	uint16_t usX_Current, usY_Current;
	
	int32_t lError_X = 0, lError_Y = 0, lDelta_X, lDelta_Y, lDistance; 
	int32_t lIncrease_X, lIncrease_Y; 	
	
	
	lDelta_X = usX2 - usX1; //计算坐标增量 
	lDelta_Y = usY2 - usY1; 
	
	usX_Current = usX1; 
	usY_Current = usY1; 
	
	
	if ( lDelta_X > 0 ) 
		lIncrease_X = 1; //设置单步方向 
	
	else if ( lDelta_X == 0 ) 
		lIncrease_X = 0;//垂直线 
	
	else 
  { 
    lIncrease_X = -1;
    lDelta_X = - lDelta_X;
  } 

	
	if ( lDelta_Y > 0 )
		lIncrease_Y = 1; 
	
	else if ( lDelta_Y == 0 )
		lIncrease_Y = 0;//水平线 
	
	else 
  {
    lIncrease_Y = -1;
    lDelta_Y = - lDelta_Y;
  } 

	
	if (  lDelta_X > lDelta_Y )
		lDistance = lDelta_X; //选取基本增量坐标轴 
	
	else 
		lDistance = lDelta_Y; 

	
	for ( us = 0; us <= lDistance + 1; us ++ )//画线输出 
	{  
		ILI9341_SetPointPixel ( usX_Current, usY_Current );//画点 
		
		lError_X += lDelta_X ; 
		lError_Y += lDelta_Y ; 
		
		if ( lError_X > lDistance ) 
		{ 
			lError_X -= lDistance; 
			usX_Current += lIncrease_X; 
		}  
		
		if ( lError_Y > lDistance ) 
		{ 
			lError_Y -= lDistance; 
			usY_Current += lIncrease_Y; 
		} 
		
	}  
	
	
}   

10、画一个矩形

/**
 * @brief  在 ILI9341 显示器上画一个矩形
 * @param  usX_Start :在特定扫描方向下矩形的起始点X坐标
 * @param  usY_Start :在特定扫描方向下矩形的起始点Y坐标
 * @param  usWidth:矩形的宽度(单位:像素)
 * @param  usHeight:矩形的高度(单位:像素)
 * @param  ucFilled :选择是否填充该矩形
  *   该参数为以下值之一:
  *     @arg 0 :空心矩形
  *     @arg 1 :实心矩形 
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DrawRectangle ( uint16_t usX_Start, uint16_t usY_Start, uint16_t usWidth, uint16_t usHeight, uint8_t ucFilled )
{
	if ( ucFilled )
	{
		ILI9341_OpenWindow ( usX_Start, usY_Start, usWidth, usHeight );
		ILI9341_FillColor ( usWidth * usHeight ,CurrentTextColor);	
	}
	else
	{
		ILI9341_DrawLine ( usX_Start, usY_Start, usX_Start + usWidth - 1, usY_Start );
		ILI9341_DrawLine ( usX_Start, usY_Start + usHeight - 1, usX_Start + usWidth - 1, usY_Start + usHeight - 1 );
		ILI9341_DrawLine ( usX_Start, usY_Start, usX_Start, usY_Start + usHeight - 1 );
		ILI9341_DrawLine ( usX_Start + usWidth - 1, usY_Start, usX_Start + usWidth - 1, usY_Start + usHeight - 1 );		
	}

}

11、Bresenham 算法画圆

/**
 * @brief  在 ILI9341 显示器上使用 Bresenham 算法画圆
 * @param  usX_Center :在特定扫描方向下圆心的X坐标
 * @param  usY_Center :在特定扫描方向下圆心的Y坐标
 * @param  usRadius:圆的半径(单位:像素)
 * @param  ucFilled :选择是否填充该圆
  *   该参数为以下值之一:
  *     @arg 0 :空心圆
  *     @arg 1 :实心圆
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DrawCircle ( uint16_t usX_Center, uint16_t usY_Center, uint16_t usRadius, uint8_t ucFilled )
{
	int16_t sCurrentX, sCurrentY;
	int16_t sError;
	
	
	sCurrentX = 0; sCurrentY = usRadius;	  
	
	sError = 3 - ( usRadius << 1 );     //判断下个点位置的标志
	
	
	while ( sCurrentX <= sCurrentY )
	{
		int16_t sCountY;
		
		
		if ( ucFilled ) 			
			for ( sCountY = sCurrentX; sCountY <= sCurrentY; sCountY ++ ) 
			{                      
				ILI9341_SetPointPixel ( usX_Center + sCurrentX, usY_Center + sCountY );           //1,研究对象 
				ILI9341_SetPointPixel ( usX_Center - sCurrentX, usY_Center + sCountY );           //2       
				ILI9341_SetPointPixel ( usX_Center - sCountY,   usY_Center + sCurrentX );         //3
				ILI9341_SetPointPixel ( usX_Center - sCountY,   usY_Center - sCurrentX );         //4
				ILI9341_SetPointPixel ( usX_Center - sCurrentX, usY_Center - sCountY );           //5    
        		ILI9341_SetPointPixel ( usX_Center + sCurrentX, usY_Center - sCountY );           //6
				ILI9341_SetPointPixel ( usX_Center + sCountY,   usY_Center - sCurrentX );         //7 	
        		ILI9341_SetPointPixel ( usX_Center + sCountY,   usY_Center + sCurrentX );         //0				
			}
		
		else
		{          
			ILI9341_SetPointPixel ( usX_Center + sCurrentX, usY_Center + sCurrentY );             //1,研究对象
			ILI9341_SetPointPixel ( usX_Center - sCurrentX, usY_Center + sCurrentY );             //2      
			ILI9341_SetPointPixel ( usX_Center - sCurrentY, usY_Center + sCurrentX );             //3
			ILI9341_SetPointPixel ( usX_Center - sCurrentY, usY_Center - sCurrentX );             //4
			ILI9341_SetPointPixel ( usX_Center - sCurrentX, usY_Center - sCurrentY );             //5       
			ILI9341_SetPointPixel ( usX_Center + sCurrentX, usY_Center - sCurrentY );             //6
			ILI9341_SetPointPixel ( usX_Center + sCurrentY, usY_Center - sCurrentX );             //7 
			ILI9341_SetPointPixel ( usX_Center + sCurrentY, usY_Center + sCurrentX );             //0
    }			
	
		sCurrentX ++;
	
		if ( sError < 0 ) 
			sError += 4 * sCurrentX + 6;	  
		else
		{
			sError += 10 + 4 * ( sCurrentX - sCurrentY );   
			sCurrentY --;
		} 
	}
}

12、显示一个英文字符

/**
 * @brief  在 ILI9341 显示器上显示一个英文字符
 * @param  usX :在特定扫描方向下字符的起始X坐标
 * @param  usY :在特定扫描方向下该点的起始Y坐标
 * @param  cChar :要显示的英文字符
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispChar_EN ( uint16_t usX, uint16_t usY, const char cChar )
{
	uint8_t  byteCount, bitCount,fontLength;	
	uint16_t ucRelativePositon;
	uint8_t *Pfont;
	
	//对ascii码表偏移(字模表不包含ASCII表的前32个非图形符号)
	ucRelativePositon = cChar - ' ';
	
	//每个字模的字节数
	fontLength = (LCD_Currentfonts->Width*LCD_Currentfonts->Height)/8;
		
	//字模首地址
	/*ascii码表偏移值乘以每个字模的字节数,求出字模的偏移位置*/
	Pfont = (uint8_t *)&LCD_Currentfonts->table[ucRelativePositon * fontLength];
	
	//设置显示窗口
	ILI9341_OpenWindow ( usX, usY, LCD_Currentfonts->Width, LCD_Currentfonts->Height);
	
	ILI9341_Write_Cmd ( CMD_SetPixel );			

	//按字节读取字模数据
	//由于前面直接设置了显示窗口,显示数据会自动换行
	for ( byteCount = 0; byteCount < fontLength; byteCount++ )
	{
			//一位一位处理要显示的颜色
			for ( bitCount = 0; bitCount < 8; bitCount++ )
			{
					if ( Pfont[byteCount] & (0x80>>bitCount) )
						ILI9341_Write_Data ( CurrentTextColor );			
					else
						ILI9341_Write_Data ( CurrentBackColor );
			}	
	}	
}

13、显示英文字符串

/**
 * @brief  在 ILI9341 显示器上显示英文字符串
 * @param  line :在特定扫描方向下字符串的起始Y坐标
  *   本参数可使用宏LINE(0)、LINE(1)等方式指定文字坐标,
  *   宏LINE(x)会根据当前选择的字体来计算Y坐标值。
	*		显示中文且使用LINE宏时,需要把英文字体设置成Font8x16
 * @param  pStr :要显示的英文字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispStringLine_EN (  uint16_t line,  char * pStr )
{
	uint16_t usX = 0;
	
	while ( * pStr != '\0' )
	{
		if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH )
		{
			usX = ILI9341_DispWindow_X_Star;
			line += LCD_Currentfonts->Height;
		}
		
		if ( ( line - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) > LCD_Y_LENGTH )
		{
			usX = ILI9341_DispWindow_X_Star;
			line = ILI9341_DispWindow_Y_Star;
		}
		
		ILI9341_DispChar_EN ( usX, line, * pStr);
		
		pStr ++;
		
		usX += LCD_Currentfonts->Width;
	}
}

14、通过行命令显示英文字符串

/**
 * @brief  在 ILI9341 显示器上显示英文字符串
 * @param  line :在特定扫描方向下字符串的起始Y坐标
  *   本参数可使用宏LINE(0)、LINE(1)等方式指定文字坐标,
  *   宏LINE(x)会根据当前选择的字体来计算Y坐标值。
	*		显示中文且使用LINE宏时,需要把英文字体设置成Font8x16
 * @param  pStr :要显示的英文字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispStringLine_EN (  uint16_t line,  char * pStr )
{
	uint16_t usX = 0;
	
	while ( * pStr != '\0' )
	{
		if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH )
		{
			usX = ILI9341_DispWindow_X_Star;
			line += LCD_Currentfonts->Height;
		}
		
		if ( ( line - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) > LCD_Y_LENGTH )
		{
			usX = ILI9341_DispWindow_X_Star;
			line = ILI9341_DispWindow_Y_Star;
		}
		
		ILI9341_DispChar_EN ( usX, line, * pStr);
		
		pStr ++;
		
		usX += LCD_Currentfonts->Width;	
	}	
}

15、显示英文字符串

/**
 * @brief  在 ILI9341 显示器上显示英文字符串
 * @param  usX :在特定扫描方向下字符的起始X坐标
 * @param  usY :在特定扫描方向下字符的起始Y坐标
 * @param  pStr :要显示的英文字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispString_EN ( 	uint16_t usX ,uint16_t usY,  char * pStr )
{
	while ( * pStr != '\0' )
	{
		if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH )
		{
			usX = ILI9341_DispWindow_X_Star;
			usY += LCD_Currentfonts->Height;
		}
		
		if ( ( usY - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) > LCD_Y_LENGTH )
		{
			usX = ILI9341_DispWindow_X_Star;
			usY = ILI9341_DispWindow_Y_Star;
		}
		ILI9341_DispChar_EN ( usX, usY, * pStr);	
		pStr ++;
		usX += LCD_Currentfonts->Width;
	}
}

16、显示英文字符串(沿Y轴方向)

/**
 * @brief  在 ILI9341 显示器上显示英文字符串(沿Y轴方向)
 * @param  usX :在特定扫描方向下字符的起始X坐标
 * @param  usY :在特定扫描方向下字符的起始Y坐标
 * @param  pStr :要显示的英文字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispString_EN_YDir (	 uint16_t usX,uint16_t usY ,  char * pStr )
{	
	while ( * pStr != '\0' )
	{
		if ( ( usY - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) >LCD_Y_LENGTH  )
		{
			usY = ILI9341_DispWindow_Y_Star;
			usX += LCD_Currentfonts->Width;
		}
		
		if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) >  LCD_X_LENGTH)
		{
			usX = ILI9341_DispWindow_X_Star;
			usY = ILI9341_DispWindow_Y_Star;
		}
		
		ILI9341_DispChar_EN ( usX, usY, * pStr);
		
		pStr ++;
		
		usY += LCD_Currentfonts->Height;		
	}	
}

17、显示一个中文字符

/**
 * @brief  在 ILI9341 显示器上显示一个中文字符
 * @param  usX :在特定扫描方向下字符的起始X坐标
 * @param  usY :在特定扫描方向下字符的起始Y坐标
 * @param  usChar :要显示的中文字符(国标码)
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */ 
void ILI9341_DispChar_CH ( uint16_t usX, uint16_t usY, uint16_t usChar )
{
	uint8_t rowCount, bitCount;
	uint8_t ucBuffer [ WIDTH_CH_CHAR*HEIGHT_CH_CHAR/8 ];	
 	uint16_t usTemp; 	

	//设置显示窗口
	ILI9341_OpenWindow ( usX, usY, WIDTH_CH_CHAR, HEIGHT_CH_CHAR );
	
	ILI9341_Write_Cmd ( CMD_SetPixel );
	
	//取字模数据  
  	GetGBKCode ( ucBuffer, usChar );	
	
	for ( rowCount = 0; rowCount < HEIGHT_CH_CHAR; rowCount++ )
	{
    /* 取出两个字节的数据,在lcd上即是一个汉字的一行 */
		usTemp = ucBuffer [ rowCount * 2 ];
		usTemp = ( usTemp << 8 );
		usTemp |= ucBuffer [ rowCount * 2 + 1 ];
		
		for ( bitCount = 0; bitCount < WIDTH_CH_CHAR; bitCount ++ )
		{			
			if ( usTemp & ( 0x8000 >> bitCount ) )  //高位在前 
			  ILI9341_Write_Data ( CurrentTextColor );				
			else
				ILI9341_Write_Data ( CurrentBackColor );			
		}		
	}
}

18、显示中文字符串

/**
 * @brief  在 ILI9341 显示器上显示中文字符串
 * @param  line :在特定扫描方向下字符串的起始Y坐标
  *   本参数可使用宏LINE(0)、LINE(1)等方式指定文字坐标,
  *   宏LINE(x)会根据当前选择的字体来计算Y坐标值。
	*		显示中文且使用LINE宏时,需要把英文字体设置成Font8x16
 * @param  pStr :要显示的英文字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispString_CH ( 	uint16_t usX , uint16_t usY, char * pStr )
{	
	uint16_t usCh;
	while( * pStr != '\0' )
	{		
		if ( ( usX - ILI9341_DispWindow_X_Star + WIDTH_CH_CHAR ) > LCD_X_LENGTH )
		{
			usX = ILI9341_DispWindow_X_Star;
			usY += HEIGHT_CH_CHAR;
		}
		
		if ( ( usY - ILI9341_DispWindow_Y_Star + HEIGHT_CH_CHAR ) > LCD_Y_LENGTH )
		{
			usX = ILI9341_DispWindow_X_Star;
			usY = ILI9341_DispWindow_Y_Star;
		}	
		
		usCh = * ( uint16_t * ) pStr;	
	  	usCh = ( usCh << 8 ) + ( usCh >> 8 );

		ILI9341_DispChar_CH ( usX, usY, usCh );
		usX += WIDTH_CH_CHAR;
		pStr += 2;           //一个汉字两个字节 
	}	   
}

19、显示中英文字符串

/**
 * @brief  在 ILI9341 显示器上显示中英文字符串
 * @param  usX :在特定扫描方向下字符的起始X坐标
 * @param  usY :在特定扫描方向下字符的起始Y坐标
 * @param  pStr :要显示的字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispString_EN_CH ( 	uint16_t usX , uint16_t usY, char * pStr )
{
	uint16_t usCh;
	
	while( * pStr != '\0' )
	{
		if ( * pStr <= 126 )	           	//英文字符
		{
			if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH )
			{
				usX = ILI9341_DispWindow_X_Star;
				usY += LCD_Currentfonts->Height;
			}
			
			if ( ( usY - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) > LCD_Y_LENGTH )
			{
				usX = ILI9341_DispWindow_X_Star;
				usY = ILI9341_DispWindow_Y_Star;
			}				
		  ILI9341_DispChar_EN ( usX, usY, * pStr );		
		  usX +=  LCD_Currentfonts->Width;	
		  pStr ++;
		}
		else	                            //汉字字符
		{
			if ( ( usX - ILI9341_DispWindow_X_Star + WIDTH_CH_CHAR ) > LCD_X_LENGTH )
			{
				usX = ILI9341_DispWindow_X_Star;
				usY += HEIGHT_CH_CHAR;
			}
			
			if ( ( usY - ILI9341_DispWindow_Y_Star + HEIGHT_CH_CHAR ) > LCD_Y_LENGTH )
			{
				usX = ILI9341_DispWindow_X_Star;
				usY = ILI9341_DispWindow_Y_Star;
			}	
			
			usCh = * ( uint16_t * ) pStr;	
			usCh = ( usCh << 8 ) + ( usCh >> 8 );	
			ILI9341_DispChar_CH ( usX, usY, usCh );	
			usX += WIDTH_CH_CHAR;	
			pStr += 2;           //一个汉字两个字节 
    }
  }	
} 

20、显示中英文字符串

/**
 * @brief  在 ILI9341 显示器上显示中英文字符串
 * @param  line :在特定扫描方向下字符串的起始Y坐标
  *   本参数可使用宏LINE(0)、LINE(1)等方式指定文字坐标,
  *   宏LINE(x)会根据当前选择的字体来计算Y坐标值。
	*		显示中文且使用LINE宏时,需要把英文字体设置成Font8x16
 * @param  pStr :要显示的字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispStringLine_EN_CH (  uint16_t line, char * pStr )
{
	uint16_t usCh;
	uint16_t usX = 0;
	
	while( * pStr != '\0' )
	{
		if ( * pStr <= 126 )	           	//英文字符
		{
			if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH )
			{
				usX = ILI9341_DispWindow_X_Star;
				line += LCD_Currentfonts->Height;
			}
			
			if ( ( line - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) > LCD_Y_LENGTH )
			{
				usX = ILI9341_DispWindow_X_Star;
				line = ILI9341_DispWindow_Y_Star;
			}			
		
		  ILI9341_DispChar_EN ( usX, line, * pStr );
		  usX +=  LCD_Currentfonts->Width;		
		  pStr ++;

		}
		
		else	                            //汉字字符
		{
			if ( ( usX - ILI9341_DispWindow_X_Star + WIDTH_CH_CHAR ) > LCD_X_LENGTH )
			{
				usX = ILI9341_DispWindow_X_Star;
				line += HEIGHT_CH_CHAR;
			}
			
			if ( ( line - ILI9341_DispWindow_Y_Star + HEIGHT_CH_CHAR ) > LCD_Y_LENGTH )
			{
				usX = ILI9341_DispWindow_X_Star;
				line = ILI9341_DispWindow_Y_Star;
			}	
			
			usCh = * ( uint16_t * ) pStr;	
			
			usCh = ( usCh << 8 ) + ( usCh >> 8 );		

			ILI9341_DispChar_CH ( usX, line, usCh );
			
			usX += WIDTH_CH_CHAR;
			
			pStr += 2;           //一个汉字两个字节 
		
    }
		
  }	
} 

21、显示中英文字符串(沿Y轴方向)

/**
 * @brief  在 ILI9341 显示器上显示中英文字符串(沿Y轴方向)
 * @param  usX :在特定扫描方向下字符的起始X坐标
 * @param  usY :在特定扫描方向下字符的起始Y坐标
 * @param  pStr :要显示的中英文字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispString_EN_CH_YDir (  uint16_t usX,uint16_t usY , char * pStr )
{
	uint16_t usCh;
	
	while( * pStr != '\0' )
	{			
			//统一使用汉字的宽高来计算换行
			if ( ( usY - ILI9341_DispWindow_Y_Star + HEIGHT_CH_CHAR ) >LCD_Y_LENGTH  )
			{
				usY = ILI9341_DispWindow_Y_Star;
				usX += WIDTH_CH_CHAR;
			}			
			if ( ( usX - ILI9341_DispWindow_X_Star + WIDTH_CH_CHAR ) >  LCD_X_LENGTH)
			{
				usX = ILI9341_DispWindow_X_Star;
				usY = ILI9341_DispWindow_Y_Star;
			}
			
		//显示	
		if ( * pStr <= 126 )	           	//英文字符
		{			
			ILI9341_DispChar_EN ( usX, usY, * pStr);
			
			pStr ++;
			
			usY += HEIGHT_CH_CHAR;		
		}
		else	                            //汉字字符
		{			
			usCh = * ( uint16_t * ) pStr;		
			usCh = ( usCh << 8 ) + ( usCh >> 8 );		
			ILI9341_DispChar_CH ( usX,usY , usCh );		
			usY += HEIGHT_CH_CHAR;		
			pStr += 2;           //一个汉字两个字节 	
    }		
  }	
}

22、缩放字体部分

#define ZOOMMAXBUFF 16384
uint8_t zoomBuff[ZOOMMAXBUFF] = {0};	//用于缩放的缓存,最大支持到128*128
uint8_t zoomTempBuff[1024] = {0};

/**
 * @brief  缩放字模,缩放后的字模由1个像素点由8个数据位来表示
										0x01表示笔迹,0x00表示空白区
 * @param  in_width :原始字符宽度
 * @param  in_heig :原始字符高度
 * @param  out_width :缩放后的字符宽度
 * @param  out_heig:缩放后的字符高度
 * @param  in_ptr :字库输入指针	注意:1pixel 1bit
 * @param  out_ptr :缩放后的字符输出指针 注意: 1pixel 8bit
 *		out_ptr实际上没有正常输出,改成了直接输出到全局指针zoomBuff中
 * @param  en_cn :0为英文,1为中文
 * @retval 无
 */
void ILI9341_zoomChar(uint16_t in_width,	//原始字符宽度
					uint16_t in_heig,		//原始字符高度
					uint16_t out_width,		//缩放后的字符宽度
					uint16_t out_heig,		//缩放后的字符高度
					uint8_t *in_ptr,		//字库输入指针	注意:1pixel 1bit
					uint8_t *out_ptr, 		//缩放后的字符输出指针 注意: 1pixel 8bit
					uint8_t en_cn)			//0为英文,1为中文	
{
	uint8_t *pts,*ots;
	//根据源字模及目标字模大小,设定运算比例因子,左移16是为了把浮点运算转成定点运算
	unsigned int xrIntFloat_16=(in_width<<16)/out_width+1; 
  unsigned int yrIntFloat_16=(in_heig<<16)/out_heig+1;
	
	unsigned int srcy_16=0;
	unsigned int y,x;
	uint8_t *pSrcLine;
	
	uint16_t byteCount,bitCount;
	
	//检查参数是否合法
	if(in_width >= 32) return;												//字库不允许超过32像素
	if(in_width * in_heig == 0) return;	
	if(in_width * in_heig >= 1024 ) return; 					//限制输入最大 32*32
	
	if(out_width * out_heig == 0) return;	
	if(out_width * out_heig >= ZOOMMAXBUFF ) return; //限制最大缩放 128*128
	pts = (uint8_t*)&zoomTempBuff;
	
	//为方便运算,字库的数据由1 pixel/1bit 映射到1pixel/8bit
	//0x01表示笔迹,0x00表示空白区
	if(en_cn == 0x00)//英文
	{
		//英文和中文字库上下边界不对,可在此处调整。需要注意tempBuff防止溢出
			for(byteCount=0;byteCount<in_heig*in_width/8;byteCount++)	
			{
				for(bitCount=0;bitCount<8;bitCount++)
					{						
						//把源字模数据由位映射到字节
						//in_ptr里bitX为1,则pts里整个字节值为1
						//in_ptr里bitX为0,则pts里整个字节值为0
						*pts++ = (in_ptr[byteCount] & (0x80>>bitCount))?1:0; 
					}
			}				
	}
	else //中文
	{			
			for(byteCount=0;byteCount<in_heig*in_width/8;byteCount++)	
			{
				for(bitCount=0;bitCount<8;bitCount++)
					{						
						//把源字模数据由位映射到字节
						//in_ptr里bitX为1,则pts里整个字节值为1
						//in_ptr里bitX为0,则pts里整个字节值为0
						*pts++ = (in_ptr[byteCount] & (0x80>>bitCount))?1:0; 
					}
			}		
	}

	//zoom过程
	pts = (uint8_t*)&zoomTempBuff;	//映射后的源数据指针
	ots = (uint8_t*)&zoomBuff;	//输出数据的指针
	for (y=0;y<out_heig;y++)	/*行遍历*/
    {
				unsigned int srcx_16=0;
        pSrcLine=pts+in_width*(srcy_16>>16);				
        for (x=0;x<out_width;x++) /*行内像素遍历*/
        {
            ots[x]=pSrcLine[srcx_16>>16]; //把源字模数据复制到目标指针中
            srcx_16+=xrIntFloat_16;			//按比例偏移源像素点
        }
        srcy_16+=yrIntFloat_16;				  //按比例偏移源像素点
        ots+=out_width;						
    }
	/*!!!缩放后的字模数据直接存储到全局指针zoomBuff里了*/
	out_ptr = (uint8_t*)&zoomBuff;	//out_ptr没有正确传出,后面调用直接改成了全局变量指针!
	
	/*实际中如果使用out_ptr不需要下面这一句!!!
		只是因为out_ptr没有使用,会导致warning。强迫症*/
	out_ptr++; 
}			


/**
 * @brief  利用缩放后的字模显示字符
 * @param  Xpos :字符显示位置x
 * @param  Ypos :字符显示位置y
 * @param  Font_width :字符宽度
 * @param  Font_Heig:字符高度
 * @param  c :要显示的字模数据
 * @param  DrawModel :是否反色显示 
 * @retval 无
 */
void ILI9341_DrawChar_Ex(uint16_t usX, 				//字符显示位置x
						uint16_t usY, 				//字符显示位置y
						uint16_t Font_width, 		//字符宽度
						uint16_t Font_Height, 		//字符高度 
						uint8_t *c,					//字模数据
						uint16_t DrawModel)			//是否反色显示
{
  uint32_t index = 0, counter = 0;

	//设置显示窗口
	ILI9341_OpenWindow ( usX, usY, Font_width, Font_Height);
	
	ILI9341_Write_Cmd ( CMD_SetPixel );		
	
	//按字节读取字模数据
	//由于前面直接设置了显示窗口,显示数据会自动换行
	for ( index = 0; index < Font_Height; index++ )
	{
			//一位一位处理要显示的颜色
			for ( counter = 0; counter < Font_width; counter++ )
			{
					//缩放后的字模数据,以一个字节表示一个像素位
					//整个字节值为1表示该像素为笔迹
					//整个字节值为0表示该像素为背景
					if ( *c++ == DrawModel )
						ILI9341_Write_Data ( CurrentBackColor );			
					else
						ILI9341_Write_Data ( CurrentTextColor );
			}	
	}	
}


/**
 * @brief  利用缩放后的字模显示字符串
 * @param  Xpos :字符显示位置x
 * @param  Ypos :字符显示位置y
 * @param  Font_width :字符宽度,英文字符在此基础上/2。注意为偶数
 * @param  Font_Heig:字符高度,注意为偶数
 * @param  c :要显示的字符串
 * @param  DrawModel :是否反色显示 
 * @retval 无
 */
void ILI9341_DisplayStringEx(uint16_t x, 				//字符显示位置x
							 uint16_t y, 				//字符显示位置y
							 uint16_t Font_width,		//要显示的字体宽度,英文字符在此基础上/2。注意为偶数
							 uint16_t Font_Height,		//要显示的字体高度,注意为偶数
							 uint8_t *ptr,				//显示的字符内容
							 uint16_t DrawModel)  		//是否反色显示



{
	uint16_t Charwidth = Font_width; //默认为Font_width,英文宽度为中文宽度的一半
	uint8_t *psr;
	uint8_t Ascii;	//英文
	uint16_t usCh;  //中文
	uint8_t ucBuffer [ WIDTH_CH_CHAR*HEIGHT_CH_CHAR/8 ];	
	
	while ( *ptr != '\0')
	{
			/****处理换行*****/
			if ( ( x - ILI9341_DispWindow_X_Star + Charwidth ) > LCD_X_LENGTH )
			{
				x = ILI9341_DispWindow_X_Star;
				y += Font_Height;
			}
			
			if ( ( y - ILI9341_DispWindow_Y_Star + Font_Height ) > LCD_Y_LENGTH )
			{
				x = ILI9341_DispWindow_X_Star;
				y = ILI9341_DispWindow_Y_Star;
			}	
			
		if(*ptr > 0x80) //如果是中文
		{			
			Charwidth = Font_width;
			usCh = * ( uint16_t * ) ptr;				
			usCh = ( usCh << 8 ) + ( usCh >> 8 );
			GetGBKCode ( ucBuffer, usCh );	//取字模数据
			//缩放字模数据,源字模为16*16
			ILI9341_zoomChar(WIDTH_CH_CHAR,HEIGHT_CH_CHAR,Charwidth,Font_Height,(uint8_t *)&ucBuffer,psr,1); 
			//显示单个字符
			ILI9341_DrawChar_Ex(x,y,Charwidth,Font_Height,(uint8_t*)&zoomBuff,DrawModel);
			x+=Charwidth;
			ptr+=2;
		}
		else
		{
				Charwidth = Font_width / 2;
				Ascii = *ptr - 32;
				//使用16*24字体缩放字模数据
				ILI9341_zoomChar(16,24,Charwidth,Font_Height,(uint8_t *)&Font16x24.table[Ascii * Font16x24.Height*Font16x24.Width/8],psr,0);
			  //显示单个字符
				ILI9341_DrawChar_Ex(x,y,Charwidth,Font_Height,(uint8_t*)&zoomBuff,DrawModel);
				x+=Charwidth;
				ptr++;
		}
	}
}


/**
 * @brief  利用缩放后的字模显示字符串(沿Y轴方向)
 * @param  Xpos :字符显示位置x
 * @param  Ypos :字符显示位置y
 * @param  Font_width :字符宽度,英文字符在此基础上/2。注意为偶数
 * @param  Font_Heig:字符高度,注意为偶数
 * @param  c :要显示的字符串
 * @param  DrawModel :是否反色显示 
 * @retval 无
 */
void ILI9341_DisplayStringEx_YDir(uint16_t x, 				//字符显示位置x
								 uint16_t y, 				//字符显示位置y
								 uint16_t Font_width,		//要显示的字体宽度,英文字符在此基础上/2。注意为偶数
								 uint16_t Font_Height,		//要显示的字体高度,注意为偶数
								 uint8_t *ptr,				//显示的字符内容
								 uint16_t DrawModel)  		//是否反色显示
{
	uint16_t Charwidth = Font_width; //默认为Font_width,英文宽度为中文宽度的一半
	uint8_t *psr;
	uint8_t Ascii;	//英文
	uint16_t usCh;  //中文
	uint8_t ucBuffer [ WIDTH_CH_CHAR*HEIGHT_CH_CHAR/8 ];	
	
	while ( *ptr != '\0')
	{			
			//统一使用汉字的宽高来计算换行
			if ( ( y - ILI9341_DispWindow_X_Star + Font_width ) > LCD_X_LENGTH )
			{
				y = ILI9341_DispWindow_X_Star;
				x += Font_width;
			}
			
			if ( ( x - ILI9341_DispWindow_Y_Star + Font_Height ) > LCD_Y_LENGTH )
			{
				y = ILI9341_DispWindow_X_Star;
				x = ILI9341_DispWindow_Y_Star;
			}	
			
		if(*ptr > 0x80) //如果是中文
		{			
			Charwidth = Font_width;
			usCh = * ( uint16_t * ) ptr;				
			usCh = ( usCh << 8 ) + ( usCh >> 8 );
			GetGBKCode ( ucBuffer, usCh );	//取字模数据
			//缩放字模数据,源字模为16*16
			ILI9341_zoomChar(WIDTH_CH_CHAR,HEIGHT_CH_CHAR,Charwidth,Font_Height,(uint8_t *)&ucBuffer,psr,1); 
			//显示单个字符
			ILI9341_DrawChar_Ex(x,y,Charwidth,Font_Height,(uint8_t*)&zoomBuff,DrawModel);
			y+=Font_Height;
			ptr+=2;
		}
		else
		{
				Charwidth = Font_width / 2;
				Ascii = *ptr - 32;
				//使用16*24字体缩放字模数据
				ILI9341_zoomChar(16,24,Charwidth,Font_Height,(uint8_t *)&Font16x24.table[Ascii * 						Font16x24.Height*Font16x24.Width/8],psr,0);
			  //显示单个字符
				ILI9341_DrawChar_Ex(x,y,Charwidth,Font_Height,(uint8_t*)&zoomBuff,DrawModel);
				y+=Font_Height;
				ptr++;
		}
	}
}

23、设置英文字体类型

/**
  * @brief  设置英文字体类型
  * @param  fonts: 指定要选择的字体
	*		参数为以下值之一
  * 	@arg:Font24x32;
  * 	@arg:Font16x24;
  * 	@arg:Font8x16;
  * @retval None
  */
void LCD_SetFont(sFONT *fonts)
{
  LCD_Currentfonts = fonts;
}

24、获取当前字体类型

/**
  * @brief  获取当前字体类型
  * @param  None.
  * @retval 返回当前字体类型
  */
sFONT *LCD_GetFont(void)
{
  return LCD_Currentfonts;
}

25、设置LCD的前景(字体)及背景颜色

/**
  * @brief  设置LCD的前景(字体)及背景颜色,RGB565
  * @param  TextColor: 指定前景(字体)颜色
  * @param  BackColor: 指定背景颜色
  * @retval None
  */
void LCD_SetColors(uint16_t TextColor, uint16_t BackColor) 
{
  CurrentTextColor = TextColor; 
  CurrentBackColor = BackColor;
}

27、获取LCD的前景(字体)及背景颜色

/**
  * @brief  获取LCD的前景(字体)及背景颜色,RGB565
  * @param  TextColor: 用来存储前景(字体)颜色的指针变量
  * @param  BackColor: 用来存储背景颜色的指针变量
  * @retval None
  */
void LCD_GetColors(uint16_t *TextColor, uint16_t *BackColor)
{
  *TextColor = CurrentTextColor;
  *BackColor = CurrentBackColor;
}

28、设置LCD的前景(字体)颜色

/**
  * @brief  设置LCD的前景(字体)颜色,RGB565
  * @param  Color: 指定前景(字体)颜色 
  * @retval None
  */
void LCD_SetTextColor(uint16_t Color)
{
  CurrentTextColor = Color;
}

29、设置LCD的背景颜色

/**
  * @brief  设置LCD的背景颜色,RGB565
  * @param  Color: 指定背景颜色 
  * @retval None
  */
void LCD_SetBackColor(uint16_t Color)
{
  CurrentBackColor = Color;
}

30、清除某行文字

/**
  * @brief  清除某行文字
  * @param  Line: 指定要删除的行
  *   本参数可使用宏LINE(0)、LINE(1)等方式指定要删除的行,
  *   宏LINE(x)会根据当前选择的字体来计算Y坐标值,并删除当前字体高度的第x行。
  * @retval None
  */
void LCD_ClearLine(uint16_t Line)
{
  ILI9341_Clear(0,Line,LCD_X_LENGTH,((sFONT *)LCD_GetFont())->Height);	/* 清屏,显示全黑 */

}

//============================================
分割线
2021/06/17添加:

void GPIO_SET_DATA_OUT(uint8_t Mode)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	/* 配置液晶相对应的数据线,PORT-D0~D15 */
	
  if(Mode == GPIO_Set_Mode_OUT)
	{
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_InitStructure.GPIO_Mode =  GPIO_Mode_Out_PP;
	}
	else{
		GPIO_InitStructure.GPIO_Mode =  GPIO_Mode_IPU;
	}	

	
	GPIO_InitStructure.GPIO_Pin = ILI9341_D0_PIN;
	GPIO_Init ( ILI9341_D0_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D1_PIN;
	GPIO_Init ( ILI9341_D1_PORT, &GPIO_InitStructure );
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_D2_PIN;
	GPIO_Init ( ILI9341_D2_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D3_PIN;
	GPIO_Init ( ILI9341_D3_PORT, &GPIO_InitStructure );	

	GPIO_InitStructure.GPIO_Pin = ILI9341_D4_PIN;
	GPIO_Init ( ILI9341_D4_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D5_PIN;
	GPIO_Init ( ILI9341_D5_PORT, &GPIO_InitStructure );

	GPIO_InitStructure.GPIO_Pin = ILI9341_D6_PIN;
	GPIO_Init ( ILI9341_D6_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D7_PIN;
	GPIO_Init ( ILI9341_D7_PORT, &GPIO_InitStructure );

	GPIO_InitStructure.GPIO_Pin = ILI9341_D8_PIN;
	GPIO_Init ( ILI9341_D8_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D9_PIN;
	GPIO_Init ( ILI9341_D9_PORT, &GPIO_InitStructure );

	GPIO_InitStructure.GPIO_Pin = ILI9341_D10_PIN;
	GPIO_Init ( ILI9341_D10_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D11_PIN;
	GPIO_Init ( ILI9341_D11_PORT, &GPIO_InitStructure );

	GPIO_InitStructure.GPIO_Pin = ILI9341_D12_PIN;
	GPIO_Init ( ILI9341_D12_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D13_PIN;
	GPIO_Init ( ILI9341_D13_PORT, &GPIO_InitStructure );
	
	GPIO_InitStructure.GPIO_Pin = ILI9341_D14_PIN;
	GPIO_Init ( ILI9341_D14_PORT, &GPIO_InitStructure );														
													
	GPIO_InitStructure.GPIO_Pin = ILI9341_D15_PIN;
	GPIO_Init ( ILI9341_D15_PORT, &GPIO_InitStructure );	
}

本文章工程下载链接:STM32任意IO模拟8080驱动TFTLCD

  • 46
    点赞
  • 208
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Terry.Z_1009

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值