第4章 STM32微控制器入门:从认识芯片到点亮LED
好,咱们进入第四课。前面聊了电机控制的基础理论,现在该碰碰硬件了。做伺服驱动,STM32几乎是绕不开的选择。我个人最早接触STM32是在2010年,那时候还在用F103,现在想想,真是满满的回忆。
这一章,我会带你快速过一遍STM32的核心知识点。别怕,咱们不搞大而全的教科书式讲解,只挑伺服开发最常用的部分。说白了,就是让你能上手写代码、能控制电机。
4.1 STM32系列怎么选?
STM32家族很庞大,从M0到M7,性能跨度很大。做伺服驱动,我建议重点关注两个系列:
| 系列 | 内核 | 主频 | 适合场景 |
|---|---|---|---|
| STM32F4 | Cortex-M4F | 168-180MHz | 通用伺服、FOC控制 |
| STM32G4 | Cortex-M4F | 170MHz | 高性能伺服、数字电源 |
| STM32H7 | Cortex-M7 | 480MHz | 多轴控制、复杂算法 |
我个人习惯用G4系列做伺服。为什么?因为它内置了CORDIC硬件加速器和HRTIM高分辨率定时器。做FOC电流环,这两个外设能省不少事。你想想看,电流环周期做到1us以内,用普通定时器很难,但HRTIM可以。
核心观点:选型不是越贵越好。F103也能做伺服,但性能上限低。G4是性价比之选,H7留给高端应用。
4.2 开发环境搭建
搭建环境这事儿,说简单也简单,说坑也多。我建议你用STM32CubeIDE,它是ST官方基于Eclipse的免费IDE,集成了CubeMX配置工具。
安装步骤很简单:
- 去ST官网下载STM32CubeIDE,选对应操作系统版本
- 一路Next安装,注意安装路径不要有中文
- 安装完成后,打开IDE,它会自动检测你的ST-Link调试器
小技巧:我第一次装的时候,死活连不上开发板。后来发现是驱动没装。记得装ST-Link驱动,或者直接用IDE自带的驱动安装工具。
建一个新项目也很简单:File → New → STM32 Project,然后选你的芯片型号。比如我用的是STM32G474RET6。接下来CubeMX会帮你配置时钟、外设,生成初始化代码。
4.3 GPIO编程——点亮第一盏灯
GPIO是基础中的基础。做伺服驱动,GPIO用来控制使能信号、读取编码器索引、输出报警信号等等。
先看一个最简单的例子:点亮LED。假设LED接在PA5引脚上,高电平点亮。
// 初始化GPIO
void GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
// 主循环中点亮
int main(void)
{
HAL_Init();
GPIO_Init();
while(1)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // 点亮
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 熄灭
HAL_Delay(500);
}
}
嗯,这里要注意:时钟使能这一步很容易忘。我刚开始学的时候,经常忘了开时钟,结果GPIO死活没反应。后来养成了习惯:用哪个外设,先开它的时钟。
4.4 定时器配置——伺服的心脏
定时器在伺服驱动里太重要了。PWM生成、编码器计数、电流采样触发,全得靠它。
STM32的定时器分几类:
- 基本定时器(TIM6/TIM7): 只能做定时,没有PWM输出
- 通用定时器(TIM2-TIM5): 有PWM、输入捕获、编码器模式
- 高级定时器(TIM1/TIM8): 有互补PWM、刹车功能、死区插入
做电机控制,高级定时器是首选。比如TIM1,可以输出三相互补PWM,带死区控制。我曾经在一个项目里,因为死区时间设得太小,导致MOS管直通烧了。嗯,从那以后我再也不敢马虎了。
看一个配置PWM输出的例子:
void TIM1_PWM_Init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef sConfigOC = {0};
__HAL_RCC_TIM1_CLK_ENABLE();
htim1.Instance = TIM1;
htim1.Init.Prescaler = psc;
htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1; // 中央对齐模式
htim1.Init.Period = arr;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_PWM_Init(&htim1);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = arr/2; // 50%占空比
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); // 启动互补通道
}
避坑指南:中央对齐模式和边沿对齐模式,产生的PWM波形不一样。做FOC电流采样,我建议用中央对齐模式,因为可以在计数器的上溢和下溢点触发ADC采样,正好避开开关噪声。
4.5 中断系统——别让CPU空转
中断是嵌入式系统的灵魂。做伺服驱动,中断用得特别多:
- 定时器中断:做周期性的电流环、速度环
- 外部中断:处理编码器索引信号、限位开关
- DMA中断:传输ADC数据、通信数据
配置一个定时器中断很简单:
void TIM2_IRQ_Init(void)
{
__HAL_RCC_TIM2_CLK_ENABLE();
htim2.Instance = TIM2;
htim2.Init.Prescaler = 84-1; // 84MHz/84 = 1MHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 1000-1; // 1ms中断一次
HAL_TIM_Base_Init(&htim2);
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); // 最高优先级
HAL_NVIC_EnableIRQ(TIM2_IRQn);
HAL_TIM_Base_Start_IT(&htim2); // 启动中断模式
}
// 中断服务函数
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2);
}
// 回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
{
// 1ms执行一次,适合做速度环
SpeedLoop();
}
}
为什么会这样?因为HAL库把中断处理封装好了,你只需要写回调函数。但要注意:中断服务函数里不要做耗时操作。我曾经见过有人直接在中断里做浮点运算,结果导致中断响应超时,电机失控。
个人经验:中断里只做标志位设置或数据拷贝,真正的计算放到主循环或任务里。比如电流环,我习惯在定时器中断里只读取ADC值、设置PWM占空比,速度环计算放到主循环。
4.6 本章知识体系
下面这张图,是我梳理的本章知识结构。你可以把它当作一个思维导图来看:
这张图把本章内容串起来了。你看,从选型开始,到环境搭建,再到GPIO、定时器、中断,每一步都是环环相扣的。做伺服驱动,这些基础打不牢,后面写FOC算法、做电流环,会非常痛苦。
好了,这一章就到这里。记住:动手实践是最好的学习方式。拿出你的开发板,把LED点亮,把PWM波形调出来,把中断跑通。遇到问题别怕,我当年也是一路踩坑过来的。
公众号:蓝海资料掘金营,微信deep3321