问答

汇集网友智慧,解决技术难题

21ic问答首页 - HC32F460JETA SPI+DMA驱动2.8寸TFT LCD非常慢!!

DMA hc32f460 spi TFT LCD ST tc

HC32F460JETA SPI+DMA驱动2.8寸TFT LCD非常慢!!

showmyself2022-07-14
大家好,最近我用HC32F460驱动一块2.8 寸的SPI TFT,分辨率320x240, 但是发现TFT刷屏的速度非常非常慢!慢到大概5到6s才会刷完一屏!后来我开了DMA,也是几乎没有任何改善!我的SPI配置用的是官方例程,SPI时钟2分频,系统时钟使用的MCU内部自带时钟,我不知道自带时钟的PCLK1是多少,但PCLK1 在2分频后总不能5,6s才刷完一次屏吧,我之前用过其他的国产MCU,仿STM32F030,主频只有48M,我拿来刷3.5寸480x320的屏都比这个快啊,调了好几天都找不到原因,后来打算换到外部时钟看看,结果换到外部时钟后,又出现新的问题了,下面是我使用这颗芯片的所有问题汇总,希望能得到专家的指点:

1,刚开始,使用的是MCU自带内部时钟,用SPI+DMA驱动屏幕,SPI+DMA的配置用的是官方例程,SPI时钟2分频,屏幕可以正常初始化和点亮,但就是速度非常非常慢,如上所提到的,5,6s刷屏一次,简直无法忍受,检查了很多地方,实在是没辙了,因为从没遇到过这样的问题,之前用过的国产32位的单片机也很多,从未遇到这样的问题,感觉很奇怪。

2,为了验证是不是内部时钟问题,我使用了外部时钟,在外面焊了一颗16M的晶振,然后用官方例程切换到外部时钟,奇怪的是切换过去之后,整个系统启动变得非常缓慢,大概2s左右,之前用内部时钟都是秒启动。而且使用这颗外部16M时钟还影响了我的timer0定时器,我明明是设置的1ms进一次中断,可是使用外部时钟后,变成大概6s进一次中断(期间也换过既可16M晶振验证,无改善)。外部时钟的时钟的配置按照官方例程,也检查了和修改了很多次,没有发现哪里有问题,所以我不知道到底外部时钟配置成功没有,从现象上看确实应该是配置成功了,但是没有按照预期运行,还影响了定时器时基。当然驱动屏幕的结果也是和使用内部时钟一样的,奇慢无比。


以下是我的部分代码:

SPI+DMA部分:

void LCD_SPI_INIT(void)
{
    stc_spi_init_t stcSpiInit; //定义结构变量
    MEM_ZERO_STRUCT(stcSpiInit); //清零结构变量

    PWC_Fcg1PeriphClockCmd(PWC_FCG1_PERIPH_SPI3, Enable); //配置外设时钟

    //配置SPI IO口
    PORT_SetFunc(PortB, Pin14, Func_Spi3_Sck, Disable); //配置PB14为SPI SCK
    PORT_SetFunc(PortB, Pin15, Func_Gpio, Disable); //配置PB15为普通GPIO,初始化已在GPIO里完成
    PORT_SetFunc(PortB, Pin12, Func_Spi3_Mosi, Disable); //配置PB12为SPI MOSI
    PORT_SetFunc(PortB, Pin13, Func_Spi3_Miso, Disable); //配置PB13为SPI MISO

    //配置SPI结构参数
    stcSpiInit.enClkDiv = SpiClkDiv2; //SPI频率为PCLK1的2分频,PCLK1为100MHz
    stcSpiInit.enFrameNumber = SpiFrameNumber1; //SPI发送的数据帧,这里为1帧数据,8bit
    stcSpiInit.enDataLength = SpiDataLengthBit8; //SPI发送数据长度为8bit
    stcSpiInit.enFirstBitPosition = SpiFirstBitPositionMSB; //数据方向为MSB在前
    stcSpiInit.enSckPolarity = SpiSckIdleLevelLow; //
    stcSpiInit.enSckPhase = SpiSckOddSampleEvenChange; //
    stcSpiInit.enReadBufferObject = SpiReadReceiverBuffer; //
    stcSpiInit.enWorkMode = SpiWorkMode4Line; //配置为4线SPI
    stcSpiInit.enTransMode = SpiTransFullDuplex; //配置为全双工模式
    stcSpiInit.enCommAutoSuspendEn = Disable;
    stcSpiInit.enModeFaultErrorDetectEn = Disable;
    stcSpiInit.enParitySelfDetectEn = Disable;
    stcSpiInit.enParityEn = Disable;
    stcSpiInit.enParity = SpiParityEven;

    //SPI MASTER模式配置,因为只是给LCD发送数据,所以只需要master模式
    stcSpiInit.enMasterSlaveMode = SpiModeMaster;
    stcSpiInit.stcDelayConfig.enSsSetupDelayOption = SpiSsSetupDelayCustomValue;
    stcSpiInit.stcDelayConfig.enSsSetupDelayTime = SpiSsSetupDelaySck1;
    stcSpiInit.stcDelayConfig.enSsHoldDelayOption = SpiSsHoldDelayCustomValue;
    stcSpiInit.stcDelayConfig.enSsHoldDelayTime = SpiSsHoldDelaySck1;
    stcSpiInit.stcDelayConfig.enSsIntervalTimeOption = SpiSsIntervalCustomValue;
    stcSpiInit.stcDelayConfig.enSsIntervalTime = SpiSsIntervalSck6PlusPck2;
    stcSpiInit.stcSsConfig.enSsValidBit = SpiSsValidChannel0;
   stcSpiInit.stcSsConfig.enSs0Polarity = SpiSsLowValid;

    SPI_Init(M4_SPI3, &stcSpiInit); //以上参数配置入SPI结构体初始化
    LCD_DMA_INIT();
}


void LCD_DMA_INIT(void)
{
    stc_dma_config_t stcDmaCfg;
    MEM_ZERO_STRUCT(stcDmaCfg);

    PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_DMA1, Enable);
    PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_AOS, Enable);

    //配置DMA发送
    stcDmaCfg.u16BlockSize = 1u; //传输的数据块数量为1,最小为1,最多为1024
    stcDmaCfg.u16TransferCnt = (uint16_t)(1); //传输的次数,每传输一次数据块,寄存器减1,直到减到0为止,如果一开始就-配置为0,则表示无限次传输
    stcDmaCfg.u32SrcAddr = (uint32_t)(0); //配置DMA发送的源地址,就是DMA取数据的地址
    stcDmaCfg.u32DesAddr = (uint32_t)(&M4_SPI3->DR); //配置DMA发送的目标地址
    stcDmaCfg.stcDmaChCfg.enSrcInc = AddressIncrease; //源地址自增
    stcDmaCfg.stcDmaChCfg.enDesInc = AddressFix; //目标地址固定
    stcDmaCfg.stcDmaChCfg.enTrnWidth = Dma8Bit; //传输数据为8bit,和SPI数据宽度一致
    stcDmaCfg.stcDmaChCfg.enIntEn = Disable; //禁止DMA中断
    DMA_InitChannel(M4_DMA1, DmaCh1, &stcDmaCfg);

    DMA_SetTriggerSrc(M4_DMA1, DmaCh1, EVT_SPI3_SPTI); //配置DMA发送触发源

    DMA_Cmd(M4_DMA1, Enable); //使能DMA
}


//LCD 指令和数据发发送
void LCD_SendCmd8_SPI_DMA(uint8_t cmd8) //发送8bit指令
{
    SendDataBuff8[0]=cmd8;
    DMA_SetSrcAddress(M4_DMA1, DmaCh1, (uint32_t)(&SendDataBuff8[0])); // 重新设置源地址和发送的数据
    DMA_SetTransferCnt(M4_DMA1, DmaCh1, (uint16_t)(1)); //设置传输次数,传输一次,计数器减到0停止

    LCD_RS_WR_REG;
    LCD_CS_SET_LOW;

    DMA_ChannelCmd(M4_DMA1, DmaCh1, Enable); //使能DMA1通道
    SPI_Cmd(M4_SPI3, Enable); //使能SPI请求DMA传输
    while (Reset == DMA_GetIrqFlag(M4_DMA1, DmaCh1, TrnCpltIrq)) ; //等待DMA传输完成
    DMA_ClearIrqFlag(M4_DMA1, DmaCh1, TrnCpltIrq); //清除标志位

    LCD_CS_SET_HIGH; //拉高片选,结束一次传输

    SPI_Cmd(M4_SPI3, Disable); //失能SPI
    DMA_ChannelCmd(M4_DMA1, DmaCh1, Disable); //失能DMA1通道

}


void LCD_SendData8_SPI_DMA(uint8_t data8) //发送8bit数据
{
    SendDataBuff8[0]=data8;
    DMA_SetSrcAddress(M4_DMA1, DmaCh1, (uint32_t)(&SendDataBuff8[0])); // 重新设置源地址和发送的数据
    DMA_SetTransferCnt(M4_DMA1, DmaCh1, (uint16_t)(1)); //设置传输次数,传输一次,计数器减到0停止

    LCD_RS_WR_DATA;
    LCD_CS_SET_LOW;

    DMA_ChannelCmd(M4_DMA1, DmaCh1, Enable); //使能DMA1通道
    SPI_Cmd(M4_SPI3, Enable); //使能SPI请求DMA传输
    while (Reset == DMA_GetIrqFlag(M4_DMA1, DmaCh1, TrnCpltIrq)) ;//等待DMA传输完成
    DMA_ClearIrqFlag(M4_DMA1, DmaCh1, TrnCpltIrq); //清除标志位

    LCD_CS_SET_HIGH; //拉高片选,结束一次传输

    SPI_Cmd(M4_SPI3, Disable); //失能SPI
    DMA_ChannelCmd(M4_DMA1, DmaCh1, Disable); //失能DMA1通道
}




//更换到外部16M时钟后,系统启动变得非常慢,而且影响了timer0的时基,配置的是1ms进一次中断,现在变成大概6s。
而驱动TFT屏的速度依然很慢,没有任何改善,估计是没有配置成功,但是对了很多遍代码,实在没觉得哪里有问题。

//下面是我配置外部16M时钟的代码(完全参考官方库函数)
同时我在system_hc32f460jeta.h中也做了如下配置
#define XTAL_VALUE   ((uint32_t)16000000) //定义一个16000000晶振
#if !defined (XTAL_VALUE)
    #define XTAL_VALUE ((uint32_t)8000000) /*!< External high speed OSC freq. */
#endif



void Set_SysCLK_to_External(void)
{
        stc_clk_sysclk_cfg_t    stcSysClkCfg;
        MEM_ZERO_STRUCT(stcSysClkCfg);
       
        //设置系统时钟分频, MPLL=200M/分频系数,注意:这个分频要满足规定的比例关系,否则不工作,具体参考规格书
        stcSysClkCfg.enHclkDiv = ClkSysclkDiv1;  //HCLK时钟:200M, 最大200M,用于CPU,SRAM,DMA等
    stcSysClkCfg.enExclkDiv = ClkSysclkDiv4; //EXCLK时钟:50M,最大100M,用于SDIO
    stcSysClkCfg.enPclk0Div = ClkSysclkDiv2; //PCLK0时钟:100M,最大200M,用于timer6计数时钟
    stcSysClkCfg.enPclk1Div = ClkSysclkDiv2; //PCLK1时钟:100M,最大100M,用于SPI, timer0等
    stcSysClkCfg.enPclk2Div = ClkSysclkDiv4; //PCLK2时钟:50M,最大60M,用于ADC
    stcSysClkCfg.enPclk3Div = ClkSysclkDiv4; //PCLK3时钟:50M,最大50M,用于RTC,I2C等
    stcSysClkCfg.enPclk4Div = ClkSysclkDiv8; //PCLK4时钟:25M,最大100M,用于ADC, TRNG等
    CLK_SysClkConfig(&stcSysClkCfg);
       
       
        /*****************************外部振荡器初始化***********************************/
        stc_clk_xtal_cfg_t stcXtalCfg; //使用高速外部振荡器
        MEM_ZERO_STRUCT(stcXtalCfg); //外部时钟初始化,清零
       
        stcXtalCfg.enMode = ClkXtalModeOsc; //配置外部振荡器作为时钟源,注意振荡器和有源晶振的区别
    stcXtalCfg.enDrv = ClkXtalMidDrv; //16M晶振属于中驱动能力
    stcXtalCfg.enFastStartup = Enable; //使能快速启动
    CLK_XtalConfig(&stcXtalCfg); //配置参数
    CLK_XtalCmd(Enable); //使能振荡器
        /*****************************外部振荡器初始化***********************************/
       
       
        /**************************MPLL和SRAM时钟配置***********************************/
        stc_clk_mpll_cfg_t      stcMpllCfg;
    stc_sram_config_t       stcSramConfig;

    MEM_ZERO_STRUCT(stcMpllCfg);
    MEM_ZERO_STRUCT(stcSramConfig);

    //SRAM初始化,包括读写/等待周期
    stcSramConfig.u8SramIdx = Sram12Idx | Sram3Idx | SramHsIdx | SramRetIdx;
    stcSramConfig.enSramRC = SramCycle2;
    stcSramConfig.enSramWC = SramCycle2;
    stcSramConfig.enSramEccMode = EccMode3; //校验模式选择
    stcSramConfig.enSramEccOp = SramNmi; //ECC校验失败不产生NMI中断
    stcSramConfig.enSramPyOp = SramNmi; //奇偶校验出错后不产生NMI中断
    SRAM_Init(&stcSramConfig); //SRAM初始化
       
        //FLASH读写等待周期设置
        EFM_Unlock();
    EFM_SetLatency(EFM_LATENCY_5); //配置延迟周期,这个参数和HCLK频率有关系,参考规格书
    EFM_Lock();
       
        //MPLL配置:(XTAL / pllmDiv * plln / PllpDiv = 200M) //注意得到的系统PLL频率为PPLq/P/R, 不是400M
        stcMpllCfg.pllmDiv = 2ul; //XTAL分频系数:外部是16M晶振,2分频后是8M
    stcMpllCfg.plln =50ul; //MPLL倍频系数:50倍频,倍频后为:外部8M x 50=400M
    stcMpllCfg.PllpDiv = 2ul; //分频系数p:400M/2=200M
    stcMpllCfg.PllqDiv = 2ul; //分频系数q:400M/2=200M
    stcMpllCfg.PllrDiv = 2ul; //分频系数r:400M/2=200M
    CLK_SetPllSource(ClkPllSrcXTAL); //配置时钟源:外部高速时钟
    CLK_MpllConfig(&stcMpllCfg); //配置上述PLL参数
        CLK_MpllCmd(Enable); //使能MPLL
       
        /**************************MPLL和SRAM时钟配置***********************************/
               
        PWC_HS2HP();
        while(Set != CLK_GetFlagStatus(ClkFlagMPLLRdy)) ;
        CLK_SetSysClkSource(CLKSysSrcMPLL);        //设置MPLL作为系统时钟源
}





//如下是Timer0定时器,受到外部时钟的影响了,时间变的不对了。配置的是1ms一次中断,用外部时钟后,变成了大概6s了
void TIMER02_INT(void)  //通用定时器
{
    stc_tim0_base_init_t stcTimerCfg;
    stc_irq_regi_conf_t stcIrqRegiConf;

    uint32_t u32Pclk1;
    stc_clk_freq_t stcClkTmp;

    MEM_ZERO_STRUCT(stcTimerCfg);
    MEM_ZERO_STRUCT(stcIrqRegiConf);

    //获取PCLK1时钟
    CLK_GetClockFreq(&stcClkTmp);
    u32Pclk1 = stcClkTmp.pclk1Freq;

    //使能Timer0的外设时钟线
    PWC_Fcg2PeriphClockCmd(PWC_FCG2_PERIPH_TIM02, Enable);

   //配置Timer02的B通道寄存器参数,同步模式:时基1ms
    stcTimerCfg.Tim0_CounterMode = Tim0_Sync;
    stcTimerCfg.Tim0_SyncClockSource = Tim0_Pclk1; //PCLK1为100M
    stcTimerCfg.Tim0_ClockDivision = Tim0_ClkDiv4; // 4分频是25M,定时器1s运行25M次,1ms运行25000次
    stcTimerCfg.Tim0_CmpValue = (uint16_t)(u32Pclk1/4ul/1000ul- 1ul); //1ms
    TIMER0_BaseInit(M4_TMR02,Tim0_ChannelB,&stcTimerCfg);


    TIMER0_IntCmd(M4_TMR02,Tim0_ChannelB,Enable); //启动通道B中断
    stcIrqRegiConf.enIRQn = Int003_IRQn; //中断编号
    stcIrqRegiConf.enIntSrc = INT_TMR02_GCMB;
    stcIrqRegiConf.pfnCallback = &Timer0B_CallBack; //中断回调函数
    enIrqRegistration(&stcIrqRegiConf);
    NVIC_ClearPendingIRQ(stcIrqRegiConf.enIRQn);
    NVIC_SetPriority(stcIrqRegiConf.enIRQn, DDL_IRQ_PRIORITY_15);
    NVIC_EnableIRQ(stcIrqRegiConf.enIRQn);

    //启动Timer0
   // TIMER0_Cmd(M4_TMR02,Tim0_ChannelA,Enable);
    TIMER0_Cmd(M4_TMR02,Tim0_ChannelB,Enable);

}



//如下是main函数
int32_t main(void)
{
    //Set_SysCLK_to_External(); //使用外部高速时钟,16Mhz,如果使用外部时钟,系统启动变的非常慢,如果把这个注释掉,系统启动很快
     GLOBAL_GPIO_INIT(); //全局GPIO初始化
    TIMER02_INT(); //启动TIMER0定时器中断,开启上面的Set_SysCLK_to_External(); 后,定期器中断不对了
    LCD_SPI_INIT(); //硬件SPI+DMA初始化
     LCD_Init();         // LCD初始化
       
      MAIN_DISPLAY_FIXED(); //这是刷屏函数,不管用外部时钟还是内部时钟,不管用模拟SPI,硬件SPI,还是硬件SPI+DMA,
    刷屏的速度都慢的无法忍受,大概5,6s左右
          
    while(1)
    {                     
        ;      
    }

}

回答 +关注 15
4246人浏览 5人回答问题 分享 举报
5 个回答

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