文章目录
-
- 一、前言
-
- [1.1 技术背景](#1.1 技术背景)
- [1.2 本文目标](#1.2 本文目标)
- [1.3 读者收获](#1.3 读者收获)
- 二、环境准备
-
- [2.1 硬件准备](#2.1 硬件准备)
- [2.2 软件安装](#2.2 软件安装)
-
- [2.2.1 安装STM32CubeMX](#2.2.1 安装STM32CubeMX)
- [2.2.2 安装STM32CubeIDE](#2.2.2 安装STM32CubeIDE)
- [2.2.3 安装驱动程序](#2.2.3 安装驱动程序)
- [2.3 环境验证](#2.3 环境验证)
-
- [2.3.1 验证STM32CubeMX](#2.3.1 验证STM32CubeMX)
- [2.3.2 验证STM32CubeIDE](#2.3.2 验证STM32CubeIDE)
- 三、项目创建与配置
-
- [3.1 使用STM32CubeMX创建工程](#3.1 使用STM32CubeMX创建工程)
-
- [3.1.1 新建工程](#3.1.1 新建工程)
- [3.1.2 配置时钟树](#3.1.2 配置时钟树)
- [3.1.3 配置GPIO引脚](#3.1.3 配置GPIO引脚)
- [3.1.4 配置GPIO参数](#3.1.4 配置GPIO参数)
- [3.1.5 配置NVIC中断](#3.1.5 配置NVIC中断)
- [3.1.6 生成代码](#3.1.6 生成代码)
- [3.2 工程代码结构](#3.2 工程代码结构)
- 四、核心代码实现
-
- [4.1 主程序代码](#4.1 主程序代码)
- [4.2 主循环实现](#4.2 主循环实现)
- [4.3 LED控制函数实现](#4.3 LED控制函数实现)
- [4.4 中断回调函数](#4.4 中断回调函数)
- [4.5 头文件修改](#4.5 头文件修改)
- 五、系统架构与流程
-
- [5.1 系统架构图](#5.1 系统架构图)
- [5.2 程序流程图](#5.2 程序流程图)
- 六、编译与下载
-
- [6.1 编译工程](#6.1 编译工程)
- [6.2 连接硬件](#6.2 连接硬件)
- [6.3 下载程序](#6.3 下载程序)
- [6.4 运行测试](#6.4 运行测试)
- 七、故障排查与问题解决
-
- [7.1 编译错误](#7.1 编译错误)
- [7.2 硬件问题](#7.2 硬件问题)
- [7.3 程序运行异常](#7.3 程序运行异常)
- [7.4 调试技巧](#7.4 调试技巧)
- 八、总结与扩展
-
- [8.1 核心知识点回顾](#8.1 核心知识点回顾)
- [8.2 扩展学习方向](#8.2 扩展学习方向)
- [8.3 学习资源](#8.3 学习资源)
一、前言
1.1 技术背景
STM32是意法半导体(STMicroelectronics)推出的基于ARM Cortex-M内核的32位微控制器系列,凭借其高性能、低功耗、丰富的外设接口和完善的开发工具链,已成为嵌入式开发领域的主流选择。STM32F103系列作为Cortex-M3内核的经典型号,以其高性价比和广泛的社区支持,成为初学者入门STM32开发的首选。
HAL(Hardware Abstraction Layer,硬件抽象层)库是ST官方推出的新一代固件库,相比早期的标准库,HAL库具有更好的可移植性、更简洁的API接口和更完善的错误处理机制。配合STM32CubeMX图形化配置工具,开发者可以快速生成初始化代码,大幅降低开发门槛。
1.2 本文目标
本文将带领读者完成以下实战项目:
- 使用STM32CubeMX配置GPIO引脚,实现LED流水灯效果
- 配置外部中断,实现按键检测功能
- 理解HAL库的工作原理和编程模型
- 掌握STM32开发的基本流程和调试技巧
1.3 读者收获
完成本教程后,你将能够:
- 独立使用STM32CubeMX创建工程并配置外设
- 理解GPIO的工作模式和HAL库的API调用方式
- 掌握外部中断的配置和回调函数编写
- 具备基础的STM32调试能力
技术栈:
- 开发板:STM32F103C8T6(最小系统板)
- 开发环境:STM32CubeIDE 或 Keil MDK
- 配置工具:STM32CubeMX 6.x
- 固件库:STM32Cube HAL Library
- 调试工具:ST-Link V2
二、环境准备
2.1 硬件准备
必需硬件:
| 设备 | 型号/规格 | 数量 | 说明 |
|---|---|---|---|
| STM32开发板 | STM32F103C8T6 最小系统板 | 1 | 核心控制器 |
| ST-Link调试器 | ST-Link V2 | 1 | 程序下载和调试 |
| LED灯 | 3mm 各色LED | 4 | 流水灯显示 |
| 电阻 | 220Ω | 4 | LED限流 |
| 按键 | 轻触开关 | 2 | 中断触发 |
| 杜邦线 | 母对母/公对母 | 若干 | 连接电路 |
| 面包板 | 标准面包板 | 1 | 搭建电路 |
硬件连接示意图:
STM32F103C8T6 引脚分配:
- PA0 - LED1 (红色)
- PA1 - LED2 (绿色)
- PA2 - LED3 (蓝色)
- PA3 - LED4 (黄色)
- PB0 - 按键1 (外部中断)
- PB1 - 按键2 (外部中断)
2.2 软件安装
2.2.1 安装STM32CubeMX
Windows系统:
- 访问ST官网下载页面:https://www.st.com/en/development-tools/stm32cubemx.html
- 下载最新版本的STM32CubeMX安装包
- 运行安装程序,按向导完成安装
- 首次启动时,配置固件库下载路径
macOS系统:
bash
# 使用Homebrew安装(如有可用formula)
brew install --cask stm32cubemx
# 或从官网下载.dmg安装包
Linux系统:
bash
# 下载Linux版本的zip包
unzip stm32cubemx.zip
cd stm32cubemx
./SetupSTM32CubeMX-6.x.x
2.2.2 安装STM32CubeIDE
STM32CubeIDE是ST官方推出的集成开发环境,基于Eclipse框架,集成了编译器、调试器和STM32CubeMX。
下载地址: https://www.st.com/en/development-tools/stm32cubeide.html
安装步骤:
- 下载对应操作系统的安装包
- 运行安装程序
- 选择安装路径(建议路径不含中文和空格)
- 完成安装并启动
2.2.3 安装驱动程序
ST-Link驱动:
- Windows:安装STM32CubeIDE时会自动安装
- 手动安装:从ST官网下载STSW-LINK009
验证驱动安装:
bash
# Windows设备管理器中应显示:
# - STMicroelectronics STLink dongle
# - STM32 STLink
2.3 环境验证
2.3.1 验证STM32CubeMX
- 启动STM32CubeMX
- 点击 File → New Project
- 在MCU选择器中输入"STM32F103C8"
- 选择 STM32F103C8Tx 并双击
- 如果能正常打开引脚配置界面,说明安装成功
2.3.2 验证STM32CubeIDE
- 启动STM32CubeIDE
- 选择工作空间路径
- 创建测试工程:
- File → New → STM32 Project
- 选择STM32F103C8Tx
- 输入项目名称"test_project"
- 完成创建
- 如果能成功创建工程并显示代码,说明环境正常
三、项目创建与配置
3.1 使用STM32CubeMX创建工程
3.1.1 新建工程
- 打开STM32CubeMX,点击 File → New Project
- 在MCU/MPU Selector中输入"STM32F103C8"
- 选择 STM32F103C8Tx ,点击 Start Project
3.1.2 配置时钟树
STM32F103最高支持72MHz主频,我们需要配置PLL使系统运行在最高频率。
- 点击 Clock Configuration 标签
- 配置时钟源:
- HSE(高速外部时钟) :选择 Crystal/Ceramic Resonator
- PLL Source Mux :选择 HSE
- PLLMul :选择 x9(8MHz * 9 = 72MHz)
- System Clock Mux :选择 PLLCLK
时钟配置验证:
- HCLK(MHz):72.0
- APB1 Prescaler:/2(36MHz)
- APB2 Prescaler:/1(72MHz)
3.1.3 配置GPIO引脚
配置LED引脚(PA0-PA3):
- 在Pinout视图中,点击PA0引脚
- 选择 GPIO_Output
- 右键PA0,选择 Enter User Label,输入"LED1"
- 重复上述步骤配置PA1、PA2、PA3,分别标记为LED2、LED3、LED4
配置按键引脚(PB0、PB1):
- 点击PB0引脚
- 选择 GPIO_EXTI0(外部中断线0)
- 右键PB0,选择 Enter User Label,输入"KEY1"
- 点击PB1引脚
- 选择 GPIO_EXTI1(外部中断线1)
- 右键PB1,选择 Enter User Label,输入"KEY2"
3.1.4 配置GPIO参数
-
点击 GPIO 标签,进入GPIO配置界面
-
配置LED引脚:
- 选择 PA0/LED1
- GPIO mode:Output Push Pull(推挽输出)
- GPIO Pull-up/Pull-down:No pull-up and no pull-down
- Maximum output speed:Low
- User Label:LED1
- 对PA1、PA2、PA3进行相同配置
-
配置按键引脚:
- 选择 PB0/KEY1
- GPIO mode:External Interrupt Mode with Rising edge trigger detection(上升沿触发)
- GPIO Pull-up/Pull-down:Pull-down(下拉,按键按下时为高电平)
- User Label:KEY1
- 对PB1进行相同配置
3.1.5 配置NVIC中断
- 点击 NVIC 标签
- 启用外部中断:
- 勾选 EXTI line0 interrupt
- 勾选 EXTI line1 interrupt
- 配置中断优先级(可选):
- 保持默认优先级即可
3.1.6 生成代码
- 点击 Project → Settings(或按Alt+P)
- 配置工程参数:
- Project Name:LED_Key_Interrupt
- Project Location:选择保存路径
- Toolchain / IDE :选择 STM32CubeIDE(或你使用的IDE)
- 点击 Code Generator 标签:
- 勾选 Generate peripheral initialization as a pair of '.c/.h' files per peripheral
- 勾选 Keep User Code when re-generating
- 点击 GENERATE CODE 按钮
- 等待代码生成完成,点击 Open Project
3.2 工程代码结构
生成的工程包含以下关键文件:
LED_Key_Interrupt/
├── Core/
│ ├── Inc/
│ │ ├── main.h # 主头文件
│ │ ├── stm32f1xx_hal_conf.h # HAL配置文件
│ │ └── stm32f1xx_it.h # 中断处理头文件
│ └── Src/
│ ├── main.c # 主程序
│ ├── stm32f1xx_hal_msp.c # HAL MSP初始化
│ ├── stm32f1xx_it.c # 中断服务函数
│ └── system_stm32f1xx.c # 系统时钟配置
├── Drivers/
│ └── STM32F1xx_HAL_Driver/ # HAL库驱动
└── LED_Key_Interrupt.ioc # CubeMX配置文件
四、核心代码实现
4.1 主程序代码
📄 创建文件:
Core/Src/main.c
打开STM32CubeIDE中的main.c文件,在/* USER CODE BEGIN PV */和/* USER CODE END PV */之间添加私有变量:
c
/* USER CODE BEGIN PV */
// LED流水灯状态变量
volatile uint8_t led_mode = 0; // 当前LED显示模式
volatile uint8_t led_direction = 0; // 流水灯方向:0=左移,1=右移
volatile uint8_t led_speed = 200; // 流水灯速度(毫秒)
// 按键消抖变量
volatile uint32_t key1_last_tick = 0;
volatile uint32_t key2_last_tick = 0;
#define KEY_DEBOUNCE_MS 200 // 按键消抖时间(毫秒)
/* USER CODE END PV */
在/* USER CODE BEGIN PFP */和/* USER CODE END PFP */之间添加函数原型声明:
c
/* USER CODE BEGIN PFP */
// LED控制函数
void LED_SetAll(uint8_t state);
void LED_FlowLeft(void);
void LED_FlowRight(void);
void LED_ToggleAll(void);
// 延时函数(非阻塞)
void Delay_NonBlocking(uint32_t ms);
/* USER CODE END PFP */
4.2 主循环实现
在main()函数的/* USER CODE BEGIN WHILE */和/* USER CODE END WHILE */之间添加主循环代码:
c
/* USER CODE BEGIN WHILE */
while (1)
{
// 根据当前模式执行对应的LED效果
switch(led_mode)
{
case 0: // 模式0:流水灯向左移动
LED_FlowLeft();
break;
case 1: // 模式1:流水灯向右移动
LED_FlowRight();
break;
case 2: // 模式2:全部LED闪烁
LED_ToggleAll();
HAL_Delay(led_speed);
break;
case 3: // 模式3:全部LED常亮
LED_SetAll(1);
break;
default: // 默认模式:全部熄灭
LED_SetAll(0);
break;
}
// 可以在这里添加其他任务
}
/* USER CODE END WHILE */
4.3 LED控制函数实现
在/* USER CODE BEGIN 4 */和/* USER CODE END 4 */之间添加LED控制函数:
c
/* USER CODE BEGIN 4 */
/**
* @brief 设置所有LED状态
* @param state: 0=全部熄灭, 1=全部点亮
* @retval None
*/
void LED_SetAll(uint8_t state)
{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, (GPIO_PinState)state);
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, (GPIO_PinState)state);
HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, (GPIO_PinState)state);
HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, (GPIO_PinState)state);
}
/**
* @brief 流水灯向左移动效果
* @param None
* @retval None
*/
void LED_FlowLeft(void)
{
static uint8_t current_led = 0; // 当前点亮的LED索引
// 先熄灭所有LED
LED_SetAll(0);
// 根据当前索引点亮对应的LED
switch(current_led)
{
case 0:
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
break;
case 1:
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
break;
case 2:
HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET);
break;
case 3:
HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET);
break;
}
// 移动到下一个LED
current_led++;
if(current_led >= 4)
{
current_led = 0;
}
// 延时
HAL_Delay(led_speed);
}
/**
* @brief 流水灯向右移动效果
* @param None
* @retval None
*/
void LED_FlowRight(void)
{
static uint8_t current_led = 3; // 当前点亮的LED索引(从右开始)
// 先熄灭所有LED
LED_SetAll(0);
// 根据当前索引点亮对应的LED
switch(current_led)
{
case 0:
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
break;
case 1:
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
break;
case 2:
HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET);
break;
case 3:
HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET);
break;
}
// 移动到下一个LED(向右)
if(current_led == 0)
{
current_led = 3;
}
else
{
current_led--;
}
// 延时
HAL_Delay(led_speed);
}
/**
* @brief 翻转所有LED状态
* @param None
* @retval None
*/
void LED_ToggleAll(void)
{
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
HAL_GPIO_TogglePin(LED4_GPIO_Port, LED4_Pin);
}
/* USER CODE END 4 */
4.4 中断回调函数
📝 修改文件:
Core/Src/stm32f1xx_it.c
在stm32f1xx_it.c文件中,找到/* USER CODE BEGIN EXTI0_IRQn 0 */和/* USER CODE END EXTI0_IRQn 0 */之间的位置,添加KEY1的中断处理代码:
c
/**
* @brief This function handles EXTI line0 interrupt.
*/
void EXTI0_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
// 软件消抖:检查距离上次按键的时间
uint32_t current_tick = HAL_GetTick();
if((current_tick - key1_last_tick) < KEY_DEBOUNCE_MS)
{
// 消抖时间内,忽略此次中断
__HAL_GPIO_EXTI_CLEAR_IT(KEY1_Pin);
return;
}
key1_last_tick = current_tick;
// 切换LED模式(0->1->2->3->0)
led_mode++;
if(led_mode > 3)
{
led_mode = 0;
}
/* USER CODE END EXTI0_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(KEY1_Pin);
/* USER CODE BEGIN EXTI0_IRQn 1 */
/* USER CODE END EXTI0_IRQn 1 */
}
在/* USER CODE BEGIN EXTI1_IRQn 0 */和/* USER CODE END EXTI1_IRQn 0 */之间添加KEY2的中断处理代码:
c
/**
* @brief This function handles EXTI line1 interrupt.
*/
void EXTI1_IRQHandler(void)
{
/* USER CODE BEGIN EXTI1_IRQn 0 */
// 软件消抖
uint32_t current_tick = HAL_GetTick();
if((current_tick - key2_last_tick) < KEY_DEBOUNCE_MS)
{
__HAL_GPIO_EXTI_CLEAR_IT(KEY2_Pin);
return;
}
key2_last_tick = current_tick;
// KEY2功能:切换流水灯速度
switch(led_speed)
{
case 200:
led_speed = 100; // 快速
break;
case 100:
led_speed = 50; // 更快
break;
case 50:
led_speed = 500; // 慢速
break;
case 500:
led_speed = 200; // 默认速度
break;
default:
led_speed = 200;
break;
}
/* USER CODE END EXTI1_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(KEY1_Pin);
/* USER CODE BEGIN EXTI1_IRQn 1 */
/* USER CODE END EXTI1_IRQn 1 */
}
4.5 头文件修改
📝 修改文件:
Core/Inc/main.h
确保main.h中包含以下宏定义(CubeMX已自动生成):
c
/* Private defines -----------------------------------------------------------*/
#define LED1_Pin GPIO_PIN_0
#define LED1_GPIO_Port GPIOA
#define LED2_Pin GPIO_PIN_1
#define LED2_GPIO_Port GPIOA
#define LED3_Pin GPIO_PIN_2
#define LED3_GPIO_Port GPIOA
#define LED4_Pin GPIO_PIN_3
#define LED4_GPIO_Port GPIOA
#define KEY1_Pin GPIO_PIN_0
#define KEY1_GPIO_Port GPIOB
#define KEY1_EXTI_IRQn EXTI0_IRQn
#define KEY2_Pin GPIO_PIN_1
#define KEY2_GPIO_Port GPIOB
#define KEY2_EXTI_IRQn EXTI1_IRQn
五、系统架构与流程
5.1 系统架构图
模式0
模式1
模式2
模式3
系统启动
时钟初始化
GPIO初始化
NVIC中断配置
主循环运行
当前模式?
流水灯左移
流水灯右移
全部闪烁
全部常亮
KEY1按下
EXTI0中断
软件消抖
切换模式
KEY2按下
EXTI1中断
软件消抖
切换速度
5.2 程序流程图
0
1
2
3
开始
系统初始化
时钟配置 72MHz
GPIO配置
PA0-3输出
PB0-1中断
NVIC中断使能
主循环
检查led_mode
流水灯左移
LED1→LED2→LED3→LED4
流水灯右移
LED4→LED3→LED2→LED1
全部闪烁
间隔led_speed
全部常亮
延时led_speed
KEY1中断
软件消抖
200ms
led_mode++
0→1→2→3→0
返回主循环
KEY2中断
软件消抖
200ms
切换速度
200→100→50→500→200
返回主循环
六、编译与下载
6.1 编译工程
-
在STM32CubeIDE中,点击工具栏的 Build 按钮(锤子图标)
-
或按快捷键 Ctrl+B
-
等待编译完成,查看Console窗口的输出:
Building target: LED_Key_Interrupt.elf
Invoking: MCU GCC Linker
...
Finished building target: LED_Key_Interrupt.elfBuild Finished. 0 errors, 0 warnings. (took 5s.)
6.2 连接硬件
按照以下方式连接ST-Link和开发板:
ST-Link V2 STM32F103C8T6
┌─────────┐ ┌──────────┐
│ 3.3V │────────→│ 3.3V │
│ GND │────────→│ G │
│ SWDIO │────────→│ DIO │
│ SWCLK │────────→│ CLK │
└─────────┘ └──────────┘
LED连接:
PA0 ──[220Ω]──┳── LED1(+)
┗── GND(-)
PA1 ──[220Ω]──┳── LED2(+)
┗── GND(-)
PA2 ──[220Ω]──┳── LED3(+)
┗── GND(-)
PA3 ──[220Ω]──┳── LED4(+)
┗── GND(-)
按键连接:
PB0 ──[按键1]──→ 3.3V
PB1 ──[按键2]──→ 3.3V
PB0 ──[10KΩ]──→ GND(下拉电阻)
PB1 ──[10KΩ]──→ GND(下拉电阻)
6.3 下载程序
- 点击工具栏的 Debug 按钮(虫子图标)
- 或按快捷键 F11
- 如果是首次下载,会提示选择调试器,选择 ST-Link
- 程序将自动下载到STM32并启动运行
6.4 运行测试
预期现象:
- 上电后:4个LED开始流水灯效果,从左到右依次点亮
- 按下KEY1 :切换LED显示模式
- 模式0:流水灯向左移动
- 模式1:流水灯向右移动
- 模式2:全部LED闪烁
- 模式3:全部LED常亮
- 按下KEY2 :切换流水灯速度
- 速度1:200ms(默认)
- 速度2:100ms(快)
- 速度3:50ms(更快)
- 速度4:500ms(慢)
七、故障排查与问题解决
7.1 编译错误
问题1:未定义标识符 'LED1_GPIO_Port'
错误现象:
error: 'LED1_GPIO_Port' undeclared (first use in this function)
原因分析:
- 宏定义未正确生成
- main.h文件未包含
- CubeMX配置后未重新生成代码
解决方案:
方案1:检查main.h文件
打开Core/Inc/main.h,确认包含以下宏定义:
c
#define LED1_Pin GPIO_PIN_0
#define LED1_GPIO_Port GPIOA
方案2:重新生成代码
- 打开STM32CubeMX
- 点击 GENERATE CODE
- 在提示"Do you want to open the project in STM32CubeIDE?"时选择 Yes
- 重新编译
方案3:手动添加宏定义
如果CubeMX未生成,在main.h的/* Private defines */区域手动添加:
c
#define LED1_Pin GPIO_PIN_0
#define LED1_GPIO_Port GPIOA
#define LED2_Pin GPIO_PIN_1
#define LED2_GPIO_Port GPIOA
#define LED3_Pin GPIO_PIN_2
#define LED3_GPIO_Port GPIOA
#define LED4_Pin GPIO_PIN_3
#define LED4_GPIO_Port GPIOA
#define KEY1_Pin GPIO_PIN_0
#define KEY1_GPIO_Port GPIOB
#define KEY2_Pin GPIO_PIN_1
#define KEY2_GPIO_Port GPIOB
问题2:重复定义错误
错误现象:
error: redefinition of 'variable_name'
原因分析:
- 变量在多个文件中重复定义
- 头文件未使用#ifndef保护
- 全局变量定义在头文件中
解决方案:
方案1:使用extern声明
在头文件中使用extern声明,在源文件中定义:
c
// main.h
extern volatile uint8_t led_mode;
// main.c
volatile uint8_t led_mode = 0;
方案2:检查头文件保护
确保所有头文件都有保护宏:
c
#ifndef __MAIN_H
#define __MAIN_H
// 头文件内容
#endif /* __MAIN_H */
7.2 硬件问题
问题3:LED不亮
排查步骤:
-
检查电源
- 使用万用表测量开发板3.3V和GND之间电压
- 正常应在3.2V-3.4V之间
-
检查LED极性
- LED长脚为正极(接GPIO)
- LED短脚为负极(接GND)
- 反接会导致LED不亮但不会损坏
-
检查限流电阻
- 确认使用220Ω电阻
- 电阻过大(如10KΩ)会导致LED亮度极低
- 电阻过小(如10Ω)可能烧毁LED
-
检查GPIO输出
- 使用万用表测量GPIO引脚电压
- 高电平应在3.0V以上
- 低电平应在0.5V以下
验证代码:
c
// 在主循环中添加测试代码
while (1)
{
// 强制点亮LED1进行测试
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
HAL_Delay(1000);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
HAL_Delay(1000);
}
问题4:按键无响应
排查步骤:
-
检查按键连接
- 确认按键一端接GPIO,一端接3.3V
- 确认下拉电阻(10KΩ)连接正确
-
检查中断配置
- 在CubeMX中确认PB0/PB1配置为External Interrupt
- 确认NVIC中启用了EXTI0和EXTI1中断
-
检查触发方式
- 如果按键按下为高电平,应选择Rising edge触发
- 如果按键按下为低电平,应选择Falling edge触发
-
添加调试代码
c
// 在中断服务函数中添加调试
void EXTI0_IRQHandler(void)
{
// 点亮LED1表示进入中断
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
// 原有代码...
}
7.3 程序运行异常
问题5:按键抖动导致模式乱跳
现象:
- 按一次按键,模式连续切换多次
原因:
- 机械按键存在抖动(5-20ms)
- 中断被多次触发
解决方案:
已在本教程中实现软件消抖:
c
// 软件消抖:检查距离上次按键的时间
uint32_t current_tick = HAL_GetTick();
if((current_tick - key1_last_tick) < KEY_DEBOUNCE_MS)
{
// 消抖时间内,忽略此次中断
__HAL_GPIO_EXTI_CLEAR_IT(KEY1_Pin);
return;
}
key1_last_tick = current_tick;
硬件消抖方案(可选):
在按键两端并联100nF电容,配合上拉/下拉电阻形成RC滤波电路。
问题6:程序卡死或进入HardFault
排查步骤:
-
检查中断服务函数
- 确保中断服务函数执行时间极短
- 不要在中断中调用延时函数(HAL_Delay)
- 不要在中断中进行复杂计算
-
检查堆栈溢出
-
在启动文件(startup_stm32f103xb.s)中检查堆栈大小:
Stack_Size EQU 0x00000400 ; 1KB栈空间
-
如果程序复杂,可适当增大:
Stack_Size EQU 0x00000800 ; 2KB栈空间
-
-
使用调试器定位
- 在Debug模式下运行
- 当程序卡死时,点击暂停按钮
- 查看Call Stack窗口,定位问题位置
7.4 调试技巧
使用断点调试
- 在代码行号左侧双击设置断点
- 以Debug模式运行程序
- 程序会在断点处暂停
- 使用以下快捷键:
- F5:继续运行
- F6:单步跳过(Step Over)
- F7:单步进入(Step Into)
- F8:继续运行(Resume)
查看变量值
- 在Debug视图中,打开 Expressions 窗口
- 点击 Add new expression
- 输入变量名(如
led_mode) - 实时查看变量值变化
使用串口输出调试信息
c
// 在main.c中添加串口初始化(CubeMX配置USART1)
printf("Current mode: %d, Speed: %d\r\n", led_mode, led_speed);
八、总结与扩展
8.1 核心知识点回顾
- STM32CubeMX使用:掌握图形化配置工具,快速生成初始化代码
- HAL库编程:理解HAL_GPIO_WritePin、HAL_GPIO_TogglePin等API的使用
- 外部中断配置:学会配置GPIO中断、编写中断服务函数、实现软件消抖
- 系统时钟配置:了解PLL倍频、时钟树配置的基本原理
8.2 扩展学习方向
进阶主题:
- 定时器中断(TIM):使用定时器替代HAL_Delay实现更精确的定时
- DMA传输:学习直接内存访问,提高数据传输效率
- 低功耗模式:掌握Sleep、Stop、Standby模式的应用
- FreeRTOS:学习实时操作系统,实现多任务调度
实战项目:
- PWM调光:使用定时器PWM功能实现LED亮度调节
- ADC采集:读取电位器电压,实现LED亮度可调
- 串口通信:通过串口接收命令,远程控制LED模式
- I2C/SPI通信:驱动OLED显示屏,显示当前模式信息
8.3 学习资源
官方文档:
官方GitHub: