Arduino PWM波形の設定

2020.02.11

ArduinoのPWM波(pulse width modulation:パルス幅変調)の仕組みを調べた記録です。MIDIを使ってレジスタの値をブラウザで表示しています。

レジスタの設定は、ATmega328/328Pのドキュメントを参照します。括弧で示したページ番号は、ドキュメントのページ番号です。PWM付き16ビットタイマ/カウンタ1(TC1)を使います。PWM波形はOC1Bから出力します。ArduinoのピンはPB2(PIN10)です。(p.12) レジスタの設定は以下の通りとしました、

レジスター解説
COM0A0OC1Aは使わない
COM0B2OC1Bを、「上昇計数時の比較一致でLow、下降計数時の比較一致でHighをOC1xピンへ出力」に設定(p.93)
ICNC1/ICES10/0未使用
CS2/CS1/CS01/0/1「clkI/O/1024 (1024分周)」(p.96)
cWGM13/cWGM12/
cWGM11/cWGM10
1/0/1/1「位相基準PWM動作」TOPはOCR1Aで設定(p.94)
OCR1A0xFFFFTOPを0xFFFFに設定(p.95)
OCR1B0x8000しきい値を0x8000に設定(p.97)

Arduino UnoのクロックclkIは16MHzです。CS2-0の設定により、1024/16,000,000sec(= 0.000064sec=64μsec)に1回カウンターを1増減します。カウンターはゼロから0xFFFF(=65536)まで増加し、その後ゼロまで戻ってきます。すなわちカウンターの1周期は65536*2*64μsec≒8.388608secとなります。また、OSR1Bをカウンターが下から上に横切るとき、OC1Bがゼロになります。また、OSR1Bをカウンターが上から下に横切るとき、OC1Bが1になります。

Arduinoのセットアップは次の通りです。


#include <avr/io.h>

#define PWMPin 10   //PWM出力ピン PB2(PIN10)を出力に設定
#define MonPin 7    //PWMモニター入力ピン
#define VolPin 0    //ボリューム入力ピン

unsigned char COM0A=0;
unsigned char COM0B=2;
unsigned char FOC0=0;
unsigned char CS0=5;
unsigned char cWGM13=1;
unsigned char cWGM12=0;
unsigned char cWGM11=1;
unsigned char cWGM10=1;

void setup() {
	pinMode(PWMPin, OUTPUT);	//PB2(PIN10)を出力に設定
    pinMode(MonPin, INPUT);     //PWMモニター入力ピン

	// モード指定
	TCCR1A = ((COM0A&0x3)<<6)+((COM0B&0x3)<<4)+((cWGM11&0x1)<<1)+(cWGM10&0x1);
	TCCR1B = ((FOC0A&0x3)<<6)+((cWGM13&0x1)<<4)+((cWGM12&0x1)<<3)+(CS0&0x7);

	// TOP値指定
	OCR1A = 0xFFFF;

	// しきい値
	 OCR1B = 0x8000;
}
Arduino PWM波形をMIDIで測定
2020.02.7

ArduinoのPWM波(pulse width modulation:パルス幅変調)の仕組みを、ArduinoのレジスタをMIDIで出力し、Web MIDIを使ってブラウザで表示して調べます。下のグラフはControl Change Monitorで表示しています。

PWM波の発生は、簡単には2つのレジスタが関係しています。ほんとはもっと沢山のレジスタが関係しているのですが、ここでは2つのレジスタを表示します。下の図で、時間は左から右へ流れているものとします。オレンジの線はカウンタ(TCNT1)で、TOPとBOTTOMの間をいったり来たりして、三角波を生成しています。緑はしきい値(スレショルド)を表しています。OCR1Bというレジスタの内容です。

オレンジの線が、緑の線をまたぐとき、OC1Bというレジスタの値は反転します。オレンジの線が下から上に緑の線を横切る時、OC1Bはゼロから1になります。オレンジの線が上から下に緑の線を横切る時、OC1Bは1からゼロになります。このOC1BがArduinoのピンからPWM波として出力されます。(この動作は設定で反対にもできます。)

OCR1Bの値を大きくしてみます。緑の線が上に上がります。それにつられて、OC1Bの反転するタイミングが上の例とは異なり、ゼロである時間が短く、1である時間は長い波形ができます。

OCR1Bの値を小さくしてみます。緑の線が下に上がります。それにつられて、OC1Bの反転するタイミングがこの場合には、1である時間が短く、ゼロである時間は長い波形ができます。




回路図
2020.02.13

PWM波はD10(PB2)から出力されます。この信号をモニターするためにD7(PD7)に直結しています。A0(PC0)にはボリュームがつながっています。このボリュームを動かすことで、OCR1Bを増減します。


プログラム
2020.02.13
void loop() {
    unsigned int val = analogRead(VolPin);  //アナログピンを読み取る
    OCR1B= val<<6;                          //10ビットを16ビットに、6ビット左シフト
    SendControlChange(0x07,(val>>3)&0x7F);  //10ビットを7ビットに、3ビット右シフト
    
    unsigned int x=TCNT1;
    x=x>>2;    //ピッチベンド16ビットを14ビットに2ビット右シフト 
    SendPitchBend(x);
    
    unsigned char pwm=digitalRead(MonPin);
    if(pwm==0)
        SendControlChange(0x01,0x10);
    else
        SendControlChange(0x01,0x70);
}
位相/周波数基準PWM動作と位相基準PWM動作
2020.02.14

ATmega328/328Pのドキュメントのp.94に記載の表20-5、波形生成種別選択の中の「位相/周波数基準PWM動作」と「位相基準PWM動作」についてどこが違うのか調べてみました。この2つのモードは、WGM10,11,12,13のレジスタで設定します。英語のドキュメントを見ると、[PWM, phase and frequency correct] [PWM, phase correct]と記載されていました。これでも意味がよくわかりません。

2つのモードの違いは、表の右から2番目の列、OCR1x更新時の値が違うことです。しきい値であるOCR1Bの値を変更したとき、OC1Bの反転が、OCR1Bの値の変化のどの時点の値かが違っています。文章では難しいので画像で示します。

「位相/周波数基準PWM動作」は、TCNT1がBOTTOMの位置にあるときのOCR1Bの値をしきい値として使います。OCR1Bの値(緑の線)が右から左へ進んでいきます。2の位置で値が大きくなります。TCNT1(茶色の線)がBOTTOMとなるAの位置の値を捕まえて、次のしきい値となり3の位置でOC1Bが反転します。その後、OCR1Bは値が小さくなりますが、TCNT1がBOTTOMとなるのはBを待たないとならないので、次の反転は3'で起こります。BでOCR1Bの値を捕まえ、4と4'で値が反転します。同様にCの時点の値が5に反映します。

「位相基準PWM動作」は、反対にOCR1Bの値の変化をTCNT1がTOPとなる位置で捕まえて次のしきい値とします。下の例では2でOCR1Bが大きくなりますが、しきい値が更新されるのは、Aの位置です。Aの位置で捕まえたOCR1Bの値で、4でOC1Bが反転します。同様に、BでOCR1Bの値を捕まえ、OC1Bは5と5’で反転します。さらにCで捕まえた値で6で反転します。