PWM驱动开发:电机调速控制¶

PWM驱动开发:电机调速控制¶

PWM驱动开发:电机调速控制¶

概述¶

PWM(Pulse Width Modulation,脉宽调制)是嵌入式系统中最常用的控制技术之一。通过改变脉冲信号的占空比,PWM可以实现对电机速度、LED亮度、舵机角度等的精确控制。掌握PWM驱动开发是实现各种控制应用的基础。

本教程将通过实战项目,带你深入理解PWM的工作原理和驱动开发方法,包括PWM配置、占空比调节、频率设置、多通道控制和高级应用等核心技术。

完成本教程后,你将能够:

理解PWM的工作原理和关键参数

掌握定时器PWM模式的配置方法

实现单通道和多通道PWM输出

控制直流电机的转速和方向

实现LED呼吸灯和RGB调色

理解互补PWM和死区时间的应用

设计可靠的PWM驱动程序

背景知识¶

PWM的工作原理¶

PWM通过快速开关信号来模拟模拟电压输出,其核心思想是通过改变高电平持续时间来控制平均功率。

PWM基本概念:

PWM信号波形:

_____ _____ _____

___| |_____| |_____| |_____

|<-T->| |<-T->| |<-T->|

|| || ||

T = 周期(Period)

Th = 高电平时间(High Time)

Tl = 低电平时间(Low Time)

f = 频率 = 1/T

D = 占空比 = Th/T × 100%

占空比与输出功率:

占空比 0%: ___________________ → 输出 0V

占空比 25%: ___ ___ ___ _ → 输出 0.825V (3.3V × 25%)

占空比 50%: _____ _____ ___ → 输出 1.65V (3.3V × 50%)

占空比 75%: _______ _______ → 输出 2.475V (3.3V × 75%)

占空比 100%: ___________________ → 输出 3.3V

PWM的优势:

效率高:开关状态功耗低,能量损失小

控制精确:可以实现精细的功率调节

实现简单:只需要数字信号,不需要DAC

抗干扰强:数字信号比模拟信号更稳定

成本低:不需要额外的模拟电路

STM32定时器PWM功能¶

STM32的定时器可以产生PWM信号,每个定时器通道都可以独立配置为PWM输出。

PWM相关寄存器:

寄存器

功能

说明

ARR

自动重载寄存器

决定PWM周期

CCRx

捕获/比较寄存器

决定占空比

CCMRx

捕获/比较模式寄存器

配置PWM模式

CCER

捕获/比较使能寄存器

使能输出

BDTR

刹车和死区寄存器

高级定时器专用

PWM模式:

STM32定时器支持两种PWM模式:

PWM模式1(Mode 1):

- 向上计数时:CNT < CCR时输出高电平,CNT ≥ CCR时输出低电平

- 向下计数时:CNT > CCR时输出低电平,CNT ≤ CCR时输出高电平

- 最常用的模式

PWM模式2(Mode 2):

- 与模式1相反

- 向上计数时:CNT < CCR时输出低电平,CNT ≥ CCR时输出高电平

PWM模式1波形示意:

向上计数模式:

CNT: 0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 0 ...

ARR = 9, CCR = 3

PWM: ___ _______________________________

|___|

高 低电平(CNT ≥ CCR)

占空比 = 3/10 = 30%

PWM频率和分辨率¶

PWM的频率和分辨率是相互制约的,需要根据应用场景进行权衡。

频率计算公式:

PWM频率 = 定时器时钟频率 / [(PSC + 1) × (ARR + 1)]

占空比分辨率 = ARR + 1

例如:定时器时钟 = 84MHz

PSC = 83, ARR = 999

PWM频率 = 84MHz / (84 × 1000) = 1kHz

分辨率 = 1000 (0.1%精度)

频率与分辨率的权衡:

应用

推荐频率

分辨率要求

ARR设置

LED调光

1-10kHz

8-10位

255-1023

直流电机

10-20kHz

8-10位

255-1023

舵机控制

50Hz

高精度

19999

开关电源

50-500kHz

较低

100-255

音频输出

44.1kHz

8位

255

频率选择原则:

LED调光:

频率 > 100Hz:避免人眼感知闪烁

推荐1-10kHz:平衡效果和EMI

电机控制:

频率 > 20kHz:超出人耳听觉范围

推荐10-20kHz:减少噪音和发热

舵机控制:

频率 = 50Hz:标准舵机频率

脉宽范围:0.5ms-2.5ms

开关电源:

频率越高,滤波电容越小

但开关损耗也越大

PWM占空比计算¶

占空比决定了输出的平均功率,是PWM控制的核心参数。

占空比计算公式:

占空比 = CCR / (ARR + 1) × 100%

反向计算CCR:

CCR = 占空比 × (ARR + 1) / 100

例如:ARR = 999

设置50%占空比:CCR = 0.5 × 1000 = 500

设置75%占空比:CCR = 0.75 × 1000 = 750

占空比与输出电压:

假设电源电压为3.3V:

占空比 0% → 平均电压 0V → 电机停止 / LED熄灭

占空比 25% → 平均电压 0.825V → 电机慢速 / LED暗

占空比 50% → 平均电压 1.65V → 电机中速 / LED中等亮度

占空比 75% → 平均电压 2.475V → 电机快速 / LED亮

占空比 100% → 平均电压 3.3V → 电机全速 / LED最亮

定时器通道与GPIO映射¶

STM32的定时器通道需要映射到特定的GPIO引脚。

TIM3通道映射(常用):

通道

默认引脚

重映射引脚

说明

CH1

PA6

PB4, PC6

通道1

CH2

PA7

PB5, PC7

通道2

CH3

PB0

PC8

通道3

CH4

PB1

PC9

通道4

TIM2通道映射:

通道

默认引脚

重映射引脚

CH1

PA0

PA15, PA5

CH2

PA1

PB3

CH3

PA2

PB10

CH4

PA3

PB11

TIM1通道映射(高级定时器):

通道

默认引脚

互补输出

CH1

PA8

PB13 (CH1N)

CH2

PA9

PB14 (CH2N)

CH3

PA10

PB15 (CH3N)

CH4

PA11

-

环境准备¶

硬件要求¶

STM32F4系列开发板(如STM32F407VET6)

直流电机(3-6V)+ L298N电机驱动模块

LED灯(若干)+ 限流电阻(220Ω)

舵机(SG90或MG996R)

示波器或逻辑分析仪(用于观察PWM波形)

面包板和杜邦线

软件要求¶

Keil MDK 5.x 或 STM32CubeIDE

STM32F4 HAL库或标准外设库

串口调试工具

硬件连接¶

基本PWM输出(LED控制):

- PA6(TIM3_CH1) -> LED1正极 -> 220Ω电阻 -> GND

- PA7(TIM3_CH2) -> LED2正极 -> 220Ω电阻 -> GND

- PB0(TIM3_CH3) -> LED3正极 -> 220Ω电阻 -> GND

- PB1(TIM3_CH4) -> LED4正极 -> 220Ω电阻 -> GND

电机控制(L298N驱动):

- PA6(TIM3_CH1) -> L298N IN1(速度控制)

- PA7(TIM3_CH2) -> L298N IN2(速度控制)

- PC0(GPIO) -> L298N IN3(方向控制)

- PC1(GPIO) -> L298N IN4(方向控制)

- L298N OUT1/OUT2 -> 电机M1

- L298N OUT3/OUT4 -> 电机M2

- L298N +12V -> 电源正极(根据电机电压)

- L298N GND -> 电源负极和STM32 GND

舵机控制:

- PA6(TIM3_CH1) -> 舵机信号线(橙色/黄色)

- 5V -> 舵机电源线(红色)

- GND -> 舵机地线(棕色/黑色)

注意:

1. LED需要串联限流电阻

2. 电机驱动模块需要独立供电

3. 舵机电源和STM32共地

4. 大功率负载需要使用外部驱动电路

核心内容¶

步骤1:GPIO和定时器时钟配置¶

首先需要配置GPIO为复用功能,并使能定时器时钟。

#include "stm32f4xx.h"

/**

* @brief 配置TIM3的GPIO引脚

* @param 无

* @retval 无

*/

void TIM3_GPIO_Init(void) {

// 1. 使能GPIOA和GPIOB时钟

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN;

// 2. 配置PA6和PA7为复用功能

// MODER: 10 = 复用功能

GPIOA->MODER &= ~((0x03 << (6 * 2)) | (0x03 << (7 * 2)));

GPIOA->MODER |= (0x02 << (6 * 2)) | (0x02 << (7 * 2));

// 3. 配置PB0和PB1为复用功能

GPIOB->MODER &= ~((0x03 << (0 * 2)) | (0x03 << (1 * 2)));

GPIOB->MODER |= (0x02 << (0 * 2)) | (0x02 << (1 * 2));

// 4. 配置输出类型为推挽输出

GPIOA->OTYPER &= ~((1 << 6) | (1 << 7));

GPIOB->OTYPER &= ~((1 << 0) | (1 << 1));

// 5. 配置输出速度为高速

GPIOA->OSPEEDR |= (0x03 << (6 * 2)) | (0x03 << (7 * 2));

GPIOB->OSPEEDR |= (0x03 << (0 * 2)) | (0x03 << (1 * 2));

// 6. 配置为无上拉下拉

GPIOA->PUPDR &= ~((0x03 << (6 * 2)) | (0x03 << (7 * 2)));

GPIOB->PUPDR &= ~((0x03 << (0 * 2)) | (0x03 << (1 * 2)));

// 7. 配置复用功能为TIM3(AF2)

// AFR[0]控制引脚0-7,AFR[1]控制引脚8-15

GPIOA->AFR[0] &= ~((0x0F << (6 * 4)) | (0x0F << (7 * 4)));

GPIOA->AFR[0] |= (0x02 << (6 * 4)) | (0x02 << (7 * 4)); // AF2 = TIM3

GPIOB->AFR[0] &= ~((0x0F << (0 * 4)) | (0x0F << (1 * 4)));

GPIOB->AFR[0] |= (0x02 << (0 * 4)) | (0x02 << (1 * 4)); // AF2 = TIM3

}

代码说明:

复用功能配置:

MODER = 10:配置为复用功能模式

AFR:选择具体的复用功能(TIM3 = AF2)

输出特性配置:

OTYPER = 0:推挽输出(驱动能力强)

OSPEEDR = 11:高速输出(减少边沿抖动)

复用功能编号:

不同引脚的复用功能编号可能不同

需要查阅数据手册确认

步骤2:定时器PWM基本配置¶

配置定时器的基本参数和PWM模式。

/**

* @brief 配置TIM3为PWM模式

* @param freq: PWM频率(Hz)

* @param resolution: 占空比分辨率(ARR+1)

* @retval 无

*/

void TIM3_PWM_Init(uint32_t freq, uint16_t resolution) {

// 1. 使能TIM3时钟(APB1总线)

RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;

// 2. 配置GPIO

TIM3_GPIO_Init();

// 3. 计算预分频器值

// TIM3时钟 = APB1时钟 × 2 = 84MHz(当APB1分频系数≠1时)

// PWM频率 = TIM3时钟 / [(PSC+1) × (ARR+1)]

uint32_t tim_clk = 84000000; // 84MHz

uint32_t psc = tim_clk / (freq * resolution) - 1;

// 4. 配置定时器基本参数

TIM3->PSC = psc; // 预分频器

TIM3->ARR = resolution - 1; // 自动重载值

TIM3->CR1 = 0; // 清除控制寄存器

TIM3->CR1 |= TIM_CR1_ARPE; // 使能自动重载预装载

// 5. 配置计数模式:向上计数

TIM3->CR1 &= ~TIM_CR1_DIR;

TIM3->CR1 &= ~TIM_CR1_CMS;

// 6. 配置通道1为PWM模式1

TIM3->CCMR1 &= ~TIM_CCMR1_OC1M;

TIM3->CCMR1 |= (0x06 << 4); // 110 = PWM模式1

TIM3->CCMR1 |= TIM_CCMR1_OC1PE; // 使能预装载

// 7. 配置通道2为PWM模式1

TIM3->CCMR1 &= ~TIM_CCMR1_OC2M;

TIM3->CCMR1 |= (0x06 << 12); // 110 = PWM模式1

TIM3->CCMR1 |= TIM_CCMR1_OC2PE;

// 8. 配置通道3为PWM模式1

TIM3->CCMR2 &= ~TIM_CCMR2_OC3M;

TIM3->CCMR2 |= (0x06 << 4); // 110 = PWM模式1

TIM3->CCMR2 |= TIM_CCMR2_OC3PE;

// 9. 配置通道4为PWM模式1

TIM3->CCMR2 &= ~TIM_CCMR2_OC4M;

TIM3->CCMR2 |= (0x06 << 12); // 110 = PWM模式1

TIM3->CCMR2 |= TIM_CCMR2_OC4PE;

// 10. 使能通道输出

TIM3->CCER |= TIM_CCER_CC1E; // 使能通道1输出

TIM3->CCER |= TIM_CCER_CC2E; // 使能通道2输出

TIM3->CCER |= TIM_CCER_CC3E; // 使能通道3输出

TIM3->CCER |= TIM_CCER_CC4E; // 使能通道4输出

// 11. 初始化CCR为0(占空比0%)

TIM3->CCR1 = 0;

TIM3->CCR2 = 0;

TIM3->CCR3 = 0;

TIM3->CCR4 = 0;

// 12. 生成更新事件,加载预装载值

TIM3->EGR |= TIM_EGR_UG;

// 13. 启动定时器

TIM3->CR1 |= TIM_CR1_CEN;

}

配置要点:

PWM模式选择:

110 = PWM模式1(最常用)

111 = PWM模式2

预装载使能:

ARPE:ARR预装载

OCxPE:CCR预装载

防止更新时产生毛刺

通道使能:

CCxE:使能通道输出

CCxP:配置输出极性(0=高电平有效)

步骤3:PWM占空比控制函数¶

实现设置PWM占空比的函数。

/**

* @brief 设置PWM占空比

* @param channel: 通道号(1-4)

* @param duty: 占空比(0-100)

* @retval 无

*/

void TIM3_SetPWM(uint8_t channel, uint8_t duty) {

// 参数检查

if (channel < 1 || channel > 4) return;

if (duty > 100) duty = 100;

// 计算CCR值

uint16_t ccr = (TIM3->ARR + 1) * duty / 100;

// 设置对应通道的CCR

switch (channel) {

case 1: TIM3->CCR1 = ccr; break;

case 2: TIM3->CCR2 = ccr; break;

case 3: TIM3->CCR3 = ccr; break;

case 4: TIM3->CCR4 = ccr; break;

}

}

/**

* @brief 设置PWM占空比(高精度)

* @param channel: 通道号(1-4)

* @param ccr: CCR值(0-ARR)

* @retval 无

*/

void TIM3_SetPWM_Raw(uint8_t channel, uint16_t ccr) {

// 参数检查

if (channel < 1 || channel > 4) return;

if (ccr > TIM3->ARR) ccr = TIM3->ARR;

// 设置CCR

switch (channel) {

case 1: TIM3->CCR1 = ccr; break;

case 2: TIM3->CCR2 = ccr; break;

case 3: TIM3->CCR3 = ccr; break;

case 4: TIM3->CCR4 = ccr; break;

}

}

/**

* @brief 获取当前占空比

* @param channel: 通道号(1-4)

* @retval 占空比(0-100)

*/

uint8_t TIM3_GetPWM(uint8_t channel) {

uint16_t ccr = 0;

// 读取CCR值

switch (channel) {

case 1: ccr = TIM3->CCR1; break;

case 2: ccr = TIM3->CCR2; break;

case 3: ccr = TIM3->CCR3; break;

case 4: ccr = TIM3->CCR4; break;

default: return 0;

}

// 计算占空比

return (uint8_t)(ccr * 100 / (TIM3->ARR + 1));

}

步骤4:LED呼吸灯效果¶

使用PWM实现LED呼吸灯效果。

/**

* @brief LED呼吸灯效果

* @param channel: PWM通道

* @param speed: 呼吸速度(1-10,越大越快)

* @retval 无

*/

void LED_Breathing(uint8_t channel, uint8_t speed) {

static uint8_t duty = 0;

static int8_t direction = 1; // 1=增加,-1=减少

// 更新占空比

duty += direction * speed;

// 反转方向

if (duty >= 100) {

duty = 100;

direction = -1;

} else if (duty <= 0) {

duty = 0;

direction = 1;

}

// 设置PWM

TIM3_SetPWM(channel, duty);

}

/**

* @brief 主函数 - 呼吸灯示例

*/

int main(void) {

SystemInit();

// 初始化PWM:1kHz频率,1000级分辨率

TIM3_PWM_Init(1000, 1000);

while (1) {

// 通道1呼吸灯

LED_Breathing(1, 1);

// 延时10ms

for (volatile int i = 0; i < 21000; i++);

}

}

步骤5:直流电机速度控制¶

使用PWM控制直流电机的转速和方向。

/**

* @brief 电机方向控制GPIO初始化

* @param 无

* @retval 无

*/

void Motor_GPIO_Init(void) {

// 使能GPIOC时钟

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;

// 配置PC0和PC1为输出模式

GPIOC->MODER &= ~((0x03 << 0) | (0x03 << 2));

GPIOC->MODER |= (0x01 << 0) | (0x01 << 2);

// 推挽输出

GPIOC->OTYPER &= ~((1 << 0) | (1 << 1));

// 初始化为低电平

GPIOC->ODR &= ~((1 << 0) | (1 << 1));

}

/**

* @brief 设置电机速度和方向

* @param motor: 电机编号(1或2)

* @param speed: 速度(-100到100,负数表示反转)

* @retval 无

*/

void Motor_SetSpeed(uint8_t motor, int8_t speed) {

uint8_t pwm_channel;

uint8_t dir_pin1, dir_pin2;

// 选择电机

if (motor == 1) {

pwm_channel = 1; // TIM3_CH1

dir_pin1 = 0; // PC0

dir_pin2 = 1; // PC1

} else if (motor == 2) {

pwm_channel = 2; // TIM3_CH2

dir_pin1 = 2; // PC2

dir_pin2 = 3; // PC3

} else {

return;

}

// 设置方向和速度

if (speed > 0) {

// 正转

GPIOC->BSRR = (1 << dir_pin1); // 设置为高

GPIOC->BSRR = (1 << (dir_pin2 + 16)); // 设置为低

TIM3_SetPWM(pwm_channel, speed);

} else if (speed < 0) {

// 反转

GPIOC->BSRR = (1 << (dir_pin1 + 16)); // 设置为低

GPIOC->BSRR = (1 << dir_pin2); // 设置为高

TIM3_SetPWM(pwm_channel, -speed);

} else {

// 停止

GPIOC->BSRR = (1 << (dir_pin1 + 16)); // 设置为低

GPIOC->BSRR = (1 << (dir_pin2 + 16)); // 设置为低

TIM3_SetPWM(pwm_channel, 0);

}

}

/**

* @brief 电机刹车

* @param motor: 电机编号

* @retval 无

*/

void Motor_Brake(uint8_t motor) {

uint8_t dir_pin1, dir_pin2;

if (motor == 1) {

dir_pin1 = 0;

dir_pin2 = 1;

} else {

dir_pin1 = 2;

dir_pin2 = 3;

}

// 两个方向引脚都设为高,实现刹车

GPIOC->BSRR = (1 << dir_pin1) | (1 << dir_pin2);

}

使用示例:

int main(void) {

SystemInit();

// 初始化PWM和电机控制

TIM3_PWM_Init(20000, 1000); // 20kHz PWM

Motor_GPIO_Init();

while (1) {

// 电机1正转,速度50%

Motor_SetSpeed(1, 50);

Delay_ms(2000);

// 电机1反转,速度75%

Motor_SetSpeed(1, -75);

Delay_ms(2000);

// 电机1刹车

Motor_Brake(1);

Delay_ms(1000);

// 电机1停止

Motor_SetSpeed(1, 0);

Delay_ms(1000);

}

}

步骤6:舵机角度控制¶

舵机需要50Hz的PWM信号,脉宽0.5ms-2.5ms对应0-180度。

/**

* @brief 初始化舵机PWM(50Hz)

* @param 无

* @retval 无

*/

void Servo_Init(void) {

// 50Hz PWM,周期20ms

// TIM3时钟84MHz,PSC=83,ARR=19999

// PWM频率 = 84MHz / (84 × 20000) = 50Hz

TIM3_PWM_Init(50, 20000);

}

/**

* @brief 设置舵机角度

* @param channel: PWM通道(1-4)

* @param angle: 角度(0-180度)

* @retval 无

*/

void Servo_SetAngle(uint8_t channel, uint8_t angle) {

// 限制角度范围

if (angle > 180) angle = 180;

// 计算脉宽

// 0度 = 0.5ms = 1000 CCR

// 90度 = 1.5ms = 3000 CCR

// 180度 = 2.5ms = 5000 CCR

// CCR = 1000 + angle × (5000 - 1000) / 180

uint16_t ccr = 1000 + (uint32_t)angle * 4000 / 180;

// 设置PWM

TIM3_SetPWM_Raw(channel, ccr);

}

/**

* @brief 舵机扫描测试

* @param channel: PWM通道

* @retval 无

*/

void Servo_Sweep(uint8_t channel) {

uint8_t angle;

// 0度到180度

for (angle = 0; angle <= 180; angle += 5) {

Servo_SetAngle(channel, angle);

Delay_ms(50);

}

// 180度到0度

for (angle = 180; angle > 0; angle -= 5) {

Servo_SetAngle(channel, angle);

Delay_ms(50);

}

}

实践示例¶

示例1:RGB LED调色¶

使用3个PWM通道控制RGB LED实现全彩调色。

/**

* @brief 设置RGB颜色

* @param red: 红色亮度(0-100)

* @param green: 绿色亮度(0-100)

* @param blue: 蓝色亮度(0-100)

* @retval 无

*/

void RGB_SetColor(uint8_t red, uint8_t green, uint8_t blue) {

TIM3_SetPWM(1, red); // 红色 -> CH1

TIM3_SetPWM(2, green); // 绿色 -> CH2

TIM3_SetPWM(3, blue); // 蓝色 -> CH3

}

/**

* @brief RGB颜色渐变

* @param 无

* @retval 无

*/

void RGB_ColorFade(void) {

uint8_t i;

// 红色渐变到绿色

for (i = 0; i <= 100; i++) {

RGB_SetColor(100 - i, i, 0);

Delay_ms(20);

}

// 绿色渐变到蓝色

for (i = 0; i <= 100; i++) {

RGB_SetColor(0, 100 - i, i);

Delay_ms(20);

}

// 蓝色渐变到红色

for (i = 0; i <= 100; i++) {

RGB_SetColor(i, 0, 100 - i);

Delay_ms(20);

}

}

/**

* @brief 主函数 - RGB调色示例

*/

int main(void) {

SystemInit();

TIM3_PWM_Init(1000, 1000);

while (1) {

// 彩虹渐变效果

RGB_ColorFade();

// 预设颜色显示

RGB_SetColor(100, 0, 0); // 红色

Delay_ms(1000);

RGB_SetColor(0, 100, 0); // 绿色

Delay_ms(1000);

RGB_SetColor(0, 0, 100); // 蓝色

Delay_ms(1000);

RGB_SetColor(100, 100, 0); // 黄色

Delay_ms(1000);

RGB_SetColor(100, 0, 100); // 紫色

Delay_ms(1000);

RGB_SetColor(0, 100, 100); // 青色

Delay_ms(1000);

RGB_SetColor(100, 100, 100);// 白色

Delay_ms(1000);

}

}

示例2:小车运动控制¶

使用PWM控制双电机小车的运动。

/**

* @brief 小车前进

* @param speed: 速度(0-100)

* @retval 无

*/

void Car_Forward(uint8_t speed) {

Motor_SetSpeed(1, speed); // 左电机正转

Motor_SetSpeed(2, speed); // 右电机正转

}

/**

* @brief 小车后退

* @param speed: 速度(0-100)

* @retval 无

*/

void Car_Backward(uint8_t speed) {

Motor_SetSpeed(1, -speed); // 左电机反转

Motor_SetSpeed(2, -speed); // 右电机反转

}

/**

* @brief 小车左转

* @param speed: 速度(0-100)

* @retval 无

*/

void Car_TurnLeft(uint8_t speed) {

Motor_SetSpeed(1, -speed); // 左电机反转

Motor_SetSpeed(2, speed); // 右电机正转

}

/**

* @brief 小车右转

* @param speed: 速度(0-100)

* @retval 无

*/

void Car_TurnRight(uint8_t speed) {

Motor_SetSpeed(1, speed); // 左电机正转

Motor_SetSpeed(2, -speed); // 右电机反转

}

/**

* @brief 小车停止

* @param 无

* @retval 无

*/

void Car_Stop(void) {

Motor_SetSpeed(1, 0);

Motor_SetSpeed(2, 0);

}

/**

* @brief 主函数 - 小车运动示例

*/

int main(void) {

SystemInit();

TIM3_PWM_Init(20000, 1000);

Motor_GPIO_Init();

while (1) {

// 前进2秒

Car_Forward(60);

Delay_ms(2000);

// 停止1秒

Car_Stop();

Delay_ms(1000);

// 后退2秒

Car_Backward(60);

Delay_ms(2000);

// 停止1秒

Car_Stop();

Delay_ms(1000);

// 左转1秒

Car_TurnLeft(50);

Delay_ms(1000);

// 右转1秒

Car_TurnRight(50);

Delay_ms(1000);

// 停止

Car_Stop();

Delay_ms(2000);

}

}

示例3:PWM频率测量¶

使用示波器或逻辑分析仪测量PWM频率和占空比。

/**

* @brief PWM测试信号生成

* @param 无

* @retval 无

*/

void PWM_TestSignal(void) {

// 生成不同频率和占空比的测试信号

// 测试1:1kHz, 50%占空比

TIM3_PWM_Init(1000, 1000);

TIM3_SetPWM(1, 50);

Delay_ms(5000);

// 测试2:10kHz, 25%占空比

TIM3_PWM_Init(10000, 1000);

TIM3_SetPWM(1, 25);

Delay_ms(5000);

// 测试3:20kHz, 75%占空比

TIM3_PWM_Init(20000, 1000);

TIM3_SetPWM(1, 75);

Delay_ms(5000);

}

示例4:PWM软启动¶

实现电机的软启动,避免启动电流过大。

/**

* @brief 电机软启动

* @param motor: 电机编号

* @param target_speed: 目标速度(0-100)

* @param ramp_time: 加速时间(ms)

* @retval 无

*/

void Motor_SoftStart(uint8_t motor, uint8_t target_speed, uint16_t ramp_time) {

uint8_t current_speed = 0;

uint16_t step_delay = ramp_time / target_speed;

// 逐步增加速度

while (current_speed < target_speed) {

current_speed++;

Motor_SetSpeed(motor, current_speed);

Delay_ms(step_delay);

}

}

/**

* @brief 电机软停止

* @param motor: 电机编号

* @param ramp_time: 减速时间(ms)

* @retval 无

*/

void Motor_SoftStop(uint8_t motor, uint16_t ramp_time) {

int8_t current_speed = Motor_GetSpeed(motor);

uint16_t step_delay = ramp_time / abs(current_speed);

// 逐步减少速度

while (current_speed != 0) {

if (current_speed > 0) {

current_speed--;

} else {

current_speed++;

}

Motor_SetSpeed(motor, current_speed);

Delay_ms(step_delay);

}

}

深入理解¶

互补PWM输出¶

高级定时器(TIM1/TIM8)支持互补PWM输出,用于H桥电机驱动。

/**

* @brief 配置TIM1互补PWM输出

* @param 无

* @retval 无

*/

void TIM1_ComplementaryPWM_Init(void) {

// 1. GPIO配置

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN;

// PA8: TIM1_CH1, PB13: TIM1_CH1N

GPIOA->MODER |= (0x02 << 16); // PA8复用

GPIOB->MODER |= (0x02 << 26); // PB13复用

GPIOA->AFR[1] |= (0x01 << 0); // AF1 = TIM1

GPIOB->AFR[1] |= (0x01 << 20); // AF1 = TIM1

// 2. 使能TIM1时钟

RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;

// 3. 配置定时器

TIM1->PSC = 83;

TIM1->ARR = 999;

TIM1->CR1 |= TIM_CR1_ARPE;

// 4. 配置PWM模式

TIM1->CCMR1 |= (0x06 << 4); // PWM模式1

TIM1->CCMR1 |= TIM_CCMR1_OC1PE;

// 5. 使能主输出和互补输出

TIM1->CCER |= TIM_CCER_CC1E; // 使能CH1

TIM1->CCER |= TIM_CCER_CC1NE; // 使能CH1N

// 6. 配置死区时间

// DTG = 72,死区时间 = 72 × (1/84MHz) ≈ 0.86μs

TIM1->BDTR |= (72 << 0);

// 7. 使能主输出

TIM1->BDTR |= TIM_BDTR_MOE;

// 8. 启动定时器

TIM1->CR1 |= TIM_CR1_CEN;

}

死区时间说明:

死区时间是为了防止H桥上下桥臂同时导通造成短路。

无死区:

CH1: _____|‾‾‾‾‾|_____

CH1N: ‾‾‾‾‾|_____|‾‾‾‾‾

↑ 可能同时导通

有死区:

CH1: _____|‾‾‾‾‾|_____

CH1N: ‾‾‾‾‾‾|___|‾‾‾‾‾‾

↑死区↑

PWM DMA更新¶

使用DMA可以实现PWM波形表输出,用于音频播放等应用。

#define WAVE_SIZE 100

uint16_t wave_table[WAVE_SIZE];

/**

* @brief 生成正弦波表

* @param 无

* @retval 无

*/

void Generate_SineWave(void) {

for (uint16_t i = 0; i < WAVE_SIZE; i++) {

// 生成0-ARR范围的正弦波

float angle = 2.0f * 3.14159f * i / WAVE_SIZE;

wave_table[i] = (uint16_t)((sin(angle) + 1.0f) * TIM3->ARR / 2.0f);

}

}

/**

* @brief 配置PWM DMA更新

* @param 无

* @retval 无

*/

void PWM_DMA_Init(void) {

// 1. 生成波形表

Generate_SineWave();

// 2. 配置DMA

RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;

DMA1_Stream5->CR = 0;

while (DMA1_Stream5->CR & DMA_SxCR_EN);

DMA1_Stream5->PAR = (uint32_t)&TIM3->CCR1;

DMA1_Stream5->M0AR = (uint32_t)wave_table;

DMA1_Stream5->NDTR = WAVE_SIZE;

DMA1_Stream5->CR = (5 << 25) | // Channel 5

(1 << 16) | // 优先级中

(1 << 13) | // 内存半字

(1 << 11) | // 外设半字

(1 << 10) | // 内存递增

(1 << 8) | // 循环模式

(1 << 6); // 内存到外设

DMA1_Stream5->CR |= DMA_SxCR_EN;

// 3. 使能TIM3 DMA请求

TIM3->DIER |= TIM_DIER_CC1DE;

}

PWM输入捕获模式¶

定时器可以配置为PWM输入模式,用于测量外部PWM信号。

/**

* @brief 配置PWM输入捕获

* @param 无

* @retval 无

*/

void PWM_Input_Init(void) {

// 配置TIM3_CH1为PWM输入模式

// 可以同时测量频率和占空比

// 1. GPIO配置(PA6)

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

GPIOA->MODER |= (0x02 << 12);

GPIOA->AFR[0] |= (0x02 << 24);

// 2. 定时器配置

RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;

TIM3->PSC = 83; // 1MHz计数频率

TIM3->ARR = 0xFFFF;

// 3. 配置CH1为输入,直接映射到TI1

TIM3->CCMR1 |= (0x01 << 0); // CC1S = 01

// 4. 配置CH2为输入,交叉映射到TI1

TIM3->CCMR1 |= (0x02 << 8); // CC2S = 10

// 5. 配置触发源为TI1FP1

TIM3->SMCR |= (0x05 << 4); // TS = 101

// 6. 配置从模式为复位模式

TIM3->SMCR |= (0x04 << 0); // SMS = 100

// 7. 使能捕获

TIM3->CCER |= TIM_CCER_CC1E | TIM_CCER_CC2E;

// 8. 启动定时器

TIM3->CR1 |= TIM_CR1_CEN;

}

/**

* @brief 读取PWM输入参数

* @param freq: 频率指针(Hz)

* @param duty: 占空比指针(0-100)

* @retval 无

*/

void PWM_Input_Read(uint32_t *freq, uint8_t *duty) {

uint16_t period = TIM3->CCR1; // 周期

uint16_t pulse = TIM3->CCR2; // 脉宽

if (period > 0) {

*freq = 1000000 / period; // 频率

*duty = (uint8_t)(pulse * 100 / period); // 占空比

}

}

常见问题¶

Q1: PWM输出没有波形?¶

可能原因:

1. GPIO未配置为复用功能

2. 复用功能编号错误

3. 通道未使能

4. 定时器未启动

5. CCR值为0或等于ARR

排查步骤:

// 1. 检查GPIO配置

printf("GPIOA MODER: 0x%08X\r\n", GPIOA->MODER);

printf("GPIOA AFR[0]: 0x%08X\r\n", GPIOA->AFR[0]);

// 2. 检查定时器配置

printf("TIM3 CR1: 0x%04X\r\n", TIM3->CR1);

printf("TIM3 PSC: %d\r\n", TIM3->PSC);

printf("TIM3 ARR: %d\r\n", TIM3->ARR);

printf("TIM3 CCR1: %d\r\n", TIM3->CCR1);

// 3. 检查通道使能

printf("TIM3 CCER: 0x%04X\r\n", TIM3->CCER);

// 4. 检查定时器是否运行

printf("TIM3 CNT: %d\r\n", TIM3->CNT);

Q2: PWM频率不准确?¶

可能原因:

1. 时钟频率计算错误

2. PSC或ARR设置不当

3. 系统时钟配置错误

解决方案:

// 重新计算并验证

uint32_t tim_clk = 84000000; // 确认定时器时钟

uint32_t target_freq = 1000; // 目标频率

uint16_t resolution = 1000; // 分辨率

uint32_t psc = tim_clk / (target_freq * resolution) - 1;

printf("PSC should be: %d\r\n", psc);

// 实际频率

uint32_t actual_freq = tim_clk / ((psc + 1) * (TIM3->ARR + 1));

printf("Actual frequency: %d Hz\r\n", actual_freq);

Q3: 电机运行不平稳,有抖动?¶

可能原因:

1. PWM频率过低

2. 电源电压不稳定

3. 驱动电路问题

4. 负载过大

解决方案:

// 1. 提高PWM频率到20kHz

TIM3_PWM_Init(20000, 1000);

// 2. 添加软启动

Motor_SoftStart(1, 60, 1000);

// 3. 添加滤波电容(硬件)

// 在电机两端并联0.1μF电容

// 4. 检查电源电压

// 使用示波器观察电源纹波

Q4: 舵机抖动或不响应?¶

可能原因:

1. PWM频率不是50Hz

2. 脉宽范围不正确

3. 电源电流不足

4. 信号线干扰

解决方案:

// 1. 确认PWM频率为50Hz

// ARR = 19999时,周期 = 20ms = 50Hz

TIM3_PWM_Init(50, 20000);

// 2. 调整脉宽范围

// 不同舵机的脉宽范围可能不同

// 标准:0.5ms-2.5ms

// 有些舵机:1ms-2ms

void Servo_SetAngle_Custom(uint8_t channel, uint8_t angle) {

// 调整这些值以适应你的舵机

uint16_t min_pulse = 1000; // 0度脉宽

uint16_t max_pulse = 5000; // 180度脉宽

uint16_t ccr = min_pulse + (uint32_t)angle * (max_pulse - min_pulse) / 180;

TIM3_SetPWM_Raw(channel, ccr);

}

// 3. 使用独立电源供电

// 舵机电流较大,不要与MCU共用电源

Q5: 多通道PWM占空比不一致?¶

可能原因:

1. 各通道CCR设置不同

2. 输出极性配置不同

3. 硬件负载不同

排查方法:

// 检查所有通道配置

void PWM_CheckChannels(void) {

printf("Channel 1: CCR=%d, CCER=0x%04X\r\n",

TIM3->CCR1, TIM3->CCER & 0x000F);

printf("Channel 2: CCR=%d, CCER=0x%04X\r\n",

TIM3->CCR2, (TIM3->CCER & 0x00F0) >> 4);

printf("Channel 3: CCR=%d, CCER=0x%04X\r\n",

TIM3->CCR3, (TIM3->CCER & 0x0F00) >> 8);

printf("Channel 4: CCR=%d, CCER=0x%04X\r\n",

TIM3->CCR4, (TIM3->CCER & 0xF000) >> 12);

}

// 统一设置所有通道

void PWM_SetAll(uint8_t duty) {

uint16_t ccr = (TIM3->ARR + 1) * duty / 100;

TIM3->CCR1 = ccr;

TIM3->CCR2 = ccr;

TIM3->CCR3 = ccr;

TIM3->CCR4 = ccr;

}

最佳实践¶

1. 代码模块化¶

将PWM驱动封装成独立模块:

// pwm_driver.h

#ifndef __PWM_DRIVER_H

#define __PWM_DRIVER_H

#include "stm32f4xx.h"

// PWM初始化

void PWM_Init(TIM_TypeDef *TIMx, uint32_t freq, uint16_t resolution);

// 设置占空比

void PWM_SetDuty(TIM_TypeDef *TIMx, uint8_t channel, uint8_t duty);

// 设置CCR值

void PWM_SetCCR(TIM_TypeDef *TIMx, uint8_t channel, uint16_t ccr);

// 获取占空比

uint8_t PWM_GetDuty(TIM_TypeDef *TIMx, uint8_t channel);

// 启动/停止PWM

void PWM_Start(TIM_TypeDef *TIMx, uint8_t channel);

void PWM_Stop(TIM_TypeDef *TIMx, uint8_t channel);

#endif

2. 参数验证¶

在函数中添加参数检查:

void PWM_SetDuty(TIM_TypeDef *TIMx, uint8_t channel, uint8_t duty) {

// 参数检查

if (TIMx == NULL) return;

if (channel < 1 || channel > 4) return;

if (duty > 100) duty = 100;

// 计算CCR

uint16_t ccr = (TIMx->ARR + 1) * duty / 100;

// 设置CCR

switch (channel) {

case 1: TIMx->CCR1 = ccr; break;

case 2: TIMx->CCR2 = ccr; break;

case 3: TIMx->CCR3 = ccr; break;

case 4: TIMx->CCR4 = ccr; break;

}

}

3. 使用宏定义¶

提高代码可读性和可维护性:

// PWM通道定义

#define PWM_CH1 1

#define PWM_CH2 2

#define PWM_CH3 3

#define PWM_CH4 4

// 电机定义

#define MOTOR_LEFT 1

#define MOTOR_RIGHT 2

// 舵机角度定义

#define SERVO_MIN_ANGLE 0

#define SERVO_MAX_ANGLE 180

#define SERVO_CENTER 90

// 使用示例

Motor_SetSpeed(MOTOR_LEFT, 60);

Servo_SetAngle(PWM_CH1, SERVO_CENTER);

4. 添加调试功能¶

/**

* @brief 打印PWM配置信息

* @param TIMx: 定时器

* @retval 无

*/

void PWM_PrintConfig(TIM_TypeDef *TIMx) {

printf("=== PWM Configuration ===\r\n");

printf("PSC: %d\r\n", TIMx->PSC);

printf("ARR: %d\r\n", TIMx->ARR);

printf("Frequency: %d Hz\r\n",

84000000 / ((TIMx->PSC + 1) * (TIMx->ARR + 1)));

printf("Resolution: %d\r\n", TIMx->ARR + 1);

printf("\r\n");

printf("Channel 1: CCR=%d, Duty=%d%%\r\n",

TIMx->CCR1, TIMx->CCR1 * 100 / (TIMx->ARR + 1));

printf("Channel 2: CCR=%d, Duty=%d%%\r\n",

TIMx->CCR2, TIMx->CCR2 * 100 / (TIMx->ARR + 1));

printf("Channel 3: CCR=%d, Duty=%d%%\r\n",

TIMx->CCR3, TIMx->CCR3 * 100 / (TIMx->ARR + 1));

printf("Channel 4: CCR=%d, Duty=%d%%\r\n",

TIMx->CCR4, TIMx->CCR4 * 100 / (TIMx->ARR + 1));

}

5. 安全保护¶

添加必要的安全保护机制:

/**

* @brief 电机安全控制

* @param motor: 电机编号

* @param speed: 速度

* @retval 无

*/

void Motor_SetSpeed_Safe(uint8_t motor, int8_t speed) {

// 限制最大速度

#define MAX_SPEED 80

if (speed > MAX_SPEED) speed = MAX_SPEED;

if (speed < -MAX_SPEED) speed = -MAX_SPEED;

// 检查过流保护

if (Motor_IsOverCurrent(motor)) {

Motor_SetSpeed(motor, 0);

printf("Motor %d overcurrent!\r\n", motor);

return;

}

// 正常设置速度

Motor_SetSpeed(motor, speed);

}

/**

* @brief PWM输出限制

* @param channel: 通道

* @param duty: 占空比

* @param min: 最小占空比

* @param max: 最大占空比

* @retval 无

*/

void PWM_SetDuty_Limited(uint8_t channel, uint8_t duty,

uint8_t min, uint8_t max) {

if (duty < min) duty = min;

if (duty > max) duty = max;

TIM3_SetPWM(channel, duty);

}

进阶学习¶

1. 高级PWM技术¶

相位移PWM:

- 多相电机控制

- 降低EMI干扰

- 提高效率

中心对齐PWM:

- 对称波形

- 减少谐波

- 适合电机控制

2. PWM与其他外设配合¶

PWM + ADC:

- 电机电流检测

- 闭环速度控制

- 功率监测

PWM + 编码器:

- 精确位置控制

- 速度反馈

- PID控制

PWM + DMA:

- 波形表输出

- 音频播放

- 复杂时序生成

3. 电机控制算法¶

PID控制:

- 速度闭环控制

- 位置闭环控制

- 参数整定

FOC控制:

- 无刷电机控制

- 高效率

- 低噪音

4. PWM应用扩展¶

WS2812 LED驱动:

- 使用PWM+DMA

- 时序精确控制

- 彩色LED灯带

红外遥控发射:

- 38kHz载波

- 编码调制

- 遥控协议

音频输出:

- PWM DAC

- 音频播放

- 音调生成

学习资源¶

官方文档¶

STM32参考手册

定时器PWM章节

寄存器详细说明

时序图和波形

应用笔记

AN4013: STM32定时器概述

AN4776: 通用定时器手册

AN3116: STM32电机控制

推荐工具¶

示波器

观察PWM波形

测量频率和占空比

分析信号质量

逻辑分析仪

多通道同时观察

时序分析

协议解码

电机测试平台

测试电机性能

调试控制算法

验证驱动电路

实践项目¶

智能风扇

PWM调速

温度控制

难度:⭐⭐

RGB氛围灯

多通道PWM

颜色渐变

难度:⭐⭐

遥控小车

双电机控制

无线遥控

难度:⭐⭐⭐

云台控制

双轴舵机

姿态控制

难度:⭐⭐⭐

无刷电机控制

三相PWM

FOC算法

难度:⭐⭐⭐⭐⭐

总结¶

本教程全面介绍了PWM驱动开发的核心知识和实践技能。

核心要点回顾:

PWM原理:通过改变占空比控制平均功率

频率选择:根据应用场景选择合适的PWM频率

占空比计算:CCR = 占空比 × (ARR + 1) / 100

GPIO配置:配置为复用功能,选择正确的AF编号

定时器配置:设置PSC、ARR、PWM模式和通道使能

应用场景:LED调光、电机控制、舵机控制

实践技能:

- 单通道和多通道PWM输出

- LED呼吸灯和RGB调色

- 直流电机速度和方向控制

- 舵机角度控制

- 小车运动控制

最佳实践:

- 代码模块化,提高可维护性

- 添加参数验证,增强健壮性

- 使用宏定义,提高可读性

- 添加调试功能,方便问题排查

- 实现安全保护,避免硬件损坏

调试技巧:

- 使用示波器观察PWM波形

- 检查GPIO和定时器配置

- 验证频率和占空比计算

- 逐步测试各个功能模块

PWM是嵌入式系统中最实用的控制技术之一,掌握PWM驱动开发将为你的项目提供强大的控制能力。

延伸阅读¶

推荐进一步学习的内容:

同模块内容:

- GPIO驱动开发:LED控制实战 - GPIO基础

- 定时器驱动基础与应用 - 定时器原理

- DMA驱动开发:高效数据传输 - PWM+DMA

相关主题:

- 中断系统基础概念

- 通用定时器高级应用

官方文档:

- STM32F4xx参考手册

- AN4013: STM32定时器概述

参考资料¶

STM32F4xx参考手册 - STMicroelectronics

ARM Cortex-M4权威指南 - Joseph Yiu

电机控制技术 - 刘和平

PWM技术及其应用 - 王兆安

嵌入式系统设计与实践 - Elecia White

练习题¶

基础练习¶

LED调光练习:

使用PWM实现LED亮度调节

通过按键控制亮度增减

显示当前亮度百分比

呼吸灯练习:

实现单色LED呼吸灯

可调节呼吸速度

添加暂停/继续功能

RGB调色练习:

使用3个PWM通道控制RGB LED

实现彩虹渐变效果

预设多种颜色

进阶练习¶

电机控制练习:

控制直流电机转速

实现正转、反转、停止

添加软启动功能

舵机控制练习:

控制舵机角度

实现0-180度扫描

添加角度限位保护

小车控制练习:

双电机小车控制

实现前进、后退、转弯

添加速度调节功能

综合练习¶

智能风扇:

PWM调速控制

温度传感器反馈

自动调节转速

LCD显示状态

RGB氛围灯:

多种灯光效果

颜色渐变和跳变

音乐律动模式

遥控器控制

思考题¶

为什么电机控制的PWM频率要选择20kHz左右?

如何选择合适的PWM频率和分辨率?

互补PWM输出中死区时间的作用是什么?如何计算?

PWM控制电机时,如何实现平滑的加减速?

如何使用PWM实现音频输出?需要注意什么?

实验任务¶

任务1:LED调光器(必做)¶

要求:

- 使用PWM控制LED亮度

- 通过按键调节亮度(0-100%)

- 串口显示当前亮度

- 实现呼吸灯模式

任务2:电机调速器(必做)¶

要求:

- 控制直流电机转速

- 支持正转和反转

- 速度可调(0-100%)

- 实现软启动和软停止

任务3:舵机控制器(选做)¶

要求:

- 控制舵机角度(0-180度)

- 通过串口输入角度值

- 实现角度扫描功能

- 添加角度限位保护

任务4:智能小车(选做)¶

要求:

- 双电机差速控制

- 实现前进、后退、左转、右转

- 速度可调

- 蓝牙遥控

评分标准:

- 代码规范性(20分)

- 功能完整性(30分)

- 控制精度(30分)

- 创新性和扩展性(20分)

下一步学习建议:

完成本教程后,建议按以下顺序继续学习:

看门狗驱动与系统可靠性 - 系统保护

DMA驱动开发:高效数据传输 - PWM+DMA应用

通用定时器高级应用 - 高级PWM技术

学习路线图:

PWM驱动 → LED控制 → 电机控制 → 舵机控制 → 复杂控制系统

RGB调色 → 氛围灯 → 显示系统

音频输出 → 音乐播放 → 语音合成

祝你学习顺利!如有问题,欢迎在社区讨论。

文档信息:

- 最后更新:2024-01-15

- 版本:v1.0

- 作者:嵌入式知识平台

- 许可:CC BY-NC-SA 4.0

相关推荐