1. 为什么需要单定时器生成四路独立PWM?
在机器人控制、无人机飞控等场景中,经常需要同时控制多个电机。传统做法是为每个电机分配独立定时器,但STM32F4的定时器资源有限(通常只有8-10个通用定时器),当需要控制四个以上电机时就会捉襟见肘。更麻烦的是,标准PWM模式下,同一个定时器的不同通道只能输出相同频率的波形(占空比可调),这在需要独立控制电机转速的场景中根本不够用。
去年我做四轴飞行器项目时就遇到这个问题:四个无刷电机需要不同转速调节,但开发板只剩TIM4可用。这时候输出比较Toggle模式就成了救命稻草——它允许单定时器的四个通道输出完全独立的PWM波形,频率和占空比都能单独设置。实测下来,用TIM4驱动四个电机,CPU占用率仅5%左右,比用四个定时器节省了75%的硬件资源。
2. 输出比较Toggle模式工作原理
2.1 与标准PWM模式的本质区别
标准PWM模式像自动售货机:设置好周期和占空比后,硬件自动输出波形,无需CPU干预。而Toggle模式更像手动挡汽车——每次电平翻转都需要程序介入。具体来说:
- PWM模式:ARR寄存器决定频率,CCRx决定占空比,硬件自动比较CNT和CCRx值
- Toggle模式:CCRx只记录下次翻转点,每次匹配后需要手动计算新翻转点
这就好比PWM模式是定好闹钟后睡觉,Toggle模式则需要每次闹铃响后重新设置下次响铃时间。虽然麻烦,但换来的是每个通道的完全独立性。
2.2 关键寄存器操作流程
以TIM4_CH1为例,完整的工作流程如下:
- CNT从0开始递增计数
- 当CNT == CCR1时:
- GPIO电平翻转
- 触发CC1中断
- 在中断中执行:
CCR1 += period/2(50%占空比)
- CNT溢出后从0重新计数
- 当CNT再次等于新的CCR1值时重复上述过程
特别注意:两次中断才形成一个完整PWM周期。第一次中断是上升沿,第二次是下降沿。这在控制步进电机时要特别注意,我曾在项目中将中断次数直接当作脉冲数,导致电机转速比预期快一倍。
3. 具体实现步骤详解
3.1 硬件配置要点
使用PB6-PB9作为TIM4的四个输出通道时,有几个易错点:
// GPIO配置关键点 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 必须推挽输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NONE; // 禁用上下拉 GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4); // 每个引脚都要单独映射定时器基础配置常见问题:
- 预分频值=84-1(84MHz主频下得到1MHz计数频率)
- 周期值设为65535实现最大分辨率
- 必须禁用预装载寄存器:
TIM_OCxPreloadConfig(TIM4, DISABLE)
3.2 中断服务程序优化技巧
原始代码中每个通道有独立的中断变量,实际上可以用结构体数组优化:
typedef struct { uint8_t edge_count; uint32_t pulse_count; uint32_t ccr_step; } PWM_Channel; PWM_Channel ch[4] = { {0, 0, TIM4_CNT_FREQ/400/2}, // CH1:400Hz {0, 0, TIM4_CNT_FREQ/300/2}, // CH2:300Hz {0, 0, TIM4_CNT_FREQ/200/2}, // CH3:200Hz {0, 0, TIM4_CNT_FREQ/100/2} // CH4:100Hz }; void TIM4_IRQHandler(void) { for(int i=0; i<4; i++) { if(TIM_GetITStatus(TIM4, TIM_IT_CC1<<i)) { TIM_ClearITPendingBit(TIM4, TIM_IT_CC1<<i); ch[i].edge_count++; TIM4->CCR[i] += ch[i].ccr_step; if(ch[i].edge_count & 0x01) { ch[i].pulse_count++; // 添加电机控制逻辑 } } } }这种结构便于扩展,新增通道只需增加数组元素即可。我在机械臂项目中用这种方法实现了6个关节的平滑控制。
4. 多电机协同控制实战应用
4.1 四轴飞行器混控案例
飞行器需要根据姿态数据动态调整四个电机的转速。使用Toggle模式时,可以这样实现混控:
void Motor_Mixing(float roll, float pitch, float yaw) { // 基础推力 float base = 300; // 300Hz基础频率 // 各通道频率计算 ch[0].ccr_step = TIM4_CNT_FREQ/(base + pitch - roll + yaw)/2; ch[1].ccr_step = TIM4_CNT_FREQ/(base + pitch + roll - yaw)/2; ch[2].ccr_step = TIM4_CNT_FREQ/(base - pitch + roll + yaw)/2; ch[3].ccr_step = TIM4_CNT_FREQ/(base - pitch - roll - yaw)/2; // 限制频率范围 for(int i=0; i<4; i++) { ch[i].ccr_step = constrain(ch[i].ccr_step, TIM4_CNT_FREQ/800/2, // 最小100Hz TIM4_CNT_FREQ/200/2); // 最大500Hz } }实测发现,中断处理时间必须控制在5us以内才能保证400Hz PWM的稳定性。建议:
- 将浮点运算转换为定点运算
- 使用查表法替代实时计算
- 关闭其他不必要的中断
4.2 机械臂关节同步技巧
控制机械臂三个关节联动时,需要保证运动同步性。我的经验是:
- 在中断中记录全局时间戳
uint32_t sync_time = DWT->CYCCNT; // 使用DWT时钟计数器- 所有通道共用这个时间基准
- 通过运动学逆解提前计算各关节的目标脉冲数
- 在中断中比较当前脉冲数与目标值
这种方法比单独控制每个电机更能保证关节运动的协调性,特别是在圆弧插补运动中效果显著。