一、课程目标
-
掌握STM32CubeMX工具链的基础使用
-
理解裸机开发中GPIO输入/输出的配置原理
-
实现按键控制LED的完整功能(按键按下点亮,松开熄灭)
-
掌握Keil MDK基础调试技巧
-
建立代码规范意识,为后续RTOS开发打基础
二、知识准备
-
裸机开发概念
-
无操作系统,直接操作硬件寄存器
-
代码运行在
main()
的超级循环中 -
适合简单、实时性要求不高的场景
-
-
CubeMX工具定位
-
ST官方图形化配置工具
-
自动生成初始化代码(HAL/LL库)
-
可视化配置时钟树、外设、中间件等
-
-
GPIO工作模式
模式 适用场景 Output Push-Pull LED驱动 Input Floating 按键检测(无上下拉) Input Pull-Up/Down 按键检测(内置电阻)
二、技术选型说明
技术栈 | 选型理由 |
---|---|
STM32CubeMX | 图形化配置工具,自动生成初始化代码,缩短开发周期 |
HAL库 | 硬件抽象层降低开发难度,跨平台移植性强 |
Keil MDK | 行业主流IDE,支持STM32全系芯片调试 |
ST-Link调试器 | 官方推荐调试工具,支持实时变量监控 |
三、实战步骤
1. CubeMX工程创建
-
芯片选型
-
选择实际使用的STM32型号(STM32F103C8T6)
-
建议使用主流开发板对应的芯片型号
-
-
时钟配置
-
启用外部高速时钟(HSE)
-
SYS 选项卡勾选Serial Wire,不勾选可能会使得无法使用stlink或jlink无法下载。
-
RCC选项卡中将HSE设置为晶振。
-
配置系统时钟为最大频率(如72MHz)
-
验证APB总线分频比(决定GPIO速度)
-
外设时钟的分配原理外设以设定的组别挂在0x0000 0000到0xFFFF FFFF的内存区域中。
-
-
GPIO配置
-
LED引脚(PA5):
-
Mode:
Output Push-Pull
-
Label:
USER_LED
-
-
按键引脚(PC13):
-
Mode:
Input Floating
-
Label:
USER_KEY
-
-
-
生成代码
-
Toolchain: MDK-ARM V5
-
勾选
Generate peripheral initialization as a pair of .c/.h files
-
5. GPIO的两个输出HAL库函数:
1. HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) * 控制一个GPIO引脚输出高或者低电平
2. HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin) * 控制一个GPIO引脚电平翻转,调用一次电平翻转一次
2. 代码开发
1. main.c 超级循环实现
// 包含HAL库头文件
#include "main.h"
// 定义按键状态检测宏(防抖动基础版)
#define KEY_PRESSED() (HAL_GPIO_ReadPin(USER_KEY_GPIO_Port, USER_KEY_Pin) == GPIO_PIN_RESET)
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while(1) {
if(KEY_PRESSED()) {
HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, GPIO_PIN_SET); // 点亮LED
} else {
HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, GPIO_PIN_RESET); // 熄灭LED
}
// 简单延时防止CPU跑满
HAL_Delay(10);
}
}
2. 按键消抖优化(状态机版)
typedef enum {
KEY_STATE_RELEASED,
KEY_STATE_DEBOUNCE,
KEY_STATE_PRESSED
} KeyState;
KeyState key_state = KEY_STATE_RELEASED;
uint32_t key_press_tick = 0;
void Key_Handler(void) {
switch(key_state) {
case KEY_STATE_RELEASED:
if(KEY_PRESSED()) {
key_press_tick = HAL_GetTick();
key_state = KEY_STATE_DEBOUNCE;
}
break;
case KEY_STATE_DEBOUNCE:
if(HAL_GetTick() - key_press_tick > 20) { // 20ms消抖
if(KEY_PRESSED()) {
key_state = KEY_STATE_PRESSED;
// 触发LED动作
HAL_GPIO_TogglePin(USER_LED_GPIO_Port, USER_LED_Pin);
} else {
key_state = KEY_STATE_RELEASED;
}
}
break;
case KEY_STATE_PRESSED:
if(!KEY_PRESSED()) {
key_state = KEY_STATE_RELEASED;
}
break;
}
}
// 在main循环中调用Key_Handler()
总结:
1. 按键按下的瞬间引脚的电平不是马上从高电平变为低电平或者从低电平变成高电平; 而是有一个抖动;
2. 可以使用加延时判断的办法消除这个抖动;
3. 缺点:延时时间不好掌握,容易让程序对按键的反应过于灵敏或者迟钝;
四、调试技巧
-
Keil Debug基础操作
-
断点设置:在关键代码行按F9
-
查看寄存器:
Peripherals → GPIO
-
逻辑分析仪:
// 添加测试引脚 #define DEBUG_PIN GPIO_PIN_0 HAL_GPIO_TogglePin(GPIOA, DEBUG_PIN); // 在按键处理中插入
-
-
常见问题排查
现象 排查步骤 LED不亮 1. 检查GPIO模式是否为输出
2. 测量引脚电压
3. 确认LED电路极性按键检测不稳定 1. 增加硬件消抖电路(10kΩ电阻+104电容)
2. 优化软件消抖算法代码卡死 1. 检查时钟配置是否正确
2. 排查未初始化的外设
五、课后总结
1. 项目立项到交付的基本流程;
2. STM32CubeMX除了帮助我们初始化外设生成代码之外,还可以用来做什么?
3. STM32CubeMX中的Debug选项如果不设置的话会有什么后果?
4. 如何将STM32F103的系统时钟配置为最大值72MHz?
关键配置点
- 时钟源:使用外部晶振(HSE,通常8MHz)
- PLL倍频:将HSE通过PLL倍频至72MHz(8MHz × 9 = 72MHz)
- 总线分频:
- AHB总线(HCLK)无分频(72MHz)
- APB1总线(PCLK1)二分频(36MHz,不超过最大限制)
- APB2总线(PCLK2)无分频(72MHz)
- Flash等待周期:设置为2个等待周期(72MHz需此配置)
5. 使用STM32CubeMX配置GPIO的步骤;
6. 使用STM32CubeMX配置UART/USART的步骤;
7. 使用STM32CubeMX生成工程之前,有哪些配置值得注意?
1. 工程保存路径,不可有特殊字符,中文
2. 时钟源选择,考虑有无外部时钟,精度问题
3. 时钟树分频与倍频系数选择,确保配置界面无红色警告
4. 代码生成选项,勾选Generate peripheral initialization as a pair of .c/.h files,为每个外设生成独立文件,便于维护
5.调试接口配置,在SYS中选择调试模式(如SWD需配置Serial Wire),否则无法通过ST-Link下载程序
6. 中间件与RTOS集成,若需使用FreeRTOS或文件系统(如FATFS),需在Middleware中启用并配置任务堆栈等参数
7. 生成后注意事项,用户代码需写在/* USER CODE BEGIN */和/* USER CODE END */之间,避免重新生成时被覆盖
8.对于读取按键,有没有更好的方法?
六、一些思考
1.为什么选择浮空输入模式而不是上拉输入?
2.HAL_Delay()是如何实现的?有什么缺点?如何不使用HAL_Delay()的情况下实现精确延时?
3.如何测量GPIO翻转的最大频率(使用示波器或逻辑分析仪)