LED通信事業プロジェクト エンジニアブログ

F#でラズパイを動かす(7)

DCモーターとPWM 前編

記事更新日 2023年7月11日


はじめに

小さくて安い教育用コンピューターであるRaspberry Pi通称ラズパイですが、このラズパイがここまで広まることとなった最大の理由はプログラムによって電圧を制御することができる物理ピンを装備していることです。この物理ピンによって高度な「電子工作」が可能になります。

今回からのF#ブログは以前お約束したとおりモーターの制御にチャレンジしたいと思いますが、ちょっと愚痴というか宣言からスタートさせてください・・・

このブログの中のF#シリーズというのは、私が個人的にF#の普及に少しでも貢献したいとスタートさせたものだったのですが、F#関連の記事はあからさまに他の記事(例えば無線系の記事とか)に比べるて、ページビューや検索ヒット数が少ないのです。そのため、この半年ぐらいはAI関連ネタを除いてF#記事は書いていませんでした。しかし、Google検索のアルゴリズムが変わったからか、記事が充実したからか、理由は色々あると思いますが、比較的ブログ全体のページビューや検索ヒット数は安定してきたため、この好機にちょっとF#のネタを復活させようと思います。いいんです、ビューが少なくても。私はF#の進化やフリーソフトなんてところで貢献できない程度の低レベルプログラマーではありますが、それでも一応プログラマーの端くれ。これまでお世話になったプログラマー文化というかそういうものにお返しをしなくてはいけません。ですから、会社には大変申し訳ないんですが、ページビューを無視して書かせていただきます。特に.NETでラズパイを動かす系の記事はC#ですら少ないですから、記事を書いてWebに残して、少ないですがこの偉大なるプログラマー文化に貢献させて頂こうと考えております。以上、宣言でした。

モーターの違い

さて、元に戻って今回からはF#でモーターを動かそうという話です。最初にモーターそのものの話をします。電子工作に使うモーターには主に3つの種類があります。モーターによって動かし方が異なるため、今後それぞれの動かし方を記事にしていく予定ですが、まず最初にそれぞれモーターの違い、特徴的なものを説明します。

DCモーター

いわゆる、皆様が思う普通の、昔からあるモーターです。DCモーターという名前の通り、直流電源を繋げば回転するモーターです。子供の頃、電池を繋いで何かを回した経験があるという方も多いと思います。また、ラジコンとかミニ四駆とかに使われていますので、それで馴染みのある方も多いでしょう。マブチモーターと社名で呼んでいる方もいるかもしれません。次に説明するブラシレスモーターとの対比で、ブラシモーターと呼ばれることもあります。

特長は構造が簡単で、動かすのも簡単なこと。構造が簡単なため小型軽量安価になりますし、電池、つまりDC電源を繋げば回るという単純さも魅力です。電子工作においては、その単純さと安さが魅力で、やはり数多く使われています。

fig.1
図1:DCモーター

ブラシレスモーター

ブラシレスモーターは、軸と回転体の間に電気的な接触がないモーターです。上に挙げたDCモーターは、回転部にコイルがあり電気が流れるため、どうしても電気的な接触部分(ブラシと呼びます)が存在します。一方、ブラシレスモーターは外側にコイルがあり、回転部は永久磁石となっているため、回転部に電気的な接触はありません。ブラシはどうしても摩擦などで摩耗し、モーター故障の原因となります。一方、ブラシレスモーターは、そういったものがないため高寿命です。ブラシレスモーターといえば世界トップシェアのニデック(旧日本電産)が有名ですね。交流で動くためACモーターと呼ばれることもあります。(現在は、ブラシレスDCモーターというものもありますが。)

ただし、ブラシレスモーターを動かすためには適切な波形を作る必要があり、単純に電圧をかければ回るというものではありません。また、構造も複雑となるため重く大型になりがちです。しかし、その複雑さのおかげで、モーターの角度や速度を細かく制御することができます。

以上の特徴から、かなり多くの用途、特に信頼性が求められる要素ではブラシレスモーターが使われています。例えば、EV、HEVのモーターや(一定サイズ以上の)ドローンのプロペラは必ずブラシレスモーターが使われます。むしろ、現在では大きさや価格がDCモーターしか選択できない場合を除いて、ほとんどの商用用途ではブラシレスモーターが採用されているというのが現状です。

fig.2
図2:ブラシレスモーター

サーボモーター

上の二つは、モーターの「原理」で分類された名前でしたが、このサーボモーターというのは「モーターの機能」を表した名前です。サーボモーターは「角度」を指定し、その角度になるまで動きます。指定された角度になれば止まります。つまりは回らないのです。サーボモーターの中身は普通の回転モーターと、角度検出装置が組み合わされてできています。サーボモーターのサイズや価格によって中のモーターがDCなのかブラシレスなのか変わり、また角度検出装置も単に回転抵抗なのか、磁気センサーか、光学センサーかによっても色々と変わります。いずれにせよ、何らかのモーターで、何らかの方法で決められた角度まで回すのがサーボモーターです。

サーボモーターは特定の角度に設定できるため、工場のロボットアームなどの可動部に使われます。電子工作でも用途が沢山あるためサーボモーターはよく使われます。

ちなみに、下の写真のサーボモーターですが、実は幅が耳除いて2cmしかありません。これぐらい小さいと、個人趣味レベルサイズのロボットでも、関節とかちょっとした可動部に使うことができますよね? 後に説明しますが、その特性からこのモーターは「同時に多数動作する」ことが求められるため、そのような制御を行います。

fig.3
図3:サーボモーター

モーターとモータードライバー

ラズパイでモーターを動かすとなると、Lチカ(LED点灯)と同じように、GPIOに直接モーターを繋げば動くと考える方が多いかも知れません。確かに、一般的なホビー用のDCモーターは3.3V駆動が多く、一方のGPIOの電圧も3.3Vですから、電圧はバッチリ合っていて動かせそうです。しかし、GPIOでモーターは動かせません。問題となるのはGPIOの電力。なんせ16mAしか流せない。ホビー用の小型DCモーターですら、おおよそ200mA程度は必要なので、電力が一桁足りないわけです。いや、もちろんとても小型なモーターだったら動くかも知れませんが、それじゃあ電子工作にすらならない。つまり、GPIOでモーターは動かせないと考えて良い訳です。

一方で、GPIOにはモーターの制御という目的もあります。そしゃ、折角ラズパイというコンピューターでモーターを動かすのに、ON/OFFだったり、回転方向や速度だったりを制御できなきゃ意味ないでしょ?GPIOはそういった意味で使えば良いわけです。

それでは、DCモーターに、制御信号用の入力と電源入力があるのかといえばそんな事はなくて、DCモーターに入れられるのは、あくまでDCの電源のみ。じゃあ、どうすれば良いの? 答えは簡単。モータードライバーというものを使えば良い、というか使うしかない。DCモーターに限らず、ラズパイでモーターを動かす場合、必ずモータードライバーが必要だ、ということを覚えておいてください。ちなみに、モーター毎に使えるモータードライバーは異なります。

実際のモータードライバーの写真が図4。接続図としては図5になります。このモータードライバーは東芝セミコンダクターのTB6612FNGDCというドライバーICを使っていて、DCモーターを2つ同時に制御できます。つまり、2系統のGPIOを別々に入力して、それぞれ別々に出力してくれます。

fig.4
図4:DCモータードライバー
fig.5
図5:モーター接続図

PWM

さて、実際のモータードライバーはどういうことをしているのでしょう? DCモーターを制御するとして、DCモーター自体には電圧をかける線が2本出ているだけ。そんなモーターで制御できる内容は、

  • 回転する速度、つまりは電力
  • 回転する方向、つまりは電圧をかける極性

の2つだけになります。モータードライバーとしてはこの二つを制御するだけですので、さほど難しいことではありません。ラズパイ側からGPIO経由でどうやって情報を伝えるのか?ここがポイントです。GPIOは高速にON/OFFできるものの、電圧も電流も極性も変える能力は持ちません。純粋なデジタル端子です。一方モーターの回転方向は「右回り」か「左回り」の2択です。だから、これはGPIOのON/OFFで切り替えれば良いですよね。しかし、回転速度は連続量です。ON/OFFしかないのにどうやって連続量を出すのか?

これも難しい話ではなく、モーター制御の世界、少なくとも電子工作のモーター制御の世界では標準の制御方法があるんです。これが「PWM」というやつ。Pulse Width Modulationの略。直訳すれば「パルスの幅による変調」ってことになるけど、別の言葉で言えばデューティー比ってやつです。デューティー比については、次の図を見て貰った方が速いです。

fig.6
図6:デューティー比

図を見ていただければ分かるとおり、デューティー比とはONになっている時間の割合。図の通り、全体の25%しかONになっていないのなら、デューティー比は25%ですし、半分なら50%、常にONなら100%です。ON、OFFだけで簡単に作れるから、デューティー比で速度を表現するのはとてもGPIOに向いている訳です。で、なぜモーターはデューティー比なのかというと、これも簡単。デューティー比は、一周期あたりの「電力(量)」を表現しているからです。例えば、デューティー比100%で100Wとするならば、デューティー比50%だと50Wとなります。単位時間あたりの電力量が半分な訳ですから。ということで、モーターの速度をデジタルで表現したい場合、デューティー比で行うというのが標準となっています。そして、パルスでデューティー比を伝える方式をPWMと呼んでいます。

PWMの実装

PWMを実装しようとした場合、「一定時間まって切替」を繰り返すループするコードを作る必要があります。まあ、F#なのでループを作る際は他の言語のようなFor文ではなく、再帰関数で作りますが。で、例えば一定時間でGPIOをON/OFFするF#のコード(実際の関数部分のみ)の例を次に載せます。この関数の引数は、ONの時間、OFFの時間、ループの回数です。

open System open System.Diagnostics //ストップウォッチ用 open System.Device.Gpio //GPIO用 // 経過時間の実験 let sleepLoop (intervalmsecON: int) (intervalmsecOFF: int) (count:int) = let sw = new Stopwatch() //ストップウォッチの宣言 let gpio = new GpioController() //GPIOの宣言 let gpioPinNo = 14 //仮番号 gpio.OpenPin(gpioPinNo, PinMode.Output) //GPIOを出力でOpen //ループさせる再帰関数 let rec mainloop count = if count <= 1 then // カウンターが1になったら終了 sw.Stop() //ストップウォッチストップ else // GPIOの開閉(カウンター奇数ON、偶数OFF) if count % 2 = 0 then gpio.Write(gpioPinNo, PinValue.Low) //OFF Threading.Thread.Sleep(intervalmsecOFF) //OFFの待ち時間 <==注目 else gpio.Write(gpioPinNo, PinValue.High) //ON Threading.Thread.Sleep(intervalmsecON) //ONの待ち時間 <==注目 mainloop (count - 1) //再帰呼び出し // ループ動作開始 sw.Reset() sw.Start() //ストップウォッチスタート do mainloop count //再帰関数スタート Console.WriteLine("経過時間" + sw.ElapsedMilliseconds.ToString()) // 終了処理 gpio.Dispose() ()

今回は、コードの説明がメインじゃないので中身を詳しくは説明しませんが、途中に2箇所あるThreading.Thread.Sleep(ミリ秒)がこのコードのポイントです。.NETでコード中に敢えて待ち時間を作るのに、最も簡単な方法はSleep(ミリ秒)メソッドを使うことです。括弧内、つまり引数に待ち時間をミリ秒で入れると、この入れた時間だけプログラムが停止します。これでONの時間やOFFの時間を調整します。「時間を待つ」ということで検索すれば、おそらくこのメソッドが出てくるため、使ったことがある人も多いのではないでしょうか?

ただ・・・ この手のコードを書いた人なら分かるんですけど、本来であればずっと等間隔で刻んでいってくれるんですけど、動かすとなかなか上手くいかないんです。結構、時間がずれる。当たり前です。待ち時間以外にも、処理時間があるわけで、それが毎回毎回誤差として載っかってくる。実際、このコードをそのまま動かすと、おおよそ1%は遅くなります※1。これに、他の重い処理も混ざってくるとさらに遅れてきます。とはいえ、全体の1~2%の遅れぐらいさほど影響ないのでは?と思いますよね。実は以前、この「ループでPWMを作る」コードで、モーターを動かすラジコンのようなアプリを作ったことがあるんです。当時の私のコードも悪かったんでしょうが、実際動かしてみると結構違和感が出るんですよ。何というか、なめらかじゃないんです、モーターの動きが。ガクガクとまでは言わないが、スーって感じでは決してない。残念ながら、これでは使えないということで、そのコードはボツとなりました(涙)。

じゃあ、どうしましょうか?もちろんプログラミング的にはSleepを使わない別の方法※2もあるんですが、結構複雑になり初心者お断りになります。何か良い方法は無いものか?もっと簡単に、もっと安定してPWMを出す方法が・・・

ハードウエアPWM

皆さん安心してください。ありますよ!ラズパイには、簡単で、安定したPWMがあるんです。その名も「ハードウエアPWM」。そう、ラズパイには、ハードウエア的にPWMを発生させる仕組みが備わっているんです!!そのハードウエアPWMは、文字通りハードウエアがPWMを生成する機能です。中の詳しい仕組みまでは分かりませんが、アプリの動き、つまりはCPUの負荷と関係無く、別ハードウエアが設定通り等間隔でPWMを出してくれます。ですから、それはそれはド安定。モーターもなめらか~に動いて大満足。しかも、コーディングもとっても簡単。上のようなわずらわしいコードは一切不要。すばらしい。

そんな便利なハードウエアPWMなんですが、実は余り有名ではありません。実際自分も、上のガタガタの自家製PWMが上手くいかずに色々と調べた過程で、この存在を知ったぐらいですから。ではなぜ、ハードウエアPWMの知名度が低いのか。最大の原因は、「使えるようにするのに手間がかかる」ということでしょう。

その手間というのが/boot/config.txtいう起動時にOSが読む設定ファイルへの書き込み。まあ、書き込むだけなので作業自体はそれほど手間じゃないんですが、困難は別のところにあります。それは「どれを書き込めば良いのか分からない」という事。

多分.NETでハードウエアPWMをやろうとすると、最終的にたどり着くのはマイクロソフトのこのページだと思います。GitHub上の.NET公式の一部なのですが、あまりラズパイ電子工作やっていない人だと、これだけ読んでも意味が分からないと思います。GitHub上のページなので、なんかあったらソースコード見ろって雰囲気もあり、とっても不親切なページです。今回は、その.NET公式ページに変わりまして、初心者でも分かるようにハードウエアPWMの設定を説明していきたいと思います。

ハードウエアPWMの設定

まず、大前提として、先ほど書いたとおり、ハードウエアPWMを有効化するには、設定ファイルである/boot/config.txtへハードウエアPMWを設定するための命令を書き込む必要があります。で、前述のGitHubのページで書き込む内容を調べると次の表が出てきます。最後のdtoverlayという項目が実際に書き込む内容なんですけど、これじゃ何をどう書き込めば良いのか分からないですよね。順を追って説明します。

表1:1チャンネル使用の場合

PWM GPIO Function Alt dtoverlay
PWM0 12 4 Alt0 dtoverlay=pwm,pin=12,func=4
PWM0 18 2 Alt5 dtoverlay=pwm,pin=18,func=2
PWM1 13 4 Alt0 dtoverlay=pwm,pin=13,func=4
PWM1 19 2 Alt5 dtoverlay=pwm,pin=19,func=2

表2:2チャンネル使用の場合

PWM0 PWM0 GPIO PWM0 Function PWM0 Alt PWM1 PWM1 GPIO PWM1 Function PWM1 Alt dtoverlay
PWM0 12 4 Alt0 PWM1 13 4 Alt0 dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4
PWM0 18 2 Alt5 PWM1 13 4 Alt0 dtoverlay=pwm-2chan,pin=18,func=2,pin2=13,func2=4
PWM0 12 4 Alt0 PWM1 19 2 Alt5 dtoverlay=pwm-2chan,pin=12,func=4,pin2=19,func2=2
PWM0 18 2 Alt5 PWM1 19 2 Alt5 dtoverlay=pwm-2chan,pin=18,func=2,pin2=19,func2=2

まず、書き込む内容となっているdtoverlayから説明。dtoverlayは「Device Tree Overlay」の略で、つまりでデバイスに何か追加するためのコマンド、と考えてください。OSが設定ファイルを読み込む際にdtoverlayコマンドを読み込むと、dtoverlayで書かれたデバイスを追加してくれるってことです。

次に設定項目ですが、その前に一つ。ハードウエアPWMは、物理的には「GPIOのピン」を使います。ラズパイの各GPIOピンは標準の設定だと通常のGPIOとして機能します。Lチカをやったあれです。しかし、ラズパイはそのピンをGPIO以外の機能へ切り替えることができます。これをAlternative Funciton(代替機能)と呼んでいるわけですが、これを設定等で切り替えることによって、GPIOのピンを、通信に使ったり、オーディオに使ったりといったことができるんです。ハードウエアPWMも、同じようにGPIOピンのAlternative Funcitonを切り替えることによって使用できるようになります。ただし、これらのAlternative Funciton(代替機能)は特定のピンにしか割り当てられていません。例えば、I2Cという通信を行うためのピンはGPIOピンの番号で、3、4番のみにしか割り当てられていません。ハードウエアPWMも決まっています。それが、表1にあるGPIO列の数字。GPIO番号(物理ピン番号ではない)で、12、13、18、19の4ピンがハードウエアPWMとして設定できます

さて、Alternative Funciton、すなわち代替機能ですが、ラズパイではAlt0からAlt5までの6種類が用意されています。これは標準のGPIOと合わせて各ピンには7種類の機能を持たせることができることを意味します。とはいえ、7つの機能全て割り振られているピンは数えるほどしかないですが。で、表にあるAlt列はハードウエアPWMがAlt0から5までのどれに割り振られているのかを表しています。表からはGPIO12、13はAlt0、GPIO18、19はAlt5に割り当てられている事が分かります。そして、表のFunction列には番号が振ってありますが、これはAlt番号と1対1で割り振られているFunction番号です。ここで出てくるのは、Alt0 = Function4Alt5 = Function2ですので、これだけ理解していれば大丈夫です。

ハードウエアPWMは、実は2つ同時までしか使用できません。ハードウエアPWM用チップが2つしかないのです。ですから、PMWには”0”と”1”の2チャンネルが存在しています。表1のPWM列や表2のPWM0列、PWM1列はそれを表しています。そして、PMW0にはGPIO12、18が、PWM1にはGPIO13、19が割り当てられています。同じPWMチャンネルの2つのピンは同時に使用することができません。例えばGPIO12とGPIO18は同時に使用できません。表2は、2チャンネル同時使用の時のdtoverlayを表していますが、ピンが4つあるのに組み合わせが4つしかないのは同チャンネルのGPIOは同時に使用できないからです。

で、ここまでの説明でハードウエアPWMに必要なdtoverlayの内容がおおよそ理解できたと思います。

  • 1チャンネルの時は、ピン番号とFunction番号(=Alt番号)
  • 2チャンネルの時は、PWMチャンネル、ピン番号、Function番号 x 2

となっています。

どのピンでハードウエアPWMを使用するかですが、これは自由に決めて問題ありません。他に使いたい機能があり、それがハードウエアPWMのピンと被っていたら考えなくてはいけませんが、それ以外は、性能差があるわけでも、配線に大きな差があるわけでもありませんので。

簡単なハードウエアPWM実装

長々説明しましたが、ハードウエアPWMを実際に動かすのは超簡単。たったこれだけで動作します。しかも安定的に。

open System.Device.Pwm let hwpwmtest() = let pwm = PwmChannel.Create(0, 0, 100) //宣言(chip=0, channel=0, f=100Hz) pwm.DutyCycle <- 0.5 //デューティー比設定 0.0~1.0 pwm.Start() //開始 // 何かする pwm.Stop() //停止

詳しくは次回説明しますが、動かすにはPwmChannel.Createでオブジェクトを生成、デューティー比を設定し、スタートさせるだけ。なんて簡単なんでしょう?デューティー比は動作中、いつでも変更できますので、速度を調整も自由自在です。ソフトウエアでのPWM実装のように時間を考えて、割合を考えて、なんてことは一切しなくていいです。すごい、すばらしい。

Windows派のための/boot/config.txtの編集

さて、ここで/boot/config.txtファイルの編集についてです。普段Windowsしか扱わない我々が、ラズパイを操作する上で引っかかるのが「権限」の問題です。いくつかの操作やファイルは権限がないと編集できないんですよね。今回使用する設定ファイルである/boot/config.txtというファイルは権限がないと編集ができない系のファイルなんです。で、そのconfig.txtをファイルマネージャーから開き編集しようとするも、最後のところで「権限がない」とか言われて保存もできない・・・ 権限の必要なファイルの編集方法は色々ありまして、なれている人であればターミナル開いて直接編集※3 なんでしょうが、Window族にしてみると、操作が分からなすぎて、ショートカットのつもりが変な字が挿入されたりして、設定ファイルをそんな状態で編集するのはさすがに怖い訳ですよ。

というわけで、Windows派でも簡単な権限必要ファイルの編集の仕方を書き残しておきます。Linuxバリバリの方には邪道と言われるでしょうが、Windows慣れしている人なら最も自然な方法です。

  1. Windowsでいうメモ帳にあたるMousePadを使用します。しかも、それを監視者権限(sudo)で起動します。sudo mousepadと打てば管理者権限のMousePadが起動します。上に赤い「警告」バーが出ていれば成功です。
  2. ファイルマネージャーを使って/boot/config.txtファイルを探します。そして、1のMousePadへドラッグアンドドロップして開きます。
  3. config.txtファイルの一番最後に、上記の表1、表2にあるdtoverlay例のどれかのテキストをコピペし保存します。
  4. ラズパイを再起動すれば反映されます。

こんな流れです。Windows派の方ならなんら違和感ないはずです。図もつけておきますので、これを見れば問題なく操作できるはずです。尚、この手順は他の権限付きファイルの編集にも使えますので是非覚えておいてください。

fig.7
図7:config.txtの編集方法

さいごに

いかがでしたでしょうか?今回はモーター編としてDCモーターとPWMの話をしました。PWMをソフトウエア的に実装するのは難しくありませんが、DCモーターをなめらかに動かすためには(F#関係無く)ハードウエアPWMは必須です。覚えてくださいね。

次回は、接続方法やDCモーターを動かすための実際のコードを説明したいと思います。自分の書いたコードで「物理的なものが動く」というのは、アプリとかPC上で完結するものとは違いちょっと感動します。次回もお楽しみに。


※1; Raspberry OS(Linux系)での測定値。WindowsはOSの仕様で15msec程度の時間分解能しかないため、Sleepメソッドではミリ秒単位での細かい制御はできない。

※2; 一般的には、時間のカウンターを常時監視したり、Sleepより精度の高いストップウォッチを使用したりして、正確な周期を刻めるようにする(Untiyを使用しない場合)。

※3; viエディターというアプリをつかい、ターミナル(コマンドプロンプト)上で編集できる。