你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32的SPI DMA调试感悟

[复制链接]
歹徒兄弟 发布时间:2019-6-18 09:50
最近我用STM32F103 DMA读写W25Q16遇到一个无法解释的问题:测试4台样机中2台采用HSI+PLL=64MHz时钟,另外2台采用HSE+PLL=72MHz时钟,结果72MHz的2台机读和写频繁出问题有时读能成功有时读不到还有时读死机了,写也是偶尔成功偶尔不成功,但64MHz时钟的2台机测试始终正常未曾出现过问题。前后折腾了2周左右那2台出问题的样机始终没有找到原因。最后实在没招了全面放弃DMA方式改用轮询SPI读写,问题居然莫名被解决了。那为什么不能采用DMA方式而非采用轮询方式才可以呢?网上查了一下相关资料才明白问题可能就出在DMA的SPI时钟连续性太好了字节与字节的间隔是连续的没有等待的原因。比如连续发几百个字节期间只要有一个位的时钟异常了那么往后的所有数据就会全部错位,这就是DMA方式数据容易出错的原因所在。你可能会认为这是STM32的性能缺陷,从我的测试现象来看这个锅不该ST来背。
      前面我说过有2台64MHz的样机采用DMA方式是正常的始终没有出过问题。为什么会这样?其实我有个条件还未交代:64MHz时钟和72MHz时钟的样机其实是两种不同版本的硬件PCB走线都不一样尤其是SPI的MISO,MOSI,SCK走线区别很大。
72MHz的硬件SCK走了一个过孔而MISO和MOSI却没有走过孔;而64MHz的硬件SCK,MISO和MOSI都走了一个过孔。我认为这就是问题的原因所在:
正是由于72MHz的硬件SCK多走了一个过孔,导致SCK信号线与MISO或MOSI信号线的阻抗不一致从而导致时钟同步信号滞后因而连续传输数据容易出错。



收藏 评论9 发布时间:2019-6-18 09:50

举报

9个回答
歹徒兄弟 最优答案 回答时间:2019-6-18 11:50:07
本帖最后由 shipeng1989 于 2019-6-18 11:53 编辑

顺便分享一下我的DMA方式读写的源代码:

/************************************************
函数名称 : SPI_Configuration
功    能 : SPI配置
            SPI为主模式,时钟线平时为低,上升沿采集数据
            8位数据格式,软件控制片选,数据高位在前
参    数 : 无
返 回 值 : 无
作    者 : ShiPeng
*************************************************/
void SPI3_Configuration(void)
{
        SPI_InitTypeDef  SPI_InitStructure;
        GPIO_InitTypeDef GPIO_InitStructure;

        
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);

        GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//JTAG Disable SWDP Enable

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3  |  GPIO_Pin_4  | GPIO_Pin_5;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_Init(GPIOB, &GPIO_InitStructure);
        /* SPI: CS推挽输出 */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

        /* SPI 初始化定义 */
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI设置为双线双向全双工
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                      //设置为主 SPI
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                  //SPI发送接收 8 位帧结构
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                        //时钟悬空高
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                       //数据捕获于第二个时钟沿
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                          //软件控制 NSS 信号

        SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; //波特率预分频值为2
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                 //数据传输从 MSB 位开始
  SPI_InitStructure.SPI_CRCPolynomial = 7;                           //定义了用于 CRC值计算的多项式
  SPI_Init(SPI3, &SPI_InitStructure);

        SPI_Cmd(SPI3, ENABLE);
        
        DMA_Config();
}
/************************************************
函数名称 : DMA_Config
功    能 : DMA配置
参    数 : 无
返 回 值 : 无
作    者 : ShiPeng
*************************************************/
void DMA_Config(void)
{
        #define SPI3_DR_Addr        ((u32)0x40003C0C)
        DMA_InitTypeDef DMA_InitStructure;
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);

        DMA_DeInit(DMA2_Channel1);
        DMA_InitStructure.DMA_PeripheralBaseAddr = SPI3_DR_Addr;
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Tx_Buffer;
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
        DMA_InitStructure.DMA_BufferSize = 256;
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
        DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
        DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
        DMA_Init(DMA2_Channel1, &DMA_InitStructure);
        
        DMA_ITConfig(DMA2_Channel1, DMA_IT_TC, DISABLE);
        
        // Disable SPI3 DMA RX request //
        SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Rx, ENABLE);
        DMA_Cmd (DMA2_Channel1,DISABLE);

        DMA_DeInit(DMA2_Channel2);
        DMA_InitStructure.DMA_PeripheralBaseAddr = SPI3_DR_Addr;
        DMA_InitStructure.DMA_MemoryBaseAddr = (u32)Tx_Buffer;
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
        DMA_InitStructure.DMA_BufferSize = 0;
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
        DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
        DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
        DMA_Init(DMA2_Channel2, &DMA_InitStructure);

        DMA_ITConfig(DMA2_Channel2, DMA_IT_TC, ENABLE);
        DMA_ITConfig(DMA2_Channel2, DMA_IT_TE, ENABLE);
        
        /* Disable SPI3 DMA TX request */
        SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE);
        DMA_Cmd (DMA2_Channel2,DISABLE);
}



启动DMA发送:

void WriteFLASHPageUseDMA(uint32_t faddr)
{
        SFLASH_WaitForNoBusy();
        SFLASH_WriteEnable();
        SPI3_CS_ENABLE;
        SPI3_WriteByte(SFLASH_WRITE_PAGE);         //《页编程》指令
        SPI3_WriteByte((uint8_t)(faddr>>16));    //发送24bit地址
        SPI3_WriteByte((uint8_t)(faddr>>8));
        SPI3_WriteByte(0);
        DMA2_Channel2->CNDTR = 256 ;
        DMA_Cmd (DMA2_Channel2,ENABLE);
}


启动DMA接收:

/*******************************************************************************
* Function Name  : SPI3_Recive
* Description    : SPI3的DMA方式接收
* Input          : None
* Output         : SPI3_RX_Buff[SPI3_ReciveBufferSize]
* Return         : None
* Attention      : 必须要先关闭通道2,然后再配置通道2的参数
*******************************************************************************/
void SPI3_Recive( u8 *buff, u16 len )
{
  buff[0] = SPI3->DR;DISABLE_SPI3;
        SPI3->CR1 |= SPI_Direction_2Lines_RxOnly;
        DMA2_Channel1->CCR &= ~( 1 << 0 );          //关闭DMA2通道1

        DMA2_Channel1->CMAR = (uint32_t)buff; //mem地址
        DMA2_Channel1->CNDTR = len ; //传输长度
        /*
        DMA2_Channel1->CCR = (0 << 14) | // 非存储器到存储器模式
                                        (2 << 12) | // 通道优先级高
                                        (0 << 11) | // 存储器数据宽度8bit
                                        (0 << 10) | // 存储器数据宽度8bit
                                        (0 <<  9) | // 外设数据宽度8bit
                                        (0 <<  8) | // 外设数据宽度8bit
                                        (1 <<  7) | // 存储器地址增量模式
                                        (0 <<  6) | // 外设地址增量模式(不增)
                                        (0 <<  5) | // 非循环模式
                                        (0 <<  4) | // 传输方向 外设-内存
                                        (0 <<  3) | // 禁止传输错误中断
                                        (0 <<  2) | // 禁止半传输中断
                                        (0 <<  1) | // 禁止传输完成中断
                                        (1);        // 通道开启
        */
        DMA2_Channel1->CCR |= ( 1 << 0 );          //开启DMA2通道1
        ENABLE_SPI3;
        while(DMA2_Channel1->CNDTR != 0){}
        DMA2_Channel1->CCR &= ~( 1 << 0 );         //关闭DMA2通道1
        DISABLE_SPI3;
        SPI3->CR1 &= ~SPI_Direction_2Lines_RxOnly;//SET SPI_Direction_2Lines_FullDuplex
        ENABLE_SPI3;
}
/************************************************
函数名称 : SFLASH_FastReadNByte
功    能 : 从ReadAddr地址开始连续快速读取SFLASH的nByte
参    数 : pBuffer ---- 数据存储区首地址
            ReadAddr --- 要读取SFLASH Flash的首地址地址
            nByte ------ 要读取的字节数(最大65535B = 64K 块)
返 回 值 : 无
作    者 : shipeng
*************************************************/
void SFLASH_FastReadNByte(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t nByte)
{
        SFLASH_WaitForNoBusy();
        SPI3_CS_ENABLE;                                 //使能器件
  SPI3_WriteByte(SFLASH_FAST_READ);               //《快读数据》指令
  SPI3_WriteByte((uint8_t)((ReadAddr)>>16));      //发送24bit地址
  SPI3_WriteByte((uint8_t)((ReadAddr)>>8));
  SPI3_WriteByte((uint8_t)ReadAddr);
  SPI3_WriteByte(0xFF);                           //等待8个时钟
        if (nByte<16)//小于16字节不采用DMA方式读取,数据量太小DMA优势体现不出来
        {
                while(nByte--)                                 //循环读数
                {
                        *pBuffer = SPI3_ReadByte();
                        pBuffer++;
                }
        }
        else SPI3_Recive(pBuffer,nByte);
  SPI3_CS_DISABLE;                                //失能器件
}


jasonZJR 回答时间:2019-6-18 10:50:10
试试在之前64MHz的板子上跑72MHz呗
歹徒兄弟 回答时间:2019-6-18 11:56:11
Cinderella6 发表于 2019-6-18 10:50
试试在之前64MHz的板子上跑72MHz呗

恕难从命,臣妾做不到啊!STM32的引脚太细不好跳线准备重新画个板子再试72MHz下的DMA。
歹徒兄弟 回答时间:2019-6-19 18:49:37
shipeng1989 发表于 2019-6-18 11:50
顺便分享一下我的DMA方式读写的源代码:

/************************************************

对了还有中断处理函数:
/*******************************************************************************  
* Function Name  : DMA2Channel2_IRQHandler  
* Description    : This function handles DMA Stream 1 interrupt request.  
* Input          : None  
* Output         : None  
* Return         : None  
*******************************************************************************/  
      
void DMA2_Channel2_IRQHandler(void)  
{
    /* Test on DMA Channel1 Transfer Complete interrupt */  
  if(DMA_GetITStatus(DMA2_IT_TC2))      //DMA2_IT_TC1:Transfer Completed
  {
    /* Get Current Data Counter value after complete transfer */  
                //u16 buf16= DMA_GetCurrDataCounter(DMA2_Channel2);
                DMA_Cmd (DMA2_Channel2,DISABLE);//SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, DISABLE);
                //if (0==buf16){}
                SPI_I2S_ClearITPendingBit(SPI3,SPI_I2S_IT_TXE);
                SPI_I2S_ITConfig(SPI3, SPI_I2S_IT_TXE, ENABLE);
    /* Clear DMA Channel1 Half Transfer, Transfer Complete and Global interrupt pending bits */
    DMA_ClearITPendingBit(DMA2_IT_GL2);
  }
  if(DMA_GetITStatus(DMA2_IT_TE2))
        {
                SPI3_CS_DISABLE;
                DMA_Config();
                DMA_ClearITPendingBit(DMA2_IT_TE2);
        }
}

void SPI3_IRQHandler(void)
{
        if (SPI_I2S_GetITStatus(SPI3,SPI_I2S_IT_TXE))
        {
                SPI_I2S_ClearITPendingBit(SPI3,SPI_I2S_IT_TXE);
                SPI_I2S_ITConfig(SPI3, SPI_I2S_IT_TXE, DISABLE);
                SPI_I2S_ClearITPendingBit(SPI3,SPI_I2S_IT_RXNE);
                SPI_I2S_ITConfig(SPI3, SPI_I2S_IT_RXNE, ENABLE);
        }
        if (SPI_I2S_GetITStatus(SPI3,SPI_I2S_IT_RXNE))
        {
                SPI3_CS_DISABLE;
                SPI_I2S_ClearITPendingBit(SPI3,SPI_I2S_IT_RXNE);
                SPI_I2S_ITConfig(SPI3, SPI_I2S_IT_RXNE, DISABLE);
        }
}
另外别忘了配置NVIC:
void NVIC_Configuration(void)
{
        NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);        //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
        /* Enable the RTC Interrupt */
  NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
        /*I2C EVENT NVIC Configuration*/
  NVIC_InitStructure.NVIC_IRQChannel = I2C1_EV_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
        /*I2C ERROR NVIC Configuration*/
  NVIC_InitStructure.NVIC_IRQChannel = I2C1_ER_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
        /*DMA2_Channel1 NVIC Configuration*//*
        NVIC_InitStructure.NVIC_IRQChannel = DMA2_Channel1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);*/
        /*DMA2_Channel2 NVIC Configuration*/
        NVIC_InitStructure.NVIC_IRQChannel = DMA2_Channel2_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
        /*TIM2 NVIC Configuration*/
        NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
        NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
        NVIC_Init(&NVIC_InitStructure);
        /*TIM3 NVIC Configuration*/
        NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
        NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
        NVIC_Init(&NVIC_InitStructure);
        /*USART1 NVIC Configuration*/
        NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//ShuZhiYueDaYouXianJiYueDi
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;                //ShuZhiYueXiaoYouXianJiYueGao
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能
        NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器
        /*EXTI NVIC Configuration*/
        NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
        /*Power Voltage Down NVIC Configuration*/
        NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
        /*SPI3 NVIC Configuration*/
        NVIC_InitStructure.NVIC_IRQChannel = SPI3_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}
mtxo2003@163.co 回答时间:2019-6-21 14:57:34
你测试下SCK波形咋样就知道是不是过孔问题了!如果波形很好就不是过孔问题,SPI传输是SCK配合MOSI和MISO,串行阻抗匹配影响没那么大,何况只是一个过孔,按资料上说一般要一根线5个过孔以上才会出问题,有可能是信号回流问题导致的信号完整性问题
bl2019 回答时间:2019-6-24 17:24:00
学习了
歹徒兄弟 回答时间:2019-6-24 17:32:45
本帖最后由 shipeng1989 于 2019-6-25 17:12 编辑
mtxo2003@163.co 发表于 2019-6-21 14:57
你测试下SCK波形咋样就知道是不是过孔问题了!如果波形很好就不是过孔问题,SPI传输是SCK配合MOSI和MISO, ...

你说的”信号回流问题导致的信号完整性“太高大上了不明白啊,请高人赐教!在下泥腿子出身对阻抗的概念都还一知半解,我只知道2块PCB一块走过孔的异常,另一块无过孔的正常,方才作此推断。其实我有试过2MHz的时钟频率下走过孔的那块板仍然有问题。
波形我这边的设备看不了啊。还有我这边的硬件工程师根本就没有高频走线的意识,都是闭着眼睛瞎走。最可气的是不管是什么信号都喜欢串电阻而且阻值还动不动就上10K,美其名曰ESD保护。你告诉他那是高频线他也要用470欧,奇怪的是USB的D+,D-串了470欧后居然还能正常工作。而且这帮菜鸡根本都没有差分走线的概念,地线敷铜也没有任何忌讳把USB线都包起来了。。。这样瞎折腾操作完后居然还能正常工作,着实让我嫉妒他们的好运气。分明是造物主在有意偏袒呐!
歹徒兄弟 回答时间:2019-6-24 17:38:58
跟这些人合作经常气的吐血
roumao 回答时间:2019-7-26 16:27:22
既然已经使用了DMA模式了为什么还要等待传输数量寄存器归零了?DMA就是为了在数据传输时释放CPU而设的,如果这样,岂不是体现不出DMA的优势了?见函数“SPI3_Recive”中的语句“while(DMA2_Channel1->CNDTR != 0){}”。

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版