【STM32H7教程】第54章 STM32H7的LTDC应用之LCD电阻触摸和电容触摸

完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第54章       STM32H7的LTDC应用之LCD电阻触摸和电容触摸

本章教程为大家讲解LTDC应用之LCD电阻触摸芯片STMPE811的4点和2点触摸校准和电容触摸芯片FT5X06、GT911和GT811的使用。

54.1 初学者重要提示

54.2 电阻触摸和电容触摸相关知识

54.3 电阻屏硬件设计

54.4 电容屏硬件设计

54.5 电阻触摸驱动设计

54.6 电容触摸驱动设计

54.7 不同触摸IC的识别

54.8 LCD触摸移植和使用

54.9 实验例程设计框架

54.10 实验例程说明(MDK)

54.11 实验例程说明(IAR)

54.12 总结

54.1 初学者重要提示

  1. 学习本章节前,务必优先学习第50章,需要对LTDC的基础知识和HAL库的几个常用API有个认识。
  2. LTDC驱动设计和相关问题在第51章有详细说明。
  3. 电阻触摸支持2点和4点校准,而电容屏无需校准。
  4. 电阻触摸校准解决的是触摸板的线性度问题,而飞点要另外处理,当前程序已经做了支持。总的来说,V7配套的电阻触摸方案已经比较成熟,可以放心用于项目。

54.2 电阻触摸和电容触摸相关知识

这部分知识点在第52章的2.2小节有详细说明,必看

54.3 电阻屏硬件设计

电阻触摸STMPE811的原理图如下:

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

通过STMPE811的原理图要了解以下几点:

  • I2C的两根通信线I2C_SCL和I2C_SDA的上拉电阻在V7的主板上。
  • 原理图右侧的GPIO-0到GPIO-7可以作为扩展IO使用,支持输入和输出。其中GPIO-4到GPIO-7用于电阻触摸校准(使用那个IO是可以配置的)。
  • 对于X-,X+,Y-和Y+,只要不是X和Y进行组合,其它组合方式可以随意接,配套的触摸校准算法都可以正常识别。

54.4 电容屏硬件设计

电容触摸主要有三种:FT5X06,GT911和GT811,其中GT811已经停产。下面是FT5X06和GT911触摸板效果(触摸板和触摸芯片是一体的):

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

触摸芯片已经集成到柔性PCB上,且已经校准好。用户使用的话,直接通过I2C方式读取数据即可。下面是电容触摸板引出的引脚:

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

注意I2C_SDK和I2C_SCL的上拉电阻在V7主板上。

54.5 电阻触摸驱动设计

下面将电阻触摸程序设计中的相关问题逐一为大家做个说明。

54.5.1        STMPE811的驱动实现

电阻触摸要比电容触摸麻烦很多,因为电阻触摸要做校准,还要做滤波,否则采集回来的触摸值会抖动或者出现飞点,出现这种情况的主要原因是电阻触摸板的线性度不够好。开发板电阻屏使用的触摸芯片是STMPE811,这个芯片其实就是12位分辨率的ADC,用来采集电阻触摸板的X轴ADC值和Y轴ADC值,然后按照一定的线性关系将ADC值转换为实际的坐标值。其中这个线性关系是通过触摸校准建立起来的,每次采集的X轴和Y轴ADC就可以代入这个线性关系,从而获得实际的坐标值。

总的来说,STMPE811的驱动不难实现,可以结合STMPE811的数据手册:http://bbs.armfly.com/read.php?tid=23306 研究开发板提供的驱动配置。配置好后仅需要提供读取的X轴,Y轴的ADC值以及触摸按下状态(判断STMPE811的中断输出引脚就可以了,如果有触摸,这个引脚输出低电平,反之,输出高电平。通过判断这个引脚就可以选择是否读取X轴,Y轴的ADC值,避免不必要的操作)。这些函数在bsp_ts_stmpe811.c文件实现。而触摸值滤波,触摸扫描和触摸校准是在bsp_touch.c文件里面实现。

下面是清除触摸中断标志函数和X轴,Y轴的ADC值读取函数,这些函数被bsp_touch.c文件所调用,而函数TOUCH_PenInt是在bsp_touch.c文件,这里也贴出来。

.    /*
2. ******************************************************************************************************
3. * 函 数 名: TOUCH_PenInt
4. * 功能说明: 判断触摸按下
5. * 形 参: 无
6. * 返 回 值: 0表示无触笔按下,1表示有触笔按下
7. ******************************************************************************************************
8. */
. uint8_t TOUCH_PenInt(void)
. {
. if ((TP_INT_GPIO_PORT->IDR & TP_INT_PIN) == )
. {
. return ;
. }
. return ;
. }
.
. /*
19. ******************************************************************************************************
20. * 函 数 名: STMPE811_ClearInt
21. * 功能说明: 清楚触笔中断
22. * 形 参: 无
23. * 返 回 值: 无
24. ******************************************************************************************************
25. */
. void STMPE811_ClearInt(void)
. {
. STMPE811_WriteReg1(REG811_INT_STA, 0xFF);
. }
.
. /*
32. ******************************************************************************************************
33. * 函 数 名: STMPE811_ReadX
34. * 功能说明: 读取X坐标adc
35. * 形 参: 无
36. * 返 回 值: X坐标值adc
37. ******************************************************************************************************
38. */
. uint16_t STMPE811_ReadX(void)
. {
. /* 按照 XY 读取模式,连续读取3字节数据,然后分解出X,Y
42. | byte0 | byte1 | byte2 |
43. | X[11:4], | X[3:0],Y[11:8] | Y[7:0] |
44. */
. uint8_t buf[];
.
. #if 0
. STMPE811_ReadBytes(buf, REG811_TSC_DATA1, );
.
. s_AdcX = ((uint16_t)buf[] << ) | (buf[] >> );
. s_AdcY = ((uint16_t)(buf[] & 0xF) << ) | buf[];
. #else
. if (STMPE811_ReadReg1(REG811_TSC_CTRL) & 0x80)
. {
. STMPE811_ReadBytes(buf, REG811_TSC_DATA1, );
.
. s_AdcX = ((uint16_t)buf[] << ) | (buf[] >> );
. s_AdcY = ((uint16_t)(buf[] & 0xF) << ) | buf[];
.
. #if 0
. /* for debug */
. {
. static int32_t s_t1 = ;
. int32_t tt;
.
. tt = bsp_GetRunTime();
. if (tt - s_t1 > )
. {
. printf("\r\n");
. s_t1 = tt;
. }
. printf("(%7d) %5d %5d\r\n", tt, s_AdcX, s_AdcY);
. }
. #endif
. }
. else
. {
. s_AdcX = ;
. s_AdcY = ;
. }
. #endif
.
. return s_AdcX;
. }
.
. /*
87. ******************************************************************************************************
88. * 函 数 名: STMPE811_ReadX
89. * 功能说明: 读取Y坐标adc
90. * 形 参: 无
91. * 返 回 值: Y坐标值adc
92. ******************************************************************************************************
93. */
. uint16_t STMPE811_ReadY(void)
. {
. return s_AdcY;
. }

下面将程序设计中的关键地方做个阐释:

  • 第9-16行,通过判断STMPE811的中断输出引脚的高低电平来判断触摸板是否被按下,如果有触摸,这个引脚输出低电平,反之,输出高电平。通过判断这个引脚就可以选择是否读取X轴,Y轴的ADC值,避免不必要的操作。
  • 第26-29行,清除触摸中断标志,检测到触摸屏未被按下时,要做清除。
  • 第39-84行,读取X轴ADC数值。
  • 第94-97行,读取Y轴ADC数值。

54.5.2        电阻触摸扫描函数TOUCH_Scan

接下来再来看bsp_touch.c文件中STMPE811触摸扫描函数TOUCH_Scan的实现:

.    /*
2. ******************************************************************************************************
3. * 函 数 名: TOUCH_Scan
4. * 功能说明: 触摸板事件检测程序。该函数被周期性调用,每ms调用1次. 见 bsp_Timer.c
5. * 形 参: 无
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
. void TOUCH_Scan(void)
. {
. uint16_t usAdcX;
. uint16_t usAdcY;
. static uint16_t s_usXBuf[SAMPLE_COUNT];
. static uint16_t s_usYBuf[SAMPLE_COUNT];
. static uint8_t s_ucPos = ;
. static uint8_t s_count = ;
. static uint8_t s_down = ;
. static uint16_t s_usSaveAdcX, s_usSaveAdcY; /* 用于触笔抬起事件,保存按下和移动的最后采样值 */
. static uint8_t s_ms = ;
.
. if (g_GT811.Enable == )
. {
. GT811_Timer1ms(); /* 电容触摸屏程序计数器 */
. return;
. }
.
. if (g_GT911.Enable == )
. {
. GT911_Timer1ms(); /* 电容触摸屏程序计数器 */
. return;
. }
.
. if (g_tFT5X06.Enable == )
. {
. FT5X06_Timer1ms(); /* 电容触摸屏程序计数器 */
. return;
. }
.
. /* 下面用于电阻触摸 */
.
. if (g_tTP.Enable == )
. {
. return;
. }
.
. if (++s_ms >= )
. {
. return;
. }
.
. /* 2ms进入一次 */
. s_ms = ;
.
. /* 触笔中断发生 */
. if (TOUCH_PenInt())
. {
. /* 获得原始的ADC值,未滤波 */
. usAdcX = STMPE811_ReadX();
. usAdcY = STMPE811_ReadY();
.
. if (TOUCH_PressValid(usAdcX, usAdcY))
. {
. /* 按压30ms之后才开始采集数据 */
. if (s_count >= DOWN_VALID / )
. {
. s_usXBuf[s_ucPos] = usAdcX;
. s_usYBuf[s_ucPos] = usAdcY;
.
. /* 采集20ms数据进行滤波 */
. if (++s_ucPos >= SAMPLE_COUNT / )
. {
. s_ucPos = ;
.
. /* 对ADC采样值进行软件滤波 */
. g_tTP.usAdcNowX = TOUCH_DataFilter(s_usXBuf, SAMPLE_COUNT / );
. g_tTP.usAdcNowY = TOUCH_DataFilter(s_usYBuf, SAMPLE_COUNT / );
.
. if (s_down == )
. {
. s_down = ;
. /* 触摸按下事件 */
. TOUCH_PutKey(TOUCH_DOWN, g_tTP.usAdcNowX, g_tTP.usAdcNowY);
.
. s_usSaveAdcX = g_tTP.usAdcNowX;
. s_usSaveAdcY = g_tTP.usAdcNowY;
. }
. else
. {
. if (TOUCH_MoveValid(s_usSaveAdcX, s_usSaveAdcY, g_tTP.usAdcNowX, g_tTP.usAdcNowY))
. {
. /* 触摸移动事件 */
. TOUCH_PutKey(TOUCH_MOVE, g_tTP.usAdcNowX, g_tTP.usAdcNowY);
.
. s_usSaveAdcX = g_tTP.usAdcNowX;
. s_usSaveAdcY = g_tTP.usAdcNowY;
. }
. else
. {
. g_tTP.usAdcNowX = ; /* for debug stop */
. }
. }
. }
. }
. else
. {
. s_count++;
. }
. }
. else
. {
. if (s_count > )
. {
. if (--s_count == )
. {
. /* 触摸释放事件 */
. //TOUCH_PutKey(TOUCH_RELEASE, g_tTP.usAdcNowX, g_tTP.usAdcNowY);
. TOUCH_PutKey(TOUCH_RELEASE, s_usSaveAdcX, s_usSaveAdcY);
.
. g_tTP.usAdcNowX = ;
. g_tTP.usAdcNowY = ;
.
. s_count = ;
. s_down = ;
.
. STMPE811_ClearInt(); /* 清触笔中断标志 */
. }
. }
. s_ucPos = ;
. }
. }
. }

下面将程序设计中的关键地方做个阐释:

  • 第9行,此函数要每1ms被调用一次。
  • 第21-37行,用于GT811,GT911和FT5X06的程序计数器。
  • 第46-52行,设置每2ms进行一次STMPE811检测。
  • 第55行,这个就是本章前面小节说的利用STMPE811的中断输出引脚的高低电平来判断触摸板是否被按下。
  • 第58-59行,读取X轴ADC数值和Y轴ADC数值。
  • 第61行,通过函数TOUCH_PressValid检测刚刚读取的X轴,Y轴数值是否在有效的范围内。

函数TOUCH_PressValid的具体实现如下,其中全局变量g_tTP.usMaxAdc = 4095,因为电阻触摸芯片STMPE811是12位ADC,最大触摸值就是2^12 – 1 = 4095。

/* 有效ADC值的判断门限. 太接近ADC临界值的坐标认为无效 */
#define ADC_VALID_OFFSET 2 /*
*********************************************************************************************************
* 函 数 名: TOUCH_PressValid
* 功能说明: 判断按压是否有效,根据X, Y的ADC值进行大致判断
* 形 参: 无
* 返 回 值: 1 表示有效; 0 表示无效
*********************************************************************************************************
*/
static uint8_t TOUCH_PressValid(uint16_t _usX, uint16_t _usY)
{
if ((_usX <= ADC_VALID_OFFSET) || (_usY <= ADC_VALID_OFFSET)
|| (_usX >= g_tTP.usMaxAdc - ADC_VALID_OFFSET)
|| (_usY >= g_tTP.usMaxAdc - ADC_VALID_OFFSET))
{
return ;
}
else
{
return ;
}
}
  • 第64行,DOWN_VALID的宏定义是

#define DOWN_VALID             30

由于是每2ms进行一次检测,这里就表示延迟30ms后进行触摸数据采集。延迟30ms是为了消除触摸抖动。

  • 第70行,SAMPLE_COUNT 的宏定义是

#define SAMPLE_COUNT 20

由于是每2ms进行一次检测,这里就表示采集够10组数据,即20ms后进行下一步操作。

  • 第75-76行,对X轴和Y轴的ADC数值都进行软件滤波。软件滤波函数TOUCH_DataFilter的实现方法是对10组数值由小到大进行排序,对第3个,第4个和第5个数值求和,然后求平均,将平均值作为最终的ADC数值。
  • 第78-86行,变量标识s_down = 0表示触摸之前是未按下状态,在此条件里面设置s_down = 1表示触摸已经按下,并通过函数TOUCH_TransX(这个函数比较关键,是通过触摸校准函数得到的一个线性关系)将当前的X轴和Y轴ADC数值转换成实际的坐标值,然后调用函数TOUCH_PutKey将当前的坐标信息存储到FIFO里面。
  • 第89-100行设置变量标识s_down = 1后会进入此条件里面,在这个条件里面通过函数TOUCH_MoveValid判断当前是否是有效的移动,如果是,就继续调用函数TOUCH_PutKey将当前的坐标信息存储到FIFO里面,如果不是,就设置全局变量g_tTP.usAdcNowX = 0。
  • 第111-128行,如果通过STMPE811的中断输出引脚检测到触摸未按下,然后判断变量s_count是否大于0,如果大于0的话,做减减运算,算是做了一个松手延迟,防止抖动。减到0的时候,将触摸未按下或者说触摸释放消息通过函数TOUCH_PutKey存储到FIFO里面。

54.5.3        电阻屏触摸校准原理(2点)

由于不同电阻触摸板的线性度参差不齐,不能直接采用比例关系将电阻触摸芯片STMPE811的返回

值转换成实际的坐标。比如我们操作的显示屏分辨率是800*480,电阻触摸芯片采用STMPE811(12位ADC,触摸值范围0-4095),获得当前的触摸值是(1024, 2048),按照比例关系转换成坐标值就是(1024*800/4096,2048*800/4096),即(200,400)。采用这种方法效果不好,容易出现点击不准确的问题。

鉴于此原因,需要通过触摸校准在ADC数值和显示屏分辨率之间建立一个新的线性关系,简单的说就是由比例关系y = ax升级为y = ax + b。如果有了新的触摸ADC数值,代入这个线性关系里面就可以得到当前实际的坐标值,触摸校准的作用就在这里了。

具体实现原理图如下:

在左上角和右下角分别设置两个坐标点(LcdX0,  LcdY0)和(LcdX1,  LcdY1),然后让用户去点击,会得到两组ADC数值(AdcX0,AdcY0)和(AdcX1,  AdcY1)。

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

根据这四个坐标点,可以建立两组方程,一个X轴的,一个Y轴。

  • 将数值(AdcX0, LcdX0)和(AdcX1,  LcdX1)代入方程y = ax + b得到X轴方程 :

y = (x - AdcX0)*(LcdX1 - LcdX0)/(AdcX1 - AdcX0) + LcdX0

  • 将数值(AdcY0, LcdY0)和(AdcY1,  LcdY1)代入方程y = ax + b得到Y轴方程

y = (x - AdcY0)*(LcdY1 - LcdY0)/(AdcY1 - AdcX0) + LcdY0

后面采集到的ADC数值直接代入上面公式就可以得到校准后的物理坐标值(实际的分辨率坐标)。

54.5.4        电阻屏触摸校准原理(4点)

4点触摸校准实现,略复杂,实现原理如下(如果理解起来麻烦的话,会用就行,一般情况下2点校准就行):

在LCD的左上角,右上角,左下角和右下角分别标坐标点(LcdX1,  LcdY1),(LcdX4, LcdY4),(LcdX3,  LcdY3)和(LcdX2, LcdY2)。然后让用户去点击,会得到四组ADC数值(AdcX1, AdcY1),(AdcX4,  AdcY4),(AdcX3, AdcY3)和(AdcX2, AdcY2)。

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

计算X轴:

  • 将数值(AdcX1,AdcY1)和(AdcX2, AdcY2)代入方程y = ax + b得到一组方程

y = (x - AdcX1)*(AdcY2- AdcY1)/(AdcX2- AdcX1) + AdcY1

  • 这里将AdcX2用AdcX3替换,那么坐标方程就变为

y = (x - AdcX1)*(AdcY2- AdcY1)/(AdcX3- AdcX1) + AdcY1。

  • 同理,将AdcX1用AdcX4替换,那么坐标方程就变为

y = (x - AdcX4)*(AdcY2- AdcY1)/(AdcX3- AdcX4) + AdcY1

那么将采集的X值代入上面两个方程会得到两个数值,假设数值是x1和x2。

  • 再将(x1,  LcdX1))和(x2,  LcdX2)代入方程y = ax + b得到一组方程

y = (x - x1)*(LcdX2- LcdX1)/(x2- x1) + LcdX1

将采集的X轴ADC数值再次代入这个方程就得到了最终的物理坐标(实际的分辨率坐标)。

计算Y轴:

  • 将数值(AdcX1, AdcY1)和(AdcX2,  AdcY2)代入方程y = ax + b得到一组方程

y = (x - AdcX1)*(AdcY2- AdcY1)/(AdcX2- AdcX1) + AdcY1

  • 这里将AdcY2用AdcY4替换,那么坐标方程就变为

y = (x - AdcX1)*(AdcY4- AdcY1)/(AdcX2- AdcX1) + AdcY1

  • 同理,将AdcX1用AdcX3替换,那么坐标方程就变为

y = (x - AdcX3)*(AdcY2- AdcY1)/(AdcX2- AdcX3) + AdcY1

那么将采集的X值代入上面两个方程会得到两个数值,假设数值是x1和x2。

  • 再将(x1, LcdY1))和(x2,  LcdY2)代入方程y = ax + b得到一组方程

y = (x - x1)*(LcdY2- LcdY1)/(x2- x1) + LcdY1

将采集的Y轴ADC数值再次代入这个方程就得到了最终的物理坐标(实际的分辨率坐标)。

54.5.5        电阻屏触摸校准的实现

对2点和4点触摸校准原理有所了解后,再看代码部分就比较好理解了:

.    /*
2. ******************************************************************************************************
3. * 函 数 名: TOUCH_Calibration
4. * 功能说明: 触摸屏校准
5. * 形 参: _PointCount : 校准点数,2 或 4.
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
. void TOUCH_Calibration(uint8_t _PointCount)
. {
. uint16_t usAdcX;
. uint16_t usAdcY;
. uint8_t usCount;
. uint8_t I;
. uint32_t n;
.
. /* 校准点数,2点或4点 */
. if (_PointCount == )
. {
. g_tTPParam.CalibPointCount = ;
. }
. else
. {
. g_tTPParam.CalibPointCount = ;
. }
.
. TOUCH_CelarFIFO(); /* 清除无效的触摸事件 */
.
. for (I = ; I < g_tTPParam.CalibPointCount; i++)
. {
. TOUCH_DispPoint(i); /* 显示校准点 */
.
. TOUCH_WaitRelease(); /* 等待触笔释放 */
.
. usCount = ;
. for (n = ; n < ; n++)
. {
. usAdcX = TOUCH_ReadAdcX();
. usAdcY = TOUCH_ReadAdcY();
.
. if (TOUCH_PressValid(usAdcX, usAdcY))
. {
. if (++usCount > )
. {
. /* 按压有效, 保存校准点ADC采样值 */
. if (I == )
. {
. g_tTPParam.usAdcX1 = usAdcX;
. g_tTPParam.usAdcY1 = usAdcY;
. }
. else if (I == )
. {
. g_tTPParam.usAdcX2 = usAdcX;
. g_tTPParam.usAdcY2 = usAdcY;
. }
. else if (I == )
. {
. g_tTPParam.usAdcX3 = usAdcX;
. g_tTPParam.usAdcY3 = usAdcY;
. }
. else
. {
. g_tTPParam.usAdcX4 = usAdcX;
. g_tTPParam.usAdcY4 = usAdcY;
. }
. break;
. }
. }
. else
. {
. usCount = ;
. }
. bsp_DelayMS();
. }
. if (n == )
. {
. return;
. }
. }
.
. TOUCH_WaitRelease(); /* 等待触笔释放 */
.
. /* 识别触摸的 X, Y 和 显示面板的 X,Y 是否需要交换 */
. g_tTPParam.XYChange = ; /* 1表示X Y需要交换 */
. if (LCD_GetHeight() < LCD_GetWidth())
. {
. if (TOUCH_Abs(g_tTPParam.usAdcX1 – g_tTPParam.usAdcX2) <
. TOUCH_Abs(g_tTPParam.usAdcY1 – g_tTPParam.usAdcY2))
. {
. g_tTPParam.XYChange = ;
. }
. }
. else
. {
. if (TOUCH_Abs(g_tTPParam.usAdcX1 – g_tTPParam.usAdcX2) >
. TOUCH_Abs(g_tTPParam.usAdcY1 – g_tTPParam.usAdcY2))
. {
. g_tTPParam.XYChange = ;
. }
. }
.
. g_tTPParam.usLcdX1 = TP_X1;
. g_tTPParam.usLcdY1 = TP_Y1;
. g_tTPParam.usLcdX2 = TP_X2;
. g_tTPParam.usLcdY2 = TP_Y2;
. g_tTPParam.usLcdX3 = TP_X3;
. g_tTPParam.usLcdY3 = TP_Y3;
. g_tTPParam.usLcdX4 = TP_X4;
. g_tTPParam.usLcdY4 = TP_Y4;
.
. /* 在最后一步,将校准参数保存入Flash 或者EEPROM */
. TOUCH_SaveParam();
. }

下面将程序设置中的关键地方做个阐释:

  • 第18-25行,用于标记是4点触摸校准还是2点触摸校准。
  • 第31行,显示触摸校准点,2点触摸校准的话,显示左上角和右下角的校准点位置。4点触摸校准的话,显示左上角,右上角,左下角和右下角的校准点位置。
  • 第33行,用于等待触摸笔释放,当校准完毕1个点后,等待释放时使用。
  • 第35-79行,读取500次X轴和Y轴的ADC数值,每10ms读取1次,每个触摸点的最大读取时间就是5秒。如果5秒内还没有触摸就会进入到第77行直接退出触摸校准。

第41行检测到有按下的话,会连续读取5次,确保已经按下了,然后标记本次按下得到的ADC数值。根据执行的是4点触摸校准还是2点触摸校准,这个操作会执行4次或者2次。

  • 第84-100行,这里涉及到一个知识点,即X轴镜像,Y轴镜像和XY交换的处理办法,详情在此贴进行了说明:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93300
  • 第112行,将校准后的参数存储到EEPROM里面,下次开发板上电可以直接从EEPROM里面读取校准参数。

54.5.6        电阻屏触摸ADC值转物理坐标

电阻屏触摸ADC值转物理坐标的公式就是由前面5.4和5.6小节而来。

.    /*
2. ******************************************************************************************************
3. * 函 数 名: CalTwoPoint
4. * 功能说明: 根据2点直线方程,计算Y值
5. * 形 参: 2个点的坐标和x输入量
6. * 返 回 值: x对应的y值
7. ******************************************************************************************************
8. */
. static int32_t CalTwoPoint(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x)
. {
. return y1 + ((int32_t)(y2 - y1) * (x - x1)) / (x2 - x1);
. }
.
. /*
15. ******************************************************************************************************
16. * 函 数 名: TOUCH_TransX
17. * 功能说明: 将触摸ADC值转换为像素坐标
18. * 形 参: 无
19. * 返 回 值: X 坐标值,允许负值
20. ******************************************************************************************************
21. */
. static int16_t TOUCH_TransX(uint16_t _usAdcX, uint16_t _usAdcY)
. {
. if (g_tTPParam.CalibPointCount == )
. {
. uint16_t x;
. int32_t y;
.
. if (g_tTPParam.XYChange == )
. {
. x = _usAdcX;
. if (x == )
. {
. y = ;
. }
. else
. {
. //y = CalTwoPoint(g_tTPParam.usAdcX1, TP_X1, g_tTPParam.usAdcX2, TP_X2, x);
. y = CalTwoPoint(g_tTPParam.usAdcX1, g_tTPParam.usLcdX1, g_tTPParam.usAdcX2,
. g_tTPParam.usLcdX2, x);
. }
. }
. else
. {
. x = _usAdcY;
. if (x == )
. {
. y = ;
. }
. else
. {
. //y = CalTwoPoint(g_tTPParam.usAdcY1, TP_X1, g_tTPParam.usAdcY2, TP_X2, x);
. y = CalTwoPoint(g_tTPParam.usAdcY1, g_tTPParam.usLcdX1, g_tTPParam.usAdcY2,
. g_tTPParam.usLcdX2, x);
. }
. }
. return y;
. }
. else /* 4点校准 */
. {
. uint16_t x, x1, x2;
. int32_t y;
.
. if (g_tTPParam.XYChange == ) /* X Y 坐标不交换 */
. {
. x = _usAdcX;
.
. /* 根据 Y ADC 实时计算直线方程的参考点x1, x2
69. if _usAdcY = usAdcY1 then 取点 = (AdcX1, TP_X1, AdcX4, TP_X4, _usAdcY)
70. if _usAdcY = usAdcY2 then 取点 = (AdcX3, TP_X3, AdcX2, TP_X2, _usAdcY)
71.
72. 其中 TP_X1 = TP_X3; TP_X4 = TP_X1 , 这是程序设定的校准位置的像素坐标, 是固定的。
73. 我们仅需要动态计算对第1个和第3个参数。同样采用2点直线方程计算。
74. */
. x1 = CalTwoPoint(g_tTPParam.usAdcY1, g_tTPParam.usAdcX1, g_tTPParam.usAdcY2,
. g_tTPParam.usAdcX3, _usAdcY);
. x2 = CalTwoPoint(g_tTPParam.usAdcY1, g_tTPParam.usAdcX4, g_tTPParam.usAdcY2,
. g_tTPParam.usAdcX2, _usAdcY);
. }
. else /* X Y 坐标交换 */
. {
. x = _usAdcY;
.
. /* 根据 X ADC 实时计算直线方程的参考点x1, x2
85. if _usAdcX = usAdcX1 then 取点 = (AdcY1, TP_X1, AdcY4, TP_X4, _usAdcX)
86. if _usAdcX = usAdcX2 then 取点 = (AdcY3, TP_X3, AdcY2, TP_X2, _usAdcX)
87.
88. 其中 TP_X1 = TP_X3; TP_X4 = TP_X1 , 这是程序设定的校准位置的像素坐标, 是固定的。
89. 我们仅需要动态计算对第1个和第3个参数。同样采用2点直线方程计算。
90. */
. x1 = CalTwoPoint(g_tTPParam.usAdcX1, g_tTPParam.usAdcY1, g_tTPParam.usAdcX2,
. g_tTPParam.usAdcY3, _usAdcX);
. x2 = CalTwoPoint(g_tTPParam.usAdcX1, g_tTPParam.usAdcY4, g_tTPParam.usAdcX2,
. g_tTPParam.usAdcY2, _usAdcX);
. }
.
. if (x == )
. {
. y = ;
. }
. else
. {
. /* 根据2点直线方程,计算坐标 */
. //y = CalTwoPoint(x1, TP_X1, x2, TP_X2, x);
. y = CalTwoPoint(x1, g_tTPParam.usLcdX1, x2, g_tTPParam.usLcdX2, x);
. }
. return y;
. }
. }
.
. /*
112. ******************************************************************************************************
113. * 函 数 名: TOUCH_TransY
114. * 功能说明: 将触摸ADC值转换为像素坐标
115. * 形 参: 无
116. * 返 回 值: Y 坐标值,允许负值
117. ******************************************************************************************************
118. */
. static int16_t TOUCH_TransY(uint16_t _usAdcX, uint16_t _usAdcY)
. {
. if (g_tTPParam.CalibPointCount == ) /* 2点校准 */
. {
. /* 类似函数TOUCH_TransX,省略未贴出 */
. }
. else /* 4点校准 */
. {
. /* 类似函数TOUCH_TransX,省略未贴出 */
. }
. }

下面将程序设计中几个关键地方做个阐释:

  • 第9-12行,y =ax+b类型的直线方程,根据前四个参数输入的两个坐标点可以确定一条直线,然后输入第5个参数x,可以得到此坐标点对应的y值。
  • 第22行,函数TOUCH_TransX的作用是将X轴采集的ADC值转换为物理坐标值。
  • 第24-58行,用于处理2点触摸校准,对应的公式就是本章5.4小节的内容。
  • 第59-108行,用于处理4点触摸校准,对应的公式就是本章5.5小节的内容。
  • 第119-129行,函数TOUCH_TransY的作用是将Y轴采集的ADC值转换为物理坐标值。

这里注意g_tTPParam.XYChange = 1情况的处理,之所以会有这种情况,详情看此贴:

http://www.armbbs.cn/forum.php?mod=viewthread&tid=93300

54.5.7        电阻触摸的使用方法

电阻触摸的使用主要分为三步:

  • 第1步,使能触摸,在bsp.c文件的函数bsp_Init里面实现。
/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
/* 省略未写 */ bsp_InitI2C(); /* 初始化I2C总线 */
TOUCH_InitHard(); /* 初始化触摸芯片,LCD面板型号的检查也在此函数,所以要在函数LCD_InitHard前调用 */
LCD_InitHard(); /* 初始化LCD */
}
  • 第2步,每毫秒调用1次触摸扫描。
/*
*********************************************************************************************************
* 函 数 名: bsp_RunPer1ms
* 功能说明: 该函数每隔1ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些需要周期性处理
* 的事务可以放在此函数。比如:触摸坐标扫描。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_RunPer1ms(void)
{
TOUCH_Scan(); /* 触摸屏 */
}
  • 第3步,用户程序里面采用下面的框架进行触摸消息处理。

TOUCH_DOWN表示按下消息。

TOUCH_MOVE表示触摸移动消息。

TOUCH_RELEASE表示触摸释放消息。

根据这几个消息,用户可以在程序里面判断当前获取的物理坐标值是否在设置的区域内来执行触摸操作。

int16_t tpX, tpY;
uint8_t ucTouch; /* 触摸事件 */ ucTouch = TOUCH_GetKey(&tpX, &tpY); /* 读取触摸事件 */
if (ucTouch != TOUCH_NONE)
{
switch (ucTouch)
{
case TOUCH_DOWN: /* 触笔按下事件 */ /* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{ }
break; case TOUCH_MOVE: /* 触笔移动事件 */
/* 实时刷新触摸ADC采样值和转换后的坐标 */
{
/* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{ }
}
break; case TOUCH_RELEASE: /* 触笔释放事件 */
/* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{ }
break;
}
}

54.6 电容触摸驱动设计

电容触摸相比电阻触摸就要简单很多了,因为电容触摸不需要做触摸校准,而且用的是触摸板和触摸芯片一体的,也不需要做寄存器初始化配置,上电后直接读取参数即可。

由于GT811已经停产,这里重点把GT911和FT5X06做个说明。

54.6.1        电容屏触摸IC---FT5X06

电容触摸IC是支持多点触摸的,FT5X06支持多达10点触摸同时按下,并提供了I2C和SPI两种

通信接口方式,开发板使用的是I2C通信接口。更多相关知识学习可以在这里下载FT5X06数据手册和应用手册:http://bbs.armfly.com/read.php?tid=16461

注意,这个芯片返回的就是实际的坐标值,比如显示屏的分辨率是800*480,那么返回的就是在这个分辨率范围内的实际坐标,然后通过函数TOUCH_PutKey将FT5X06读出的实际坐标值存储到FIFO中即可,具体实现代码如下:

.    /*
2. ******************************************************************************************************
3. * 函 数 名: FT5X06_Scan
4. * 功能说明: 读取触摸数据。读取全部的数据。放在主程序 bsp_Idle()中执行
5. * 形 参: 无
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
. void FT5X06_Scan(void)
. {
. uint8_t buf[CFG_POINT_READ_BUF];
. uint8_t i;
. static uint8_t s_tp_down = ;
. uint16_t x, y;
. static uint16_t x_save, y_save;
. static uint8_t s_count = ;
.
. if (g_tFT5X06.Enable == )
. {
. return;
. }
.
. /* 10ms 执行一次 */
. if (g_tFT5X06.TimerCount < )
. {
. return;
. }
.
. g_tFT5X06.TimerCount = ;
.
. #if 1 /* 方案1: 检测INT引脚电平. */
. if (TOUCH_PenInt() == )
. {
. #else /* 方案2:不用INT引脚,读状态寄存器 */
. FT5X06_ReadReg(, buf, );
. if ((buf[] & 0x07) == )
. {
. #endif
. /* 持续按下时,INT电平是脉冲信号。每隔18ms出现1个宽度4ms的高电平。 */
. if (s_tp_down == )
. {
. if (++s_count > )
. {
. s_count = ;
. s_tp_down = ;
. TOUCH_PutKey(TOUCH_RELEASE, x_save, y_save);
. }
. }
. return;
. }
. s_count = ;
.
. /* 有触摸,读取完整的数据 */
. FT5X06_ReadReg(, buf, CFG_POINT_READ_BUF);
.
. g_tFT5X06.Count = buf[] & 0x07;
. if (g_tFT5X06.Count > FT5X06_TOUCH_POINTS)
. {
. g_tFT5X06.Count = FT5X06_TOUCH_POINTS;
. }
.
. g_tFT5X06.Count = ;
. for (i = ; i < FT5X06_TOUCH_POINTS; i++)
. {
. uint8_t pointid;
.
. pointid = (buf[ + *i]) >> ;
. if (pointid >= 0x0f)
. {
. break;
. }
. else
. {
. g_tFT5X06.Count++;
. g_tFT5X06.X[i] = (int16_t)(buf[ + *i] & 0x0F)<< | (int16_t)buf[ + *i];
. g_tFT5X06.Y[i] = (int16_t)(buf[ + *i] & 0x0F)<< | (int16_t)buf[ + *i];
. g_tFT5X06.Event[i] = buf[0x3 + *i] >> ;
. g_tFT5X06.id[i] = (buf[ + *i])>>;
. }
. }
.
. /* 检测按下 */
. {
. if (g_tFT5X06.ChipID == 0x55) /* 4.3寸 480 * 272 */
. {
. x = g_tFT5X06.Y[];
. y = g_tFT5X06.X[];
.
. /* 判断值域 */
. if (x > )
. {
. x = ;
. }
.
. if (y > )
. {
. y = ;
. }
. }
. else if (g_tFT5X06.ChipID == 0x0A) /* 5.0寸 800 * 480 */
. {
. x = g_tFT5X06.X[];
. y = g_tFT5X06.Y[];
.
. /* 判断值域 */
. if (x > )
. {
. x = ;
. }
. if (y > )
. {
. y = ;
. }
. }
. else /* id == 0x06 表示7寸电容屏(FT芯片) */
. {
. x = g_tFT5X06.X[];
. y = g_tFT5X06.Y[];
.
. /* 判断值域 */
. if (x > )
. {
. x = ;
. }
. if (y > )
. {
. y = ;
. }
. }
. }
.
. if (s_tp_down == )
. {
. s_tp_down = ;
.
. TOUCH_PutKey(TOUCH_DOWN, x, y);
. }
. else
. {
. TOUCH_PutKey(TOUCH_MOVE, x, y);
. }
. x_save = x; /* 保存坐标,用于释放事件 */
. y_save = y;
.
. #if 0
. for (i = ; i < CFG_POINT_READ_BUF; i++)
. {
. printf("%02X ", buf[i]);
. }
. printf("\r\n");
. #endif
.
. #if 0 /* 打印5个坐标点数据 */
. printf("(%5d,%5d,%3d,%3d) ", g_tFT5X06.X[], g_tFT5X06.Y[], g_tFT5X06.Event[],
. g_tFT5X06.id[]);
. printf("(%5d,%5d,%3d,%3d) ", g_tFT5X06.X[], g_tFT5X06.Y[], g_tFT5X06.Event[],
. g_tFT5X06.id[]);
. printf("(%5d,%5d,%3d,%3d) ", g_tFT5X06.X[], g_tFT5X06.Y[], g_tFT5X06.Event[],
. g_tFT5X06.id[]);
. printf("(%5d,%5d,%3d,%3d) ", g_tFT5X06.X[], g_tFT5X06.Y[], g_tFT5X06.Event[],
. g_tFT5X06.id[]);
. printf("(%5d,%5d,%3d,%3d) ", g_tFT5X06.X[], g_tFT5X06.Y[], g_tFT5X06.Event[],
. g_tFT5X06.id[]);
. printf("\r\n");
. #endif
. }

下面将程序设计中几个关键地方做个阐释:

  • 第32行,通过判断FT5X06的中断输出引脚的高低电平来判断触摸板是否被按下,如果有触摸,这个引脚输出低电平,反之,输出高电平。通过判断这个引脚可以避免不必要的操作。
  • 第35-36行,从寄存器2读取一个数据,判断是否有触摸数据,这种方式就没有直接判断中断引脚方便。
  • 第40-50行,如果没有触摸数据,而且变量标志s_tp_down = 1(此变量等于1表示之前处于触摸按下或者移动状态,如果等于0表示之前处于未被触摸状态),那么此时要通过函数TOUCH_PutKey存储松手消息,这一步比较重要,切不可省略。如果变量标志s_tp_down = 0,直接退出即可。另外,特别注意一点,这里是通过s_count变量连续记录到两次松手消息才执行。
  • 第50-80行,如果有触摸,将所有触摸值全部记录下来(由于支持多点触摸,会有多个触摸值)。
  • 第83-130行,根据不同的分辨率的显示屏,做值域范围处理,防止超出范围。
  • 第132-137行,如果变量s_tp_down = 0表示之前处于未被触摸状态,这里设置此变量为1,并通过函数TOUCH_PutKey存储按下消息和当前的坐标值。
  • 第138-141行,如果变量不等于0(其实这里也就是1)表示之前处于按下状态,此时触摸处于移动状态,这里不用重复设置此变量了,但通过函数TOUCH_PutKey存储按下消息和当前的坐标值。
  • 第153-165行,这里预留的条件编译主要是方便调试阶段使用。

54.6.2        电容屏触摸IC---GT911

GT911支持多达5点触摸同时按下,并提供了I2C通信接口方式,更多相关知识学习可以在这里下载GT911数据手册:http://www.armbbs.cn/forum.php?mod=viewthread&tid=87205

GT911的使用基本跟FT5X06是一样(注意,芯片GT911返回的就是实际的坐标值,比如显示屏的分辨率是800*480,那么返回的就是在这个分辨率范围内的实际坐标,跟FT5X06也是一样的),具体实现代码如下(这里没有使用多点触摸功能,仅存了一个触摸值,一般情况下已经够用):

.    /*
2. ******************************************************************************************************
3. * 函 数 名: GT911_Scan
4. * 功能说明: 读取GT911触摸数据。读取全部的数据,需要 720us左右。放在 bsp_Idle()中执行
5. * 形 参: 无
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
. void GT911_Scan(void)
. {
. uint8_t buf[];
. static uint8_t s_tp_down = ;
. uint16_t x, y;
. static uint16_t x_save, y_save;
. uint8_t clear_flag = ;
. static uint8_t s_count = ;
.
. if (g_GT911.Enable == )
. {
. return;
. }
.
. /* 10ms 执行一次 */
. if (g_GT911.TimerCount < )
. {
. return;
. }
.
. g_GT911.TimerCount = ;
.
. #if 1 /* 方案1: 检测INT引脚电平. */
. if (TOUCH_PenInt() == )
. {
. #else /* 方案2:不用INT引脚,读状态寄存器 */
. GT911_ReadReg(GT911_READ_XY_REG, buf, );
. if (buf[] == )
. {
. #endif
. if (s_tp_down == )
. {
. if (++s_count > )
. {
. s_count = ;
. s_tp_down = ;
. TOUCH_PutKey(TOUCH_RELEASE, x_save, y_save);
. }
. }
. return;
. }
. s_count = ;
.
. #if 1 /* 一般应用只读1点 */
. GT911_ReadReg(GT911_READ_XY_REG, buf, );
. #else /* 读5个触摸点 */
. GT911_ReadReg(GT911_READ_XY_REG, buf, );
. #endif
.
. GT911_WriteReg(GT911_READ_XY_REG, &clear_flag, ); /* 读完坐标后必须写0清除 */
.
. /*
61. 0x814E R/W Bufferstatus Large_Detect number of touch points
62. Bit7: Buffer status,1表示坐标(或按键)已经准备好,主控可以读取;0表示未就绪,数据无效。
63. 当主控读取完坐标后,必须通过I2C将此标志(或整个字节)写为0。
64. Bit4: HaveKey, 1表示有按键,0表示无按键(已经松键)。
65. Bit3~0: Number of touch points, 屏上的坐标点个数
66.
67. 0x814F R Point1 track id
68. 0x8150 R Point1Xl 触摸点 1,X 坐标低 8 位
69. 0x8151 R Point1Xh 触摸点 1,X 坐标高 8 位
70. 0x8152 R Point1Yl 触摸点 1,Y 坐标低 8 位
71. 0x8153 R Point1Yh 触摸点 1,Y 坐标高 8 位
72. 0x8154 R Point1 触摸点 1,触摸面积低 8 位
73. 0x8155 R Point1 触摸点 1,触摸面积高 8 位
74. 0x8156 ----
75.
76. 0x8157 R Point2 track id
77. 0x8158 R Point2Xl 触摸点 2,X 坐标低 8 位
78. 0x8159 R Point2Xh 触摸点 2,X 坐标高 8 位
79. 0x815A R Point2Yl 触摸点 2,Y 坐标低 8 位
80. 0x815B R Point2Yh 触摸点 2,Y 坐标高 8 位
81. 0x815C R Point2 触摸点 2,触摸面积低 8 位
82. 0x815D R Point2 触摸点 2,触摸面积高 8 位
83. 0x815E ----
84.
85. 0x815F R Point3 track id
86. 0x8160 R Point3Xl 触摸点 3,X 坐标低 8 位
87. 0x8161 R Point3Xh 触摸点 3,X 坐标高 8 位
88. 0x8162 R Point3Yl 触摸点 3,Y 坐标低 8 位
89. 0x8163 R Point3Yh 触摸点 3,Y 坐标高 8 位
90. 0x8164 R Point3 触摸点 3,触摸面积低 8 位
91. 0x8165 R Point3 触摸点 3,触摸面积高 8 位
92. 0x8166 ----
93.
94. 0x8167 R Point4 track id
95. 0x8168 R Point4Xl 触摸点 4,X 坐标低 8 位
96. 0x8169 R Point4Xh 触摸点 4,X 坐标高 8 位
97. 0x816A R Point4Yl 触摸点 4,Y 坐标低 8 位
98. 0x816B R Point4Yh 触摸点 4,Y 坐标高 8 位
99. 0x816C R Point4 触摸点 4,触摸面积低 8 位
100. 0x816D R Point4 触摸点 4,触摸面积高 8 位
101. 0x816E ----
102.
103. 0x816F R Point5 track id
104. 0x8170 R Point5Xl 触摸点 5,X 坐标低 8 位
105. 0x8171 R Point5Xh 触摸点 5,X 坐标高 8 位
106. 0x8172 R Point5Yl 触摸点 5,Y 坐标低 8 位
107. 0x8173 R Point5Yh 触摸点 5,Y 坐标高 8 位
108. 0x8174 R Point5 触摸点 5,触摸面积低 8 位
109. 0x8175 R Point5 触摸点 5,触摸面积高 8 位
110. 0x8176 --
111.
112. */
. g_GT911.TouchpointFlag = buf[];
. g_GT911.Touchkeystate = buf[];
.
. g_GT911.X0 = ((uint16_t)buf[] << ) + buf[];
. g_GT911.Y0 = ((uint16_t)buf[] << ) + buf[];
. g_GT911.P0 = ((uint16_t)buf[] << ) + buf[];
.
. #if 0 /* 其余4点一般不用 */
. g_GT911.X1 = ((uint16_t)buf[] << ) + buf[];
. g_GT911.Y1 = ((uint16_t)buf[] << ) + buf[];
. g_GT911.P1 = ((uint16_t)buf[] << ) + buf[];
.
. g_GT911.X2 = ((uint16_t)buf[] << ) + buf[];
. g_GT911.Y2 = ((uint16_t)buf[] << ) + buf[];
. g_GT911.P2 = ((uint16_t)buf[] << ) + buf[];
.
. g_GT911.X3 = ((uint16_t)buf[] << ) + buf[];
. g_GT911.Y3 = ((uint16_t)buf[] << ) + buf[];
. g_GT911.P3 = ((uint16_t)buf[] << ) + buf[];
.
. g_GT911.X4 = ((uint16_t)buf[] << ) + buf[];
. g_GT911.Y4 = ((uint16_t)buf[] << ) + buf[];
. g_GT911.P4 = ((uint16_t)buf[] << ) + buf[];
. #endif
.
. /* 检测按下 */
. {
. /* 坐标转换 :
141. 电容触摸板左下角是 (0,0); 右上角是 (479,799)
142. 需要转到LCD的像素坐标 (左上角是 (0,0), 右下角是 (799,479)
143. */
. {
. x = g_GT911.X0;
. y = g_GT911.Y0;
.
. if (x > )
. {
. x = ;
. }
.
. if (y > )
. {
. y = ;
. }
. }
. }
.
. if (s_tp_down == )
. {
. s_tp_down = ;
.
. TOUCH_PutKey(TOUCH_DOWN, x, y);
. }
. else
. {
. TOUCH_PutKey(TOUCH_MOVE, x, y);
. }
. x_save = x; /* 保存坐标,用于释放事件 */
. y_save = y;
.
. #if 0
. {
. uint8_t i;
.
. for (i = ; i < ; i++)
. {
. printf("%02X ", buf[i]);
. }
. printf("\r\n");
.
. printf("(%5d,%5d,%3d) ", g_GT911.X0, g_GT911.Y0, g_GT911.P0);
. printf("(%5d,%5d,%3d) ", g_GT911.X1, g_GT911.Y1, g_GT911.P1);
. printf("(%5d,%5d,%3d) ", g_GT911.X2, g_GT911.Y2, g_GT911.P2);
. printf("(%5d,%5d,%3d) ", g_GT911.X3, g_GT911.Y3, g_GT911.P3);
. printf("(%5d,%5d,%3d) ", x, y, g_GT911.P4);
. printf("\r\n");
. }
. #endif
. }

下面将程序设计中几个关键地方做个阐释:

  • 第18-21行,使能标志,在文件bsp_touch.c文件里面有一个函数bsp_DetectLcdType专门检测不同的触摸IC,如果是GT911的话,变量g_tFT5X06.Enable会被置1。
  • 第24-27行,表示函数GT911_Scan每10ms执行1次。
  • 第32行,通过判断GT911的中断输出引脚的高低电平来判断触摸板是否被按下,如果有触摸,这个引脚输出低电平,反之,输出高电平。通过判断这个引脚可以避免不必要的操作。
  • 第35-36行,从寄存器2读取一个数据,判断是否有触摸数据,这种方式就没有直接判断中断引脚方便。
  • 第39-49行,如果没有触摸数据,而且变量标志s_tp_down = 1(此变量等于1表示之前处于触摸按下或者移动状态,如果等于0表示之前处于未被触摸状态),那么此时要通过函数TOUCH_PutKey存储松手消息,这一步比较重要,切不可省略。如果变量标志s_tp_down = 0,直接退出即可。另外,特别注意一点,这里是通过s_count变量连续记录到两次松手消息才执行。
  • 第52-136行,仅读取一组。这里没有使用多点触摸功能,仅存了一个触摸值,一般情况下已经够用。
  • 第138-158行,对读取的值域范围处理,防止超出范围。
  • 第160-165行,如果变量s_tp_down = 0表示之前处于未被触摸状态,这里设置此变量为1,并通过函数TOUCH_PutKey存储按下消息和当前的坐标值。
  • 第166-169行,如果变量不等于0(其实这里也就是1)表示之前处于按下状态,此时触摸处于移动状态,这里不用重复设置此变量了,但通过函数TOUCH_PutKey存储按下消息和当前的坐标值。
  • 第173-190行,这里预留的条件编译主要是方便调试阶段使用。

54.6.3 电容触摸的使用方法

电容触摸的使用主要分为三步:

  • 第1步,使能触摸,在bsp.c文件的函数bsp_Init里面实现。
/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
/* 省略未写 */ bsp_InitI2C(); /* 初始化I2C总线 */
TOUCH_InitHard(); /* 初始化触摸芯片,LCD面板型号的检查也在此函数,所以要在函数LCD_InitHard前调用 */
LCD_InitHard(); /* 初始化LCD */
}
  • 第2步,每毫秒调用1次触摸扫描。对于电容屏来说,这个函数主要提供了一个计数器功能。并且需要用户在主任务里面一直调用函数bsp_Idle,这个函数主要实现电容触摸屏扫描。
/*
*********************************************************************************************************
* 函 数 名: bsp_RunPer1ms
* 功能说明: 该函数每隔1ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些需要周期性处理
* 的事务可以放在此函数。比如:触摸坐标扫描。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_RunPer1ms(void)
{
TOUCH_Scan(); /* 触摸屏 */
} /*
*********************************************************************************************************
* 函 数 名: bsp_Idle
* 功能说明: 空闲时执行的函数。一般主程序在for和while循环程序体中需要插入 CPU_IDLE() 宏来调用本函数。
* 本函数缺省为空操作。用户可以添加喂狗、设置CPU进入休眠模式的功能。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Idle(void)
{
/* --- 喂狗 */ /* --- 让CPU进入休眠,由Systick定时中断唤醒或者其他中断唤醒 */ /* 例如 emWin 图形库,可以插入图形库需要的轮询函数 */
//GUI_Exec(); /* 例如 uIP 协议,可以插入uip轮询函数 */
TOUCH_CapScan();
}
  • 第3步,用户程序里面采用下面的框架进行触摸消息处理。

TOUCH_DOWN表示按下消息。

TOUCH_MOVE表示触摸移动消息。

TOUCH_RELEASE表示触摸释放消息。

根据这几个消息,用户可以在程序里面判断当前获取的物理坐标值是否在设置的区域内来执行触摸操作。

int16_t tpX, tpY;
uint8_t ucTouch; /* 触摸事件 */ ucTouch = TOUCH_GetKey(&tpX, &tpY); /* 读取触摸事件 */
if (ucTouch != TOUCH_NONE)
{
switch (ucTouch)
{
case TOUCH_DOWN: /* 触笔按下事件 */ /* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{ }
break; case TOUCH_MOVE: /* 触笔移动事件 */
/* 实时刷新触摸ADC采样值和转换后的坐标 */
{
/* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{ }
}
break; case TOUCH_RELEASE: /* 触笔释放事件 */
/* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{ }
break;
}
}

54.7 不同触摸IC的识别

由于V7开发板要做不同显示屏的自适应,所以关联了多个文件。自适应方法也比较简单,因为触摸芯片GT911,GT811,FT5X06和STMPE811都是I2C接口,通过I2C地址就区分开了,具体代码如下:

.    /*
2. ******************************************************************************************************
3. * 函 数 名: bsp_DetectLcdType
4. * 功能说明: 通过I2C触摸芯片,识别LCD模组类型。结果存放在全局变量 g_LcdType 和 g_TouchType
5. * 形 参: 无
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
. void bsp_DetectLcdType(void)
. {
. uint8_t i;
.
. g_TouchType = 0xFF;
. g_LcdType = 0xFF;
.
. bsp_DelayUS();
.
. touch_printf("正在识别触摸屏型号\r\n");
.
. /* 50ms,等待GT811复位就绪,才能探测GT811芯片 ID */
. for (i = ; i < ; i++)
. {
. /*
24. GT811电容触摸板和GT911的I2C地址相同
25. 一般就 0x28 、 0xBA 两种。
26. 通过读取触摸IC的芯片ID来识别。
27. */
. if (i2c_CheckDevice(0x28) == )
. {
. uint32_t id;
.
. bsp_DelayUS();
. g_GT911.i2c_addr = 0x28;
. id = GT911_ReadID();
. if (id == 0x00313139)
. {
. g_GT911.i2c_addr = 0x28;
. g_TouchType = CT_GT911;
. g_LcdType = LCD_70_800X480;
. touch_printf("检测到7.0寸电容触摸屏GT911(0x28) 800x480\r\n");
. }
. else /* GT811 */
. {
. g_GT811.i2c_addr = 0x28;
. g_TouchType = CT_GT811;
. g_LcdType = LCD_70_800X480;
. touch_printf("检测到7.0寸电容触摸屏GT811(0x28) 800x480\r\n");
. }
. break;
. }
.
. if (i2c_CheckDevice(0xBA) == )
. {
. uint32_t id;
.
. bsp_DelayUS();
. g_GT911.i2c_addr = 0xBA;
. id = GT911_ReadID();
. if (id == 0x00313139)
. {
. g_GT911.i2c_addr = 0xBA;
. g_TouchType = CT_GT911;
. g_LcdType = LCD_70_800X480;
. touch_printf("检测到7.0寸电容触摸屏GT911(0xBA) 800x480\r\n");
. }
. else /* GT811 */
. {
. g_GT811.i2c_addr = 0xBA;
. g_TouchType = CT_GT811;
. g_LcdType = LCD_70_800X480;
. touch_printf("检测到7.0寸电容触摸屏GT811(0xBA) 800x480\r\n");
. }
. break;
. }
.
. /* FT系列电容触摸: 4.3寸id = 0x55 5.0寸id = 0x0A 7.0寸id = 0x06 */
. if (i2c_CheckDevice(FT5X06_I2C_ADDR) == )
. {
. uint8_t id;
.
. bsp_DelayUS(); /* 延迟50ms */
. id = FT5X06_ReadID();
. if (id == 0x55)
. {
. g_TouchType = CT_FT5X06;
. g_LcdType = LCD_43_480X272;
. touch_printf("检测到4.3寸电容触摸屏\r\n");
. }
. else if (id == 0x0A)
. {
. g_TouchType = CT_FT5X06;
. g_LcdType = LCD_50_800X480;
. touch_printf("检测到5.0寸电容触摸屏\r\n");
. }
. else /* id == 0x06 表示7寸电容屏(FT芯片) */
. {
. g_TouchType = CT_FT5X06;
. g_LcdType = LCD_70_800X480;
. touch_printf("检测到7.0寸电容触摸屏FT\r\n");
. }
. break;
. }
.
. /* 电阻触摸板 */
. if (i2c_CheckDevice(STMPE811_I2C_ADDRESS) == )
. {
. /*
108. 0 = 4.3寸屏(480X272)
109. 1 = 5.0寸屏(480X272)
110. 2 = 5.0寸屏(800X480)
111. 3 = 7.0寸屏(800X480)
112. 4 = 7.0寸屏(1024X600)
113. 5 = 3.5寸屏(480X320)
114. */
. uint8_t id;
.
. g_TouchType = CT_STMPE811; /* 触摸类型 */
.
. STMPE811_InitHard(); /* 必须先配置才能读取ID */
.
. id = STMPE811_ReadIO(); /* 识别LCD硬件类型 */
.
. touch_printf("检测到电阻触摸屏, id = %d\r\n", id);
. switch (id)
. {
. case :
. g_LcdType = LCD_43_480X272;
. break;
.
. case :
. g_LcdType = LCD_50_480X272;
. break;
.
. case :
. g_LcdType = LCD_50_800X480;
. break;
.
. case :
. g_LcdType = LCD_70_800X480;
. break;
.
. case :
. g_LcdType = LCD_70_1024X600;
. break;
.
. case :
. g_LcdType = LCD_35_480X320;
. break;
.
. default:
. g_LcdType = LCD_35_480X320;
. break;
. }
. break;
. }
.
. bsp_DelayMS();
. }
.
. if (i == )
. {
. touch_printf("未识别出显示模块\r\n");
. }
. }

下面将程序设计中几个关键地方做个阐释:

  • 第21行,上电后,这几款传感器一定要延迟一段时间才可以正常读取ID,所以这里做了一个for循环做检测。
  • 第28-74行,GT811电容触摸板和GT911的I2C地址相同,一般就0x28和0xBA两种。程序里面对这两个地址都做了检测,然后通过ID区分是GT811还是GT911。
  • 第77-102行,FT5X06电容触摸板检测,通过ID区分不同分辨率。
  • 第105-158行,STMPE811电阻触摸屏检测,通过函数STMPE811_ReadIO返回的IO状态可以识别3.5寸480*320分辨率显示屏,4.3寸480*272分辨率显示屏,5寸480*272和800*480分辨率显示屏以及7寸800*480和1024*600分辨率显示屏。
  • 第160-163行,用于调试。

54.8 LCD触摸移植和使用

LCD驱动的移植与第51章51.7小节相同,这里重点说下触摸部分的移植。

通过前面的讲解,移植触摸驱动到自己的板子上,最简单的办法是将开发板上与触摸相关的文件全部移植过来,然后在这些文件的基础上进行修改。下面分两种情况进行说明:

  • 电容屏触摸的移植比较简单,如果用户用的触摸IC跟开发板一样,直接拿来用即可,如果不一样,需要先将触摸IC的驱动实现,然后按照开发板提供的GT811、GT911和FT5X06的触摸扫描函数,照葫芦画瓢实现一个即可。
  • 电阻屏的移植稍麻烦些,如果用户用的触摸IC跟开发板一样,直接拿来用即可,如果不一样,需要先将触摸IC的驱动实现,然后仿照TOUCH_STMPE811.c文件提供触摸按下状态函数,X轴,Y轴的ADC数值读取函数和清除触摸中断标志函数。最后用重新实现的这几个函数替换bsp_touch.c文件中的原函数即可。另外要注意一点,这种方式实现后,虽然触摸校准依然可以使用,但是开发板的触摸校准参数保存在EEPROM中,用户可以根据自己的实际情况选择存储介质。

当然,如果大家不想用开发板实现的方案,想自己重新实现一个,也是没问题的。

54.9 实验例程设计框架

通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

  第1阶段,上电启动阶段:

  • 这部分在第14章进行了详细说明。

  第2阶段,进入main函数:

  • 第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED,串口,LCD,SDRAM等。
  • 第2步,LCD应用程序设计部分。通过按键执行功能,同时LCD界面做了一个简单的画板功能。

54.10          实验例程说明(MDK)

配套例子:

V7-034_LCD的电阻触摸和电容触摸(电阻触摸支持2点和4点校准)

实验目的:

  1. 学习LCD的电阻触摸和电容触摸。

实验内容:

  1. 电容屏无需校准,电阻屏需要校准。
  2. LCD界面实现了一个简单的画板功能,方便检测触摸是否准确。
  3. 启动1个200ms的自动重装定时器,让LED2每200ms翻转一次。

LCD界面显示效果如下:

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

程序设计:

系统栈大小分配:

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

RAM空间用的DTCM:

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
/* 配置MPU */
MPU_Config(); /* 使能L1 Cache */
CPU_CACHE_Enable(); /*
STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- 设置NVIV优先级分组为4。
*/
HAL_Init(); /*
配置系统时钟到400MHz
- 切换使用HSE。
- 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config(); /*
Event Recorder:
- 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并开启 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitTimer(); /* 初始化滴答定时器 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */ bsp_InitI2C(); /* 初始化I2C总线 */
TOUCH_InitHard(); /* 初始化触摸芯片,LCD面板型号的检查也在此函数,所以要在函数LCD_InitHard前调用 */
LCD_InitHard(); /* 初始化LCD */
}

MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和SDRAM。由于SDRAM要用于LCD的显存,方便起见,直接将其配置为WT模式。

/*
*********************************************************************************************************
* 函 数 名: MPU_Config
* 功能说明: 配置MPU
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */
HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置SDRAM的MPU属性为Write through, read allocate,no write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xC0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32MB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
} /*
*********************************************************************************************************
* 函 数 名: CPU_CACHE_Enable
* 功能说明: 使能L1 Cache
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
/* 使能 I-Cache */
SCB_EnableICache(); /* 使能 D-Cache */
SCB_EnableDCache();
}

主功能:

主程序实现如下操作:

  • LCD界面实现了一个简单的画板功能,方便检测触摸是否准确。
  • 启动1个200ms的自动重装定时器,让LED2每200ms翻转一次。
  • 摇杆上键,增加LCD背景光亮度。
  • 摇杆下键,降低LCD背景光亮度。
  • 摇杆左键,电阻触摸屏校准。
  • 摇杆OK键,清屏。
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: c程序入口
* 形 参: 无
* 返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
uint16_t ucBright; /* 背光亮度(0-255) */
uint8_t ucKeyCode; /* 按键代码 */
uint8_t fRefresh; /* 刷屏请求标志,1表示需要刷新 */
FONT_T tFont; /* 定义一个字体结构体变量,用于设置字体参数 */
char buf[];
uint16_t usAdcX, usAdcY;
int16_t tpX, tpY;
uint8_t ucTouch; /* 触摸事件 */ /* 设置字体参数 */
{
tFont.FontCode = FC_ST_16; /* 字体代码 16点阵 */
tFont.FrontColor = CL_WHITE; /* 字体颜色 */
tFont.BackColor = CL_BLUE; /* 文字背景颜色 */
tFont.Space = ; /* 文字间距,单位 = 像素 */
} bsp_Init(); /* 硬件初始化 */
PrintfLogo(); /* 打印例程名称和版本等信息 */
PrintfHelp(); /* 打印操作提示 */ /* 延迟200ms再点亮背光,避免瞬间高亮 */
bsp_DelayMS(); DispFirstPage(); /* 显示第1页 */ /* 界面整体显示完毕后,再打开背光,设置为缺省亮度 */
bsp_DelayMS();
ucBright = BRIGHT_DEFAULT;
LCD_SetBackLight(ucBright); bsp_StartAutoTimer(, ); /* 启动1个200ms的自动重装的定时器,软件定时器0 */ /* 进入主程序循环体 */
fRefresh = ;
while ()
{
bsp_Idle(); /* 判断软件定时器0是否超时 */
if(bsp_CheckTimer())
{
/* 每隔200ms 进来一次 */
bsp_LedToggle();
} if (fRefresh)
{
fRefresh = ; /* 实时刷新触摸ADC采样值和转换后的坐标 */
{ /* 读取并显示当前X轴和Y轴的ADC采样值 */
usAdcX = TOUCH_ReadAdcX();
usAdcY = TOUCH_ReadAdcY();
sprintf(buf, "触摸ADC值 X = %4d, Y = %4d ", usAdcX, usAdcY);
LCD_DispStr(, , buf, &tFont); /* 读取并显示当前触摸坐标 */
tpX = TOUCH_GetX();
tpY = TOUCH_GetY();
sprintf(buf, "触摸坐标 X = %4d, Y = %4d ", tpX, tpY);
LCD_DispStr(, , buf, &tFont); /* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{
LCD_DrawCircle(tpX, tpY, , CL_YELLOW);
}
} /* 在屏幕边沿绘制2个矩形框(用于检测面板边缘像素是否正常) */
LCD_DrawRect(, , LCD_GetHeight(), LCD_GetWidth(), CL_WHITE);
LCD_DrawRect(, , LCD_GetHeight() - , LCD_GetWidth() - , CL_YELLOW); /* 显示背光值 */
sprintf(buf, "当前背光亮度: %3d", ucBright);
LCD_DispStr(, , buf, &tFont);
} ucTouch = TOUCH_GetKey(&tpX, &tpY); /* 读取触摸事件 */
if (ucTouch != TOUCH_NONE)
{
switch (ucTouch)
{
case TOUCH_DOWN: /* 触笔按下事件 */ /* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{
LCD_DrawCircle(tpX, tpY, , CL_RED);
}
break; case TOUCH_MOVE: /* 触笔移动事件 */
/* 实时刷新触摸ADC采样值和转换后的坐标 */
{
/* 读取并显示当前X轴和Y轴的ADC采样值 */
usAdcX = TOUCH_ReadAdcX();
usAdcY = TOUCH_ReadAdcY();
sprintf(buf, "触摸ADC值 X = %4d, Y = %4d ", usAdcX, usAdcY);
LCD_DispStr(, , buf, &tFont); /* 读取并显示当前触摸坐标 */
//tpX = TOUCH_GetX();
//tpY = TOUCH_GetY();
sprintf(buf, "触摸坐标 X = %4d, Y = %4d ", tpX, tpY);
LCD_DispStr(, , buf, &tFont); /* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{
LCD_DrawCircle(tpX, tpY, , CL_YELLOW);
}
}
break; case TOUCH_RELEASE: /* 触笔释放事件 */
/* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{
LCD_DrawCircle(tpX, tpY, , CL_WHITE);
}
break;
}
} ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
/* 有键按下 */
switch (ucKeyCode)
{
case JOY_DOWN_L: /* 摇杆LEFT键按下, 2点触摸校准 */
TOUCH_Calibration();
DispFirstPage();
fRefresh = ; /* 请求刷新LCD */
break; case JOY_DOWN_OK: /* 摇杆OK键,清屏 */
DispFirstPage();
fRefresh = ; /* 请求刷新LCD */
break; case JOY_DOWN_U: /* 摇杆UP键按下 */
ucBright += BRIGHT_STEP;
if (ucBright > BRIGHT_MAX)
{
ucBright = BRIGHT_MAX;
}
LCD_SetBackLight(ucBright);
fRefresh = ; /* 请求刷新LCD */
break; case JOY_DOWN_D: /* 摇杆DOWN键按下 */
if (ucBright < BRIGHT_STEP)
{
ucBright = ;
}
else
{
ucBright -= BRIGHT_STEP;
}
LCD_SetBackLight(ucBright);
fRefresh = ; /* 请求刷新LCD */
break; default:
break;
}
}
}
}

LCD主界面显示内容如下:

/*
*********************************************************************************************************
* 函 数 名: DispFirstPage
* 功能说明: 显示操作提示
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
static void DispFirstPage(void)
{
FONT_T tFont; /* 定义一个字体结构体变量,用于设置字体参数 */
uint16_t y; /* Y坐标 */
uint16_t usLineCap; /* 行高 */
char buf[]; LCD_ClrScr(CL_BLUE); /* 清屏,背景蓝色 */ /* 设置字体属性 */
tFont.FontCode = FC_ST_16; /* 字体选择宋体16点阵,高16x宽15) */
tFont.FrontColor = CL_WHITE; /* 字体颜色设置为白色 */
tFont.BackColor = CL_MASK; /* 文字背景颜色,透明 */
tFont.Space = ; /* 字符水平间距, 单位 = 像素 */ y = ;
usLineCap = ; /* 行间距 */
LCD_DispStr(, y, "安富莱STM32-V7开发板 www.armfly.com", &tFont);
y += usLineCap;
LCD_DispStr(, y, "电阻屏和电容屏实验,电容屏无需校准,电阻需要校准", &tFont); y += * usLineCap;
LCD_DispStr(, y, "操作提示:", &tFont); y += usLineCap;
LCD_DispStr(, y, "摇杆上键 = 增加背光亮度", &tFont); y += usLineCap;
LCD_DispStr(, y, "摇杆下键 = 降低背光亮度", &tFont); y += usLineCap;
LCD_DispStr(, y, "摇杆左键 = 电阻屏触摸校准", &tFont); y += usLineCap;
LCD_DispStr(, y, "摇杆OK键 = 清屏", &tFont); y += * usLineCap; /* 显示TFT控制器型号和屏幕分辨率 */
//LCD_GetChipDescribe(buf); /* 读取TFT驱动芯片型号 */
if (g_TouchType == CT_GT811)
{
strcpy(buf, "STM32H743 + GT811");
}
else if (g_TouchType == CT_GT911)
{
strcpy(buf, "STM32H743 + GT911");
}
else if (g_TouchType == CT_FT5X06)
{
strcpy(buf, "STM32H743 + FT5X06");
}
else if (g_TouchType == CT_STMPE811)
{
strcpy(buf, "STM32H743 + STMPE811");
}
else
{
strcpy(buf, "STM32H743 + NoTouch");
}
sprintf(&buf[strlen(buf)], " %d x %d", LCD_GetWidth(), LCD_GetHeight());
LCD_DispStr(, y, (char *)buf, &tFont);
}

54.11          实验例程说明(IAR)

配套例子:

V7-034_LCD的电阻触摸和电容触摸(电阻触摸支持2点和4点校准)

实验目的:

  1. 学习LCD的电阻触摸和电容触摸。

实验内容:

  1. 电容屏无需校准,电阻屏需要校准。
  2. LCD界面实现了一个简单的画板功能,方便检测触摸是否准确。
  3. 启动1个200ms的自动重装定时器,让LED2每200ms翻转一次。

LCD界面显示效果如下:

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

程序设计:

系统栈大小分配:

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

RAM空间用的DTCM:

【STM32H7教程】第54章  STM32H7的LTDC应用之LCD电阻触摸和电容触摸

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
/* 配置MPU */
MPU_Config(); /* 使能L1 Cache */
CPU_CACHE_Enable(); /*
STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- 设置NVIV优先级分组为4。
*/
HAL_Init(); /*
配置系统时钟到400MHz
- 切换使用HSE。
- 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config(); /*
Event Recorder:
- 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并开启 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitTimer(); /* 初始化滴答定时器 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */ bsp_InitI2C(); /* 初始化I2C总线 */
TOUCH_InitHard(); /* 初始化触摸芯片,LCD面板型号的检查也在此函数,所以要在函数LCD_InitHard前调用 */
LCD_InitHard(); /* 初始化LCD */
}

MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和SDRAM。由于SDRAM要用于LCD的显存,方便起见,直接将其配置为WT模式。

/*
*********************************************************************************************************
* 函 数 名: MPU_Config
* 功能说明: 配置MPU
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */
HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置SDRAM的MPU属性为Write through, read allocate,no write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xC0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32MB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
} /*
*********************************************************************************************************
* 函 数 名: CPU_CACHE_Enable
* 功能说明: 使能L1 Cache
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
/* 使能 I-Cache */
SCB_EnableICache(); /* 使能 D-Cache */
SCB_EnableDCache();
}

主功能:

主程序实现如下操作:

  • LCD界面实现了一个简单的画板功能,方便检测触摸是否准确。
  • 启动1个200ms的自动重装定时器,让LED2每200ms翻转一次。
  • 摇杆上键,增加LCD背景光亮度。
  • 摇杆下键,降低LCD背景光亮度。
  • 摇杆左键,电阻触摸屏校准。
  • 摇杆OK键,清屏。
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: c程序入口
* 形 参: 无
* 返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
uint16_t ucBright; /* 背光亮度(0-255) */
uint8_t ucKeyCode; /* 按键代码 */
uint8_t fRefresh; /* 刷屏请求标志,1表示需要刷新 */
FONT_T tFont; /* 定义一个字体结构体变量,用于设置字体参数 */
char buf[];
uint16_t usAdcX, usAdcY;
int16_t tpX, tpY;
uint8_t ucTouch; /* 触摸事件 */ /* 设置字体参数 */
{
tFont.FontCode = FC_ST_16; /* 字体代码 16点阵 */
tFont.FrontColor = CL_WHITE; /* 字体颜色 */
tFont.BackColor = CL_BLUE; /* 文字背景颜色 */
tFont.Space = ; /* 文字间距,单位 = 像素 */
} bsp_Init(); /* 硬件初始化 */
PrintfLogo(); /* 打印例程名称和版本等信息 */
PrintfHelp(); /* 打印操作提示 */ /* 延迟200ms再点亮背光,避免瞬间高亮 */
bsp_DelayMS(); DispFirstPage(); /* 显示第1页 */ /* 界面整体显示完毕后,再打开背光,设置为缺省亮度 */
bsp_DelayMS();
ucBright = BRIGHT_DEFAULT;
LCD_SetBackLight(ucBright); bsp_StartAutoTimer(, ); /* 启动1个200ms的自动重装的定时器,软件定时器0 */ /* 进入主程序循环体 */
fRefresh = ;
while ()
{
bsp_Idle(); /* 判断软件定时器0是否超时 */
if(bsp_CheckTimer())
{
/* 每隔200ms 进来一次 */
bsp_LedToggle();
} if (fRefresh)
{
fRefresh = ; /* 实时刷新触摸ADC采样值和转换后的坐标 */
{ /* 读取并显示当前X轴和Y轴的ADC采样值 */
usAdcX = TOUCH_ReadAdcX();
usAdcY = TOUCH_ReadAdcY();
sprintf(buf, "触摸ADC值 X = %4d, Y = %4d ", usAdcX, usAdcY);
LCD_DispStr(, , buf, &tFont); /* 读取并显示当前触摸坐标 */
tpX = TOUCH_GetX();
tpY = TOUCH_GetY();
sprintf(buf, "触摸坐标 X = %4d, Y = %4d ", tpX, tpY);
LCD_DispStr(, , buf, &tFont); /* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{
LCD_DrawCircle(tpX, tpY, , CL_YELLOW);
}
} /* 在屏幕边沿绘制2个矩形框(用于检测面板边缘像素是否正常) */
LCD_DrawRect(, , LCD_GetHeight(), LCD_GetWidth(), CL_WHITE);
LCD_DrawRect(, , LCD_GetHeight() - , LCD_GetWidth() - , CL_YELLOW); /* 显示背光值 */
sprintf(buf, "当前背光亮度: %3d", ucBright);
LCD_DispStr(, , buf, &tFont);
} ucTouch = TOUCH_GetKey(&tpX, &tpY); /* 读取触摸事件 */
if (ucTouch != TOUCH_NONE)
{
switch (ucTouch)
{
case TOUCH_DOWN: /* 触笔按下事件 */ /* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{
LCD_DrawCircle(tpX, tpY, , CL_RED);
}
break; case TOUCH_MOVE: /* 触笔移动事件 */
/* 实时刷新触摸ADC采样值和转换后的坐标 */
{
/* 读取并显示当前X轴和Y轴的ADC采样值 */
usAdcX = TOUCH_ReadAdcX();
usAdcY = TOUCH_ReadAdcY();
sprintf(buf, "触摸ADC值 X = %4d, Y = %4d ", usAdcX, usAdcY);
LCD_DispStr(, , buf, &tFont); /* 读取并显示当前触摸坐标 */
//tpX = TOUCH_GetX();
//tpY = TOUCH_GetY();
sprintf(buf, "触摸坐标 X = %4d, Y = %4d ", tpX, tpY);
LCD_DispStr(, , buf, &tFont); /* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{
LCD_DrawCircle(tpX, tpY, , CL_YELLOW);
}
}
break; case TOUCH_RELEASE: /* 触笔释放事件 */
/* 在触笔所在位置显示一个小圈 */
if ((tpX > ) && (tpY > ))
{
LCD_DrawCircle(tpX, tpY, , CL_WHITE);
}
break;
}
} ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
/* 有键按下 */
switch (ucKeyCode)
{
case JOY_DOWN_L: /* 摇杆LEFT键按下, 2点触摸校准 */
TOUCH_Calibration();
DispFirstPage();
fRefresh = ; /* 请求刷新LCD */
break; case JOY_DOWN_OK: /* 摇杆OK键,清屏 */
DispFirstPage();
fRefresh = ; /* 请求刷新LCD */
break; case JOY_DOWN_U: /* 摇杆UP键按下 */
ucBright += BRIGHT_STEP;
if (ucBright > BRIGHT_MAX)
{
ucBright = BRIGHT_MAX;
}
LCD_SetBackLight(ucBright);
fRefresh = ; /* 请求刷新LCD */
break; case JOY_DOWN_D: /* 摇杆DOWN键按下 */
if (ucBright < BRIGHT_STEP)
{
ucBright = ;
}
else
{
ucBright -= BRIGHT_STEP;
}
LCD_SetBackLight(ucBright);
fRefresh = ; /* 请求刷新LCD */
break; default:
break;
}
}
}
}

LCD主界面显示内容如下:

/*
*********************************************************************************************************
* 函 数 名: DispFirstPage
* 功能说明: 显示操作提示
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
static void DispFirstPage(void)
{
FONT_T tFont; /* 定义一个字体结构体变量,用于设置字体参数 */
uint16_t y; /* Y坐标 */
uint16_t usLineCap; /* 行高 */
char buf[]; LCD_ClrScr(CL_BLUE); /* 清屏,背景蓝色 */ /* 设置字体属性 */
tFont.FontCode = FC_ST_16; /* 字体选择宋体16点阵,高16x宽15) */
tFont.FrontColor = CL_WHITE; /* 字体颜色设置为白色 */
tFont.BackColor = CL_MASK; /* 文字背景颜色,透明 */
tFont.Space = ; /* 字符水平间距, 单位 = 像素 */ y = ;
usLineCap = ; /* 行间距 */
LCD_DispStr(, y, "安富莱STM32-V7开发板 www.armfly.com", &tFont);
y += usLineCap;
LCD_DispStr(, y, "电阻屏和电容屏实验,电容屏无需校准,电阻需要校准", &tFont); y += * usLineCap;
LCD_DispStr(, y, "操作提示:", &tFont); y += usLineCap;
LCD_DispStr(, y, "摇杆上键 = 增加背光亮度", &tFont); y += usLineCap;
LCD_DispStr(, y, "摇杆下键 = 降低背光亮度", &tFont); y += usLineCap;
LCD_DispStr(, y, "摇杆左键 = 电阻屏触摸校准", &tFont); y += usLineCap;
LCD_DispStr(, y, "摇杆OK键 = 清屏", &tFont); y += * usLineCap; /* 显示TFT控制器型号和屏幕分辨率 */
//LCD_GetChipDescribe(buf); /* 读取TFT驱动芯片型号 */
if (g_TouchType == CT_GT811)
{
strcpy(buf, "STM32H743 + GT811");
}
else if (g_TouchType == CT_GT911)
{
strcpy(buf, "STM32H743 + GT911");
}
else if (g_TouchType == CT_FT5X06)
{
strcpy(buf, "STM32H743 + FT5X06");
}
else if (g_TouchType == CT_STMPE811)
{
strcpy(buf, "STM32H743 + STMPE811");
}
else
{
strcpy(buf, "STM32H743 + NoTouch");
}
sprintf(&buf[strlen(buf)], " %d x %d", LCD_GetWidth(), LCD_GetHeight());
LCD_DispStr(, y, (char *)buf, &tFont);
}

54.12   总结

本章节为大家讲解的电阻触摸方案和电容触摸方案都是经过实战总结的,实际项目中比较实用。

上一篇:Hibernate配置文件的hbm2ddl.auto属性


下一篇:python基础——sorted()函数