前回は任意の周波数のPWMを生成するためのパラメータを決定しました。今回はその決定したパラメータを実際にプログラムに落とし込む方法を紹介します。
レジスタの設定
いままでの計算で目的の周波数のPWMを出力する設定は見つかりました。今度は実際にその設定をレジスタに入力していきます。設定情報は毎度のごとくAtmega328Pのデータシート(https://avr.jp/user/DS/PDF/mega328P.pdf )から探します。タイマー0のレジスタ設定はP76~78、タイマー1のレジスタ設定はP94~96、タイマー2のレジスタ設定はP112~114に書かれています。なお、8ビットタイマーの「タイマー0,1」と16ビットタイマーの「タイマー1」は分けて紹介したいと思います。
8ビットタイマー
8ビットタイマーは「タイマー0」と「タイマー2」に搭載されていますがほぼ設定内容は同一なので「タイマー0」を基準に紹介していきます。(分周のところだけ違うのでそこだけ別途説明します)タイマー0における、PWM出力の設定レジスタは「TCCR0A」と「TCCR0B」です。また、可変周波数PWMを行う場合の波の高さは「OCR0A」レジスタに、duty比を設定する閾値は「OCR0B」レジスタに入力します。それでは、各レジスタの中身を見ていきましょう。
PWMを生成するのに使用するレジスタはこの4つですが、下の2つのレジスタは数値を入力するだけなので、詳細な説明は省略します。上の2つのレジスタは各ビットの状態を使ってさまざまな、設定を行っています。それぞれの設定項目別に紹介をしていきます。
OC0Aの端子の出力設定(比較A出力選択)「TCCR0Aの第6,7ビット」
この項目で確認する必要があるのは、図の赤で囲んだところです。PWMの動作種別(波の形)によって「高速PWM比較A出力選択」か「位相基準PWM動作比較A出力選択」を確認して下さい。波の高さを可変する可変周波数PWMを出力する場合は、ここで出力設定をする「OC0A」端子に対応する、比較レジスタ(PWM生成の閾値)が波の高さを設定するレジスタそのものであるため、この端子からPWMを出力することはできません。そのため、可変周波数PWMを使用する場合は図の赤で示した「標準ポート動作(OC0A切断)」を選択します。つまり、COM0A1,COM0A0ともに0となります。
可変周波数PWMを使わず、限られた周波数のPWMのみを出力する場合は「OC0A」端子もPWM出力が可能となり、その場合は図の青←で示した「比較一致でLow…」「上昇計数時の比較一致でLow…」の設定を選択します。
OC0Bの端子の出力設定(比較B出力選択) 「TCCR0Aの第4,5ビット」
この項目の中身自体は先ほどの設定と大差なく、確認する箇所も同じですが、可変周波数PWMを使用する場合は設定する内容が違うことに注意が必要です。ここで設定を行う「OC0B」端子は、可変周波数PWMを行う場合ではPWMが出力される端子です。そのため、図の赤←で示した「比較一致でLow…」「上昇計数時の比較一致でLow…」、つまり「COM0B1:1, COM0B2:0」を選択しなければなりません。(データシートのレジスタの項目名は間違いです)この設定をしないとPWMが正しく出力されないので注意が必要です。(なお、COM0A1,2:11を選ぶと出力波形が反転します)
タイマー波形選択「TCCR0Aの第0,1ビット、TCCR0Bの第4ビット」
ここの項目ではタイマーで生成される波形の種類を選択します。可変周波数のPWMを生成する場合は波の高さを変更できるようにする必要があるため、TOP値をレジスタで設定できる項目を選ばなければなりません。そのため、図の赤の←で示したTOP値が「OCR0A」で定義できる設定を選択します。波形が三角波の「位相基準PWM動作」を選択する場合は赤←1で示した「TOP値:OCR0Aの位相基準PWM動作」(WGM02:1 WGM01:0
WGM00:1)を選択します。また、波形がのこぎり波の「高速PWM動作」を選択する場合は赤←2で示した「TOP値:OCR0Aの高速PWM動作」(WGM02:1 WGM01:1 WGM00:1)を選択します。
可変周波数のPWMを使用しない場合は図の青←で示したTOP値が「$FF(255)」の項目を選択します。
また、この設定項目を保存するレジスタはWGM01とWGM00が「TCCR0A」、WGM02が「TCCR0B」と2つに分かれていることに注意しなければなりません。
この項目では、タイマーに使う分周の値を選択します。必要な分周に応じた項目を選択してください。ただし、この項目は同じ8ビットタイマーでも「タイマー0」と「タイマー2」で選択肢が異なるので注意してください。上の画像は「タイマー0」の設定です。「タイマー2」の場合は以下のような選択肢に変わります。
このように、同じ分周数でもレジスタに設定する値が変わってくるので注意が必要です。
レジスタの設定例
これまでに説明した、手順で実際に任意の周波数のPWMを出力する設定を紹介します。
今回はシステムクロック16Mhzで出力PWMを1kHzとし、タイマーの波形は位相基準PWMとした場合です。また、duty比は0.5としておきます。
初めに、分周値とTOP値を選定します。(分周は64として代入)
計算結果より、TOP値は125となり使用可能範囲であるため、設定パラメータは分周64、TOP値125とします。
各レジスタに設定した値を入力していくと以下のようなパラメータとなります。なお、赤字は関係ないor設定できない項目なので0にしています。
TOP値を定義する「OCR0A」とduty比を設定する閾値を定義する「OCR0B」には以下のように値をそのまま入力させます。
OCR0A=125
OCR0B=OCR0A*0.5=62
このように設定することで任意の周波数のPWMを生成することができます。
16ビットタイマー
16ビットタイマー(タイマー1)の場合も8ビットタイマーと同様に設定を行っていきます。データシートのP94~96より、PWMの設定レジスタは「TCCR1A」と「TCCR0B」となっています。そして、可変周波数PWMを使う場合の波の最大値は「OCR1A」or「ICR1」、PWM生成の閾値は「OCR1B」となっています。後者の3つのレジスタは値を設定するレジスタです。16ビットタイマーなので設定できる値は16ビットとなりますが、あくまでAtmega328Pは8ビットマイコンなので8ビットのレジスタを2つ使う形となっています。
8ビットタイマーとシステムが近いので違いがある点を絞って紹介をしていきたいと思います。まずは、設定レジスタである「TCCR1A」と「TCCR1B」の中身を見てみます。
設定レジスタの中身はこのようになっており、タイマーの波形選択である「WGMn」以外の設定は8ビットタイマーと同じ設定です。今回は8ビットタイマーと設定が異なる波形の形の設定を見てみましょう。
タイマー波形の選択「TCCR1Aの第0,1ビット TCCR1Bの第3,4ビット」
8ビットタイマーに比べて設定項目が非常に多くなっています。8ビットタイマーとの違いは主にTOP値の選択項目が多くなっているということで、「0x00FF」「0x01FF」「0x3FF」「OCR1A」「ICR1」と5種類のなかから選ぶことが可能になります。可変周波数PWMを使う場合は赤色の←示した「TOP値OCR1Aの位相/周波数基準PWM動作」(WGM1: 1 0 0 1)か「TOP値OCR1Aの高速PWM動作」(WGM0:
1 1 1 1)を選択すると良いでしょう。また、可変周波数PWMを使わない場合でほかの8ビットタイマーと同様に使いたい場合は青←で示した「TOP値0x00FFの8ビット位相基準PWM動作」(WGM1: 0 0 0 1)や「TOP値0x00FFの8ビット高速PWM動作」(WGM1: 0 1 0 1)を選択すると良いでしょう。
16ビットタイマーでもほかの設定は8ビットタイマーと同じなので省略します。次に波の高さ(TOP値)やPWMを生成する閾値を設定するレジスタを見てみます。
Atmega328Pはあくまで8ビットマイコンなので16ビットの値を1つのレジスタとして扱うことができません。そのため、内部的には下位バイトと上位バイトの2つのレジスタに分けられていますが、Atmel Studioではこれら2つをまとめて「OCR1A」として扱うことが可能になっています。そのため、ほかの8ビットタイマーなどと同様の感覚で使うことが可能です。(この書き方ができない環境の場合はビット演算とビットシフトを使って、それぞれのレジスタに書き込みをする必要があります)
レジスタの設定は、8ビットタイマーの時と同様に行います。
サンプルプログラム
今回は、ある変数に入力された周波数のPWMを生成するプログラムをサンプルとして紹介したいと思います。今回はタイマー0を使用しDuty比は31%に固定としています。
#include <avr/io.h>
volatile int freq = 1000; //出力周波数
volatile double duty = 80; // 80/255→0.313→31%
int main(void)
{
DDRD = 0x68;//PWMピンを出力設定
DDRB = 0x08;
//矩形波生成タイマー設定
TCCR0A = 0x01; //PWM出力停止 位相基準 OCR0A max
TCCR0B = 0x0C; // 256分周
TCCR0A &= ~0x20;//PWM停止
if((freq > 125) && (freq < 2000)){
TCCR0B = 0x0C;//256分周
OCR0A = (unsigned int)(31250 / freq) ; //割り込み周波数
OCR0B = OCR0A * (duty / 250.0);
TCCR0A |= 0x20;//PWM起動 OCR0Bのみ
}else if((freq > 50) && (freq < 125)){
TCCR0B = 0x0D;//1024分周
OCR0A = (unsigned int)(7812 / freq) ; //割り込み周波数
OCR0B = OCR0A * (duty / 250.0);
TCCR0A |= 0x20;//PWM起動 OCR0Bのみ
}else if((freq > 2000) && (freq < 7000)){
TCCR0B = 0x0B;//64分周
OCR0A = (unsigned int)(125000 / freq) ; //割り込み周波数
OCR0B = OCR0A * (duty / 250.0);
TCCR0A |= 0x20;//PWM起動 OCR0Bのみ
}
while (1)
{
}
プログラムとしては、TOP値が8ビットに収まる範囲で場合分けをして分周を区切っています。また、プログラム実行中の周波数を変更も視野に入れたため、念のためPWM周波数を変更する処理を行うときはPWM出力を一旦停止させています。TOP値の計算は前回紹介したものをあらかじめ計算可能な場所のみ計算したものを使用しています。
以上で任意の周波数のPWMを出力する方法の紹介は終わりです。次はタイマー割り込みのお話を予定しています。