跳转至

PWM编程

概述

PWM(Pulse Width Modulation,脉冲宽度调制)是通过改变脉冲宽度来控制模拟量输出的技术,广泛应用于电机控制、LED调光、电源控制等场景。

PWM参数

参数 说明
频率 每秒脉冲次数(Hz)
周期 一个脉冲的时间(T=1/f)
占空比 高电平时间/周期(0-100%)
分辨率 占空比可调节的最小单位

PWM波形示例

Text Only
占空比 50%:
    ____    ____    ____
___|    |__|    |__|    |__

占空比 25%:
    __      __      __
___|  |____|  |____|  |____

占空比 75%:
    ______  ______  ______
___|      ||      ||      |

Linux PWM编程

sysfs接口

Bash
# 导出PWM
echo 0 > /sys/class/pwm/pwmchip0/export

# 设置周期(纳秒)
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period    # 1ms周期,1kHz

# 设置占空比(纳秒)
echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle # 50%占空比

# 启用PWM
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable

# 关闭PWM
echo 0 > /sys/class/pwm/pwmchip0/pwm0/enable

C语言接口

C
#include <stdio.h>
#include <stdlib.h>

void pwm_init(int chip, int channel)
{
    char path[64];
    snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%d/export", chip);
    FILE *f = fopen(path, "w");
    if (f) {
        fprintf(f, "%d", channel);
        fclose(f);
    }
    usleep(100000);  // 等待创建
}

void pwm_set_period(int chip, int channel, unsigned int period_ns)
{
    char path[64];
    snprintf(path, sizeof(path), 
             "/sys/class/pwm/pwmchip%d/pwm%d/period", chip, channel);
    FILE *f = fopen(path, "w");
    if (f) {
        fprintf(f, "%u", period_ns);
        fclose(f);
    }
}

void pwm_set_duty(int chip, int channel, unsigned int duty_ns)
{
    char path[64];
    snprintf(path, sizeof(path),
             "/sys/class/pwm/pwmchip%d/pwm%d/duty_cycle", chip, channel);
    FILE *f = fopen(path, "w");
    if (f) {
        fprintf(f, "%u", duty_ns);
        fclose(f);
    }
}

void pwm_enable(int chip, int channel, int enable)
{
    char path[64];
    snprintf(path, sizeof(path),
             "/sys/class/pwm/pwmchip%d/pwm%d/enable", chip, channel);
    FILE *f = fopen(path, "w");
    if (f) {
        fprintf(f, "%d", enable);
        fclose(f);
    }
}

// 设置占空比百分比
void pwm_set_percent(int chip, int channel, unsigned int period_ns, float percent)
{
    unsigned int duty_ns = (unsigned int)(period_ns * percent / 100.0);
    pwm_set_duty(chip, channel, duty_ns);
}

STM32 PWM编程

HAL库方式

C
#include "stm32f4xx_hal.h"

TIM_HandleTypeDef htim2;

void PWM_Init(void)
{
    TIM_OC_InitTypeDef sConfigOC = {0};
    
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 83;            // 84MHz / 84 = 1MHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 999;              // 1MHz / 1000 = 1kHz
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&htim2);
    
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 500;                // 50%占空比
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
    
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
}

void set_pwm_duty(uint32_t duty)
{
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
}

void set_pwm_percent(float percent)
{
    uint32_t period = __HAL_TIM_GET_AUTORELOAD(&htim2);
    uint32_t duty = (uint32_t)(period * percent / 100.0);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
}

频率和占空比计算

C
// 设置PWM频率
void set_pwm_frequency(uint32_t frequency)
{
    uint32_t timer_clock = 84000000;  // 84MHz
    uint32_t prescaler = 83;          // 预分频后1MHz
    uint32_t period = (timer_clock / (prescaler + 1)) / frequency - 1;
    
    __HAL_TIM_SET_PRESCALER(&htim2, prescaler);
    __HAL_TIM_SET_AUTORELOAD(&htim2, period);
}

// 设置频率和占空比
void set_pwm(uint32_t frequency, float percent)
{
    uint32_t timer_clock = 1000000;   // 预分频后时钟
    uint32_t period = timer_clock / frequency - 1;
    uint32_t duty = (uint32_t)((period + 1) * percent / 100.0);
    
    __HAL_TIM_SET_AUTORELOAD(&htim2, period);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
}

ESP32 PWM编程

LEDC(LED PWM控制器)

C
#include "driver/ledc.h"

#define LEDC_TIMER      LEDC_TIMER_0
#define LEDC_MODE       LEDC_LOW_SPEED_MODE
#define LEDC_CHANNEL    LEDC_CHANNEL_0
#define LEDC_DUTY_RES   LEDC_TIMER_13_BIT  // 13位分辨率
#define LEDC_FREQUENCY  5000               // 5kHz
#define LEDC_GPIO_NUM   18

void pwm_init(void)
{
    ledc_timer_config_t timer_conf = {
        .speed_mode = LEDC_MODE,
        .duty_resolution = LEDC_DUTY_RES,
        .timer_num = LEDC_TIMER,
        .freq_hz = LEDC_FREQUENCY,
        .clk_cfg = LEDC_AUTO_CLK
    };
    ledc_timer_config(&timer_conf);
    
    ledc_channel_config_t channel_conf = {
        .gpio_num = LEDC_GPIO_NUM,
        .speed_mode = LEDC_MODE,
        .channel = LEDC_CHANNEL,
        .intr_type = LEDC_INTR_DISABLE,
        .timer_sel = LEDC_TIMER,
        .duty = 0,
        .hpoint = 0,
    };
    ledc_channel_config(&channel_conf);
}

void set_pwm_duty(uint32_t duty)
{
    ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty);
    ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
}

void set_pwm_percent(float percent)
{
    uint32_t max_duty = (1 << 13) - 1;  // 13位分辨率
    uint32_t duty = (uint32_t)(max_duty * percent / 100.0);
    set_pwm_duty(duty);
}

渐变效果

C
1
2
3
4
5
6
// PWM渐变
void pwm_fade(uint32_t start_duty, uint32_t end_duty, int fade_time_ms)
{
    ledc_set_fade_with_time(LEDC_MODE, LEDC_CHANNEL, end_duty, fade_time_ms);
    ledc_fade_start(LEDC_MODE, LEDC_CHANNEL, LEDC_FADE_WAIT_DONE);
}

树莓派PWM编程

RPi.GPIO(软件PWM)

Python
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)

pwm = GPIO.PWM(18, 1000)    # 1kHz
pwm.start(50)               # 50%占空比

# 改变占空比
pwm.ChangeDutyCycle(75)

# 改变频率
pwm.ChangeFrequency(2000)

pwm.stop()
GPIO.cleanup()

pigpio(硬件PWM)

Python
import pigpio

pi = pigpio.pi()

# 硬件PWM
pi.hardware_PWM(18, 1000, 500000)  # GPIO, 频率, 占空比*1e6

# 占空比范围: 0-1000000 (0%-100%)
pi.hardware_PWM(18, 1000, 250000)  # 25%占空比

pi.stop()

PWM应用示例

LED呼吸灯

C
void breathing_led(void)
{
    int duty = 0;
    int direction = 1;
    
    while (1) {
        duty += direction * 10;
        
        if (duty >= 100) {
            duty = 100;
            direction = -1;
        } else if (duty <= 0) {
            duty = 0;
            direction = 1;
        }
        
        set_pwm_percent(duty);
        HAL_Delay(20);
    }
}

舵机控制

C
// 标准舵机: 50Hz, 脉宽1ms-2ms
void servo_init(void)
{
    set_pwm_frequency(50);  // 50Hz
}

void servo_set_angle(float angle)
{
    // 角度0°-180°对应脉宽1ms-2ms
    // 周期20ms,占空比5%-10%
    float percent = 5.0 + (angle / 180.0) * 5.0;
    set_pwm_percent(percent);
}

// 或直接设置脉宽
void servo_set_pulse_us(uint16_t pulse_us)
{
    // 脉宽范围: 1000-2000us
    uint32_t period_us = 20000;  // 20ms周期
    float percent = (float)pulse_us / period_us * 100.0;
    set_pwm_percent(percent);
}

直流电机调速

C
void motor_init(void)
{
    // PWM频率通常10-20kHz
    set_pwm_frequency(10000);
}

void motor_set_speed(float speed)
{
    // speed: 0-100%
    if (speed < 0) speed = 0;
    if (speed > 100) speed = 100;
    
    set_pwm_percent(speed);
}

// 带方向控制
void motor_set_speed_dir(float speed, int direction)
{
    if (direction > 0) {
        GPIO_WritePin(DIR_PIN, GPIO_PIN_SET);
    } else {
        GPIO_WritePin(DIR_PIN, GPIO_PIN_RESET);
    }
    set_pwm_percent(speed);
}

蜂鸣器控制

C
void buzzer_on(uint32_t frequency)
{
    set_pwm_frequency(frequency);
    set_pwm_percent(50);  // 50%占空比
}

void buzzer_off(void)
{
    set_pwm_percent(0);
}

// 播放音符
void play_note(uint8_t note)
{
    const uint16_t notes[] = {
        262, 294, 330, 349, 392, 440, 494, 523  // C4-C5
    };
    
    if (note < sizeof(notes)/sizeof(notes[0])) {
        buzzer_on(notes[note]);
    }
}

多通道PWM

RGB LED控制

C
void rgb_led_init(void)
{
    // 初始化三个PWM通道
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);  // Red
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);  // Green
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);  // Blue
}

void rgb_led_set(uint8_t r, uint8_t g, uint8_t b)
{
    uint32_t period = __HAL_TIM_GET_AUTORELOAD(&htim2);
    
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, (period * r / 255));
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, (period * g / 255));
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, (period * b / 255));
}

// HSV转RGB
void rgb_led_set_hsv(float h, float s, float v)
{
    float r, g, b;
    
    int i = (int)(h / 60) % 6;
    float f = h / 60 - i;
    float p = v * (1 - s);
    float q = v * (1 - f * s);
    float t = v * (1 - (1 - f) * s);
    
    switch (i) {
        case 0: r = v; g = t; b = p; break;
        case 1: r = q; g = v; b = p; break;
        case 2: r = p; g = v; b = t; break;
        case 3: r = p; g = q; b = v; break;
        case 4: r = t; g = p; b = v; break;
        case 5: r = v; g = p; b = q; break;
    }
    
    rgb_led_set((uint8_t)(r * 255), (uint8_t)(g * 255), (uint8_t)(b * 255));
}

参考资料