解决STM32F407VET6使用SPI访问Flash数据返回0xff的问题

STM32F407VET6使用SPI访问Flash数据返回0xff(先写入数据,再读取数据,却返回0xff,但是通过debug却可以正常输出)

看了野火的STM32F103VET6板子的SPI操作Flash的视频,用了自己的STM32F407VET6板子试了一下,出现了点问题,在网上看了很久也没有找出原因,现在问题解决了,就写一篇,如果大家有这种用F4的板子操作的情况,可以参考一下。

出现题目括号中所说的问题,我开始以为是我读取的太快导致数据还有写入进去,就读取了,所以导致读到的数据都是未初始化的flash地址数据0xff。后来加了延时函数也是不行,但是通过debug模式却可以正常读取到写入的数据。其实这个问题很简单,就是遗漏一点。在使用SPI读写flash数据的时候,首先需要进行扇区数据的擦除(擦除的函数,看野火的应该都知道),虽然其他的所有的写入函数中都已经添加了等待写入完成操作;但是,并没有位擦除数据的函数写入一个等待写入完成的一步。因为对数据扇区的擦除操作同样也是写入操作,所以必须需要加上等待写入完成操作,否则就会发现在读取数据的时候全部返回0xff。

下面附上代码(STM32F407VET6的板子,参考野火F103写的,代码在复制的时候格式自己乱了,不过问题不大)。

spi_flash.h

#ifndef _SPI_FLASH_
#define _SPI_FLASH_
#include "stm32f4xx.h"
#include "delay.h"

//spi参数定义
#define FLASH_SPIX                	SPI1
#define FLASH_SPI_APBX_CLOCK      	RCC_APB2PeriphClockCmd//spi1是挂在再APB2上,时钟线使能
#define FLASH_SPI_CLK 							RCC_APB2Periph_SPI1//RCC_APB2PeriphClockCmd的使能参数
#define FLASH_SPI_GPIO_APBX_CLOCK 	RCC_AHB1PeriphClockCmd//给GPIO使能
#define FLASH_SPI_GPIO_CLK        	RCC_AHB1Periph_GPIOB//flash的GPIO引脚是GPIOB 挂在再AHB1上的

#define FLASH_SPI_SCK_PORT					GPIOB
#define FLASH_SPI_SCK_PIN						GPIO_Pin_3

#define FLASH_SPI_MOSI_PORT					GPIOB						
#define FLASH_SPI_MOSI_PIN					GPIO_Pin_5

#define FLASH_SPI_MISO_PORT					GPIOB
#define FLASH_SPI_MISO_PIN					GPIO_Pin_4

#define FLASH_SPI_CS_PORT						GPIOB
#define FLASH_SPI_CS_PIN						GPIO_Pin_0

#define FLASH_SPI_CS_HIGH           GPIO_SetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);
#define FLASH_SPI_CS_LOW           	GPIO_ResetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);


#define CHAN                         0x00//随机数据
#define READ_ID                      0x9f
#define ERASE_SECTOR								 0x20//擦除扇区代码
#define READ_STATUS								   0x05//0x05或者0x35
#define READ_DATA										 0x03//读数据命令
#define WRITE_DATA									 0x02//写数据命令
#define WRITE_ENABLE								 0x06//写使能命令
#define SEND_DATA_LENGTH						 256

void spi_flash_gpio_init(void);
void spi_flash_init(void);
void spi_waitfor_write_end(void);
uint8_t spi_flash_read_data(void);
uint32_t spi_flash_read_id(void);
void spi_erase_sector(uint32_t addr);
void spi_read_data(uint32_t addr, uint8_t *buffer,uint32_t numbytestoread);
void spi_write_data(uint32_t addr, uint8_t *write_buffer,uint32_t numbytestowrite);
void spi_write_enable(void);
#endif

spi_flash.c

#include "spi_flash.h"

//初始化SPI GPIO引脚

void spi_flash_gpio_init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	//使能SPI相关时钟
	FLASH_SPI_GPIO_APBX_CLOCK(FLASH_SPI_GPIO_CLK, ENABLE);
  FLASH_SPI_APBX_CLOCK(FLASH_SPI_CLK, ENABLE);
	
	/**
	*	GPIO的所选用的模式要根据不同信号的开发板进行变动
	*/
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN | FLASH_SPI_MOSI_PIN | FLASH_SPI_MISO_PIN;//F4系列全部配置推挽复用输出就行
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//50MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);//初始化GPIO
	
	//初始化CS引脚,使用软件控制,设置为推挽输出
	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);//初始化GPIO
	
	//这里一定要对引脚进行复用,才能连接上SPI1,否则读取flashID会返回0
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为 SPI1
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4复用为 SPI1
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5复用为 SPI1
	
	//这里只针对SPI口初始化
	//RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1
	//RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1

	FLASH_SPI_CS_HIGH
}

//初始化SPI的结构体
void spi_flash_init(void)
{
	SPI_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//配置波特率预分频值 
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//配置为模式0
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//配置CPHA和CPOL为四种模式之一即可
	SPI_InitStructure.SPI_CRCPolynomial = 0;//不适用CRC
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//八位数据传输
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线全双工
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//高位有限发送
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//作为主机
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//使用软件控制cs
	//写入配置到寄存器
	SPI_Init(FLASH_SPIX,&SPI_InitStructure);
	//使能
	SPI_Cmd(FLASH_SPIX,ENABLE);
	
}

//发送并接收一个字节,接收也要发送数据 才能正常接收
uint8_t spi_flash_send_byte(uint8_t data)
{
	//检查TXE为空的时候再发送数据,缓冲区非空的标志为0
	while(SPI_I2S_GetFlagStatus(FLASH_SPIX,SPI_I2S_FLAG_TXE) == RESET);
	//通过这个while后说明数据BUFFER为空了,就可以发送数据了
	SPI_I2S_SendData(FLASH_SPIX,data);
	//检测RXNE标志为置1表示数据发送完毕
	while(SPI_I2S_GetFlagStatus(FLASH_SPIX,SPI_I2S_FLAG_RXNE) == RESET);
	//返回接收到的数据
	return SPI_I2S_ReceiveData(FLASH_SPIX);
}

uint8_t spi_flash_read_data(void)
{
	return spi_flash_send_byte(CHAN);//随意发送数据
}


//等待flash内部时序完成
void spi_waitfor_write_end(void)
{
	uint8_t status_flag = 0;
	//片选使能
	FLASH_SPI_CS_LOW;
	spi_flash_send_byte(READ_STATUS);
	do{
		//发送一个随机数据,来获取装态返回信息
		status_flag = spi_flash_send_byte(CHAN);
	}while((status_flag & 0x01) == 1 );
	FLASH_SPI_CS_HIGH;
}
//读取设备的id号
uint32_t spi_flash_read_id(void)
{
	uint32_t flash_id;//因为返回的到id是3个八位二进制位
	spi_write_enable();
	
	//片选使能
	FLASH_SPI_CS_LOW;
	spi_flash_send_byte(READ_ID);
	flash_id = spi_flash_send_byte(CHAN);
	flash_id <<= 8;
	flash_id |= spi_flash_send_byte(CHAN);
	flash_id <<= 8;
	flash_id |= spi_flash_send_byte(CHAN);
	
	FLASH_SPI_CS_HIGH;
	return flash_id;
}

//写使能命令
void spi_write_enable(void)
{
	FLASH_SPI_CS_LOW;
	
	spi_flash_send_byte(WRITE_ENABLE);
	
	FLASH_SPI_CS_HIGH;
}

//擦除指定扇区,参数位扇区地址
void spi_erase_sector(uint32_t addr)
{
	spi_write_enable();
	//片选使能
	FLASH_SPI_CS_LOW;
	spi_flash_send_byte(ERASE_SECTOR);
	spi_flash_send_byte((addr>>16)&0x0f);
	spi_flash_send_byte((addr>>8)&0x0f);
	spi_flash_send_byte(addr&0x0f);
	
	FLASH_SPI_CS_HIGH;
	//这里擦除数据一定要等待一下 不然会擦除不成功,导致后面的写入失败
	spi_waitfor_write_end();
}


//读取flash的内容
void spi_read_data(uint32_t addr, uint8_t *buffer,uint32_t numbytestoread)
{
	spi_write_enable();
	spi_waitfor_write_end();
	//片选使能
	FLASH_SPI_CS_LOW;
	spi_flash_send_byte(READ_DATA);//发送读命令
	spi_flash_send_byte((addr>>16)&0x0f);//将要读的24位地址从高到低发送
	spi_flash_send_byte((addr>>8)&0x0f);
	spi_flash_send_byte(addr&0x0f);
	
	while(numbytestoread --)
	{
		*buffer = spi_flash_send_byte(CHAN);
		buffer++;
	}
	FLASH_SPI_CS_HIGH;
	spi_waitfor_write_end();
}


//向flash进行写入数据
void spi_write_data(uint32_t addr, uint8_t *write_buffer,uint32_t numbytestowrite)
{
	//写数据之前要使能写命令
	spi_write_enable();
	spi_waitfor_write_end();
	//片选使能
	FLASH_SPI_CS_LOW;
	spi_flash_send_byte(WRITE_DATA);//发送写命令
	spi_flash_send_byte((uint8_t)((addr>>16)&0x0f));//将要读的24位地址从高到低发送
	spi_flash_send_byte((uint8_t)((addr>>8)&0x0f));
	spi_flash_send_byte((uint8_t)(addr&0x0f));
	
	while(numbytestowrite --)
	{
		spi_flash_send_byte(*write_buffer);
		write_buffer++;
	}
	FLASH_SPI_CS_HIGH;
	spi_waitfor_write_end();
}

在测试程序的时候加了个按键操作,可以自行修改主程序
key.h

#ifndef __KEY_H
#define __KEY_H	 
#include "sys.h" 

/*下面的方式是通过直接操作库函数方式读取IO*/
#define KEY0 		GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) //PE4
#define KEY1 		GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)	//PE3 
#define WK_UP 	GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)	//PA0


/*下面方式是通过位带操作方式读取IO*/
/*
#define KEY0 		PEin(4)   	//PE4
#define KEY1 		PEin(3)		//PE3 
#define KEY2 		PEin(2)		//P32
#define WK_UP 	PAin(0)		//PA0
*/


#define KEY0_PRES 	1
#define KEY1_PRES	2
//#define KEY2_PRES	3
#define WKUP_PRES   4

void KEY_Init(void);	//IO初始化
u8 KEY_Scan(u8);  		//按键扫描函数	

#endif

key.c

#include "key.h"
#include "delay.h" 

//按键初始化函数
void KEY_Init(void)
{
	
	GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOA,GPIOE时钟
 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4; //KEY2 KEY3对应引脚
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100M
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE2,3,4
	
	 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//WK_UP对应引脚PA0
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN ;//下拉
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA0
 
} 
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,KEY0按下
//2,KEY1按下
//3,KEY2按下 
//4,WKUP按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY2>WK_UP!!
u8 KEY_Scan(u8 mode)
{	 
	static u8 key_up=1;//按键按松开标志
	if(mode)key_up=1;  //支持连按		  
	if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
	{
		delay_ms(10);//去抖动 
		key_up=0;
		if(KEY0==0)return 1;
		else if(KEY1==0)return 2;
//		else if(KEY2==0)return 3;
		else if(WK_UP==1)return 4;
	}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1; 	    
 	return 0;// 无按键按下
}

mian.c

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "spi_flash.h"
#include "key.h"


//定义为全局变量,就不会占用栈的存储空间,因为4096超过限制,会导致程序卡死
uint8_t read_buffer[4096];
uint8_t write_buffer[4096];

int main(void)
{ 
	uint32_t chan_id;
	uint8_t key_status;
	int i;
	delay_init(168);		  //初始化延时函数
	LED_Init();		        //初始化LED端口
	KEY_Init();
	
	uart_init(115200);//初始化串口1
	printf("\r\n测试SPI读写FLASH功能\r\n");
	
	spi_flash_gpio_init();
	spi_flash_init();
	chan_id = spi_flash_read_id();
	printf("\r\n收到的chen_ID:%x\r\n",chan_id);
	//擦除数据
	spi_erase_sector(0);
	//等待擦除成功,等待步骤在擦除数据操作中已经写入,下面可以不写
	spi_waitfor_write_end();
	//写入数据
	for(int i = 0 ;i<SEND_DATA_LENGTH ;i++)
	{
		write_buffer[i] = i;
	}
	spi_write_data(0,write_buffer,SEND_DATA_LENGTH);
	spi_waitfor_write_end();//其实再write中已经写入了等待,可以不加
	
	

	while(1)
	{
		key_status = KEY_Scan(0);
		if(key_status)
		{
			GPIO_ResetBits(GPIOA,GPIO_Pin_6);
			spi_read_data(0,read_buffer,SEND_DATA_LENGTH);
			for(i = 0;i<SEND_DATA_LENGTH;i++)
			{
				printf(" 0x%x ",read_buffer[i]);
				if((i != 0) && (i%10 == 0))
				{
					printf("\r\n");
				}
			}
		}
//	GPIO_ResetBits(GPIOA,GPIO_Pin_6);  //LED0对应引脚GPIOA.6拉低,亮  等同LED0=0;
//	GPIO_SetBits(GPIOA,GPIO_Pin_7);   //LED1对应引脚GPIOA.7拉高,灭 等同LED1=1;
//	delay_ms(500);  		   //延时500ms
//	GPIO_SetBits(GPIOA,GPIO_Pin_6);	   //LED0对应引脚GPIOA.6拉高,灭  等同LED0=1;
//	GPIO_ResetBits(GPIOA,GPIO_Pin_7); //LED1对应引脚GPIOA.7拉低,亮 等同LED1=0;
//	delay_ms(500);                     //延时300ms
	}
}

物联沃分享整理
物联沃-IOTWORD物联网 » 解决STM32F407VET6使用SPI访问Flash数据返回0xff的问题

发表评论