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) | CLK | SPI 時鐘信號 |
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 的實現方法,你可以根據實際需求進行調整和擴展,有什么不懂的可以留言私信。