Arduino Uno – PWM周波数を”自由に”変更する

PWM周波数の変更方法について書いてくれてるブログやサイトはたくさん見つかるけど、じゃ結局どうすりゃ変わるのよってところでつまづいたので、自分なりにまとめてみました。

(丁寧に説明してくれてるとこもあるんですけど、丁寧のベクトルが違うっていうか、私みたいな電子工作初心者にはわかりづらい内容だったり…)

(あと検索法としては、Arduinoで検索するよりも マイコンICである328Pであったり、AVRの情報を直接探した方が情報が多かった。-> というより、そっちで調べたらいっぱいありましたw

こういう小難しいことをする人達はArduinoの状態では納得がいかないようだ(´・ω・`) )

実行環境:
  • Arduino Uno R3 (ATmega 328P)
  • Arduino IDE 1.8.0
  • Windows 10 Home (どれ使っても一緒だろ!!いい加減にしr)
  • 出力ピン – 10ピン (タイマ1を使用しているため)
目的:

PWM制御のモーターの磁励音 (DCだけどこれでいいのかな?) を可聴域まで下げたい。

コード:

とりあえず”自由に”周波数変更できるコードだけ見せろって方もいらっしゃるかと思いますので、実例を。

これで 440Hz – デューティ比 50% の 矩形波 が出力されるはず。

内心ミスってるんじゃないかと思いつつ  実験した結果がこちら。

周波数 440Hz – デューティ比 50% の綺麗な波が出力されました。

もちろん、周波数やデューティ比の変数をいじってあげれば”自由に”波を作れます。

(こちらのオシロは秋月で売ってるキットです。組み立て の前段階の抵抗値の選別 が大変だった。

周波数表示機能とかもあるみたいですが、うまく動かない… 誰か教えて頂けると嬉しいです。)

注意点:

”自由に”波を作れると書きましたが、上記コードでは 16Hz – 1MHz までの波しかつくれません。

(1MHzというのは理論値です。実際に出力されるかは試してないので分かりません。

また、1MHzに近づくにつれデューティ比の指定が困難となるので、10kHzくらいが限界ではないでしょうか。)

分周比の設定などによって変わる為、この範囲外の周波数を扱いたい方は下の解説っぽいものを読んで、理解して下さい。

周波数が変化するコードを書いて、不具合が起きたらこの範囲外になっていないかチェックしてみて下さい。

また、上記コードでは出力ピンは10ピン限定です。

PWMPin の値を変えても波は出力されません。

解説(のような何か)

PWMの動作原理

Arduino Uno には PWM出力に使われる タイマ/カウンタ が 0,1,2 の三個 あります。

あらかじめ設定しておいた TOP値 まで カウンタ をインクリメントし、OCRxA / OCRxB (xはカウンタ番号。カウンタにつき2つの出力ピン A,B が割り当てられている) と一致したときにピンの出力を変化させます。

Phase Correct PWM (位相基準PWM)、Phase and Frequency Correct PWM (位相・周波数基準PWM) では、カウンタが TOP値 に到達するとデクリメントし、0になるまで同様の動作をします。

カウンタのインクリメントスピードは IC の動作周波数に依存しますので、TOP値 が大きいほど出力される周波数は低くなります。

0,2は8bit、1は16bit で動作する為、TOP値 の最大値はそれぞれ 255, 65535 となります。

可聴域まで周波数を下げたいので、上記コードでは 16bit の タイマ1 を使用しています。

また、インクリメントスピードは 分周比 の設定によってざっくりと変更することが出来ます。(1/8/64/256/1024 の中から選択)

分周比 ってなんぞや?って方は以下のコードを見ればなんとなく理解して頂けるかと思います。

 

従って、出力される周波数は、以下の式で表せます。

f = IC の動作周波数 / (分周比 * TOP値)

また、Phase Correct / Phase and Frequency Correct ではデクリメントもすることから、この式からさらに2で割ります。

Phase and Frequency Correct を使用した Arduino Uno の場合、以下の式となります。

f = 16,000,000 / (分周比 * TOP値 * 2) = 8,000,000 / (分周比 * TOP値)

逆に TOP値 を求める式をつくると下のようになるということです。

TOP = 8,000,000 / (分周比 * f)

より詳しいデータや動作概念は garretlab さんの HP にわかりやすくまとめられておりますので、そちらをご覧下さい。(決してめんどくさくなったわけではない。)

実際にスケッチに書くのは?

ライブラリのインクルード

レジスタを直接いじるには、上記のライブラリをインクルードする必要があります。

これを忘れて 30分 ほどハマりました(´・ω・`)

レジスタの設定

まず、タイマーを動かしたり、分周比を変更したりする、レジスタ の設定をします。

設定するレジスタは TCCRxA / TCCRxB (xはカウンタ番号)です。

(タイマ1にはTCCR1Cがあるのですが、強制出力モードの指定に使うレジスタらしいです。そのような使い方はしないのでここでは無視します。

強制出力は標準 / CTCモードのみで有効なようです。使い道がよくわかりません。)

モードの詳細については garretlab さんの HPうしこさん の HP をご覧になった方が早いと思われますので、割愛させて頂きます。

PWMモードは Phase Correct and Frequency Correct モードを指定します。

同モードの指定には WGM10 が異なる2つの選択法がありますが、これは ICR1OCR1A のどちらかを選択し、 TOP値 として割り当てることが出来るためです。

ICR1 を TOP値 とすれば、OCR1A/B の両方で同周波数の波を出力出来ます。

OCR1A を TOP値 として割り当てると、ダブルバッファが行われるとのことで、基本的にはこちらを選択する方が良いかと思われます。

今回は 10ピン のみ出力しますので、TOP値OCR1A に設定します。

よって、WGM13 / WGM12 / WGM11 / WGM10 はそれぞれ、1,0,0,1 となります。

COM1B0 / COM1B00, 0無出力0, 1トグル動作 (一致時の出力を反転)、

1, 0 は カウンタ がOCR1A/B – TOP間 にある場合 LOW0 – OCR1A/B間HIGH となるよう出力、

1, 1 は1, 0 の逆です。

スケッチを書く際にわかりやすいので 1, 0 を選択します。

分周比8 を 選択していますので、CS12 / CS11 / CS10 はそれぞれ、0, 1, 0 となります。

全てまとめると以下のようになります。

2進数で指定していますので、0bを先頭に追加します。Bとか0Bとかでも大丈夫っぽいです。

TCCR1A には 0bHGFEDCBAH / G / F / E / D / C / B / A にそれぞれ COM1A1, COM1A0, COM1B, 無指定, 無指定, WGM11, WGM1010 で設定します。

同様に、TCCR1B には それぞれ、無指定(ICNC1), 無指定(ICES1), 無指定, WGM13, WGM12, CS12, CS11, CS10 を設定します。

このレジスタ設定はビット演算子を用いて指定している方が多いようですが、私は初心者ですので、混乱を防ぐため単純に代入して指定しています。

周波数、デューティ比の指定

OCR1A には動作原理の方で求めた TOP値 を代入してやればおk

OCR1B には指定したデューティ比で切り替わるように TOP値 * デューティ比 の値を代入します。

また、どちらの値16bit の上限である、65535 を超え、オーバーフローすることのないよう、unsigned int でキャストしてやります。

(Arduino には word という型が unsigned int と同じように使えるらしい。しかし、Arduino以外では使われておらず、キモい 不便なのでここではunsigned int を使ってます。)

これで思った通りの出力がされるはずです。

出力を止めたいとき

デューティ比0 にするか、COM1B1/00, 0 にするなり

個人的には後者の方がしっかりしてる気がする(小並感)

終わりに

Arduinoは解説ページや入門書などが多く、電子工作初心者向けにとても良いツールだと思います。

ただ、今回のようにマイコンの世界に一歩踏み入れたようなところでは、解説ページがデータシートと同じように、読む人任せ になっちゃってる部分が多いですね。

データシートは仕方ないにしても、マイコンの世界での”解説ページ”は、小難しく書くことをよしとしている風潮 があるというか、初心者に優しくない気がします…

マイコンの世界に限らず、マイナーな世界はどこもそんな感じですよね…

(単純に自分が理解してることを第三者に伝えるのがダルいだけかもしれませんがw)

解説ページ書くならわかりやすく書きやがれっておもいました(小並感)

私はそういうマイナーな趣味が多いので、わかりやすさを第一に色んな記事を書いていきたいなー、と思ってます。

(そもそも覚書だし、わかりやすくないと意味がない)

分かりづらいところがあったら是非コメントを残していって下さい!

参考:

http://garretlab.web.fc2.com/arduino/inside/arduino/wiring_analog.c/analogWrite.html

https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM

http://usicolog.nomaki.jp/engineering/avr/avrPWM.html

http://www.avrfreaks.net/forum/icr1-vs-ocr1a-top-value-pwm

http://www.avrbeginners.net/architecture/timers/timers.html

Arduino Uno – PWM周波数を”自由に”変更する」への2件のフィードバック

  1. きゅうなな

    マイコン初心者の学生です。ブログ拝見させていただいたところ、非常にわかりやすくためになりました。これを機にarduinoにも手を出してみたいと考えています。
    そこで質問なのですが、このPWM周波数の変更はブラシレスモーターにも適用可能でしょうか?
    手元にHDDのモーターがあるので回してみたかったのですが、どうせならVVVF制御できないものかと思い、いろいろ調べてみました。するとPWM周波数をキャリア波に、duty比をモーターの位相にすることで回転できそうなことがわかりました。またデフォルトのPWM出力であれば、analogWriteを3つ記述することでduty比の異なる3つの信号を送出できることがわかりました。
    しかし本記事を読んだところ、タイマの周波数を変更した場合、出力ピンは10ピン限定だということで、3つの信号が送出できない可能性があることがわかり、すると回転しないのではと考えました。
    3つのタイマはそれぞれ仕様が違うようなので、できれば一つのタイマのみでPWM制御できないかと考えています。
    arduinoでPWMの周波数を変更しつつ、duty比の異なる三相の信号を異なるピンで出力することは可能でしょうか?教えていただけると幸いです。

    返信
    1. S2KTS. 投稿作成者

      きゅうなな さん
      コメントいただきありがとうございます!

      Arduino、というよりも、ATmega328Pの仕様なんですが、1つのタイマからは2ピンまでしか出力できません。
      なので、ご希望の方法では「不可能」、というのが結論になるかと思います・・・。

      また、この記事で、出力ピンが10ピン限定になってるのは、スケッチが煩雑にならないようにするためであって、解説部にあるように、ICRをTOP値として指定するよう、WGM10を0に変更すれば9ピンに出力することもできるはずです (未検証ですが・・・)。

      Duty比の異なる3つの信号をanalogWrite()にてPWM出力することはもちろん可能です。
      ただし、三相モーターを回すには磁界を回転させる必要があります。
      三相モーターの理想的な電流は、正弦波であり、回転させるためにはそれぞれの波の位相をずらす必要がある、ということについてはご存知でしょうか?

      もちろん、綺麗な正弦波を出力するのはそこそこ難しいので、通常は以下のようなデジタル制御方法が用いられます。
      https://www.orientalmotor.co.jp/tech/reference/brushless01/

      カクカク動かすイメージですね(^^)/

      また、仮にanalogWrite()でDuty比の異なる信号を出力したとしても、位相が全て同じ信号が出力されてしまうので、残念ながら、モーターは回転しません・・・。

      従いまして、個人的には、上記のリンクのような制御方法を用いて、デジタル制御するのが最も現実的な方法かなぁと思っています。(実はこのスケッチを書くにあたって、私も三相モーターについて色々調べておりましたw)

      digitalWriteだと速度不足・・・ということでしたら以下の記事のように、AVRマイコンを直接動かすような書き方をすると、高速化することができると思います!
      http://ehbtj.com/electronics/speedup-arduino/

      返信

コメントを残す