一、工具
1、硬件:STM32F103VET6单片机(HAL库)
2、编译环境:Keil 5.24.2
3、辅助工具:STM32CubeMX
二、电路原理图
三、单片机系统时钟配置
1、时钟源选择:
2、时钟树:
四、SPI配置
1、选用的是SPI1,全双工主机模式(单片机是主机,外部FLASH做从机),片选引脚由软件控制。
2、再检查一下SPI1的引脚设置是否正确。
3、设置FLASH的片选引脚即PC0引脚为输出模式,因为SPI1上只有一个器件,默认输出低电平。
五、生成代码
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();
}