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)
	{
	
	}

再测试:

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

 

posted @ 2018-07-01 12:16  半生戎马,共话桑麻、  阅读(465)  评论(0)    收藏  举报
levels of contents