单片机产生占空比可调方波(PWM)教程

单片机产生占空比可调方波(PWM)教程

基础知识

1. PWM原理

占空比(Duty Cycle) = 高电平时间 / 周期时间 × 100%

PWM频率 = 1 / (T_high + T_low)
T_high: 高电平时间
T_low: 低电平时间

2. 常见单片机PWM实现方式

单片机型号 PWM模块 最高频率 分辨率 通道数
STM32F103 高级/通用定时器 72MHz 16-bit 最多12路
STM32F407 高级/通用定时器 168MHz 16-bit 最多12路
ATmega328P(Arduino) 定时器0/1/2 16MHz 8-bit/16-bit 6路
ESP32 LEDC/MCPWM 80MHz 16-bit 最多16路
51单片机(无硬件PWM) 软件模拟 取决于主频 软件可调 不限

代码实现

1. STM32实现方案(基于HAL库)

主程序(main.c)

/**
  * STM32 PWM波生成程序
  * MCU: STM32F103C8T6
  * 功能:4路独立PWM输出,占空比可调
  * 频率范围:1Hz - 100KHz
  * 占空比范围:0-100%
  */

#include "main.h"
#include <stdio.h>
#include <string.h>

// 全局变量
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim2;
UART_HandleTypeDef huart1;

PWM_Channel pwm_channels[4];
uint8_t current_channel = 0;
char uart_buffer[100];

// PWM通道结构体
typedef struct {
    TIM_HandleTypeDef* htim;   // 定时器句柄
    uint32_t channel;          // 定时器通道
    uint32_t frequency;        // 频率(Hz)
    uint32_t duty_cycle;       // 占空比(0-10000对应0-100%)
    uint32_t period;           // 周期值(ARR)
    uint32_t pulse;            // 比较值(CCR)
    uint8_t enabled;           // 使能状态
} PWM_Channel;

// 函数声明
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);
static void MX_TIM2_Init(void);
static void MX_USART1_UART_Init(void);
void PWM_Init_Channel(PWM_Channel* channel, TIM_HandleTypeDef* htim, 
                      uint32_t ch, uint32_t freq, uint32_t duty);
void PWM_Set_Frequency(PWM_Channel* channel, uint32_t freq);
void PWM_Set_Duty(PWM_Channel* channel, uint32_t duty);
void PWM_Start(PWM_Channel* channel);
void PWM_Stop(PWM_Channel* channel);
void PWM_Update(PWM_Channel* channel);
void UART_Send_String(char* str);
void Display_Menu(void);
void Process_Command(char* cmd);

int main(void) {
    HAL_Init();
    SystemClock_Config();
    
    MX_GPIO_Init();
    MX_TIM1_Init();
    MX_TIM2_Init();
    MX_USART1_UART_Init();
    
    // 初始化PWM通道
    // 通道1: PA8, TIM1_CH1, 1KHz, 50%占空比
    PWM_Init_Channel(&pwm_channels[0], &htim1, TIM_CHANNEL_1, 1000, 5000);
    
    // 通道2: PA9, TIM1_CH2, 2KHz, 25%占空比
    PWM_Init_Channel(&pwm_channels[1], &htim1, TIM_CHANNEL_2, 2000, 2500);
    
    // 通道3: PA0, TIM2_CH1, 5KHz, 75%占空比
    PWM_Init_Channel(&pwm_channels[2], &htim2, TIM_CHANNEL_1, 5000, 7500);
    
    // 通道4: PA1, TIM2_CH2, 10KHz, 10%占空比
    PWM_Init_Channel(&pwm_channels[3], &htim2, TIM_CHANNEL_2, 10000, 1000);
    
    // 启动所有PWM通道
    for(int i = 0; i < 4; i++) {
        PWM_Start(&pwm_channels[i]);
    }
    
    // 显示菜单
    Display_Menu();
    
    // 主循环
    char rx_buffer[50];
    uint8_t rx_index = 0;
    
    while (1) {
        // 接收串口命令
        if(HAL_UART_Receive(&huart1, (uint8_t*)&rx_buffer[rx_index], 1, 10) == HAL_OK) {
            if(rx_buffer[rx_index] == '\r' || rx_buffer[rx_index] == '\n') {
                rx_buffer[rx_index] = '\0';
                if(rx_index > 0) {
                    Process_Command(rx_buffer);
                }
                rx_index = 0;
            } else {
                rx_index = (rx_index + 1) % 50;
            }
        }
        
        // 其他任务...
        HAL_Delay(10);
    }
}

/**
  * 初始化PWM通道
  */
void PWM_Init_Channel(PWM_Channel* channel, TIM_HandleTypeDef* htim, 
                      uint32_t ch, uint32_t freq, uint32_t duty) {
    channel->htim = htim;
    channel->channel = ch;
    channel->frequency = freq;
    channel->duty_cycle = duty;
    channel->enabled = 0;
    
    PWM_Set_Frequency(channel, freq);
    PWM_Set_Duty(channel, duty);
}

/**
  * 设置PWM频率
  * 频率范围: 1Hz - 100KHz
  */
void PWM_Set_Frequency(PWM_Channel* channel, uint32_t freq) {
    if(freq < 1) freq = 1;
    if(freq > 100000) freq = 100000;
    
    channel->frequency = freq;
    
    // 计算周期值 ARR = 时钟频率 / (预分频 * PWM频率) - 1
    // 系统时钟72MHz,预分频设为72-1,这样计数器时钟为1MHz
    uint32_t timer_clock = 1000000;  // 1MHz
    channel->period = (timer_clock / freq) - 1;
    
    // 更新定时器配置
    __HAL_TIM_SET_AUTORELOAD(channel->htim, channel->period);
    
    // 重新计算占空比
    PWM_Set_Duty(channel, channel->duty_cycle);
    
    // 如果通道已使能,重新启动
    if(channel->enabled) {
        PWM_Update(channel);
    }
}

/**
  * 设置PWM占空比
  * duty: 0-10000对应0-100%
  */
void PWM_Set_Duty(PWM_Channel* channel, uint32_t duty) {
    if(duty > 10000) duty = 10000;
    
    channel->duty_cycle = duty;
    
    // 计算脉冲宽度 CCR = 占空比 * 周期 / 10000
    channel->pulse = (duty * channel->period) / 10000;
    
    // 更新比较寄存器
    switch(channel->channel) {
        case TIM_CHANNEL_1:
            __HAL_TIM_SET_COMPARE(channel->htim, TIM_CHANNEL_1, channel->pulse);
            break;
        case TIM_CHANNEL_2:
            __HAL_TIM_SET_COMPARE(channel->htim, TIM_CHANNEL_2, channel->pulse);
            break;
        case TIM_CHANNEL_3:
            __HAL_TIM_SET_COMPARE(channel->htim, TIM_CHANNEL_3, channel->pulse);
            break;
        case TIM_CHANNEL_4:
            __HAL_TIM_SET_COMPARE(channel->htim, TIM_CHANNEL_4, channel->pulse);
            break;
    }
    
    // 如果通道已使能,重新启动
    if(channel->enabled) {
        PWM_Update(channel);
    }
}

/**
  * 启动PWM输出
  */
void PWM_Start(PWM_Channel* channel) {
    HAL_TIM_PWM_Start(channel->htim, channel->channel);
    channel->enabled = 1;
}

/**
  * 停止PWM输出
  */
void PWM_Stop(PWM_Channel* channel) {
    HAL_TIM_PWM_Stop(channel->htim, channel->channel);
    channel->enabled = 0;
}

/**
  * 更新PWM输出
  */
void PWM_Update(PWM_Channel* channel) {
    // 先停止
    HAL_TIM_PWM_Stop(channel->htim, channel->channel);
    
    // 重新配置
    __HAL_TIM_SET_AUTORELOAD(channel->htim, channel->period);
    
    // 设置比较值
    PWM_Set_Duty(channel, channel->duty_cycle);
    
    // 重新启动
    HAL_TIM_PWM_Start(channel->htim, channel->channel);
}

/**
  * 串口发送字符串
  */
void UART_Send_String(char* str) {
    HAL_UART_Transmit(&huart1, (uint8_t*)str, strlen(str), 1000);
    HAL_UART_Transmit(&huart1, (uint8_t*)"\r\n", 2, 1000);
}

/**
  * 显示菜单
  */
void Display_Menu(void) {
    UART_Send_String("\r\n=== PWM波形发生器 ===");
    UART_Send_String("命令列表:");
    UART_Send_String("  c [1-4] - 选择通道");
    UART_Send_String("  f [Hz]  - 设置频率");
    UART_Send_String("  d [0-100] - 设置占空比");
    UART_Send_String("  s       - 启动/停止");
    UART_Send_String("  i       - 显示信息");
    UART_Send_String("  m       - 显示菜单");
    UART_Send_String("当前通道: 1");
    
    // 显示所有通道状态
    for(int i = 0; i < 4; i++) {
        sprintf(uart_buffer, "通道%d: %dHz, %.1f%%", 
                i+1, 
                pwm_channels[i].frequency,
                pwm_channels[i].duty_cycle / 100.0);
        UART_Send_String(uart_buffer);
    }
}

/**
  * 处理串口命令
  */
void Process_Command(char* cmd) {
    char command = cmd[0];
    int value = 0;
    
    if(strlen(cmd) > 2) {
        sscanf(&cmd[2], "%d", &value);
    }
    
    switch(command) {
        case 'c':  // 选择通道
            if(value >= 1 && value <= 4) {
                current_channel = value - 1;
                sprintf(uart_buffer, "已选择通道%d", value);
                UART_Send_String(uart_buffer);
            }
            break;
            
        case 'f':  // 设置频率
            if(value > 0) {
                PWM_Set_Frequency(&pwm_channels[current_channel], value);
                sprintf(uart_buffer, "通道%d频率设为%dHz", 
                        current_channel+1, value);
                UART_Send_String(uart_buffer);
            }
            break;
            
        case 'd':  // 设置占空比
            if(value >= 0 && value <= 100) {
                PWM_Set_Duty(&pwm_channels[current_channel], value * 100);
                sprintf(uart_buffer, "通道%d占空比设为%d%%", 
                        current_channel+1, value);
                UART_Send_String(uart_buffer);
            }
            break;
            
        case 's':  // 启动/停止
            if(pwm_channels[current_channel].enabled) {
                PWM_Stop(&pwm_channels[current_channel]);
                sprintf(uart_buffer, "通道%d已停止", current_channel+1);
            } else {
                PWM_Start(&pwm_channels[current_channel]);
                sprintf(uart_buffer, "通道%d已启动", current_channel+1);
            }
            UART_Send_String(uart_buffer);
            break;
            
        case 'i':  // 显示信息
            sprintf(uart_buffer, "通道%d: %dHz, %.1f%%, %s", 
                    current_channel+1,
                    pwm_channels[current_channel].frequency,
                    pwm_channels[current_channel].duty_cycle / 100.0,
                    pwm_channels[current_channel].enabled ? "运行" : "停止");
            UART_Send_String(uart_buffer);
            break;
            
        case 'm':  // 显示菜单
            Display_Menu();
            break;
            
        default:
            UART_Send_String("未知命令,输入'm'查看菜单");
            break;
    }
}

定时器1初始化(tim.c)

/**
  * TIM1初始化
  * 通道1: PA8
  * 通道2: PA9
  * 通道3: PA10
  * 通道4: PA11
  */
static void MX_TIM1_Init(void) {
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};
    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
    
    // 时钟配置
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 72 - 1;  // 72MHz/72 = 1MHz
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim1.Init.Period = 1000 - 1;  // 默认1KHz
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim1.Init.RepetitionCounter = 0;
    htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_Base_Init(&htim1);
    
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig);
    HAL_TIM_PWM_Init(&htim1);
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
    
    // 输出比较配置
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 500;  // 默认50%占空比
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
    
    // 配置4个通道
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2);
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3);
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4);
    
    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
    sBreakDeadTimeConfig.DeadTime = 0;
    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
    sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
    HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
}

定时器2初始化

/**
  * TIM2初始化
  * 通道1: PA0
  * 通道2: PA1
  * 通道3: PA2
  * 通道4: PA3
  */
static void MX_TIM2_Init(void) {
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};
    
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 72 - 1;  // 72MHz/72 = 1MHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 1000 - 1;  // 默认1KHz
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_Base_Init(&htim2);
    
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);
    HAL_TIM_PWM_Init(&htim2);
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
    
    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_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2);
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_3);
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4);
}

2. Arduino实现方案

/**
  * Arduino PWM波形生成程序
  * 支持6路PWM输出
  * 频率范围:1Hz - 31KHz (不同引脚不同)
  * 占空比范围:0-100%
  */

#include <Arduino.h>

// 引脚定义
const int pwm_pins[] = {3, 5, 6, 9, 10, 11};  // 支持PWM的引脚
const int num_channels = 6;

// PWM通道结构
struct PWM_Channel {
    int pin;            // 引脚号
    float frequency;    // 频率(Hz)
    float duty_cycle;   // 占空比(0-100)
    uint32_t period;    // 周期(us)
    uint32_t on_time;   // 高电平时间(us)
    bool enabled;       // 使能状态
};

PWM_Channel channels[num_channels];

// 定时器配置表
struct TimerConfig {
    uint8_t timer;      // 定时器编号
    uint8_t tccra;      // TCCRnA寄存器值
    uint8_t tccrb;      // TCCRnB寄存器值
    uint8_t wgm;        // 波形生成模式
    uint8_t com;        // 比较输出模式
    uint32_t frequency; // 最大频率
};

// Arduino Uno的定时器配置
const TimerConfig timer_configs[] = {
    // 定时器0 (引脚5,6)
    {0, 0, 0, 0, 0, 62500},  // 模式0: 62.5KHz
    {0, 1, 3, 5, 1, 31250},  // 模式1: 31.25KHz
    {0, 1, 3, 2, 1, 7812},   // 模式2: 7.812KHz
    {0, 1, 3, 7, 1, 976},    // 模式3: 976Hz
    
    // 定时器1 (引脚9,10)
    {1, 1, 3, 5, 1, 31250},  // 模式5: 31.25KHz
    {1, 1, 3, 2, 1, 3906},   // 模式2: 3.906KHz
    {1, 1, 3, 8, 1, 488},    // 模式8: 488Hz
    
    // 定时器2 (引脚3,11)
    {2, 1, 3, 5, 1, 31250},  // 模式5: 31.25KHz
    {2, 1, 3, 2, 1, 3906},   // 模式2: 3.906KHz
    {2, 1, 3, 7, 1, 976}     // 模式7: 976Hz
};

void setup() {
    Serial.begin(115200);
    
    // 初始化所有通道
    for(int i = 0; i < num_channels; i++) {
        channels[i].pin = pwm_pins[i];
        channels[i].frequency = 1000;  // 默认1KHz
        channels[i].duty_cycle = 50;   // 默认50%
        channels[i].enabled = true;
        
        pinMode(channels[i].pin, OUTPUT);
        
        // 设置默认频率
        setPWMFrequency(channels[i].pin, channels[i].frequency);
        
        // 设置默认占空比
        analogWrite(channels[i].pin, channels[i].duty_cycle * 2.55);
    }
    
    Serial.println("=== Arduino PWM波形发生器 ===");
    Serial.println("初始化完成,6路PWM输出已就绪");
    displayStatus();
}

void loop() {
    // 处理串口命令
    if(Serial.available() > 0) {
        char cmd = Serial.read();
        processCommand(cmd);
    }
    
    // 可以添加其他功能...
    delay(10);
}

/**
  * 设置PWM频率
  * 注意:改变频率会影响所有使用同一定时器的引脚
  */
void setPWMFrequency(int pin, float frequency) {
    uint8_t timer = digitalPinToTimer(pin);
    
    switch(timer) {
        case TIMER0A:  // 引脚5
        case TIMER0B:  // 引脚6
            setTimer0Frequency(frequency);
            break;
            
        case TIMER1A:  // 引脚9
        case TIMER1B:  // 引脚10
            setTimer1Frequency(frequency);
            break;
            
        case TIMER2A:  // 引脚11
        case TIMER2B:  // 引脚3
            setTimer2Frequency(frequency);
            break;
    }
}

void setTimer0Frequency(float frequency) {
    // 保存当前占空比
    uint8_t duty5 = (OCR0A * 100) / 255;
    uint8_t duty6 = (OCR0B * 100) / 255;
    
    if(frequency >= 31250) {
        // 模式1: 31.25KHz
        TCCR0A = (1 << COM0A1) | (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
        TCCR0B = (1 << CS00);
        OCR0A = (duty5 * 255) / 100;
        OCR0B = (duty6 * 255) / 100;
    } else if(frequency >= 7812) {
        // 模式2: 7.812KHz
        TCCR0A = (1 << COM0A1) | (1 << COM0B1) | (1 << WGM01);
        TCCR0B = (1 << CS01);
        OCR0A = (duty5 * 255) / 100;
        OCR0B = (duty6 * 255) / 100;
    } else {
        // 模式3: 默认976Hz
        TCCR0A = (1 << COM0A1) | (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
        TCCR0B = (1 << CS01) | (1 << CS00);
        OCR0A = (duty5 * 255) / 100;
        OCR0B = (duty6 * 255) / 100;
    }
}

void setTimer1Frequency(float frequency) {
    // 保存当前占空比
    uint16_t duty9 = (OCR1A * 100) / 65535;
    uint16_t duty10 = (OCR1B * 100) / 65535;
    
    if(frequency >= 31250) {
        // 模式5: 31.25KHz
        TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM10);
        TCCR1B = (1 << WGM12) | (1 << CS10);
        OCR1A = (duty9 * 65535) / 100;
        OCR1B = (duty10 * 65535) / 100;
    } else if(frequency >= 3906) {
        // 模式2: 3.906KHz
        TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11);
        TCCR1B = (1 << WGM13) | (1 << CS11);
        OCR1A = (duty9 * 65535) / 100;
        OCR1B = (duty10 * 65535) / 100;
    } else {
        // 模式8: 默认488Hz
        TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11) | (1 << WGM10);
        TCCR1B = (1 << WGM13) | (1 << CS11) | (1 << CS10);
        OCR1A = (duty9 * 65535) / 100;
        OCR1B = (duty10 * 65535) / 100;
    }
}

void setTimer2Frequency(float frequency) {
    // 保存当前占空比
    uint8_t duty3 = (OCR2B * 100) / 255;
    uint8_t duty11 = (OCR2A * 100) / 255;
    
    if(frequency >= 31250) {
        // 模式5: 31.25KHz
        TCCR2A = (1 << COM2A1) | (1 << COM2B1) | (1 << WGM20);
        TCCR2B = (1 << WGM22) | (1 << CS20);
        OCR2A = (duty11 * 255) / 100;
        OCR2B = (duty3 * 255) / 100;
    } else if(frequency >= 3906) {
        // 模式2: 3.906KHz
        TCCR2A = (1 << COM2A1) | (1 << COM2B1) | (1 << WGM21);
        TCCR2B = (1 << WGM22) | (1 << CS21);
        OCR2A = (duty11 * 255) / 100;
        OCR2B = (duty3 * 255) / 100;
    } else {
        // 模式7: 默认976Hz
        TCCR2A = (1 << COM2A1) | (1 << COM2B1) | (1 << WGM21) | (1 << WGM20);
        TCCR2B = (1 << CS22) | (1 << CS21) | (1 << CS20);
        OCR2A = (duty11 * 255) / 100;
        OCR2B = (duty3 * 255) / 100;
    }
}

/**
  * 设置PWM占空比
  */
void setPWMDuty(int pin, float duty) {
    if(duty < 0) duty = 0;
    if(duty > 100) duty = 100;
    
    // 找到对应的通道
    for(int i = 0; i < num_channels; i++) {
        if(channels[i].pin == pin) {
            channels[i].duty_cycle = duty;
            break;
        }
    }
    
    // Arduino的analogWrite使用8位分辨率(0-255)
    analogWrite(pin, duty * 2.55);
}

/**
  * 软件PWM实现(任意引脚)
  * 精度较低,但可以在任意引脚使用
  */
class SoftwarePWM {
private:
    int pin;
    float frequency;
    float duty_cycle;
    uint32_t period_us;
    uint32_t on_time_us;
    bool running;
    uint32_t last_time;
    
public:
    SoftwarePWM(int p) {
        pin = p;
        frequency = 1000;  // 默认1KHz
        duty_cycle = 50;   // 默认50%
        running = false;
        pinMode(pin, OUTPUT);
    }
    
    void begin(float freq, float duty) {
        frequency = freq;
        duty_cycle = duty;
        period_us = 1000000 / frequency;
        on_time_us = (period_us * duty_cycle) / 100;
        running = true;
        last_time = micros();
    }
    
    void update() {
        if(!running) return;
        
        uint32_t current_time = micros();
        uint32_t elapsed = current_time - last_time;
        
        if(elapsed >= period_us) {
            last_time = current_time;
            digitalWrite(pin, HIGH);
        } else if(elapsed >= on_time_us) {
            digitalWrite(pin, LOW);
        }
    }
    
    void setFrequency(float freq) {
        frequency = freq;
        period_us = 1000000 / frequency;
        on_time_us = (period_us * duty_cycle) / 100;
    }
    
    void setDutyCycle(float duty) {
        duty_cycle = duty;
        on_time_us = (period_us * duty_cycle) / 100;
    }
    
    void stop() {
        running = false;
        digitalWrite(pin, LOW);
    }
    
    void start() {
        running = true;
        last_time = micros();
    }
};

// 使用示例
SoftwarePWM soft_pwm1(2);  // 在引脚2上使用软件PWM
SoftwarePWM soft_pwm2(4);  // 在引脚4上使用软件PWM

/**
  * 处理串口命令
  */
void processCommand(char cmd) {
    switch(cmd) {
        case 's':  // 显示状态
            displayStatus();
            break;
            
        case '1':  // 通道1设置
            setChannel(0);
            break;
            
        case '2':  // 通道2设置
            setChannel(1);
            break;
            
        // ... 其他通道
            
        case 'h':  // 帮助
            displayHelp();
            break;
    }
}

void displayStatus() {
    Serial.println("\n=== PWM通道状态 ===");
    for(int i = 0; i < num_channels; i++) {
        Serial.print("通道");
        Serial.print(i+1);
        Serial.print("(引脚");
        Serial.print(channels[i].pin);
        Serial.print("): ");
        Serial.print(channels[i].frequency);
        Serial.print("Hz, ");
        Serial.print(channels[i].duty_cycle);
        Serial.print("%");
        Serial.println(channels[i].enabled ? " [运行]" : " [停止]");
    }
}

void displayHelp() {
    Serial.println("\n=== 命令列表 ===");
    Serial.println("s - 显示状态");
    Serial.println("1-6 - 选择通道");
    Serial.println("f [频率] - 设置频率");
    Serial.println("d [占空比] - 设置占空比");
    Serial.println("e - 启用/禁用");
    Serial.println("h - 显示帮助");
}

void setChannel(int ch) {
    if(ch < 0 || ch >= num_channels) return;
    
    Serial.print("\n设置通道");
    Serial.println(ch+1);
    Serial.println("输入频率(Hz): ");
    
    while(!Serial.available());
    float freq = Serial.parseFloat();
    
    Serial.println("输入占空比(%): ");
    while(!Serial.available());
    float duty = Serial.parseFloat();
    
    setPWMFrequency(channels[ch].pin, freq);
    setPWMDuty(channels[ch].pin, duty);
    
    channels[ch].frequency = freq;
    channels[ch].duty_cycle = duty;
    
    Serial.print("通道");
    Serial.print(ch+1);
    Serial.print("已设置为: ");
    Serial.print(freq);
    Serial.print("Hz, ");
    Serial.print(duty);
    Serial.println("%");
}

3. 51单片机实现方案(软件PWM)

/**
  * 51单片机软件PWM实现
  * MCU: AT89C51/STC89C52
  * 晶振: 11.0592MHz
  * 可实现4路独立PWM
  */

#include <reg52.h>
#include <intrins.h>

// 引脚定义
sbit PWM1 = P1^0;  // PWM通道1
sbit PWM2 = P1^1;  // PWM通道2  
sbit PWM3 = P1^2;  // PWM通道3
sbit PWM4 = P1^3;  // PWM通道4

sbit KEY_UP = P3^2;    // 增加占空比
sbit KEY_DOWN = P3^3;  // 减少占空比
sbit KEY_CH = P3^4;    // 切换通道
sbit KEY_FREQ = P3^5;  // 切换频率

// LCD1602引脚定义(可选,用于显示)
sbit LCD_RS = P2^0;
sbit LCD_RW = P2^1;
sbit LCD_EN = P2^2;
#define LCD_DATA P0

// PWM结构体
typedef struct {
    unsigned char duty;     // 占空比 0-100
    unsigned int high_time; // 高电平时间
    unsigned int low_time;  // 低电平时间
    unsigned int counter;   // 计数器
    bit output;            // 当前输出状态
    bit enabled;           // 使能状态
} PWM_Channel;

PWM_Channel pwm[4];
unsigned char current_channel = 0;
unsigned int pwm_period = 1000;  // 周期1000us = 1KHz
unsigned char frequencies[] = {100, 200, 500, 1000, 2000};  // 频率选项(Hz)
unsigned char freq_index = 2;  // 默认500Hz

// 函数声明
void Timer0_Init(void);
void PWM_Init(void);
void PWM_Update(void);
void Key_Scan(void);
void LCD_Init(void);
void LCD_Write_Cmd(unsigned char cmd);
void LCD_Write_Data(unsigned char dat);
void LCD_Show_String(unsigned char x, unsigned char y, unsigned char *str);
void LCD_Show_Value(unsigned char x, unsigned char y, unsigned int value);
void Delay_ms(unsigned int ms);

void main(void) {
    Timer0_Init();
    LCD_Init();
    PWM_Init();
    
    // 显示初始信息
    LCD_Show_String(0, 0, "PWM Generator");
    LCD_Show_String(0, 1, "CH:1 500Hz 50%");
    
    while(1) {
        Key_Scan();
        // 主循环可以添加其他功能
    }
}

/**
  * 定时器0初始化
  * 11.0592MHz,定时50us
  */
void Timer0_Init(void) {
    TMOD &= 0xF0;  // 设置定时器0模式1
    TMOD |= 0x01;
    
    // 50us定时
    // 11.0592MHz / 12 = 921600Hz
    // 定时50us需要计数: 50us * 921.6KHz ≈ 46
    TH0 = (65536 - 46) / 256;
    TL0 = (65536 - 46) % 256;
    
    ET0 = 1;  // 允许定时器0中断
    TR0 = 1;  // 启动定时器0
    EA = 1;   // 开总中断
}

/**
  * PWM初始化
  */
void PWM_Init(void) {
    unsigned char i;
    
    for(i = 0; i < 4; i++) {
        pwm[i].duty = 50;  // 默认50%占空比
        pwm[i].enabled = 1;
        
        // 计算高电平和低电平时间
        pwm_period = 1000000 / frequencies[freq_index];  // 周期(us)
        pwm[i].high_time = (pwm_period * pwm[i].duty) / 100;
        pwm[i].low_time = pwm_period - pwm[i].high_time;
        pwm[i].counter = 0;
        pwm[i].output = 0;
    }
    
    // 设置PWM引脚为输出
    PWM1 = 0;
    PWM2 = 0;
    PWM3 = 0;
    PWM4 = 0;
}

/**
  * 定时器0中断服务函数
  * 50us中断一次,用于PWM生成
  */
void Timer0_ISR(void) interrupt 1 {
    static unsigned int timer_counter = 0;
    
    // 重装初值
    TH0 = (65536 - 46) / 256;
    TL0 = (65536 - 46) % 256;
    
    timer_counter++;
    
    // 每50us更新一次PWM
    if(timer_counter >= 20) {  // 20*50us = 1ms
        timer_counter = 0;
        PWM_Update();
    }
}

/**
  * 更新PWM输出
  */
void PWM_Update(void) {
    unsigned char i;
    
    for(i = 0; i < 4; i++) {
        if(!pwm[i].enabled) {
            // 通道禁用,输出低电平
            switch(i) {
                case 0: PWM1 = 0; break;
                case 1: PWM2 = 0; break;
                case 2: PWM3 = 0; break;
                case 3: PWM4 = 0; break;
            }
            continue;
        }
        
        pwm[i].counter++;
        
        if(pwm[i].output) {
            // 当前是高电平
            if(pwm[i].counter >= pwm[i].high_time) {
                pwm[i].output = 0;
                pwm[i].counter = 0;
                
                // 输出低电平
                switch(i) {
                    case 0: PWM1 = 0; break;
                    case 1: PWM2 = 0; break;
                    case 2: PWM3 = 0; break;
                    case 3: PWM4 = 0; break;
                }
            }
        } else {
            // 当前是低电平
            if(pwm[i].counter >= pwm[i].low_time) {
                pwm[i].output = 1;
                pwm[i].counter = 0;
                
                // 输出高电平
                switch(i) {
                    case 0: PWM1 = 1; break;
                    case 1: PWM2 = 1; break;
                    case 2: PWM3 = 1; break;
                    case 3: PWM4 = 1; break;
                }
            }
        }
    }
}

/**
  * 设置PWM频率
  */
void Set_PWM_Frequency(unsigned char freq_hz) {
    unsigned char i;
    
    pwm_period = 1000000 / freq_hz;  // 周期(us)
    
    for(i = 0; i < 4; i++) {
        pwm[i].high_time = (pwm_period * pwm[i].duty) / 100;
        pwm[i].low_time = pwm_period - pwm[i].high_time;
        pwm[i].counter = 0;
    }
}

/**
  * 设置PWM占空比
  */
void Set_PWM_Duty(unsigned char ch, unsigned char duty) {
    if(ch >= 4) return;
    if(duty > 100) duty = 100;
    
    pwm[ch].duty = duty;
    pwm[ch].high_time = (pwm_period * duty) / 100;
    pwm[ch].low_time = pwm_period - pwm[ch].high_time;
    pwm[ch].counter = 0;
    
    // 更新显示
    LCD_Show_String(0, 1, "CH:");
    LCD_Write_Data(current_channel + 1 + '0');
    LCD_Show_String(4, 1, "Hz ");
    LCD_Show_Value(7, 1, frequencies[freq_index]);
    LCD_Show_String(11, 1, "  ");
    LCD_Show_Value(11, 1, duty);
    LCD_Show_String(13, 1, "% ");
}

/**
  * 按键扫描
  */
void Key_Scan(void) {
    static bit key_up_flag = 0;
    static bit key_down_flag = 0;
    static bit key_ch_flag = 0;
    static bit key_freq_flag = 0;
    
    if(!KEY_UP) {
        if(!key_up_flag) {
            key_up_flag = 1;
            Delay_ms(10);
            if(!KEY_UP) {
                // 增加占空比
                if(pwm[current_channel].duty < 100) {
                    Set_PWM_Duty(current_channel, pwm[current_channel].duty + 1);
                }
            }
        }
    } else {
        key_up_flag = 0;
    }
    
    if(!KEY_DOWN) {
        if(!key_down_flag) {
            key_down_flag = 1;
            Delay_ms(10);
            if(!KEY_DOWN) {
                // 减少占空比
                if(pwm[current_channel].duty > 0) {
                    Set_PWM_Duty(current_channel, pwm[current_channel].duty - 1);
                }
            }
        }
    } else {
        key_down_flag = 0;
    }
    
    if(!KEY_CH) {
        if(!key_ch_flag) {
            key_ch_flag = 1;
            Delay_ms(10);
            if(!KEY_CH) {
                // 切换通道
                current_channel = (current_channel + 1) % 4;
                
                // 更新显示
                LCD_Show_String(0, 1, "CH:");
                LCD_Write_Data(current_channel + 1 + '0');
                LCD_Show_String(4, 1, "Hz ");
                LCD_Show_Value(7, 1, frequencies[freq_index]);
                LCD_Show_String(11, 1, "  ");
                LCD_Show_Value(11, 1, pwm[current_channel].duty);
                LCD_Show_String(13, 1, "% ");
            }
        }
    } else {
        key_ch_flag = 0;
    }
    
    if(!KEY_FREQ) {
        if(!key_freq_flag) {
            key_freq_flag = 1;
            Delay_ms(10);
            if(!KEY_FREQ) {
                // 切换频率
                freq_index = (freq_index + 1) % 5;
                Set_PWM_Frequency(frequencies[freq_index]);
                
                // 更新显示
                LCD_Show_String(0, 1, "CH:");
                LCD_Write_Data(current_channel + 1 + '0');
                LCD_Show_String(4, 1, "Hz ");
                LCD_Show_Value(7, 1, frequencies[freq_index]);
                LCD_Show_String(11, 1, "  ");
                LCD_Show_Value(11, 1, pwm[current_channel].duty);
                LCD_Show_String(13, 1, "% ");
            }
        }
    } else {
        key_freq_flag = 0;
    }
}

/**
  * LCD1602初始化
  */
void LCD_Init(void) {
    LCD_Write_Cmd(0x38);  // 8位数据,2行显示,5×7点阵
    Delay_ms(5);
    LCD_Write_Cmd(0x0C);  // 开显示,不显示光标
    Delay_ms(5);
    LCD_Write_Cmd(0x06);  // 写入后地址自动加1
    Delay_ms(5);
    LCD_Write_Cmd(0x01);  // 清屏
    Delay_ms(5);
}

void LCD_Write_Cmd(unsigned char cmd) {
    LCD_RS = 0;
    LCD_RW = 0;
    LCD_EN = 0;
    LCD_DATA = cmd;
    Delay_ms(1);
    LCD_EN = 1;
    Delay_ms(1);
    LCD_EN = 0;
}

void LCD_Write_Data(unsigned char dat) {
    LCD_RS = 1;
    LCD_RW = 0;
    LCD_EN = 0;
    LCD_DATA = dat;
    Delay_ms(1);
    LCD_EN = 1;
    Delay_ms(1);
    LCD_EN = 0;
}

void LCD_Show_String(unsigned char x, unsigned char y, unsigned char *str) {
    unsigned char i = 0;
    
    if(y == 0) {
        LCD_Write_Cmd(0x80 + x);
    } else {
        LCD_Write_Cmd(0xC0 + x);
    }
    
    while(str[i] != '\0') {
        LCD_Write_Data(str[i]);
        i++;
    }
}

void LCD_Show_Value(unsigned char x, unsigned char y, unsigned int value) {
    unsigned char str[6];
    unsigned char i = 0;
    
    if(value == 0) {
        str[0] = '0';
        str[1] = '\0';
    } else {
        while(value > 0) {
            str[i++] = value % 10 + '0';
            value /= 10;
        }
        str[i] = '\0';
        
        // 反转字符串
        for(int j = 0; j < i/2; j++) {
            unsigned char temp = str[j];
            str[j] = str[i-1-j];
            str[i-1-j] = temp;
        }
    }
    
    LCD_Show_String(x, y, str);
}

void Delay_ms(unsigned int ms) {
    unsigned int i, j;
    for(i = 0; i < ms; i++) {
        for(j = 0; j < 114; j++);
    }
}

参考代码 单片机产生占空比可调方波(PWM) www.youwenfan.com/contentcst/134336.html

性能参数对比

实现方式 频率范围 占空比精度 通道数 CPU占用 适用场景
STM32硬件PWM 1Hz-72MHz 16-bit(0.0015%) 最多12路 很低 电机控制、LED调光
Arduino硬件PWM 30Hz-62KHz 8-bit(0.4%) 6路 简单控制、舵机
51软件PWM 1Hz-2KHz 8-bit(0.4%) 不限 简单应用、学习
ESP32硬件PWM 1Hz-40MHz 16-bit(0.0015%) 16路 很低 IoT设备、复杂控制

实用电路设计

1. PWM信号调理电路

PWM输出 → 电压跟随器 → 低通滤波 → 功率放大 → 负载

2. 典型应用电路

; LED调光电路
VCC ──┬── LED ──┬── 限流电阻 ── PWM引脚
      │         │
     电容      NPN三极管
      │         │
     GND       GND

; 电机驱动电路
PWM ── 74HC14(施密特触发) ── IR2104(半桥驱动) ── MOSFET ── 电机

应用实例

1. LED呼吸灯

// STM32实现呼吸灯
void Breath_LED(TIM_HandleTypeDef* htim, uint32_t channel) {
    static uint16_t pwm_val = 0;
    static int8_t dir = 1;
    
    pwm_val += dir;
    
    if(pwm_val >= 10000) dir = -1;
    if(pwm_val <= 0) dir = 1;
    
    __HAL_TIM_SET_COMPARE(htim, channel, pwm_val);
    HAL_Delay(1);
}

2. 舵机控制

// 舵机角度控制 (0-180度对应0.5ms-2.5ms脉冲)
void Servo_Set_Angle(TIM_HandleTypeDef* htim, uint32_t channel, uint8_t angle) {
    // 舵机控制信号: 周期20ms, 高电平0.5ms-2.5ms
    uint32_t pulse = 500 + (angle * 2000 / 180);  // 转换为us
    
    // 假设定时器频率为1MHz
    __HAL_TIM_SET_COMPARE(htim, channel, pulse);
}

3. 直流电机调速

// 电机正反转和调速
typedef enum {
    MOTOR_STOP = 0,
    MOTOR_FORWARD,
    MOTOR_BACKWARD
} Motor_Direction;

void Motor_Control(TIM_HandleTypeDef* htim_pwm, uint32_t pwm_channel,
                   GPIO_TypeDef* dir_port, uint16_t dir_pin1, uint16_t dir_pin2,
                   Motor_Direction dir, uint16_t speed) {
    // 设置方向
    if(dir == MOTOR_FORWARD) {
        HAL_GPIO_WritePin(dir_port, dir_pin1, GPIO_PIN_SET);
        HAL_GPIO_WritePin(dir_port, dir_pin2, GPIO_PIN_RESET);
    } else if(dir == MOTOR_BACKWARD) {
        HAL_GPIO_WritePin(dir_port, dir_pin1, GPIO_PIN_RESET);
        HAL_GPIO_WritePin(dir_port, dir_pin2, GPIO_PIN_SET);
    } else {
        HAL_GPIO_WritePin(dir_port, dir_pin1, GPIO_PIN_RESET);
        HAL_GPIO_WritePin(dir_port, dir_pin2, GPIO_PIN_RESET);
    }
    
    // 设置速度
    __HAL_TIM_SET_COMPARE(htim_pwm, pwm_channel, speed);
}

调试技巧

  1. 示波器使用:观察PWM波形,测量频率和占空比
  2. 万用表测量:测量平均电压验证占空比
  3. 软件调试:通过串口输出当前PWM参数
  4. 逐步调整:从低频开始测试,逐步提高频率

专注于matlab/simulink,电子电路,编程