LVGL移植教程

LVGL移植教程

  • 一、开发平台简介
  • 二、LVGL简介
  • 三、下载LVGL 8.2源码
  • 四、LVGL移植
    • 4.1、硬件平台要求
      • 4.1.1 MCU
      • 4.1.2 显示器
    • 4.2 LVGL源码文件简介
    • 4.3 LVGL移植
      • 4.3.1 移植源码的基础工程
      • 4.3.2 移植LVGL
        • 4.3.2.1 移植文件
        • 4.3.2.2 修改lv_conf_h文件
        • 4.3.2.3 链接显示屏和LVGL
        • 4.3.2.3链接触摸与LVGL
        • 4.3.2.4 修改main.c函数
  • 五、可能会出现的故障

一、开发平台简介

目前是基于LVGL8.2版本和野火STM32mini开发板进行学习开发的,学习参考资料正点原子LVGL教程哔哩哔哩正点原子LVGL教学视频
LVGL官网
LVGL中文网(百问网)

二、LVGL简介

LVGL(Light and Versatile Graphics Library)是一个免费的轻量级开源图形库,其主要特
征有:

  1. 丰富的部件:开关、按钮、图表、列表、滑块、图片,等等。
  2. 高级图形属性:具有动画、抗锯齿、不透明度、平滑滚动等高级图形属性。
  3. 支持多种输入设备:如触摸屏、鼠标、键盘、编码器等。
  4. 支持多语言:UTF-8 编码。
  5. 支持多显示器:它可以同时使用多个 TFT 或者单色显示器。
  6. 支持多种样式属性:它具有类 CSS 的样式,支持自定义图形元素。
  7. 独立于硬件之外:它可以与任何微控制器或显示器一起使用。
  8. 可扩展性:它能够以小内存运行(最低 64 kB 闪存,16 kB RAM 的 MCU)。
  9. 支持操作系统、外部存储器和 GPU(不是必需的)。
  10. 具有高级图形效果:可进行单帧缓冲区操作。
  11. 纯 C 编写: LVGL 基于 C 语言编写,以获得最大的兼容性。
    LVGL 是一款具有丰富部件,具备高级图形特性,支持多种输入设备和多国语言,独立于硬件之外的开源图形库。该网页主要包含用户文档、图片转换器和字体转换器。

三、下载LVGL 8.2源码

源码下载提供以下2种方式:

  1. GitHub
  2. 百度网盘 提取码:08ej

四、LVGL移植

4.1、硬件平台要求

4.1.1 MCU

要求 说明
微处理器 16、32、64位
时钟频率 >16MHz
Flash/ROM >64kB,推荐>180kB
RAM 8kB,推荐24kB

4.1.2 显示器

LVGL 只需要一个简单的驱动程序函数即可将像素阵列复制到显示器的给定区域中,其对
显示屏的兼容性很强,具体要求如下(满足其一即可):
① 具有 8/16 /24/ 32 位色深的显示屏。
② HDMI 端口的显示器。
③ 小型单色显示器。
④ LED 矩阵。
⑤ 其他可以控制像素颜色/状态的显示器。

4.2 LVGL源码文件简介

4.3 LVGL移植

4.3.1 移植源码的基础工程

使用STM32CubMX生成STM32F103RCT6的HAL库工程基础,配置如下:
仿真配置:
LVGL移植教程_第1张图片
时钟配置:
LVGL移植教程_第2张图片
定时器配置(1ms中断):
LVGL移植教程_第3张图片

串口配置:
LVGL移植教程_第4张图片
LVGL移植教程_第5张图片
触摸引脚配置:
LVGL移植教程_第6张图片
其中PC12是XPT2046 触摸屏触摸信号指示引脚,PC8,PC9,PC10,PC11是 XPT2046 触摸屏模拟SPI引脚。

时钟配置:
LVGL移植教程_第7张图片
工程生成:
LVGL移植教程_第8张图片
LVGL移植教程_第9张图片
完成以上配置后,生成工程文件。

修改工程文件:(Application/MDK-ARM文件夹下的startup_stm32f103xe.s文件)
LVGL移植教程_第10张图片
在工程内添加如下文件夹:
LVGL移植教程_第11张图片
在LCD文件夹内添加路径(野火STM32F103MINI开发板资料\1-程序源码_教程文档\1-[野火]《STM32库开发实战指南》(标准库源码)【优先学习】\1-书籍配套例程-F103RCMINI_20210921\29-电阻触摸屏—触摸画板\User)中的lcd和font文件夹中的所有文件
LVGL移植教程_第12张图片
添加完成后,如下:
LVGL移植教程_第13张图片
修改bsp_ili9341_lcd.c和.h文件:

.c文件末尾添加以下内容
/*

  LVGL适配接口

*/
/**
 * @brief  对ILI9341显示器的某一点以某种颜色进行填充
 * @param  usX :在特定扫描方向下该点的X坐标
 * @param  usY :在特定扫描方向下该点的Y坐标
 * @param  Color :需要画的颜色
 * @retval 无
 */
void ILI9341_lvglSetDrawPixel ( uint16_t usX, uint16_t usY ,uint16_t Color)	
{	
	if ( ( usX < LCD_X_LENGTH ) && ( usY < LCD_Y_LENGTH ) )
  {
		ILI9341_SetCursor ( usX, usY );	
		ILI9341_FillColor ( 1, Color );
	}
}

/**
 * @brief  对ILI9341显示器的某一窗口以某种颜色进行清屏
 * @param  usX :在特定扫描方向下窗口的起点X坐标
 * @param  usY :在特定扫描方向下窗口的起点Y坐标
 * @param  usWidth :窗口的宽度
 * @param  usHeight :窗口的高度
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_lvglClear ( uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,uint16_t *color)
{
	uint32_t i = 0;
	ILI9341_OpenWindow ( x1, y1, x2-x1+1, y2-y1+1);
	/* memory write */
	ILI9341_Write_Cmd ( CMD_SetPixel );		
	for ( i = 0; i < (x2-x1+1)*(y2-y1+1); i ++ )
	{
		ILI9341_Write_Data (color[i]);
	}
				
}

.h文件添加函数声明
void ILI9341_lvglSetDrawPixel ( uint16_t usX, uint16_t usY ,uint16_t Color);
void ILI9341_lvglClear ( uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,uint16_t *color);

fonts.c文件修改:解决报错,屏蔽掉FLASH相关的函数(实际功能中不需要使用)

bsp_xpt2046_lcd.c和.h文件修改:

.h文件修改(由于驱动文件是基于标准库编写的,需要将标准库相关定义修改成hal库)
#ifndef __BSP_XPT2046_LCD_H
#define	__BSP_XPT2046_LCD_H


#include "main.h"


/******************************* XPT2046 触摸屏触摸信号指示引脚定义(不使用中断) ***************************/
//#define             XPT2046_PENIRQ_GPIO_CLK                        RCC_APB2Periph_GPIOC   
#define             XPT2046_PENIRQ_GPIO_PORT                       GPIOC
#define             XPT2046_PENIRQ_GPIO_PIN                        GPIO_PIN_12

//触屏信号有效电平
#define             XPT2046_PENIRQ_ActiveLevel                     0
#define             XPT2046_PENIRQ_Read()                          HAL_GPIO_ReadPin(XPT2046_PENIRQ_GPIO_PORT, XPT2046_PENIRQ_GPIO_PIN)



/******************************* XPT2046 触摸屏模拟SPI引脚定义 ***************************/
//#define             XPT2046_SPI_GPIO_CLK                         RCC_APB2Periph_GPIOC

#define             XPT2046_SPI_CS_PIN		                        GPIO_PIN_9
#define             XPT2046_SPI_CS_PORT		                      GPIOC

#define	            XPT2046_SPI_CLK_PIN	                        GPIO_PIN_8
#define             XPT2046_SPI_CLK_PORT	                        GPIOC

#define	            XPT2046_SPI_MOSI_PIN	                        GPIO_PIN_10
#define	            XPT2046_SPI_MOSI_PORT	                      GPIOC

#define	            XPT2046_SPI_MISO_PIN	                        GPIO_PIN_11
#define	            XPT2046_SPI_MISO_PORT	                      GPIOC


#define             XPT2046_CS_ENABLE()                          HAL_GPIO_WritePin(XPT2046_SPI_CS_PORT,XPT2046_SPI_CS_PIN,GPIO_PIN_SET)
#define             XPT2046_CS_DISABLE()                         HAL_GPIO_WritePin(XPT2046_SPI_CS_PORT, XPT2046_SPI_CS_PIN,GPIO_PIN_RESET)  

#define             XPT2046_CLK_HIGH()                           HAL_GPIO_WritePin(XPT2046_SPI_CLK_PORT, XPT2046_SPI_CLK_PIN,GPIO_PIN_SET)  
#define             XPT2046_CLK_LOW()                            HAL_GPIO_WritePin(XPT2046_SPI_CLK_PORT, XPT2046_SPI_CLK_PIN,GPIO_PIN_RESET) 

#define             XPT2046_MOSI_1()                             HAL_GPIO_WritePin(XPT2046_SPI_MOSI_PORT, XPT2046_SPI_MOSI_PIN,GPIO_PIN_SET) 
#define             XPT2046_MOSI_0()                             HAL_GPIO_WritePin( XPT2046_SPI_MOSI_PORT, XPT2046_SPI_MOSI_PIN,GPIO_PIN_RESET)

#define             XPT2046_MISO()                               HAL_GPIO_ReadPin( XPT2046_SPI_MISO_PORT, XPT2046_SPI_MISO_PIN )



/******************************* XPT2046 触摸屏参数定义 ***************************/
//校准触摸屏时触摸坐标的AD值相差门限 
#define             XPT2046_THRESHOLD_CalDiff                    2               

#define	            XPT2046_CHANNEL_X 	                          0x90 	          //通道Y+的选择控制字	
#define	            XPT2046_CHANNEL_Y 	                          0xd0	          //通道X+的选择控制字

//触摸参数写到FLASH里的标志
#define							FLASH_TOUCH_PARA_FLAG_VALUE					0xA5

//触摸标志写到FLASH里的地址
#define 							FLASH_TOUCH_PARA_FLAG_ADDR						(1*1024)

//触摸参数写到FLASH里的地址
#define 							FLASH_TOUCH_PARA_ADDR									(2*1024)


/*信息输出*/
#define XPT2046_DEBUG_ON         0

//#define XPT2046_INFO(fmt,arg...)           printf("<<-XPT2046-INFO->> "fmt"\n",##arg)
//#define XPT2046_ERROR(fmt,arg...)          printf("<<-XPT2046-ERROR->> "fmt"\n",##arg)
//#define XPT2046_DEBUG(fmt,arg...)          do{\
//                                          if(XPT2046_DEBUG_ON)\
//                                          printf("<<-XPT2046-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
//                                          }while(0)

/******************************* 声明 XPT2046 相关的数据类型 ***************************/
typedef	struct          //液晶坐标结构体 
{		
	/*负数值表示无新数据*/
   int16_t x;			//记录最新的触摸参数值
   int16_t y; 
	
	/*用于记录连续触摸时(长按)的上一次触摸位置*/
	 int16_t pre_x;		
   int16_t pre_y;
	
} strType_XPT2046_Coordinate;   


typedef struct         //校准因子结构体 
{
	 float An,  		 //注:sizeof(long double) = 8
					Bn,     
					Cn,   
					Dn,    
					En,    
					Fn,     
					Divider;
	
} strType_XPT2046_Calibration;


typedef struct         //校准系数结构体(最终使用)
{
	 float dX_X,  			 
					dX_Y,     
					dX,   
					dY_X,    
					dY_Y,    
					dY;

} strType_XPT2046_TouchPara;

/******触摸状态机相关******/
typedef enum
{
	XPT2046_STATE_RELEASE  = 0,	//触摸释放
	XPT2046_STATE_WAITING,			//触摸按下
	XPT2046_STATE_PRESSED,			//触摸按下
}enumTouchState	;

#define TOUCH_PRESSED 				1
#define TOUCH_NOT_PRESSED			0

//触摸消抖阈值
#define DURIATION_TIME				2

/******************************* 声明 XPT2046 相关的外部全局变量 ***************************/
extern volatile uint8_t               ucXPT2046_TouchFlag;

extern strType_XPT2046_TouchPara      strXPT2046_TouchPara[];



/******************************** XPT2046 触摸屏函数声明 **********************************/
void XPT2046_Init( void );  //XPT2046 初始化函数
uint8_t XPT2046_Touch_Calibrate( uint8_t LCD_Mode );  //XPT2046 触摸屏校准
uint8_t XPT2046_Get_TouchedPoint( strType_XPT2046_Coordinate * displayPtr, strType_XPT2046_TouchPara * para ); //获取 XPT2046 触摸点(校准后)的坐标
void XPT2046_TouchDown(strType_XPT2046_Coordinate * touch); //触摸屏被按下的时候会调用本函数
void XPT2046_TouchUp(strType_XPT2046_Coordinate * touch);   //触摸屏释放的时候会调用本函数
void XPT2046_TouchEvenHandler(void );        //检测到触摸中断时调用的处理函数,通过它调用tp_down 和tp_up汇报触摸点
void Calibrate_or_Get_TouchParaWithFlash(uint8_t LCD_Mode,uint8_t forceCal);  //从FLASH中获取 或 重新校正触摸参数(校正后会写入到SPI FLASH中)
uint8_t XPT2046_TouchDetect(void);  //原文件中并未在.h文件中声明,在使用LVGL是必须要声明
#endif /* __BSP_TOUCH_H */
.c文件
/**
  ******************************************************************************
  * @file    bsp_ili9341_lcd.c
  * @version V1.0
  * @date    2013-xx-xx
  * @brief   触摸屏驱动
  ******************************************************************************
  * @attention
  *
  * 实验平台:野火 F103-MINI STM32 开发板 
  * 论坛    :http://www.firebbs.cn
  * 淘宝    :https://fire-stm32.taobao.com
  *
  ******************************************************************************
  */ 

#include "bsp_xpt2046_lcd.h"
#include "bsp_ili9341_lcd.h"
#include "fonts.h"
#include  
#include 

/******************************* 声明 XPT2046 相关的静态函数 ***************************/
static void                   XPT2046_DelayUS                       ( __IO uint32_t ulCount );
static void                   XPT2046_WriteCMD                      ( uint8_t ucCmd );
static uint16_t               XPT2046_ReadCMD                       ( void );
static uint16_t               XPT2046_ReadAdc                       ( uint8_t ucChannel );
static void                   XPT2046_ReadAdc_XY                    ( int16_t * sX_Ad, int16_t * sY_Ad );
static uint8_t                XPT2046_ReadAdc_Smooth_XY             ( strType_XPT2046_Coordinate * pScreenCoordinate );
static uint8_t                XPT2046_Calculate_CalibrationFactor   ( strType_XPT2046_Coordinate * pDisplayCoordinate, strType_XPT2046_Coordinate * pScreenSample, strType_XPT2046_Calibration * pCalibrationFactor );
static void                   ILI9341_DrawCross                     ( uint16_t usX, uint16_t usY );



/******************************* 定义 XPT2046 全局变量 ***************************/
//默认触摸参数,不同的屏幕稍有差异,可重新调用触摸校准函数获取
strType_XPT2046_TouchPara strXPT2046_TouchPara[] = { 	
 -0.006464,   -0.073259,  280.358032,    0.074878,    0.002052,   -6.545977,//扫描方式0
	0.086314,    0.001891,  -12.836658,   -0.003722,   -0.065799,  254.715714,//扫描方式1
	0.002782,    0.061522,  -11.595689,    0.083393,    0.005159,  -15.650089,//扫描方式2
	0.089743,   -0.000289,  -20.612209,   -0.001374,    0.064451,  -16.054003,//扫描方式3
	0.000767,   -0.068258,  250.891769,   -0.085559,   -0.000195,  334.747650,//扫描方式4
 -0.084744,    0.000047,  323.163147,   -0.002109,   -0.066371,  260.985809,//扫描方式5
 -0.001848,    0.066984,  -12.807136,   -0.084858,   -0.000805,  333.395386,//扫描方式6
 -0.085470,   -0.000876,  334.023163,   -0.003390,    0.064725,   -6.211169,//扫描方式7
};

volatile uint8_t ucXPT2046_TouchFlag = 0;



/**
  * @brief  XPT2046 初始化函数
  * @param  无
  * @retval 无
  */	
void XPT2046_Init ( void )
{

//  GPIO_InitTypeDef  GPIO_InitStructure;
//	

//  /* 开启GPIO时钟 */
//  RCC_APB2PeriphClockCmd ( XPT2046_SPI_GPIO_CLK|XPT2046_PENIRQ_GPIO_CLK, ENABLE );

//  /* 模拟SPI GPIO初始化 */          
//  GPIO_InitStructure.GPIO_Pin=XPT2046_SPI_CLK_PIN;
//  GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz ;	  
//  GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
//  GPIO_Init(XPT2046_SPI_CLK_PORT, &GPIO_InitStructure);

//  GPIO_InitStructure.GPIO_Pin = XPT2046_SPI_MOSI_PIN;
//  GPIO_Init(XPT2046_SPI_MOSI_PORT, &GPIO_InitStructure);

//  GPIO_InitStructure.GPIO_Pin = XPT2046_SPI_MISO_PIN; 
//  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz ;
//  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;      
//  GPIO_Init(XPT2046_SPI_MISO_PORT, &GPIO_InitStructure);

//  GPIO_InitStructure.GPIO_Pin = XPT2046_SPI_CS_PIN; 
//  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz ;
//  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;      
//  GPIO_Init(XPT2046_SPI_CS_PORT, &GPIO_InitStructure); 
//   
//  /* 拉低片选,选择XPT2046 */
//  XPT2046_CS_DISABLE();		
//								
//	//触摸屏触摸信号指示引脚,不使用中断
//  GPIO_InitStructure.GPIO_Pin = XPT2046_PENIRQ_GPIO_PIN;       
//  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	 // 上拉输入
//  GPIO_Init(XPT2046_PENIRQ_GPIO_PORT, &GPIO_InitStructure);

		
}



/**
  * @brief  用于 XPT2046 的简单微秒级延时函数
  * @param  nCount :延时计数值,单位为微妙
  * @retval 无
  */	
static void XPT2046_DelayUS ( __IO uint32_t ulCount )
{
	uint32_t i;


	for ( i = 0; i < ulCount; i ++ )
	{
		uint8_t uc = 12;     //设置值为12,大约延1微秒  
	      
		while ( uc -- );     //延1微秒	

	}
	
}



/**
  * @brief  XPT2046 的写入命令
  * @param  ucCmd :命令
  *   该参数为以下值之一:
  *     @arg 0x90 :通道Y+的选择控制字
  *     @arg 0xd0 :通道X+的选择控制字
  * @retval 无
  */
static void XPT2046_WriteCMD ( uint8_t ucCmd ) 
{
	uint8_t i;


	XPT2046_MOSI_0();
	
	XPT2046_CLK_LOW();

	for ( i = 0; i < 8; i ++ ) 
	{
		( ( ucCmd >> ( 7 - i ) ) & 0x01 ) ? XPT2046_MOSI_1() : XPT2046_MOSI_0();
		
	  XPT2046_DelayUS ( 5 );
		
		XPT2046_CLK_HIGH();

	  XPT2046_DelayUS ( 5 );

		XPT2046_CLK_LOW();
	}
	
}


/**
  * @brief  XPT2046 的读取命令
  * @param  无
  * @retval 读取到的数据
  */
static uint16_t XPT2046_ReadCMD ( void ) 
{
	uint8_t i;
	uint16_t usBuf=0, usTemp;
	


	XPT2046_MOSI_0();

	XPT2046_CLK_HIGH();

	for ( i=0;i<12;i++ ) 
	{
		XPT2046_CLK_LOW();    
	
		usTemp = XPT2046_MISO();
		
		usBuf |= usTemp << ( 11 - i );
	
		XPT2046_CLK_HIGH();
		
	}
	
	return usBuf;

}


/**
  * @brief  对 XPT2046 选择一个模拟通道后,启动ADC,并返回ADC采样结果
  * @param  ucChannel
  *   该参数为以下值之一:
  *     @arg 0x90 :通道Y+的选择控制字
  *     @arg 0xd0 :通道X+的选择控制字
  * @retval 该通道的ADC采样结果
  */
static uint16_t XPT2046_ReadAdc ( uint8_t ucChannel )
{
	XPT2046_WriteCMD ( ucChannel );

  return 	XPT2046_ReadCMD ();
	
}


/**
  * @brief  读取 XPT2046 的X通道和Y通道的AD值(12 bit,最大是4096)
  * @param  sX_Ad :存放X通道AD值的地址
  * @param  sY_Ad :存放Y通道AD值的地址
  * @retval 无
  */
static void XPT2046_ReadAdc_XY ( int16_t * sX_Ad, int16_t * sY_Ad )  
{ 
	int16_t sX_Ad_Temp, sY_Ad_Temp; 
	
	sX_Ad_Temp = XPT2046_ReadAdc ( XPT2046_CHANNEL_X );

	XPT2046_DelayUS ( 1 ); 

	sY_Ad_Temp = XPT2046_ReadAdc ( XPT2046_CHANNEL_Y ); 
	
	
	* sX_Ad = sX_Ad_Temp; 
	* sY_Ad = sY_Ad_Temp; 
	
	
}

 
/**
  * @brief  在触摸 XPT2046 屏幕时获取一组坐标的AD值,并对该坐标进行滤波
  * @param  无
  * @retval 滤波之后的坐标AD值
  */
#if   0                 //注意:校正较精准,但是相对复杂,速度较慢
static uint8_t XPT2046_ReadAdc_Smooth_XY ( strType_XPT2046_Coordinate * pScreenCoordinate )
{
	uint8_t ucCount = 0;
	
	int16_t sAD_X, sAD_Y;
	int16_t sBufferArray [ 2 ] [ 9 ] = { { 0 }, { 0 } };  //坐标X和Y进行9次采样

	int32_t lAverage  [ 3 ], lDifference [ 3 ];
	

	do
	{		   
		XPT2046_ReadAdc_XY ( & sAD_X, & sAD_Y );
		
		sBufferArray [ 0 ] [ ucCount ] = sAD_X;  
		sBufferArray [ 1 ] [ ucCount ] = sAD_Y;
		
		ucCount ++; 
			 
	} while ( ( XPT2046_EXTI_Read() == XPT2046_EXTI_ActiveLevel ) && ( ucCount < 9 ) ); 	//用户点击触摸屏时即TP_INT_IN信号为低 并且 ucCount<9*/
	 
	
	/*如果触笔弹起*/
	if ( XPT2046_EXTI_Read() != XPT2046_EXTI_ActiveLevel )
		ucXPT2046_TouchFlag = 0;			//触摸中断标志复位		

	
	/* 如果成功采样9次,进行滤波 */ 
	if ( ucCount == 9 )   								
	{  
		/* 为减少运算量,分别分3组取平均值 */
		lAverage  [ 0 ] = ( sBufferArray [ 0 ] [ 0 ] + sBufferArray [ 0 ] [ 1 ] + sBufferArray [ 0 ] [ 2 ] ) / 3;
		lAverage  [ 1 ] = ( sBufferArray [ 0 ] [ 3 ] + sBufferArray [ 0 ] [ 4 ] + sBufferArray [ 0 ] [ 5 ] ) / 3;
		lAverage  [ 2 ] = ( sBufferArray [ 0 ] [ 6 ] + sBufferArray [ 0 ] [ 7 ] + sBufferArray [ 0 ] [ 8 ] ) / 3;
		
		/* 计算3组数据的差值 */
		lDifference [ 0 ] = lAverage  [ 0 ]-lAverage  [ 1 ];
		lDifference [ 1 ] = lAverage  [ 1 ]-lAverage  [ 2 ];
		lDifference [ 2 ] = lAverage  [ 2 ]-lAverage  [ 0 ];
		
		/* 对上述差值取绝对值 */
		lDifference [ 0 ] = lDifference [ 0 ]>0?lDifference [ 0 ]: ( -lDifference [ 0 ] );
		lDifference [ 1 ] = lDifference [ 1 ]>0?lDifference [ 1 ]: ( -lDifference [ 1 ] );
		lDifference [ 2 ] = lDifference [ 2 ]>0?lDifference [ 2 ]: ( -lDifference [ 2 ] );
		
		
		/* 判断绝对差值是否都超过差值门限,如果这3个绝对差值都超过门限值,则判定这次采样点为野点,抛弃采样点,差值门限取为2 */
		if (  lDifference [ 0 ] > XPT2046_THRESHOLD_CalDiff  &&  lDifference [ 1 ] > XPT2046_THRESHOLD_CalDiff  &&  lDifference [ 2 ] > XPT2046_THRESHOLD_CalDiff  ) 
			return 0;
		
		
		/* 计算它们的平均值,同时赋值给strScreenCoordinate */ 
		if ( lDifference [ 0 ] < lDifference [ 1 ] )
		{
			if ( lDifference [ 2 ] < lDifference [ 0 ] ) 
				pScreenCoordinate ->x = ( lAverage  [ 0 ] + lAverage  [ 2 ] ) / 2;
			else 
				pScreenCoordinate ->x = ( lAverage  [ 0 ] + lAverage  [ 1 ] ) / 2;	
		}
		
		else if ( lDifference [ 2 ] < lDifference [ 1 ] ) 
			pScreenCoordinate -> x = ( lAverage  [ 0 ] + lAverage  [ 2 ] ) / 2;
		
		else 
			pScreenCoordinate ->x = ( lAverage  [ 1 ] + lAverage  [ 2 ] ) / 2;
		
		
		/* 同上,计算Y的平均值 */
		lAverage  [ 0 ] = ( sBufferArray [ 1 ] [ 0 ] + sBufferArray [ 1 ] [ 1 ] + sBufferArray [ 1 ] [ 2 ] ) / 3;
		lAverage  [ 1 ] = ( sBufferArray [ 1 ] [ 3 ] + sBufferArray [ 1 ] [ 4 ] + sBufferArray [ 1 ] [ 5 ] ) / 3;
		lAverage  [ 2 ] = ( sBufferArray [ 1 ] [ 6 ] + sBufferArray [ 1 ] [ 7 ] + sBufferArray [ 1 ] [ 8 ] ) / 3;
		
		lDifference [ 0 ] = lAverage  [ 0 ] - lAverage  [ 1 ];
		lDifference [ 1 ] = lAverage  [ 1 ] - lAverage  [ 2 ];
		lDifference [ 2 ] = lAverage  [ 2 ] - lAverage  [ 0 ];
		
		/* 取绝对值 */
		lDifference [ 0 ] = lDifference [ 0 ] > 0 ? lDifference [ 0 ] : ( - lDifference [ 0 ] );
		lDifference [ 1 ] = lDifference [ 1 ] > 0 ? lDifference [ 1 ] : ( - lDifference [ 1 ] );
		lDifference [ 2 ] = lDifference [ 2 ] > 0 ? lDifference [ 2 ] : ( - lDifference [ 2 ] );
		
		
		if ( lDifference [ 0 ] > XPT2046_THRESHOLD_CalDiff && lDifference [ 1 ] > XPT2046_THRESHOLD_CalDiff && lDifference [ 2 ] > XPT2046_THRESHOLD_CalDiff ) 
			return 0;
		
		if ( lDifference [ 0 ] < lDifference [ 1 ] )
		{
			if ( lDifference [ 2 ] < lDifference [ 0 ] ) 
				pScreenCoordinate ->y =  ( lAverage  [ 0 ] + lAverage  [ 2 ] ) / 2;
			else 
				pScreenCoordinate ->y =  ( lAverage  [ 0 ] + lAverage  [ 1 ] ) / 2;	
		}
		else if ( lDifference [ 2 ] < lDifference [ 1 ] ) 
			pScreenCoordinate ->y =  ( lAverage  [ 0 ] + lAverage  [ 2 ] ) / 2;
		else
			pScreenCoordinate ->y =  ( lAverage  [ 1 ] + lAverage  [ 2 ] ) / 2;
				
		return 1;		
	}
	
	else if ( ucCount > 1 )
	{
		pScreenCoordinate ->x = sBufferArray [ 0 ] [ 0 ];
		pScreenCoordinate ->y = sBufferArray [ 1 ] [ 0 ];	
		return 0;		
	}  	
	return 0; 	
}


#else     //注意:画板应用实例专用,不是很精准,但是简单,速度比较快   
static uint8_t XPT2046_ReadAdc_Smooth_XY ( strType_XPT2046_Coordinate * pScreenCoordinate )
{
	uint8_t ucCount = 0, i;
	
	int16_t sAD_X, sAD_Y;
	int16_t sBufferArray [ 2 ] [ 10 ] = { { 0 },{ 0 } };  //坐标X和Y进行多次采样
	
	//存储采样中的最小值、最大值
	int32_t lX_Min, lX_Max, lY_Min, lY_Max;


	/* 循环采样10次 */ 
	do					       				
	{		  
		XPT2046_ReadAdc_XY ( & sAD_X, & sAD_Y );  
		
		sBufferArray [ 0 ] [ ucCount ] = sAD_X;  
		sBufferArray [ 1 ] [ ucCount ] = sAD_Y;
		
		ucCount ++;  
		
	}	while ( ( XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel ) && ( ucCount < 10 ) );//用户点击触摸屏时即TP_INT_IN信号为低 并且 ucCount<10
	
	
	/*如果触笔弹起*/
	if ( XPT2046_PENIRQ_Read() != XPT2046_PENIRQ_ActiveLevel )
		ucXPT2046_TouchFlag = 0;			//中断标志复位

	
	/*如果成功采样10个样本*/
	if ( ucCount ==10 )		 					
	{
		lX_Max = lX_Min = sBufferArray [ 0 ] [ 0 ];
		lY_Max = lY_Min = sBufferArray [ 1 ] [ 0 ];       
		
		for ( i = 1; i < 10; i ++ )
		{
			if ( sBufferArray[ 0 ] [ i ] < lX_Min )
				lX_Min = sBufferArray [ 0 ] [ i ];
			
			else if ( sBufferArray [ 0 ] [ i ] > lX_Max )
				lX_Max = sBufferArray [ 0 ] [ i ];

		}
		
		for ( i = 1; i < 10; i ++ )
		{
			if ( sBufferArray [ 1 ] [ i ] < lY_Min )
				lY_Min = sBufferArray [ 1 ] [ i ];
			
			else if ( sBufferArray [ 1 ] [ i ] > lY_Max )
				lY_Max = sBufferArray [ 1 ] [ i ];

		}
		
		
		/*去除最小值和最大值之后求平均值*/
		pScreenCoordinate ->x =  ( sBufferArray [ 0 ] [ 0 ] + sBufferArray [ 0 ] [ 1 ] + sBufferArray [ 0 ] [ 2 ] + sBufferArray [ 0 ] [ 3 ] + sBufferArray [ 0 ] [ 4 ] + 
		                           sBufferArray [ 0 ] [ 5 ] + sBufferArray [ 0 ] [ 6 ] + sBufferArray [ 0 ] [ 7 ] + sBufferArray [ 0 ] [ 8 ] + sBufferArray [ 0 ] [ 9 ] - lX_Min-lX_Max ) >> 3;
		
		pScreenCoordinate ->y =  ( sBufferArray [ 1 ] [ 0 ] + sBufferArray [ 1 ] [ 1 ] + sBufferArray [ 1 ] [ 2 ] + sBufferArray [ 1 ] [ 3 ] + sBufferArray [ 1 ] [ 4 ] + 
		                           sBufferArray [ 1 ] [ 5 ] + sBufferArray [ 1 ] [ 6 ] + sBufferArray [ 1 ] [ 7 ] + sBufferArray [ 1 ] [ 8 ] + sBufferArray [ 1 ] [ 9 ] - lY_Min-lY_Max ) >> 3; 
		
		
		return 1;		
		
	}   	
	return 0;    	
}


#endif


/**
  * @brief  计算 XPT2046 触摸坐标校正系数(注意:只有在LCD和触摸屏间的误差角度非常小时,才能运用下面公式)
  * @param  pDisplayCoordinate :屏幕人为显示的已知坐标
  * @param  pstrScreenSample :对已知坐标点触摸时 XPT2046 产生的坐标
  * @param  pCalibrationFactor :根据人为设定坐标和采样回来的坐标计算出来的屏幕触摸校正系数
  * @retval 计算状态
	*   该返回值为以下值之一:
  *     @arg 1 :计算成功
  *     @arg 0 :计算失败
  */
static uint8_t XPT2046_Calculate_CalibrationFactor ( strType_XPT2046_Coordinate * pDisplayCoordinate, strType_XPT2046_Coordinate * pScreenSample, strType_XPT2046_Calibration * pCalibrationFactor )
{
	uint8_t ucRet = 1;

	
	/* K= ( X0-X2 )  ( Y1-Y2 )- ( X1-X2 )  ( Y0-Y2 ) */
	pCalibrationFactor -> Divider =  ( ( pScreenSample [ 0 ] .x - pScreenSample [ 2 ] .x ) *  ( pScreenSample [ 1 ] .y - pScreenSample [ 2 ] .y ) ) - 
									                 ( ( pScreenSample [ 1 ] .x - pScreenSample [ 2 ] .x ) *  ( pScreenSample [ 0 ] .y - pScreenSample [ 2 ] .y ) ) ;
	
	
	if (  pCalibrationFactor -> Divider == 0  )
		ucRet = 0;

	else
	{
		/* A= (  ( XD0-XD2 )  ( Y1-Y2 )- ( XD1-XD2 )  ( Y0-Y2 ) )/K	*/
		pCalibrationFactor -> An =  ( ( pDisplayCoordinate [ 0 ] .x - pDisplayCoordinate [ 2 ] .x ) *  ( pScreenSample [ 1 ] .y - pScreenSample [ 2 ] .y ) ) - 
								                ( ( pDisplayCoordinate [ 1 ] .x - pDisplayCoordinate [ 2 ] .x ) *  ( pScreenSample [ 0 ] .y - pScreenSample [ 2 ] .y ) );
		
		/* B= (  ( X0-X2 )  ( XD1-XD2 )- ( XD0-XD2 )  ( X1-X2 ) )/K	*/
		pCalibrationFactor -> Bn =  ( ( pScreenSample [ 0 ] .x - pScreenSample [ 2 ] .x ) *  ( pDisplayCoordinate [ 1 ] .x - pDisplayCoordinate [ 2 ] .x ) ) - 
								                ( ( pDisplayCoordinate [ 0 ] .x - pDisplayCoordinate [ 2 ] .x ) *  ( pScreenSample [ 1 ] .x - pScreenSample [ 2 ] .x ) );
		
		/* C= ( Y0 ( X2XD1-X1XD2 )+Y1 ( X0XD2-X2XD0 )+Y2 ( X1XD0-X0XD1 ) )/K */
		pCalibrationFactor -> Cn =  ( pScreenSample [ 2 ] .x * pDisplayCoordinate [ 1 ] .x - pScreenSample [ 1 ] .x * pDisplayCoordinate [ 2 ] .x ) * pScreenSample [ 0 ] .y +
								                ( pScreenSample [ 0 ] .x * pDisplayCoordinate [ 2 ] .x - pScreenSample [ 2 ] .x * pDisplayCoordinate [ 0 ] .x ) * pScreenSample [ 1 ] .y +
								                ( pScreenSample [ 1 ] .x * pDisplayCoordinate [ 0 ] .x - pScreenSample [ 0 ] .x * pDisplayCoordinate [ 1 ] .x ) * pScreenSample [ 2 ] .y ;
		
		/* D= (  ( YD0-YD2 )  ( Y1-Y2 )- ( YD1-YD2 )  ( Y0-Y2 ) )/K	*/
		pCalibrationFactor -> Dn =  ( ( pDisplayCoordinate [ 0 ] .y - pDisplayCoordinate [ 2 ] .y ) *  ( pScreenSample [ 1 ] .y - pScreenSample [ 2 ] .y ) ) - 
								                ( ( pDisplayCoordinate [ 1 ] .y - pDisplayCoordinate [ 2 ] .y ) *  ( pScreenSample [ 0 ] .y - pScreenSample [ 2 ] .y ) ) ;
		
		/* E= (  ( X0-X2 )  ( YD1-YD2 )- ( YD0-YD2 )  ( X1-X2 ) )/K	*/
		pCalibrationFactor -> En =  ( ( pScreenSample [ 0 ] .x - pScreenSample [ 2 ] .x ) *  ( pDisplayCoordinate [ 1 ] .y - pDisplayCoordinate [ 2 ] .y ) ) - 
								                ( ( pDisplayCoordinate [ 0 ] .y - pDisplayCoordinate [ 2 ] .y ) *  ( pScreenSample [ 1 ] .x - pScreenSample [ 2 ] .x ) ) ;
		
		
		/* F= ( Y0 ( X2YD1-X1YD2 )+Y1 ( X0YD2-X2YD0 )+Y2 ( X1YD0-X0YD1 ) )/K */
		pCalibrationFactor -> Fn =  ( pScreenSample [ 2 ] .x * pDisplayCoordinate [ 1 ] .y - pScreenSample [ 1 ] .x * pDisplayCoordinate [ 2 ] .y ) * pScreenSample [ 0 ] .y +
								                ( pScreenSample [ 0 ] .x * pDisplayCoordinate [ 2 ] .y - pScreenSample [ 2 ] .x * pDisplayCoordinate [ 0 ] .y ) * pScreenSample [ 1 ] .y +
								                ( pScreenSample [ 1 ] .x * pDisplayCoordinate [ 0 ] .y - pScreenSample [ 0 ] .x * pDisplayCoordinate [ 1 ] .y ) * pScreenSample [ 2 ] .y;
			
	}
	
	
	return ucRet;
	
	
}


/**
  * @brief  在 ILI9341 上显示校正触摸时需要的十字
  * @param  usX :在特定扫描方向下十字交叉点的X坐标
  * @param  usY :在特定扫描方向下十字交叉点的Y坐标
  * @retval 无
  */
static void ILI9341_DrawCross ( uint16_t usX, uint16_t usY )
{
	ILI9341_DrawLine(usX-10,usY,usX+10,usY);
	ILI9341_DrawLine(usX, usY - 10, usX, usY+10);	
}


/**
  * @brief  XPT2046 触摸屏校准
	* @param	LCD_Mode:指定要校正哪种液晶扫描模式的参数
  * @note  本函数调用后会把液晶模式设置为LCD_Mode
  * @retval 校准结果
	*   该返回值为以下值之一:
  *     @arg 1 :校准成功
  *     @arg 0 :校准失败
  */
uint8_t XPT2046_Touch_Calibrate ( uint8_t LCD_Mode ) 
{

		uint8_t i;
		
		char cStr [ 100 ];
		
		uint16_t usTest_x = 0, usTest_y = 0, usGap_x = 0, usGap_y = 0;
		
	  char * pStr = 0;
	
    strType_XPT2046_Coordinate strCrossCoordinate[4], strScreenSample[4];
	  
	  strType_XPT2046_Calibration CalibrationFactor;
    		
		LCD_SetFont(&Font8x16);
		LCD_SetColors(BLUE,BLACK);
	
		//设置扫描方向,横屏
		ILI9341_GramScan ( LCD_Mode );
		
		
		/* 设定“十”字交叉点的坐标 */ 
		strCrossCoordinate [0].x = LCD_X_LENGTH >> 2;
		strCrossCoordinate[0].y = LCD_Y_LENGTH >> 2;
		
		strCrossCoordinate[1].x = strCrossCoordinate[0].x;
		strCrossCoordinate[1].y = ( LCD_Y_LENGTH * 3 ) >> 2;
		
		strCrossCoordinate[2].x = ( LCD_X_LENGTH * 3 ) >> 2;
		strCrossCoordinate[2].y = strCrossCoordinate[1].y;
		
		strCrossCoordinate[3].x = strCrossCoordinate[2].x;
		strCrossCoordinate[3].y = strCrossCoordinate[0].y;	
		
		
		for ( i = 0; i < 4; i ++ )
		{ 
			ILI9341_Clear ( 0, 0, LCD_X_LENGTH, LCD_Y_LENGTH );       
			
			pStr = "Touch Calibrate ......";		
			//插入空格,居中显示
			sprintf(cStr,"%*c%s",((LCD_X_LENGTH/(((sFONT *)LCD_GetFont())->Width))-strlen(pStr))/2,' ',pStr)	;	
      ILI9341_DispStringLine_EN (LCD_Y_LENGTH >> 1, cStr );			
		
			//插入空格,居中显示
			sprintf ( cStr, "%*c%d",((LCD_X_LENGTH/(((sFONT *)LCD_GetFont())->Width)) -1)/2,' ',i + 1 );
			ILI9341_DispStringLine_EN (( LCD_Y_LENGTH >> 1 ) - (((sFONT *)LCD_GetFont())->Height), cStr ); 
		
			XPT2046_DelayUS ( 300000 );		                     //适当的延时很有必要
			
			ILI9341_DrawCross ( strCrossCoordinate[i] .x, strCrossCoordinate[i].y );  //显示校正用的“十”字

			while ( ! XPT2046_ReadAdc_Smooth_XY ( & strScreenSample [i] ) );               //读取XPT2046数据到变量pCoordinate,当ptr为空时表示没有触点被按下

		}
		
		
		XPT2046_Calculate_CalibrationFactor ( strCrossCoordinate, strScreenSample, & CalibrationFactor ) ;  	 //用原始参数计算出 原始参数与坐标的转换系数
		
		if ( CalibrationFactor.Divider == 0 ) goto Failure;
		
			
		usTest_x = ( ( CalibrationFactor.An * strScreenSample[3].x ) + ( CalibrationFactor.Bn * strScreenSample[3].y ) + CalibrationFactor.Cn ) / CalibrationFactor.Divider;		//取一个点计算X值	 
		usTest_y = ( ( CalibrationFactor.Dn * strScreenSample[3].x ) + ( CalibrationFactor.En * strScreenSample[3].y ) + CalibrationFactor.Fn ) / CalibrationFactor.Divider;    //取一个点计算Y值
		
		usGap_x = ( usTest_x > strCrossCoordinate[3].x ) ? ( usTest_x - strCrossCoordinate[3].x ) : ( strCrossCoordinate[3].x - usTest_x );   //实际X坐标与计算坐标的绝对差
		usGap_y = ( usTest_y > strCrossCoordinate[3].y ) ? ( usTest_y - strCrossCoordinate[3].y ) : ( strCrossCoordinate[3].y - usTest_y );   //实际Y坐标与计算坐标的绝对差
		
    if ( ( usGap_x > 15 ) || ( usGap_y > 15 ) ) goto Failure;       //可以通过修改这两个值的大小来调整精度    
		

    /* 校准系数为全局变量 */ 
		strXPT2046_TouchPara[LCD_Mode].dX_X = ( CalibrationFactor.An * 1.0 ) / CalibrationFactor.Divider;
		strXPT2046_TouchPara[LCD_Mode].dX_Y = ( CalibrationFactor.Bn * 1.0 ) / CalibrationFactor.Divider;
		strXPT2046_TouchPara[LCD_Mode].dX   = ( CalibrationFactor.Cn * 1.0 ) / CalibrationFactor.Divider;
		
		strXPT2046_TouchPara[LCD_Mode].dY_X = ( CalibrationFactor.Dn * 1.0 ) / CalibrationFactor.Divider;
		strXPT2046_TouchPara[LCD_Mode].dY_Y = ( CalibrationFactor.En * 1.0 ) / CalibrationFactor.Divider;
		strXPT2046_TouchPara[LCD_Mode].dY   = ( CalibrationFactor.Fn * 1.0 ) / CalibrationFactor.Divider;

		#if 0  //输出调试信息,注意要先初始化串口
			{
						float * ulHeadAddres ;
				/* 打印校校准系数 */ 
				XPT2046_INFO ( "显示模式【%d】校准系数如下:", LCD_Mode);
				
				ulHeadAddres = ( float* ) ( & strXPT2046_TouchPara[LCD_Mode] );
				
				for ( i = 0; i < 6; i ++ )
				{					
					printf ( "%12f,", *ulHeadAddres++  );			
				}	
				printf("\r\n");
			}
		#endif
			
	ILI9341_Clear ( 0, 0, LCD_X_LENGTH, LCD_Y_LENGTH );
	
	LCD_SetTextColor(GREEN);
	
	pStr = "Calibrate Succed";
	//插入空格,居中显示	
	sprintf(cStr,"%*c%s",((LCD_X_LENGTH/(((sFONT *)LCD_GetFont())->Width))-strlen(pStr))/2,' ',pStr)	;	
  ILI9341_DispStringLine_EN (LCD_Y_LENGTH >> 1, cStr );	

  XPT2046_DelayUS ( 1000000 );

	return 1;    
	

Failure:
	
	ILI9341_Clear ( 0, 0, LCD_X_LENGTH, LCD_Y_LENGTH ); 
	
	LCD_SetTextColor(RED);
	
	pStr = "Calibrate fail";	
	//插入空格,居中显示	
	sprintf(cStr,"%*c%s",((LCD_X_LENGTH/(((sFONT *)LCD_GetFont())->Width))-strlen(pStr))/2,' ',pStr)	;	
  ILI9341_DispStringLine_EN (LCD_Y_LENGTH >> 1, cStr );	

	pStr = "try again";
	//插入空格,居中显示		
	sprintf(cStr,"%*c%s",((LCD_X_LENGTH/(((sFONT *)LCD_GetFont())->Width))-strlen(pStr))/2,' ',pStr)	;	
	ILI9341_DispStringLine_EN ( ( LCD_Y_LENGTH >> 1 ) + (((sFONT *)LCD_GetFont())->Height), cStr );				

	XPT2046_DelayUS ( 1000000 );		
	
	return 0; 
		
		
}



/**
  * @brief  从FLASH中获取 或 重新校正触摸参数(校正后会写入到SPI FLASH中)
  * @note		若FLASH中从未写入过触摸参数,
	*						会触发校正程序校正LCD_Mode指定模式的触摸参数,此时其它模式写入默认值
  *
	*					若FLASH中已有触摸参数,且不强制重新校正
	*						会直接使用FLASH里的触摸参数值
  *
	*					每次校正时只会更新指定的LCD_Mode模式的触摸参数,其它模式的不变
  * @note  本函数调用后会把液晶模式设置为LCD_Mode
  *
	* @param  LCD_Mode:要校正触摸参数的液晶模式
	* @param  forceCal:是否强制重新校正参数,可以为以下值:
	*		@arg 1:强制重新校正
	*		@arg 0:只有当FLASH中不存在触摸参数标志时才重新校正
  * @retval 无
  */	
void Calibrate_or_Get_TouchParaWithFlash(uint8_t LCD_Mode,uint8_t forceCal)
{
	uint8_t para_flag=0;
	
	//初始化FLASH
//	SPI_FLASH_Init();
	
	//读取触摸参数标志
//	SPI_FLASH_BufferRead(¶_flag,FLASH_TOUCH_PARA_FLAG_ADDR,1);

	//若不存在标志或florceCal=1时,重新校正参数
	if(para_flag != FLASH_TOUCH_PARA_FLAG_VALUE | forceCal ==1)
	{ 		
		//若标志存在,说明原本FLASH内有触摸参数,
		//先读回所有LCD模式的参数值,以便稍后强制更新时只更新指定LCD模式的参数,其它模式的不变
//		if(  para_flag == FLASH_TOUCH_PARA_FLAG_VALUE && forceCal == 1)
//		{
//			SPI_FLASH_BufferRead((uint8_t *)&strXPT2046_TouchPara,FLASH_TOUCH_PARA_ADDR,4*6*8);	
//		}
		
		//等待触摸屏校正完毕,更新指定LCD模式的触摸参数值
		while( ! XPT2046_Touch_Calibrate (LCD_Mode) );     

		//擦除扇区
//		SPI_FLASH_SectorErase(0);
		
		//设置触摸参数标志
		para_flag = FLASH_TOUCH_PARA_FLAG_VALUE;
		//写入触摸参数标志
//		SPI_FLASH_BufferWrite(¶_flag,FLASH_TOUCH_PARA_FLAG_ADDR,1);
		//写入最新的触摸参数
//		SPI_FLASH_BufferWrite((uint8_t *)&strXPT2046_TouchPara,FLASH_TOUCH_PARA_ADDR,4*6*8);
 
	}
	else	//若标志存在且不强制校正,则直接从FLASH中读取
	{
//		SPI_FLASH_BufferRead((uint8_t *)&strXPT2046_TouchPara,FLASH_TOUCH_PARA_ADDR,4*6*8);	 

			#if 0	//输出调试信息,注意要初始化串口
				{
					
					uint8_t para_flag=0,i;
					float *ulHeadAddres  ;
					
					/* 打印校校准系数 */ 
					XPT2046_INFO ( "从FLASH里读取得的校准系数如下:" );
					
					ulHeadAddres = ( float* ) ( & strXPT2046_TouchPara );

					for ( i = 0; i < 6*8; i ++ )
					{				
						if(i%6==0)
							printf("\r\n");			
									
						printf ( "%12f,", *ulHeadAddres );
						ulHeadAddres++;				
					}
					printf("\r\n");
				}
			#endif
	}
	

}
   
/**
  * @brief  获取 XPT2046 触摸点(校准后)的坐标
  * @param  pDisplayCoordinate :该指针存放获取到的触摸点坐标
  * @param  pTouchPara:坐标校准系数
  * @retval 获取情况
	*   该返回值为以下值之一:
  *     @arg 1 :获取成功
  *     @arg 0 :获取失败
  */
uint8_t XPT2046_Get_TouchedPoint ( strType_XPT2046_Coordinate * pDisplayCoordinate, strType_XPT2046_TouchPara * pTouchPara )
{
	uint8_t ucRet = 1;           //若正常,则返回0
	
	strType_XPT2046_Coordinate strScreenCoordinate; 
	

  if ( XPT2046_ReadAdc_Smooth_XY ( & strScreenCoordinate ) )
  {    
		pDisplayCoordinate ->x = ( ( pTouchPara[LCD_SCAN_MODE].dX_X * strScreenCoordinate.x ) + ( pTouchPara[LCD_SCAN_MODE].dX_Y * strScreenCoordinate.y ) + pTouchPara[LCD_SCAN_MODE].dX );        
		pDisplayCoordinate ->y = ( ( pTouchPara[LCD_SCAN_MODE].dY_X * strScreenCoordinate.x ) + ( pTouchPara[LCD_SCAN_MODE].dY_Y * strScreenCoordinate.y ) + pTouchPara[LCD_SCAN_MODE].dY );

  }
	 
	else ucRet = 0;            //如果获取的触点信息有误,则返回0
		
  return ucRet;
} 





/**
  * @brief  触摸屏检测状态机
  * @retval 触摸状态
	*   该返回值为以下值之一:
  *     @arg TOUCH_PRESSED :触摸按下
  *     @arg TOUCH_NOT_PRESSED :无触摸
  */
uint8_t XPT2046_TouchDetect(void)
{ 
	static enumTouchState touch_state = XPT2046_STATE_RELEASE;
	static uint32_t i;
	uint8_t detectResult = TOUCH_NOT_PRESSED;
	
	switch(touch_state)
	{
		case XPT2046_STATE_RELEASE:	
			if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel) //第一次出现触摸信号
			{
				touch_state = XPT2046_STATE_WAITING;
				detectResult =TOUCH_NOT_PRESSED;
				}
			else	//无触摸
			{
				touch_state = XPT2046_STATE_RELEASE;
				detectResult =TOUCH_NOT_PRESSED;
			}
			break;
				
		case XPT2046_STATE_WAITING:
				if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel)
				{
					 i++;
					//等待时间大于阈值则认为触摸被按下
					//消抖时间 = DURIATION_TIME * 本函数被调用的时间间隔
					//如在定时器中调用,每10ms调用一次,则消抖时间为:DURIATION_TIME*10ms
					if(i > DURIATION_TIME)		
					{
						i=0;
						touch_state = XPT2046_STATE_PRESSED;
						detectResult = TOUCH_PRESSED;
					}
					else												//等待时间累加
					{
						touch_state = XPT2046_STATE_WAITING;
						detectResult =	 TOUCH_NOT_PRESSED;					
					}
				}
				else	//等待时间值未达到阈值就为无效电平,当成抖动处理					
				{
				    i = 0;
            touch_state = XPT2046_STATE_RELEASE; 
						detectResult = TOUCH_NOT_PRESSED;
				}
		
			break;
		
		case XPT2046_STATE_PRESSED:	
				if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel)		//触摸持续按下
				{
					touch_state = XPT2046_STATE_PRESSED;
					detectResult = TOUCH_PRESSED;
				}
				else	//触摸释放
				{
					touch_state = XPT2046_STATE_RELEASE;
					detectResult = TOUCH_NOT_PRESSED;
				}
			break;			
		
		default:
				touch_state = XPT2046_STATE_RELEASE;
				detectResult = TOUCH_NOT_PRESSED;
				break;
	
	}		
	
	return detectResult;
}


/**
  * @brief   触摸屏被按下的时候会调用本函数
  * @param  touch包含触摸坐标的结构体
  * @note  请在本函数中编写自己的触摸按下处理应用
  * @retval 无
  */
void XPT2046_TouchDown(strType_XPT2046_Coordinate * touch)
{
	//若为负值表示之前已处理过
	if(touch->pre_x == -1 && touch->pre_y == -1)
		return;
	
	/***在此处编写自己的触摸按下处理应用***/
  printf("and\r\n");
	/*处理触摸画板的选择按钮*/
//  Touch_Button_Down(touch->x,touch->y);
//  
//  /*处理描绘轨迹*/
//  Draw_Trail(touch->pre_x,touch->pre_y,touch->x,touch->y,&brush);
	
	/***在上面编写自己的触摸按下处理应用***/
	
	
}

/**
  * @brief   触摸屏释放的时候会调用本函数
  * @param  touch包含触摸坐标的结构体
  * @note  请在本函数中编写自己的触摸释放处理应用
  * @retval 无
  */
void XPT2046_TouchUp(strType_XPT2046_Coordinate * touch) 
{
	//若为负值表示之前已处理过
	if(touch->pre_x == -1 && touch->pre_y == -1)
		return;
		
	/***在此处编写自己的触摸释放处理应用***/
  
	/*处理触摸画板的选择按钮*/
//  Touch_Button_Up(touch->pre_x,touch->pre_y);	
	
	/***在上面编写自己的触摸释放处理应用***/
}

/**
	* @brief   检测到触摸中断时调用的处理函数,通过它调用tp_down 和tp_up汇报触摸点
	*	@note 	 本函数需要在while循环里被调用,也可使用定时器定时调用
	*			例如,可以每隔5ms调用一次,消抖阈值宏DURIATION_TIME可设置为2,这样每秒最多可以检测100个点。
	*						可在XPT2046_TouchDown及XPT2046_TouchUp函数中编写自己的触摸应用
	* @param   none
	* @retval  none
	*/
void XPT2046_TouchEvenHandler(void )
{
	  static strType_XPT2046_Coordinate cinfo={-1,-1,-1,-1};
	  
		if(XPT2046_TouchDetect() == TOUCH_PRESSED)
		{
//			LED_GREEN;
			
			//获取触摸坐标
			XPT2046_Get_TouchedPoint(&cinfo,strXPT2046_TouchPara);
			
			//输出调试信息到串口
//			XPT2046_DEBUG("while: x=%d,y=%d\r\n",cinfo.x,cinfo.y);
			printf("x=%d,y=%d\r\n",cinfo.x,cinfo.y);
			//调用触摸被按下时的处理函数,可在该函数编写自己的触摸按下处理过程
			XPT2046_TouchDown(&cinfo);
			
			/*更新触摸信息到pre xy*/
			cinfo.pre_x = cinfo.x; cinfo.pre_y = cinfo.y;  

		}
		else
		{
//			LED_BLUE;
			
			//调用触摸被释放时的处理函数,可在该函数编写自己的触摸释放处理过程
			XPT2046_TouchUp(&cinfo); 
			
			/*触笔释放,把 xy 重置为负*/
			cinfo.x = -1;
			cinfo.y = -1; 
			cinfo.pre_x = -1;
			cinfo.pre_y = -1;
		}

}


/***************************end of file*****************************/

以上基础工程建立完成。

4.3.2 移植LVGL

4.3.2.1 移植文件

在工程创建如下结构的文件夹,用于保存LVGL源码以及自己编写的APP代码文件
LVGL移植教程_第14张图片
采用这样的文件夹结构,主要是为了兼容 LVGL源码中包含头文件的格式。对于初学者,这里建议按照此结构新建文件夹,否则有可能会遇到很多关于头文件的报错。

将精简过的LVGL源码复制到Middlewares/LVGL/GUI/lvgl 路径下,如下图所示:
把 lv_conf_template.h 文件改名为 lv_conf.h。LVGL移植教程_第15张图片
在工程中按照如下添加文件:
(1) 往 Middlewares/lvgl/src/core 分组中添加 core 文件夹下的全部.c 文件,如下图所示:
LVGL移植教程_第16张图片
(2) 往 Middlewares/lvgl/src/draw 组中添加 draw 文件夹下除 nxp_pxp、nxp_vglite、sdl 和stm32_dma2d 文件夹之外的全部.c 文件,如下图所示:
LVGL移植教程_第17张图片
(3)往 Middlewares/lvgl/src/extra 组中添加 extra 文件夹下除了 lib 文件夹之外的全部.c 文件,如下图所示:
LVGL移植教程_第18张图片
(4)往 Middlewares/lvgl/src/font 组中添加 font 文件夹下的全部.c 文件,如下图所示:
LVGL移植教程_第19张图片
(5) 往 Middlewares/lvgl/src/gpu 组中添加 draw/stm32_dma2d 和 draw/sdl文件夹下的全部.c文件,如下图所示:
LVGL移植教程_第20张图片
(6) 往 Middlewares/lvgl/src/hal 组中添加 hal 文件夹下的全部.c 文件,如下图所示:
LVGL移植教程_第21张图片
(7) 往 Middlewares/lvgl/src/misc 组中添加 misc 文件夹下的全部.c 文件,如下图所示:
LVGL移植教程_第22张图片
(8)往 Middlewares/lvgl/src/widgets 组中添加 widgets 文件夹下的全部.c 文件,如下图所示:
LVGL移植教程_第23张图片
(9) Middlewares/lvgl/examples/porting 组添加 Middlewares/LVGL/GUI/lvgl/examples/porting目录下的 lv_port_disp_template.c 和 lv_port_indev_template.c 文件,如下图所示:
LVGL移植教程_第24张图片
添加对应路径:
移植 LVGL 只需要添加关键的头文件路径即可,因为 lvgl.h 文件已经为我们省去了很多包含头文件的操作,添加的头文件路径如下图所示:
LVGL移植教程_第25张图片

4.3.2.2 修改lv_conf_h文件

1、修改条件宏编译
在这里插入图片描述
2、修改动态内存大小(不修改可能有些单片机内存不足)
LVGL移植教程_第26张图片

4.3.2.3 链接显示屏和LVGL

修改lv_port_disp_template.h文件的条件宏编译为1;
修改lv_port_disp_template.c文件如下:

/*
 * @Author: 烟雨江南、
 * @Date: 2023-06-28 09:40:40
 * @LastEditors: huazaili [email protected]
 * @LastEditTime: 2023-06-28 11:13:29
 * @FilePath: \MDK-ARMg:\STM32 procedure\LVGL_Dome\BSP\Middlewares\LVGL\GUI\lvgl\examples\porting\lv_port_disp_template.c
 * @Description: 
 * 
 * Copyright (c) 2023 by 烟雨江南、, All Rights Reserved. 
 */
/**
 * @file lv_port_disp_templ.c
 *
 */

 /*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_disp_template.h"
#include "../../lvgl.h"

#include "bsp_ili9341_lcd.h"
/*********************
 *      DEFINES
 *********************/
#define USE_SRAM 0
/**********************
 *      TYPEDEFS
 **********************/
#define  MY_DISP_HOR_RES  (ILI9341_MORE_PIXEL)  //屏幕宽度
#define  MY_DISP_VER_RES  (ILI9341_LESS_PIXEL)  //屏幕高度
/**********************
 *  STATIC PROTOTYPES
 **********************/
static void disp_init(void);       //初始化显示设备

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);  // 显示设备刷新函数

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();
    /* Example for 1) */
    static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[MY_DISP_HOR_RES * 10];                          /*A buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/
    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/
    static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/
    /*Set up the functions to access to your display*/
    /*Set the resolution of the display*/
    disp_drv.hor_res = 320;
    disp_drv.ver_res = 240;
    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;
    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_1;
    lv_disp_drv_register(&disp_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/*Initialize your display and the required peripherals.*/

/**
 * @brief 初始化显示设备 
 * @note: 
 * @param 无
 * @return 
 */
static void disp_init(void)
{   
    ILI9341_GramScan(3);
}

/**
* @brief 将内部缓冲区的内容刷新到显示屏上的特定区域
* @note 可以使用 DMA 或者任何硬件在后台加速执行这个操作
* 但是,需要在刷新完成后调用函数 'lv_disp_flush_ready()'
* @param disp_drv : 显示设备
* @arg area : 要刷新的区域,包含了填充矩形的对角坐标
* @arg color_p : 颜色数组
*
* @retval 无
*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /* 显示接口驱动,有的屏幕提供的函数时不能直接使用的 */
//  	int32_t x;
//    int32_t y;
//    for(y = area->y1; y <= area->y2; y++) {
//        for(x = area->x1; x <= area->x2; x++) {
//            /*Put a pixel to the display. For example:*/
//            /*put_px(x, y, *color_p)*/		 
//           ILI9341_lvglSetDrawPixel(x,y,color_p->full);
//           color_p++;
//        }
//    }

    /*在换屏幕时,需注意接口驱动的编写*/
	ILI9341_lvglClear (area->x1,area->y1, area->x2, area->y2,(uint16_t *)color_p);
    /*重要!!!* 通知图形库,已经刷新完毕了*/
    lv_disp_flush_ready(disp_drv);
}
#else /*Enable this file at the top*/

/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif

4.3.2.3链接触摸与LVGL

修改lv_port_indev_template.h文件的条件宏编译为1;
修改lv_port_indev_template.c文件如下:

/**
 * @file lv_port_indev_templ.c
 *
 */

 /*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_indev_template.h"
#include "../../lvgl.h"

#include "bsp_xpt2046_lcd.h"
#include "bsp_ili9341_lcd.h"

#include "usart.h"
#include "main.h"
#include "stdio.h"
/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/
strType_XPT2046_Coordinate cinfo1={-1,-1,-1,-1};
/**********************
 *  STATIC PROTOTYPES
 **********************/
/*触摸屏*/
static void touchpad_init(void);
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static bool touchpad_is_pressed(void);
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y);

/**********************
 *  STATIC VARIABLES
 **********************/
lv_indev_t * indev_touchpad;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_indev_init(void)
{
    /**
 * 
 * 在这里你可以找到 LittlevGL 支持的出入设备的实现示例:
 * - 触摸屏
 * - 鼠标 (支持光标)
 * - 键盘 (仅支持按键的 GUI 用法)
 * - 编码器 (支持的 GUI 用法仅包括: 左, 右, 按下)
 * - 按钮 (按下屏幕上指定点的外部按钮)
 *
 * 函数 `..._read()` 只是示例
 * 你需要根据具体的硬件来完成这些函数
 */

    static lv_indev_drv_t indev_drv;

    /*------------------
     * 触摸屏 touchpad
     * -----------------*/

    /*Initialize your touchpad if you have*/
    touchpad_init();

    /*Register a touchpad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read;
    indev_touchpad = lv_indev_drv_register(&indev_drv);

}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/*------------------
 * Touchpad
 * -----------------*/

/*初始化触摸屏*/
static void touchpad_init(void)
{
    XPT2046_Init(); 
    // lcd_clear(WHITE); /* 清屏 */
    XPT2046_Touch_Calibrate( LCD_SCAN_MODE );
//    tp_save_adjust_data();

}

/*Will be called by the library to read the touchpad*/
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    static lv_coord_t last_x = 0;
    static lv_coord_t last_y = 0;
    printf("进入read回调函数\r\n");
    /*Save the pressed coordinates and the state*/
    if(touchpad_is_pressed()) {
        touchpad_get_xy(&last_x, &last_y);
        data->state = LV_INDEV_STATE_PR;
			printf("read:1\r\n");
    } else {
        data->state = LV_INDEV_STATE_REL;
			printf("read:0\r\n");
    }

    /*Set the last pressed coordinates*/
    data->point.x = last_x;
    data->point.y = last_y;
}

/*Return true is the touchpad is pressed*/
static bool touchpad_is_pressed(void)
{
    /*Your code comes here*/
    if(XPT2046_TouchDetect()==TOUCH_PRESSED) return true;  /*按下返回真*/
    return false;
}

/*Get the x and y coordinates if the touchpad is pressed*/
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
{
    /*Your code comes here*/
    XPT2046_Get_TouchedPoint(&cinfo1,strXPT2046_TouchPara);  //获取坐标
 
    (*x) = cinfo1.x;        //把获取的值给LVGL
    (*y) = cinfo1.y;
    printf("touchpoint:x=%d,y=%d\r\n",cinfo1.x ,cinfo1.y);
}

#else /*Enable this file at the top*/

/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif

4.3.2.4 修改main.c函数

#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

#include "stdio.h"
#include "bsp_ili9341_lcd.h"
#include "lvgl.h"
#include "lv_port_disp_template.h"
#include "lv_port_indev_template.h"
#include "lv_mainstart.h"
#include "bsp_xpt2046_lcd.h"
void SystemClock_Config(void);
int main(void)
{
 
  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim3);
  printf("LVGL_Dome\r\n");
  ILI9341_Init();
  ILI9341_GramScan(3);
  lv_init();
  lv_port_disp_init();
  lv_port_indev_init();
  // vSimple_Btn();
  lv_obj_t *btn = lv_btn_create(lv_scr_act());
  lv_obj_set_size(btn,100,50);
  lv_obj_set_align(btn,LV_ALIGN_CENTER);
  lv_obj_set_style_bg_color(btn,lv_color_hex(0xde4d3e),LV_STATE_PRESSED);
  while (1)
  {
    
	  lv_timer_handler(); /* LVGL 绠$悊鍑芥暟鐩稿綋 RTOS 瑙﹀彂浠诲姟璋冨害鍑芥暟 */ 

  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim ) 
{
  	lv_tick_inc(1);   //娣诲姞lvgl锟??1ms蹇冭烦
}

int fputc(int ch,FILE*stream){
uint8_t c =ch;
HAL_UART_Transmit(&huart1,&c,1,50);
return ch;
}

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  
  __disable_irq();
  while (1)
  {
  }
  
}

#ifdef  USE_FULL_ASSERT

void assert_failed(uint8_t *file, uint32_t line)
{
 
}
#endif /* USE_FULL_ASSERT */

以上移植完成;

五、可能会出现的故障

lv_tick_inc(1);未被定时器正常调用会出现能显示最初的页页,但无其他响应。

你可能感兴趣的:(LVGL,单片机,stm32)