tinnu 发表于 2020-10-6 18:04

【GD32450I-EVAL】SPI收发与触摸芯片XPT2046驱动及笔中断

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

<p><span style="font-size:24px;"><span style="font-family:楷体;">(一)XPT2046</span></span></p>

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

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

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

<p>&nbsp;</p>

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

<p>【】</p>

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

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

<p>&nbsp;</p>

<p><span style="font-size:24px;"><span style="font-family:楷体;">(二)SPI硬件接口</span></span></p>

<p>LCD的触摸SPI接口如图:</p>

<p></p>

<p></p>

<p>使用的是SPI4,查看映射表:</p>

<p></p>

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

<pre>
<code>    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);</code></pre>

<p>&nbsp;</p>

<p><span style="font-size:24px;"><span style="font-family:楷体;">(三)SPI软件配置</span></span></p>

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

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

<pre>
<code class="language-cpp">SPI SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                              //时钟悬空高电平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;            //在第二个时钟采集数据</code></pre>

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

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

<p>因此配置应为:</p>

<pre>
<code class="language-cpp">    /* 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, &amp;spi_init_struct);</code></pre>

<p>&nbsp;</p>

<p><span style="font-size:24px;"><span style="font-family:楷体;">(四)SPI收发</span></span></p>

<p>轮询收发函数:</p>

<pre>
<code>spi_i2s_data_transmit
spi_i2s_data_receive</code></pre>

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

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

<p>&nbsp;</p>

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

<p></p>

<p></p>

<p></p>

<p>此时的代码:</p>

<pre>
<code class="language-cpp">    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 = 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 = spi_i2s_data_receive(SPI4);

    usart_data_transmit(USART0, t_char);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, t_char);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, t_char);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));</code></pre>

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

<p>根据这个猜想排除bug:</p>

<pre>
<code class="language-cpp">    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 = 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 = 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 = spi_i2s_data_receive(SPI4);

    usart_data_transmit(USART0, t_char);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, t_char);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, t_char);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));</code></pre>

<p>收发果然就正常了。</p>

<p></p>

<p>&nbsp;</p>

<p></p>

<p>&nbsp;</p>

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

<p></p>

<pre>
<code class="language-cpp">   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 = 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 = spi_i2s_data_receive(SPI4);

    usart_data_transmit(USART0, t_char);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, t_char);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));</code></pre>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p><span style="font-size:24px;"><span style="font-family:楷体;">(五)笔中断测试</span></span></p>

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

<p></p>

<p></p>

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

<pre>
<code class="language-cpp">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);
}</code></pre>

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

<pre>
<code class="language-cpp">
void EXTI3_IRQHandler(void)
{
    uint16_t t_data = 0x44, t_x=0, t_y=0;
    char t_char = "";
    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(&amp;t_x, &amp;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);
    }
}

</code></pre>

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

<p>&nbsp;</p>

<p><span style="font-size:24px;"><span style="font-family:楷体;">(六)AD精度与SPI速度</span></span></p>

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

<pre>
<code class="language-cpp">spi_init_struct.prescale             = SPI_PSC_8;</code></pre>

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

<p></p>

<p></p>

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

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

<p>12位分辨极限为4096。</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p><span style="font-size:24px;"><span style="font-family:楷体;">(七)接收的数据处理</span></span></p>

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

<p>&nbsp;&nbsp;&nbsp;&nbsp;t_x&nbsp;=&nbsp;spi_i2s_data_receive(SPI4);</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;t_x&nbsp;=&nbsp;(t_x&amp;&nbsp;0x7F)&lt;&lt;8&nbsp;;</p>

<p>&nbsp;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;t_x&nbsp;=&nbsp;t_x|spi_i2s_data_receive(SPI4);</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;t_x&nbsp;=&nbsp;t_x&gt;&gt;3;</p>

<p>&nbsp;</p>

<p>完整的测试函数:</p>

<pre>
<code class="language-cpp">void ReadTPTest()
{
    char t_char="";
    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&amp; 0x7F)&lt;&lt;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&gt;&gt;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&amp; 0x7F) &lt;&lt;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&gt;&gt;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);
}</code></pre>

<p>&nbsp;</p>

<p>&nbsp;</p>

tinnu 发表于 2020-10-6 18:10

<p>源码查看本人gitee开源代码:<a href="https://gitee.com/tinnu/littlevgl_gd32f450" target="_blank">LittleVGL_GD32F450</a></p>

okhxyyo 发表于 2020-10-9 15:47

<p><a href="https://bbs.eeworld.com.cn/thread-1140981-1-1.html" target="_blank">兆易GD32450I-EVAL</a></p>

<p>汇总贴:<a href="https://bbs.eeworld.com.cn/thread-1140981-1-1.html">https://bbs.eeworld.com.cn/thread-1140981-1-1.html</a></p>
页: [1]
查看完整版本: 【GD32450I-EVAL】SPI收发与触摸芯片XPT2046驱动及笔中断