第二十七章 触摸屏实验
本章将介绍如何使用ESP32-P4来驱动触摸屏,ESP32-P4本身并没有触摸屏控制器,但是它支持触摸屏,可以通过外接带触摸屏的LCD模块(比如正点原子RGBLCD和MIPILCD),来实现触摸屏控制。在本章中,我们将向大家介绍ESP32-P4控制正点原子LCD模块,实现触摸屏驱动,最终实现一个手写板的功能。
本章分为如下几个小节:
27.1 触摸屏介绍
27.2 硬件设计
27.3 程序设计
27.4 下载验证
27.1 触摸屏介绍
触摸屏是在显示屏的基础上,在屏幕或屏幕上方分布一层与屏幕大小相近的传感器形成的组合器件。触摸和显示功能由软件控制,可以独立也可以组合实现,用户可以通过侦测传感器的触点再配合相应的软件实现触摸效果。目前最常用的触摸屏有两种,电阻式触摸屏和电容式触摸屏。
由于ESP32-P4开发板支持使用的RGBLCD和MIPILCD,这两种类型的屏幕带的都是电容式触摸屏,没有电阻式触摸屏,只有正点原子的MCU屏才会涉及到电阻触摸屏。对于电阻式触摸屏的介绍,大家请看正点原子STM32开发板的触摸屏实验章节即可。
现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏,电容屏是利用人体感应进行触点检测控制,不需要直接接触或只需要轻微接触,通过检测感应电流来定位触摸坐标。正点原子RGBLCD和MIPILCD模块自带的触摸屏采用的是电容式触摸屏,下面简单介绍下电容式触摸屏的原理。
电容式触摸屏主要分为两种:
1、表面电容式电容触摸屏。
表面电容式触摸屏技术是利用ITO(铟锡氧化物,是一种透明的导电材料)导电膜,通过电场感应方式感测屏幕表面的触摸行为进行。但是表面电容式触摸屏有一些局限性,它只能识别一个手指或者一次触摸。
2、投射式电容触摸屏。
投射电容式触摸屏是传感器利用触摸屏电极发射出静电场线。一般用于投射电容传感技术的电容类型有两种:自我电容和交互电容。
自我电容又称绝对电容,是最广为采用的一种方法,自我电容通常是指扫描电极与地构成的电容。在玻璃表面有用ITO制成的横向与纵向的扫描电极,这些电极和地之间就构成一个电容的两极。当用手或触摸笔触摸的时候就会并联一个电容到电路中去,从而使在该条扫描线上的总体的电容量有所改变。在扫描的时候,控制IC依次扫描纵向和横向电极,并根据扫描前后的电容变化来确定触摸点坐标位置。笔记本电脑触摸输入板就是采用的这种方式,笔记本电脑的输入板采用X*Y的传感电极阵列形成一个传感格子,当手指靠近触摸输入板时,在手指和传感电极之间产生一个小量电荷。采用特定的运算法则处理来自行、列传感器的信号来确定手指的位置。
交互电容又叫做跨越电容,它是在玻璃表面的横向和纵向的ITO电极的交叉处形成电容。交互电容的扫描方式就是扫描每个交叉处的电容变化,来判定触摸点的位置。当触摸的时候就会影响到相邻电极的耦合,从而改变交叉处的电容量,交互电容的扫面方法可以侦测到每个交叉点的电容值和触摸后电容变化,因而它需要的扫描时间与自我电容的扫描方式相比要长一些,需要扫描检测X*Y根电极。目前智能手机/平板电脑等的触摸屏,都是采用交互电容技术。
正点原子所选择的电容触摸屏,也是采用的是投射式电容屏(交互电容类型),所以后面仅以投射式电容屏作为介绍。
投射式电容触摸屏采用纵横两列电极组成感应矩阵,来感应触摸。以两个交叉的电极矩阵,即:X轴电极和Y轴电极,来检测每一格感应单元的电容变化,如图27.1.1所示:

图27.1.1 投射式电容屏电极矩阵示意图
示意图中的电极,实际是透明的,这里是为了方便大家理解。图中,X、Y轴的透明电极电容屏的精度、分辨率与X、Y轴的通道数有关,通道数越多,精度越高。以上就是电容触摸屏的基本原理,接下来看看电容触摸屏的优缺点:
电容触摸屏的优点:手感好、无需校准、支持多点触摸、透光性好。
电容触摸屏的缺点:成本高、精度不高、抗干扰能力差。
这里特别提醒大家电容触摸屏对工作环境的要求是比较高的,在潮湿、多尘、高低温环境下面,都是不适合使用电容屏的。
电容触摸屏一般都需要一个驱动IC来检测电容触摸,正点原子的电容触摸屏使用的是IIC接口输出触摸数据的触摸芯片。下面通过一个表格了解一下电容触摸屏对应的触摸IC情况,如下表所示。

表27.1.1 正点原子电容触摸屏触摸IC情况
只要触摸屏驱动IC是汇顶系列(GTXXX),其驱动方式差不多的。若是不同系列的芯片,驱动方式就会存在比较大的区别了。在本章,以正点原子7寸RGBLCD模块的电容触摸屏为例来介绍一下。7寸RGBLCD模块的电容触摸屏采用的是15*10的驱动结构(10个感应通道,15个驱动通道),采用的是GT911/FT5426作为驱动IC。这里我们以GT911为例给大家做介绍,其他的大家参考着学习即可。
GT911与MCU通过4根线连接:SDA、SCL、RST和INT。GT911的7位IIC设备地址,可以是0X14或者0X5D,当复位结束后的5ms内,如果INT是高电平,则使用0X14作为地址,否则使用0X5D作为地址,具体的设置过程,请看:GT911数据手册.pdf这个文档。本章我们使用0X14作为器件地址(不含最低位,换算成读写命令则是读:0X29,写:0X28),接下来,介绍一下GT911的几个重要的寄存器。
1,控制命令寄存器(0X8040)
该寄存器可以写入不同值,实现不同的控制,我们一般使用0和2这两个值,写入2,即可软复位GT911。在硬复位之后,一般要往该寄存器写2,实行软复位。然后,写入0,即可正常读取坐标数据(并且会结束软复位)。
2,配置寄存器组(0X8047~0X8100)
这里共186个寄存器,用于配置GT911的各个参数,这些配置一般由厂家提供给我们(一个数组),所以我们只需要将厂家给我们的配置,写入到这些寄存器里面,即可完成GT911的配置。由于GT911可以保存配置信息(可写入内部FLASH,从而不需要每次上电都更新配置),我们有几点注意的地方提醒大家:1,0X8047寄存器用于指示配置文件版本号,程序写入的版本号,必须大于等于GT911本地保存的版本号,才可以更新配置。2,0X80FF寄存器用于存储校验和,使得0X8047~0X80FF之间所有数据之和为0。3,0X8100用于控制是否将配置保存在本地,写0,则不保存配置,写1则保存配置。
3,产品ID寄存器(0X8140~0X8143)
这里总共由4个寄存器组成,用于保存产品ID,对于GT911,这4个寄存器读出来就是:9,1,1,?,四个字符(ASCII码格式)。因此,我们可以通过这4个寄存器的值,来判断驱动IC的型号,以便执行不同的初始化。
4,状态寄存器(0X814E)
该寄存器各位描述如表27.1.2所示:

表27.1.2 状态寄存器各位描述
这里,我们仅关心最高位和最低4位,最高位用于表示buffer状态,如果有数据(坐标/按键),buffer就会是1,最低4位用于表示有效触点的个数,范围是:0~5,0,表示没有触摸,5表示有5点触摸。最后,该寄存器在每次读取后,如果bit7有效,则必须写0,清除这个位,否则不会输出下一次数据!!这个要特别注意!!!
5,坐标数据寄存器(共30个)
这里共分成5组(5个点),每组6个寄存器存储数据,以触点1的坐标数据寄存器组为例,如表27.1.3所示:

表27.1.3 触点1坐标寄存器组描述
我们一般只用到触点的x,y坐标,所以只需要读取0X8150~0X8153的数据,组合即可得到触点坐标。其他4组分别是:0X8158、0X8160、0X8168和0X8170等开头的16个寄存器组成,分别针对触点2~4的坐标。同样GT911也支持寄存器地址自增,我们只需要发送寄存器组的首地址,然后连续读取即可,GT911会自动地址自增,从而提高读取速度。
GT911相关寄存器的介绍就介绍到这里,更详细的资料,请参考:GT9147编程指南.pdf 这个文档。
GT911只需要经过简单的初始化就可以正常使用了,初始化流程:硬复位à延时10msà结束硬复位à设置IIC地址à延时100msà软复位à更新配置(需要时)à结束软复位。此时GT911即可正常使用了。然后,我们不停的查询0X814E寄存器,判断是否有有效触点,如果有,则读取坐标数据寄存器,得到触点坐标。特别注意,如果0X814E读到的值最高位为1,就必须对该位写0,否则无法读到下一次坐标数据。
电容式触摸屏部分,就介绍到这里。
27.2 硬件设计
27.2.1 例程功能
经过一系列的初始化之后,进入电容触摸屏测试程序,用户可在画板上绘画字符、线条等,在测试界面的右上角会有一个清空的操作区域(RST),点击这个地方就会将输入全部清除,恢复白板状态。
27.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)KEY按键
BOOT - IO35
3)RGBLCD / MIPILCD(引脚太多,不罗列出来)
4)触摸屏
CT_INT - IO21
CT_RST - IO45
CT_SCL - IO32
CT_SDA - IO33
27.2.3 原理图
RGBLCD的触摸屏接口在RGBLCD接口上,而MIPILCD的触摸屏接口在MIPILCD接口上,RGBLCD接口和MIPILCD接口如下图所示。


图27.2.3.1 RGBLCD接口和MIPILCD接口
27.3 程序设计
IIC外设驱动已经在第十九章19.3.1小节做了说明,这里就不再赘述了。
27.3.1 程序流程图

图27.3.1.1 触摸屏实验程序流程图
27.3.2 程序解析
在18_touch_screen例程中,作者在18_touch_screen \components\BSP路径下新增了1个文件夹TOUCH,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1. TOUCH驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。TOUCH驱动源码包括四个文件:touch.c、touch.h、gt9xxx.c和gt9xxx.h。
由于正点原子有比较多款屏幕模块,为了解决多种驱动芯片问题,touch.c和touch.h两个文件主要用来管理各类型的驱动。不同的驱动芯片类型可以在touch.c中集中添加,并通过touch.c中的接口统一调用,不同的触摸芯片各自编写独立的.c/.h文件,需要时被touch.c调用。
1.触摸管理驱动代码
因为需要支持的触摸驱动比较多,为了方便管理和添加新的驱动,我们用touch.c文件来统一管理这些触摸驱动,然后针对各类触摸芯片编写独立的驱动。为了方便管理触摸,我们在touch.h中定义一个用于管理触摸信息的结构体类型,具体代码如下:
/* 触摸屏控制器 */
typedef struct
{
esp_err_t (*init)(void); /* 初始化触摸屏控制器 */
uint8_t (*scan)(uint8_t); /* 扫描触摸屏.0,屏幕扫描;1,物理坐标; */
uint16_t x[CT_MAX_TOUCH]; /* 当前坐标 */
uint16_t y[CT_MAX_TOUCH]; /* 电容屏有最多10组坐标,
* x[9],y[9]存储第一次按下时的坐标.
*/
uint16_t sta; /* 笔的状态
* b15:按下1/松开0;
* b14:0,没有按键按下;1,有按键按下.
* b13~b10:保留
* b9~b0:电容触摸屏按下的点数(0,表示未按下,1表示按下)
*/
/* 新增的参数,当触摸屏的左右上下完全颠倒时需要用到.
* b0:0, 竖屏(适合左右为X坐标,上下为Y坐标的TP)
* 1, 横屏(适合左右为Y坐标,上下为X坐标的TP)
* b1~6: 保留.
* b7:0, 电阻屏
* 1, 电容屏
*/
uint8_t touchtype;
} _m_tp_dev;
这里我们定义了函数指针,只要把相对应的触摸芯片的函数指针赋值给它,就可以通过这个通用接口很方便调用不同芯片的函数接口。
(*init)(void)这个结构体函数指针,默认指向tp_init,而在tp_init里面对触摸屏进行初始化并对(*scan)(uint8_t)函数指针做了指向。在这里简单看一下touch.c的触摸屏初始化函数tp_init,其代码如下:
/**
* @brief 触摸屏初始化
* @param 无
* @retval ESP_OK,触摸屏初始化成功
* 其他,触摸屏有问题
*/
esp_err_t tp_init(void)
{
tp_dev.touchtype = 0; /* 默认设置竖屏 */
tp_dev.touchtype |= lcddev.dir & 0X01; /* 根据LCD判定是横屏还是竖屏 */
gt9xxx_init(); /* 初始化gt9xxx器件 */
tp_dev.scan = gt9xxx_scan; /* 扫描函数指向GT触摸屏扫描 */
tp_dev.touchtype |= 0X80; /* 电容屏 */
return ESP_OK;
}
通过上面的触摸初始化后,就可以读取相关的触点信息用于显示编程了。
2.电容屏触摸驱动代码
电容触摸芯片使用的是IIC接口,与其他IIC接口设备一起共用。除了IIC接口相关引脚IIC_SCL和IIC_SDA,还有CT_INT和CT_RST。
gt9xxx_init的实现也比较简单,代码如下:
/**
* @brief 初始化gt9xxx触摸屏
* @param 无
* @retval 0, 初始化成功; 1, 初始化失败;
*/
esp_err_t gt9xxx_init(void)
{
uint8_t temp[5];
/* 未调用myiic_init初始化IIC */
if (bus_handle == NULL)
{
ESP_ERROR_CHECK(myiic_init());
}
i2c_device_config_t gt9xxx_i2c_dev_conf = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7, /* 从机地址长度 */
.scl_speed_hz = IIC_SPEED_CLK, /* 传输速率 */
.device_address = GT9XXX_DEV_ID, /* 从机7位的地址 */
};
/* I2C总线上添加gt9xxx设备 */
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, >9xxx_i2c_dev_conf,
>9xxx_handle));
/* 以下是对CT_RST和CT_INT引脚做配置,同时会产生的时序决定了驱动IC的器件地址为0x14(某些芯片还会有另外一种时序,会使器件地址为0x5D) */
gpio_config_t gpio_init_struct = {0};
gpio_init_struct.intr_type = GPIO_INTR_DISABLE; /* 失能引脚中断 */
gpio_init_struct.mode = GPIO_MODE_OUTPUT; /* 输出模式 */
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; /* 使能上拉 */
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 失能下拉 */
gpio_init_struct.pin_bit_mask = 1ull << GT9XXX_RST_GPIO_PIN; /* 引脚掩码 */
gpio_config(&gpio_init_struct); /* 配置复位引脚 */
gpio_init_struct.mode = GPIO_MODE_INPUT; /* 输入模式 */
gpio_init_struct.pin_bit_mask = 1ull << GT9XXX_INT_GPIO_PIN; /* 引脚掩码 */
gpio_config(&gpio_init_struct); /* 配置中断引脚 */
CT_RST(0); /* 复位 */
vTaskDelay(10);
CT_RST(1); /* 释放复位 */
vTaskDelay(10);
gpio_init_struct.mode = GPIO_MODE_INPUT; /* 输入输出模式 */
gpio_init_struct.pull_up_en = GPIO_PULLUP_DISABLE; /* 失能上拉 */
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 失能下拉 */
gpio_init_struct.pin_bit_mask = 1ull << GT9XXX_INT_GPIO_PIN; /* 引脚掩码 */
gpio_config(&gpio_init_struct); /* 配置中断引脚 */
vTaskDelay(100);
gt9xxx_rd_reg(GT9XXX_PID_REG, temp, 4); /* 读取产品ID */
temp[4] = 0;
/* 判断一下是否是特定的触摸屏 */
if (strcmp((char *)temp, "911") && strcmp((char *)temp, "9147") &&
strcmp((char *)temp, "1158") && strcmp((char *)temp, "9271") &&
strcmp((char *)temp, "928"))
{
/*不是触摸屏用到的GT911/9147/1158/9271/928初始化失败,需查看型号以及查看时序函数是否正确*/
return 1;
}
ESP_LOGI("GT9XXX", "CTP:%s", temp); /* 打印触摸屏驱动IC的ID */
if (strcmp((char *)temp, "9271") == 0) /* ID==9271, 支持10点触摸 */
{
g_gt_tnum = 10; /* 支持10点触摸屏 */
}
temp[0] = 0X02;
gt9xxx_wr_reg(GT9XXX_CTRL_REG, temp, 1); /* 软复位GT9XXX */
vTaskDelay(10);
temp[0] = 0X00;
gt9xxx_wr_reg(GT9XXX_CTRL_REG, temp, 1); /* 结束复位, 进入读坐标状态 */
return ESP_OK;
}
该函数实现调用myiic_init函数实现对IIC_SCL和IIC_SDA初始化和CT_INT和CT_RST引脚初始化。函数中CT_INT和CT_RST的时序可决定GT9xxx设备地址,按上面的时序,GT9xxx设备地址为0x14。通过封装IIC底层读写接口编写gt9xxx_rd_reg和gt9xxx_wr_reg函数,用于读写GT9xxx设备的寄存器。上述代码也有读取触摸IC的ID操作,便于我们第一时间判断通信是否正常。由于电容触摸屏在设计时是根据屏幕进行参数设计的,参数已经保存在芯片内部,所在在初始化后,便可以通过IIC读函数从相对应的坐标数据寄存器中把对应的XY坐标数据读出来,再通过数据整理转成LCD坐标。
GT9xxx系列触摸IC时支持中断或轮询方式得到触摸状态,本实验使用的是轮询方式:
1、按照读时序,先读取寄存器0x814E,若当前buffer(buffer status为1)数据准备好,则依据有效触点个数到相对应的坐标数据地址处进行坐标数据读取。
2、若在1中发现buffer数据(buffer status为0)未准备好,则等待1ms再进行读取。
这里,gt9xxx_scan函数的实现如下:
/* GT9XXX 10个触摸点(最多) 对应的寄存器表 */
const uint16_t GT9XXX_TPX_TBL[10] =
{
GT9XXX_TP1_REG, GT9XXX_TP2_REG, GT9XXX_TP3_REG, GT9XXX_TP4_REG, GT9XXX_TP5_REG,
GT9XXX_TP6_REG, GT9XXX_TP7_REG, GT9XXX_TP8_REG, GT9XXX_TP9_REG, GT9XXX_TP10_REG,
};
/**
* @brief 扫描触摸屏(采用查询方式)
* @param mode : 电容屏未用到此参数
* @retval 当前触屏状态
* @arg 0, 触屏无触摸;
* @arg 1, 触屏有触摸;
*/
uint8_t gt9xxx_scan(uint8_t mode)
{
uint8_t buf[4];
uint8_t i = 0;
uint8_t res = 0;
uint16_t temp;
uint16_t tempsta;
static uint8_t t = 0; /* 控制查询间隔,从而降低CPU占用率 */
t++;
/* 空闲时,每进入10次CTP_Scan函数才检测1次,从而节省CPU使用率 */
if ((t % 10) == 0 || t < 10)
{
gt9xxx_rd_reg(GT9XXX_GSTID_REG, &mode, 1); /* 读取触摸点的状态 */
if ((mode & 0X80) && ((mode & 0XF) <= g_gt_tnum))
{
i = 0;
gt9xxx_wr_reg(GT9XXX_GSTID_REG, &i, 1); /* 清标志 */
}
if ((mode & 0XF) && ((mode & 0XF) <= g_gt_tnum))
{ /* 将点的个数转换为1的位数,匹配tp_dev.sta定义 */
temp = 0XFFFF << (mode & 0XF);
tempsta = tp_dev.sta; /* 保存当前的tp_dev.sta值 */
tp_dev.sta = (~temp) | TP_PRES_DOWN | TP_CATH_PRES;
tp_dev.x[g_gt_tnum - 1] = tp_dev.x[0]; /* 保存点0数据,在最后一个上 */
tp_dev.y[g_gt_tnum - 1] = tp_dev.y[0];
for (i = 0; i < g_gt_tnum; i++)
{
if (tp_dev.sta & (1 << i)) /* 触摸有效? */
{
gt9xxx_rd_reg(GT9XXX_TPX_TBL[i], buf, 4); /* 读取XY坐标值 */
if (lcddev.id <= 0x7084) /* RGB屏触摸屏 */
{
if (rgbdev.dir == 1) /* 横屏 */
{
tp_dev.x[i] = ((uint16_t)buf[1] << 8) + buf[0];
tp_dev.y[i] = ((uint16_t)buf[3] << 8) + buf[2];
}
else /* 竖屏 */
{
tp_dev.x[i] = lcddev.width –
(((uint16_t)buf[3] << 8) + buf[2]);
tp_dev.y[i] = ((uint16_t)buf[1] << 8) + buf[0];
}
}
else /* MIPI屏触摸屏 */
{
if (lcddev.id == 0x9881) /* 10.1寸MIPI屏触摸屏 */
{
tp_dev.x[i] = ((uint16_t)buf[1] << 8) + buf[0];
tp_dev.y[i] = ((uint16_t)buf[3] << 8) + buf[2];
}
else /* 5.5寸MIPI屏触摸屏 */
{
tp_dev.x[i] = lcddev.width –
(((uint16_t)buf[1] << 8) + buf[0]);
tp_dev.y[i] = lcddev.height –
(((uint16_t)buf[3] << 8) + buf[2]);
}
}
}
// ESP_LOGI("GT9XXX", "x[%d]:%d,y[%d]:%d\r\n", i, tp_dev.x[i], i, tp_dev.y[i]);
}
res = 1;
if (tp_dev.x[0] > lcddev.width || tp_dev.y[0] > lcddev.height)
{ /* 非法数据(坐标超出了) */
if ((mode & 0XF) > 1) /* 其他点有数据,则复第二个点的数据到第一个点 */
{
tp_dev.x[0] = tp_dev.x[1];
tp_dev.y[0] = tp_dev.y[1];
t = 0; /* 触发一次,则会连续监测10次,从而提高命中率 */
}
else /* 非法数据,则忽略此次数据(还原原来的) */
{
tp_dev.x[0] = tp_dev.x[g_gt_tnum - 1];
tp_dev.y[0] = tp_dev.y[g_gt_tnum - 1];
mode = 0X80;
tp_dev.sta = tempsta; /* 恢复tp_dev.sta */
}
}
else
{
t = 0;
}
}
}
if ((mode & 0X8F) == 0X80) /* 无触摸点按下 */
{
if (tp_dev.sta & TP_PRES_DOWN) /* 之前是被按下的 */
{
tp_dev.sta &= ~TP_PRES_DOWN; /* 标记按键松开 */
}
else /* 之前就没有被按下 */
{
tp_dev.x[0] = 0xffff;
tp_dev.y[0] = 0xffff;
tp_dev.sta &= 0XE000; /* 清除点有效标记 */
}
}
if (t > 240)
{
t = 10; /* 重新从10开始计数 */
}
return res;
}
可以打开gt9xxx芯片对应的编程手册,对照时序,即可理解上述的实现过程,只是程序中为了匹配多种屏幕和横屏显示,添加了一些代码。
电容屏的触摸实验代码讲解到这里。
2. CMakeLists.txt文件
本例程的功能实现主要依靠TOUCH驱动。要在main函数中,成功调用TOUCH文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
set(src_dirs
LED
LCD
MYIIC
TOUCH)
set(include_dirs
LED
LCD
MYIIC
TOUCH)
set(requires
driver
esp_lcd
esp_common)
idf_component_register( SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
3. main.c驱动代码
在main.c里面编写如下代码。
void ctp_test(void)
{
uint8_t t = 0;
uint16_t lastpos[10][2]; /* 最后一次的数据 */
uint8_t maxp = 5;
while (1)
{
tp_dev.scan(0);
for (t = 0; t < maxp; t++)
{
if ((tp_dev.sta) & (1 << t))
{
if (tp_dev.x[t] < lcddev.width && tp_dev.y[t] < lcddev.height)
{ /* 坐标在屏幕范围内 */
if (lastpos[t][0] == 0xFFFF)
{
lastpos[t][0] = tp_dev.x[t];
lastpos[t][1] = tp_dev.y[t];
}
lcd_draw_bline(lastpos[t][0], lastpos[t][1],
tp_dev.x[t], tp_dev.y[t], 4, POINT_COLOR_TBL[t]); /* 画线 */
lastpos[t][0] = tp_dev.x[t];
lastpos[t][1] = tp_dev.y[t];
if (tp_dev.x[t] > (lcddev.width - 48) && tp_dev.y[t] < 40)
{
load_draw_dialog();
}
}
}
else
{
lastpos[t][0] = 0xFFFF;
}
}
vTaskDelay(pdMS_TO_TICKS(5));
}
}
void app_main(void)
{
esp_err_t ret;
ret = nvs_flash_init(); /* 初始化NVS */
if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
led_init(); /* LED初始化 */
lcd_init(); /* LCD屏初始化 */
tp_dev.init(); /* 初始化触摸屏 */
lcd_show_string(30, 50, 200, 16, 16, "ESP32-P4", RED);
lcd_show_string(30, 70, 200, 16, 16, "TOUCH TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
vTaskDelay(1500);
load_draw_dialog();
ctp_test(); /* 电容触摸屏测试 */
}
上面没有main.c全部代码罗列出来,只是列出重要函数,这里简单介绍一下这两个函数,
ctp_test函数用于电容触摸屏的测试,由于我们采用tp_dev.sta来标记当前按下的触摸屏点数,所以判断是否有电容触摸屏按下,也就是判断tp_dev.sta的最低5位。如果有数据,则画线,如果没有数据则忽略。为了方便区分,5个点画线的颜色各不一样。如果按中“RST”区域,则执行清屏。
在app_main函数中,完成所需外设初始化以后,直接执行电容触摸屏测试代码。
27.4 下载验证
在代码编译成功后,下载代码到开发板上,测试界面如图27.4.1所示。

图27.4.1 触摸屏测试界面
图中,作者在触摸屏上绘画了“ALIENTEK”字符串。按下右上角的“RST”标志,便可以清屏。