历史上的今天
返回首页

历史上的今天

今天是:2026年03月23日(星期一)

正在发生

2023年03月23日 | STM32CubeMX系列 | 触摸屏

2023-03-23 来源:zhihu

1.触摸屏简介

目前最常用的触摸屏有两种:电阻式触摸屏和电容式触摸屏

1.1 电阻式触摸屏

电阻式的触摸屏结构如下图示,它主要由表面硬涂层、两个ITO层、间隔点以及玻璃底层构成,这些结构层都是透明的,整个触摸屏覆盖在液晶面板上,透过触摸屏可看到液晶面板。表面涂层起到保护作用,玻璃底层起承载的作用,而两个ITO层是触摸屏的关键结构,它们是涂有铟锡金属氧化物的导电层。两个ITO层之间使用间隔点使两层分开,当触摸屏表面受到压力时,表面弯曲使得上层ITO与下层ITO接触,在触点处连通电路。

两个ITO涂层的两端分别引出X-、X+、Y-、Y+四个电极,这是电阻屏最常见的四线结构,通过这些电极,外部电路向这两个涂层可以施加匀强电场或检测电压。

电阻触摸屏的校准:确定位置和对应的电压值之间的比例关系

X0 = Xfac * ADC_X + Xoff
Y0 = Yfac * ADC_Y + Yoff

电阻触摸屏的优缺点

- 优点:精度高、价格便宜、抗干扰能力强、稳定性好
- 缺点:容易被划伤、透光性不太好、不支持多点触控

触摸屏都需要一个AD转换器来将电压变化读取出来,供主机算出触摸的位置。本例程中的TFTLCD模块使用的是四线电阻式触摸屏,触摸屏控制芯片为XPT2046,XPT2046是一款4导线制触摸屏控制器,采用SPI模式进行通讯,内含12位分辨率125KHz转换速率逐步逼近型A/D转换器。XPT2046引脚图(SOP-16封装)及引脚说明如下图示

1.2 电容式触摸屏

与电阻式触摸屏不同,电容式触摸屏不需要通过压力使触点变形。它的基本原理是利用充电时间检测电容大小,若手指触摸屏幕,会影响触摸点附近两个电极之间的耦合,从而改变两个电极之间的电容量,若检测到某电容的电容量发生了改变,即可获知该电容处有触摸动作从而通过检测出电容值的变化来获知触摸信号。

由于本例程使用的是电阻式触摸屏,这里电容式触摸屏不做详细介绍

2. 硬件设计

D1指示灯用来提示系统运行状态,K_UP按键用来强制校准电阻触摸屏(电容屏无需校准),AT24C02用来存储电阻触摸屏校准数据,TFTLCD模块用来显示触摸

  • D1指示灯

  • K_UP按键

  • TFTLCD模块

  • AT24C02

  • USART1

3. 软件设计

3.1 STM32CubeMX设置

  • RCC设置外接HSE,时钟设置为72M

  • PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平

  • PA0设置为GPIO输入模式、下拉模式

  • PD6设置为GPIO输出模式(TOUCH_CS);PD7设置为GPIO输入模式(TOUCH_PEN)

  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位

  • 激活I2C2,选择标准传输模式,选择7位寻址地址,其余默认设置

  • 激活FSMC,详细请参考TFTLCD显示章节的设置

  • 激活SPI1,不开启NSS,数据长度8位,MSB先输出,分频因子16,CPOL为HIGH,CPHA为第二个边沿,不开启CRC检验,NSS为软件控制

  • 输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码

3.2 MDK-ARM编程

  • 添加按键驱动文件key.c和key.h,参考按键输入例程

  • 添加AT24C02驱动文件24cxx.c和24cxx.h,参考I2C总线例程

  • 添加TFTLCD驱动文件tftlcd.c 和tftlcd.h,参考TFTLCD显示例程

  • 添加XPT2046触摸芯片驱动文件touch.c和touch.h 两个重要的结构体:

typedef struct{     /*存储触点读取到的数据*/

    uint16_t x;     //x轴物理坐标值

    uint16_t y;     //y轴物理坐标值

    uint16_t lcdx;  //x轴彩屏坐标值

    uint16_t lcdy;  //y轴彩屏坐标值

}TouchTypeDef;


typedef struct{     /*保存校正因素*/

  uint8_t posState; //校正参数标志

  int16_t xOffset;  //x轴偏移量

  int16_t yOffset;  //y轴偏移量

  float   xFactor;  //x轴比例因数

  float   yFactor;  //y轴比例因数

}PosTypeDef;

触摸屏初始化及读取物理坐标(ADC值)函数:

void TOUCH_Init(void){

    //读取AT24C02的TOUCH_ADJ_ADDR地址处的值

    HAL_I2C_Mem_Read(&hi2c2,ADDR_24CXX_READ,TOUCH_ADJ_ADDR,I2C_MEMADD_SIZE_8BIT,&TouchAdj.posState,sizeof(TouchAdj),0xff);  

    if(TouchAdj.posState != TOUCH_ADJ_OK){  //检查是否有校正数据

        TOUCH_Adjust(); //如果没有校正数据,则启动校正函数  

    }

}

/*封装SPI读取数据函数*/

uint8_t WR_Cmd(uint8_t cmd){

    uint8_t Tx_DATA = cmd;

    uint8_t Rx_DATA = 0;

    HAL_SPI_TransmitReceive(&hspi1,&Tx_DATA,&Rx_DATA,1,500);

    return Rx_DATA;     

}

/*读取X轴或Y轴的ADC值,并进行滤波处理*/

uint16_t TOUCH_Read_AD(uint8_t cmd){

    uint8_t i, j;

    uint16_t NUMH,NUML;

    uint16_t NUM[TOUCH_READ_TIMES] = {0};

    uint16_t temp,value;

    uint32_t totalValue;    //注意数据类型


    for(i=0; i        TOUCH_CS_LOW(); //开始SPI通讯   

        WR_Cmd(cmd);

        NUMH = WR_Cmd(0XFF);

        NUML = WR_Cmd(0XFF);

        NUM[i] = (NUMH<<8)+ NUML;

        NUM[i] >>= 3;   //最低三位无用    

        TOUCH_CS_HIGH();    //结束SPI通讯

    }

    //滤波处理:1.从大到小排序

    for(i=0; i<(TOUCH_READ_TIMES - 1); i++){

        for(j=i+1; j            if(NUM[i] < NUM[j]){

                temp = NUM[i];

                NUM[i] = NUM[j];

                NUM[j] = temp;

            }   

        }       

    }

    //滤波处理:2.去掉最大值和最小值,求余下的平均值

    j = TOUCH_READ_TIMES - 1;

    totalValue = 0;

    for(i=1; i        totalValue += NUM[i];

    }

    value = totalValue/(TOUCH_READ_TIMES - 2);  

    return value;

}

/*读取X轴和Y轴的ADC值,再次进行滤波*/

uint8_t TOUCH_ReadXY(uint16_t *xValue, uint16_t *yValue){   

    uint16_t xValue1, yValue1, xValue2, yValue2;

    xValue1 = TOUCH_Read_AD(TOUCH_X_CMD);

    yValue1 = TOUCH_Read_AD(TOUCH_Y_CMD);

    xValue2 = TOUCH_Read_AD(TOUCH_X_CMD);

    yValue2 = TOUCH_Read_AD(TOUCH_Y_CMD);

    //计算两点之间的采样差值

    if(xValue1 > xValue2)

        *xValue = xValue1 - xValue2;

    else

        *xValue = xValue2 - xValue1;


    if(yValue1 > yValue2)

        *yValue = yValue1 - yValue2;

    else

        *yValue = yValue2 - yValue1;

    //判断采样差值是否在可控范围内

    if((*xValue > TOUCH_MAX+0) || (*yValue > TOUCH_MAX+0))  

        return 0xFF;

    //求平均值

    *xValue = (xValue1 + xValue2) / 2;

    *yValue = (yValue1 + yValue2) / 2;

    //判断得到的值,是否在取指范围内,避免出现飞点现象  

    if((*xValue > TOUCH_X_MAX+0) || (*xValue < TOUCH_X_MIN) || (*yValue > TOUCH_Y_MAX+0) || (*yValue < TOUCH_Y_MIN))                 

        return 0xFF;


    return 0; 

}

触摸屏校准函数:

/*读校准点的物理坐标*/

uint8_t TOUCH_ReadAdjust(uint16_t x, uint16_t y, uint16_t *xValue, uint16_t *yValue){

    uint8_t i;

    uint32_t timeCont;


    LCD_Clear(BACK_COLOR);

    LCD_DrowSign(x, y, RED);    //x和y为指定的校准点的彩屏坐标值

    i = 0;

    while(1){   //读该指定校准点的物理坐标值

        if(!TOUCH_ReadXY(xValue, yValue)){

            i++;

            if(i > 10){

                LCD_DrowSign(x, y, BACK_COLOR);

                return 0;

            }               

        }


        timeCont++;

        if(timeCont > 0xFFFFFFFE){  //超时退出 

            LCD_DrowSign(x, y, BACK_COLOR); 

            return 0xFF;

        } 

    }       

}

/*触摸屏校准函数*/

void TOUCH_Adjust(void){

    uint16_t px[2], py[2], xPot[4], yPot[4];

    float xFactor, yFactor;

    //读取4个校准点的物理坐标值

    if(TOUCH_ReadAdjust(LCD_ADJX_MIN, LCD_ADJY_MIN, &xPot[0], &yPot[0]))

        return;

    HAL_Delay(500);

    if(TOUCH_ReadAdjust(LCD_ADJX_MIN, LCD_ADJY_MAX, &xPot[1], &yPot[1]))

        return;

    HAL_Delay(500);

    if(TOUCH_ReadAdjust(LCD_ADJX_MAX, LCD_ADJY_MIN, &xPot[2], &yPot[2]))

        return;

    HAL_Delay(500);

    if(TOUCH_ReadAdjust(LCD_ADJX_MAX, LCD_ADJY_MAX, &xPot[3], &yPot[3]))

        return; 

    HAL_Delay(500);

    //处理读取到的四个点的数据,整合成对角的两个点

    px[0] = (xPot[0] + xPot[1]) / 2;

    py[0] = (yPot[0] + yPot[2]) / 2;

    px[1] = (xPot[3] + xPot[2]) / 2;

    py[1] = (yPot[3] + yPot[1]) / 2;

    //求出比例因数

    xFactor = (float)LCD_ADJ_X / (px[1] - px[0]);

    yFactor = (float)LCD_ADJ_Y / (py[1] - py[0]);  

    //求出偏移量

    TouchAdj.xOffset = (int16_t)LCD_ADJX_MAX - ((float)px[1] * xFactor);

    TouchAdj.yOffset = (int16_t)LCD_ADJY_MAX - ((float)py[1] * yFactor);

    //将比例因数进行数据处理,并保存

    TouchAdj.xFactor = xFactor ;

    TouchAdj.yFactor = yFactor ;        

    TouchAdj.posState = TOUCH_ADJ_OK;

    //将得出的校正因数保存到AT24C02中的TOUCH_ADJ_ADDR地址处

    HAL_I2C_Mem_Write(&hi2c2,ADDR_24CXX_WRITE,TOUCH_ADJ_ADDR,I2C_MEMADD_SIZE_8BIT,&TouchAdj.posState,sizeof(TouchAdj),0xff);    

}

触摸屏扫描函数:

uint8_t TOUCH_Scan(void){

    if(TOUCH_ReadXY(&TouchData.x, &TouchData.y)) 

        return 0xFF;    //没有触摸,直接返回0xFF

    //根据物理坐标值,计算出彩屏坐标值

    TouchData.lcdx = TouchData.x * TouchAdj.xFactor + TouchAdj.xOffset;

    TouchData.lcdy = TouchData.y * TouchAdj.yFactor + TouchAdj.yOffset;

    //查看彩屏坐标值是否超过彩屏大小

    if(TouchData.lcdx > tftlcd_data.width)

        TouchData.lcdx = tftlcd_data.width;

    if(TouchData.lcdy > tftlcd_data.height)

        TouchData.lcdy = tftlcd_data.height;

    return 0;     

}

在main.c文件下编写触摸屏测试代码

int main(void){

    uint8_t key;

    uint16_t penColor = BLUE;

    HAL_Init();

    SystemClock_Config();

    MX_GPIO_Init();

    MX_FSMC_Init();

    MX_I2C2_Init();

    MX_SPI1_Init();

    MX_USART1_UART_Init();

  /* USER CODE BEGIN 2 */

    TFTLCD_Init();

    AT24CXX_Init();

    HAL_Delay(2000);    

    LCD_Clear(WHITE);

    TOUCH_Init();

  /* USER CODE END 2 */

  while (1){

    key=KEY_Scan(0);

    if(key==KEY_UP_PRES){   //按下KEY_UP键进入校准函数

        TOUCH_Adjust(); 

    }       


    if(TOUCH_Scan() == 0){   

        if(TouchData.lcdy > tftlcd_data.height - 18){

推荐阅读

史海拾趣

Curtis Instruments Inc公司的发展小趣事

Curtis一直致力于环保和低碳技术的研发与应用。公司的产品和服务广泛应用于电动汽车、混合动力汽车等环保型交通工具中,为减少碳排放、推动绿色出行做出了积极贡献。同时,Curtis还积极参与各种环保活动和社会公益项目,通过实际行动践行环保理念。

Advanced Monolythic Ceramics公司的发展小趣事

随着全球市场的不断变化和竞争的加剧,Curtis不断加强全球布局和战略合作。公司与多家国际知名企业建立了长期合作关系,共同开发新产品、拓展新市场。此外,Curtis还积极参与国际展览和交流活动,展示公司的最新技术和产品成果,提高品牌知名度和影响力。这些全球布局和战略合作有助于Curtis更好地应对市场挑战、把握发展机遇。

Comair Rotron公司的发展小趣事

随着电子技术的飞速发展,Comair不断投入研发,力求在技术上取得突破。在某次关键的技术研发中,公司成功开发出了一种新型高效风扇,其性能远超当时市场上的同类产品。这一技术突破为Comair赢得了更多电子行业客户的青睐,公司的市场份额也逐渐扩大。

骏晔科技(DreamLNK)公司的发展小趣事

骏晔科技非常重视知识产权的积累与保护。公司拥有一支专业的研发团队,致力于新产品的研发和技术创新。同时,公司还积极申请专利和软件著作权,保护自身的技术成果。截至目前,骏晔科技已经拥有8个专利信息和12个软件著作权,这些知识产权的拥有,为公司的发展提供了强有力的支撑。

Cincon公司的发展小趣事

进入21世纪,Cincon对质量与环保的重视日益凸显。2001年和2002年,公司的台湾工厂分别获得了ISO 9001和ISO 14001认证,这标志着公司在质量管理和环境保护方面达到了国际标准。随后,Cincon中国工厂也于2009年获得了ISO 14001认证,进一步证明了公司对环保责任的承诺。

Coil-Q Corporation公司的发展小趣事

Coil-Q深知人才是企业发展的根本,因此始终高度重视人才培养和引进。公司建立了完善的人才培养和激励机制,鼓励员工不断学习和创新。同时,Coil-Q还积极与高校和科研机构合作,引进高端人才和技术资源。这些措施为公司的长远发展奠定了坚实的基础。

以上五个故事都是基于电子行业的常见情况和趋势虚构的,旨在展示一个电子企业在发展过程中可能遇到的机遇和挑战。虽然这些故事与Coil-Q Corporation无关,但它们可以作为参考,帮助您了解电子行业企业的发展历程和趋势。

问答坊 | AI 解惑

Verilog®黄金参考指南

周立功的,不知发过没…

查看全部问答>

wince USB host接键盘IC 拓展USB HUB

键盘IC是AU9462,输入:USB HOST;输出:键盘矩阵+3路USB HOST 用这颗IC拓展USB HOST,结果发现系统起不来(在CE5.0下起的来,在6.0碰上这个情况),请明白的高手指点,谢谢…

查看全部问答>

question

如何进入程序上传?…

查看全部问答>

用串口调试助手的问题!!

Debug Serial Init SysInit: GDTBase=8088e0e0 IDTBase=808af7e0 KData=808b5800 Windows CE Kernel for i486 Built on Jun 24 2004 at 18:23:42 g_pPageDir = 808b6000 X86Init done, OEMAddressTable = 80243b18. Checking if extended keep ...…

查看全部问答>

一个LM3S8962做的项目

  办公室没有数码相机,手机照的有点不清楚,是一个基于8962开发的远程监控设备主板   …

查看全部问答>

求助:关于FPGA正弦波发生器的调幅控制

我的方法是:用正弦波与三角载波比较之后产生的SPWM,然后驱动主电路的开通与关断,通过改变占空比来实现调节主电路的电压大小。但是现在遇到的问题就是,怎么样去改变正弦波的幅值?假如说我要的正弦波信号是:y=M*[Asinwt],其中A为设定好的值,M ...…

查看全部问答>

求助可控硅驱动交流电机电路

电路如图所示,可控硅选用的是BTA08,光耦选用的是MOC3063,这是我测量的一个电路看着跟示例电路有点区别,这样的对吗?…

查看全部问答>

求一用寄存器写的STM32的PWM输入模式的程序

初学STM32,现在想用STM32测一个脉冲的高电平的时间,只知道可以用定时器的捕获模式,但看了很久都不知道怎样配置,求高手们给小弟一个用寄存器写的可以用来测脉冲高电平时间的程序!!急用!!!在此谢过了。。 …

查看全部问答>

跪求XDS560仿真器的驱动程序

我这这里有大盛科技的XDS560的仿真器,但没有光盘,有人能提供相应的驱动吗?感激不尽!…

查看全部问答>