Flash基础入门之SPI_FLASH
小标 2018-08-24 来源 : 阅读 2194 评论 0

摘要:本文主要向大家介绍了Flash基础入门之SPI_FLASH,通过具体的内容向大家展现,希望对大家学习Flash基础入门有所帮助。

本文主要向大家介绍了Flash基础入门之SPI_FLASH,通过具体的内容向大家展现,希望对大家学习Flash基础入门有所帮助。

读取JEDEC ID(FLASH型号)

程序清单:
bsp_usart.h

#ifndef __BSP_USART_H__
#define __BSP_USART_H__

#include "stm32f10x.h"
#include "stdio.h"

// ----------------------- 串口1-USART1
// 使用哪个串口(串口1..5)
#define  DEBUG_USARTx                   USART1     
// APB2串口的同步时钟
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1 
// APB2系统时钟(因为串口USART1是挂载到APB2总线上的,所以要打开APB2总线的时钟)
#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd 
// 串口通信的波特率
#define  DEBUG_USART_BAUDRATE           19200


// ----------------------- USART GPIO 引脚宏定义
// GPIO引脚
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA) 
// APB2系统时钟(因为串口USART1是挂载到APB2总线上的,所以要打开APB2总线的时钟)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd 

// GPIO引脚,发送接PA9,接收接PA10   
#define  DEBUG_USART_TX_GPIO_PORT       GPIOA     
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10

#define  DEBUG_USART_IRQ                USART1_IRQn
#define  DEBUG_USART_IRQHandler         USART1_IRQHandler

/* 串口调试配置函数:配置串口的相关参数,使能串口 */
void DEBUG_USART_Config(void);

/* 发送一个字节 */
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t ch);

/* 发送字符串 */
void Usart_SendString(USART_TypeDef* pUSARTx, char* str);

#endif  /* __BSP_USART_H__ */


 bsp_usart.c

#include "./usart/bsp_usart.h"

/* 串口中断配置函数 */
static void NVIC_Configuration(void)
{
 NVIC_InitTypeDef NVIC_InitStructure;
  
 /* 嵌套向量中断控制器组选择 */
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
 /* 配置USART为中断源 */
 NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
 /* 抢断优先级*/
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
 /* 子优先级 */
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
 /* 使能中断 */
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 
 /* 初始化配置NVIC */
 NVIC_Init(&NVIC_InitStructure);
}

/* 串口调试配置函数:配置串口的相关参数,使能串口 */
void DEBUG_USART_Config(void)
{
 /* 结构体变量声明 */
 GPIO_InitTypeDef GPIO_InitStructure;  // GPIO
 USART_InitTypeDef USART_InitStructure;  // USART
 
 /* ------------ 第一步:初始化GPIO */
 // 打开串口GPIO的时钟
 DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE); 
 
 // 将USART Tx的GPIO配置为推挽复用模式
 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;  // 引脚
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    // 模式
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   // 速率
 GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // 初始化结构体
 
 // 将USART Rx的GPIO配置为浮空输入模式
 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
 
 /* ------------ 第二步:配置串口的初始化结构体 */
 // 打开串口外设的时钟
 DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
 /* 配置串口的工作参数 */
 // 波特率
 USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
 // 针数据字长
 USART_InitStructure.USART_WordLength = USART_WordLength_8b;
 // 停止位
 USART_InitStructure.USART_StopBits = USART_StopBits_1;
 // 校验位
 USART_InitStructure.USART_Parity = USART_Parity_No ;
 // 硬件流控制
 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
 // 工作模式,收发一起
 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
 // 完成串口的初始化配置
 USART_Init(DEBUG_USARTx, &USART_InitStructure);
 
 /* -------------------------------------------------------- */
 // 串口中断优先级配置
 //NVIC_Configuration(); 
 
 // 使能串口接收中断
 //USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
 /* -------------------------------------------------------- */
 
 /* ------------ 第三步:使能串口 */
 USART_Cmd(DEBUG_USARTx, ENABLE);
}

/* 发送一个字节 */
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t ch)
{
 /* 发送一个字节数据到USART */
 USART_SendData(pUSARTx, ch);
  
 /* 等待发送数据寄存器为空 */
 while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); 
}

/* 发送字符串 */
void Usart_SendString(USART_TypeDef* pUSARTx, char* str)
{
 unsigned int k=0;
 do 
 {
  Usart_SendByte(pUSARTx, *(str + k));
  k++;
 } while(*(str + k)!='\0');
  
 /* 等待发送完成 */
 while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC) == RESET);
}

/* 重定向c库函数printf到串口,重定向后可使用printf函数 */
int fputc(int ch, FILE *f)
{
 /* 发送一个字节数据到串口 */
 USART_SendData(DEBUG_USARTx, (uint8_t) ch);
  
 /* 等待发送完毕 */
 while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);  
 
 return (ch);
}

/* 重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数 */
int fgetc(FILE *f)
{
 /* 等待串口输入数据 */
 while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

 return (int)USART_ReceiveData(DEBUG_USARTx);
}


 bsp_spi_flash.h

#ifndef __BSP_SPI_FLASH_H__
#define __BSP_SPI_FLASH_H__

#include "stm32f10x.h"
#include "stdio.h"

/* =================== GPIO引脚定义 ==================== */
#define SPI_FLASH_GPIO_CLK            RCC_APB2Periph_GPIOA 
#define SPI_FLASH_GPIO_APBxClkCmd  RCC_APB2PeriphClockCmd

/* =================== SPI外设接口定义 ==================== */
#define SPI_FLASHx      SPI1
#define SPI_FLASH_CLK                 RCC_APB2Periph_SPI1
#define SPI_FLASH_APBxClkCmd          RCC_APB2PeriphClockCmd      

/* =================== SPI-FLASH引脚定义 ==================== */
// CS(NSS)引脚
#define SPI_FLASH_NSS_GPIO_PORT   GPIOA
#define SPI_FLASH_NSS_GPIO_PIN   GPIO_Pin_4

// SCK引脚
#define SPI_FLASH_SCK_GPIO_PORT   GPIOA
#define SPI_FLASH_SCK_GPIO_PIN   GPIO_Pin_5

// MISO引脚
#define SPI_FLASH_MISO_GPIO_PORT  GPIOA
#define SPI_FLASH_MISO_GPIO_PIN   GPIO_Pin_6

// MOSI引脚
#define SPI_FLASH_MOSI_GPIO_PORT  GPIOA
#define SPI_FLASH_MOSI_GPIO_PIN   GPIO_Pin_7

/* =================== 常量定义 ==================== */
#define SPI_FLASH_WAIT_TIMEOUT   10000  // SPI-FLASH超时时间

/***************** 指令定义-开头 *****************/
#define W25X_WriteEnable         0x06 
#define W25X_WriteDisable         0x04 
#define W25X_ReadStatusReg       0x05 
#define W25X_WriteStatusReg       0x01 
#define W25X_ReadData           0x03 
#define W25X_FastReadData         0x0B 
#define W25X_FastReadDual         0x3B 
#define W25X_PageProgram         0x02 
#define W25X_BlockErase          0xD8 
#define W25X_SectorErase         0x20 
#define W25X_ChipErase          0xC7 
#define W25X_PowerDown          0xB9 
#define W25X_ReleasePowerDown      0xAB 
#define W25X_DeviceID           0xAB 
#define W25X_ManufactDeviceID     0x90 
#define W25X_JedecDeviceID       0x9F

/* =================== 函数宏定义 ==================== */
#define SPI_FLASH_NSS_LOW()    GPIO_ResetBits(SPI_FLASH_NSS_GPIO_PORT,SPI_FLASH_NSS_GPIO_PIN);
#define SPI_FLASH_NSS_HIGH()   GPIO_SetBits(SPI_FLASH_NSS_GPIO_PORT,SPI_FLASH_NSS_GPIO_PIN);


/* =================== SPI-FLASH相关函数 ==================== */
/* SPI-FLASH初始化 */
void SPI_FLASH_Init(void);

/* 发送一个字节 */
uint8_t SPI_FLASH_Send_Data(uint8_t data);

/* 接收一个字节 */
uint8_t SPI_FLASH_Receive_Data(void);

/* 读取一个字节 */
uint32_t SPI_FLASH_Read_JEDEC_ID(void);

#endif  /* __BSP_SPI_FLASH_H__ */


 bsp_spi_flash.c

#include "./spi/bsp_spi_flash.h"

uint16_t time_out;

/* SPI-FLASH初始化 */
void SPI_FLASH_Init(void)
{
 // 结构体变量声明
 GPIO_InitTypeDef GPIO_InitStructure;  // GPIO
 SPI_InitTypeDef SPI_InitStructure;   // SPI
 
 /* =========================== 第一步:打开时钟 =========================== */
 // 打开SPI GPIO的时钟
 SPI_FLASH_GPIO_APBxClkCmd(SPI_FLASH_GPIO_CLK, ENABLE); 
 
 // 打开SPI外设的时钟
 SPI_FLASH_APBxClkCmd(SPI_FLASH_CLK, ENABLE);
 
 /* =========================== 第二步:配置引脚 =========================== */
 // 配置CS(NSS)引脚
 GPIO_InitStructure.GPIO_Pin = SPI_FLASH_NSS_GPIO_PIN;  
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   
 GPIO_Init(SPI_FLASH_NSS_GPIO_PORT, &GPIO_InitStructure); 
 
 // 配置SCK引脚
 GPIO_InitStructure.GPIO_Pin = SPI_FLASH_SCK_GPIO_PIN;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_Init(SPI_FLASH_SCK_GPIO_PORT, &GPIO_InitStructure);
 
 // MISO引脚
 GPIO_InitStructure.GPIO_Pin = SPI_FLASH_MISO_GPIO_PIN;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
 GPIO_Init(SPI_FLASH_MISO_GPIO_PORT, &GPIO_InitStructure);
 
 // MOSI引脚
 GPIO_InitStructure.GPIO_Pin = SPI_FLASH_MOSI_GPIO_PIN;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_Init(SPI_FLASH_MOSI_GPIO_PORT, &GPIO_InitStructure);
 
 /* =========================== 第三步:配置SPI工作模式 =========================== */
 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;  // 二分频
 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;  // 第一边沿采样
 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;   // CPOL和CPHA都为0,即模式0
 SPI_InitStructure.SPI_CRCPolynomial = 0;   // 这个参数不要求,但也要配置,否则报错
 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8个数据位
 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 双线全双工
 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 高位先行
 SPI_InitStructure.SPI_Mode = SPI_Mode_Master;  // STM32配置成主机,FLASH等其他外设为从机
 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;   // 软件控制
 // SPI初始化
 SPI_Init(SPI_FLASHx, &SPI_InitStructure);
 
 /* =========================== 第四步:使能SPI =========================== */
 SPI_Cmd(SPI_FLASHx, ENABLE);
}

/* 出错时调用回调函数返回错误代码(错误信息) */
uint8_t SPI_Timeout_CallBack(uint8_t data)
{
 printf("\r\n SPI检测超时,错误代码:%d \r\n", data);
 return 0;
}

/* 
功能:发送一个字节 
data:要发送的数据
返回:发送过程中接收回来的数据
*/
uint8_t SPI_FLASH_Send_Data(uint8_t data)
{
 uint8_t read_temp;
 
 // 检测TXE
 time_out = SPI_FLASH_WAIT_TIMEOUT;
 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) // 发送缓冲区为空,可以向里面发送数据
 {
  if(time_out-- == 0)  // 超时
  {
   return SPI_Timeout_CallBack(1);
  }
 }
 SPI_I2S_SendData(SPI1, data); // 发送数据
 
 // 检测RXNE
 time_out = SPI_FLASH_WAIT_TIMEOUT;
 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) // 接收缓冲区为空,可以读取里面的数据
 {
  if(time_out-- == 0)  // 超时
  {
   return SPI_Timeout_CallBack(2);
  }
 }
 read_temp = (uint8_t)SPI_I2S_ReceiveData(SPI1);   // 接收数据
 
 return read_temp;
}

/* 
功能:接收一个字节 
返回:接收到的数据
说明:根据时序图写
*/
uint8_t SPI_FLASH_Receive_Data(void)
{
 return SPI_FLASH_Send_Data(0xFF);
}

/* 读取一个字节 */
uint32_t SPI_FLASH_Read_JEDEC_ID(void)
{
 uint32_t id;
 
 // 拉低NSS,开始读取数据
 SPI_FLASH_NSS_LOW();
 
 // 发送一个数据,触发SCK时钟,这个数据不作为有效数据传输
 SPI_FLASH_Send_Data(W25X_JedecDeviceID);
 
 // 读取数据
 id = SPI_FLASH_Receive_Data();
 
 // id左移八位,腾出低八位继续接收数据
 id <<= 8;
 // 继续接收数据
 id |= SPI_FLASH_Receive_Data();
 
 // id左移八位,腾出低八位继续接收数据
 id <<= 8;
 // 继续接收数据
 id |= SPI_FLASH_Receive_Data();
 
 // 拉高NSS,结束读取数据
 SPI_FLASH_NSS_HIGH();
 
 // 返回读取的id
 return id;
}


 main.c

#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./spi/bsp_spi_flash.h"

int main(void)

 uint32_t W25X_JEDEC_DEVICE_ID = 0;
 
 // 串口初始化
 DEBUG_USART_Config();
 
 // SPI-FLASH初始化
 SPI_FLASH_Init();
 
 // 随便打印一句话方便调试
 printf("\r\n 欢迎使用秉火STM32F103RCT6开发板-SPI-FLASH实验 \r\n");
 
 // 读取数据
 W25X_JEDEC_DEVICE_ID = SPI_FLASH_Read_JEDEC_ID();
 
 // 输出数据
 printf("\r\n W25X_JEDEC_DEVICE_ID = %x \r\n", W25X_JEDEC_DEVICE_ID);
}


实验结果:

 
扇区擦除操作
擦除后,被擦除的扇区数据都是0xFF,即所有位都为1
程序清单:继承上面的程序
bsp_spi_flash.h

/* 擦除一个扇区 */
void SPI_FLASH_Erase_Sector(uint32_t addr);


 bsp_spi_flash.c

/* 功能:读取状态寄存器,用于检测忙碌还是空闲 */
void SPI_FLASH_WaitForWriteEnd(void)
{
 uint8_t status;
 
 // 拉低NSS,开始读取数据
 SPI_FLASH_NSS_LOW();
 
 // 发送一个数据,触发SCK时钟,这个数据不作为有效数据传输
 SPI_FLASH_Send_Data(W25X_ReadStatusReg);  // 发送05h指令进行读取状态操作
 
 // 读取状态
 do
 {
  status = SPI_FLASH_Receive_Data();
 } while(status & 0x01);  // BUSY标志S7-S0,如果S0这个位是1表示忙碌,0表示空闲,所以只要检测这一位即可  
 
 // 拉高NSS,结束读取数据
 SPI_FLASH_NSS_HIGH();
}

/* 
功能:擦除一个扇区 
addr:扇区地址
说明:根据SPI说明文档,在擦除或写入之前,要先使能写命令
*/
void SPI_FLASH_Erase_Sector(uint32_t addr)
{
 // 等待其他操作完成之后再进行擦除操作
 SPI_FLASH_WaitForWriteEnd();
 
 // 在擦除之前进行使能操作
 SPI_FLASH_Write_Enable();
 
 /* 发送要擦出的二十四位地址码 */
 // 拉低NSS,开始读取数据
 SPI_FLASH_NSS_LOW();
 
 // 发送一个数据,触发SCK时钟,这个数据不作为有效数据传输
 SPI_FLASH_Send_Data(W25X_SectorErase);  // 发送20h指令进行擦除操作
 
 // 开始发送地址:一次发送八位,移位操作
 SPI_FLASH_Send_Data((addr & 0xFF0000) >> 16);
 SPI_FLASH_Send_Data((addr & 0xFF00) >> 8);
 SPI_FLASH_Send_Data((addr & 0xFF));
 
 // 拉高NSS,结束读取数据
 SPI_FLASH_NSS_HIGH();
 
 // 检测是否已经擦除完
 SPI_FLASH_WaitForWriteEnd();
}


 main.c

#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./spi/bsp_spi_flash.h"

int main(void)

 uint32_t W25X_JEDEC_DEVICE_ID = 0;
 
 /* ============================ 初始化 =========================== */
 // 串口初始化
 DEBUG_USART_Config();
 
 // SPI-FLASH初始化
 SPI_FLASH_Init();
 
 /* ============================ 读取JEDEC ID =========================== */
 // 随便打印一句话方便调试
 printf("\r\n 欢迎使用秉火STM32F103RCT6开发板-SPI-FLASH实验 \r\n");
 
 // 读取数据
 W25X_JEDEC_DEVICE_ID = SPI_FLASH_Read_JEDEC_ID();
 
 // 输出数据
 printf("\r\n W25X_JEDEC_DEVICE_ID = %x \r\n", W25X_JEDEC_DEVICE_ID);
 
 /* ============================ 擦除操作 =========================== */
 /* 
 这里只要擦除第0扇区就足够做实验了,禁止擦除其他扇区,因为板子在出厂之前已经
 设置好了一些实验的数据格式,如果擦除了后面的实验就不能做了,恢复也麻烦。     说明:每一个区有4096个字节
 */
 SPI_FLASH_Erase_Sector(0); 
 printf("\r\n 擦除完毕! \r\n");
}


实验结果:

 
读取SPI扇区的数据(读取刚刚擦除的扇区数据)
bsp_spi_flash.h

/* 读取SPI的数据 */
void SPI_FLASH_Read_Buffer(uint32_t addr, uint8_t* data, uint32_t size);


 bsp_spi_flash.c

/* 
功能:读取SPI的数据
addr:扇区地址
*data:将读取到的数据放到指针变量中存储起来
size:读取多少个字节
*/
void SPI_FLASH_Read_Buffer(uint32_t addr, uint8_t* data, uint32_t size)
{
 uint32_t i;
 
 // 拉低NSS,开始读取数据
 SPI_FLASH_NSS_LOW();
 
 // 发送一个数据,触发SCK时钟,这个数据不作为有效数据传输
 SPI_FLASH_Send_Data(W25X_ReadData);  // 发送03h指令进行读操作
 
 // 开始发送地址:一次发送八位,移位操作
 SPI_FLASH_Send_Data((addr & 0xFF0000) >> 16);
 SPI_FLASH_Send_Data((addr & 0xFF00) >> 8);
 SPI_FLASH_Send_Data((addr & 0xFF));
 
 // 开始读取操作
 for(i=0; i<size; i++)
 {
  // 读取数据
  *data = SPI_FLASH_Receive_Data();
  
  // 指针指向下一个地址
  data ++;  
 }
 
 // 拉高NSS,结束读取数据
 SPI_FLASH_NSS_HIGH();
}


main.c

#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./spi/bsp_spi_flash.h"

/*
将读取的数据用一个数组接收,这个数组很大,不要放到一个函数内部,会占用板子的大部分空间,
STM32F103RCT6板子默认的栈内存空间只有1024,这样会导致内存溢出。
*/
uint8_t read_temp[4096] = {0};

int main(void)

 uint32_t W25X_JEDEC_DEVICE_ID = 0;
 uint32_t i;
 
 /* ============================ 初始化 =========================== */
 // 串口初始化
 DEBUG_USART_Config();
 
 // SPI-FLASH初始化
 SPI_FLASH_Init();
 
 /* ============================ 读取JEDEC ID =========================== */
 // 随便打印一句话方便调试
 printf("\r\n欢迎使用秉火STM32F103RCT6开发板-SPI-FLASH实验 \r\n");
 
 // 读取数据
 W25X_JEDEC_DEVICE_ID = SPI_FLASH_Read_JEDEC_ID();
 
 // 输出数据
 printf("\r\nW25X_JEDEC_DEVICE_ID = %x \r\n", W25X_JEDEC_DEVICE_ID);
 
 /* ============================ 擦除 =========================== */
 /* 
 这里只要擦除第0扇区就足够做实验了,禁止擦除其他扇区,因为板子在出厂之前已经
 设置好了一些实验的数据格式,如果擦除了后面的实验就不能做了,恢复也麻烦。
 说明:每一个区有4096个字节
 */
 SPI_FLASH_Erase_Sector(0); 
 printf("\r\n擦除完毕! \r\n");
 
 /* ============================ 读取SPI扇区数据 =========================== */
 // 读取刚刚擦除的SPI扇区数据,正常情况下擦除完的数据应该全部为1,即十六进制的F
 SPI_FLASH_Read_Buffer(0, read_temp, sizeof(read_temp));
 
 // 打印数据
 printf("\r\n读取刚刚擦除完毕的第0扇区的数据如下:\r\n");
 for(i=0; i<sizeof(read_temp); i++)
 {
  printf("%x ", read_temp[i]);
 }
}


实验结果:读取到的4096个字节全部都是ff,说明刚刚擦除成功了,但还有一种可能就是出错的时候也会返回ff,这一点在下面的write写函数中会证明。

 
往SPI扇区中写入数据(页写入,Page Write,一次最多写入256个字节)
 继承上面的程序
bsp_spi_flash.h

/* 向SPI扇区写入数据(一次最多写入256个字节) */
void SPI_FLASH_Page_Write(uint32_t addr, uint8_t* data, uint32_t size);


 bsp_spi_flash.c

/* 
功能:向SPI扇区写入数据(一次最多写入256个字节)
addr:SPI扇区地址
data:要写入的数据
size:要写入多少个字节数据,size<=256
*/
void SPI_FLASH_Page_Write(uint32_t addr, uint8_t* data, uint32_t size)
{
 uint32_t i;
 
 if(size > 256)
 {
  printf("\r\nPage Write函数不能写入超过256个字节数据\r\n");
  return;
 }
 
 // 等待其他操作完成之后再进行写操作
 SPI_FLASH_WaitForWriteEnd();
 
 // 在写之前进行写使能操作
 SPI_FLASH_Write_Enable();
 
 /* 发送要擦出的二十四位地址码 */
 // 拉低NSS,开始写数据
 SPI_FLASH_NSS_LOW();
 
 // 发送一个数据,触发SCK时钟,这个数据不作为有效数据传输
 SPI_FLASH_Send_Data(W25X_PageProgram);  // 发送02h指令进行写入操作
 
 // 开始发送地址:一次发送八位,移位操作
 SPI_FLASH_Send_Data((addr & 0xFF0000) >> 16);
 SPI_FLASH_Send_Data((addr & 0xFF00) >> 8);
 SPI_FLASH_Send_Data((addr & 0xFF));
 
 // 开始写入数据
 for(i=0; i<size; i++)
 {
  // 会在外部定义一个数组
  SPI_FLASH_Send_Data(*data);
  
  // 指针自增,指向下一个数据地址
  data++;
 }
 
 // 拉高NSS,结束写数据
 SPI_FLASH_NSS_HIGH();
 
 // 检测是否已经写完
 SPI_FLASH_WaitForWriteEnd();
}


 main.c

#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./spi/bsp_spi_flash.h"

/*
将读取的数据用一个数组接收,这个数组很大,不要放到一个函数内部,会占用板子的大部分空间,
STM32F103RCT6板子默认的栈内存空间只有1024,这样会导致内存溢出。
*/
uint8_t read_temp[256] = {0};

// Page Write函数写入的数据
uint8_t write_temp[256] = {0};

int main(void)

 uint32_t W25X_JEDEC_DEVICE_ID = 0;
 uint32_t i, j;
 
 /* ============================ 初始化 =========================== */
 // 串口初始化
 DEBUG_USART_Config();
 
 // SPI-FLASH初始化
 SPI_FLASH_Init();
 
 /* ============================ 读取JEDEC ID =========================== */
 // 随便打印一句话方便调试
 printf("\r\n欢迎使用秉火STM32F103RCT6开发板-SPI-FLASH实验\r\n");
 
 // 读取数据
 W25X_JEDEC_DEVICE_ID = SPI_FLASH_Read_JEDEC_ID();
 
 // 输出数据
 printf("\r\nW25X_JEDEC_DEVICE_ID = %x\r\n", W25X_JEDEC_DEVICE_ID);
 
 /* ============================ 擦除 =========================== */
 /* 
 这里只要擦除第0扇区就足够做实验了,禁止擦除其他扇区,因为板子在出厂之前已经
 设置好了一些实验的数据格式,如果擦除了后面的实验就不能做了,恢复也麻烦。
 说明:每一个区有4096个字节
 */
 SPI_FLASH_Erase_Sector(0); 
 printf("\r\n擦除完毕!\r\n");
 
 /* ============================ 页写入:Page Write =========================== */
 // 初始化要写入的数据
 for(j=0; j<sizeof(write_temp); j++)
 {
  write_temp[j] = j;
 }
 // 开始写入数据,最多写入256个字节
 SPI_FLASH_Page_Write(0, write_temp, sizeof(write_temp));
 printf("\r\n写入完毕!\r\n");
 
 /* ============================ 读取SPI扇区数据 =========================== */
 // 读取刚刚擦除的SPI扇区数据,正常情况下擦除完的数据应该全部为1,即十六进制的F
 SPI_FLASH_Read_Buffer(0, read_temp, sizeof(read_temp));
 
 // 打印数据
 printf("\r\n读取到的第0扇区的数据如下:\r\n");
 for(i=0; i<sizeof(read_temp); i++)
 {
  printf("%x ", read_temp[i]);
 }
}


实验结果:

把写入的字节数改成大于256个字节,再测试:

uint8_t read_temp[300] = {0};

// Page Write函数写入的数据
uint8_t write_temp[300] = {0};


 
写入任意多个字节
bsp_spi_flash.h

/* 向SPI扇区写入数据(写入任意多个字节) */
void SPI_FLASH_Write_Buffer(uint32_t addr, uint8_t* data, uint32_t size);


 bsp_spi_flash.c

/* 
功能:向SPI扇区写入数据(写入任意多个字节)
addr:SPI扇区地址
data:要写入的数据
size:要写入多少个字节数据
*/
void SPI_FLASH_Write_Buffer(uint32_t addr, uint8_t* data, uint32_t size)
{
 uint32_t i;
 
 for(i=0; i<size; i++)
 {
  // 地址对齐:和EEPROM一样,在每一页开头需要重新发送一次时钟,重新开始写入
  if((i == 0) || (addr % 8 == 0))
  {
   // 拉高NSS,结束写数据
   SPI_FLASH_NSS_HIGH();
   
   // 检测是否已经写完
   SPI_FLASH_WaitForWriteEnd();
   
   // 等待其他操作完成之后再进行写操作
   SPI_FLASH_WaitForWriteEnd();
   
   // 在写之前进行写使能操作
   SPI_FLASH_Write_Enable();
   
   /* 发送要擦出的二十四位地址码 */
   // 拉低NSS,开始写数据
   SPI_FLASH_NSS_LOW();
   
   // 发送一个数据,触发SCK时钟,这个数据不作为有效数据传输
   SPI_FLASH_Send_Data(W25X_PageProgram);  // 发送02h指令进行写入操作
   
   // 开始发送地址:一次发送八位,移位操作
   SPI_FLASH_Send_Data((addr & 0xFF0000) >> 16);
   SPI_FLASH_Send_Data((addr & 0xFF00) >> 8);
   SPI_FLASH_Send_Data((addr & 0xFF));
  }
  
  // 开始写入数据
  SPI_FLASH_Send_Data(*data);
  
  // 指针自增,指向下一个数组数据
  data++;
  
  // 地址自增,指向下一个数据地址
  addr++;
 }
 
 // 拉高NSS,结束写数据
 SPI_FLASH_NSS_HIGH();
 
 // 检测是否已经写完
 SPI_FLASH_WaitForWriteEnd();
}


 main.c

#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./spi/bsp_spi_flash.h"

#define SIZE 256

/*
将读取的数据用一个数组接收,这个数组很大,不要放到一个函数内部,会占用板子的大部分空间,
STM32F103RCT6板子默认的栈内存空间只有1024,这样会导致内存溢出。
*/
uint8_t read_temp[SIZE] = {0};

// Page Write函数写入的数据
uint8_t write_temp[SIZE] = {0};

int main(void)

 uint32_t W25X_JEDEC_DEVICE_ID = 0;
 uint32_t i, j, s;
 
 /* ============================ 初始化 =========================== */
 // 串口初始化
 DEBUG_USART_Config();
 
 // SPI-FLASH初始化
 SPI_FLASH_Init();
 
 /* ============================ 读取JEDEC ID =========================== */
 // 随便打印一句话方便调试
 printf("\r\n欢迎使用秉火STM32F103RCT6开发板-SPI-FLASH实验\r\n");
 
 // 读取数据
 W25X_JEDEC_DEVICE_ID = SPI_FLASH_Read_JEDEC_ID();
 
 // 输出数据
 printf("\r\nW25X_JEDEC_DEVICE_ID = %x\r\n", W25X_JEDEC_DEVICE_ID);
 
 /* ============================ 擦除 =========================== */
 /* 
 这里只要擦除第0扇区就足够做实验了,禁止擦除其他扇区,因为板子在出厂之前已经
 设置好了一些实验的数据格式,如果擦除了后面的实验就不能做了,恢复也麻烦。
 说明:每一个区有4096个字节
 */
 SPI_FLASH_Erase_Sector(0); 
 printf("\r\n擦除完毕\r\n");
 
 /* ============================ 写入 =========================== */
 // 初始化要写入的数据
 for(j=0; j<SIZE; j++)
 {
  write_temp[j] = j;
 }
 // 开始写入数据
 //SPI_FLASH_Page_Write(0, write_temp, SIZE); // 最多写入256个字节
 SPI_FLASH_Write_Buffer(0, write_temp, SIZE);
 printf("\r\n写入完毕\r\n");
 
 /* ============================ 读取SPI扇区数据 =========================== */
 // 读取刚刚擦除的SPI扇区数据,正常情况下擦除完的数据应该全部为1,即十六进制的F
 SPI_FLASH_Read_Buffer(0, read_temp, SIZE);
 
 // 打印数据
 printf("\r\n读取到的第0扇区的数据如下:\r\n");
 for(i=0; i<SIZE; i++)
 {
  printf("%x ", read_temp[i]);
 }
 
 // 检验写入和读取的数据是否一致
 for(s=0; s<SIZE; s++)
 {
  if(write_temp[s] != read_temp[s])
  {
   printf("\r\n读写不一致\r\n");
   break;
  }
 }
 printf("\r\n读写校验结束\r\n");
 
 while(1)
 {
 
 }
}


实验结果:

把写入和读取的数据改成大于256个字节的数据,main.c

#define SIZE 400


再测试:

手动改一个数据,看看是否校验一致,如果不一致就说明写入读写都正常
main.c

 read_temp[1] = 50;
 // 检验写入和读取的数据是否一致
 for(s=0; s<SIZE; s++)
 {
  if(write_temp[s] != read_temp[s])
  {
   printf("\r\n读写不一致\r\n");
   break;
  }
 }
 printf("\r\n读写校验结束\r\n");
 
 while(1)
 {
 
 }


再测试:

校验不一致,说明校验起作用了,读写正常。
     

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标常用软件Flash频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved