搞过单片机控制直流电机的老铁都知道,PWM调速是必杀技。今天咱们就拿STM32和L298N这对黄金搭档,手把手教你实现一个稳如老狗的电机控制系统。先说清楚,这可不是那些只会贴代码的教程,我会把原理、坑点、调试技巧全盘托出,保你从连电路到写代码一条龙打通关。
先看核心装备:STM32F103C8T6(江湖人称蓝 pill)和L298N驱动模块。为什么选这俩?STM32的定时器输出PWM那叫一个溜,而L298N皮实耐操,最大能扛46V电压、2A电流,驱动个小电机跟玩似的。实测用12V电源时,模块发热量完全可以接受,长时间运行不带虚的。
L298N的接线看着简单,但新手最容易在这里翻车。记住这个铁律:
重要提示:L298N的逻辑供电(5V)和驱动供电(12V)必须共地!我见过太多人因为没共地导致电机抽风似的乱转。
仿真时这些细节要命:
来看这段关键代码,我加了超详细注释:
c复制void TIM3_PWM_Init(u16 arr, u16 psc) {
// 1. 时钟使能 - 定时器3挂载在APB1上
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. GPIO配置 - 必须复用推挽输出!
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 重点!普通推挽输出不行
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 时基配置
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = arr; // 自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = psc; // 预分频系数
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// 4. PWM模式配置
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_Cmd(TIM3, ENABLE); // 启动定时器
}
PWM频率计算公式:
code复制Fpwm = 主频 / ( (arr+1) * (psc+1) )
举例:72MHz主频,psc=71,arr=999,则:
code复制Fpwm = 72MHz / (72 * 1000) = 1kHz
为什么选1kHz?实测发现:
c复制if(KEY0==0){
delay_ms(10); // 必须消抖!
if(KEY0==0){
speed +=10; // 步进10个单位
if(speed >1000) speed=1000; // 限制最大值
TIM_SetCompare1(TIM3,speed); // 修改CCR值
}
}
经验之谈:直接跳变占空比会导致电机抖动,实测步进值设为ARR的1%-5%最平滑。
加个加速度限制更专业:
c复制void Set_Smooth_Speed(u16 target) {
static u16 current = 0;
while(current != target) {
if(current < target) current++;
else current--;
TIM_SetCompare1(TIM3, current);
delay_ms(5); // 调整这个值改变加速度
}
}
正反转切换的正确姿势:
c复制void Motor_Direction(u8 dir) {
// 先停PWM
TIM_SetCompare1(TIM3, 0);
delay_ms(5); // 给MOS管续流时间
if(dir == FORWARD){
GPIO_SetBits(GPIOA,GPIO_Pin_0);
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}else{
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
delay_ms(5); // 稳定后再启PWM
}
为什么这么麻烦?因为L298N内部的MOS管需要时间完全关断,直接切换会导致瞬间短路,轻则电机抖动,重则烧驱动!
c复制printf("当前速度:%d%%\r\n", speed*100/1000);
记得在Keil里勾选Use MicroLIB,否则浮点打印会卡死。建议用整型运算代替浮点,节省资源。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 电机不转 | 供电不足 | 检查12V电源电流是否≥2A |
| 只有一个方向转 | IN1/IN2接反 | 交换两个控制线 |
| 电机抖动严重 | PWM频率不对 | 调整arr/psc到1-3kHz |
| L298N发烫 | 电机堵转 | 检查机械负载是否过大 |
| 仿真报错 | 模型设置错误 | 确认L298N组件值改为"L298" |
最后说个血泪教训:有一次调试时电机突然反转,查了半天发现是杜邦线接触不良。所以强烈建议: