analogWrite怎么做PWM输出?改PWM频率(定时器相关)


原文繁体,不易阅读,这里转换为简体并修改了格式,原文地址:
https://www.arduino.cn/thread-12906-1-1.html
大家都知道在 Arduino UNO 有六支 pin 可以使用 analogWrite( )做 PWM 输出, 在板子上 pin 旁边标示有 “~” 符号, analogWrite( ) 可以用来产生仿真电压, 很多人一定很好奇那是怎么做到的 ?

1. 首先来看看 PWM 仿真电压的原理, 这在官网上就有解说了:

http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM
所谓的 PWM 全称是 Pulse-Width Modulation (PWM), 其实这也没啥学问, 就是对 GPIO 脚位不断的切换 “有电” “没电”, 每秒钟循环几次即为其 Frequency(频率), 每次"有电"时间占一个循环的百分比称为其占空比(Duty cycle);

1.1 官网上的仿真范例:

int pin = 13;
void setup() {
  pinMode(pin, OUTPUT);
}
void loop(){
  digitalWrite(pin, HIGH);
  delayMicroseconds(100); // Approximately 10% duty cycle @ 1KHz
  digitalWrite(pin, LOW);
  delayMicroseconds(1000 - 100);
}

这个范例中, 一个循环是 1000 us = 1ms, 所以一秒循环 1000次, 因此 Frequency 是 1 KHz,
每个循环中, 有电的比率是 100/1000 * 100% = 10%, 所以 duty cycle (占空比)为 10%;
这样就可以模拟出 5Volt x 10% = 0.5 Volt 的电压!

1.2 如果真的这样做, 有好处也有坏处, 官网上已经说了:

  1. 好处是任一支 pin 都可这样用, 包括 Pint 0 到 Pin 13, 以及 Pin A0 到 A5 共 20支 pin 都可以!

  2. 坏处却更多, 首先就是频率(Frequency)和占空比(duty cycle)可能受中断(Interrup)的影响变成不是很准确 !

  3. 最大的坏处是, 在某支 pin 做 PWM 输出期间都没办法做别的事情 !
    既然说这只是示范可以这样做, 在 Arduino 当然不可能是这么做,

2. 那 Arduino 是怎么做的呢?

2.1 通过 Timer 定时器直接控制 pin 做 PWM 输出, Arduino UNO 的 MCU 有三个 timer,

  • timer0 控制 pin 5, pin 6;
  • timer1 控制 pin 9, pin 10;
  • timer 2 控制 pin 11, pin 3;

我们可以对这些 pin 用 analogWrite(pin, val); 输出 0 到 255 的 val 值到 pin ;

  1. 如果输出 val 是 0, 它会偷偷直接改用 digitalWrite(pin, 0); 输出,
  2. 如果 val 是 255, 也是会偷偷直接改用 digitalWrite(pin, 1); 输出!
  3. 如果 val 是 1 到 254, 则会下命令请 pin 脚对应的 timer 定时器(定时器)帮忙!
    How ?

2.1 timer 的基本知识:

  1. 每个 timer 一定有个 counter, 例如 timer0 的TCNT0, timer1 的TCNT1, timer2 的TCNT2;
    该 counter 一定是每个 tick 会加 1, 每个 tick 通常是把 CPU 的 clock 拿来经过一个除频电路, 然后给 timer 使用; Arduino UNO 采用 AVR ATmega328 MCU, 且 clock Rate 是 16MHz,
    每个 timer 的除频 Prescaler 是独立设定的, 通常可以设 1, 2, 4, 8, 64, 256, or 1024 等, 这必须看 MCU 的 datasheet.

  2. 每个 timer 通常提供许多 mode 运作模式, 例如 counter溢出(Overflow)或Rollover归零时产生中断, 或TCNT(0,1,2) 达到某个值时产生中断等, Arduino ATmega328 的 timer 有 16种 mode, 许多 Mode 是与 PWM 有关;
    要设定 timer 的 Mode 可以透过修改 timer 的控制缓存器, 例如 TCCR?A, TCCR?B, 注意以 ATmega328 为例, TCCR?A 和 TCCR?B 要合起来用, 此处的 A, B 与 channel A, channel B 无关!

  3. 每个 timer 通常有比较缓存器(Compare Register), 当 TCNT? 值与该些比较缓存器相同时可以做某事, 不一定是对 CPU 产生中断! Arduino 每个 timer 有两个比较缓存器, 分别命名 OCR?AOCR?B,

其中 ? 是 0, 1, 2 分别对应到 timer0, timer1, 和 timer2 这三个定时器.

你可以先偷看 analogWrite( ) 的程序码: 在你 Arduino IDE 下的 hardware\arduino\cores\arduino\wiring_analog.c

2.2 analogWrite( ) 真正请 timer 帮忙只做三件事:

  1. 找出对应的 port,
  2. 设定控制缓存器,
  3. 填入 analog的值到比较缓存器!

不过你会发现看不太懂, 因为还不知道硬件 timer 控制 PWM 运作方式与原理!
不想看 datasheet 可以参考这:
http://letsmakerobots.com/content/arduino-101-timers-and-interrupts

2.2 硬件 timer 控制 PWM 运作方式与原理

Arduino UNOtimer1为例, 在 mode 5 (Fast PWM, 8 bit), 此时, TCNT1 从 0 数到 255, 通常从 255 (此 mode 的最大值)又加 1 变为 0 之时会产生 OVF 中断(TIMSK1的TOIE1要 set), 不过这与 PWM 无关!
PWM 不是用 Interrupt 中断请求做的, 不必麻烦 CPU, CPU 只要下命令给 timer, timer 就会照命令执行PWM工作 !

PWM 是利用每个 timer 上的两个"匹配符合输出"缓存器(Compare Match Output) COM?ACOM?B;
(注意虽是 Compare Match Output, 但缓存器名称是 COMxy 不是 CMOxy 喔 !)

在timer1 的 mode 5, 又称 Fast PWM mode, (不过请注意 Arduino 的 init( ) 设定只有 timer0 用这, 另外 timer1 和 timer2 不是用这 mode),

这时可以把 1 到 254 之间的值放入 OCR1AOCR1B 以便控制 pin 9 或 pin 10的 PWM duty cycle, 1 到 254 分别对应到 (1+1)/256, …, (254+1)/256 的 duty cycle. +1 是硬件电路设计上的关系, data sheet 上说:
Note that fast PWM holds the output high one cycle longer than the compare register value.
TCNT1 等于 0 之时, COM1A and/or COM1B 会输出(当然要 TCCR1A 内的 COM1A1 and/or COM1B1 有set),
然后在 TCNT1 等于 OCR1A 则关闭 COM1A, 当 TCNT1 等于 OCR1B 则关闭 COM1B,

注意没有立即关闭, 是延迟一个 tick 才关闭 ! 所以才会多加1, 因为一个循环是 256, 不是 255,如果不延迟加 1, 则输出 val 是 254 时变成 254/256, 还差一点点, 所以牺牲 1/256, 就是没有 1/256占空比 !

由于 Arduino 的 init( )把 timer1 的 Prescaler 设定为 64,
(参考在你 Arduino IDE 内的 hardware\arduino\cores\arduino\wiring.c )
且把 timer1 设定为 8-bit phase correct pwm mode, 所以其频率是 490.196Hz, 不是 976.5625Hz;
所谓的 8-bit phase correct pwm mode, 意思是 TCNT? 从 0 数到 255, 接着又从 255 倒着数回 0,
那何时把 COM1A and/or COM1B 的输出打开或关闭呢?
根据 datasheet, 在从 0 往上数, 碰到 OCR1A 时把 COM1A 关闭,
然后从 255 往回数, 数到 OCR1A 时把 COM1A 打开(有电); 对于 OCR1B 和 COM1B 也是这样!
这使得 duty cycle (占空比) 更准确, 也就是 val 1 ~ 254 分别对应到 1/255 到 254/255 的 duty cycle.
但是 Frequency 则不是除以 256, 是要除以 255 再除以 2, 于是: (注意是 255, 不是 256喔!)
Frequency = 16 MHz / 64 / 255 / 2 = 490.196Hz;

timer 2 也是在 init( )被设为 Prescaler 64 的 phase correct pwm (8-bit);
但是, timer0 虽然 Prescaler 也设 64, 但 PWM 是用 Fast PWM mode,
不使用 phase correct mode 是为了避免影响维护 millis( ) 的中断 timer0 Overflow Interrupt,即 ISR(TIMER0_OVF_vect) 这中断处理程序, 否则 millis( ) 和 micros( ) 以及 delay() 都会受到影响 !
因此 , timer0 控制的 PWM 其 Frequency 是 976.5625Hz,
16 MHz / 64 / 256 = 976.5625Hz
注意用 timer0 控制的pin 5, pin 6 之 PWM 的 duty cycle 无法是 1/256, 它是 0 再来就 2/256了!
结论:
timer0 控制 pin 5, pin 6, PWM 频率 976.5625Hz, duty cycle可以 2/256 ~ 255/256 (对应 1 到254);
timer1 控制 pin 9, pin 10, PWM 频率 490.196Hz, duty cycle 可以 1/255 ~ 254/255(对应 1 到254);
timer2 控制 pin 11, pin 3, PWM 频率 与 duty cycle 跟 timer1 控制的相同 !

Q: 那 PWM 的 Frequency 可不可以更改?
A: 可以, 偷改 timer 的 Prescaler 就可以达到更改 Frequency 的目的 !
但是, 千万不要更改 timer0 的 Prescaler, 否则 millis( ) 和 micros( ) 以及 delay() 都会受到影响 !!
以下是以 timer1 控制的 pin 9, pin 10 为例(注意两个 pin 的频率相同!)
在你的 setup( ) { 内, 写如下两句即可:
int fff = 3; // 可以是 1, 2, 3, 4, 5
TCCR1B = TCCR1B & 0xF8 | ?;
其中 fff 与对应频率如下:

Prescaler Frequency
1 1 31372.549 Hz
2 8 3921.569
3 64 490.196 <–DEFAULT
4 256 122.549
5 1024 30.637 Hz

至于 timer2 控制的 pin 11 和 pin 3,
则在 setup( ) { 内写:
TCCR2B = TCCR2B & 0xF8 | ?;
此处的 ? 可以有七种:

? Prescaler Frequency
1 1 31372.549 Hz
2 8 3921.569
3 32 980.392
4 64 490.196 <–DEFAULT
5 128 245.098
6 256 122.549
7 1024 30.637 Hz

如果你坚持要改 timer0 的 Prescaler, 以更改 pin 5, pin 6 的 PWM 频率:
(注意 millis( ) 和 micros( ) 以及 delay() 都会受到影响 ! )
则在 setup( ) { 内写:
TCCR0B = TCCR0B & 0xF8 | ?;
此处的 ? 可以有五种:

? Prescaler Frequency
1 1 362500 Hz
2 8 7812.5
3 64 976.5625 <–DEFAULT
4 256 244.140625
5 1024 61.03515625 Hz

参考:
http://playground.arduino.cc/Main/TimerPWMCheatsheet
http://www.atmel.com/Images/doc8161.pdf

猜你喜欢

转载自blog.csdn.net/acktomas/article/details/89282164
今日推荐