单片机产生占空比可调方波(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);
}
调试技巧
- 示波器使用:观察PWM波形,测量频率和占空比
- 万用表测量:测量平均电压验证占空比
- 软件调试:通过串口输出当前PWM参数
- 逐步调整:从低频开始测试,逐步提高频率