今回はさまざまな用途に利用することの多いPWMをAVRから出力することについて書きます。 PWMとはなに?という方も参考にしてください。 最初から最後までわかりやすく解説するのが目標ですが、抜けていたり飛躍している箇所がたくさんあります。 コメントで指摘していただけるとたいへんありがたいですし、自分のためにも質問にはできる限り答えるようにしようと思っています。
PWMとは
PWMとはPulse Width Modulationの略で日本語ではパルス幅変調と言います。つまり、Hi期間とLo期間を個別に操作されたパルスです。また、Hi時間とLo時間の合計時間を周期、1周期のうちのHi時間の割合をデューティー比といいます。 例えば、Hi時間3マイクロ秒、Lo時間5マイクロ秒のPWMの場合、周期が8マイクロ秒でデューティー比が37.5%のPWMということになります。 用途の一つとして、例えばLEDの明るさを変えたい場合LEDに流す電流を変えるという方法が一つですが、これはデジタル回路では大変な作業です。 そこで、LEDを高速で点滅させて、点灯している時間(パルスのHi期間に相当)と消灯している時間(Lo期間に相当)を制御することによって人間の目では明るさが変っていると認知させる方法が一般的です。 また同じようにモーターの速度を変えるときにも電圧を変えるという方法ではなく、ONにしたりOFFにしたりして結果的に速度を変えたことにするという目的にも利用できます。
タイマーを使う理由
もちろん、上記のようなPWMはタイマーのようなマイコンの周辺機能を使わずとも汎用出力ポートを叩くことで作り出すこともできます。しかしそれではPWMを生成することにCPUのパワーを使い、他の作業ができません。 それを避けるために多くのマイコンでタイマーを設定するだけで任意のPWMを自動的に生成することができるようになっています。 今回はAVRの一つであるATtiny2313でPWMでLEDの明るさを変えてみることを通し、PWMの生成手順を解説しようとおもいます。
回路
AVRの場合タイマーから出力できる端子にはOC0A、OC0BやOC1Aなどという名前がついています。 番号はタイマーの番号と一致し、A,BはタイマーのレジスタOCR0AやOCR0Bに対応します。同じタイマーからは同じ周期のパルスしか生成できません。が、デューティー比の異なるパルスは作ることができます。詳しいことはソフトウエアの項目で解説します。 今回はタイマー0を利用し、OC0AとOC0Bの両方の端子を用いて、2つのLEDの明るさを変えてみようとおもいます。
タイマー0とタイマー1の違いはカウンタのビット数と、2本のPWMを出す際に周期が指定できないかできるかの違いです。タイマー0はカウンタは8ビットしか無く、2本のPWMを出すときには周期が変更できません。タイマー1はカウンタが16ビットで2本のPWMを出すときでも任意の周期が指定できます。
ソフトウエア
タイマー0を設定するのに利用するレジスタは、
- TCCR0A
- TCCR0B
- OCR0A
- OCR0B
- TIMSK(割り込みを使う場合)
の5つです。
タイマー0にはいくつかの動作モードがありますが、今回は8ビット高速PWMモードを利用します。このモードは周期が任意に指定できない代わりに2本のPWMを生成できます。任意の周期を指定する場合は高速PWMモードを利用しますが、これについては後ほど説明します。動作モードはAVRのデータシートを参照してください。
動作の指定
タイマーがどのように動作するのかを指定するためのレジスタは
- TCCR0A
- TCCR0B
の2つです。次の設定がこの二カ所に分散して含まれています。
- COM0A/COM0B OC0AおよびOC0B端子の動作設定
- WGM0 タイマーの動作モード
- CS0 分周器の設定
- FOC0A/FOC0B OC0AおよびOC0Bの強制変更設定(今回は利用されない)
今回はデータシートにそって上記の8ビット高速PWMモードになるように設定します。詳しくはソースコードのコメントを参照してください。
ソースコード
AVR Studioのプロジェクトをおいておきます。WinAVRも必要です。
PWM_Sample(AVR Studioのプロジェクト)
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
//Sinは計算できないので事前にテーブルをFLASHにおいておく
//振幅0xffの180度分のサインテーブル(分解能256)
const uint8_t sin_table[] PROGMEM =
{0,3,6,9,12,15,18,21,25,28,31,34,37,40,
43,46,49,53,56,59,62,65,68,71,74,77,80,
83,86,89,92,95,97,100,103,106,109,112,
115,117,120,123,126,128,131,134,136,139,
142,144,147,149,152,154,157,159,162,164,
167,169,171,174,176,178,180,183,185,187,
189,191,193,195,197,199,201,203,205,207,
209,210,212,214,215,217,219,220,222,223,
225,226,228,229,230,232,233,234,236,237,
238,239,240,241,242,243,244,245,246,246,
247,248,249,249,250,250,251,251,252,252,
253,253,253,254,254,254,254,254,254,254,
254,254,254,254,254,254,254,253,253,253,
252,252,251,251,250,250,249,249,248,247,
246,246,245,244,243,242,241,240,239,238,
237,236,234,233,232,230,229,228,226,225,
223,222,220,219,217,215,214,212,210,209,
207,205,203,201,199,197,195,193,191,189,
187,185,183,180,178,176,174,171,169,167,
164,162,159,157,154,152,149,147,144,142,
139,136,134,131,128,126,123,120,117,115,
112,109,106,103,100,97,95,92,89,86,83,80,
77,74,71,68,65,62,59,56,53,49,46,43,40,
37,34,31,28,25,21,18,15,12,9,6,3,0};
void init(){
//OC0AとOC0Bを出力に設定
DDRB |= 0x04;
DDRD |= 0x20;
//タイマー0で2本のPWMを生成するために
//周期が0xffで固定されている「8ビット高速PWM」
//モードに指定
//このとき、OC0AとOC0Bは
//「BOTTOMでHi」「比較一致でLo」の非反転動作に設定
//分周はなし
TCCR0A = 0b10100011;
TCCR0B = 0b00000101;
//とりあえずデューティー比は0%
OCR0A = 0;
OCR0B = 0;
//0xffのときデューティー非は100%
}
void hirei(){
uint8_t i;
for(i=0;i<0xff;i++){
OCR0A = i;
OCR0B = 0x3f-i;
_delay_ms(40);
}
for(i=0xff;i>0;i--){
OCR0A = i;
OCR0B = 0xff-i;
_delay_ms(40);
}
}
void sin_curb(){
uint16_t i;
for(i=0;i<=0xff;i++){
OCR0A = pgm_read_byte(&sin_table[i]);
//OCR0Bは半周期ずらす
OCR0B = pgm_read_byte(&sin_table[(i+0x7f)%0x100]);
_delay_ms(20);
}
}
int main(){
uint8_t i;
init();
while(1){
//単純にデューティー比を増減
for(i=0;i<10;i++)
hirei();
//サインカーブに沿ってデューティー比を増減
for(i=0;i<10;i++)
sin_curb();
}
}
図およびビデオは鋭意制作中…