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

        最近项目需要,要用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的波形发现我数据传输都是正常的。

沁恒微WCH32v003驱动ST7735S硬件spi+DMA调试小坑_第1张图片

     

   

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

沁恒微WCH32v003驱动ST7735S硬件spi+DMA调试小坑_第2张图片

       

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

       

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

        

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

while(!DMA_GetFlagStatus(DMA1_FLAG_TC3));

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

沁恒微WCH32v003驱动ST7735S硬件spi+DMA调试小坑_第3张图片

       

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

        DMA_INTFR的地址是0x40020000

         沁恒微WCH32v003驱动ST7735S硬件spi+DMA调试小坑_第4张图片

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

        

       再看代码里的

沁恒微WCH32v003驱动ST7735S硬件spi+DMA调试小坑_第5张图片

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

        

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

        再看看这句话,当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上搜有挺多的,免得说我打广告)

沁恒微WCH32v003驱动ST7735S硬件spi+DMA调试小坑_第6张图片

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

        

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

沁恒微WCH32v003驱动ST7735S硬件spi+DMA调试小坑_第7张图片

        这里可以看出确实是在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

沁恒微WCH32v003驱动ST7735S硬件spi+DMA调试小坑_第8张图片

        

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

        

        

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

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

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

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

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

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

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

你可能感兴趣的:(单片机,嵌入式硬件,risc-v)