## 1. PWM 原理:为什么能控制这么多设备? PWM 信号本质上是固定频率的方波,通过改变高电平在一个周期内的占比(即**占空比**)来传递信息。对于不同负载,PWM 的作用机制略有不同: - **舵机**:舵机内部电路将 PWM 的脉宽(高电平时间)映射为转角。典型周期为 20ms,脉宽 0.5ms~2.5ms 对应 0°~180°。 - **直流电机**:PWM 的平均电压决定了电机两端有效电压,从而控制转速。配合 H 桥电路,还可实现正反转。 - **LED**:人眼感知亮度与平均功率近似线性,高频 PWM 调光无闪烁,且比线性调压效率更高。 STM32 定时器可以自动产生 PWM 波形,无需 CPU 干预,非常适合实时控制。 ## 2. STM32 定时器 PWM 工作机制 STM32 的通用定时器(TIM2~TIM5)和高级定时器(TIM1、TIM8)均具备 PWM 生成功能。其核心原理如下: - **计数寄存器 CNT**:从 0 计数到自动重载值 ARR,然后清零重新计数。 - **比较寄存器 CCRx**:当 CNT < CCRx 时,输出高电平;当 CNT >= CCRx 时,输出低电平(PWM 模式 1)。 - **占空比 = CCRx / (ARR+1)**,频率 = 定时器时钟 / ((ARR+1) * (PSC+1))。 通过配置 ARR 和 PSC 可设定频率,通过修改 CCRx 即可调节占空比。 ## 3. 硬件连接与配置步骤 ### 3.1 硬件准备 - STM32F103 开发板(或其他系列) - 舵机(SG90)连接 PA8(TIM1_CH1) - 直流电机驱动模块(L298N)连接 PA6(TIM3_CH1) - LED 串联电阻连接 PA7(TIM3_CH2) ### 3.2 配置步骤(以 HAL 库为例) 1. 启用定时器时钟,并配置 GPIO 为复用推挽输出。 2. 设置定时器时基:PSC 和 ARR 决定频率。 3. 配置 PWM 通道:模式、极性、初始占空比。 4. 使能 PWM 输出,并启动定时器。 5. 修改 CCRx 寄存器或使用 `__HAL_TIM_SET_COMPARE` 来更新占空比。 ## 4. 完整代码示例(HAL 库) 以下代码同时演示舵机、电机和 LED 的控制。使用 TIM1_CH1(PA8)驱动舵机,TIM3_CH1(PA6)驱动电机,TIM3_CH2(PA7)驱动 LED。 ```c /* 定时器句柄 */ TIM_HandleTypeDef htim1, htim3; void MX_TIM1_Init(void) { TIM_OC_InitTypeDef sConfigOC = {0}; __HAL_RCC_TIM1_CLK_ENABLE(); htim1.Instance = TIM1; htim1.Init.Prescaler = 7199; // 72MHz/7200 = 10kHz htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 199; // 10kHz/200 = 50Hz(20ms周期) htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&htim1); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 15; // 初始占空比 15/200=7.5%(对应90°) sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); } void MX_TIM3_Init(void) { TIM_OC_InitTypeDef sConfigOC = {0}; __HAL_RCC_TIM3_CLK_ENABLE(); htim3.Instance = TIM3; htim3.Init.Prescaler = 71; // 72MHz/72 = 1MHz htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 999; // 1MHz/1000 = 1kHz htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&htim3); /* 通道1:电机 */ sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 0; // 初始停止 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); /* 通道2:LED */ sConfigOC.Pulse = 500; // 初始50%亮度 HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2); } /* GPIO 复用配置 */ void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /* PA8: TIM1_CH1, PA6: TIM3_CH1, PA7: TIM3_CH2 */ GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } /* 应用:设置舵机角度(0~180) */ void set_servo_angle(uint8_t angle) { uint32_t pulse = 10 + (angle * 10) / 180; // 脉宽:0.5ms~2.5ms,按10kHz计数,即5~25 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pulse); } /* 应用:设置电机转速(-100~100,负值需配合H桥) */ void set_motor_speed(int16_t speed) { if (speed >= 0) { __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, speed * 10); // 0~1000 } // 如需反转,通过GPIO控制H桥方向引脚 } /* 应用:设置LED亮度(0~255) */ void set_led_brightness(uint8_t brightness) { __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, brightness * 4); // 0~1020 } int main(void) { HAL_Init(); MX_GPIO_Init(); MX_TIM1_Init(); MX_TIM3_Init(); set_servo_angle(90); set_motor_speed(50); set_led_brightness(128); while (1) { // 动态调节示例:呼吸灯 for (int i = 0; i <= 255; i++) { set_led_brightness(i); HAL_Delay(5); } for (int i = 255; i >= 0; i--) { set_led_brightness(i); HAL_Delay(5); } } } ``` ## 5. 关键细节与注意事项 - **频率选择**:舵机必须 50Hz(周期20ms),否则会抖动或烧毁。电机一般 1kHz~20kHz(高频率可降低噪音),LED 调光建议 >1kHz 以避免闪烁。 - **占空比计算**:先确定 ARR,再根据需要的脉宽或百分比计算 CCR。例如舵机:ARR=199,PSC=7199,计数时钟10kHz,则 0.5ms = 5,2.5ms = 25。 - **高级定时器注意**:TIM1/TIM8 默认输出极性可能相反,且需要使能主输出 `__HAL_TIM_MOE_ENABLE(&htim1)`。如果使用 HAL_PWM_Start 则已包含。 - **死区与互补输出**:若控制 H 桥或电机驱动,需注意上下桥臂的互补 PWM 和死区插入,防止直通短路。 - **动态调参**:修改 ARR 会改变频率,建议只修改 CCR 调整占空比。若需可变频率,使用 DMA 或更新事件中断。 - **标准库移植**:将 HAL 的 `__HAL_TIM_SET_COMPARE` 替换为 `TIM_SetCompare1(TIMx, value)` 即可,初始化部分需手动配置寄存器或参考标准库例程。 ## 6. 总结 STM32 的 PWM 功能是连接数字世界与模拟世界的桥梁。理解定时器时基与比较寄存器之间的关系,是配置任何 PWM 应用的基础。通过本文的示例,你可以轻松拓展到多通道舵机控制板、无刷电机 ESC、RGB LED 调光等场景。建议动手在开发板上验证,并将逻辑分析和示波器测量作为调试利器,才能深刻掌握 PWM 的精髓。