STM32F407使用SPI的DMA来读取外部FLASH---W25Q16

STM32F407使用SPI的DMA来读取外部FLASH—W25Q16

  1. SPI功能是常用的片内外设,以前调试经常使用的其正常的读写功能,即不用DMA。当进行读取或者写入大数据量的操作的时候,使用DMA会对读写速度进行一定提高。这次利用开发板上的板载外部FLASHW25Q16来进行SPI的DMA进行调试。记录一下调试过程。
  2. 采用的MCU为STM32F407VET6,FLASH的具体型号为W25Q16的内存大小为2M的外部FLASH。电路连接上,采用STM32F4的SPI1和FLASH进行通信,IO口为PB3,PB4,PB5。
  3. 从STM32F4的中文参考手册来看,SPI1使用DMA1,其中SPI_TX是属于数据流3,SPI_RX属于数据流2,这两个数据流都是使用DMA1的通道3进行搬运数据。其中TX的搬运方向为内部存储器(程序中的定义的数组)到片内外设(SPI1->DR),RX的搬运方向为片内外设(SPI1->DR)到内部存储器(程序中的定义的数组)。因此根据这些可以对SPI以及其DMA进行初始化。
  4. 对SPI的初始化如下:
void SPI1_Init(void)
{	 
	u16 tempreg=0;
	RCC->AHB1ENR|=1<<0;    	//使能PORTA时钟	   
	RCC->APB2ENR|=1<<12;   	//SPI1时钟使能 
	GPIO_Set(GPIOB,7<<3,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU);	//PB3~5复用功能输出	
  	GPIO_AF_Set(GPIOB,3,5);	//PB3,AF5
 	GPIO_AF_Set(GPIOB,4,5);	//PB4,AF5
 	GPIO_AF_Set(GPIOB,5,5);	//PB5,AF5 

	//这里只针对SPI口初始化
	RCC->APB2RSTR|=1<<12;	//复位SPI1
	RCC->APB2RSTR&=~(1<<12);//停止复位SPI1
	tempreg|=0<<10;			//全双工模式	
	tempreg|=1<<9;			//软件nss管理
	tempreg|=1<<8;			 
	tempreg|=1<<2;			//SPI主机  
	tempreg|=0<<11;			//8位数据格式	
	tempreg|=1<<1;			//空闲模式下SCK为1 CPOL=1 
	tempreg|=1<<0;			//数据采样从第2个时间边沿开始,CPHA=1  
 	//对SPI1属于APB2的外设.时钟频率最大为84Mhz频率.
	tempreg|=7<<3;			//Fsck=Fpclk1/256
	tempreg|=0<<7;			//MSB First  
	tempreg|=1<<6;			//SPI启动 
	SPI1->CR1=tempreg; 		//设置CR1
    SPI1->CR2|=1<<0;        //使能接收缓冲区DMA使能
    SPI1->CR2|=1<<1;        //使能发送缓冲区DMA使能	
	SPI1->I2SCFGR&=~(1<<11);//选择SPI模式
	//SPI1_ReadWriteByte(0xff);//启动传输		 
}  
  1. 对SPI_TX的DMA初始化如下,主要是配置好传输方向,其方向为存储器到外设
void MYDMA_TX_Config(DMA_Stream_TypeDef *DMA_Streamx,u8 chx,u32 par,u32 mar,u16 ndtr)
{
    DMA_TypeDef *DMAx;
	u8 streamx;
	if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
	{
		DMAx=DMA2;
		RCC->AHB1ENR|=1<<22;//DMA2时钟使能 
	}else 
	{
		DMAx=DMA1; 
 		RCC->AHB1ENR|=1<<21;//DMA1时钟使能 
	}
	while(DMA_Streamx->CR&0X01);//等待DMA可配置 
	streamx=(((u32)DMA_Streamx-(u32)DMAx)-0X10)/0X18;		//得到stream通道号
 	if(streamx>=6)DMAx->HIFCR|=0X3D<<(6*(streamx-6)+16);	//清空之前该stream上的所有中断标志
	else if(streamx>=4)DMAx->HIFCR|=0X3D<<6*(streamx-4);    //清空之前该stream上的所有中断标志
	else if(streamx>=2)DMAx->LIFCR|=0X3D<<(6*(streamx-2)+16);//清空之前该stream上的所有中断标志
	else DMAx->LIFCR|=0X3D<<6*streamx;						//清空之前该stream上的所有中断标志
	
	DMA_Streamx->PAR=par;		//DMA外设地址
	DMA_Streamx->M0AR=mar;		//DMA 存储器0地址
	DMA_Streamx->NDTR=ndtr;		//DMA 存储器0地址
	DMA_Streamx->CR=0;			//先全部复位CR寄存器值 
	
	DMA_Streamx->CR|=1<<6;		//存储器到外设模式
	DMA_Streamx->CR|=0<<8;		//非循环模式(即使用普通模式)
	DMA_Streamx->CR|=0<<9;		//外设非增量模式
	DMA_Streamx->CR|=1<<10;		//存储器增量模式
	DMA_Streamx->CR|=0<<11;		//外设数据长度:8位
	DMA_Streamx->CR|=0<<13;		//存储器数据长度:8位
	DMA_Streamx->CR|=1<<17;		//高等优先级
	DMA_Streamx->CR|=0<<21;		//外设突发单次传输
	DMA_Streamx->CR|=0<<23;		//存储器突发单次传输
	DMA_Streamx->CR|=(u32)chx<<25;//通道选择
}
  1. 对SPI1的RX的DMA进行配置如下,主要是配置数据流号,数据传输的方向,即外设到存储器模式
void MYDMA_RX_Config(DMA_Stream_TypeDef *DMA_Streamx,u8 chx,u32 par,u32 mar,u16 ndtr)
{
    DMA_TypeDef *DMAx;
	u8 streamx;
	if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
	{
		DMAx=DMA2;
		RCC->AHB1ENR|=1<<22;//DMA2时钟使能 
	}else 
	{
		DMAx=DMA1; 
 		RCC->AHB1ENR|=1<<21;//DMA1时钟使能 
	}
	while(DMA_Streamx->CR&0X01);//等待DMA可配置 
	streamx=(((u32)DMA_Streamx-(u32)DMAx)-0X10)/0X18;		//得到stream通道号
 	if(streamx>=6)DMAx->HIFCR|=0X3D<<(6*(streamx-6)+16);	//清空之前该stream上的所有中断标志
	else if(streamx>=4)DMAx->HIFCR|=0X3D<<6*(streamx-4);    //清空之前该stream上的所有中断标志
	else if(streamx>=2)DMAx->LIFCR|=0X3D<<(6*(streamx-2)+16);//清空之前该stream上的所有中断标志
	else DMAx->LIFCR|=0X3D<<6*streamx;						//清空之前该stream上的所有中断标志
	
	DMA_Streamx->PAR=par;		//DMA外设地址
	DMA_Streamx->M0AR=mar;		//DMA 存储器0地址
	DMA_Streamx->NDTR=ndtr;		//DMA 存储器0地址
	DMA_Streamx->CR=0;			//先全部复位CR寄存器值 
	
	DMA_Streamx->CR&=0<<6;		//外设到存储器模式
	DMA_Streamx->CR|=0<<8;		//非循环模式(即使用普通模式)
	DMA_Streamx->CR|=0<<9;		//外设非增量模式
	DMA_Streamx->CR|=1<<10;		//存储器增量模式
	DMA_Streamx->CR|=0<<11;		//外设数据长度:8位
	DMA_Streamx->CR|=0<<13;		//存储器数据长度:8位
	DMA_Streamx->CR|=1<<16;		//中等优先级
	DMA_Streamx->CR|=0<<21;		//外设突发单次传输
	DMA_Streamx->CR|=0<<23;		//存储器突发单次传输
	DMA_Streamx->CR|=(u32)chx<<25;//通道选择
}
  1. 接下来就是用SPI的DMA进行传输数据,这里在进行调试的时候,忽略了在全双工的模式下进行SPI通信时,主机对从机发送有效命令字的时候,从机是对主机有回复数据的,这些数据是无用数据。当主机对从机读取数据的时候,要发送0XFF。这点在调试时候忽略。造成读写不正常。这是在调试的时候遇到的第一个问题。读写函数如下:
/*
*Description :通过DMA写入数据
*Param       :length传输的长度
*Return Code :None
*/
void SPI1_DMA_Trans(u16 length)
{
    SPI1->DR;
	while((SPI1->SR&0x02)==0);                //好像并没有用
	MYDMA_Enable(DMA2_Stream3,length);         //开始一次传输
	MYDMA_Enable(DMA2_Stream2,length);         //开始一次传输 
	while((DMA2->LISR&(1<<21))==0);          //等待发送完成
	while((DMA2->LISR&(1<<27))==0);          //等待发送完成
    MYDMA_Disable(DMA2_Stream2);             //关闭该DMA传输流
    MYDMA_Disable(DMA2_Stream3);             //关闭该DMA传输流
	DMA2->LIFCR|=1<<21;	                     //清除DMA2_Stream3完成标志
	DMA2->LIFCR|=1<<27;	                     //清除DMA2_Stream3完成标
}
  1. 接下来就是通过SPI的DMA传输功能对FLASH进行读写。改正了SPI的读写函数后,又遇到了第2个问题,发现测试的时候,读数据正常,写数据不对;读正确是因为在修改例程之前,已经对FLASH进行了读写操作,这时候读取的数据是其以前写进去的数据。进入Debug模式下,发现写数据时发送的命令字不正常。应该为0X02数据,结果却是0X05数据。这时候就可以确定出在写函数那里出现了问题。查看写函数发现问题出现在把W25QXX_Write_Enable();函数调用在了读写数据后面。这时候相当于没有使能外部FLASH的情况下就对器件进行写操作,肯定写不进行,即使能写进去。该函数的命令字也会覆盖写函数的命令字(因为都是用一个SPI1_TX_Buff数组进行传输)。错误函数如下:
void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{

	u16 i; 
	
    SPI1_TX_Buff[0]=W25X_PageProgram;           //给发送赋值
	SPI1_TX_Buff[1]=(u8)((WriteAddr)>>16);    //给发送赋值
	SPI1_TX_Buff[2]=(u8)((WriteAddr)>>8);     //给发送赋值	
	SPI1_TX_Buff[3]=(u8)WriteAddr;            //给发送赋值
    for(i=0;i<NumByteToWrite;i++)
	{
	    SPI1_TX_Buff[i+4]=pBuffer[i];
	}		
	W25QXX_Write_Enable();                  //不应该在此调用,应该在给SPI1_TX_Buff赋值前调用
	W25QXX_CS=0; 
	//SPI1_DMA_Write_Read(NumByteToWrite+4,0);
	SPI1_DMA_Trans(NumByteToWrite+4);
	W25QXX_CS=1;
    W25QXX_Wait_Busy();					   //等待写入结束
} 

正确的写函数如下:

void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{

	u16 i; 
	W25QXX_Write_Enable();                  //应该在此调用,应该在给SPI1_TX_Buff赋值前调用
    SPI1_TX_Buff[0]=W25X_PageProgram;           //给发送赋值
	SPI1_TX_Buff[1]=(u8)((WriteAddr)>>16);    //给发送赋值
	SPI1_TX_Buff[2]=(u8)((WriteAddr)>>8);     //给发送赋值	
	SPI1_TX_Buff[3]=(u8)WriteAddr;            //给发送赋值
    for(i=0;i<NumByteToWrite;i++)
	{
	    SPI1_TX_Buff[i+4]=pBuffer[i];
	}		
	W25QXX_CS=0; 
	//SPI1_DMA_Write_Read(NumByteToWrite+4,0);
	SPI1_DMA_Trans(NumByteToWrite+4);
	W25QXX_CS=1;
    W25QXX_Wait_Busy();					   //等待写入结束
} 
  1. 对程序测试结果如图1所示:
    图1 测试结果图

  2. 发现一个问题:在进行写入的时候会存在一个问题,如果已经写入内容的地址,再对其进行写入新的内容则会数据发生错误,查了很久,没找到问题所在。偷了个懒,在写入之前把要写入数据的扇区给擦除。

  3. 总结:A 设定好SPI1_RX和SPI1_TX的DMA传输方向
    B 在使用SPI的DMA进行发送的时候,也要将接收到的无用数据进行搬运

  • 9
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 19
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dog345

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值