简介:本资源是一个基于STM32的智能鱼缸嵌入式项目源码包,面向嵌入式初学者与课程设计实践者,聚焦环境参数监测(如水温、水位)、自动增氧、LED照明调控等典型物联网控制场景,助力理解MCU外设驱动、传感器数据采集与闭环控制逻辑。压缩包共1714个文件,主体为393个C源文件与310个头文件(h),构成完整Keil工程结构;辅以215个依赖文件(d)、195个目标文件(o)及调试配置(dbgconf)、链接脚本(sct)、固件镜像(bin/hex/axf)等,体现从开发、编译到烧录的全流程工程形态;另含PNG/GIF界面图、Python脚本及LVGL图形库示例代码(如lv_demo_printer_img_*.c),表明支持轻量级GUI交互。资源大小61.29MB,目录命名清晰(如Smart_Fish_Tank.uvprojx工程入口),结构规范便于学习迁移。目前已有1813人学习下载,提供可直接编译运行的完整工程框架、模块化驱动代码与典型外设应用范例,是掌握STM32实战开发的优质参考素材。
1. 用 STM32 做鱼缸控制器,不是拼传感器堆功能,而是让水温、水位、光照真正可闭环
“stm32鱼缸1.zip”这个压缩包名在嵌入式初学者论坛和毕业设计资源区高频出现——它往往代表一个未完整标注的 STM32 最小系统实践入口:不是工业级水产监控,而是面向家庭小型生态缸的轻量级智能管理原型。这类项目核心诉求非常具体:在不依赖 Wi-Fi 模块或云平台的前提下,用 STM32F103C8T6(或类似主流 Cortex-M3 芯片)实时采集水温(DS18B20)、水位(超声波或电极式)、光照强度(BH1750)、继电器控制加热棒/水泵/LED 灯,并通过 OLED 或串口调试助手实现状态反馈。它不追求高并发或远程 APP,但对时序稳定性、ADC 采样抗干扰、IO 驱动能力、低功耗待机有明确要求。适合电子类本科毕设、课设、创客入门者快速验证“单片机+模拟信号+执行器”闭环逻辑。如果你正卡在 Keil5 加载芯片包后报错error: no stm32 target found!,或发现 DS18B20 读数跳变、继电器吸合时 OLED 屏闪,说明你已进入真实调试阶段——这恰恰是本方案要拆解的实操断点。
2. 从 .zip 解压到 Keil5 工程可编译:STM32F103 开发环境搭建与芯片包安装关键路径
2.1 解压后文件结构识别与工程定位逻辑
stm32fish1.zip通常包含以下典型目录:
Core/:HAL 库或标准外设库(如stm32f10x.h+stm32f10x_conf.h)Drivers/:自定义驱动(ds18b20.c、hc_sr04.c、bh1750.c)User/:主逻辑(main.c、led_control.c、relay_control.c)Startup/:启动文件(startup_stm32f10x_md.s)Project.uvprojx:Keil5 工程文件
提示:若解压后无
.uvprojx文件,说明该压缩包为源码包而非完整工程,需手动新建工程并添加文件;若存在但 Keil 打开报错,大概率是芯片型号未匹配或 CMSIS 包缺失。
2.2 Keil5 安装 STM32F103 芯片包的三步确认法
Keil5 默认不内置 STM32 支持,必须通过 Pack Installer 补全。常见错误error: no stm32 target found!的根因并非硬件连接问题,而是软件层缺失:
2.2.1 检查 Pack Installer 中的 STM32F1xx Device Family Support
打开 Keil →Pack Installer(工具栏图标或Project → Manage → Pack Installer),在左侧树状列表中展开:
Keil → STM32F1xx_DFP→ 确认版本 ≥ 2.3.0(2021 年后版本兼容 F103C8T6)ARM → CMSIS→ 确认CMSIS 5.x.x已安装(HAL 库依赖此)Keil → ARM Compiler→ 确认ARM Compiler 5.06或ARM Compiler 6.x已启用(F1 系列推荐 AC5)
若未安装,勾选后点击右下角Install。安装完成后重启 Keil。
2.2.2 工程配置中 Target 页的关键参数设置
打开工程 →Project → Options for Target...→Target页:
Device:必须选择STM32F103C8(非 Generic Cortex-M3)Crystal (MHz):填入你板子实际晶振值(常见 8 MHz,不是 12 MHz;若用内部 RC 则填 8)Use MicroLIB:勾选(减少 printf 占用空间,避免重定向失败)Code Generation:ARM Compiler选ARM Compiler 5
注意:若此处 Device 下拉菜单为空,说明 DFP 未正确加载;若选中后编译仍报
no target found,检查是否误选了STM32F103CB(大容量版)而实际使用的是C8T6(中容量版),二者 Flash 大小不同,链接脚本会失败。
2.2.3 启动文件与链接脚本的型号匹配验证
在Project → Options for Target... → Target页底部,点击Manage Run-Time Environment→ 展开Device → Startup→ 勾选startup_stm32f10x_md.s(对应 Medium-density,即 64KB Flash 的 C8T6)。
同时确认Linker页中Use Memory Layout from Target Dialog已勾选,且IRAM1和IROM1地址与STM32F103C8规格一致:
| 区域 | 起始地址 | 大小 |
|---|---|---|
| IROM1 | 0x08000000 | 0x00010000 (64KB) |
| IRAM1 | 0x20000000 | 0x00005000 (20KB) |
若链接时报region IROM1 overflowed,说明代码体积超限,需关闭Debug → Debug Information或启用Optimization Level: -O2。
2.3 编译前必做的三个头文件路径修正
Keil 默认不自动识别 HAL/StdPeriph 库路径,需手动添加:Project → Options for Target... → C/C++ → Include Paths中添加:
.\Core\Inc .\Drivers\Inc .\User\ .\CMSIS\Device\ST\STM32F1xx\Include .\CMSIS\Include其中CMSIS目录需从 Keil 安装目录复制(如C:\Keil_v5\ARM\PACK\Keil\STM32F1xx_DFP\2.3.0\CMSIS),或从官网下载CMSIS_5解压后引用。缺少core_cm3.h报错即源于此。
3. 鱼缸核心传感器与执行器驱动落地:DS18B20 水温、HC-SR04 水位、继电器控制的抗干扰实现
3.1 DS18B20 单总线通信的时序容错与温度解析
鱼缸水温监测对精度和稳定性要求高,DS18B20 是最常用选择,但其 1-Wire 协议极易受布线干扰导致CRC check failed。stm32fish1.zip中常见ds18b20.c实现需强化三点:
3.1.1 GPIO 初始化必须禁用上拉/下拉,仅设为推挽输出+浮空输入
// 正确配置(以 PA0 为例) GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 输出模式用于写0 GPIO_InitStruct.Pull = GPIO_NOPULL; // 绝对禁止上拉!否则读1时被拉低 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 读取时临时切换为输入 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); // 释放总线 HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0); GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式读1 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);3.1.2 温度读取函数增加重试与 CRC 校验强制过滤
uint8_t DS18B20_Read_Temp(float *temp) { uint8_t data[9]; uint8_t i; // 发送 Skip ROM + Convert T 命令 if (DS18B20_Reset() != DS18B20_PRESENCE_OK) return 1; DS18B20_Write_Byte(0xCC); // Skip ROM DS18B20_Write_Byte(0x44); // Convert T HAL_Delay(750); // 必须等待转换完成,不能用定时器替代 if (DS18B20_Reset() != DS18B20_PRESENCE_OK) return 1; DS18B20_Write_Byte(0xCC); DS18B20_Write_Byte(0xBE); // Read Scratchpad for(i=0; i<9; i++) data[i] = DS18B20_Read_Byte(); // 读9字节 // CRC8 校验(X8+X5+X4+1 多项式) uint8_t crc = 0; for(i=0; i<8; i++) { crc ^= data[i]; for(uint8_t j=0; j<8; j++) { if(crc & 0x01) crc = (crc >> 1) ^ 0x8C; else crc >>= 1; } } if(crc != data[8]) return 1; // CRC 失败直接丢弃 int16_t raw = (data[1] << 8) | data[0]; // 温度原始值 *temp = (float)raw * 0.0625f; // 转换为摄氏度 return 0; }提示:
HAL_Delay(750)不可省略,DS18B20 在 12-bit 分辨率下转换需 750ms;若用HAL_TIM_Base_Start_IT()替代,需确保中断优先级高于传感器读取,否则时序错乱。
3.2 HC-SR04 超声波水位检测的脉宽测量与液面反射修正
鱼缸水位易受水面波动、气泡、壁面反射影响,单纯测距误差可达 ±2cm。stm32fish1.zip中常采用 TIM2 输入捕获,但需规避常见陷阱:
3.2.1 TIM2 输入捕获通道配置要点
// 使用 TIM2_CH1 (PA0) 捕获 Echo 高电平时间 __HAL_RCC_TIM2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); TIM_IC_InitTypeDef sConfigIC = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 71; // 72MHz / (71+1) = 1MHz 计数频率 htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 0xFFFF; HAL_TIM_IC_Init(&htim2); sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0x0F; // 15个采样周期滤波,抑制毛刺 HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);3.2.2 水位计算引入温度补偿与多次采样中值滤波
#define SPEED_OF_SOUND_AT_25C 346.1f // m/s #define TEMP_COEFF 0.6f // 每℃速度变化 0.6 m/s float get_water_level_cm(float temp_c) { static uint16_t capture_values[5] = {0}; static uint8_t idx = 0; // 获取5次捕获值存入数组(实际应用中需在中断中完成) uint32_t t_high_us = (capture_values[idx] * 1000) / 1000; // 转为微秒 float distance_mm = (t_high_us * SPEED_OF_SOUND_AT_25C * (1.0f + TEMP_COEFF * (temp_c - 25.0f))) / 2000.0f; // 中值滤波去抖动 uint16_t sorted[5]; memcpy(sorted, capture_values, sizeof(sorted)); qsort(sorted, 5, sizeof(uint16_t), compare_uint16); uint16_t median = sorted[2]; idx = (idx + 1) % 5; return distance_mm / 10.0f; // 转为 cm }注意:
ICFilter = 0x0F是关键,HC-SR04 Echo 信号边沿易受干扰,不加滤波会导致捕获值随机跳变;温度补偿不可省略,水温每变10℃,声速偏差约6m/s,对应水位误差达±3mm。
3.3 继电器驱动电路与 IO 口保护设计
鱼缸加热棒/水泵功率常达 20W~100W,STM32 IO 口无法直驱,必须通过光耦+MOSFET 隔离。stm32fish1.zip中常见错误是直接用 PB0 控制继电器模块输入端,导致:
- 继电器线圈反向电动势击穿 STM32 IO(无续流二极管)
- 多路继电器共地引发串扰(水泵启停时 LED 灯频闪)
3.3.1 推荐硬件连接方式(以 5V 继电器模块为例)
| STM32 IO | 光耦输入 | 光耦输出 | MOSFET G | 继电器线圈 |
|---|---|---|---|---|
| PB0 | PC817 A | PC817 K | SI2302 G | VCC → 线圈 → S |
| GND | PC817 K | MOSFET S | MOSFET S | MOSFET D → GND |
必须添加:
- 光耦输入端串联 330Ω 限流电阻(防止过流)
- 继电器线圈并联 1N4007 续流二极管(阴极接 VCC)
- MOSFET 漏极与源极间加 10kΩ 下拉电阻(确保关断)
3.3.2 软件层防误触发与状态同步
typedef enum { RELAY_OFF, RELAY_ON } RelayState; RelayState heater_state = RELAY_OFF; void set_heater(RelayState state) { if(state == RELAY_ON && heater_state == RELAY_OFF) { // 开启前延时 10ms 消除电源波动影响 HAL_Delay(10); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); heater_state = RELAY_ON; } else if(state == RELAY_OFF && heater_state == RELAY_ON) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); heater_state = RELAY_OFF; HAL_Delay(10); // 关断后保持低电平 10ms } } // 主循环中调用 if(water_temp < 24.0f && heater_state == RELAY_OFF) { set_heater(RELAY_ON); } else if(water_temp > 26.0f && heater_state == RELAY_ON) { set_heater(RELAY_OFF); }提示:
HAL_Delay(10)在开关瞬间插入,可有效抑制继电器触点弹跳引起的误动作;状态变量heater_state必须与物理状态严格同步,避免因中断打断导致状态错乱。
4. OLED 显示与串口调试双通道状态可视化:SSD1306 驱动优化与 UART 数据帧解析
4.1 SSD1306 I2C 驱动在 STM32F103 上的时序适配
stm32fish1.zip中 OLED 显示常出现花屏或部分区域不刷新,根源在于 I2C 速率与 SSD1306 时序冲突。F103 的 I2C1 最高支持 400kHz,但 SSD1306 在 100kHz 下更稳定:
4.1.1 I2C 初始化关键参数设置
hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; // 强制设为 100kHz,非 400kHz hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_16_9; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; HAL_I2C_Init(&hi2c1);注意:
ClockSpeed = 100000是硬性要求,实测 400kHz 下 SSD1306 的0x20(内存寻址模式)命令易丢失,导致后续数据写入错位;NoStretchMode = DISABLE允许从机拉低 SCL,适应 OLED 内部处理延迟。
4.1.2 OLED 刷新策略:局部更新 + 帧缓冲压缩
全屏刷新(128×64=1024 字节)耗时约 80ms,导致主循环卡顿。应改为只刷新变化区域:
uint8_t oled_buffer[128*8]; // 128×64 点阵,每行8字节 void oled_update_temp(float temp) { // 计算温度数值所在区域(第2行,x=32,y=16) uint8_t page = 2; // 第2页(0~7页) uint8_t start_x = 32; uint8_t len = 4; // "25.5" 占4字符,每个字符8×16,需4×2=8字节/行 uint8_t *src = font_ascii_8x16[(int)(temp)+10]; // 简化字体映射 for(uint8_t y=0; y<16; y++) { for(uint8_t x=0; x<len; x++) { oled_buffer[(page*128) + start_x + x + y*128] = src[y*len + x]; } } oled_write_buffer(oled_buffer, page, start_x, len, 16); }oled_write_buffer()函数只发送变化的 16×4 区域(64 字节),耗时降至 5ms 内。
4.2 串口调试协议设计:ASCII 帧格式与防粘包解析
为方便调试,stm32fish1.zip通常启用 USART1 输出状态,但原始代码多为printf("Temp:%.1f\r\n", temp),导致接收端(如串口助手)无法区分多条消息。应定义结构化帧:
4.2.1 自定义帧格式与发送函数
// 帧格式:$FISH,TEMP,25.3,HUMI,65.2,LEVEL,12.5,*XX\r\n // XX 为校验和(所有字符 ASCII 码异或) void send_fish_frame(float temp, float humi, float level) { char buf[64]; uint8_t checksum = 0; int len = sprintf(buf, "$FISH,TEMP,%.1f,HUMI,%.1f,LEVEL,%.1f,", temp, humi, level); for(int i=0; i<len; i++) checksum ^= buf[i]; sprintf(buf+len, "*%02X\r\n", checksum); HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 100); }4.2.2 接收端环形缓冲区 + 帧头帧尾识别
#define RX_BUF_SIZE 128 uint8_t rx_buffer[RX_BUF_SIZE]; volatile uint16_t rx_head = 0, rx_tail = 0; void USART1_IRQHandler(void) { uint8_t ch; if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET) { ch = (uint8_t)(huart1.Instance->DR & 0xFF); rx_buffer[rx_head] = ch; rx_head = (rx_head + 1) % RX_BUF_SIZE; } } // 主循环中解析 void parse_uart_frame() { while(rx_head != rx_tail) { if(rx_buffer[rx_tail] == '$') { uint16_t pos = rx_tail; while(pos != rx_head && rx_buffer[pos] != '\n') pos = (pos + 1) % RX_BUF_SIZE; if(pos != rx_head && rx_buffer[pos] == '\n') { // 提取 $...*XX\n 区间 uint8_t frame_len = (pos - rx_tail + 1 + RX_BUF_SIZE) % RX_BUF_SIZE; if(frame_len < 20) { // 过滤过短帧 parse_fish_command(&rx_buffer[rx_tail], frame_len); } rx_tail = (pos + 1) % RX_BUF_SIZE; // 移动尾指针 } else { rx_tail = (rx_tail + 1) % RX_BUF_SIZE; // 丢弃无效字符 } } else { rx_tail = (rx_tail + 1) % RX_BUF_SIZE; } } }提示:
parse_fish_command()函数需实现逗号分割与*XX校验和验证,避免因线路干扰导致误解析;环形缓冲区大小RX_BUF_SIZE至少为最大帧长的 2 倍(建议 128),防止溢出丢帧。
5. 实战排错:从 ST-Link 下载失败到传感器读数跳变的六类高频问题定位技巧
5.1 ST-Link Utility 无法识别芯片的硬件级排查清单
当 Keil 编译通过但Download按钮灰色或报No STM32 target found,按顺序检查:
| 检查项 | 正确状态 | 错误表现 |
|---|---|---|
| SWD 接线 | SWCLK→PA13, SWDIO→PA14, GND 共地,不接 NRST(除非需复位下载) | 用杜邦线直连时,SWDIO 线过长(>15cm)导致信号反射 |
| 供电电压 | VDDA/VDD = 3.3V ±0.3V,用电压表实测芯片引脚 | USB 供电不足(尤其接 OLED+继电器时),VDD 掉至 2.8V |
| BOOT0/BOOT1 | BOOT0=0, BOOT1=0(正常运行模式) | BOOT0=1 导致进入系统存储器,ST-Link 无法接管 |
| 晶振起振 | 示波器测 OSC_IN 有 8MHz 正弦波 | 无波形 → 晶振虚焊或负载电容不匹配(8MHz 晶振配 22pF) |
注意:若 ST-Link Utility 能识别芯片但 Keil 不能,检查 Keil
Options for Target → Debug → Settings → Port是否选SW而非JTAG;JTAG 引脚(PB3/PB4)若被复用为普通 IO,必须禁用 JTAG 才能释放。
5.2 DS18B20 读数跳变的三层归因与验证法
现象:同一水体温度显示在 22.1℃ ~ 28.5℃ 间无规律跳变。
5.2.1 物理层:单总线终端电阻与布线
- 必须在 DS18B20 VDD 与 GND 间加 4.7kΩ 上拉电阻(非 10kΩ)
- 数据线长度 ≤ 2 米,超过需加 DS2480B 总线驱动器
- 水中探头需用热缩管完全密封,避免水汽渗入导致漏电
5.2.2 电气层:电源纹波与地线噪声
用示波器测 VDD 波形,若峰峰值 > 100mV:
- 在 VDD 与 GND 间加 100μF 电解电容 + 0.1μF 陶瓷电容
- 绝对禁止将继电器驱动电源与 STM32 共用一路 LDO,必须分离供电
5.2.3 代码层:时序精度与 CRC 强制校验
- 禁用
HAL_Delay(),改用SysTick中断计时(HAL_InitTick(TICK_INT_PRIORITY)) - 在
DS18B20_Read_Temp()中增加 CRC 校验失败重试(最多 3 次),失败则返回上次有效值
5.3 OLED 显示异常的针对性修复方案
| 现象 | 根本原因 | 修复操作 |
|---|---|---|
| 全屏黑/白 | I2C 地址错误(SSD1306 默认 0x78,部分模块为 0x7A) | 用 I2C 扫描工具(如 Arduino I2C Scanner)确认地址 |
| 右侧 16 列不亮 | 初始化命令0xA8(Set Multiplex Ratio)参数错误 | 将0xA8, 0x3F改为0xA8, 0x3F(64 行) |
| 文字残影 | 未发送清屏命令或帧缓冲未初始化 | 在OLED_Init()结尾添加OLED_Fill(0x00) |
5.4 串口接收数据粘连的缓冲区溢出防护
当连续发送多帧$FISH,...\r\n时,rx_buffer可能被填满导致后续帧丢失:
// 在 USART1_IRQHandler 中添加溢出保护 if((rx_head + 1) % RX_BUF_SIZE == rx_tail) { // 缓冲区满,丢弃最早字节 rx_tail = (rx_tail + 1) % RX_BUF_SIZE; } rx_buffer[rx_head] = ch; rx_head = (rx_head + 1) % RX_BUF_SIZE;同时在parse_uart_frame()中增加帧长上限判断:
if(frame_len > 64) { // 超过预设最大帧长 rx_tail = (rx_tail + 1) % RX_BUF_SIZE; // 丢弃当前帧 continue; }5.5 Keil5 编译报错undefined symbol的三类源头
| 错误示例 | 源头 | 解决方案 |
|---|---|---|
undefined symbol HAL_GPIO_WritePin | stm32f1xx_hal_gpio.c未添加到工程 | Project → Add Group → Add Files to Group添加该文件 |
undefined symbol __use_no_semihosting | 未重定向printf | 在main.c添加#pragma import(__use_no_semihosting)并实现_sys_exit() |
undefined symbol SystemCoreClock | system_stm32f1xx.c未加入工程或HSE_VALUE定义错误 | 检查stm32f1xx.h中#define HSE_VALUE ((uint32_t)8000000)是否匹配晶振 |
5.6 鱼缸项目特有的低功耗陷阱:待机时继电器意外吸合
现象:主循环while(1) { HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI); }后,继电器仍保持吸合。
根源:STM32 睡眠模式下,IO 口默认保持最后电平。若吸合时 PB0 为高,则睡眠中持续高电平驱动光耦。
修复方案:
// 进入睡眠前强制关闭所有执行器 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); // 加热棒 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); // 水泵 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET); // LED // 配置唤醒源(如按键 EXTI) HAL_NVIC_EnableIRQ(EXTI0_IRQn); HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);唤醒后需重新初始化外设时钟,否则HAL_GPIO_WritePin无效。
本文还有配套的精品资源,点击获取