6796|2

111

帖子

0

TA的资源

一粒金砂(中级)

楼主
 

【GD32450I-EVAL】SPI收发与触摸芯片XPT2046驱动及笔中断 [复制链接]

本帖最后由 tinnu 于 2020-10-6 18:08 编辑

(一)XPT2046

官方配置的是一个电阻屏,屏幕拆开一看,果然是XPT2046,这是一个常见的电阻屏驱动IC,SPI接口,本质上就是一个AD芯片,不过针对触摸优化,能够直接输出触摸结果。

不过它之所以如此出名,主要还是因为它是正点原子电阻屏的触摸芯片,作为一个现象级品牌,带动诸多产品都使用了该款芯片。

虽然我手上有两个8080并口MCU屏和一个SPI串行屏幕都是使用这颗芯片作为触摸驱动,但实际上我根本没有调过它的程序,在此之前也没有对XPT2046有多少了解。曾经为矿渣画过一块扩展板,用了H2046代替电阻触摸驱动,结果没驱动起来,于是放弃了。这次重新再看数据手册着实迷糊了好久。

 

事实上XPT2046的SPI收发并不复杂,发送的指令只有一个:

【】

依次对行和列进行扫描,然后比例换算出触摸点。

按照格式发送指令之后可以依次读两个8位数据接收,其中第一个周期第二位开始才是有效数值。

 

(二)SPI硬件接口

LCD的触摸SPI接口如图:

使用的是SPI4,查看映射表:

映射到第五个功能,配置GPIO为AF5,同时使能片选口:

    rcu_periph_clock_enable(RCU_GPIOF);
    rcu_periph_clock_enable(RCU_SPI4);

    /*k SPI1 GPIO config */
    gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_7 | GPIO_PIN_8 |GPIO_PIN_9);
    gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7 | GPIO_PIN_8 |GPIO_PIN_9);
    gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7 | GPIO_PIN_8 |GPIO_PIN_9);
    /* set SPI4_NSS as GPIO*/
    gpio_mode_set(GPIOF,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_6);
    gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
    gpio_bit_set(GPIOF, GPIO_PIN_6);

 

(三)SPI软件配置

SPI软件配置的关键在于PL和PH,PL指哪个边沿读取数据,PH指空闲时时钟线拉高还是拉低。

网上不知为何出现大量误导的教程,以ST平台驱动XPT2046为例举出这样的配置:

SPI SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                              //时钟悬空高电平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;              //在第二个时钟采集数据

事实上是错的,刚好反了过来,看数据手册的描述:

DCLK时钟线在空闲时为低,即PH=low,并且在上升沿读取数据,即第一个边沿读取,即PL=high

因此配置应为:

    /* SPI4 parameter config */
    spi_init_struct.trans_mode           = SPI_TRANSMODE_FULLDUPLEX;
    spi_init_struct.device_mode          = SPI_MASTER;
    spi_init_struct.frame_size           = SPI_FRAMESIZE_8BIT;
    spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
    spi_init_struct.nss                  = SPI_NSS_SOFT;
    spi_init_struct.prescale             = SPI_PSC_256;
    spi_init_struct.endian               = SPI_ENDIAN_MSB;
    spi_init(SPI4, &spi_init_struct);

 

(四)SPI收发

轮询收发函数:

spi_i2s_data_transmit
spi_i2s_data_receive

但实际测试发现,spi_i2s_data_receive 不具备操作硬件的功能,他只是从接收的寄存器里面读一个数据出来。

因此每次接收之前需要额外再写一次。

 

1-但这个时候出现了一个问题,就是从寄存器里面读取的数据是上上次接收的数据:

此时的代码:

    gpio_bit_reset(GPIOF, GPIO_PIN_6);
    //X
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0xD3);
	delay_ms(2);
    //read 1
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x00);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_char[1] = spi_i2s_data_receive(SPI4);
    //read2
	delay_ms(1);
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x00);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_char[2] = spi_i2s_data_receive(SPI4);

    usart_data_transmit(USART0, t_char[0]);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, t_char[1]);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, t_char[2]);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));

排查良久,感觉导致错位的第一个数据是写命令时读的那个数据,猜测可能是有某种缓冲机制——数据写入之后必须立即读一次,如果没有立即读出来会导致数据滞后一位。

根据这个猜想排除bug:

    gpio_bit_reset(GPIOF, GPIO_PIN_6);
    //X
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0xD3);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_char[0] = spi_i2s_data_receive(SPI4);
	delay_ms(2);
    //read 1
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x00);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_char[1] = spi_i2s_data_receive(SPI4);
    //read2
	delay_ms(1);
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x00);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_char[2] = spi_i2s_data_receive(SPI4);

    usart_data_transmit(USART0, t_char[0]);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, t_char[1]);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, t_char[2]);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));

收发果然就正常了。

 

 

2-实际上在排除上述的bug中间还出现过另一个问题,就是忘记获取读允许标志位立即就读取的话还会导致下一个写操作丢失:

   while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x93);
    // while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    spi_i2s_data_receive(SPI4);
	delay_us(100);
    
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x00);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_char[0] = spi_i2s_data_receive(SPI4);

    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x00);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_char[1] = spi_i2s_data_receive(SPI4);

    usart_data_transmit(USART0, t_char[0]);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, t_char[1]);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));

 

 

 

(五)笔中断测试

XPT2046的笔中断实际上就相当于一个外部中断,甚至松手的时候还会有一个0.2ms左右的抖动,类似轻触按键那种抖动,需要延时去抖。

硬件部分可以参考一开始的贴图,使用的Pi3引脚,对应的是EXIT3中断,配置函数:

void EXIT3_TPInit()
{
    /* enable and set key EXTI interrupt to the lowest priority */
    nvic_irq_enable(EXTI3_IRQn, 2U, 0U);
    /* connect key EXTI line to key GPIO pin */
    syscfg_exti_line_config(EXTI_SOURCE_GPIOI, EXTI_SOURCE_PIN3);
    /* configure key EXTI line */
    exti_init(EXTI_3, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
    exti_interrupt_flag_clear(EXTI_3);
}

中断函数,给一个1ms的延时实际上能排除大部分抖动带来的误触,但有一小部分的依旧会漏掉,需要起码延时100ms以上:


void EXTI3_IRQHandler(void)
{
    uint16_t t_data = 0x44, t_x=0, t_y=0;
    char t_char[30] = "";
    if (RESET != exti_interrupt_flag_get(EXTI_3)) {
        delay_ms(100);
        if(gpio_input_bit_get(GPIOI, GPIO_PIN_3)==RESET)
        {
            gd_eval_led_toggle(LED2);
            PRINTF_UART0("TCH:");
            ReadTPXYOver(&t_x, &t_y);
            sprintf(t_char, "x:%d\ty:%d\r\n", t_x, t_y);
            PRINTF_UART0(t_char);
        }
        // lvChange_sliderTest();
        exti_interrupt_flag_clear(EXTI_3);
    }
}

此时触摸一下屏幕,就会打印一次AD值。

 

(六)AD精度与SPI速度

调过程序才发现,原来AD的转化精度是取决于SPI速度的,换言之SPI还不能读得太快,一开始使用了8分频,即:

spi_init_struct.prescale             = SPI_PSC_8;

几乎整个屏幕只有屈指可数的几个分辨率,要滑很长的距离才会出现一个突变的数值,查看HEX码,只有第一位转化了,换言之精度不足4位。

而使用256的时候基本可以实现像素间的区分了,上图是256分频时的时钟间隔,一个周期2.5ms

因为发送的命令时0x90和0xD0,所以数据是12位的。

12位分辨极限为4096。

 

 

(七)接收的数据处理

接收到的是一个16位的数据,第一位是busy,AD正在转化,无效。从第二位开始算起12位:

    t_x = spi_i2s_data_receive(SPI4);

    t_x = (t_x& 0x7F)<<8 ;

 

    t_x = t_x|spi_i2s_data_receive(SPI4);

    t_x = t_x>>3;

 

完整的测试函数:

void ReadTPTest()
{
    char t_char[30]="";
    uint16_t t_x=0,t_y=0;
    
    gpio_bit_reset(GPIOF, GPIO_PIN_6);
    //X
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0xD0);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    spi_i2s_data_receive(SPI4);

	delay_us(10);
    //read 1
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x00);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_x = spi_i2s_data_receive(SPI4);
    t_x = (t_x& 0x7F)<<8 ;
    //read2
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x90);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_x = t_x|spi_i2s_data_receive(SPI4);
    t_x = t_x>>3;

	delay_us(10);
    
    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x00);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_y = spi_i2s_data_receive(SPI4);
    t_y = (t_y& 0x7F) <<8;

    while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE));
    spi_i2s_data_transmit(SPI4, 0x00);
    while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
    t_y = t_y|spi_i2s_data_receive(SPI4);
    t_y = t_y>>3;

    gpio_bit_set(GPIOF, GPIO_PIN_6);

    sprintf(t_char, "x:%d\ty:%d\r\n", t_x, t_y);
    PRINTF_UART0(t_char);
}

 

 

此帖出自GD32 MCU论坛

最新回复

兆易GD32450I-EVAL 汇总贴:https://bbs.eeworld.com.cn/thread-1140981-1-1.html   详情 回复 发表于 2020-10-9 15:47
点赞 关注
 

回复
举报

111

帖子

0

TA的资源

一粒金砂(中级)

沙发
 

源码查看本人gitee开源代码:

链接已隐藏,如需查看请登录或者注册

此帖出自GD32 MCU论坛
 
 
 

回复

1万

帖子

2853

TA的资源

管理员

板凳
 

兆易GD32450I-EVAL

汇总贴:https://bbs.eeworld.com.cn/thread-1140981-1-1.html

此帖出自GD32 MCU论坛
加EE小助手好友,
入技术交流群
EE服务号
精彩活动e手掌握
EE订阅号
热门资讯e网打尽
聚焦汽车电子软硬件开发
认真关注技术本身
 
个人签名玩板看这里:
https://bbs.eeworld.com.cn/elecplay.html
EEWorld测评频道众多好板等你来玩,还可以来频道许愿树许愿说说你想要玩的板子,我们都在努力为大家实现!
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表