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.mzph.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;并总…