day22:SPI_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)
{
}
再测试:

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

浙公网安备 33010602011771号