自家製工作物

ジャンルを問わず趣味で作ってみたものを紹介

Arduino UNO R4 - PWM出力(3) - analogWrite()

Arduino UNO R4でPWMを出力するために、従来のArduinoで使われてきたanalogWrite()を使うこともできます。あれっと思ったのはこちらの、NANO R4のフルカラーLEDコントロールです。

inte-gonext.hatenablog.com

RGB-LEDはRA4M1のポートP409、P410、P411に接続され、タイマーではGPT5_A、GPT6_B、GPT6_Aにつなぐことができます。これらをanalogWrite()で出力するとフルカラーになったハズ、それがわかりやすいプログラムじゃなかったけど。pwm.hライブラリでは1つのGPTのA、Bを両方使うと出なくなるのに、analogWrite()では普通にRGBが出ますね。それから、UNO R4はPWMたくさん出る、って情報を以前どこかで見たこともあります。

analogWrite()は周波数が固定とか制約はありますが、数の制約は吸収してくれているようです。PWMを簡単にたくさん出したいなら最適です。ここでは1つのGPTで2つPWMが出せるのを確かめつつ、多数PWM出力を試してみます。

ーーーーー 目次 ーーーーー

PWM出力(1) - PWMライブラリ

PWM出力(2) - PWMライブラリ + イベントリンク

PWM出力(3) - analogWrite() (このページ)

PWM出力(4) - FspTimerライブラリ

PWM出力(5) - FspTimerライブラリ 裏技編

 

 

 analogWrite()でPWM出力する + おまけ

UNO R4 MINIMAを使って1つのタイマーGPT1からD2ピン/P105 GPT1_Aと、D3ピン/P104 GPT1_BからPWMを出力するプログラムを作りました。GitHubに置いてあり、プログラム名は pwm_by_analogwrite.ino です。実行結果も示します。

https://github.com/inteGN/Arduino_UNO_R4_GPT_PWM_Examples

タイマーの起動状態を表すレジスタR_GPT0->GTSTRは2で、確かにGPT1のみが走っています。そしてはい、D2、D3ピンの2つからPWMが出力されました。出力2つでカウンタを共有しており、PWMの立ち上がりは揃っています、というか揃ってしまいます。analogWrite()は使い方は簡単で、ピンに合わせてGPTのA、Bを自動で割り当ててくれるので、ユーザーはGPTの構成を意識する必要はありません。

この簡単さを利用して、analogWrite() が多数のPWM出力を破綻なく扱えるかを確認するおまけプログラムを作ってみました。LEDを順次点灯するプログラムで、動画では配線が容易なNANO R4を使っています。同じくGitHubに置いてあり、プログラム名は knight_rider_by_analogwrite.ino です。

www.youtube.com

8個のLEDをanalogWrite()で点灯させています。1個のLEDを点灯させ増光、減光させるクラスを定義し、そこから8個のインスタンスを作ってloop()内で点灯開始と増光、減光の処理を呼び出しています。タイマーのGPTとA、Bを意識することなくピン番号とデューティ比を指定すればいいので、クラスで書くのに好都合です。

 

 簡単なのは使う側だけ

analogWrite()そのものは従来からある関数ですが、MCU毎に異なるハードウェアに対応させていると思われます。UNO R4について、Arduinoコアライブラリのanalog.cppを眺めて仕組みを見てみましょう。実際にanalogWrite()の実装を見てみると、pwm.hライブラリをラップしつつGPTのA、Bをうまく共有するための工夫が入っています。

static PwmArray pwms{};

(中略)

  PwmOut *ptr = pwms.get(pinNumber);  /* verify if PwmOut has already been instantiated */
  if (ptr == nullptr) {
    /* if not instatiate it */
    ptr = new PwmOut(pinNumber);
    bool added = pwms.set(pinNumber, ptr);
    if(!added || !ptr->begin()) {
      delete ptr;
      ptr = nullptr;
    }
  }

  if(ptr != nullptr) {
    //check the pinmux in case it's been modified by a call to pinMode()
    bool has_peripheral_mux = R_PFS->PORT[g_pin_cfg[pinNumber].pin >> IOPORT_PRV_PORT_OFFSET].PIN[g_pin_cfg[pinNumber].pin & BSP_IO_PRV_8BIT_MASK].PmnPFS & IOPORT_CFG_PERIPHERAL_PIN;
    if (!has_peripheral_mux) {
      ptr->end();
      ptr->begin();
    }
  }

赤字にした部分、ここですね。ピンの状態や GPT の使用状況を確認しながら、必要に応じてend()して再度begin()しています。トリッキーな動作ですが、begin/endを内部で使いpwm.hライブラリとうまく協調して衝突を避け、2つのPWM出力ができるようにしています。analogWrite()は周波数を変えたりできない分以前のカウンタ設定を壊すこともなく、そのため2つめの追加が可能であったと言えそうです。

pwm.hライブラリでは難しいA、B の同時利用ですが、analogWrite()では簡単に扱うことができます。試していないですが、UNO R4 MINIMAを使ってこの方法で同時出力可能な本数は14本と思われます。

 

 補足

analogWrite()は周波数は固定ですが、その値はFspTimer.hの最初の部分で決めています。

#define STANDARD_PWM_FREQ_HZ       (490.0)

 

pwm.hライブラリの意外な使いにくさが目立ってしまうかのようですが、そんなことはないと断言できます。前のページで示したように、非公式であれば7本まで周期も自由に設定してPWM出力できます。要は適材適所です。

MCUのRA4M1、ハードウェア的にはポテンシャルがありますから、あくまでもそれぞれのライブラリの設計方針ととらえられます。さらに細かい制御が必要な場面、高位の抽象化ライブラリではレジスタ直接操作やむなしとなりますが、そうでなく FspTimer.h ライブラリを使った低位の抽象化による設定も利用できます。