Arduino PWM 周波数 高速化
概要
ATmega PWMレジスタを直接使用する
ATmega168P/328P チップには 3 つの PWM タイマーがあり、6 つの PWM 出力を制御します。 (5番と6番, 9番と10番, 3番と11番ピン)。
チップのタイマー レジスタを直接操作することで、analogWrite関数が提供する以上の制御を得ることができます。
AVR ATmega328P データシートにはPWM タイマの詳細な説明が記載されていますが、タイマにはさまざまな制御および出力モードがあるため、データシートを理解するのが難しい場合があります。以下では、タイマーの使用法を明確にします。
ATmega328Pには、タイマ0、タイマ1、およびタイマ2として知られる3つのタイマがあります。各タイマには、タイマの2つの出力のPWM幅を制御する2つの出力比較レジスタがあります。タイマが比較レジスタ値に達すると、対応する出力がトグルされます。各タイマーの 2 つの出力は通常同じ周波数を持ちますが、それぞれの出力比較レジスタに応じて異なるデューティ サイクルを持つことができます 。
デフォルトの周波数は、
タイマー0の5番, 6番Pinは977Hz, タイマー1の9番,10番Pinは490Hz, タイマー2の3番,11番Pinも490Hzです.
各タイマーには、システム クロックを 1、8、64、256、または 1024 などのプリスケール係数で分周してタイマー クロックを生成するプリスケーラーがあります。Arduino のシステム クロックは 16MHz で、タイマー クロック周波数はシステムクロック周波数をプリスケール係数で割った値。タイマー 2 には他のタイマーとは異なるプリスケール値のセットがあることに注意してください。
タイマーはいくつかの異なるモードによって複雑になっています。主な PWM モードは、以下に説明する「高速 PWM」と「位相補正 PWM」です。タイマーは 0 ~ 255 の範囲で実行することも、0 ~ 固定値の範囲で実行することもできます。(16 ビット タイマ 1 には、最大 16 ビットのタイマ値をサポートする追加モードがあります。) 各出力は反転することもできます。
※タイマーは16ビットであるが Arduino Uno では8ビットで使用している。
「高速PWMモード(FAST PWM)」:
- PWM波形を出力する際に使用。
- ただしタイマー波形はノコギリ波ベースとなる
「位相基準PWMモード(Phase Correct PWM)」:
- こちらもPWM波形を出力する際に使用。
- ただしタイマー波形は三角波ベースとなる
分周比(プリスケーラ)は
6ピン5ピンがTCCR0B、
9ピン10ピンがTCCR1B、
3ピン11ピンがTCCR2B
で設定されています。
TCCR0BとTCCR1Bでは下位3ビットが001なら分周比1、010なら分周比8、011なら分周比64となります。
TCCR2Bでは下位3ビットが001なら分周比1、010なら分周比8、011なら分周比32、100なら分周比64となります。
タイマー0の高速PWMの実効周波数
$$\begin{gather} f_0=\frac{16[MHz]}{分周比 \times (TOP+1)} \tag1 \\ \text{ここにおいて、} TOP=255 \notag \end{gather}$$
タイマー1/2の8bit位相標準PWMの実効周波数
$$\begin{gather} f_0=\frac{16[MHz]}{分周比 \times TOP \times 2} \tag2 \\ \text{ここにおいて、} TOP=255 \notag \end{gather}$$
# 本文
PWMの動作原理
引用:PWM制御の仕組みと構成
Arduino Uno には PWM出力に使われる タイマ/カウンタ が 0,1,2 の三個 あります。
タイマ カウンタ コントロールによって、各レジスタの条件を設定する。
比較値のレジスタにデューティ比の値を入力する。
比較値の値とタイマカウンタの値を比較してパルス波形を生成する。
波形の生成は、4種類の波形生成モード中から選択できる。
1) Fast PWM
最も単純なPWMモードでは、タイマーは0から255まで繰り返しカウントします。OCRnAとOCRnBの2つの特定の値の出力を示しています。両方の出力の周波数は同じで、完全なタイマーサイクルの周波数と一致している
2) Phase-Correct
PWMタイマーは0から255までカウントし、その後カウントダウンしながら0に戻ります。二つの出力パルスは対称で、デューティ比が変化してもその中心は一致します。即ち波形の位相はズレません。出力周波数はFast PWMモードの値の約半分になります。
波形生成モードは、WGM02などの3つのビットの組み合わせで意味が変わります。下記はタイマー0と2の場合です。タイマー1はもう少し複雑です。参照
Mode | WGM02/WGM22 | WGM01/WGM21 | WGM00/WGM20 | 意味 | TOP |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 通常 | 0xFF |
1 | 0 | 0 | 1 | PWM、Phase Correct | 0xFF |
2 | 0 | 1 | 0 | CTC | OCR2A |
3 | 0 | 1 | 1 | Fast PWM | 0xFF |
4 | 1 | 0 | 0 | 予約 | - |
5 | 1 | 0 | 1 | PWM、Phase Correct | OCR2A |
6 | 1 | 1 | 0 | 予約 | - |
7 | 1 | 1 | 1 | Fast PWM | OCR2A |
PWM周波数の変更;高速化
個々のタイマーや波形生成モードを体系的に説明すると複雑過ぎて訳が分からないので、ネットで見つけた事例を載せることにします。
下記の引用例からレジスタを変更するときのビットの割り当ては次の記事に詳しく載っています。(今はわかるけど、日がたつと忘れてわからなくなると思います。)
引用:PWM制御の仕組みと構成
引用:Arduinoで遊ぶページ analogWrite()
Timer0 (D6, D5) Fast PWM モードの場合
このモードでの出力周波数は下記の数式で算出できます。
出力周波数 = システムクロック / (分周比 * (255+1)) = 16MHz / (64 * 256) ≒ 977Hz
デフォルトでは分周比 64 ですが、これを小さくすると周波数を高くすることができます。分周比を大きくして周波数を低くすることもできます。
CS02 | CS01 | CS00 | 意味 |
---|---|---|---|
0 | 0 | 0 | クロックなし |
0 | 0 | 1 | 分周比1 |
0 | 1 | 0 | 分周比8 |
0 | 1 | 1 | 分周比64 |
1 | 0 | 0 | 分周比256 |
1 | 0 | 1 | 分周比1024 |
1 | 1 | 0 | 外部クロック。立下りでオン。 |
1 | 1 | 1 | 外部クロック。立ち上がりでオン。 |
分周比はTCCR0Bの下位3ビットを使って指定します。
分周比 64(デフォルト)
//fast pwm setting
TCCR2B &= B11111000;
TCCR2B |= B00000011;
分周比 1(上表参照)
//fast pwm setting
TCCR2B &= B11111000;
TCCR2B |= B00000001;
分周比 1のとき,
出力周波数 = システムクロック / (分周比 * 256) = 16MHz / (1 * 256) ≒ 62.5kHz
// TIMER0はPD5(ArduinoのD5),PD6(ArduinoのD6)に対応
// OC0BがPD5,OC0AがPD6
void setup(){
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
TCCR0A = _BV(COM0A1)|_BV(COM0B1)|_BV(WGM01)|_BV(WGM00); // TCCR0A = B10100011;
TCCR0B = _BV(CS00); // TCCR0B = B00000001;
// D5,D6 を比較出力モードに設定
// fast PWM mode
// 動作クロックは16MHz
// PWMキャリア波の周波数は分周比1なので、16MHz/(1x (1+255))=62.5kHz(T=16us)
}
void loop(){
OCR0A = 127;// Duty=127/255=0.5 127までカウントアップするとLOWになる
OCR0B = 180;// Duty=180/255=0.7 180までカウントアップするとLOWになる
// OCR0AのCompare Match値を127にセット
// PD6ピンからvalue=127に対応するアナログ電圧(Unoでは5*127/255=2.49V)が出力される
// 同様に、OCR0Bを180にセット 3.5V出力
}
引用:Arduinoプログラムの高速化 を少し改変
Timer1 (D9, D10) Phase Correct PWM モードの場合
#include
#define PWMPin 10
unsigned int frq = 440; // 周波数
float duty = 0.5; // 指定したいデューティ比
void setup() {
pinMode(PWMPin, OUTPUT);
}
void loop() {
// モード指定
TCCR1A = 0b00100001;
TCCR1B = 0b00010010;
// TOP値指定
OCR1A = (unsigned int)(1000000 / frq);
// Duty比指定
OCR1B = (unsigned int)(1000000 / frq * duty);
}
引用元:Arduino Uno ? PWM周波数を”自由に”変更する
なぜか、TinkerCADでのシミュレーションが実行されない。(計算が始まらない)
Timer1 (D9, D10) Fastt PWM モードの場合
void setup () {
// Pin 9(OC1A),10(OC1B) is output
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
// Load 101 to generate 7.81KHz fast PWM
OCR1A= 127;
OCR1B= 101;
// inverted fast PWM on OC1A and OC1B with prescalar of 8
TCCR1A = (1<1
引用:Programming Arduino Timer 1 in Fast PWM mode
この引用記事には、Timer1 PWM の色々な設定例が載っています。
Timer2 (D11, D3) Fast PWM モードの場合
CS22 | CS21 | CS20 | 意味 |
---|---|---|---|
0 | 0 | 0 | クロックなし |
0 | 0 | 1 | 分周比1 |
0 | 1 | 0 | 分周比8 |
0 | 1 | 1 | 分周比32 |
1 | 0 | 0 | 分周比64 |
1 | 0 | 1 | 分周比128 |
1 | 1 | 0 | 分周比256 |
1 | 1 | 1 | 分周比1024 |
Timer2 WGMode 7 Fast PWM (TOP=OCR2A)の場合
//
// 38kHz to atmega328p D3
// based on https://gist.github.com/chendy/69454f6ec77b54f9a710
//
//
byte oscOut = 3 ; // Atmega328p OC2A=D11 ; OC2B=D3 ;
void setup(){
pinMode(oscOut, OUTPUT);
OCR2A = 51; // defines the frequency 51 = 38.4 KHz, 54 = 36.2 KHz, 58 = 34 KHz, 62 = 32 KHz
OCR2B = 26; // defines the duty cycle - Half the OCR2A value for 50%
TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); // TCCR2A = B00100011 COM2B1 (output to OC2B) ; WGMode 7 Fast PWM (part 1)
TCCR2B = _BV(WGM22) | _BV(CS21); // TCCR2B = B00001010 prescalere x8 ; WGMode 7 Fast PWM (part 1)
}
void loop(){ }
引用元:Atmega328p - timer2 - fastpwm mode
周波数は前述の(1)式より、次のように求められます。例えば、$TOP=51$ ならば、
Fast PWMの実効周波数
$$\begin{aligned} f_0&=\frac{16[MHz]}{分周比 \times (TOP+1)} \tag1 \\&=\frac{16[MHz]}{8 \times (51+1)} \\ &= 38.46 [kHz] \end{aligned}$$Dutyは、ひとつの周期の過程でTCNT2がOCR2A までカウントアップするときにOCR2B の値と一致すると出力がLOWになる作用です。 したがって、OCR2A 対する(整数)Duty比の割合として、OCR2B に設定します。(Half the OCR2A value for 50%)
これは、Uno クローンのピン OC2B (ピン D3) では正常に機能します。ただし、ピンを OC2A (D11) に変更し、設定レジスタ TCCR2A で COM2B1 を COM2A1 に置き換えると、機能しません。
この理由は、モード 7 はレジスタ OCR2A を使用して TOP を設定するため、そのモードではピン 11 を PWM にプログラムできません。
デューティをサイン波状に変化させる
- currentTime / 1000.0:現在の時間をミリ秒から秒に変換しています。currentTime は millis() 関数で取得した現在のミリ秒単位の時間です。
- * 2 * PI:1秒サイクルのサイン波を生成するために、時間をラジアンに変換しています。1周するのに必要な角度は2π(約6.283)です。
- sin():時間をラジアンに変換した値のサインを計算します。これにより、-1から1までの間を周期的に変化する値を得ることができます。
- map()関数が扱うのは整数の為、sin()を100倍して整数化しています。
- map():得られたサイン波の値を、0から255の範囲に変換します。map() 関数は、ある範囲の値を別の範囲に変換するために使われます。
- この場合、-100から100のサイン値*100 の値を0から255のPWMデューティサイクルに変換しています。
void setup(){
pinMode(3, OUTPUT);//R2B
pinMode(11, OUTPUT);//R2A
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);//TCCR2A = B10100011 WGMが011でfast PWM。COM2A,COM2Bが10で出力AとB に非反転 PWM。
TCCR2B = _BV(CS20);//TCCR2B = B00000010 8分周
}
void loop(){
unsigned long currentTime = millis();
int dutyCycleA = map(sin(currentTime / 1000.0 * 2 * PI)*100, -100, 100, 0, 255);
int dutyCycleB = map(sin(currentTime / 1000.0 * 2 * PI + PI/2 )*100, -100, 100, 0, 255);
OCR2A = dutyCycleA;
OCR2B = dutyCycleB;
delay(10);
}
Timer2 WGMode 3 Fast PWM (TOP=0xFF)の場合
void setup(){
pinMode(3, OUTPUT);//R2B
pinMode(11, OUTPUT);//R2A
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);//TCCR2A = B10100011 WGMが011でfast PWM。COM2A,COM2Bが10で出力AとB に非反転 PWM。
TCCR2B = _BV(CS21);//TCCR2B = B00000010 8分周
OCR2A = 180;// (180+1)/256=0.7
OCR2B = 50;// (50+1)/256=0.20
}
void loop(){ }
/*
出力 A(D11) 周波数:16MHz / 8 / 256 = 7.81kHz
出力 A デューティ サイクル: (180+1) / 256 = 70.7%
出力 B(D3) 周波数: 16 MHz / 8 / 256 = 7.81kHz
出力 B のデューティ サイクル: (50+1) / 256 = 19.9%
*/
前例のMode 7 Fast PWM のように任意の周波数を作ることはできません。分周比を変えてざっくりとした周波数を作ることができます。出力A/Bの周波数は同じですが、出力A/BのDutyをそれぞれに設定できます。
Timer2 (D11, D3) Phase-Correct(位相補償)PWM モードの場合
このモードでは、タイマーは 0 から 255 までカウントし、その後 0 に戻ります。タイマーが途中で出力比較レジスタ値に達すると出力はオフになり、タイマーが出力比較レジスタ値に達すると出力は再びオンになります。下りの道。その結果、より対称的な出力が得られます。タイマーはアップとダウンの両方で動作するため、出力周波数は高速 PWM モードの値の約半分になります。
void setup(){
pinMode(3, OUTPUT);
pinMode(11, OUTPUT);
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);//WGM 001でPhase Correct PWM モード
TCCR2B = _BV(CS21);// 分周比 8
OCR2A = 180;
OCR2B = 50;
}
void loop(){ }
/*
出力 A 周波数:16MHz / 8 / 255 / 2 = 3.92kHz
出力 A デューティ サイクル: 180 / 255 = 70.6%
出力 B 周波数: 16 MHz / 8 / 255 / 2 = 3.92kHz
出力 B デューティ サイクル: 50 / 255 = 19.6%
*/
下手なことを考えず簡単に周波数を変える方法
固定周波数

ArduinoのanalogWriteの周波数を変える【PWM】【コピペ対応】
どちらかというと自分用のメモ的なエントリです。でも誰かの役に立つかも?とも思うのでまとめておきます。
周波数とDuty可変
ArduinoNANOにてタイマカウンタ1と2を使ってPWM周波数を変更する。
Arduinoで、出力したい周波数を設定するときに見るあんちょこ。 atmega328のデータシートは以下URLから参照 https://avr.jp/user/DS/PDF/mega328P.pdf atmega328はタイマー0,タイマー1,タイマー2があり、Arduinoはタイマー0を時間管理に使用しているので レジスタ周りを触ってはいけません。
関連リンク

Programming Arduino Timer 0 in Fast PWM mode
In this tutorial Fast PWM mode of Timer 0 of Arduino is explained with program example codes.

Programming Arduino Timer 1 in Fast PWM mode
In this tutorial Fast PWM mode of Timer 1 of Arduino is explained with arduino program example codes.

Programming Arduino Timer 2 in Fast PWM mode
In this Arduino electronics tutorial Fast PWM mode of Timer 2 of Arduino is explained with program example codes.

Arduino PWM 周波数 高速化 | LabVIEW info. Sharing 新館
こんなキーワードで検索すると多くのサイトが見つかります。 感謝ですね。
Arduino PWM周波数の高周波化
ArduinoのPWM周波数を変更したいので調べてみました. ATMELの マニュアル は大変参考になります 日本語ではgarretlabさんの analogWrite() の項目が体系的にまとめられています. 基本的にはそちらを見ていただいた方がいいと思います. 以下...
Arduino Uno ? PWM周波数を”自由に”変更する
PWM周波数の変更方法について書いてくれてるブログやサイトはたくさん見つかるけど、じゃ結局どうすりゃ変わるのよ…
PWMで作るアナログ電圧(0~5V)

ArduinoのanalogWrite()はPWM
なんだか当たり前のことを書いてしまったようなタイトルだけど、「analogWrite()はアナログ値が出てくるのではなくてPWMのデューティを指定するものなので、それを実測してみよう」という実験。 基礎実験(デューティ50%) まず、ana
- ArduinoのanalogWrite()の出力はPWM。アナログ電圧を得たいのなら、CRフィルタを通す。
- CもRも大きい方がリプルは小さくなる
- ただし、Rによる電圧降下に要注意。
- 出力電流が多めだと電圧が下がる(ATmega328Pからの供給電圧も、CRフィルタによる電圧降下も)。
- デューティ比にできるだけ合わせた出力電圧(計算通りのの出力電圧)を得たいなら、負荷は軽く抑える。
- CRフィルタのRは小さくCは大きく。
- 負荷には直接電流を供給せずに、ボルテージフォロワをかませる。
- PWMの周波数が高いほうが、リプルの点では有利。