STM32学习--SPI:HAL库读写外部Flash(W25Q64)

一、工具

1、硬件:STM32F103VET6单片机(HAL库)

2、编译环境:Keil 5.24.2

3、辅助工具:STM32CubeMX

二、电路原理图

img

三、单片机系统时钟配置

1、时钟源选择:

img

2、时钟树:

img

四、SPI配置

1、选用的是SPI1,全双工主机模式(单片机是主机,外部FLASH做从机),片选引脚由软件控制。

img

2、再检查一下SPI1的引脚设置是否正确。

img

3、设置FLASH的片选引脚即PC0引脚为输出模式,因为SPI1上只有一个器件,默认输出低电平。

img

五、生成代码

1、SPI1初始化直接经过CubeMx直接生成代码:spi.c

#include "spi.h"

SPI_HandleTypeDef hspi1;

/* SPI1 init function */
void MX_SPI1_Init(void)
{
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI1)
  {
    /* SPI1 clock enable */
    __HAL_RCC_SPI1_CLK_ENABLE();

__HAL_RCC_GPIOA_CLK_ENABLE();
/**SPI1 GPIO Configuration
PA5     ------> SPI1_SCK
PA6     ------> SPI1_MISO
PA7     ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}

void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{

  if(spiHandle->Instance==SPI1)
  {
    /* Peripheral clock disable */
    __HAL_RCC_SPI1_CLK_DISABLE();

/**SPI1 GPIO Configuration
PA5     ------> SPI1_SCK
PA6     ------> SPI1_MISO
PA7     ------> SPI1_MOSI
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);
}

2、GPIO初始化:初始化SPI 对应的 CS 引脚:gpio.c

#include "gpio.h"

/**----------------------------------------------------------------------------*/
/* Configure GPIO                                                             */
/*----------------------------------------------------------------------------*/

void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);

  /*Configure GPIO pin : PC0 */
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}

六、编写与Flash的相关代码

a、BSP_W25QXX.h

#ifndef __BSP_W25QXX_H
#define __BSP_W25QXX_H

#include "stm32f1xx.h"
#include "spi.h"

//W25Q64 Instruction Set Table 1
#define	W25Q64_WriteEnable 										0x06
#define	W25Q64_WriteDisable 									0x04
#define	W25Q64_Read_Status_Register_1 				0x05
#define	W25Q64_Read_Status_Register_2 				0x35
#define	W25Q64_Write_Status_Register 					0x01
#define	W25Q64_Page_Program 									0x02
#define	W25Q64_Quad_Page_Program 							0x32
#define	W25Q64_Block_Erase_64KB 							0xD8
#define	W25Q64_Block_Erase_32KB 							0x52
#define	W25Q64_Sector_Erase_4KB 							0x20
#define	W25Q64_Chip_Erase 										0xC7
#define	W25Q64_Erase_Suspend 									0x75
#define	W25Q64_Erase_Resume 									0x7A
#define	W25Q64_Power_down 										0xB9
#define	W25Q64_High_Performance_Mode 					0xA3
#define	W25Q64_Continuous_Read_ModeReset			0xFF
#define	W25Q64_Release_Power_down							0xAB
#define	W25Q64_HPM_OR_Device_ID								0xAB
#define	W25Q64_Manufacturer_OR_Device_ID 			0x90
#define	W25Q64_Read_Unique_ID	 								0x4B
#define	W25Q64_JEDEC_ID 											0x9F

//W25Q64 Instruction Set Table 2(Read Instructions)
#define	W25Q64_Read_Data 									0x03
#define	W25Q64_Fast_Read 									0x0B
#define	W25Q64_Fast_Read_Dual_Output 			0x3B
#define	W25Q64_Fast_Read_Dual_I_O 				0xBB
#define	W25Q64_Fast_Read_Quad_Output 			0x6B
#define	W25Q64_Fast_Read_Quad_I_O 				0xEB
#define	W25Q64_Octal_Word Read_Quad_I_O 	0xE3

#define Dummy_Byte												0xFF
/**************************************************************/



#define W25Qx_OK            ((uint8_t)0x00)
#define W25Qx_ERROR         ((uint8_t)0x01)
#define W25Qx_BUSY          ((uint8_t)0x02)
#define W25Qx_TIMEOUT				((uint8_t)0x03)


/* Flag Status Register */
#define W25Q128FV_FSR_BUSY                    ((uint8_t)0x01)    /*!< busy */
#define W25Q128FV_FSR_WREN                    ((uint8_t)0x02)    /*!< write enable */
#define W25Q128FV_FSR_QE                      ((uint8_t)0x02)    /*!< quad enable */



#define W25QXX_CS_GPIO_Port		GPIOC
#define W25QXX_CS_Pin					GPIO_PIN_0

#define W25Qxx_CS_LOW() 			HAL_GPIO_WritePin(W25QXX_CS_GPIO_Port, W25QXX_CS_Pin, GPIO_PIN_RESET)
#define W25Qxx_CS_HIGH() 			HAL_GPIO_WritePin(W25QXX_CS_GPIO_Port, W25QXX_CS_Pin, GPIO_PIN_SET)

#define W25Qxx_TIMEOUT_VALUE			1000
#define W25Q64_PageSize						256


uint8_t BSP_W25Qxx_Read_Byte(void);
uint8_t BSP_W25Qxx_Write_Byte(uint8_t Tx_Byte);
void BSP_W25Qxx_WriteEnable(void);
static void BSP_W25Qxx_Wait_for_Write_End(void);

uint32_t BSP_W25Qxx_Read_ID(void);
uint8_t BSP_W25Qxx_BufferRead(uint8_t *ReadBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);

void BSP_W25Qxx_PageWrite(uint8_t *WriteBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void BSP_W25Qxx_BufferWrite(uint8_t *WriteBuffer, uint32_t ReadAddr, uint16_t NumByteToWrite);

void BSP_W25Qxx_SectorErase(uint32_t Address);
void BSP_W25Qxx_BlockErase(void);

#endif


b、BSP_W25QXX.c

注意:在擦除操作后必须要进行写等待,等待忙状态结束,或者直接延时一下,不然可能写不进去

1、Flash写使能

/**
  * @brief  向FLASH发送 写使能 命令
  * @param  none
  * @retval none
  */
void BSP_W25Qxx_WriteEnable(void)
{
	uint8_t cmd = W25Q64_WriteEnable;
	
	/* 通讯开始,CS拉低 */
	W25Qxx_CS_LOW();
	
  /* 发送写使能命令*/
  HAL_SPI_Transmit(&hspi1, &cmd, 1, W25Qxx_TIMEOUT_VALUE);
	
	/* 通讯结束,CS拉高 */
	W25Qxx_CS_HIGH();
}

2、Flash等待写结束

/**
  * @brief  FLASH 等待写结束
  * @param  none
  * @retval none
  */
static void BSP_W25Qxx_Wait_for_Write_End(void)
{
  uint8_t state = 0;
	uint8_t cmd = W25Q64_Read_Status_Register_1;

	/* 通讯开始,CS拉低 */
  W25Qxx_CS_LOW();

  /* 发送命令 */
  HAL_SPI_Transmit(&hspi1, &cmd, 1, W25Qxx_TIMEOUT_VALUE);
	
  do
  {
		HAL_SPI_Receive(&hspi1, &state, 1, W25Qxx_TIMEOUT_VALUE);
  }
  while((state & 0x01) == SET);

	/* 通讯结束,CS拉高 */
  W25Qxx_CS_HIGH();
}

3、读flash

/**
  * @brief   读取FLASH数据
  * @param 	 ReadBuffer,存储读出的数据的指针
  * @param   ReadAddr,读取地址
  * @param   NumByte,读取数据长度
  * @retval  无
  */
uint8_t BSP_W25Qxx_BufferRead(uint8_t *ReadBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
	uint8_t cmd[4];
	cmd[0] = W25Q64_Read_Data;
	cmd[1] = (uint8_t)(ReadAddr >> 16);
	cmd[2] = (uint8_t)(ReadAddr >> 8);
	cmd[3] = (uint8_t)ReadAddr;

	/* 通讯开始,CS拉低 */
	W25Qxx_CS_LOW();
  
	/* 写入指令、地址 */	
	HAL_SPI_Transmit(&hspi1, cmd, 4, W25Qxx_TIMEOUT_VALUE);
		
		
	/* 读取数据 */
	HAL_SPI_Receive(&hspi1, ReadBuffer, NumByteToRead, W25Qxx_TIMEOUT_VALUE);
		
	/* 通讯结束,CS拉高 */
	W25Qxx_CS_HIGH();
	
	return W25Qx_OK;
}

4、按页写Flash

 /**
  * @brief  对FLASH进行页写入数据,调用本函数写入数据前需要先擦除扇区
  * @param	WriteBuffer,要写入的数据的指针
  * @param  WriteAddr,写入地址
  * @param  NumByteToWrite,写入数据长度
  * @retval 无
  */
void BSP_W25Qxx_PageWrite(uint8_t *WriteBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
//	uint16_t i;
	
	uint8_t cmd[4];
	cmd[0] = W25Q64_Page_Program;
	cmd[1] = (uint8_t)(WriteAddr >> 16);
	cmd[2] = (uint8_t)(WriteAddr >> 8);
	cmd[3] = (uint8_t)WriteAddr;
	
	/* 发送FLASH写使能命令 */
	BSP_W25Qxx_WriteEnable();
	
	/* 通讯开始,CS拉低 */
	W25Qxx_CS_LOW();

  /* 写入指令、地址、数据 */
	HAL_SPI_Transmit(&hspi1, cmd, 4, W25Qxx_TIMEOUT_VALUE);
	
	/* 写入数据 */
	HAL_SPI_Transmit(&hspi1, WriteBuffer, NumByteToWrite, W25Qxx_TIMEOUT_VALUE);
	
	/* 通讯结束,CS拉高 */
	W25Qxx_CS_HIGH();
	
	//需要在 W25Qxx_CS_HIGH 之后,即数据传输开始之后
	BSP_W25Qxx_Wait_for_Write_End();
}

5、写flash

/**
  * @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
  * @param	WriteBuffer,要写入数据的指针
  * @param  WriteAddr,写入地址
  * @param  NumByteToWrite,写入数据的长度
  * @retval none
  */
void BSP_W25Qxx_BufferWrite(uint8_t *WriteBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
	uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
	
/************************* 对于写入一共四种情况 *******************/
/**			起始地址 WriteAddr 和某一页的起始地址对齐
	*1、数据小于一页
	*2、数据大于一页
	*
	*		起始地址 WriteAddr 不和某一页的起始地址对齐
	*3、数据小于一页
	*4、数据大于一页
	*/
/******************************************************************/

/*mod运算求余,若WriteAddr是W25Q64_PageSize整数倍,运算结果Addr值为0*/
  Addr = WriteAddr % W25Q64_PageSize;
	/*差count个字节数据,刚好可以对齐到页地址*/
	count = W25Q64_PageSize - Addr;
	/*计算出要写多少整数页*/
	NumOfPage = NumByteToWrite / W25Q64_PageSize;
	/*mod运算求余,计算出剩余不满一页的字节数*/
	NumOfSingle = NumByteToWrite % W25Q64_PageSize;
	
	 /* Addr=0,则起始地址WriteAddr刚好是某一页的起始地址 */
	 if(Addr == 0)
	 {
			/* 写入的数据小于一页:NumByteToWrite < W25Q64_PageSize */
			if(NumOfPage == 0)
			{
				BSP_W25Qxx_PageWrite(WriteBuffer, WriteAddr, NumByteToWrite);
			}
			else /* 写入的数据不小于一页:NumByteToWrite >= W25Q64_PageSize */
			{
				/* 先把整数页都写了 */
				while(NumOfPage--)
				{
					BSP_W25Qxx_PageWrite(WriteBuffer, WriteAddr, W25Q64_PageSize);
					WriteAddr +=  W25Q64_PageSize;
					WriteBuffer += W25Q64_PageSize;
				}
				
				/* 若有不满一页的数据,则把它写完 */
				if(NumOfSingle)
					BSP_W25Qxx_PageWrite(WriteBuffer, WriteAddr, NumOfSingle);
			}
	 }
	 /* Addr!=0,则起始地址WriteAddr不是某一页的起始地址 */
	 else
	 {
			/* 写入的数据小于一页:NumByteToWrite < W25Q64_PageSize */
			if(NumOfPage == 0)
			{
				/* 但是尾地址在下一页 */
				if(NumOfSingle > count )
				{
					temp = NumOfSingle - count;
					
					/* 先写满当前页 */
					BSP_W25Qxx_PageWrite(WriteBuffer, WriteAddr, count);
					WriteAddr +=  count;
					WriteBuffer += count;
					
					/* 再写下一页的剩余数据 */
					BSP_W25Qxx_PageWrite(WriteBuffer, WriteAddr, temp);
				}
				/* 尾地址和起始地址WriteAddr在同一页 */
				else
				{
					BSP_W25Qxx_PageWrite(WriteBuffer, WriteAddr, NumByteToWrite);
				}
			}
			else/* 写入的数据不小于一页:NumByteToWrite >= W25Q64_PageSize */
			{
				/* 把头部不对齐的部分(count)单独处理一下,就跟对齐了的是一种情况了 */
				BSP_W25Qxx_PageWrite(WriteBuffer, WriteAddr, count);
				
				NumByteToWrite -= count;
				NumOfPage =  NumByteToWrite / W25Q64_PageSize;
				NumOfSingle = NumByteToWrite % W25Q64_PageSize;
				
				WriteAddr +=  count;
				WriteBuffer += count;
				
				/* 先把整数页都写了 */
				while(NumOfPage--)
				{
					BSP_W25Qxx_PageWrite(WriteBuffer, WriteAddr, W25Q64_PageSize);
					WriteAddr +=  W25Q64_PageSize;
					WriteBuffer += W25Q64_PageSize;
				}
				/* 若有不满一页的数据,则把它写完 */
				if(NumOfSingle)
					BSP_W25Qxx_PageWrite(WriteBuffer, WriteAddr, NumOfSingle);
			}
	 }
}

6、擦除Flash扇区

/**
  * @brief  擦除FLASH扇区
  * @param  SectorAddr:要擦除的扇区地址
  * @retval none
  */
void BSP_W25Qxx_SectorErase(uint32_t SectorAddr)
{
	uint8_t cmd[4];
	cmd[0] = W25Q64_Sector_Erase_4KB;
	cmd[1] = (uint8_t)(SectorAddr >> 16);
	cmd[2] = (uint8_t)(SectorAddr >> 8);
	cmd[3] = (uint8_t)SectorAddr;
	
	/* 发送FLASH写使能命令 */
	BSP_W25Qxx_WriteEnable();
	
	/* 通讯开始,CS拉低 */
	W25Qxx_CS_LOW();
	
	/* 发送命令和地址 */
	HAL_SPI_Transmit(&hspi1, cmd, 4, W25Qxx_TIMEOUT_VALUE);
	
	/* 通讯结束,CS拉高 */
	W25Qxx_CS_HIGH();
	
	/* 需要在W25Qxx_CS_HIGH 之后,即数据传输开始之后 */
	BSP_W25Qxx_Wait_for_Write_End();
}

7、擦除Flash块

/**
  * @brief  擦除FLASH块
  * @param  none
  * @retval none
  */
void BSP_W25Qxx_BlockErase(void)
{
	/* 发送FLASH写使能命令 */
	BSP_W25Qxx_WriteEnable();

	/* 通讯开始,CS拉低 */
	W25Qxx_CS_LOW();
	
  BSP_W25Qxx_Write_Byte(W25Q64_Chip_Erase);
		
	/* 通讯结束,CS拉高 */
	W25Qxx_CS_HIGH();
	
	/* 需要在W25Qxx_CS_HIGH 之后,即数据传输开始之后 */
	BSP_W25Qxx_Wait_for_Write_End();
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用STM32F427 HAL库进行SPI3读写W25Q32的基本步骤: 1. 配置SPI3 ```c hspi3.Instance = SPI3; hspi3.Init.Mode = SPI_MODE_MASTER; hspi3.Init.Direction = SPI_DIRECTION_2LINES; hspi3.Init.DataSize = SPI_DATASIZE_8BIT; hspi3.Init.CLKPolarity = SPI_POLARITY_LOW; hspi3.Init.CLKPhase = SPI_PHASE_1EDGE; hspi3.Init.NSS = SPI_NSS_SOFT; hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi3.Init.TIMode = SPI_TIMODE_DISABLE; hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi3.Init.CRCPolynomial = 10; HAL_SPI_Init(&hspi3); ``` 2. 配置GPIO引脚 ```c GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); ``` 3. 读取ID ```c uint8_t rxData[2]; uint8_t txData[2]; txData[0] = 0x9F; // 发送读取ID命令 txData[1] = 0x00; HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, GPIO_PIN_RESET); // 使能W25Q32 HAL_SPI_TransmitReceive(&hspi3, txData, rxData, 2, 100); // 发送命令并接收数据 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, GPIO_PIN_SET); // 禁用W25Q32 ``` 4. 读取数据 ```c uint8_t rxData[256]; uint8_t txData[256]; txData[0] = 0x03; // 发送读取数据命令 txData[1] = 0x00; txData[2] = 0x00; txData[3] = 0x00; HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, GPIO_PIN_RESET); // 使能W25Q32 HAL_SPI_Transmit(&hspi3, txData, 4, 1000); // 发送命令 HAL_SPI_Receive(&hspi3, rxData, 256, 1000); // 接收数据 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, GPIO_PIN_SET); // 禁用W25Q32 ``` 5. 写入数据 ```c uint8_t txData[256]; txData[0] = 0x06; // 发送写使能命令 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, GPIO_PIN_RESET); // 使能W25Q32 HAL_SPI_Transmit(&hspi3, txData, 1, 1000); // 发送命令 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, GPIO_PIN_SET); // 禁用W25Q32 txData[0] = 0x02; // 发送写数据命令 txData[1] = 0x00; txData[2] = 0x00; txData[3] = 0x00; for (int i = 0; i < 256; i++) { txData[4 + i] = i; // 写入数据 } HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, GPIO_PIN_RESET); // 使能W25Q32 HAL_SPI_Transmit(&hspi3, txData, 260, 1000); // 发送命令和数据 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, GPIO_PIN_SET); // 禁用W25Q32 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值