PWM(Pulse Width Modulation)是一种方波控制信号。采用不同的占空比来模拟“模拟输出”。基本的PWM信号如下图所示:
周期
Ton 表示导通时间,Toff 表示信号的关断时间。周期是导通和关断时间的总和,并按照以下公式计算:
占空比
占空比用于计算为一段时间的导通时间。使用上面计算的周期,占空比计算为:
常见的占空比如下图所示:
1、analogWrite(pin,dutyCycle)
由1.3的图可知,在 UNO 中3,5,6,9,10,11 接口可以通过简单语句 analogWrite(pin, dutyCycle)
来实现一个指定占空比的 PWM。其中 pin 的值选择(3,5,6,9,10,11),dutyCycle 的值在0~255之间,0为占空比0%,255为占空比100%,对应电压从0到+5V。在调用 analogWrite()
函数之后,引脚将产生指定占空比的稳定方波,直到下一次调用 analogWrite()
或在相同引脚上调用 digitalRead()
或 digitalWrite()
。但是这种方式 PWM 信号的频率是固定的默认值,大多数引脚上的 PWM 信号频率约为490 Hz。在 Uno 和类似的板上,引脚5和6的频率约为980Hz。Leonardo上的引脚3和11也以980Hz运行。提示:在使用PWM时,可以使用示波器检测一下频率。
示例代码:
// 引脚命名
# define analogPin 3
void setup()
{
pinMode(analogPin,OUTPUT);
}
void loop()
{
analogWrite(analogPin,100); // 输出PWM,占空比为 100/255
}
2、手动实现 PWM
通过 delayMicroseconds()
手动实现频率可调的 PWM,也被称作数字IO轮转法,使用方法:
示例代码:
void setup()
{
pinMode(8, OUTPUT); // 设置8号引脚为输出模式
}
void loop()
{
digitalWrite(8, HIGH);
delayMicroseconds(100); // 输出PWM,占空比为100/1000=10%
digitalWrite(8, LOW);
delayMicroseconds(1000 - 100); // 修改这里的1000可以调整频率,总周期为1000us,所以频率为1000Hz.
}
上面这段代码会产生一个PWM=0.1的,周期为1ms的方波(1kHz),这种方式的优缺点很明显:
对 Arduino 时钟的介绍,请参考Arduino中断的使用。时钟一般可以有多种不同的运行模式。常见的模式包括“快速PWM”和“相位修正PWM”。
Arduino除了常用的比较匹配寄存器外,还有一些其他的寄存器用来控制时钟。
不同时钟的这些设置位稍有不同,所以使用的时候需要查一下资料。其中Timer1是一个16位的时钟,Timer2可以使用不同的预定标器。
1、快速PWM
对于快速 PWM 来说,时钟都是从0计数到255。当计数器=0时,输出高电平1,当计数器等于比较寄存器时,输出低电平0。所以输出比较器越大,占空比越高。这就是传说中的快速PWM模式。后面的例子会解释如何用OCRnA和OCRnB设置两路输出的占空比。很明显这种情况下,这两路输出的周期是相同的,只是占空比不同。
参考示例
下面这个例子以 Timer2 为例,把 Pin3 和 Pin11 作为快速PWM的两个输出管脚。其中:
pinMode(3, OUTPUT);
pinMode(11, OUTPUT);
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
TCCR2B = _BV(CS22);
OCR2A = 180;
OCR2B = 50;
// _BV(n)的意思就是1< COM2A1,表示COM2A的第1位为1,由于寄存器一般是倒序,最后一位为0,所以_BV(COM2A1)表示COM2A = 10
在Arduino Due 开发板,上面这几行代码的结果为:
频率的计算里都除以了256,这是因为除以64是得到了时钟的计数周期,而256个计数周期是一个循环,所以PWM的周期指的是这个循环。另外,占空比的计算都加了1,这个是因为从0开始计数。
2、相位修正PWM
另外一种 PWM 模式是相位修正模式,也有人把它叫做“双斜率PWM”。这种模式下,计数器从0数到255,然后从255再倒数到0。当计数器在上升过程中遇到比较器的时候,输出0;在下降过程中遇到比较器的时候,输出1。
相位修正PWM的例子
继续以 Timer2 为例,设置 Pin3 和 Pin11 为输出管脚。其中WGM设置为001,表示相位修正模式,其他位设置和前面的例子相同:
pinMode(3, OUTPUT);
pinMode(11, OUTPUT);
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
TCCR2B = _BV(CS22);
OCR2A = 180;
OCR2B = 50;
在Arduino Due开发板,上面这几行代码的结果为:
**提示:**与快速 PWM 相比,相位校正 PWM 将频率除以2,因为定时器同时上下运行。有些令人惊讶的是,频率被255除,而不是256除,占空比计算不加一作为快速脉宽调制。
一般来说,普通用户是不需要设置这些时钟参数。Arduino 默认有一些设置,所有的时钟周期都是系统周期的1/64。Timer0 默认是快速 PWM,而 Timer1 和 Timer2 默认是相位修正 PWM。具体的设置可以查看Arduino源代码中writing.c的设置。
需要特别特别注意的是,Arduino的开发系统中,millis()和delay()这两个函数是基于Timer0时钟的,所以如果你修改了Timer0的时钟周期,这两个函数也会受到影响。直接的效果就是delay(1000)不再是标准的1秒,也许会变成1/64秒,这个需要特别注意。
如果想要修改时钟频率,以及时钟的计数上限,请参考文章后面的链接。