SNSへはこちら

AVR32マイコンやってみよう(9) - PWM

PWM で波形を生成してみます。
ここではピンとペリフェラルを繋ぐ方法を学ぶ感じです(適当)。

リンクたち

ところで、AVR32 について追加でためになるサイトを発見したため、ここに示させてもらいます。

  • AVR32を使う – 茱萸note
    • AVR32 の概要やその使い方・注意点等を完結にまとめてあります。
    • 対象とする型番が UC3L シリーズなので、当ブログで扱っているものと異なる。特に FLASH についてが少し違います。
  • PeripheralPinMapのブログ
    • より深い理解に。僕がまだ理解できていない(理解しようとしていない)割り込みコントローラーについて述べられている記事もあり。

2種類のペリフェラル

このマイコンには PWM 波形を生成できるペリフェラルが2つあって、それぞれ TCPWM です。

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 による内蔵プルアップ抵抗の設定は反映されます。
そして、PMR0PMR1 (Port Mode Register) の値で切り替えるペリフェラル Function を設定します。例えば今回は GPIO32 を用いるので、port1 の 0ビット目を設定すればいいわけです。
そして以下の表によると、PMR0PMR1 の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 とその関連のことをやります。