PWM で波形を生成してみます。
ここではピンとペリフェラルを繋ぐ方法を学ぶ感じです(適当)。
リンクたち
ところで、AVR32 について追加でためになるサイトを発見したため、ここに示させてもらいます。
- AVR32を使う – 茱萸note
- AVR32 の概要やその使い方・注意点等を完結にまとめてあります。
- 対象とする型番が UC3L シリーズなので、当ブログで扱っているものと異なる。特に FLASH についてが少し違います。
- PeripheralPinMapのブログ
- より深い理解に。僕がまだ理解できていない(理解しようとしていない)割り込みコントローラーについて述べられている記事もあり。
2種類のペリフェラル
このマイコンには PWM 波形を生成できるペリフェラルが2つあって、それぞれ TC と PWM です。
TC は前回の記事で用いなかった Wave Generation Mode を用います。
PWM というのはその名の通り PWM 波形の生成専用ペリフェラルです。とはいっても、割り込みを発生させることもできますし、まあこのマイコンにおいてはカウンタビット幅の広い、低機能版のタイマだという認識でいいでしょう。
以下では、この2種類での PWM 波形生成法を記述します。
TCでPWM
先述したように、Wave Generation Mode を用います。
波形モードの選択
Wave Generation Mode ではカウントの方法を変えることで、ちょっとしたバリエーションを以て波形生成が可能です。それは CMRx.WAVSEL
(x
は整数)で指定可能なのですが、詳しくはデータシートをご覧あれ。
ここでは WAVESEL == 2
とすることで、周期と Duty をどちらもレジスタで設定可能になります。その様子を以下に示します。
下にある TIOA, TIOB がチャネル出力で、これがピンに割り当てられます。そしてこの挙動も、ピンごとにレジスタで決定することができます。
出力ピンの選択
PWM では波形をペリフェラルから直接出力するわけですから、ペリフェラル出力を有効にする必要があるということになります。今回は TC のチャネル0 にある、TIOA から PWM 波形を出力することにしましょう。
ではまず、これがどのピンに割り当てられているかを見ます。データシートの 4.2 Peripheral Multiplexing on I/O lines を見ましょう。
ふむ、どうやら、6番ピンにある GPIO32 を Function A として設定すればいいようです。ではどのレジスタを設定すればいいのか見ていきましょう。
まず、GPIO ペリフェラルで設定を行います。ここの GPER
(GPIO Enable Register) をいじることによって、そのピンを GPIO で管理するか、それとも他のペリフェラルで管理するかを決定します。具体的には、対応するビットを0とすることでペリフェラル管理になります。ちなみに ペリフェラル管理になっても、GPIO による内蔵プルアップ抵抗の設定は反映されます。
そして、PMR0
と PMR1
(Port Mode Register) の値で切り替えるペリフェラル Function を設定します。例えば今回は GPIO32 を用いるので、port1 の 0ビット目を設定すればいいわけです。
そして以下の表によると、PMR0
と PMR1
の0ビット目を0にすれば良いので、結果として以下のようになります。
// output: TC_A0 (GPIO32, #6), func A
AVR32_GPIO.port[1].pmr0c = 1 << 0;
AVR32_GPIO.port[1].pmr1c = 1 << 0;
AVR32_GPIO.port[1].gperc = 1 << 0;
クロック入力とレジスタ設定
前回はさり気なくスルーしていましたが、今回はちゃんとクロック入力を設定します。
いい忘れましたが、クロックは PLL からの 60MHz とするので、それを前提にします。ひとまず最大に遅い TIMER_CLOCK5 にして、128分周をします。
また、TIOA では RA
レジスタとのマッチで1に、周期レジスタである RC
とのマッチで0にするように設定します。
AVR32_TC.channel[0].CMR.waveform.wave = 1; // Wave Generation Mode
AVR32_TC.channel[0].CMR.waveform.tcclks = AVR32_TC_CMR0_TCCLKS_TIMER_CLOCK5; // 60MHz / 128 == 468750Hz
AVR32_TC.channel[0].CMR.waveform.wavsel = 2; // UP mode with RC compare
AVR32_TC.channel[0].CMR.waveform.acpa = AVR32_TC_CMR0_ACPA_SET;
AVR32_TC.channel[0].CMR.waveform.acpc = AVR32_TC_CMR0_ACPC_CLEAR;
コード全景
以上を設定するコードを以下に書きます。当然ですが、一度この関数を初期化してしまえば main 関数内でチカチカさせるコードを書く必要はありません。こう見るのちょっと面倒くさいですよね。
void tc_pwm_init(void) {
// output: TC_A0 (GPIO32, #6), func A
AVR32_GPIO.port[1].pmr0c = 1;
AVR32_GPIO.port[1].pmr1c = 1;
AVR32_GPIO.port[1].gperc = 1;
AVR32_PM.pbamask |= 1 << 12;
AVR32_TC.channel[0].CMR.waveform.wave = 1; // Wave Generation Mode
AVR32_TC.channel[0].CMR.waveform.tcclks = AVR32_TC_CMR0_TCCLKS_TIMER_CLOCK5; // 60MHz / 128 == 468750Hz
AVR32_TC.channel[0].CMR.waveform.wavsel = 2; // UP mode with RC compare
AVR32_TC.channel[0].CMR.waveform.acpa = AVR32_TC_CMR0_ACPA_SET;
AVR32_TC.channel[0].CMR.waveform.acpc = AVR32_TC_CMR0_ACPC_CLEAR;
AVR32_TC.channel[0].rc = 46875 - 1; // match occurs per 1/10 sec.
AVR32_TC.channel[0].ra = 46875 / 2 - 1; // match occurs per 2/10 sec.
AVR32_TC.channel[0].CCR.clken = 1;
AVR32_TC.channel[0].CCR.swtrg = 1; // clock enabled
}
PWMでPWM
タイトルがわけわからんですが、前項と統一するためですw PWM によって、TC に比べてとても楽に波形生成を実現することが可能です。
概要/コード
上の TC に比べてとても簡単なので、ほとんど説明することがありません。CPRD
(Channel Period Register) で周期カウントを設定し、CDTY
(Channel Duty Cycle Register) で Duty を決定します。どちらもカウント数を指定するので、単純計算で CDTY = CPRD * duty
とすればいいでしょう。今回は単純に 50% としています。そして周期を 1sec にしています。
void pwm_init(void) {
// output: PWM0 (GPIO7, #13), func A
AVR32_GPIO.port[0].pmr0c = 1 << 7;
AVR32_GPIO.port[0].pmr1c = 1 << 7;
AVR32_GPIO.port[0].gperc = 1 << 7;
AVR32_PM.pbamask |= 1 << 10;
AVR32_PWM.channel[0].CMR.cpre = AVR32_PWM_CMR_CPRE_MCK_DIV_128;
AVR32_PWM.channel[0].cprd = 468750; // period: 1sec
AVR32_PWM.channel[0].cdty = 468750 / 2; // duty
AVR32_PWM.ENA.chid0 = 1;
}
〆
やはり 32bit マイコンのペリフェラル設定は面倒くさいですね〜〜。TC
で思い知らされましたよ。
とりあえずタイマーの征服は終わったので、今度は UART とその関連のことをやります。