操作SPI,读写型号为W25Qxx的flash芯片
1. 硬件连接
SPI 有4根线,这个芯片可以支持QSPI,此方案中没有使用
2. CubeMX生成代码
2.1 时钟配置
2.2 配置时钟,SWD 串口,SPI2
2.3 配置 CS 这个 GPIO
- 设置GPIO 名称 : W25Q64_CHIP_SELECT
- 设置输出为 High
2.4 配置工程名称 生成代码
3. 编写代码
先给上flash控制的库函数
W25Q64.h代码
c
#ifndef __W25Q64__H__
#define __W25Q64__H__
#include "main.h"
#define ManufactDeviceID_CMD 0x90
#define READ_STATU_REGISTER_1 0x05
#define READ_STATU_REGISTER_2 0x35
#define READ_DATA_CMD 0x03
#define WRITE_ENABLE_CMD 0x06
#define WRITE_DISABLE_CMD 0x04
#define SECTOR_ERASE_CMD 0x20
#define CHIP_ERASE_CMD 0xc7
#define PAGE_PROGRAM_CMD 0x02
static HAL_StatusTypeDef SPI_Transmit(uint8_t* send_buf, uint16_t size);//向 SPI Flash 发送数据的函数
static HAL_StatusTypeDef SPI_Receive(uint8_t* recv_buf, uint16_t size);//从 SPI Flash 接收数据的函数
static HAL_StatusTypeDef SPI_TransmitReceive(uint8_t* send_buf, uint8_t* recv_buf, uint16_t size);//发送数据的同时读取数据的函数
uint16_t W25QXX_ReadID(void);//读取Flash内部的ID
static uint8_t W25QXX_ReadSR(uint8_t reg);//读取W25QXX的状态寄存器,W25Q64一共有2个状态寄存器
int W25QXX_Read(uint8_t* buffer, uint32_t start_addr, uint16_t nbytes);//
void W25QXX_Write_Enable(void);
void W25QXX_Write_Disable(void);
void W25QXX_Erase_Sector(uint32_t sector_addr);
void W25QXX_Page_Program(uint8_t* dat, uint32_t WriteAddr, uint16_t nbytes);
#endif /* __MAIN_H */
W25Q64.C代码
c
#include "main.h"
#include "W25Q64.h"
extern SPI_HandleTypeDef hspi1;
extern SPI_HandleTypeDef hspi2;
/**
* @brief SPI发送指定长度的数据
* @param buf —— 发送数据缓冲区首地址
* @param size —— 要发送数据的字节数
* @retval 成功返回HAL_OK
*/
static HAL_StatusTypeDef SPI_Transmit(uint8_t* send_buf, uint16_t size)
{
return HAL_SPI_Transmit(&hspi2, send_buf, size, 100);
}
/**
* @brief SPI接收指定长度的数据
* @param buf —— 接收数据缓冲区首地址
* @param size —— 要接收数据的字节数
* @retval 成功返回HAL_OK
*/
static HAL_StatusTypeDef SPI_Receive(uint8_t* recv_buf, uint16_t size)
{
return HAL_SPI_Receive(&hspi2, recv_buf, size, 100);
}
/**
* @brief SPI在发送数据的同时接收指定长度的数据
* @param send_buf —— 接收数据缓冲区首地址
* @param recv_buf —— 接收数据缓冲区首地址
* @param size —— 要发送/接收数据的字节数
* @retval 成功返回HAL_OK
*/
static HAL_StatusTypeDef SPI_TransmitReceive(uint8_t* send_buf, uint8_t* recv_buf, uint16_t size)
{
return HAL_SPI_TransmitReceive(&hspi2, send_buf, recv_buf, size, 100);
}
/**
* @brief 读取Flash内部的ID
* @param none
* @retval 成功返回device_id
*/
uint16_t W25QXX_ReadID(void)
{
uint8_t recv_buf[2] = {0}; //recv_buf[0]存放Manufacture ID, recv_buf[1]存放Device ID
uint16_t device_id = 0;
uint8_t send_data[4] = {ManufactDeviceID_CMD,0x00,0x00,0x00}; //待发送数据,命令+地址
/* 使能片选 */
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
/* 发送并读取数据 */
if (HAL_OK == SPI_Transmit(send_data, 4))
{
if (HAL_OK == SPI_Receive(recv_buf, 2))
{
device_id = (recv_buf[0] << 8) | recv_buf[1];
}
}
/* 取消片选 */
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
return device_id;
}
/**
* @brief 读取W25QXX的状态寄存器,W25Q64一共有2个状态寄存器
* @param reg —— 状态寄存器编号(1~2)
* @retval 状态寄存器的值
*/
static uint8_t W25QXX_ReadSR(uint8_t reg)
{
uint8_t result = 0;
uint8_t send_buf[4] = {0x00,0x00,0x00,0x00};
switch(reg)
{
case 1:
send_buf[0] = READ_STATU_REGISTER_1;
case 2:
send_buf[0] = READ_STATU_REGISTER_2;
case 0:
default:
send_buf[0] = READ_STATU_REGISTER_1;
}
/* 使能片选 */
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
if (HAL_OK == SPI_Transmit(send_buf, 4))
{
if (HAL_OK == SPI_Receive(&result, 1))
{
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
return result;
}
}
/* 取消片选 */
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
return 0;
}
/**
* @brief 阻塞等待Flash处于空闲状态
* @param none
* @retval none
*/
static void W25QXX_Wait_Busy(void)
{
while((W25QXX_ReadSR(1) & 0x01) == 0x01); // 等待BUSY位清空
}
/**
* @brief 读取SPI FLASH数据
* @param buffer —— 数据存储区
* @param start_addr —— 开始读取的地址(最大32bit)
* @param nbytes —— 要读取的字节数(最大65535)
* @retval 成功返回0,失败返回-1
*/
int W25QXX_Read(uint8_t* buffer, uint32_t start_addr, uint16_t nbytes)
{
uint8_t cmd = READ_DATA_CMD;
start_addr = start_addr << 8;
W25QXX_Wait_Busy();
/* 使能片选 */
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
SPI_Transmit(&cmd, 1);
if (HAL_OK == SPI_Transmit((uint8_t*)&start_addr, 3))
{
if (HAL_OK == SPI_Receive(buffer, nbytes))
{
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
return 0;
}
}
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
return -1;
}
/**
* @brief W25QXX写使能,将S1寄存器的WEL置位
* @param none
* @retval
*/
void W25QXX_Write_Enable(void)
{
uint8_t cmd= WRITE_ENABLE_CMD;
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
SPI_Transmit(&cmd, 1);
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
W25QXX_Wait_Busy();
}
/**
* @brief W25QXX写禁止,将WEL清零
* @param none
* @retval none
*/
void W25QXX_Write_Disable(void)
{
uint8_t cmd = WRITE_DISABLE_CMD;
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
SPI_Transmit(&cmd, 1);
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
W25QXX_Wait_Busy();
}
/**
* @brief W25QXX擦除一个扇区
* @param sector_addr —— 扇区地址 根据实际容量设置
* @retval none
* @note 阻塞操作
*/
void W25QXX_Erase_Sector(uint32_t sector_addr)
{
uint8_t cmd = SECTOR_ERASE_CMD;
sector_addr *= 4096; //每个块有16个扇区,每个扇区的大小是4KB,需要换算为实际地址
sector_addr <<= 8;
W25QXX_Write_Enable(); //擦除操作即写入0xFF,需要开启写使能
W25QXX_Wait_Busy(); //等待写使能完成
/* 使能片选 */
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
SPI_Transmit(&cmd, 1);
SPI_Transmit((uint8_t*)§or_addr, 3);
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
W25QXX_Wait_Busy(); //等待扇区擦除完成
}
/**
* @brief 页写入操作
* @param dat —— 要写入的数据缓冲区首地址
* @param WriteAddr —— 要写入的地址
* @param byte_to_write —— 要写入的字节数(0-256)
* @retval none
*/
void W25QXX_Page_Program(uint8_t* dat, uint32_t WriteAddr, uint16_t nbytes)
{
uint8_t cmd = PAGE_PROGRAM_CMD;
WriteAddr <<= 8;
W25QXX_Write_Enable();
/* 使能片选 */
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
SPI_Transmit(&cmd, 1);
SPI_Transmit((uint8_t*)&WriteAddr, 3);
SPI_Transmit(dat, nbytes);
HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);
W25QXX_Wait_Busy();
}
下面是main函数中的代码
串口重定向
将串口的代码重新使用了printf来输出,
C
#include <stdio.h>
//重定向c库函数printf到串口USARTx,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口USARTx */
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return (ch);
}
void ShowHex(uint8_t *buf,uint8_t len)
{
uint8_t i;
printf("hex = ");
for( i = 0; i < len; i++){
printf(" %02X",buf[i]); //使用前导0补齐
//printf(" %2X",buf[i]); //使用前导空格补齐
//printf(" %X",buf[i]); //输出最短的16进制格式
}
printf( "\r\n");
}
完整的main函数代码
C
#include "W25Q64.h"
uint16_t device_id;
uint8_t read_buf[10] = {0};
uint8_t write_buf[10] = {0};
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
MX_SPI2_Init();
/* USER CODE BEGIN WHILE */
printf("****************** start **********************\r\n");
/* 读取要写入地址处的数据 */
printf("read data before write\r\n");
W25QXX_Read(read_buf, 0, 10);
ShowHex(read_buf,10);
/* 擦除该扇区 */
printf("erase sector 0 \r\n");
W25QXX_Erase_Sector(0);
/* 再次读数据 */
printf("read erase data\r\n");
W25QXX_Read(read_buf, 0, 10);
ShowHex(read_buf,10);
/* 写数据 */
printf("write data \r\n");
for(int i = 0; i < 10; i++)
{
write_buf[i] = i;
}
W25QXX_Page_Program(write_buf, 0, 10);//写数据
/* 再次读数据 */
printf("read write data \r\n");
W25QXX_Read(read_buf, 0, 10);
ShowHex(read_buf,10);
while (1)
{
device_id = W25QXX_ReadID();
printf("W25Q64 Device ID is 0x%04x\r\n",device_id);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
4. 后记
无