搞过单片机控制直流电机的都知道,PWM调速绝对是基本功里的战斗机。今天咱们就拿STM32和L298N这对黄金搭档,手把手教你实现一个稳如老狗的电机调速系统。别看原理简单,这里面的门道可不少——从硬件连线到寄存器配置,从仿真调试到实际运行,每个环节都有新手容易踩的坑。我把自己在工控项目里摸爬滚打总结的经验都揉进这个教程,保证你看完就能撸出可用的代码。
先说说为什么选这套方案。STM32的定时器产生PWM波形那叫一个稳,而L298N作为经典双H桥驱动芯片,最大能扛46V电压、2A电流,驱动个小电机绰绰有余。最关键的是这组合成本低、资料多,特别适合练手。实测下来,1kHz左右的PWM频率对大多数直流电机都是甜点区,既避免了高频啸叫,又保证了调速平滑性。
code复制STM32F103C8T6 L298N驱动板
PA6(定时器3_CH1) ---> ENA
PA0 ---> IN1
PA1 ---> IN2
GND ---> GND
重要提示:ENA必须接PWM引脚!有些教程用普通IO口直接高低电平控制,那样只能开关不能调速,属于典型错误用法。
c复制void TIM3_PWM_Init(u16 arr, u16 psc)
{
// 时钟使能部分
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 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);
// 定时器基础配置
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);
// 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);
}
假设系统时钟72MHz,要生成1kHz PWM:
调试技巧:用逻辑分析仪抓取PA6波形,确认频率和占空比是否符合预期。如果发现波形畸变,检查GPIO是否配置为复用推挽输出模式。
原始代码直接步进10%的占空比变化会导致电机明显抖动,改进方案:
c复制// 平滑调速函数
void Smooth_Speed_Adjust(int target)
{
static int current = 0;
int step = (target > current) ? 5 : -5;
while(current != target) {
current += step;
if((step > 0 && current > target) ||
(step < 0 && current < target)) {
current = target;
}
TIM_SetCompare1(TIM3, current);
delay_ms(20); // 调节这个值控制加速斜率
}
}
实测对比数据:
| 调速方式 | 启动电流峰值 | 机械噪音 | 响应时间 |
|---|---|---|---|
| 直接跳变 | 2.1A | 明显咔哒声 | <10ms |
| 平滑调速 | 1.3A | 几乎无声 | 约200ms |
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 电机不转 | L298N模型未修改 | 按4.1步骤修改组件值 |
| 转速不稳定 | 仿真步长太大 | 菜单栏→Debug→Set Animation Options→将步长改为1ms |
| PWM波形异常 | 定时器配置错误 | 检查TIM3初始化参数是否与代码一致 |
| 方向控制失效 | 输入引脚接反 | 确认IN1/IN2与代码中的GPIO对应关系 |
在仿真电路中添加虚拟示波器,同时监测:
这样能直观看到PWM占空比与电机响应的对应关系。特别当电机堵转时,电流波形会出现明显尖峰,这时就该考虑增加过流保护了。
原始代码中直接切换方向可能导致H桥上下管瞬间直通,改进方案:
c复制void Safe_Direction_Switch(u8 dir)
{
TIM_SetCompare1(TIM3, 0); // 先停止PWM输出
delay_ms(5); // 等待电流衰减
if(dir == FORWARD) {
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);
GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET);
} else {
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);
GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);
}
delay_ms(5); // 确保方向稳定后再启PWM
}
当电机长时间保持固定转速时,可以降低PWM频率来减少开关损耗:
c复制void Set_PWM_Frequency(u32 freq)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 计算最优预分频和重载值
u32 clock = SystemCoreClock / 2; // APB1定时器时钟
u32 psc = clock / (freq * 1000) - 1;
u32 arr = 1000 - 1;
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);
TIM_Cmd(TIM3, ENABLE);
}
实测数据表明,当电机转速稳定时,将PWM频率从1kHz降到500Hz可降低L298N温升约15℃,而对转速稳定性几乎没有影响。
工业现场常见问题及解决方案:
code复制Motor_Control_Project/
├── Hardware/
│ ├── L298N_Schematic.pdf # 驱动板原理图
│ └── Wiring_Diagram.jpg # 实物接线参考
├── Software/
│ ├── Core/
│ │ ├── main.c # 主循环和按键处理
│ │ └── motor.c # 电机控制核心函数
│ ├── Drivers/
│ │ ├── tim_pwm.c # PWM定时器配置
│ │ └── gpio.c # 方向控制IO配置
│ └── Project/
│ └── Motor_Control.uvprojx # Keil工程文件
└── Simulation/
├── Motor_Control.pdsprj # Proteus工程
└── Virtual_Test_Plan.md # 仿真测试用例
在Keil工程设置中务必勾选"Use MicroLIB",否则printf浮点数会卡死。如果遇到"no space"错误,在Target选项里把IRAM和IROM大小调整为: