打印
[单片机芯片]

沁恒微WCH32v003驱动ST7735S硬件spi+DMA调试小坑

[复制链接]
1768|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-3-1 08:55 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
       最近项目需要,要用wch32v003驱动ST7735S,用硬件spi+DMA方式可以提高屏幕刷新率,但是使用过程遇到一下问题,分享出来,有清楚的大佬可以指点指点。

        这篇文章并不是给着急移植程序使用的人看的,因为在赶进度的时候都是希望越快实现越好,不会细细琢磨,如果你有时间可以耐心看完,希望对你有所帮助。

        下面是spi+dma代码部分,屏幕部分的代码就先不放了,可以去我另外一篇文章里面移植。让你学会写ST7735s驱动LCD程序(SPI)-CSDN博客

        硬件SPI+DMA驱动0.96寸ST7735S(含代码)_st7735s 指令0x2a-CSDN博客

/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2022/08/08
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/
/*
*@Note
*SPI DMA, master/slave mode transceiver routine:
*Master:PI1_SCK(PC5)\PI1_MISO(PC7)\PI1_MOSI(PC6).
*Slave:PI1_SCK(PC5)\PI1_MISO(PC7)\PI1_MOSI(PC6).
*
*This example demonstrates simultaneous full-duplex transmission and reception
*between Master and Slave.
*Note: The two boards download the Master and Slave programs respectively,
*and power on at the same time.
*     Hardware connection:
*           PC5  -- PC5
*           PC6 -- PC6
*           PC7 -- PC7
*
*/

#include "debug.h"
#include "string.h"
#include "lcd_init.h"
/* SPI Mode Definition */
#define HOST_MODE     0
#define SLAVE_MODE    1

/* SPI Communication Mode Selection */
#define SPI_MODE   HOST_MODE
//#define SPI_MODE      SLAVE_MODE

/* Global define */
#define Size          18

/* Global Variable */
u16 TxData[Size] = {0x1234, 0x5678, 0x9abc, 0xe123, 0x4567, 0x89ab,
                    0xcde0, 0x1234, 0x5678, 0x9abc, 0xe123, 0x4567,
                    0x89ab, 0xcde0, 0x1234, 0x5678, 0x9abc,0xe123};
u16 RxData[Size];



/*********************************************************************
* @fn      SPI_FullDuplex_Init
*
* @brief   Configuring the SPI for full-duplex communication.
*
* @return  none
*/
void SPI_FullDuplex_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    SPI_InitTypeDef  SPI_InitStructure = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_SPI1, ENABLE);

#if(SPI_MODE == HOST_MODE)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

//    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
//    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//    GPIO_Init(GPIOC, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

#elif(SPI_MODE == SLAVE_MODE)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

//    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
//    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//    GPIO_Init(GPIOC, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

#endif

    SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;

#if(SPI_MODE == HOST_MODE)
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

#elif(SPI_MODE == SLAVE_MODE)
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;

#endif

    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 0;
    SPI_Init(SPI1, &SPI_InitStructure);

    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
//    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);

    SPI_Cmd(SPI1, ENABLE);
}

/*********************************************************************
* @fn      DMA_Tx_Init
*
* @brief   Initializes the DMAy Channelx configuration.
*
* @param   DMA_CHx - x can be 1 to 7.
*          ppadr - Peripheral base address.
*          memadr - Memory base address.
*          bufsize - DMA channel buffer size.
*
* @return  none
*/
void DMA_Tx_Init(DMA_Channel_TypeDef *DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize)
{
    DMA_InitTypeDef DMA_InitStructure = {0};

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    DMA_DeInit(DMA_CHx);

    DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
    DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = bufsize;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA_CHx, &DMA_InitStructure);
}

/*********************************************************************
* @fn      DMA_Rx_Init
*
* @brief   Initializes the SPI1 DMA Channelx configuration.
*
* @param   DMA_CHx - x can be 1 to 7.
*          ppadr - Peripheral base address.
*          memadr - Memory base address.
*          bufsize - DMA channel buffer size.
*
* @return  none
*/
void DMA_Rx_Init(DMA_Channel_TypeDef *DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize)
{
    DMA_InitTypeDef DMA_InitStructure = {0};

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    DMA_DeInit(DMA_CHx);

    DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
    DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = bufsize;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA_CHx, &DMA_InitStructure);
}

/*********************************************************************
* @fn      main
*
* @brief   Main program.
*
* @return  none
*/
int main(void)
{

    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    SPI_FullDuplex_Init();

#if(SPI_MODE == SLAVE_MODE)
    printf("Slave Mode\r\n");
    Delay_Ms(1000);

#endif


#if(SPI_MODE == HOST_MODE)
    printf("Host Mode\r\n");
    Delay_Ms(100);
#endif
    LCD_GPIO_Init();

    LCD_RST_CLR;
    DMA_Tx_Init(DMA1_Channel3, (u32)&SPI1->DATAR, (u32)TxData, 15);
//    DMA_Rx_Init(DMA1_Channel2, (u32)&SPI1->DATAR, (u32)RxData, Size);
    DMA_Cmd(DMA1_Channel3, ENABLE);
//    DMA_Cmd(DMA1_Channel2, ENABLE);
    while(!DMA_GetFlagStatus(DMA1_FLAG_TC3));
    LCD_RST_SET;


    while(1)
    {



    }
}
        之所以没有放屏幕点亮部分是因为屏幕没有点亮,但是我用逻辑分析仪抓取spi的波形发现我数据传输都是正常的。



     



         但是一直驱动不了屏幕,我最近用了很多spi,说实话软件硬件spi都已经比较熟悉了,既然发送通信没有问题,我想问题可能出现在RST或者DC数据/指令这两个线上了。经过我的测试发现,问题出现在DC数据/指令这个线上,我代码里有一个测试的地方



      

        就是让一个引脚拉低,然后启用dma发送,然后等待发送完成,最后再将引脚拉高,最后看看波形。



      

        最下面的就是这个翻转的引脚,可以看到在数据并没有传输完成的时候,引脚已经被拉高了,所以在驱动屏幕的时候,会出现DC线操作和数据、指令发送不匹配的问题,当然无法驱动屏幕了。



        然后我又把目光放在了等待传输完成这个地方,会不会是这个用错了

while(!DMA_GetFlagStatus(DMA1_FLAG_TC3));
        点进去看发现这个函数就是读寄存器是否置1,我猜测是判断dma传输完成中断标志位,于是我查阅了一下寄存器使用手册。





        可以看到DMA_INTFR的TCIFx就是完成时候的标志位,由硬件置位,再看看具体的是第几位。



        DMA_INTFR的地址是0x40020000

         

        找到上面说的TCIF位,因为我代码里发送的使用dma的通道3,所以我们要找的是TCIF3,也就是第9位,换成十六进制就是0x00002000



       再看代码里的








        可以看到跟我的推断完全一样,这说明函数没有用错。



        但是在我总结的时候,再看一眼文字描述的时候发现了一个之前没有注意到的地方



        再看看这句话,当dma的传输字节数目减至0将会产生DMA传输完成标志

        看到这里我大概猜测是因为dma启动传输一个字节后就会减一,但是其实最后那里只是启动了传输,但是并没有真正意义上的传输完成,所以这里有一个时间差。我们可以验证一下这个猜想。(顺便提一嘴,我现在一直找问题的习惯都是提出猜想,想办法去验证它,如果不对那就继续重复,直到找到为止)

        我们先计算一下spi+dma的时钟频率,芯片的时钟频率是48M



        SPI我用的是64分频,所以就是48M/64=0.75M,也就是750kHz,一个脉冲周期就是1/75K=0.000001333s,也就是1.33us,按50%的占空比算,scl的一个高电平或者低电平持续时间大概是1.33/2=0.665us,差不多是600ns,用逻辑分析仪验证一下。(这里我提一嘴,要是有逻辑分析仪真的很有用,尤其是这种高速通信中的问题,很难找到问题所在,用传统示波器也不好分析,有条件大家可以整一个,牌子就不说了,tb上搜有挺多的,免得说我打广告)



        实际的是650ns左右,有误差很正常,周期1.3us,跟我算的一样。



        芯片时钟是48Mhz,一个时钟周期是1/48M=0.020833us,一个机器周期包含12个时钟周期,所以一个机器周期是12*0.020833=0.25us,简单指令运行是一个机器周期时间,大约250nm



        这里可以看出确实是在DMA开始传输后大约一个指令周期的时间引脚就被拉高了,可以验证上面的猜想应该是正确的。



        既然找到的问题,验证了猜想,下一步就是解决问题,我的想法是用延时函数,简单粗暴,如果大家有什么好的方法也可以提出,大家一起讨论进步。



        算了一下最后是剩下16位(这里还有点疑问就是为什么描述是1个字节为单位,而这里剩下2字节,这个问题还有待研究,有知道的也欢迎提出),一个周期大概是1.3us,16个就是20.8us,保险起见我们延时25us。如果库函数自带延时us函数可以直接调用,如果没有可以参考我下面的函数。(我的库里是有延迟us函数的,所以我直接调用)



/*
        简易的us级别的延迟函数
        我的芯片时钟是48Mhz
        一个时钟周期是1/48M=0.020833us,一个机器周期包含12个时钟周期,所以一个机器周期是12*0.020833=0.25us
        所以这里i++运行一次是0.25us,1us大概是运行4次
*/
void delay_us(uint16_t num)
{
        for(uint16_t j=0;j<num;j++)
        {
                for(uint16_t i=0;i<4;i++){}//根据不同的芯片周期修改i的数值
        }
}







        编译下载后再看波形,普天同庆,跟我的猜想一模一样,最后的引脚变化已经是完全在传输结束之后了,问题成功解决。





        最后总结一下,我写csdn的初衷只有两个,总结自己,帮助别人。在嵌入式领域我也是一个新手,刚接触没几年,一路上也是看其他csdn文章一步步学过来的,不能说有什么成就,但是还是有一些小小的心得,希望可以帮助到刚踏上这条路的你。

        首先我想说的是不要害怕底层!不要害怕底层!不要害怕底层!重要的事情说三遍!!

刚开始接触的时候特别讨厌底层、封装、寄存器那些,觉得特别难懂,所以一直喜欢逃避,就喜欢用别人的封装库,最好是一顿复制粘贴就可以使用的那种,但是后面我发现这样并不是学习,只是在搬运、缝合,我反问自己,这真的能学到什么呢?后面我想明白了,我要走出我自己的“舒适圈”,我下定决心要弄懂原理,慢慢接触底层知识。人最大的恐惧是源于无知,当你从未接触之前,你觉得什么都难,但是你沉下心来学习时,其实并没有想象中这么难,你只要告诉自己,别人能写,别人能看,为什么自己就不能呢?真正难的,是你踏出自己舒适圈的第一步。尝试去接触底层原理,去看寄存器手册,芯片手册。可以看看库函数封装的内容,本质其实还是操作寄存器。

        第二,不仅要踏出舒适圈,还要不断的踏出舒适圈。之前总有一种想法,就是我学这么多差不多了,够用了已经,但是后面我慢慢醒悟过来,时代在发展,技术在进步,没有人可以说自己学得已经够多了,千万不要安于现状!!!一定要不断学习,不断前进,不断提升自我,嵌入式这条道路上是没有所谓的尽头的,大家都在不断向前,如逆水行舟,不进则退。

        第三,不但要学,还要应用。应用才能体现出你的理解和存在的问题,可以查漏补缺,可以加深理解,所以多去找项目做,多去用。

        第四,学会总结,我觉得总结才是最重要的。以前的我对总结是嗤之以鼻,现在的我是逐字分析。总结也是学习的过程,像是上面提到了那句话也是我在总结的过程中发现的,不但可以加深自己的理解,整合自己的想法。学会总结,才算真正的学习。

        希望可以对大家有所帮助、有所启发。说的不对的地方也欢迎大家批评指正。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/abc565846881/article/details/135966497

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1942

主题

15667

帖子

12

粉丝