1. 初识STM32CubeMX与STM32F4开发板
第一次接触STM32CubeMX时,我完全被它的图形化界面惊艳到了。这个由ST公司推出的免费工具,彻底改变了传统嵌入式开发的配置方式。记得刚开始用寄存器开发STM32时,光是配置一个GPIO就要查半天参考手册,而现在只需要在图形界面上点几下就能完成。
STM32F4系列是ST的明星产品,我手头这块STM32F407G-DISC1开发板搭载了Cortex-M4内核,主频高达168MHz,还自带浮点运算单元。对于初学者来说,这块板子最棒的地方是板载了ST-LINK调试器,省去了额外购买调试工具的麻烦。
开发环境搭建很简单:先安装STM32CubeMX软件(目前最新版是6.10.0),再装个Keil MDK-ARM或者IAR EWARM。我推荐用Keil,因为它的社区版对STM32F4完全免费。装好软件后,记得更新STM32F4的HAL库,CubeMX里一键就能下载最新版本。
2. 第一个GPIO实验:点亮LED
2.1 工程创建与基本配置
打开CubeMX,点击"New Project",在MCU选择器里输入STM32F407VG(这是开发板的主控型号)。第一次使用时可能会被密密麻麻的引脚图吓到,别担心,我们只需要关注LED对应的引脚。
在开发板原理图上查到,绿色LED连接在PD12引脚。在CubeMX的引脚图中找到PD12,点击选择"GPIO_Output"。这时右侧的配置面板会显示GPIO参数:
- GPIO输出模式:推挽输出(Push-pull)
- 上拉/下拉:无
- 默认输出电平:低电平
- 最大输出速度:低速(Low)就够用
接着配置时钟树。在Clock Configuration标签页,把HCLK设为最大168MHz。CubeMX会自动计算各总线时钟,确保不超频。这里有个小技巧:先点击"输入频率"框旁边的HSE,输入8(开发板外部晶振是8MHz),然后直接输入168到HCLK框,软件会自动计算分频系数。
2.2 生成代码与编程
在Project Manager标签页设置工程名称和路径,我习惯把Toolchain选为MDK-ARM。关键是要勾选"Generate peripheral initialization as a pair of '.c/.h' files per peripheral",这样每个外设的代码会单独生成文件,方便维护。
点击GENERATE CODE,等进度条走完,直接"Open Project"启动Keil。在main.c的while(1)循环里添加这段代码:
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); HAL_Delay(500);编译后点击Load按钮,如果一切顺利,你会看到板子上的绿色LED开始每秒闪烁一次。第一次点灯成功的感觉特别棒,虽然只是个简单的GPIO操作,但这是掌握STM32的重要第一步。
2.3 GPIO使用技巧
在实际项目中,有几点经验值得分享:
- 驱动LED时,如果发现亮度不够,可以把GPIO速度改为High
- 按键检测要加软件消抖,我一般用10ms延时
- 不用的引脚最好配置为模拟输入,可以降低功耗
- 读取GPIO状态用HAL_GPIO_ReadPin()函数,返回值是GPIO_PIN_SET或GPIO_PIN_RESET
3. 进阶GPIO应用:按键控制LED
3.1 硬件电路分析
开发板上的用户按键连接在PA0,电路设计很有意思:按键松开时,PA0通过10kΩ电阻下拉到地;按下时,通过另一个电阻上拉到3.3V。这种设计省去了外部上拉电阻,但需要注意软件配置要与硬件匹配。
在CubeMX中添加PA0为GPIO_Input,由于硬件已有下拉电阻,GPIO配置中选择"No pull-up and no pull-down"(浮空输入)。如果硬件没有上下拉电阻,就需要在这里选择软件上拉或下拉。
3.2 按键检测实现
生成代码后,在主循环中添加按键检测逻辑。我推荐这种带消抖的写法:
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) { HAL_Delay(10); // 消抖延时 if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) { HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); // 翻转LED while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET); // 等待释放 } }这段代码实现了按键按下时LED状态翻转。while循环会卡住直到按键释放,防止多次触发。对于需要连续检测的应用,可以改用状态机方式实现。
4. 定时器基础:精准定时控制
4.1 定时器工作原理
STM32F407有14个定时器,分为基本定时器(TIM6/7)、通用定时器(TIM2-5,9-14)和高级定时器(TIM1/8)。基本定时器最简单,只有定时功能;通用定时器增加了输入捕获和PWM输出;高级定时器还有死区控制等高级功能。
定时器的核心是计数器,时钟源通常来自APB总线。以TIM6为例,当APB1时钟为84MHz时,通过预分频器(PSC)分频后得到计数时钟。计数器(CNT)从0开始递增,达到自动重装载值(ARR)时产生溢出,触发中断或事件。
4.2 定时器配置实例
我们配置TIM6每500ms触发一次中断。在CubeMX的Timers标签页找到TIM6:
- 时钟源选择内部时钟(Internal Clock)
- Prescaler设为8399(分频系数=8399+1=8400)
- Counter Mode选择Up(向上计数)
- Counter Period设为4999(ARR值=4999+1=5000)
- 勾选定时器中断
计算定时时间:(PSC+1)(ARR+1)/时钟频率 = 84005000/84MHz = 0.5s
生成代码后,在stm32f4xx_it.c的TIM6中断处理函数中,会自动调用HAL_TIM_IRQHandler()。我们需要在main.c里实现回调函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim == &htim6) { HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); } }别忘了在main()中启动定时器:
HAL_TIM_Base_Start_IT(&htim6);5. PWM输出:呼吸灯效果
5.1 PWM原理与配置
PWM(脉宽调制)通过调节占空比来控制平均电压,广泛应用于电机控制、LED调光等场景。STM32的通用定时器都能产生PWM,我们以TIM4的通道1(PD12,与LED共用引脚)为例。
在CubeMX中配置TIM4:
- 时钟源:内部时钟
- Channel1:PWM Generation CH1
- Prescaler:83(84分频,1MHz计数频率)
- Counter Period:999(ARR=999,PWM周期=1ms)
- Pulse:初始占空比设为500(50%)
在代码中启动PWM:
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);5.2 实现呼吸灯效果
通过不断调整CCR(捕获比较寄存器)值来改变占空比:
uint16_t pulse = 0; uint8_t dir = 1; while (1) { HAL_Delay(10); if(dir) { pulse++; if(pulse >= 999) dir = 0; } else { pulse--; if(pulse <= 0) dir = 1; } __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, pulse); }这段代码会让LED亮度从暗到亮循环变化,产生呼吸灯效果。实际项目中,PWM频率要根据负载特性选择,比如控制电机通常用10-20kHz,避免可闻噪声。
6. 输入捕获:测量PWM参数
6.1 输入捕获原理
输入捕获功能可以测量脉冲宽度或频率。STM32的PWM输入模式特别实用,它能自动测量周期和占空比,原理是利用两个通道分别捕获上升沿和下降沿。
我们使用TIM9的通道1(PE5)来测量TIM4输出的PWM波形。需要先用杜邦线连接PD12(TIM4_CH1)和PE5(TIM9_CH1)。
6.2 CubeMX配置
配置TIM9为PWM输入模式:
- Combined Channels:PWM Input on CH1
- Channel1:Input Capture direct mode
- Channel2:Input Capture indirect mode
- 极性:Channel1上升沿,Channel2下降沿
- 分频和滤波保持默认
- 使能TIM9全局中断
生成代码后,实现捕获回调函数:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim == &htim9) { uint32_t ic1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); uint32_t ic2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); printf("Period: %d us, Duty: %d%%\r\n", ic1/84, ic2*100/ic1); // 84MHz时钟,转换为us } }启动定时器和捕获:
HAL_TIM_Base_Start_IT(&htim9); HAL_TIM_IC_Start_IT(&htim9, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim9, TIM_CHANNEL_2);串口会打印出测量的PWM周期和占空比,可以验证前面的呼吸灯程序是否按预期工作。