搜索
bottom↓
回复: 6

SPI的DMA接收功能,DMA完成中断后SPI的时钟不停止,啥原因?

[复制链接]

出0入0汤圆

发表于 2018-11-27 15:02:09 | 显示全部楼层 |阅读模式
本帖最后由 silence_sky 于 2018-11-27 15:08 编辑

SPI配置:

  SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //全双工
  SPI_InitStruct.SPI_Mode = SPI_Mode_Master; //主
  SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; //8位帧
  SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;//空闲状态为高电平
  SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;//第二个跳变沿数据被采样
  SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; //NSS 信号由软件控制
  SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; //预分频36/2 =18Mhz
  SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
  SPI_InitStruct.SPI_CRCPolynomial = 7; //CRC 值计算的多项式
  SPI_Init(SPI2, &SPI_InitStruct); //根据指定的参数初始化外设 SPIx 寄存器
  SPI_Cmd(SPI2,ENABLE);//使能SPI外设
  
  SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Rx, DISABLE);

DMA配置:

  DMA_DeInit(DMA1_Channel4); //将DMA的通道1寄存器重设为缺省值
  DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI2->DR; //DMA外设ADC基地址
  DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&bmpbuffA[0]; //DMA内存基地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //内存作为数据传输的目的地
  DMA_InitStructure.DMA_BufferSize = 0; //DMA通道的DMA缓存的大小
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为16位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为16位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA_Mode_Circular; //工作在循环缓存模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
  DMA_Init(DMA1_Channel4, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道
  
  DMA_Cmd(DMA1_Channel4, DISABLE);            //关闭DMA
  DMA_ClearFlag(DMA1_FLAG_GL4|DMA1_FLAG_TC4|DMA1_FLAG_HT4|DMA1_FLAG_TE4);
  DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);  //DMA中断使能

启动接收
//-------------------------------------------------------------------------------
//16K读dma读取
//-------------------------------------------------------------------------------
void SFLASH_DMA_ReadBlock16K(u32 addr,u8 *Buff)   
{
  addr*=16384;
  FLASH_CS_LOW();
  SPI_FLASH_SendByte(W25X_FastReadData);  // W25X_ReadData   W25X_FastReadData
  SPI_FLASH_SendByte(addr>>16);
  SPI_FLASH_SendByte(addr>>8);
  SPI_FLASH_SendByte(addr);
  SPI_FLASH_SendByte(0xff);

  while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY) == SET);
  //while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);  
  addr=SPI2->DR ;        //读一下,清空
  SPI2->CR1=0X074f;    //配置为单发,注意波特率
  SPI2->CR2|=0X01;     //启动DMA请求
  DMA1_Channel4->CNDTR = 16384;
  DMA1_Channel4->CMAR =(u32)Buff;
  DMA1_Channel4->CCR|=0x01; //启动DMA
}

中断
void DMA1_Channel4_IRQHandler(void)
{
  OSIntEnter();
  if(DMA_GetFlagStatus(DMA1_FLAG_TC4));
  {
    SPI2->CR2&=0Xfffe;     //关闭DMA请求
    FLASH_CS_HIGH();
    DMA1_Channel4->CCR&=0XFFFE;      //关DMA
    DMA_ClearFlag(DMA1_FLAG_TC4);   
    SPI2->CR1 &= 0xffbf;//关闭SPI
    SPI2->CR1  = 0X034f;  //打开,恢复到双工,注意波特率配置   
    OSSemPost(Sem_SPI2_DMA_TC);   
  }
  OSIntExit();
}

要说有什么特别的,就是SPI的DMA接收模式必须配成只接收(双线只接收),配的时候为了速度我直接寄存器赋值了。

SPI的DMA请求什么时候开,先开请求、先开DMA使能最后结果都一样。不管传多少数据量,最后SPI的CLK时钟就没停。
关键是:如果中断里不配置关闭SPI或者改变SPI的模式,时钟就一直有
最开始发现问题就是我需要在中断里恢复SPI为双线收发模式,结果老是失败,导致下一次使用SPI时数据发送错误。一查才发现是DMA中断后SPI并没有停止工作,而SPI的CR寄存器不可以在工作期间改变。

数组里的头尾都没问题,也就是说DMA的TC中断的时候DMA已经停止往数组里送数了。单纯就是SPI没停。
数据到AF就该停了。后面B0\B1已经是下一组数据的开头了。

感觉B0、B1会传出来就是因为还没进中断程序。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出10入12汤圆

发表于 2018-11-27 16:28:31 | 显示全部楼层
没有发送完啊!!!!!!!!

出0入0汤圆

 楼主| 发表于 2018-11-28 10:28:36 | 显示全部楼层
wowangru 发表于 2018-11-27 16:28
没有发送完啊!!!!!!!!

我这是接收,不是发送。
是SPI从spi-flash芯片读数据。
图里的数据是我故意做出来的,16K数据的最后16个字节专门写成A0~AF。下一组数据头是B0~BF。所以看到B0、B1时就知道SPI一定是多从flash里读数了。而且如果不关闭SPI会一直读下去。


看明白再说话,不然你打100个叹号也没意义。

出0入0汤圆

发表于 2018-11-28 11:29:04 | 显示全部楼层
本帖最后由 styleno1 于 2018-11-28 11:31 编辑

Receive-only主模式下,一旦SPE置位,时钟会持续,直到关闭外设。
你中断里是关闭了SPI的,只能细细检查遍了。

编辑说明:补充

出0入0汤圆

 楼主| 发表于 2018-11-28 11:56:06 | 显示全部楼层
本帖最后由 silence_sky 于 2018-11-28 12:10 编辑
styleno1 发表于 2018-11-28 11:29
Receive-only主模式下,一旦SPE置位,时钟会持续,直到关闭外设。
你中断里是关闭了SPI的,只能细细检查遍 ...


确认手册里确实是这样说的。表现出来的也是如此。

但是,按照网上多处的说法,必须把SPI配成Receive-only才能启动DMA的接收功能。我刚刚也简单试了一下,双工模式下DMA 只接收没工作起来,或这应该是双工模式下必须把DMA发送也打开,循环发现空数据,DMA接收才工作。

这样的话,等于2种情况都不完美:双工必须占用TX,只接收就必须想法处理时钟信号不停的问题。


首先发送地址和读命令,在这里重要说明一下,刚开始我把SPI配置成全双工模式,因为牵扯到要获取flash地址的操作,但是我们在用SPI以DMA读flash数据的时候,就不能使用全双工模式了,在全双工模式下,我们读取flash的时候需要一直发送一个无效数据0xff,来使电平发生变化,这样就限制了DMA的性能。所以在用DMA读flash数据的时候,我们把SPI模式配置成只读模式,如上面程序中的样子,这个时候就可以直接读取数据,而不需要在发送无效数据0xff,大大提高了性能。

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_RxOnly;
SPI_Init(SPI1, &SPI_InitStructure);配置为只读模式

出0入0汤圆

发表于 2018-11-28 14:56:14 | 显示全部楼层
SPI使用"真"DMA收发需要2个通道参与。近几年出的加了4级FIFO,中断方式也能达到DMA效果。

另,你引用的使用单接收模式:
大大提高了性能

我比较怀疑,作者指的哪款MCU?建议你辩证地分析下。

出0入0汤圆

 楼主| 发表于 2018-11-28 17:32:42 | 显示全部楼层
本帖最后由 silence_sky 于 2018-11-28 17:37 编辑
styleno1 发表于 2018-11-28 14:56
SPI使用"真"DMA收发需要2个通道参与。近几年出的加了4级FIFO,中断方式也能达到DMA效果。

另,你引用的使 ...


引用的文章说的是STM32F103系列的使用。我在用的也是103。目前问题解决了。
总结下就是:

如果是非RXolny,双工模式,那么DMA接收通道不会主动发时钟向从设备要数据。SPI对应的DMA发送通道也要打开,发送同样多个空数据,这样DMA接收通道才被动的接收数据。
缺点就是要多占用一个DMA通道用来发时钟(不占用DMA的发送通道,就要软件一个一个发数据了),并且每次接收前、后都要处理一下DMA发送的配置。

RXolny模式下,时钟自动产生不停,DMA接收通道就可以一直工作下去。
缺点是DMA接收完成后必须立刻关SPI,最后几个时钟不够8个,可能会让从设备混乱(一个字节没发完CS就拉高了)。

不过SPIflash上没这问题,我就不处理了。

所以这里RXolny模式确实比双工模式用起来方便不少。

稍微处理一下是是中断里等一下RXNE,然后拉高CS,再关闭SPI。

if(DMA_GetFlagStatus(DMA1_FLAG_TC4));
{
  while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
  FLASH_CS_HIGH();
  SPI2->CR1 &= 0xffbf;//关闭SPI
……
……
……

这样从逻辑分析仪上看一定是完整字节传完了CS才拉高。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-4 07:02

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表