1. STM32 PWM动态输出原理与配置
PWM(脉冲宽度调制)是嵌入式系统中控制外设的常用技术,通过调节脉冲的占空比实现对设备(如电机、LED等)的精确控制。STM32系列微控制器内置了强大的定时器模块,可以方便地生成PWM信号。
1.1 PWM基础概念
PWM波有三个关键参数:
- 频率:每秒周期数,由定时器时钟和ARR(自动重装载寄存器)决定
- 占空比:高电平时间占整个周期的百分比
- 分辨率:占空比可调节的最小步进值
在STM32中,PWM生成依赖于定时器的四个关键寄存器:
- ARR(Auto-reload register):决定PWM周期
- CCRx(Capture/Compare register):决定通道x的占空比
- PSC(Prescaler):时钟预分频系数
- CNT(Counter):当前计数值
提示:占空比 = (CCRx / ARR) × 100%,因此调节CCRx即可改变占空比
1.2 CubeMX配置详解
使用STM32CubeMX配置PWM输出的关键步骤:
-
时钟配置:
- 确保定时器时钟源已启用
- 根据需求设置APB总线时钟分频
-
定时器参数设置:
c复制htim2.Instance = TIM2; htim2.Init.Prescaler = 71; // 72MHz/(71+1)=1MHz htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 999; // ARR值,决定PWM周期 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; -
PWM通道配置:
c复制sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 500; // 初始占空比50% sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; -
GPIO设置:
- 选择正确的复用功能(AF)模式
- 配置输出类型为推挽输出
- 根据负载设置合适的输出速度
2. 动态PWM实现方法
2.1 基础动态调节实现
原始代码展示了最基本的动态PWM调节方法:
c复制HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
uint16_t duty = 0;
uint8_t dir = 1;
while(1) {
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
if(dir) {
duty += 10;
if(duty >= 999) dir = 0;
} else {
duty -= 10;
if(duty <= 0) dir = 1;
}
HAL_Delay(20);
}
这段代码实现了:
- 启动TIM2的PWM输出
- 在循环中动态修改CCR值
- 通过dir变量控制占空比增减方向
- 使用延时控制变化速度
2.2 高级动态调节技巧
2.2.1 非线性变化算法
实现更自然的呼吸灯效果可以使用指数曲线:
c复制// 指数变化算法
float gamma = 2.2; // 伽马校正值
uint16_t pwm_table[100]; // 预计算PWM值
// 初始化PWM表
for(int i=0; i<100; i++) {
float x = i / 99.0;
pwm_table[i] = (uint16_t)(pow(x, gamma) * 999);
}
// 应用非线性变化
uint8_t index = 0;
uint8_t dir = 1;
while(1) {
duty = pwm_table[index];
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
if(dir) {
if(++index >= 99) dir = 0;
} else {
if(--index == 0) dir = 1;
}
HAL_Delay(10);
}
2.2.2 使用定时器中断实现精确控制
避免使用Delay函数,改用定时器中断:
c复制// 在定时器中断回调中修改PWM
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim == &htim3) { // 使用TIM3作为时基
static uint16_t duty = 0;
static uint8_t dir = 1;
if(dir) {
duty += 5;
if(duty >= 999) dir = 0;
} else {
duty -= 5;
if(duty <= 0) dir = 1;
}
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
}
}
3. 实际应用案例分析
3.1 LED呼吸灯实现
完整呼吸灯实现代码:
c复制// 初始化
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
// 主循环
while(1) {
// 淡入
for(uint16_t i=0; i<1000; i+=10) {
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, i);
HAL_Delay(15);
}
// 淡出
for(uint16_t i=1000; i>0; i-=10) {
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, i);
HAL_Delay(15);
}
// 闪烁效果
for(uint8_t i=0; i<3; i++) {
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 1000);
HAL_Delay(100);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0);
HAL_Delay(100);
}
}
3.2 电机速度控制
直流电机控制需要考虑:
- 最小启动占空比
- 加速/减速曲线
- 死区保护
c复制#define MIN_DUTY 200 // 电机能启动的最小占空比
void Motor_Control(uint16_t target_duty) {
static uint16_t current_duty = 0;
// 限制范围
if(target_duty > 999) target_duty = 999;
if(target_duty < MIN_DUTY && target_duty != 0)
target_duty = MIN_DUTY;
// 平滑过渡
int16_t step = (target_duty > current_duty) ? 5 : -5;
while(current_duty != target_duty) {
current_duty += step;
// 防止过冲
if((step > 0 && current_duty > target_duty) ||
(step < 0 && current_duty < target_duty)) {
current_duty = target_duty;
}
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, current_duty);
HAL_Delay(20); // 控制加速度
}
}
4. 常见问题与解决方案
4.1 PWM输出不稳定问题排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无输出 | GPIO配置错误 | 检查复用功能配置 |
| 频率不对 | 定时器配置错误 | 重新计算PSC和ARR |
| 占空比不准 | CCR值超出ARR | 确保CCR ≤ ARR |
| 波形抖动 | 中断干扰 | 优化中断优先级 |
4.2 性能优化技巧
-
使用DMA传输PWM数据
c复制// 配置DMA从内存到TIMx_CCRx hdma_tim2_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_tim2_ch1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_tim2_ch1.Init.MemInc = DMA_MINC_ENABLE; -
利用定时器主从模式同步多个PWM
-
使用预装载寄存器避免毛刺
c复制TIM_OC_InitTypeDef sConfigOC; sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; sConfigOC.Pulse = 500; HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
4.3 高级应用:PWM音频生成
通过PWM生成简单音频信号:
c复制// 音符频率表
const uint16_t notes[] = {
0, 262, 294, 330, 349, 392, 440, 494, 523
};
void Play_Tone(uint8_t note, uint16_t duration) {
if(note >= sizeof(notes)/sizeof(notes[0])) return;
// 计算ARR值 (72MHz/(PSC+1)/freq)-1
uint32_t arr = (72000000 / 71 / notes[note]) - 1;
__HAL_TIM_SET_AUTORELOAD(&htim2, arr);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, arr/2); // 50%占空比
HAL_Delay(duration);
// 停止声音
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0);
}
5. 扩展应用与进阶技巧
5.1 多通道同步控制
使用单个定时器控制多个PWM通道:
c复制// 启动所有通道
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
// 同步更新多个通道
void Set_RGB(uint16_t r, uint16_t g, uint16_t b) {
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, r);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, g);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, b);
}
5.2 使用输入捕获测量PWM
配置另一个定时器测量PWM输入:
c复制// 输入捕获配置
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);
// 启动输入捕获
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
// 在回调函数中处理测量结果
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
uint32_t value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
// 计算频率或占空比
}
}
在实际项目中,我发现PWM动态调节的关键在于平衡响应速度和稳定性。过快的占空比变化会导致设备响应不线性,而过慢的调节又会影响用户体验。经过多次测试,20-50ms的步进间隔对于大多数LED和电机应用是最佳平衡点。
