Youtubeでも活動中チャンネルはこちら!

256を渡すとどうなる?Arduino analogWrite()の内部動作徹底解析

256-digital-to-analog-arduino-eyecatch

 最近ArduinoのPWM関係で調べる機会があり、そこで気づいた内容を今回ご紹介します。具体的には、ArduinoのanalogWriteを記述する際、第二引数にmax値の255を入れても、duty比が100%にならない。という話です。これはどうもArduino UNO R4の問題のようです。

 Arduino UNO R3までの場合は、ちゃんと255を指定するとduty比が100%になっていたようなので、搭載されているチップが異なることで挙動が変わっていると考えられます。

 いまいち言葉だけでは説明が難しいので、オシロスコープの測定結果も含めてご紹介します。

この記事を読むことでわかること

analogWriteのひっかけに気づける。

自己紹介

東証一部上場企業でサラリーマンしてます。

主に工場(生産現場)で使用する検査装置のアプリケーション開発してます。

ヒトの作業を自動化して簡略化するアプリケーションを日々開発中。

転職に成功して現在は超大手企業でシステム系の開発をしています。

_/_/_/_/_/Youtubeでも情報発信中!_/_/_/_/_/

本ブログはアフィリエイトを用いた広告を掲載している場合があります。

analogWriteでPWM制御

 Arduinoで電圧の上限をさせたい場合、できるのはPWMであることがほとんどです。最近のArduinoの一部機種ではDACと呼ばれるアナログ変換が入っていて、それを通すことで電圧をちゃんと上下させることができます。

 それに比べて、PWMは、5Vを出す時間を間引くことで、疑似的に電圧を調整しています。信号の波形と、テスターで測定した電圧を比べてみるとわかりやすいです。

 具体例としては5Vを出す時間を半分にした場合、電圧が半分の2.5Vになります。5Vが出ている時間が、出ていない時間とちょうど同じ。つまり、半分の時間しか5Vが出ていない状態です。これをオシロスコープで見てみると、このような波形になります。NWというのが、0Vの時間(1.024ms)、PWというのが5Vが出ている時間(1.016ms)です。ほとんど一致していますね。

PWM-duty-50
duty比50%で設定。5Vが出ている時間と出ていない時間がほぼ一致している。

 テスターではどうなるでしょうか?ほぼ半分になっていますね。

pwm-duty--half-multimater
PWMのduty50%の状態をテスターで測定した結果。ちょっと低い…

ArduinoでのanalogWriteの使い方

 それでは、ArduinoでanalogWriteを使うにはどんなスケッチにしたらいいか?を解説しましょう。スケッチはこんな感じになります。

int analogWritePin = 3;
void setup() {
  pinMode(analogWritePin,OUTPUT);  
}

void loop() {
  analogWrite(analogWritePin,255);
}

まずは、どのピンでもいいのですが、~と記号が付いているピンがanalogWriteを使えるピンです。まずはここをスケッチで指定します。今回はpin3を指定します。

 setup()では、pinModeでOUTPUTに指定しなさいとドキュメントで指定があるので記述しておきましょう。なくても動いちゃうんですけどね…

 最後にloop()の中でanalogWrite(ピン番号,dutyサイクル)を書いて終了です。第二引数のdutyサイクルとは、0~255の256階調(8bit)で指定可能で、0にすると常にOFF、255にすると常にONとなります。

 で、この255にすると常にONになる。という文言がkン回のひっかけポイントです。Arduinoの公式ドキュメントを引用しておきます。

    • pin: the Arduino pin to output the PWM signal. Allowed data types: int
    • value: the duty cycle: between 0 (always off) and 255 (always on). Allowed data types: int

    デューティサイクルをmaxの255にした場合

     それでは引っかかりポイントの解説です。先ほどご説明したanalogWriteの第二引数、こちらを255にして、信号をオシロスコープで見てみましょう。結果がこちらです。

    PWM-duty-100
    第二引数に255を入れた状態のanalogWrite。常にONではない。

     どうでしょうか?何か違和感を感じませんか?255で実行した場合、常にONになっているはず。つまり、1msたりとも0Vになることはない。と思いますよね。

     ただ、今回の結果の通り、1周期につき20μsのあいだ0Vになっていることがわかります。ちなみに255が上限なはずですが、256としてみるとどうでしょうか?

    duty-genuin-100
    第二引数に256を入れた状態のanalogWrite。255が上限ではなかった。

     実は256にすると、0Vに落ちる時間が無いんですね。257でも同じ結果でした。つまり、255にすると常にONではなく、256以上にすると常にON。が正解です。

    デューティサイクルのmax値が256になっている原因について

     それではここまでの謎を整理して、原因についても一緒に解説してきましょう。

    ここまでの謎
    • なぜデューティサイクルに256以上の数値を入れてもエラーにならないのか?
    • なぜ255で常に5Vにならず、256で常に5Vが出るのか?

    なぜデューティサイクルに256以上の数値を入れてもエラーにならないのか?

     公式ドキュメントでは、デューティサイクルは255が最大と記載があります。なのに、実際には256や257といった、255よりも大きい数字を入れても特にエラーにはなりません。なぜでしょうか??

     原因は、analogWriteの関数の中身にあります。具体的には、dac.cppというC++のソースコードを読み解くことで説明できます。大事なのはanalogWriteの関数の冒頭部分です。

    void CDac::analogWrite(int value) {
    
      if (value > (1 << analogWriteResolution())) {
        value = (1 << analogWriteResolution());
      }

     引数のvalueが、0~256で入れていたデューティサイクルの値です。この値が、1<<analogWriteResolution()よりも大きいかどうかをif文で条件分岐しています。

     この1<<analogWriteResolution()は、12bitと指定していない限り、デフォルトは1<<8と置き換えられます。つまり、256となります。ですから、このif文としては、引数のvalueが256より大きければ、256にする。という意味です。この作業を、256にクランプする。なんて呼んだりもします。

     です

     次に、もう少し読み進めていくと、このような記述があります。

    #if DAC8_HOWMANY > 0
        value = map(value, 0, (1 << analogWriteResolution()), 0, (1 << 8));
        open(&ctrl_dac8, &cfg);
        write(&ctrl_dac8, value);
        start(&ctrl_dac8);

     ここでは、map関数を用いて、引数のvalueの値が、0~256の間のどこに来るかをスケーリングします。正規化とも言いますね。重要なのは、0~256としているところです。もう一度言います。0~256の範囲で指定しています。

     これが今回引っかかった理由ですね。Arduinoの公式ドキュメントには、0~255で指定して、255では常にONになるんだよ。と書いてありますが、実際には255が上限ではなく、256が上限であると、このコードからも読み取れます。

     そもそも8bitなんだから、0~255の256階調というのが普通ですよね。なんで0~256の257階調なんだ?という話ですよね…

    今回のまとめ「公式ドキュメントも信じすぎてはいけない」

     公式ドキュメントに書いてあることは基本的には間違いないはずです。ただ、今回のように、チップが変更されているようなものについては、今までとは一致しないこともあるようです。

     ですから、妥協せずに厳密に制御したい場合は、ちゃんとオシロを使って測定するのが大事なんですね….

    今回のまとめ
    • Arduino UNO R4が特殊なのか?analogWrite(pinNo,255);は常にONではない。
      常にONにしたいなら、analogWrite(pinNo,256):