STM32F407使用SPI的DMA来读取外部FLASH—W25Q16
- SPI功能是常用的片内外设,以前调试经常使用的其正常的读写功能,即不用DMA。当进行读取或者写入大数据量的操作的时候,使用DMA会对读写速度进行一定提高。这次利用开发板上的板载外部FLASHW25Q16来进行SPI的DMA进行调试。记录一下调试过程。
- 采用的MCU为STM32F407VET6,FLASH的具体型号为W25Q16的内存大小为2M的外部FLASH。电路连接上,采用STM32F4的SPI1和FLASH进行通信,IO口为PB3,PB4,PB5。
- 从STM32F4的中文参考手册来看,SPI1使用DMA1,其中SPI_TX是属于数据流3,SPI_RX属于数据流2,这两个数据流都是使用DMA1的通道3进行搬运数据。其中TX的搬运方向为内部存储器(程序中的定义的数组)到片内外设(SPI1->DR),RX的搬运方向为片内外设(SPI1->DR)到内部存储器(程序中的定义的数组)。因此根据这些可以对SPI以及其DMA进行初始化。
- 对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);//启动传输
}
- 对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;//通道选择
}
- 对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;//通道选择
}
- 接下来就是用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完成标
}
- 接下来就是通过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所示:
-
发现一个问题:在进行写入的时候会存在一个问题,如果已经写入内容的地址,再对其进行写入新的内容则会数据发生错误,查了很久,没找到问题所在。偷了个懒,在写入之前把要写入数据的扇区给擦除。
-
总结:A 设定好SPI1_RX和SPI1_TX的DMA传输方向
B 在使用SPI的DMA进行发送的时候,也要将接收到的无用数据进行搬运