STM32 HAL庫SPI讀寫W25Q128(軟件模擬+硬件spi)

1. 引言

在嵌入式系統開發中,SPI(Serial Peripheral Interface)總線是一種常用的串行通信協議,用于在微控制器和外部設備之間進行高速數據傳輸。W25Q128 是一款常見的 SPI Flash 芯片,具有 128Mbit(16MB)的存儲容量,廣泛應用于數據存儲和程序代碼存儲等場景。STM32F407 是一款高性能的 ARM Cortex - M4 內核微控制器,它支持硬件 SPI 接口,同時也可以通過軟件模擬 SPI 通信。本文將詳細介紹基于 STM32F407 HAL 庫實現軟件模擬 SPI 和硬件 SPI 讀寫 W25Q128 的方法。

2. 硬件連接

2.1 STM32F407 與 W25Q128 的硬件連接
STM32F407 引腳W25Q128 引腳功能說明
PA5(SCK)CLKSPI 時鐘信號
PA6(MISO)DO主設備輸入從設備輸出
PA7(MOSI)DI主設備輸出從設備輸入
PA4(NSS)CS片選信號

3. 軟件模擬 SPI 讀寫 W25Q128

3.1 初始化 GPIO 引腳

在軟件模擬 SPI 通信中,需要將相應的 GPIO 引腳配置為輸出或輸入模式。以下是初始化 GPIO 引腳的代碼示例:

#include "stm32f4xx_hal.h"// 定義SPI通信所需的GPIO引腳
// 時鐘信號引腳
#define SPI_SCK_PIN GPIO_PIN_5
// 主設備輸入從設備輸出引腳
#define SPI_MISO_PIN GPIO_PIN_6
// 主設備輸出從設備輸入引腳
#define SPI_MOSI_PIN GPIO_PIN_7
// 片選信號引腳
#define SPI_NSS_PIN GPIO_PIN_4
// 這些引腳所在的GPIO端口
#define SPI_GPIO_PORT GPIOA// 初始化SPI通信所需的GPIO引腳
void SPI_GPIO_Init(void) {// 定義一個GPIO初始化結構體變量,用于配置GPIO引腳GPIO_InitTypeDef GPIO_InitStruct = {0};// 使能GPIOA端口的時鐘,因為SPI通信使用的引腳位于GPIOA端口__HAL_RCC_GPIOA_CLK_ENABLE();// 配置時鐘信號、主設備輸出從設備輸入和片選信號引腳// 將這些引腳的配置信息存儲在GPIO_InitStruct結構體中GPIO_InitStruct.Pin = SPI_SCK_PIN | SPI_MOSI_PIN | SPI_NSS_PIN;// 設置這些引腳為推挽輸出模式GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;// 不使用上拉或下拉電阻GPIO_InitStruct.Pull = GPIO_NOPULL;// 設置引腳的輸出速度為低頻GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;// 根據上述配置信息初始化SPI_GPIO_PORT(即GPIOA)端口的相應引腳HAL_GPIO_Init(SPI_GPIO_PORT, &GPIO_InitStruct);// 配置主設備輸入從設備輸出引腳// 重新設置引腳為SPI_MISO_PINGPIO_InitStruct.Pin = SPI_MISO_PIN;// 設置該引腳為輸入模式GPIO_InitStruct.Mode = GPIO_MODE_INPUT;// 不使用上拉或下拉電阻GPIO_InitStruct.Pull = GPIO_NOPULL;// 根據上述配置信息初始化SPI_GPIO_PORT(即GPIOA)端口的SPI_MISO_PIN引腳HAL_GPIO_Init(SPI_GPIO_PORT, &GPIO_InitStruct);// 初始時將片選信號引腳拉高,禁用從設備HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);
}
3.2 軟件模擬 SPI 位操作

實現 SPI 的位操作函數,包括時鐘信號的產生和數據的發送與接收。

/*** @brief 發送一個SPI位數據* * 此函數用于通過軟件模擬SPI協議發送一個位的數據。* 它首先將待發送的位數據寫入MOSI引腳,然后通過操作SCK引腳產生一個時鐘脈沖。* * @param bit 待發送的位數據,取值為0或1*/
void SPI_SendBit(uint8_t bit) {// 將待發送的位數據寫入MOSI引腳,將bit強制轉換為GPIO_PinState類型HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_MOSI_PIN, (GPIO_PinState)bit);// 將SCK引腳置高,產生時鐘信號的上升沿HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_SET);// 將SCK引腳置低,產生時鐘信號的下降沿,完成一個時鐘周期HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_RESET);
}/*** @brief 接收一個SPI位數據* * 此函數用于通過軟件模擬SPI協議接收一個位的數據。* 它通過操作SCK引腳產生一個時鐘脈沖,在時鐘上升沿時讀取MISO引腳的數據。* * @return uint8_t 接收到的位數據,取值為0或1*/
uint8_t SPI_ReceiveBit(void) {// 定義一個變量用于存儲接收到的位數據uint8_t bit;// 將SCK引腳置高,產生時鐘信號的上升沿HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_SET);// 讀取MISO引腳的電平狀態,將其賦值給bit變量bit = HAL_GPIO_ReadPin(SPI_GPIO_PORT, SPI_MISO_PIN);// 將SCK引腳置低,產生時鐘信號的下降沿,完成一個時鐘周期HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_RESET);// 返回接收到的位數據return bit;
}
3.3 軟件模擬 SPI 字節操作

基于位操作函數,實現字節的發送和接收。

/*** @brief 通過軟件模擬SPI協議發送一個字節的數據,并同時接收一個字節的數據** 該函數會循環8次,每次發送一位數據并接收一位數據,最終組合成一個完整的字節。* 發送數據時,從最高位開始逐位發送;接收數據時,同樣從最高位開始逐位接收并組合。** @param byte 要發送的字節數據* @return uint8_t 接收到的字節數據*/
uint8_t SPI_SendByte(uint8_t byte) {// 定義循環變量i用于循環發送和接收每一位數據// 定義變量received_byte用于存儲接收到的字節數據,初始化為0uint8_t i, received_byte = 0;// 循環8次,因為一個字節有8位for (i = 0; i < 8; i++) {// 發送當前位的數據// (byte >> (7 - i))將byte右移(7 - i)位,使得要發送的位移動到最低位// & 0x01將該位與1進行按位與操作,提取出該位的值// 調用SPI_SendBit函數發送該位SPI_SendBit((byte >> (7 - i)) & 0x01);// 接收一位數據并組合到received_byte中// 調用SPI_ReceiveBit函數接收一位數據// 將接收到的位左移(7 - i)位,移動到合適的位置// 然后與received_byte進行按位或操作,將該位組合到received_byte中received_byte |= (SPI_ReceiveBit() << (7 - i));}// 返回接收到的完整字節數據return received_byte;
}
3.4 讀寫 W25Q128 函數

實現對 W25Q128 的讀寫操作,包括讀取設備 ID、寫使能、擦除扇區和寫入數據等功能。

/*** @brief 讀取W25Q128的設備ID* * 該函數通過SPI接口向W25Q128發送讀取設備ID的命令,然后接收并返回設備ID。* * @return uint16_t 讀取到的W25Q128設備ID*/
uint16_t W25Q128_ReadID(void) {// 用于存儲讀取到的設備IDuint16_t device_id;// 拉低片選信號,選中W25Q128HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET);// 發送讀取設備ID的命令碼0x90SPI_SendByte(0x90);// 發送地址字節,這里地址為0x000000SPI_SendByte(0x00);SPI_SendByte(0x00);SPI_SendByte(0x00);// 先接收高8位數據,并左移8位存儲到device_id的高8位device_id = (uint16_t)SPI_SendByte(0xFF) << 8;// 再接收低8位數據,并與device_id進行按位或操作,組合成完整的16位設備IDdevice_id |= SPI_SendByte(0xFF);// 拉高片選信號,取消選中W25Q128HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);// 返回讀取到的設備IDreturn device_id;
}/*** @brief 使能W25Q128的寫操作* * 該函數通過SPI接口向W25Q128發送寫使能命令,允許后續的寫操作。*/
void W25Q128_WriteEnable(void) {// 拉低片選信號,選中W25Q128HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET);// 發送寫使能命令碼0x06SPI_SendByte(0x06);// 拉高片選信號,取消選中W25Q128HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);
}/*** @brief 擦除W25Q128的指定扇區* * 該函數先使能寫操作,然后通過SPI接口向W25Q128發送扇區擦除命令和扇區地址,完成扇區擦除操作。* * @param sector_address 要擦除的扇區地址*/
void W25Q128_SectorErase(uint32_t sector_address) {// 使能寫操作,允許后續的擦除操作W25Q128_WriteEnable();// 拉低片選信號,選中W25Q128HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET);// 發送扇區擦除命令碼0x20SPI_SendByte(0x20);// 發送扇區地址的高8位SPI_SendByte((sector_address >> 16) & 0xFF);// 發送扇區地址的中間8位SPI_SendByte((sector_address >> 8) & 0xFF);// 發送扇區地址的低8位SPI_SendByte(sector_address & 0xFF);// 拉高片選信號,取消選中W25Q128HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);
}/*** @brief 向W25Q128的指定地址寫入數據* * 該函數先使能寫操作,然后通過SPI接口向W25Q128發送頁編程命令和寫入地址,最后將數據逐字節寫入。* * @param address 寫入數據的起始地址* @param data 要寫入的數據數組* @param length 要寫入的數據長度*/
void W25Q128_PageProgram(uint32_t address, uint8_t *data, uint16_t length) {// 使能寫操作,允許后續的寫入操作W25Q128_WriteEnable();// 拉低片選信號,選中W25Q128HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET);// 發送頁編程命令碼0x02SPI_SendByte(0x02);// 發送寫入地址的高8位SPI_SendByte((address >> 16) & 0xFF);// 發送寫入地址的中間8位SPI_SendByte((address >> 8) & 0xFF);// 發送寫入地址的低8位SPI_SendByte(address & 0xFF);// 循環將數據逐字節寫入W25Q128for (uint16_t i = 0; i < length; i++) {SPI_SendByte(data[i]);}// 拉高片選信號,取消選中W25Q128HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);
}/*** @brief 從W25Q128的指定地址讀取數據* * 該函數通過SPI接口向W25Q128發送讀取數據命令和讀取地址,然后將數據逐字節讀取到指定數組中。* * @param address 讀取數據的起始地址* @param data 用于存儲讀取數據的數組* @param length 要讀取的數據長度*/
void W25Q128_ReadData(uint32_t address, uint8_t *data, uint16_t length) {// 拉低片選信號,選中W25Q128HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET);// 發送讀取數據命令碼0x03SPI_SendByte(0x03);// 發送讀取地址的高8位SPI_SendByte((address >> 16) & 0xFF);// 發送讀取地址的中間8位SPI_SendByte((address >> 8) & 0xFF);// 發送讀取地址的低8位SPI_SendByte(address & 0xFF);// 循環將數據逐字節從W25Q128讀取到data數組中for (uint16_t i = 0; i < length; i++) {data[i] = SPI_SendByte(0xFF);}// 拉高片選信號,取消選中W25Q128HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);
}

4. 硬件 SPI 讀寫 W25Q128

4.1 初始化硬件 SPI

使用 STM32F407 的硬件 SPI 接口進行通信,需要初始化 SPI 外設。

// 定義一個SPI句柄結構體變量,用于配置和操作SPI1外設
SPI_HandleTypeDef hspi1;/*** @brief 初始化SPI1外設* * 此函數用于對SPI1外設進行配置,設置其工作模式、數據方向、數據大小等參數,* 并調用HAL庫的初始化函數進行初始化。若初始化失敗,調用錯誤處理函數。*/
void SPI1_Init(void) {// 指定使用的SPI外設實例為SPI1hspi1.Instance = SPI1;// 設置SPI工作模式為主模式,即STM32作為主設備控制通信hspi1.Init.Mode = SPI_MODE_MASTER;// 設置SPI數據傳輸方向為雙線模式,即同時支持發送和接收數據hspi1.Init.Direction = SPI_DIRECTION_2LINES;// 設置SPI數據傳輸大小為8位,即每次傳輸一個字節的數據hspi1.Init.DataSize = SPI_DATASIZE_8BIT;// 設置SPI時鐘極性為低電平,即空閑狀態下時鐘信號為低電平hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;// 設置SPI時鐘相位為第一個邊沿采樣數據,即數據在時鐘信號的第一個邊沿被采樣hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;// 設置SPI片選信號為軟件控制,即通過軟件來控制片選引腳的電平hspi1.Init.NSS = SPI_NSS_SOFT;// 設置SPI波特率預分頻系數為256,用于降低SPI通信的時鐘頻率hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;// 設置SPI數據傳輸的位順序為高位在前,即先傳輸數據的最高位hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;// 禁用SPI的TI模式,TI模式通常用于特定的通信協議hspi1.Init.TIMode = SPI_TIMODE_DISABLE;// 禁用SPI的CRC校驗功能,CRC校驗用于數據傳輸的錯誤檢測hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;// 設置CRC多項式的值為7,由于CRC校驗已禁用,此值無實際作用hspi1.Init.CRCPolynomial = 7;// 調用HAL庫的SPI初始化函數進行SPI1外設的初始化if (HAL_SPI_Init(&hspi1) != HAL_OK) {// 若初始化失敗,調用錯誤處理函數進行處理Error_Handler();}
}/*** @brief SPI外設的底層硬件初始化函數* * 此函數用于對SPI外設所使用的GPIO引腳進行初始化配置,* 包括使能相關時鐘、設置引腳模式、速度和復用功能等。* * @param spiHandle 指向SPI句柄結構體的指針,用于判斷是哪個SPI外設*/
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) {// 定義一個GPIO初始化結構體變量,用于配置GPIO引腳GPIO_InitTypeDef GPIO_InitStruct = {0};// 判斷要初始化的SPI外設實例是否為SPI1if(spiHandle->Instance==SPI1) {// 使能SPI1外設的時鐘,以便可以對其進行配置和使用__HAL_RCC_SPI1_CLK_ENABLE();// 使能GPIOA端口的時鐘,因為SPI1使用的引腳位于GPIOA端口__HAL_RCC_GPIOA_CLK_ENABLE();// 配置SPI1的SCK(時鐘)、MISO(主設備輸入從設備輸出)、MOSI(主設備輸出從設備輸入)引腳// 將這些引腳的配置信息存儲在GPIO_InitStruct結構體中GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;// 設置這些引腳為復用推挽輸出模式,用于SPI通信GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;// 不使用上拉或下拉電阻GPIO_InitStruct.Pull = GPIO_NOPULL;// 設置引腳的輸出速度為非常高,以適應高速SPI通信GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;// 設置這些引腳的復用功能為SPI1,即作為SPI1的相關信號引腳GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;// 根據上述配置信息初始化GPIOA端口的相應引腳HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);// 配置SPI1的NSS(片選)引腳// 重新設置引腳為GPIO_PIN_4GPIO_InitStruct.Pin = GPIO_PIN_4;// 設置該引腳為推挽輸出模式,用于軟件控制片選信號GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;// 不使用上拉或下拉電阻GPIO_InitStruct.Pull = GPIO_NOPULL;// 設置引腳的輸出速度為低頻GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;// 根據上述配置信息初始化GPIOA端口的SPI_NSS_PIN引腳HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);// 初始時將片選信號引腳拉高,禁用從設備HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);}
}
4.2 讀寫 W25Q128 函數

使用硬件 SPI 實現對 W25Q128 的讀寫操作。

/*** @brief 使用硬件SPI讀取W25Q128的設備ID* * 該函數通過硬件SPI接口向W25Q128發送讀取設備ID的命令,然后接收并返回設備ID。* * @return uint16_t 讀取到的W25Q128設備ID*/
uint16_t W25Q128_ReadID_HardwareSPI(void) {// 定義發送數據的數組,包含讀取設備ID的命令和地址信息uint8_t tx_data[4] = {0x90, 0x00, 0x00, 0x00};// 定義接收數據的數組,用于存儲讀取到的設備IDuint8_t rx_data[2];// 拉低片選信號,選中W25Q128芯片HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);// 通過硬件SPI發送tx_data數組中的4個字節數據HAL_SPI_Transmit(&hspi1, tx_data, 4, HAL_MAX_DELAY);// 通過硬件SPI接收2個字節的數據到rx_data數組中HAL_SPI_Receive(&hspi1, rx_data, 2, HAL_MAX_DELAY);// 拉高片選信號,取消選中W25Q128芯片HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);// 將接收到的兩個字節數據組合成一個16位的設備IDreturn (uint16_t)rx_data[0] << 8 | rx_data[1];
}/*** @brief 使用硬件SPI使能W25Q128的寫操作* * 該函數通過硬件SPI接口向W25Q128發送寫使能命令,以允許后續的寫操作。*/
void W25Q128_WriteEnable_HardwareSPI(void) {// 定義要發送的寫使能命令uint8_t tx_data = 0x06;// 拉低片選信號,選中W25Q128芯片HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);// 通過硬件SPI發送寫使能命令HAL_SPI_Transmit(&hspi1, &tx_data, 1, HAL_MAX_DELAY);// 拉高片選信號,取消選中W25Q128芯片HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}/*** @brief 使用硬件SPI擦除W25Q128的指定扇區* * 該函數先使能寫操作,然后通過硬件SPI接口向W25Q128發送扇區擦除命令和扇區地址。* * @param sector_address 要擦除的扇區地址*/
void W25Q128_SectorErase_HardwareSPI(uint32_t sector_address) {// 調用寫使能函數,允許后續的擦除操作W25Q128_WriteEnable_HardwareSPI();// 定義發送數據的數組,包含扇區擦除命令和扇區地址信息uint8_t tx_data[4] = {0x20, (uint8_t)(sector_address >> 16), (uint8_t)(sector_address >> 8), (uint8_t)sector_address};// 拉低片選信號,選中W25Q128芯片HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);// 通過硬件SPI發送tx_data數組中的4個字節數據HAL_SPI_Transmit(&hspi1, tx_data, 4, HAL_MAX_DELAY);// 拉高片選信號,取消選中W25Q128芯片HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}/*** @brief 使用硬件SPI向W25Q128的指定地址寫入數據* * 該函數先使能寫操作,然后通過硬件SPI接口向W25Q128發送頁編程命令、寫入地址和數據。* * @param address 寫入數據的起始地址* @param data 要寫入的數據數組指針* @param length 要寫入的數據長度*/
void W25Q128_PageProgram_HardwareSPI(uint32_t address, uint8_t *data, uint16_t length) {// 調用寫使能函數,允許后續的寫入操作W25Q128_WriteEnable_HardwareSPI();// 定義發送數據的數組,包含頁編程命令和寫入地址信息uint8_t tx_data[4] = {0x02, (uint8_t)(address >> 16), (uint8_t)(address >> 8), (uint8_t)address};// 拉低片選信號,選中W25Q128芯片HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);// 通過硬件SPI發送tx_data數組中的4個字節數據HAL_SPI_Transmit(&hspi1, tx_data, 4, HAL_MAX_DELAY);// 通過硬件SPI發送要寫入的數據HAL_SPI_Transmit(&hspi1, data, length, HAL_MAX_DELAY);// 拉高片選信號,取消選中W25Q128芯片HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}/*** @brief 使用硬件SPI從W25Q128的指定地址讀取數據* * 該函數通過硬件SPI接口向W25Q128發送讀取數據命令和讀取地址,然后接收數據。* * @param address 讀取數據的起始地址* @param data 用于存儲讀取數據的數組指針* @param length 要讀取的數據長度*/
void W25Q128_ReadData_HardwareSPI(uint32_t address, uint8_t *data, uint16_t length) {// 定義發送數據的數組,包含讀取數據命令和讀取地址信息uint8_t tx_data[4] = {0x03, (uint8_t)(address >> 16), (uint8_t)(address >> 8), (uint8_t)address};// 拉低片選信號,選中W25Q128芯片HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);// 通過硬件SPI發送tx_data數組中的4個字節數據HAL_SPI_Transmit(&hspi1, tx_data, 4, HAL_MAX_DELAY);// 通過硬件SPI接收指定長度的數據到data數組中HAL_SPI_Receive(&hspi1, data, length, HAL_MAX_DELAY);// 拉高片選信號,取消選中W25Q128芯片HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}

5. 主函數測試

在主函數中調用上述函數進行測試。

/*** @brief 主函數,程序的入口點* * 此函數完成系統初始化,包括HAL庫、系統時鐘、SPI相關的GPIO和SPI外設,* 然后通過軟件模擬SPI和硬件SPI兩種方式讀取W25Q128的設備ID,* 接著分別使用軟件模擬SPI和硬件SPI對W25Q128進行扇區擦除、數據寫入和讀取操作,* 最后進入一個無限循環,保持程序持續運行。*/
int main(void) {// 初始化HAL庫,這是STM32 HAL庫的基礎初始化步驟,// 它會進行一些底層的硬件初始化和配置,為后續使用HAL庫函數做準備HAL_Init();// 配置系統時鐘,設置合適的時鐘頻率,確保系統各個模塊能正常工作// 該函數可能包含了對晶振、PLL等時鐘源和時鐘分頻器的配置SystemClock_Config();// 初始化SPI通信所需的GPIO引腳,包括SCK、MISO、MOSI和NSS引腳,// 為軟件模擬SPI通信做準備SPI_GPIO_Init();// 初始化SPI1外設,配置SPI1的工作模式、數據方向、時鐘極性等參數,// 為硬件SPI通信做準備SPI1_Init();// 使用軟件模擬SPI的方式讀取W25Q128的設備ID,// 將讀取到的設備ID存儲在device_id_soft變量中uint16_t device_id_soft = W25Q128_ReadID();// 使用硬件SPI的方式讀取W25Q128的設備ID,// 將讀取到的設備ID存儲在device_id_hard變量中uint16_t device_id_hard = W25Q128_ReadID_HardwareSPI();// 定義要寫入W25Q128的數據數組,包含4個字節的數據uint8_t write_data[] = {0x01, 0x02, 0x03, 0x04};// 定義一個數組用于存儲從W25Q128讀取的數據,大小為4個字節uint8_t read_data[4];// 使用軟件模擬SPI的方式擦除W25Q128的0x000000扇區,// 擦除操作會將該扇區的數據全部置為0xFFW25Q128_SectorErase(0x000000);// 使用軟件模擬SPI的方式將write_data數組中的數據寫入W25Q128的0x000000地址,// 寫入數據長度為4個字節W25Q128_PageProgram(0x000000, write_data, 4);// 使用軟件模擬SPI的方式從W25Q128的0x000000地址讀取4個字節的數據到read_data數組中W25Q128_ReadData(0x000000, read_data, 4);// 使用硬件SPI的方式擦除W25Q128的0x000000扇區W25Q128_SectorErase_HardwareSPI(0x000000);// 使用硬件SPI的方式將write_data數組中的數據寫入W25Q128的0x000000地址,// 寫入數據長度為4個字節W25Q128_PageProgram_HardwareSPI(0x000000, write_data, 4);// 使用硬件SPI的方式從W25Q128的0x000000地址讀取4個字節的數據到read_data數組中W25Q128_ReadData_HardwareSPI(0x000000, read_data, 4);// 進入一個無限循環,程序會一直停留在這個循環中,// 可以在此處添加其他需要持續運行的代碼邏輯while (1) {}
}

6. 總結

本文詳細介紹了基于 STM32F407 HAL 庫實現軟件模擬 SPI 和硬件 SPI 讀寫 W25Q128 的方法。軟件模擬 SPI 具有靈活性高、無需特定硬件支持的優點,但通信速度相對較慢;硬件 SPI 則具有通信速度快、穩定性高的特點,但需要使用特定的硬件資源。在實際應用中,可根據具體需求選擇合適的 SPI 通信方式。

7. 注意事項

  • 在使用軟件模擬 SPI 時,要注意時鐘信號的產生和數據的發送與接收順序,確保通信的正確性。
  • 在使用硬件 SPI 時,要正確配置 SPI 外設的參數,如時鐘極性、時鐘相位、數據位寬等。
  • 在對 W25Q128 進行寫操作之前,需要先進行寫使能操作,并且在擦除扇區和寫入數據時要注意地址的正確性。

以上內容詳細介紹了基于 STM32F407 HAL 庫軟件模擬 SPI 讀寫 W25Q128 與硬件 SPI 讀寫 W25Q128 的實現方法,你可以根據實際需求進行調整和擴展,有什么不懂的可以留言私信。

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

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

相關文章

系統的安全及應用

倉庫做了哪些優化 倉庫源換成國內源不使用root用戶登錄將不必要的開機啟動項關閉內核的調優 系統做了哪些安全加固 禁止使用root禁止使用弱命令將常見的 遠程連接端口換掉 系統安全及應用 Cpu負載高 java程序 運行異常中病毒&#xff1f; ps aux - - sort %cpu %mem Cpu …

Java Lambda 表達式詳解:發展史、語法、使用場景及代碼示例

Java Lambda 表達式詳解&#xff1a;發展史、語法、使用場景及代碼示例 1. Lambda 表達式的發展史 背景與動機 JDK 7 前&#xff1a;Java的匿名內部類雖強大&#xff0c;但代碼冗余&#xff08;如事件監聽器、集合遍歷&#xff09;。JDK 8&#xff08;2014&#xff09;&#…

Linux 命令全解析:從零開始掌握 Linux 命令行

Linux 作為一款強大的開源操作系統&#xff0c;廣泛應用于服務器、嵌入式系統以及超級計算機領域。掌握 Linux 命令行技能&#xff0c;是每一位開發者和系統管理員的必備能力。本文將從基礎開始&#xff0c;為你詳細介紹常用的 Linux 命令&#xff0c;以及它們的使用場景和示例…

【已更新完畢】2025泰迪杯數據挖掘競賽C題數學建模思路代碼文章教學:競賽智能客服機器人構建

完整內容請看文末最后的推廣群 基于大模型的競賽智能客服機器人構建 摘要 隨著國內學科和技能競賽的增多&#xff0c;參賽者對競賽相關信息的需求不斷上升&#xff0c;但傳統人工客服存在效率低、成本高、服務不穩定和用戶體驗差的問題。因此&#xff0c;設計一款智能客服機器…

關于汽車輔助駕駛不同等級、技術對比、傳感器差異及未來發展方向的詳細分析

以下是關于汽車輔助駕駛不同等級、技術對比、傳感器差異及未來發展方向的詳細分析&#xff1a; 一、汽車輔助駕駛等級詳解 根據SAE&#xff08;國際自動機工程師學會&#xff09;的標準&#xff0c;自動駕駛分為 L0到L5 六個等級&#xff1a; 1. L0&#xff08;無自動化&…

神經網絡如何表示數據

神經網絡是如何工作的&#xff1f;這是一個讓新手和專家都感到困惑的問題。麻省理工學院計算機科學和人工智能實驗室&#xff08;CSAIL&#xff09;的一個團隊表示&#xff0c;理解這些表示&#xff0c;以及它們如何為神經網絡從數據中學習的方式提供信息&#xff0c;對于提高深…

網絡復習二(TCP【3】)

一、為什么TIME_WAIT等待的時間是2MSL&#xff1f; MSL&#xff1a;報文最大生存時間 我們要知道TCP報文是基于IP協議生存的&#xff0c;而在IP頭中有一個TTL&#xff08;經過路由跳數&#xff09;&#xff0c;當TTL為0使&#xff0c;數據報被丟失&#xff0c;同時發送ICMP報…

Go:基本數據

文章目錄 整數浮點數復數布爾值字符串字符串字面量UnicodeUTF - 8字符串和字節 slice字符串和數字的相互轉換 常量常量生成器 iota無類型常量 整數 分類 Go 的整數類型按大小分有 8 位、16 位、32 位、64 位 &#xff0c;同時有符號整數包括int8、int16、int32、int64 &#…

0x03.Redis 通常應用于哪些場景?

回答重點 1)緩存(Cache): Redis 最常用的場景是作為緩存層,以減少數據庫的負載,提高數據讀取速度。例如,常用的用戶會話數據和頁面渲染結果可以存儲在 Redis 中。2)分布式鎖(Distributed Lock): Redis 可以用作分布式鎖的實現,確保在分布式系統中資源的安全訪問,避免…

大數據學習筆記

文章目錄 1. 大數據概述1.1 大數據的特性1.2 大數據技術生態1.2.1 Hadoop 的概念特性1.2.2 Hadoop生態圈 — 核心組件與技術棧1.2.3 Hadoop生態演進趨勢 2. 數據處理流程與技術棧2.1 數據采集2.1.1 日志采集工具2.1.2 實時數據流2.1.3 數據遷移 2.2 數據預處理2.2.1 批處理2.2.…

Spring Boot 自定義商標(Logo)的完整示例及配置說明( banner.txt 文件和配置文件屬性信息)

Spring Boot 自定義商標&#xff08;Logo&#xff09;的完整示例及配置說明 1. Spring Boot 商標&#xff08;Banner&#xff09;功能概述 Spring Boot 在啟動時會顯示一個 ASCII 藝術的商標 LOGO&#xff08;默認為 Spring 的標志&#xff09;。開發者可通過以下方式自定義&a…

1. k8s的簡介

Kubernetes&#xff08;k8s&#xff09;簡介 1. 產生背景 隨著云計算和微服務架構的興起&#xff0c;傳統的單體應用逐漸被拆分為多個小型、松耦合的服務&#xff08;微服務&#xff09;。這種架構雖然提升了開發靈活性和可維護性&#xff0c;但也帶來了新的挑戰&#xff1a;…

OpenCV 圖形API(35)圖像濾波-----中值模糊函數medianBlur()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 使用中值濾波器模糊圖像。 該函數使用帶有 ksizeksize 開口的中值濾波器來平滑圖像。多通道圖像的每個通道都是獨立處理的。輸出圖像必須與輸入…

03 UV

04 Display工具欄_嗶哩嗶哩_bilibili 講的很棒 ctrlMMB 移動點 s 打針 ss 批量打針

PTA:古風排版

中國的古人寫文字&#xff0c;是從右向左豎向排版的。本題就請你編寫程序&#xff0c;把一段文字按古風排版。 輸入格式&#xff1a; 輸入在第一行給出一個正整數N&#xff08;<100&#xff09;&#xff0c;是每一列的字符數。第二行給出一個長度不超過1000的非空字符串&a…

每日一題(小白)暴力娛樂篇30

順時針旋轉&#xff0c;從上圖中不難看出行列進行了變換。因為這是一道暴力可以解決的問題&#xff0c;我們直接嘗試使用行列轉換看能不能得到想要的結果。 public static void main(String[] args) {Scanner scan new Scanner(System.in);int nscan.nextInt();int mscan.next…

邊緣計算場景下的模型輕量化:TensorRT部署YOLOv7的端到端優化指南

一、邊緣計算場景下的技術挑戰與優化路徑 在邊緣設備&#xff08;如Jetson系列&#xff09;部署YOLOv7需兼顧模型精度、推理速度與功耗限制三重約束。TensorRT作為NVIDIA官方推理加速庫&#xff0c;通過算子融合、量化壓縮和內存復用等優化技術&#xff0c;可將模型推理速度提…

rce漏洞學習

什么是rce漏洞 rce漏洞又稱遠程代碼執行漏洞&#xff0c;它允許攻擊者在目標服務器上遠程執行任意代碼或操作系統命令。rce漏洞通常出現在 應用程序提供給用戶執行命令的接口&#xff0c;例如網頁的ping功能也就是網頁的url欄&#xff0c;如果不對上傳的數據進行嚴格的管控就可…

VMware下Ubuntu空間擴容

目的&#xff1a; Ubuntu空間剩余不足&#xff0c;需要對Ubuntu進行擴容。 使用工具&#xff1a; 使用Ubuntu系統中的gparted工具進行系統擴容。 前提&#xff1a; 1、電腦有多余的未分配磁盤空間&#xff0c;比如我的Ubuntu磁盤G盤是200G&#xff0c;現在快滿了&#xff0c…

國產數據庫與Oracle數據庫事務差異分析

數據庫中的ACID是事務的基本特性&#xff0c;而在Oracle等數據庫遷移到國產數據庫國產中&#xff0c;可能因為不同數據庫事務處理機制的不同&#xff0c;在遷移后的業務邏輯處理上存在差異。本文簡要介紹了事務的ACID屬性、事務的隔離級別、回滾機制和超時機制&#xff0c;并總…