【STM32】HAL庫中的實現(九):SPI(串行外設接口)

SPI 接口通信原理

請添加圖片描述

SPI(Serial Peripheral Interface)是全雙工主從通信協議,特點是:

信號線功能
SCK串行時鐘
MOSI主設備輸出,從設備輸入
MISO主設備輸入,從設備輸出
CS(NSS)片選信號,低電平選中

請添加圖片描述
請添加圖片描述

(1)SPI每發送一個數據的同時會接收到一個字節的數據。
(2)SPI有4條線,MISO,MOSI,SCLK三條數據線,還有片選線CS,片選線對于SPI接口的從設備是低電平有效,主機輸出一個低電平從機就被選中。這樣就方便一個主機可以連接多個從設備,只需要使用不同的片選線。

SPI 四種模式

SPI的相位(CPHA)和極性(CPOL)都可以為0或1,對應的4種組合構成了SPI的4種模式:

Mode 0: CPOL=0, CPHA=0
Mode 1 :CPOL=0, CPHA=1
Mode 2 :CPOL=1, CPHA=0
Mode 3 :CPOL=1, CPHA=1

請添加圖片描述

📘 這里我們將 W25Qxx 作為 SPI 從機(你也可以用其他外設作為從機使用,例如 93C46 EEPROM存儲器),STM32 為主機

W25Q64 是華邦公司推出的大容量SPI FLASH 產品,W25Q64 的容量為 64Mb,W25Q128的容量為128Mb。
W25Q64 的擦寫周期多達 10W 次,具有 20 年的數據保存期限,支持電壓為 2.7~3.6V。

需要理解 SPI原理 可移步至:【STM32】SPI接口原理與配置(提供完整實例代碼)。此處僅簡化帶過,不再過多詳細說明SPI的工作原理。

這篇博客實現了 STM32 使用 HAL 庫通過 SPI 接口驅動 W25Qxx Flash 存儲器 的代碼框架,并通過 CubeMX、原理圖、調試串口等方式進行了全流程驗證。我將從原理、配置、電路、代碼結構、調試技巧 全方面為你系統說明HAL庫中的SPI使用方法。


STM32 CubeMX配置SPI訪問W25Qxx

一、硬件接線與原理圖說明

請添加圖片描述
請添加圖片描述

如圖所示:
CLK----SPI1_SCK(PA5)
SO-----SPI1_MISO(PA6)
SI------SPI1_MOSI(PA7)

CS----PB11 通過軟件片選(PA4)

STM32引腳					W25Qxx引腳				說明
PA5							CLK						SPI SCK
PA6							MISO					SPI MISO
PA7							MOSI					SPI MOSI
PA4							CS						軟件控制 FLASH 片選
VCC							VCC						3.3V
GND							GND						地線
/WP							GND						關閉寫保護
/HOLD						VCC						禁止暫停傳輸注:外接 10kΩ 上拉電阻到 MOSI/MISO/CS(推薦)

SPI引腳設置模式:
在這里插入圖片描述

?? 二、CubeMX 配置說明

? SPI1 配置

模式:全雙工主機(Full-Duplex Master)
數據大小:8 bits
時鐘極性 CPOL = 1
時鐘相位 CPHA = 1(W25Qxx 數據手冊建議)
NSS 信號:Software(軟件管理片選)

請添加圖片描述

為片選引腳配置GPIO:(SPI 連接到的資源:PA4作為片選引腳,一般來說我們常用片選引腳作為模擬片選管腳使用)。

? GPIO 配置

PA4 → 設置為 GPIO Output Push Pull,用戶標簽:FLASH_CS

請添加圖片描述

配置好后點擊創建工程。

三、軟件實現結構說明

我們在這里復習一下此前的標準庫 SPI 配置五步法:

步驟函數
1?? 初始化 GPIOGPIO_Init()
2?? 設置 SPI 參數SPI_Init()
3?? 使能 SPISPI_Cmd()
4?? 發送接收數據SPI_I2S_SendData() / ReceiveData()
5?? 檢查狀態SPI_I2S_GetFlagStatus()

在這里,HAL庫配置則簡單得多(用 STM32CubeMX 生成 HAL 工程后):

HAL_SPI_Transmit(&hspi1, data, len, timeout);
HAL_SPI_Receive(&hspi1, data, len, timeout);
HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, len, timeout);

當然,我們在這里是 STM32 CubeMX配置SPI訪問W25Qxx

工程簡單分為 3 層結構:

1?? 應用層: main.c

W25qxx_Init();
printf("Read Device ID: 0x%08X",W25qxx_ReadDeviceID());

2?? 驅動層: w25qxx.c / w25qxx.h

  • 驅動層內封裝了 ReadID / WritePage / ReadBuffer / Erase / Init 等函數
  • 支持多種容量芯片識別(W25Q16~W25Q512)

3?? 配置層: w25qxx_config.h

#define _W25QXX_SPI        hspi1
#define _W25QXX_CS_GPIO    FLASH_CS_GPIO_Port
#define _W25QXX_CS_PIN     FLASH_CS_Pin

補充知識儲備:可自行查詢SPIFLASH資源使用:W25Q64數據手冊.pdf
可詳細了解W25Qxx的內部詳情

在這里插入圖片描述
請添加圖片描述


完整代碼

📄 main.c

/* USER CODE BEGIN Header */
/********************************************************************************* @file           : main.c* @brief          : Main program body******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"#include "w25qxx.h"
#include "w25qxx_config.h"
/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD *//* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 *//* USER CODE END 0 *//*** @brief  The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_USART1_UART_Init();MX_SPI1_Init();/* USER CODE BEGIN 2 */HAL_UARTEx_ReceiveToIdle_IT(  &huart1 , U1RxData, U1RxDataSize);W25qxx_Init();/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */printf("Read Device ID: 0x%08X",W25qxx_ReadDeviceID() );while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}
/*參考鏈接:https://www.cnblogs.com/kdsj/p/15371137.html *//* USER CODE END 3 */
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){Error_Handler();}
}/* USER CODE BEGIN 4 *//* USER CODE END 4 *//*** @brief  This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}#ifdef  USE_FULL_ASSERT
/*** @brief  Reports the name of the source file and the source line number*         where the assert_param error has occurred.* @param  file: pointer to the source file name* @param  line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

📄 w25qxx.c

#include "w25qxx.h"
#include "w25qxx_config.h"#if (_W25QXX_DEBUG == 1)
#include <stdio.h>
#endif#define W25QXX_CS_H 			HAL_GPIO_WritePin(_W25QXX_CS_GPIO, _W25QXX_CS_PIN, GPIO_PIN_SET);
#define W25QXX_CS_L 			HAL_GPIO_WritePin(_W25QXX_CS_GPIO, _W25QXX_CS_PIN, GPIO_PIN_RESET);w25qxx_t	w25qxx;#if (_W25QXX_USE_FREERTOS == 1)
#define	W25qxx_Delay(delay)		osDelay(delay)
#include "cmsis_os.h"
#else
#define	W25qxx_Delay(delay) for(uint8_t i=0;i<255;i++);;
#endif//###################################################################################################################
uint8_t	W25qxx_Spi(uint8_t	Data)
{uint8_t	ret;HAL_SPI_TransmitReceive(&_W25QXX_SPI, &Data, &ret, 1, 100);return ret;	
}
//###################################################################################################################
uint32_t W25qxx_ReadID(void)
{uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;W25QXX_CS_L;W25qxx_Spi(W25X_JedecDeviceID);Temp0 = W25qxx_Spi(W25QXX_DUMMY_BYTE);Temp1 = W25qxx_Spi(W25QXX_DUMMY_BYTE);Temp2 = W25qxx_Spi(W25QXX_DUMMY_BYTE);W25QXX_CS_H;Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;return Temp;
}uint32_t W25qxx_ReadDeviceID(void)
{uint16_t Temp = 0;W25QXX_CS_L;W25qxx_Spi(W25X_ManufactDeviceID);W25qxx_Spi(0x00);W25qxx_Spi(0x00);W25qxx_Spi(0x00);Temp |= W25qxx_Spi(W25QXX_DUMMY_BYTE) << 8;Temp |= W25qxx_Spi(W25QXX_DUMMY_BYTE);
//	Temp2 = W25qxx_Spi(W25QXX_DUMMY_BYTE);W25QXX_CS_H;
//	Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;return Temp;
}
//###################################################################################################################
void W25qxx_ReadUniqID(void)
{W25QXX_CS_L;W25qxx_Spi(0x4B);for(uint8_t	i = 0; i < 4; i++){W25qxx_Spi(W25QXX_DUMMY_BYTE);}for(uint8_t	i = 0; i < 8; i++){w25qxx.UniqID[i] = W25qxx_Spi(W25QXX_DUMMY_BYTE);}W25QXX_CS_H;
}
//###################################################################################################################
void W25qxx_WriteEnable(void)
{W25QXX_CS_L;W25qxx_Spi(W25X_WriteEnable);W25QXX_CS_H;W25qxx_Delay(1);
}
//###################################################################################################################
void W25qxx_WriteDisable(void)
{W25QXX_CS_L;W25qxx_Spi(W25X_WriteDisable);W25QXX_CS_H;W25qxx_Delay(1);
}
//###################################################################################################################
uint8_t W25qxx_ReadStatusRegister(uint8_t SelectStatusRegister_1_2_3)
{uint8_t	status=0;W25QXX_CS_L;if(SelectStatusRegister_1_2_3 == 1){W25qxx_Spi(W25X_ReadStatusReg1);status=W25qxx_Spi(W25QXX_DUMMY_BYTE);	w25qxx.StatusRegister1 = status;}else if(SelectStatusRegister_1_2_3 == 2){W25qxx_Spi(0x35);status=W25qxx_Spi(W25QXX_DUMMY_BYTE);	w25qxx.StatusRegister2 = status;}else{W25qxx_Spi(0x15);status=W25qxx_Spi(W25QXX_DUMMY_BYTE);	w25qxx.StatusRegister3 = status;}	W25QXX_CS_H;return status;
}
//###################################################################################################################
void W25qxx_WriteStatusRegister(uint8_t	SelectStatusRegister_1_2_3, uint8_t Data)
{W25QXX_CS_L;if(SelectStatusRegister_1_2_3 == 1){W25qxx_Spi(W25X_WriteStatusReg1);w25qxx.StatusRegister1 = Data;}else if(SelectStatusRegister_1_2_3 == 2){W25qxx_Spi(0x31);w25qxx.StatusRegister2 = Data;}else{W25qxx_Spi(0x11);w25qxx.StatusRegister3 = Data;}W25qxx_Spi(Data);W25QXX_CS_H;
}
//###################################################################################################################
void W25qxx_WaitForWriteEnd(void)
{W25QXX_CS_L;W25qxx_Spi(W25X_ReadStatusReg1);do{w25qxx.StatusRegister1 = W25qxx_Spi(W25QXX_DUMMY_BYTE);W25qxx_Delay(1);}while ((w25qxx.StatusRegister1 & WIP_Flag) == 0x01);W25QXX_CS_H;}
//###################################################################################################################
bool W25qxx_Init(void)
{w25qxx.Lock=1;	
//	while(HAL_GetTick()<100)
//		W25qxx_Delay(1);W25QXX_CS_H;
//  W25qxx_Delay(100);uint32_t	id;
#if (_W25QXX_DEBUG==1)printf("w25qxx Init Begin...\r\n");
#endifid = W25qxx_ReadID();#if (_W25QXX_DEBUG==1)printf("w25qxx ID:0x%X\r\n",id);
#endifswitch(id&0x0000FFFF){case 0x401A:	// 	w25q512w25qxx.ID=W25Q512;w25qxx.BlockCount=1024;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q512\r\n");
#endifbreak;case 0x4019:	// 	w25q256w25qxx.ID=W25Q256;w25qxx.BlockCount=512;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q256\r\n");
#endifbreak;case 0x4018:	// 	w25q128w25qxx.ID=W25Q128;w25qxx.BlockCount=256;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q128\r\n");
#endifbreak;case 0x4017:	//	w25q64w25qxx.ID=W25Q64;w25qxx.BlockCount=128;
#if (_W25QXX_DEBUG == 1)printf("w25qxx Chip: w25q64\r\n");
#endifbreak;case 0x4016:	//	w25q32w25qxx.ID=W25Q32;w25qxx.BlockCount=64;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q32\r\n");
#endifbreak;case 0x4015:	//	w25q16w25qxx.ID=W25Q16;w25qxx.BlockCount=32;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q16\r\n");
#endifbreak;case 0x4014:	//	w25q80w25qxx.ID=W25Q80;w25qxx.BlockCount=16;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q80\r\n");
#endifbreak;case 0x4013:	//	w25q40w25qxx.ID=W25Q40;w25qxx.BlockCount=8;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q40\r\n");
#endifbreak;case 0x4012:	//	w25q20w25qxx.ID=W25Q20;w25qxx.BlockCount=4;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q20\r\n");
#endifbreak;case 0x4011:	//	w25q10w25qxx.ID=W25Q10;w25qxx.BlockCount=2;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q10\r\n");
#endifbreak;default:
#if (_W25QXX_DEBUG==1)printf("w25qxx Unknown ID\r\n");
#endifw25qxx.Lock=0;	return false;		}		w25qxx.PageSize = 256;w25qxx.SectorSize = 0x1000;w25qxx.SectorCount = w25qxx.BlockCount * 16;w25qxx.PageCount = (w25qxx.SectorCount * w25qxx.SectorSize) / w25qxx.PageSize;w25qxx.BlockSize = w25qxx.SectorSize*16;w25qxx.CapacityInKiloByte = (w25qxx.SectorCount * w25qxx.SectorSize) / 1024;W25qxx_ReadUniqID();W25qxx_ReadStatusRegister(1);W25qxx_ReadStatusRegister(2);W25qxx_ReadStatusRegister(3);
#if (_W25QXX_DEBUG == 1)printf("w25qxx Page Size: %d Bytes\r\n",w25qxx.PageSize);printf("w25qxx Page Count: %d\r\n",w25qxx.PageCount);printf("w25qxx Sector Size: %d Bytes\r\n",w25qxx.SectorSize);printf("w25qxx Sector Count: %d\r\n",w25qxx.SectorCount);printf("w25qxx Block Size: %d Bytes\r\n",w25qxx.BlockSize);printf("w25qxx Block Count: %d\r\n",w25qxx.BlockCount);printf("w25qxx Capacity: %d KiloBytes\r\n",w25qxx.CapacityInKiloByte);printf("w25qxx Init Done\r\n");
#endifw25qxx.Lock = 0;	return true;
}	
//###################################################################################################################
void W25qxx_EraseChip(void)
{while(w25qxx.Lock==1){W25qxx_Delay(1);}w25qxx.Lock=1;	
#if (_W25QXX_DEBUG == 1)uint32_t	StartTime=HAL_GetTick();	printf("w25qxx EraseChip Begin...\r\n");
#endifW25qxx_WriteEnable();W25QXX_CS_L;W25qxx_Spi(W25X_ChipErase);W25QXX_CS_H;W25qxx_WaitForWriteEnd();
#if (_W25QXX_DEBUG == 1)printf("w25qxx EraseBlock done after %d ms!\r\n",HAL_GetTick()-StartTime);
#endifw25qxx.Lock = 0;	
}
//###################################################################################################################
void W25qxx_EraseSector(uint32_t SectorAddr)
{while(w25qxx.Lock == 1){W25qxx_Delay(1);}w25qxx.Lock = 1;	
#if (_W25QXX_DEBUG == 1)uint32_t	StartTime=HAL_GetTick();	printf("w25qxx EraseSector %d Begin...\r\n",SectorAddr);
#endifW25qxx_WriteEnable();W25qxx_WaitForWriteEnd();W25QXX_CS_L;W25qxx_Spi(W25X_SectorErase);if(w25qxx.ID == W25Q256){W25qxx_Spi((SectorAddr & 0xFF000000) >> 24);}W25qxx_Spi((SectorAddr & 0xFF0000) >> 16);W25qxx_Spi((SectorAddr & 0xFF00) >> 8);W25qxx_Spi(SectorAddr & 0xFF);W25QXX_CS_H;W25qxx_WaitForWriteEnd();
#if (_W25QXX_DEBUG==1)printf("w25qxx EraseSector done after %d ms\r\n",HAL_GetTick()-StartTime);
#endifw25qxx.Lock=0;
}
//###################################################################################################################
void W25qxx_EraseBlock(uint32_t BlockAddr)
{while(w25qxx.Lock==1){W25qxx_Delay(1);}w25qxx.Lock=1;	
#if (_W25QXX_DEBUG==1)printf("w25qxx EraseBlock %d Begin...\r\n",BlockAddr);W25qxx_Delay(100);uint32_t StartTime=HAL_GetTick();	
#endifW25qxx_WriteEnable();W25qxx_WaitForWriteEnd();BlockAddr = BlockAddr * w25qxx.SectorSize * 16;W25QXX_CS_L;W25qxx_Spi(W25X_BlockErase);if(w25qxx.ID == W25Q256){W25qxx_Spi((BlockAddr & 0xFF000000) >> 24);}W25qxx_Spi((BlockAddr & 0xFF0000) >> 16);W25qxx_Spi((BlockAddr & 0xFF00) >> 8);W25qxx_Spi(BlockAddr & 0xFF);W25QXX_CS_H;W25qxx_WaitForWriteEnd();
#if (_W25QXX_DEBUG==1)printf("w25qxx EraseBlock done after %d ms\r\n",HAL_GetTick()-StartTime);W25qxx_Delay(100);
#endifw25qxx.Lock=0;
}
//###################################################################################################################
uint32_t W25qxx_PageToSector(uint32_t PageAddress)
{return ((PageAddress * w25qxx.PageSize) / w25qxx.SectorSize);
}
//###################################################################################################################
uint32_t W25qxx_PageToBlock(uint32_t PageAddress)
{return ((PageAddress * w25qxx.PageSize) / w25qxx.BlockSize);
}
//###################################################################################################################
uint32_t W25qxx_SectorToBlock(uint32_t SectorAddress)
{return ((SectorAddress * w25qxx.SectorSize) / w25qxx.BlockSize);
}
//###################################################################################################################
uint32_t W25qxx_SectorToPage(uint32_t SectorAddress)
{return (SectorAddress * w25qxx.SectorSize) / w25qxx.PageSize;
}
//###################################################################################################################
uint32_t W25qxx_BlockToPage(uint32_t BlockAddress)
{return (BlockAddress * w25qxx.BlockSize) / w25qxx.PageSize;
}
//###################################################################################################################
void W25qxx_WriteByte(uint8_t pBuffer, uint32_t WriteAddr)
{while(w25qxx.Lock==1){W25qxx_Delay(1);}w25qxx.Lock = 1;
#if (_W25QXX_DEBUG==1)uint32_t	StartTime=HAL_GetTick();printf("w25qxx WriteByte 0x%02X at address %d begin...",pBuffer,WriteAddr);
#endifW25qxx_WriteEnable();W25qxx_WaitForWriteEnd();W25QXX_CS_L;W25qxx_Spi(W25X_PageProgram);if(w25qxx.ID == W25Q256){W25qxx_Spi((WriteAddr & 0xFF000000) >> 24);}W25qxx_Spi((WriteAddr & 0xFF0000) >> 16);W25qxx_Spi((WriteAddr & 0xFF00) >> 8);W25qxx_Spi(WriteAddr & 0xFF);W25qxx_Spi(pBuffer);W25QXX_CS_H;W25qxx_WaitForWriteEnd();
#if (_W25QXX_DEBUG==1)printf("w25qxx WriteByte done after %d ms\r\n",HAL_GetTick()-StartTime);
#endifw25qxx.Lock=0;
}
//###################################################################################################################
void W25qxx_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{while(w25qxx.Lock==1){W25qxx_Delay(1);}w25qxx.Lock = 1;W25qxx_WriteEnable();W25qxx_WaitForWriteEnd();W25QXX_CS_L;W25qxx_Spi(W25X_PageProgram);if(w25qxx.ID == W25Q256){W25qxx_Spi((WriteAddr & 0xFF000000) >> 24);}W25qxx_Spi((WriteAddr & 0xFF0000) >> 16);W25qxx_Spi((WriteAddr & 0xFF00) >> 8);W25qxx_Spi(WriteAddr & 0xFF);if(NumByteToWrite > SPI_FLASH_PerWritePageSize){NumByteToWrite = SPI_FLASH_PerWritePageSize;}while (NumByteToWrite--){W25qxx_Spi(*pBuffer);pBuffer++;}W25QXX_CS_H;W25qxx_WaitForWriteEnd();w25qxx.Lock=0;
}
//###################################################################################################################
void W25qxx_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;Addr = WriteAddr % SPI_FLASH_PageSize;count = SPI_FLASH_PageSize - Addr;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;if (Addr == 0) /* WriteAddr is SPI_FLASH_PageSize aligned  */{if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */{W25qxx_WritePage(pBuffer, WriteAddr, NumByteToWrite);}else /* NumByteToWrite > SPI_FLASH_PageSize */{while (NumOfPage--){W25qxx_WritePage(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}W25qxx_WritePage(pBuffer, WriteAddr, NumOfSingle);}}else /* WriteAddr is not SPI_FLASH_PageSize aligned  */{if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */{if (NumOfSingle > count) /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */{temp = NumOfSingle - count;W25qxx_WritePage(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;W25qxx_WritePage(pBuffer, WriteAddr, temp);}else{W25qxx_WritePage(pBuffer, WriteAddr, NumByteToWrite);}}else /* NumByteToWrite > SPI_FLASH_PageSize */{NumByteToWrite -= count;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;W25qxx_WritePage(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;while (NumOfPage--){W25qxx_WritePage(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}if (NumOfSingle != 0){W25qxx_WritePage(pBuffer, WriteAddr, NumOfSingle);}}}
}
//###################################################################################################################
void W25qxx_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{W25QXX_CS_L;W25qxx_Spi(W25X_ReadData);if(w25qxx.ID == W25Q256){W25qxx_Spi((ReadAddr & 0xFF000000) >> 24);}W25qxx_Spi((ReadAddr & 0xFF0000) >> 16);W25qxx_Spi((ReadAddr& 0xFF00) >> 8);W25qxx_Spi(ReadAddr & 0xFF);while (NumByteToRead--){*pBuffer = W25qxx_Spi(W25QXX_DUMMY_BYTE);pBuffer++;}W25QXX_CS_H;
}
//###################################################################################################################

📄 w25qxx.h

#ifndef __W25QXX_H__
#define __W25QXX_H__#include <stdbool.h>
#include "spi.h"#define SPI_FLASH_PageSize                  	256
#define SPI_FLASH_PerWritePageSize          	256#define W25X_WriteEnable		              	0x06 
#define W25X_WriteDisable		              	0x04 
#define W25X_ReadStatusReg1		              	0x05 
#define W25X_WriteStatusReg1	              	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 WIP_Flag                              	0x01  /* Write In Progress (WIP) flag */#define W25QXX_DUMMY_BYTE					  	0xFF#pragma pack(1)typedef enum
{W25Q10=1,W25Q20,W25Q40,W25Q80,W25Q16,W25Q32,W25Q64,W25Q128,W25Q256,W25Q512,}W25QXX_ID_t;typedef struct
{W25QXX_ID_t	ID;uint8_t		UniqID[8];uint16_t	PageSize;uint32_t	PageCount;uint32_t	SectorSize;uint32_t	SectorCount;uint32_t	BlockSize;uint32_t	BlockCount;uint32_t	CapacityInKiloByte;uint8_t		StatusRegister1;uint8_t		StatusRegister2;uint8_t		StatusRegister3;	uint8_t		Lock;}w25qxx_t;#pragma pack()extern w25qxx_t	w25qxx;/************************************************用戶API*******************************************/
bool		W25qxx_Init(void);uint32_t W25qxx_ReadDeviceID(void);
void		W25qxx_EraseChip(void);
void 		W25qxx_EraseSector(uint32_t SectorAddr);
void 		W25qxx_EraseBlock(uint32_t BlockAddr);uint32_t	W25qxx_PageToSector(uint32_t PageAddress);
uint32_t	W25qxx_PageToBlock(uint32_t PageAddress);
uint32_t	W25qxx_SectorToBlock(uint32_t SectorAddress);
uint32_t	W25qxx_SectorToPage(uint32_t SectorAddress);
uint32_t	W25qxx_BlockToPage(uint32_t BlockAddress);void 		W25qxx_WriteByte(uint8_t pBuffer, uint32_t WriteAddr);
void 		W25qxx_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void 		W25qxx_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);void 		W25qxx_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);#endif

📄 w25qxx_config.h

#ifndef __W25QXX_CONFIG_H__
#define __W25QXX_CONFIG_H__#include "main.h"
#include "usart.h"
#include "stdio.h"#define _W25QXX_SPI                   hspi1
#define _W25QXX_CS_GPIO               FLASH_CS_GPIO_Port
#define _W25QXX_CS_PIN                FLASH_CS_Pin
#define _W25QXX_USE_FREERTOS          0
#define _W25QXX_DEBUG                 1#endif

文件結構:
在這里插入圖片描述
在這里插入圖片描述

導入文件后記得要為代碼添加路徑依賴:
請添加圖片描述
在這里插入圖片描述


燒錄驗證

燒錄程序,開發板上電后首先讀取FLASH芯片的ID,并通過串口顯示給用戶,然后輸出操作提示,按下KEY0按鍵會擦除塊0內容,擦除后按下KEY1按鍵讀取內容會發現全是FF,然后按下KEY2按鍵將數據寫入,此時再按下KEY1按鍵讀取內容會發現和我們寫入的內容一致,如下圖所示為整個過程串口詳細輸出信息。

硬件SPI驅動w25qxx:
在這里插入圖片描述
說明 SPI 初始化成功,識別芯片正確。

一些常見的問題排查
問題						說明
讀 ID 為 0xFFFFFF		片選腳未拉低或 SPI 配置錯誤
寫入失敗					沒有先執行 W25qxx_WriteEnable()
讀寫數據出錯				SPI 模式(CPOL/CPHA)不一致
燒錄后串口無輸出			波特率錯誤或未定義重定向 printf

SPI還可以實現很多功能,例如:? 寫入一段數據并讀取驗證:W25qxx_WriteBuffer + W25qxx_ReadBuffer;? 擦除某個扇區:W25qxx_EraseSector(uint32_t sector);? 文件系統支持:對接 FATFS 做 SPI Flash 虛擬 U盤;? 多 SPI Flash:多片選,使用 GPIO 動態控制 CS 引腳;? 用 DMA 提高效率:配置 SPI DMA 模式,加快大容量數據傳輸。

這里的 STM32 HAL庫 SPI 驅動 W25Qxx Flash 是嵌入式存儲應用的經典實現,適合日志存儲、配置保存、音視頻緩存等場景,CubeMX 配合 HAL 封裝讓原來標準庫中復雜的流程變得更加簡潔、高效。

以上,歡迎有從事同行業的電子信息工程、互聯網通信、嵌入式開發的朋友共同探討與提問,我可以提供實戰演示或模板庫。希望內容能夠對你產生幫助!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/919666.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/919666.shtml
英文地址,請注明出處:http://en.pswp.cn/news/919666.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Git常用操作大全(附git操作命令)

Git常用操作大全 一、基礎配置 1.1 設置用戶名和郵箱 git config --global user.name "你的名字" git config --global user.email "你的郵箱"1.2 查看配置 git config --list二、倉庫管理 2.1 初始化本地倉庫 git init2.2 克隆遠程倉庫 git clone <倉庫…

詳解flink table api基礎(三)

文章目錄1.使用flink的原因&#xff1a;2. Flink支持兩種模式&#xff1a;3. flink table api工作原理&#xff1a;4. Flink table api 使用5. select語句&flink table api&#xff1a;6. 使用flink table api 創建table7. 使用flink table api 寫流式數據輸出到表或sink8.…

Vue2+Vue3前端開發_Day5

參考課程: 【黑馬程序員 Vue2Vue3基礎入門到實戰項目】 [https://www.bilibili.com/video/BV1HV4y1a7n4] ZZHow(ZZHow1024) 自定義指令 基本語法&#xff08;全局 & 局部注冊&#xff09; 介紹&#xff1a;自己定義的指令&#xff0c;可以封裝一些 DOM 操作&#xff0c…

機器學習--決策樹2

目錄 第一代裁判&#xff1a;ID3 與信息增益的 “偏愛” 第二代裁判&#xff1a;C4.5 用 “增益率” 找平衡 第三代裁判&#xff1a;CART 的 “基尼指數” 新思路 遇到連續值&#xff1f;先 “砍幾刀” 再說 給決策樹 “減肥”&#xff1a;剪枝的學問 動手試試&#xff1…

yggjs_react使用教程 v0.1.1

yggjs_react是一個用于快速創建React項目的工具&#xff0c;它集成了Vite、TypeScript、Zustand和React Router等現代前端技術棧&#xff0c;幫助開發者快速搭建高質量的React應用。 快速入門 快速入門部分將指導您如何安裝yggjs_react工具、創建新項目并啟動開發服務器。 安…

vulhub可用的docker源

這一塊不太容易找&#xff0c;我試了好幾個源&#xff0c;下面是20250820測試可用源 編輯方法sudo mkdir -p /etc/docker sudo vim /etc/docker/daemon.json 配置內容 [1] {"registry-mirrors" : ["https://docker.registry.cyou", "https://docker-…

基于YOLOv8-SEAttention與LLMs融合的農作物害蟲智能診斷與防控決策系統

1. 引言 1.1 研究背景與意義 農作物蟲害是制約農業產量與質量的重要因素。據FAO報告&#xff0c;全球每年因病蟲害造成的糧食損失高達 20%–40%。傳統人工巡查與經驗診斷具有時效性差、成本高與專業人才不足等缺陷。近年來&#xff0c;計算機視覺特別是目標檢測技術在農業檢測…

從零開始構建GraphRAG紅樓夢知識圖譜問答項目(三)

文章結尾有CSDN官方提供的學長的聯系方式&#xff01;&#xff01; 歡迎關注B站從零開始構建一個基于GraphRAG的紅樓夢項目 第三集01 搭建后端服務 創建一個python文件server.py 完整源碼放到文章最后了。 1.1 graphrag 相關導入 # GraphRAG 相關導入 from graphrag.query.cont…

S32K328(Arm Cortex-M7)適配CmBacktrace錯誤追蹤

CmBacktrace 相當于重寫了hard_fault函數&#xff0c;在hard_fault函數里面去分析SCB寄存器的信息和堆棧信息&#xff0c;然后把這些信息打印出來(或者寫到flash)&#xff1b;通過使用串口輸出產生hard_fault的堆棧信息&#xff0c;然后利用addr2line工具反推出具體的代碼執行函…

AI研究引擎的簡單技術實現步驟

產品愿景與核心功能 1.1 產品使命 “洞見 Weaver”是一個全棧AI Web應用,旨在將用戶的復雜研究問題,通過AI驅動的動態思維導圖和結構化報告,轉化為一次沉浸式的、可追溯的視覺探索之旅。我們的使命是,將AI復雜的推理過程透明化,將人類的探索直覺與AI的分析能力無縫結合,…

open webui源碼分析5-Tools

本文從最簡單的時間工具入手&#xff0c;分析Tools相關的代碼。一、安裝工具git clone https://github.com/open-webui/openapi-servers cd openapi-servers# 進入時間工具目錄 cd servers/timepip install -r requirements.txt# 啟動服務 uvicorn main:app --host 0.0.0.0 --r…

windows下通過vscode遠程調試linux c/cpp程序配置

windows下通過vscode遠程調試linux c/cpp程序配置vscode插件配置linux依賴工具安裝launch.json配置vscode插件配置 CodeLLDB插件需要提前下載&#xff1a; linux依賴工具安裝 sudo apt update sudo apt install cmake clangdlaunch.json配置 {"version": "0…

IDEA報JDK版本問題

解決思路&#xff1a;1.找到配置jdk的IDEA配置位置settings和project structure2.先配置setting3.再修改項目結構

VirtualBox 安裝 Ubuntu Server 系統及 Ubuntu 初始配置

文章目錄簡介VirtualBoxUbuntu Server 簡介Ubuntu Server 下載安裝 Ubuntu Server首選項配置導入系統鏡像配置系統用戶配置內存 CPU 虛擬硬盤開始安裝 Ubuntu安裝完成登錄系統配置網絡Ubuntu 系統配置安裝常用工具安裝 SSH設置 root 密碼配置 IP 地址&#xff08;推薦自動分配I…

Milvus 可觀測性最佳實踐

Milvus 介紹 Milvus 是一個開源的向量數據庫&#xff0c;專為處理大規模、高維度向量數據而設計&#xff0c;廣泛應用于人工智能、推薦系統、圖像檢索、自然語言處理等場景。它支持億級向量的高效存儲與快速檢索&#xff0c;內置多種相似度搜索算法&#xff08;如 HNSW、IVF、…

arcgis-空間矯正工具(將下發數據A的信息放置原始數據B的原始信息并放置到成果數據C中,主要按下發數據A的范圍)

正常來說&#xff0c;可以直接相交獲取&#xff0c;但是會存在原始數據B將下發數據A進行分割&#xff0c;所以相交功能會導致最終成果會產生稀碎圖斑及圖斑切割&#xff0c;因此&#xff0c;經學習了解&#xff0c;學會此方法進行既保留原始數據B的信息&#xff0c;又按下發數據…

MySQL深分頁慢問題及性能優化

在數據驅動的應用中&#xff0c;分頁是不可或缺的功能。然而&#xff0c;當數據量達到百萬甚至千萬級別時&#xff0c;傳統基于 LIMIT OFFSET 的分頁方式會遭遇嚴重的性能瓶頸&#xff0c;即“深分頁”問題。本文將剖析其根源并提供主流的優化策略。問題根源&#xff1a;LIMIT …

漫談《數字圖像處理》之平滑

在數字圖像處理中&#xff0c;平滑&#xff08;Smoothing&#xff09; 的核心目標是降低圖像噪聲、模糊細節或簡化紋理&#xff0c;本質是通過 “局部鄰域運算” 對像素值進行 “平均化” 或 “規整化”&#xff0c;讓圖像整體更 “平緩”。形態學平滑與高斯平滑、均值平滑等其…

機器學習之數據預處理學習總結

在機器學習中&#xff0c;數據預處理是模型訓練前至關重要的環節&#xff0c;直接影響模型的性能和準確性。通過本次學習&#xff0c;我系統掌握了數據預處理的核心方法與工具&#xff0c;現將主要內容總結如下&#xff1a;一、缺失值處理缺失值是實際數據中常見的問題&#xf…

在完全沒有無線網絡(Wi-Fi)和移動網絡(蜂窩數據)的環境下,使用安卓平板,通過USB數據線(而不是Wi-Fi)來控制電腦(版本2)

在完全沒有無線網絡&#xff08;Wi-Fi&#xff09;和移動網絡&#xff08;蜂窩數據&#xff09;的環境下&#xff0c;要實現用安卓手機通過USB數據線控制電腦&#xff0c;核心思路是&#xff1a;利用USB數據線創建一個純粹的、本地的有線網絡連接。 這不僅是可行的&#xff0c;…