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_PIN
GPIO_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用于存储接收到的字节数据,初始化为0
uint8_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) {
// 用于存储读取到的设备ID
uint16_t device_id;
// 拉低片选信号,选中W25Q128
HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET);
// 发送读取设备ID的命令码0x90
SPI_SendByte(0x90);
// 发送地址字节,这里地址为0x000000
SPI_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位设备ID
device_id |= SPI_SendByte(0xFF);
// 拉高片选信号,取消选中W25Q128
HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);
// 返回读取到的设备ID
return device_id;
}
/**
* @brief 使能W25Q128的写操作
*
* 该函数通过SPI接口向W25Q128发送写使能命令,允许后续的写操作。
*/
void W25Q128_WriteEnable(void) {
// 拉低片选信号,选中W25Q128
HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET);
// 发送写使能命令码0x06
SPI_SendByte(0x06);
// 拉高片选信号,取消选中W25Q128
HAL_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();
// 拉低片选信号,选中W25Q128
HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET);
// 发送扇区擦除命令码0x20
SPI_SendByte(0x20);
// 发送扇区地址的高8位
SPI_SendByte((sector_address >> 16) & 0xFF);
// 发送扇区地址的中间8位
SPI_SendByte((sector_address >> 8) & 0xFF);
// 发送扇区地址的低8位
SPI_SendByte(sector_address & 0xFF);
// 拉高片选信号,取消选中W25Q128
HAL_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();
// 拉低片选信号,选中W25Q128
HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET);
// 发送页编程命令码0x02
SPI_SendByte(0x02);
// 发送写入地址的高8位
SPI_SendByte((address >> 16) & 0xFF);
// 发送写入地址的中间8位
SPI_SendByte((address >> 8) & 0xFF);
// 发送写入地址的低8位
SPI_SendByte(address & 0xFF);
// 循环将数据逐字节写入W25Q128
for (uint16_t i = 0; i < length; i++) {
SPI_SendByte(data[i]);
}
// 拉高片选信号,取消选中W25Q128
HAL_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) {
// 拉低片选信号,选中W25Q128
HAL_GPIO_WritePin(SPI_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET);
// 发送读取数据命令码0x03
SPI_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);
}
// 拉高片选信号,取消选中W25Q128
HAL_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外设实例为SPI1
hspi1.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外设实例是否为SPI1
if(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_4
GPIO_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};
// 定义接收数据的数组,用于存储读取到的设备ID
uint8_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位的设备ID
return (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扇区,
// 擦除操作会将该扇区的数据全部置为0xFF
W25Q128_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 的实现方法,你可以根据实际需求进行调整和扩展,有什么不懂的可以留言私信。