STM32CubeMX + HAL 库:用硬件SPI接口实现W25Q64 SPI Flash芯片的读写操作
本实验基于STM32微控制器通过SPI接口实现对W25Q64 NOR Flash存储芯片的读写操作。实验详细介绍了SPI协议的工作原理(4种工作模式)和W25Q64芯片特性(8MB容量、4KB扇区结构及指令集)。通过STM32CubeMX配置硬件SPI2接口,并实现驱动函数包括ID读取、扇区擦除、页编程和数据读取等功能。主程序演示了芯片ID识别、随机扇区擦除、多页数据写入和读取验证等完整流程。
1. 概述
1.1 实验目的
本实验旨在通过 STM32 微控制器的硬件 SPI 接口,对 W25Q64 外部 NOR Flash 存储芯片进行读写操作。实验演示了芯片ID读取、随机扇区擦除、多页数据写入和扇区数据读取。并提供对应的驱动文件,帮助读者深入理解 STM32 硬件 SPI 通信协议的工作机制,并掌握基于串行 Flash 的数据存储与访问方法。
通过本实验,读者不仅可以掌握 SPI 接口的基本使用技巧,还能够熟悉 W25Q 系列 Flash 的命令结构、存储特性及应用方法。这对于日后在嵌入式系统开发中涉及外部存储扩展(如固件存储、日志记录、字库管理等)提供了技术积累和实践基础,具有良好的工程指导意义。
1.2 SPI协议介绍
SPI(Serial Peripheral Interface,串行外设接口)是一种全双工、高速、主从架构的同步串行通信协议。由 Motorola 首创,目前已被广泛应用于微控制器与外设(如 Flash、LCD、传感器、ADC、DAC 等)之间的通信中。SPI 接口结构简单、速率高、实现方便,是嵌入式系统中最常用的通信方式之一。
1.2.1 接口引脚
| 引脚名称 | 方向(以主机为参考) | 说明 |
|---|---|---|
| SCLK | 主机输出 | 串行时钟信号 |
| MOSI | 主机输出 | 主出从入(Master Out Slave In) |
| MISO | 从机输出 | 主入从出(Master In Slave Out) |
| CS | 主机输出 | 片选信号,低电平有效,用于选中从设备 |
注: 对于多个从机设备,通常采用多个 CS 引脚来分别控制。
1.2.2 工作模式
SPI 定义了 4 种工作模式,具体取决于时钟极性(CPOL)和时钟相位(CPHA):
| 模式 | CPOL | CPHA | 空闲时钟 | 采样时刻 |
|---|---|---|---|---|
| 0 | 0 | 0 | 低 | 第一个边沿(上升沿) |
| 1 | 0 | 1 | 低 | 第二个边沿(下降沿) |
| 2 | 1 | 0 | 高 | 第一个边沿(下降沿) |
| 3 | 1 | 1 | 高 | 第二个边沿(上升沿) |
W25Q64 一般工作在 Mode 0(CPOL=0,CPHA=0) 模式下
1.3 W25Q64介绍


W25Q系列存储芯片凭借其出色的读写速度与大容量存储优势,在嵌入式领域应用广泛,常被用于存储字库信息、LCD显示界面图片、语音播报音频以及固件数据等各类关键内容。
| 数据类型 | 示例 |
|---|---|
| 程序代码 | Bootloader、主程序固件 |
| 字体库/图形数据 | 用于 OLED/LCD 显示的中文字库、位图图标等 |
| 参数配置 | JSON配置、阈值设置、网络参数等 |
| 日志/历史数据 | 温湿度采集记录、事件发生时间、设备运行状态等 |
| 音频数据 | 播报文件、提示音、报警音 |
1.3.1 芯片引脚
| 引脚编号 | 引脚名称 | 引脚用途 |
|---|---|---|
| 1 | /CS | 芯片选择,低电平选中芯片进行通信,高电平芯片待机 |
| 2 | DO | 数据输出,向外部设备输出芯片内部数据 |
| 3 | /WP | 写保护,低电平禁止芯片写入和擦除操作 |
| 4 | GND | 接地,提供电气参考地 |
| 5 | DI | 数据输入,接收外部输入到芯片的命令、地址和数据 |
| 6 | CLK | 串行时钟,由主设备提供时钟信号同步数据传输 |
| 7 | /HOLD | 暂停,低电平芯片暂停操作进入低功耗,高电平恢复 |
| 8 | VCC | 电源,为芯片提供2.7V - 3.6V工作电压 |

1.3.2 其他型号
W25Q芯片共有如下型号及对应的容量和寻址范围:
| 型号 | 容量(Mbit) | 容量(MByte) | 寻址范围(十六进制) |
|---|---|---|---|
| W25Q40 | 4 | 0.5 | 0x000000 – 0x07FFFF |
| W25Q80 | 8 | 1 | 0x000000 – 0x0FFFFF |
| W25Q16 | 16 | 2 | 0x000000 – 0x1FFFFF |
| W25Q32 | 32 | 4 | 0x000000 – 0x3FFFFF |
| W25Q64 | 64 | 8 | 0x000000 – 0x7FFFFF |
| W25Q128 | 128 | 16 | 0x000000 – 0xFFFFFF |
| W25Q256 | 256 | 32 | 0x000000 – 0x1FFFFFF |
| W25Q512 | 512 | 64 | 0x000000 – 0x3FFFFFF |
1.3.3 存储空间
W25Q64 的地址空间从 0x000000 开始,到 0x7FFFFF 结束,总计 8 × 1024 × 1024 = 8,388,608 个字节地址(即 8MB)。每一个存储单元(地址)表示一个字节(8 bit)。
| 层级 | 大小 | 数量 | 总大小 |
|---|---|---|---|
| 芯片 | 8 MByte(64 Mbit) | 1 | 8 MByte |
| 块(Block) | 64 KB | 128 | 128 块 × 64 KB = 8 MB |
| 扇区(Sector) | 4 KB | 2048 | 128 块 × 16 扇区 × 4 KB = 8 MB |
| 页(Page) | 256 Bytes | 32,768 | 128 块 × 16 扇区 × 16 页 × 256B = 8 MB |
注:每个 块包含 16 个扇区,每个扇区包含 16 页。
1.3.4 指令介绍
常用指令
| 指令码(Hex) | 指令名称 | 功能描述 | 备注说明 |
|---|---|---|---|
| 0x06 | Write Enable | 使能写操作(必须在写/擦前调用) | 写入和擦除前必须调用一次 |
| 0x04 | Write Disable | 禁用写操作,防止误操作 | 可选调用 |
| 0x05 | Read Status Register 1 | 读取状态寄存器1(SR1) | 包含 WIP(忙标志)、WEL(写使能)等 |
| 0x35 | Read Status Register 2 | 读取状态寄存器2(SR2) | 包含 QE(Quad Enable)位 |
| 0x01 | Write Status Registers | 写入状态寄存器1 和 2 | 设置保护区、启用 Quad 模式等 |
| 0x9F | Read JEDEC ID | 读取 JEDEC 厂商/设备 ID | 返回厂商 ID(EFh)、容量等 |
| 0x90 | Read Manufacturer/Device ID | 读取制造商和设备 ID | 需提供 3 字节 Dummy 地址 |
读写指令
| 指令码(Hex) | 指令名称 | 功能描述 | 备注说明 |
|---|---|---|---|
| 0x03 | Read Data | 标准读取(单线 SPI,低速) | 最常用读取指令 |
| 0x0B | Fast Read | 快速读取(需一个 Dummy Byte) | 速率更高 |
| 0x6B | Fast Read Quad Output | 快速四线读取(Quad SPI 模式) | 需要启用 QE 位 |
| 0x02 | Page Program | 页写入(最大 256 字节,页内写) | 写前需执行 Write Enable |
擦除指令
| 指令码(Hex) | 指令名称 | 功能描述 | 备注说明 |
|---|---|---|---|
| 0x20 | Sector Erase (4KB) | 擦除 1 个扇区(4KB) | 最小擦除单位 |
| 0x52 | Block Erase 32KB | 擦除 1 个 32KB 块 | 可选中等粒度 |
| 0xD8 | Block Erase 64KB | 擦除 1 个 64KB 块 | 常用于固件清理 |
| 0xC7 或 0x60 | Chip Erase | 擦除整片 Flash | 时间较长(几秒) |
其他指令
| 指令码(Hex) | 指令名称 | 功能描述 | 备注说明 |
|---|---|---|---|
| 0xB9 | Power Down | 进入低功耗休眠模式 | 降低待机功耗 |
| 0xAB | Release Power Down / Read ID | 释放休眠模式并读取 ID | 用于唤醒芯片 |
| 0xFF | Dummy Instruction | 空指令(可用于调试或 SPI 同步) | SPI 多字节读取常用填充字节 |
| 0x9F | Read JEDEC ID | 厂商ID=0xEF 类型=0x40h 容量=0x17 | 容量(字节) = 2^(容量ID的十进制值) = 2^23 byte |
1.3.5 读写流程

读操作:
(1)写入操作前,必须先进行写使能。
(2)每个数据位只能由1改写为0,不能由0改写为1。
(3)写入数据前必须先檫除,檫除后,所有数据位变为1。
(4)擦除必须按最小擦除单元(扇区)进行。
(5)连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入。
(6)写入操作结束后,芯片进入忙状态,不响应新的读写操作,需进行等待才能进行后续操作。
写操作:
(1)直接调用读取时序,无需读使能,无需额外操作,没有页的限制。
(2)读取操作结束后不会进入忙状态,但不能在忙状态时读取。
1.3.6 地址对齐
| 分类 | 大小 | 对齐要求(起始地址必须满足) | 编号计算方式 | 起始地址计算方式 |
|---|---|---|---|---|
| 页(Page) | 256 字节 | addr % 256 == 0 |
page = addr / 256 |
pageStartAddr = page * 256 |
| 扇区(Sector) | 4KB(4096 字节) | addr % 4096 == 0 |
sector = addr / 4096 |
sectorStartAddr = sector * 4096 |
| 块(Block) | 64KB(65536 字节) | addr % 65536 == 0 |
block = addr / 65536 |
blockStartAddr = block * 65536 |
2 STM32CubeMx
2.1 SYS设置

2.2 RCC设置


2.3 USART设置

2.4 GPIO设置
用于片选设备引脚,与w25q64的片选CS引脚相连,配置为输出,默认高电平即可

2.5 SPI设置

黄色感叹号是提示SPI引脚被占用,是因为作者把PB12引脚配置成了通用输出连接w25q64芯片的片选引脚,PB12引脚同时也是复用引脚,用作当STM32作为从机时SPI的NSS片选引脚,相当于w25q64芯片的CS引脚。我们这里是把STM32作为主机,所以这个复用硬件占用不影响实验目的。
2.6 project配置


配置完毕后,点击右上角的GENERATE CODE即可生成代码,并打开工程,在 keil MDK中进行驱动文件的添加和相关工程的配置。
3 keil MDK配置
3.1 debug配置



3.2 新增驱动文件

点击新建后,CTRL+S 进行保存,存放路径和文件名如下图所示,hardware是作者在Core目录下新建的一个文件夹:

3.3 构建工程目录

3.4 构建工程文件

3.5 引入依赖包
这一步是为了引入一个依赖包,在调试过程中,使用串口打印重定向,需要这个依赖包

4 VSCode
4.1 驱动文件
#ifndef __W25Q64_H
#define __W25Q64_H
#include "stm32f1xx_hal.h"
#include "gpio.h"
// ==== SPI 与引脚定义 ====
// 在 main.c 或其他初始化文件中定义实际使用的 SPI 句柄和 CS 引脚
#define W25Q64_SPI hspi2
#define W25Q64_CS_GPIO W25Q64_CS_GPIO_Port
#define W25Q64_CS_PIN W25Q64_CS_Pin
// ==== 指令码定义 ====
#define W25Q64_CMD_WRITE_ENABLE 0x06
#define W25Q64_CMD_WRITE_DISABLE 0x04
#define W25Q64_CMD_READ_STATUS1 0x05
#define W25Q64_CMD_READ_DATA 0x03
#define W25Q64_CMD_PAGE_PROGRAM 0x02
#define W25Q64_CMD_SECTOR_ERASE 0x20
#define W25Q64_CMD_CHIP_ERASE 0xC7
#define W25Q64_CMD_READ_ID 0x9F
// ==== 容量参数 ====
#define W25Q64_SECTOR_SIZE 4096
#define W25Q64_PAGE_SIZE 256
#define W25Q64_TOTAL_SIZE (8 * 1024 * 1024) // 8MB
// ==== 函数声明 ====
uint32_t W25Q64_ReadID(void);
void W25Q64_WriteEnable(void);
uint8_t W25Q64_ReadStatus1(void);
void W25Q64_WaitBusy(void);
void W25Q64_ReadData(uint32_t addr, uint8_t* buf, uint32_t len);
void W25Q64_PageProgram(uint32_t addr, const uint8_t* buf, uint32_t len);
void W25Q64_SectorErase(uint32_t addr);
void W25Q64_ChipErase(void);
#endif
#include "w25q64.h"
extern SPI_HandleTypeDef W25Q64_SPI;
// === 片选控制 ===
static void W25Q64_CS_LOW(void) {
HAL_GPIO_WritePin(W25Q64_CS_GPIO, W25Q64_CS_PIN, GPIO_PIN_RESET);
}
static void W25Q64_CS_HIGH(void) {
HAL_GPIO_WritePin(W25Q64_CS_GPIO, W25Q64_CS_PIN, GPIO_PIN_SET);
}
// === 写使能 ===
void W25Q64_WriteEnable(void) {
uint8_t cmd = W25Q64_CMD_WRITE_ENABLE;
W25Q64_CS_LOW();
HAL_SPI_Transmit(&W25Q64_SPI, &cmd, 1, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
}
// === 读取状态寄存器1 ===
uint8_t W25Q64_ReadStatus1(void) {
uint8_t cmd = W25Q64_CMD_READ_STATUS1;
uint8_t status;
W25Q64_CS_LOW();
HAL_SPI_Transmit(&W25Q64_SPI, &cmd, 1, HAL_MAX_DELAY);
HAL_SPI_Receive(&W25Q64_SPI, &status, 1, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
return status;
}
// === 等待空闲(WIP=0) ===
void W25Q64_WaitBusy(void) {
while (W25Q64_ReadStatus1() & 0x01);
}
// === 读取 JEDEC ID ===
uint32_t W25Q64_ReadID(void) {
uint8_t cmd = W25Q64_CMD_READ_ID;
uint8_t id_buf[3];
W25Q64_CS_LOW();
HAL_SPI_Transmit(&W25Q64_SPI, &cmd, 1, HAL_MAX_DELAY);
HAL_SPI_Receive(&W25Q64_SPI, id_buf, 3, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
return (id_buf[0] << 16) | (id_buf[1] << 8) | id_buf[2];
}
// === 读取数据 ===
void W25Q64_ReadData(uint32_t addr, uint8_t* buf, uint32_t len) {
uint8_t cmd[4];
cmd[0] = W25Q64_CMD_READ_DATA;
cmd[1] = (addr >> 16) & 0xFF;
cmd[2] = (addr >> 8) & 0xFF;
cmd[3] = addr & 0xFF;
W25Q64_CS_LOW();
HAL_SPI_Transmit(&W25Q64_SPI, cmd, 4, HAL_MAX_DELAY);
HAL_SPI_Receive(&W25Q64_SPI, buf, len, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
}
// === 页编程(最大 256 字节,不能跨页) ===
void W25Q64_PageProgram(uint32_t addr, const uint8_t* buf, uint32_t len) {
if (len > W25Q64_PAGE_SIZE) len = W25Q64_PAGE_SIZE;
W25Q64_WriteEnable();
uint8_t cmd[4];
cmd[0] = W25Q64_CMD_PAGE_PROGRAM;
cmd[1] = (addr >> 16) & 0xFF;
cmd[2] = (addr >> 8) & 0xFF;
cmd[3] = addr & 0xFF;
W25Q64_CS_LOW();
HAL_SPI_Transmit(&W25Q64_SPI, cmd, 4, HAL_MAX_DELAY);
HAL_SPI_Transmit(&W25Q64_SPI, (uint8_t*)buf, len, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
W25Q64_WaitBusy();
}
// === 扇区擦除(按4KB) ===
void W25Q64_SectorErase(uint32_t addr) {
W25Q64_WriteEnable();
uint8_t cmd[4];
cmd[0] = W25Q64_CMD_SECTOR_ERASE;
cmd[1] = (addr >> 16) & 0xFF;
cmd[2] = (addr >> 8) & 0xFF;
cmd[3] = addr & 0xFF;
W25Q64_CS_LOW();
HAL_SPI_Transmit(&W25Q64_SPI, cmd, 4, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
W25Q64_WaitBusy();
}
// === 整片擦除 ===
void W25Q64_ChipErase(void) {
W25Q64_WriteEnable();
uint8_t cmd = W25Q64_CMD_CHIP_ERASE;
W25Q64_CS_LOW();
HAL_SPI_Transmit(&W25Q64_SPI, &cmd, 1, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
W25Q64_WaitBusy();
}
4.2 主函数
/* 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 "w25q64.h"
#include <stdlib.h> // 提供 rand(), srand()
#include <string.h> // 提供 strlen()
/* 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 */
static uint8_t read_buffer[W25Q64_SECTOR_SIZE]; // 避免栈溢出
/* 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_SPI2_Init();
/* USER CODE BEGIN 2 */
printf("W25Q64 ID Read Test\r\n");
// 1.1 读取W25Q64芯片ID
uint32_t chipID = W25Q64_ReadID();
// 1.2 解析ID(W25Q64的ID是3字节:0xEF 0x40 0x17)
uint8_t manufacturer_id = (chipID >> 16) & 0xFF; // 第1字节:厂商ID(0xEF)
uint8_t memory_type = (chipID >> 8) & 0xFF; // 第2字节:内存类型(0x40)
uint8_t capacity_id = chipID & 0xFF; // 第3字节:容量ID(0x17)
// 1.3 计算实际容量(单位:字节)
uint32_t capacity_bytes = (1 << capacity_id); // 2^capacity_id(例如 0x17=23 → 2^23=8MB)
// 1.4 打印结果
printf("Manufacturer ID: 0x%02X\r\n", manufacturer_id); // 应为0xEF(Winbond)
printf("Memory Type: 0x%02X\r\n", memory_type); // 应为0x40(SPI Flash)
printf("Capacity ID: 0x%02X\r\n", capacity_id); // 应为0x17(64Mbit/8MB)
printf("Full ID: 0x%02X 0x%02X 0x%02X\r\n",
manufacturer_id, memory_type, capacity_id);
printf("Calculated Capacity: %lu bytes (%lu Mbit)\r\n",
capacity_bytes, capacity_bytes * 8 / (1024 * 1024));
printf("W25Q64 Flash Sector Test\r\n");
// 2.1 随机定义扇区编号(0 ~ 2047,因为 W25Q64 有 2048 个扇区)
uint32_t num = 1223;
uint32_t sector_num = num % 2048; // 随机扇区号
printf("Selected random sector: %lu\r\n", sector_num);
// 2.2 计算扇区的起始地址(扇区号 * 4KB)
uint32_t sector_addr = sector_num * W25Q64_SECTOR_SIZE; // 扇区大小(4KB)
printf("Sector start address: 0x%06lX\r\n", sector_addr);
// 2.3 擦除目标扇区(4KB)
printf("Erasing sector at 0x%06lX...\r\n", sector_addr);
W25Q64_SectorErase(sector_addr);
W25Q64_WaitBusy(); // 等待擦除完成
// 2.4 写入测试字符串到扇区起始地址
printf("Writing test string to sector...\r\n");
char data_string[W25Q64_PAGE_SIZE] = {0};// 定义可变字符串缓冲区(最大长度只能写一页 即 256 字节)
snprintf(data_string, sizeof(data_string),
"the fire page context: \r\n page 1, this is a w25q64 write data test, i can write 256 bytes in this page \r\n");
W25Q64_PageProgram(sector_addr, (uint8_t *)data_string, strlen(data_string)); // 写入第一页
W25Q64_WaitBusy(); // 等待写入完成
memset(data_string, 0, sizeof(data_string)); // 清零
snprintf(data_string, sizeof(data_string),
"the second page context: \r\n page 2, this is a w25q64 write data test, i can write 256 bytes in this page \r\n");
W25Q64_PageProgram(sector_addr + W25Q64_PAGE_SIZE, (uint8_t *)data_string, strlen(data_string)); // 写入第二页
W25Q64_WaitBusy(); // 等待写入完成
// 2.5 读取整个扇区内容
// uint8_t read_buffer[W25Q64_SECTOR_SIZE] = {0};
printf("Reading sector content...\r\n");
W25Q64_ReadData(sector_addr, read_buffer, W25Q64_SECTOR_SIZE);
// 2.6 打印读出的数据(以字符串形式,仅打印前 4页内容,即1024字节 避免刷屏)
printf("Read Data (as string, first 256 bytes):\r\n");
for (int i = 0; i < 1024; i++) {
char c = read_buffer[i];
printf("%c", c);
}
printf("\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("go on");
HAL_Delay(3000);
}
/* 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 */
5. 实验验证

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)