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文章来源:https://www.toymoban.com/news/detail-610842.html
#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文章来源地址https://www.toymoban.com/news/detail-610842.html
#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
}
}
到了这里,关于STM32F407VET6使用SPI访问Flash数据返回0xff(先写入数据,再读取数据,却返回0xff,但是通过debug却可以正常输出)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!