单片机通识之PWM
- PWM是什么
- PWM可以实现什么
PWM是什么
PWM,即脉宽调制(PWM,Pulse Width Modulation),在这篇文章中,暂时将之简单的理解为: 在一段时间内,控制单片机输出高低电平的手段。
PWM可以实现什么
在单片机中,经常用PWM实现变速马达,渐变灯,这两者均是通过控制一段时间内单片机输出高电平或低电平而实现想要的功能,以渐变灯为例。
假如现需要一个LED灯以 90HZ 的频率,在 3.5S 内从灭到亮,单片机的中断周期为 32.5us,假使LED为高推(高电平点亮)。
当看到这个题目时,首先要想到的是 需要用PWM实现。目前市面上的单片机多数都提供了若干通道的硬件PWM,我们仅需要设定寄存器就可以实现我们所需的功能,但这篇文章,要讲解 软件 实现PWM。这就需要引入 占空比 和 周期计数 两个概念。
- 如何理解占空比这一概念?首先要明白它是一个 比例,即在 一个周期(稍后会讲解什么是一个周期)内,电路导通时间 占整段时间的比值,假如一个周期是17ms,电路导通时间是2ms,那么就可以说它的占空比是 2/17 ≈ 11.8% 。一个周期是由给定的频率得来,题目中要求LED以 90HZ 的频率渐变,也就是告诉我们,LED灯的周期为 1/90 ≈ 11ms。
- 其次是 周期计数 这一概念,既然占空比和周期有关,那么就需要有一个变量来记录 这一时刻 处于这一周期的哪个位置。周期计数可参考
Period_cnt = T周期 / T中断周期 = 11ms / 32.5us ≈ 338 次中断。
接着来分析这一题目。90HZ 的频率决定了LED灯的周期,频率影响着灯变化的细腻程度,频率越高,人眼看到灯的变化就越柔和。回想在文章开头提到PWM的概念: 在一段时间内,控制单片机输出高低电平的手段。 既然有提到一段时间内,那么我们就要想,是哪一段时间?又根据什么判断应该 输出高 还是 输出低 ?
- 一段时间的解释: 在上文中,我们得知LED的周期是11ms,即11ms 就是这一段时间。题目中将要在3.5s内从灭到亮,这3.5s包含着 3.5s / 11ms ≈ 318 个11 ms,也就意味着,我们需要在这318个11ms中,控制单片机输出高电平还是低电平。
- 根据什么判断的解释: 上文中有解释占空比这一概念,也就意味着,我们需要在3.5s内,让占空比从0逐渐增加到100,也就是题目中提到的渐变。占空比可以参考 下方公式,也就是说,从灭到亮的3.5s中,占空比需要从0增加至338。
Duty = T周期 / T中断周期 = 11ms / 32.5us ≈ 338
在算出占空比后,我们还需要明白一件事情,即: 中断多少次,占空比增加1?可参考 下方公式,即每中断318次,占空比 + 1。
Duty_interrupt = T渐变时长 / T中断周期 / CNT占空比 = 3.5s / 32.5us / 338 ≈ 318
至此,我们已然把计算工作做完,接下来,就是利用代码实现PWM。在代码中会详细讲解 占空比及周期计数的关系。
/*
PWM本质上是利用周期计数和占空比相比较, 从而输出高电平 或者 低电平(假若高电平驱动外部设备)
假如说一个周期是100ms, 要求占空比是20%, 那么在一个周期内的 0-20ms 间要输出高电平, 20 - 100ms
要输出低电平. 在此后的若干个周期内按此循环。
在该文章的题目中, 要求LED渐亮, 所以占空比要逐渐的增加.
*/
struct PWM {
unsigned int duty; // 记录当前的占空比, 范围: 0 - 338
// duty_interrupt 和 period_cnt 均为每中断一次就 +1
// 记录中断了多少次, 每中断 318 次清零, duty ++
unsigned int duty_interrupt;
// 周期计数, 中断 338 次代表一个周期到, 故每338次中断需要清零
unsigned int period_cnt;
} pwm;
void main() {
while (1) {
// Your code
}
}
// void interrupt 为 32.5us 中断函数
void interrupt() {
// 每次进入中断,
pwm.duty_interrupt++;
pwm.period_cnt++;
// 每中断 318 次, 占空比 ++, 同时 duty_interrupt 清零
if (pwm.duty_interrupt >= 318) {
pwm.duty_interrupt = 0;
pwm.duty++;
}
// 中断338次表示一个周期到, 清零
if (pwm.period_cnt >= 338) {
pwm.period_cnt = 0;
}
// 周期计数大于占空比, 熄灭LED; 否则点亮LED
if (pwm.period_cnt >= pwm.duty) {
// PA1 为控制LED的IO口
PA1 = 0;
}
else {
PA1 = 1;
}
}
至此,就已经完成了软件实现PWM。若文章没有讲清楚,可通过以下方式联系共同探讨。
计算占空比、周期计数的工具: https://www.aliyundrive.com/s/sMEpMxf6D6y