Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
.NET対応組み込みデバイス「Netduino」入門(15)

.NET対応組み込みデバイス「Netduino」入門(15)

Netduinoでロボットコントロール(後編) ― 16個のサーボモーターを一度に制御する

2016年2月5日

連載最終回。前回に引き続き、Netduinoを使ってPLEN.Dを制御してみる。今回は16個のサーボモーターを制御できるボードを使って複雑なロボットの動きを実現する。

Microsoft MVP for Windows Development 初音 玲
  • このエントリーをはてなブックマークに追加

 PLEN.DPLEN Projectが開発しDMM.make ROBOTSから2015年5月に発売が開始された全長25cmの運動神経抜群な組み立て式のロボットだ。全部で18軸のサーボモーターを搭載し、PWM信号△で制御している。

 前回はそのモーターの2つをNetduinoに接続して制御してみたが、今回は合計16個のサーボモーターを一度に制御してみたいと思う。

PWM制御ボードについて

Adafruit 16-Channel 12-bit PWM/Servo Shield

 Adafruit 16-Channel 12-bit PWM/Servo Shield(図1)は、Arduinoの上に乗せて(このようなボードをシールドと呼ぶ)I2Cで接続して使うボードだ。全部で16個のサーボモーターをI2Cインターフェースで制御でき、また、サーボモーターの動作用電源もArduinoからではなく専用の電源端子から供給できるようになっている。

Adafruit 16-Channel 12-bit PWM/Servo Shield
図1 Adafruit 16-Channel 12-bit PWM/Servo Shield

 I2Cなので別々のI2Cアドレスを設定することで、最大62個のAdafruit 16-Channel 12-bit PWM/Servo Shield同時使用が可能で、この場合は、全部で992個のサーボモーターを一度に制御する事すら可能になる。

 ピンのはんだ付け箇所は多いが、一度作ってしまえば余計な配線もなくNetduinoと接続できる。

Adafruit 16-Channel 12-bit PWM/Servo Driver

 シールドとしてNetduinoに直接乗せなくてもいいのであれば、もっと小型のAdafruit 16-Channel 12-bit PWM/Servo Driver(図2)も使い勝手がいい。

 最大接続数の性能やソースコードもAdafruit 16-Channel 12-bit PWM/Servo Shieldと同じなので好みで選択してもいいだろう。

Adafruit 16-Channel 12-bit PWM/Servo Driver
図2 Adafruit 16-Channel 12-bit PWM/Servo Driver

I2Cコマンドについて

 それでは、今回のシールド/ドライバーの制御用コントローラーであるPCA9685のデータシートから使い方をひもといていこう。

 PWMは一定の周期で5V0Vを繰り返す。この繰り返し間隔をPWM周波数といい、制御する対象によって決まってくる。今回は60Hzの動作周波数としたい。このような設定をしたいときに使うコマンドが、PWM周波数設定コマンドだ。

PWM周波数設定コマンド

図3 PWM周波数設定コマンドフォーマット(2bytes)

制御バイトは10進数で254(16進数でFE)となり、PWM出力周波数のプリスケーラー(=分周回路)へのコマンドを読み書きするための、PRE_SCALEという名前のレジスタを意味している。PRE_SCALEレジスタへのコマンド書き込みは、MODE1レジスタ(=10進数/16進数で000)のSLEEPモードがオン(後述)の場合にはブロックされる。

 コマンドバイトへの設定値は、設定したい周波数fから次の式で計算する。

設定値=ROUND(25MHz/(4069×f[Hz]))-1

 例えば、60Hzであれば設定値は100となる。

 周波数設定コマンドを投入するためには、Mode register 1(=MODE1レジスタ)のSLEEPモードを設定してから、周波数設定コマンドを(PRE_SCALEレジスタに)投入し、次にSLEEPモードを解除する。

Visual Basic
'' レジスタ番号
Private Const PCA9685_MODE1 As Byte = &H0
Private Const PCA9685_PRESCALE As Byte = &HFE
……中略……

public void SetPWMFreq (float freq)
{
  Dim prescaleval As Single = 25000000  '' オシレーターのクロック周波数
  prescaleval /= 4096
  prescaleval /= freq
  Dim prescale = CType(prescaleval - 1, Byte)

  WriteToRegister(PCA9685_MODE1, &H31)         '' SLEEPモードオン
  WriteToRegister(PCA9685_PRESCALE, prescale)  '' PWM出力周波数の設定
  WriteToRegister(PCA9685_MODE1, &H21)         '' SLEEPモードオフ
  ……中略……
}
C#
// レジスタ番号
private const byte PCA9685_MODE1 = 0x0;
private const byte PCA9685_PRESCALE = 0xFE;
……中略……

Public Sub SetPWMFreq(freq As Single)
  float prescaleval = 25000000;       // オシレーターのクロック周波数
  prescaleval /= 4096;
  prescaleval /= freq;
  var prescale = (byte)(prescaleval - 1);

  WriteToRegister(PCA9685_MODE1, 0x31);         // SLEEPモードオン
  WriteToRegister(PCA9685_PRESCALE, prescale);  // PWM出力周波数の設定
  WriteToRegister(PCA9685_MODE1, 0x21);         // SLEEPモードオフ
  ……中略……
End Sub
リスト1 周波数設定コマンドシーケンスコード(上:PWMServoLib.vb、下:PWMServoLib.cs)抜粋

MODE1レジスタにおけるSLEEPモードのオン/オフは、4番ビットを2進数で0100に設定することで指定できる。16進数で31はこのbitがオン、21はオフとなる。なお、いずれも0番ビットと5番ビットをオンにしているので注意してほしい。これらのビットの意味はデータシートを参照されたい。

WriteToRegisterメソッドは、制御バイトとコマンドバイトの2bytes分を書き込むために独自に実装したもの。

PWM出力コマンド

 今回使用している制御ボードでのPWM出力は12bitデータで、PWMのオンとオフのタイミングが指定できる。つまり、PWM周波数設定コマンドで指定した周波数を4096で分割したタイミングで制御が可能となる。例えば、オンタイミングを0として、オフタイミングを4096にすると、60HzのPWMが出力できる。

 PWM出力コマンドのフォーマットは図4のとおりで、出力ピン(=16チャンネル分の16本がある)に応じたレジスタ番号の1byteを指定し、それに続いて2bytes(=12bitデータで、そのうち4bit分は使用しない)のPWMがオンになるタイミング、2bytesのオフになるタイミングを設定することで実現する。

図4 PWM出力コマンドフォーマット(5bytes)

 注意点としては、各2bytes値が下位バイトから設定する点だろう。具体的なコードは次のようになる。

Visual Basic
'' レジスタ番号
Private Const LED0_ON_L As Byte = &H06  '' 先頭0番ピンのレジスタ番号。残りの15ピンは、+4ごとの番号になる
……中略……

Public Sub SetPWM(num As Byte, pwmOn As UShort, pwmOff As UShort)
  Dim bOn = BitConverter.GetBytes(pwmOn)
  Dim bOff = BitConverter.GetBytes(pwmOff)
  Dim data = New Byte() {CType(LED0_ON_L + 4 * num, Byte), bOn(0), bOn(1), bOff(0), bOff(1)}
  Write(data)
End Sub
C#
// レジスタ番号
private const byte LED0_ON_L = 0x06;  // 先頭0番ピンのレジスタ番号。残りの15ピンは、+4ごとの番号になる

……中略……

public void setPWM(byte num, ushort pwmOn, ushort pwmOff)
{
  var bOn = BitConverter.GetBytes(pwmOn);
  var bOff = BitConverter.GetBytes(pwmOff);
  var data = new byte[] { (byte)(LED0_ON_L + 4 * num), bOn[0], bOn[1], bOff[0], bOff[1]  };
  Write(data);
}
リスト2 周波数設定コマンドシーケンスコード(上:PWMServoLib.vb、下:PWMServoLib.cs)抜粋

サーボモーターSG90での動作確認

 今回のシールドやドライバーは、サーボモーターの電源を外部から共有するようになっている。そこで、ブレッドボード用5V/3.3V電源ボード Micro-B版を使ってUSBから5Vを供給するように配線する。

 シールドならNetduinoの上に設置すれば設置完了だ。ドライバーの場合は次のように配線を行う。

図5 ドライバー配線例(ドライバー給電部分は省略)

 配線ができたならば、ゆっくりサーボモーターを動かすコードを実行してみよう。

Visual Basic
Sub Main()
  Do While (True)
    Using pwm As New PWMServoLib()
      pwm.SetPWMFreq(60)
      For i As Short = 150 To 700 - 1
        For pwmnum As Byte = 0 To 15
          pwm.SetPWM(pwmnum, 0, i)
        Next
      Next
      Thread.Sleep(500)
      For i As Short = 700 To 150 - 1 Step -1
        For pwmnum As Byte = 0 To 15
          pwm.SetPWM(pwmnum, 0, i)
        Next
      Next
    End Using
  Loop
End Sub
C#
public static void Main()
{
  while (true)
  {
    using (var pwm = new PWMServoLib())
    {
      pwm.SetPWMFreq(60);
      for (ushort i = 150; i < 700; i++)
      {
        for (byte pwmnum = 0; pwmnum < 16; pwmnum++)
        {
          pwm.setPWM(pwmnum, 0, i);
        }
      }
      Thread.Sleep(500);
      for (ushort i = 700; i > 150; i--)
      {
        for (byte pwmnum = 0; pwmnum < 16; pwmnum++)
        {
          pwm.setPWM(pwmnum, 0, i);
        }
      }
      Thread.Sleep(500);
    }
  }
}
リスト3 SG90動作確認コード(上:Module1.vb、下:Program.cs)抜粋

【動画】SG90動作確認

PLEN.Dのサーボモーター構成

 PLEN.Dは全部で18個のサーボモーターが使用されている。1番から18番までがどこの動作を担当しているかを一覧にすると次のようになる。

サーボモーター番号用途
1 左腕(前後)
2 左足(ひねり)
3 左腕(左右)
4 左ひじ
5 左足(左右)
6 左足(上下)
7 左ひざ
8 左足首(上下)
9 左足首(左右)
10 右腕(前後)
11 右足(ひねり)
12 右腕(左右)
13 右ひじ
14 右足(左右)
15 右足(上下)
16 右ひざ
17 右足首(上下)
18 右足首(左右)
表1 PLEN.Dのサーボモーター構成

Netduinoとの接続

 今回、18個のサーボモーターに対して16チャンネルのPWM出力を割り当てる。サーボモーターは動かさないときでもその位置を指定したPWM出力を与えていないと外力により位置が動いてしまう。その特性を考えると自重を支える下半身関連は全て制御下に置かざるを得ないので、比較的影響の少なそうなPLEN.Dの4番(左ひじ)と13番(右ひじ)以外は全て接続することにする。

図6 NetduinoとPLEN.Dとの接続(白線側が上にくるように接続)

PLEN.Dプログラミング

 PLEN.Dだけではないがラジコン飛行機も含め、多数のサーボモーターを使う場合には、事前にセンタリング位置のばらつきを合わせる作業が必要になってくる。今回も、まずは「気を付け」で直立不動になるPWMパルス間隔を、SG90動作確認サンプルを使ってブレークポイントで止めながら調査した。ブレークポイントで止めれば変数値を確認できるので、Visual Studioのリモートデバッグ機能はとても便利である。

 PWMパルス間隔が決まったら、左右の手を元気よく振らせてみよう。

Visual Basic
Sub Main()
  Dim pwmBase = New UShort() {380, 460, 460, 380, 380, 550, 430, 360, 390, 400, 330, 400, 440, 300, 390, 400}

  Using pwm As New PWMServoLib()
    pwm.SetPWMFreq(60)
    ''直立
    For index As Byte = 0 To 15
      pwm.SetPWM(index, 0, pwmBase(index))
    Next
    Thread.Sleep(1000)
    ''
    pwm.SetPWM(Byte.Parse(EnumBorn.LeftArmLR.ToString()), 0, 440)
    pwm.SetPWM(Byte.Parse(EnumBorn.LeftArmLR.ToString()), 0, 350)
    Do While (True)
      pwm.SetPWM(Byte.Parse(EnumBorn.LeftArmLR.ToString()), 0, 220)
      pwm.SetPWM(Byte.Parse(EnumBorn.RightArmFB.ToString()), 0, 220)
      Thread.Sleep(500)
      pwm.SetPWM(Byte.Parse(EnumBorn.LeftArmLR.ToString()), 0, 560)
      pwm.SetPWM(Byte.Parse(EnumBorn.RightArmFB.ToString()), 0, 560)
      Thread.Sleep(500)
    Loop
  End Using
End Sub
C#
public static void Main()
{
  var pwmBase = new ushort[] { 380, 460, 460, 380, 380, 550, 430, 360, 390, 400, 330, 400, 440, 300, 390, 400 };

  using (var pwm = new PWMServoLib())
  {
    pwm.SetPWMFreq(60);
    // 直立
    for (byte index = 0; index < 16;index++)
    {
      pwm.setPWM(index, 0, pwmBase[index]);
    }
    Thread.Sleep(1000);
    //
    pwm.setPWM((byte)EnumBorn.LeftArmLR, 0, 440);
    pwm.setPWM((byte)EnumBorn.RightArmLR, 0, 350);
    while (true)
    {
      pwm.setPWM((byte)EnumBorn.LeftArmFB, 0, 220);
      pwm.setPWM((byte)EnumBorn.RightArmFB, 0, 220);
      Thread.Sleep(500);
      pwm.setPWM((byte)EnumBorn.LeftArmFB, 0, 560);
      pwm.setPWM((byte)EnumBorn.RightArmFB, 0, 560);
      Thread.Sleep(500);
    }
  }
}
リスト4 PLEN.D動作確認(上:Module1.vb、下:Program.cs)抜粋

EnumBorn列挙体は表1の内容をそのままの順番で値として保持している(サーボモーターの先頭1番は、ピン番号としては先頭0番に対応しており、つまり値としては0スタートになるので注意してほしい)。

【動画】PLEN.D動作確認

まとめ

 Netduinoでのロボット制御はいかがだっただろうか。サイズ的に収納は難しかったが、例えば、Netduinoの代わりに.NET Micro Frameworkが動作するG30TH ModuleAdafruit 16-Channel 12-bit PWM/Servo Driver、そしてbluetooth接続用にSparkFun Bluetooth Modemを組み合わせれば、PLEN.Dの.NET Micro Framework化も十分可能かもしれない。興味のある方はPLEN.Dを購入して挑戦してみるのもよいだろう。

 Netduinoをはじめとする.NET Micro Framework動作機器は、センサーからの入力を一次加工してクラウドに送信したり、一時的にMicroSDカードに保存してbluetoothを使ってスマホ経由でクラウドに送信したりなど、IoT時代になってますます注目されつつある。現に、.NET Micro Frameworkの次の版ではUWP(Universal Windows Platform) APIの一部やAllJoynなどのWindows 10近接通信がサポートされる予定になっている。

 Windows 10 IoTが登場し、.NET Micro Frameworkはどうなるのかと心配した時期もあったが、今後もWindows 10 IoTよりもさらに小さな組込み用として生き残っていくだろう。

 IoT時代には、センサーデバイスのハード知識、ノイズ対策、電源回りの知識を持った人間が全体設計をすることで、システムの総合的パフォーマンスを向上させることができるだろう。私はソフトだけ、私はハードだけの縦割りではなく、お互いが相手の分野を理解できるようになるのが理想的だ。本連載がソフトウェア開発者を対象にハードを作る楽しさを念頭に置いて、ソフトもハードも取り上げてきたのにはそういった意図があった。もし、この連載をきっかけにそういった技術者がふえてくれたら本望である。

※以下では、本稿の前後を合わせて5回分(第12回~第16回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

.NET対応組み込みデバイス「Netduino」入門(15)
12. Bluetoothで遠隔操作しよう

BluetoothでPCと連携し、電源もバッテリー給電とすることで、移動体などへの搭載も可能なコードレス仕様のDCモーター制御を実現してみよう。

.NET対応組み込みデバイス「Netduino」入門(15)
13. littleBitsではんだ付けなしの電子工作を実現

磁石で電子回路をつないで電子工作が行えるlittleBits。Netduinoと組み合わせると、どのような電子工作が実現できるのか? その可能性を探る。

.NET対応組み込みデバイス「Netduino」入門(15)
14. MESHとNetduinoでiOSデバイスとの連携を実現

SONYのスマートDIYキット「MESH」とは? さまざまな連携が実現可能なMESHを使って、NetduinoとiPadを連携をさせてみよう。

.NET対応組み込みデバイス「Netduino」入門(15)
15. Netduinoでロボットコントロール(前編) ― PLEN.Dを直接動かす

組み立て式のロボット「PLEN.D」に使われているサーボモーターを、NetduinoのPWM出力で制御してみよう。

.NET対応組み込みデバイス「Netduino」入門(15)
16. 【現在、表示中】≫ Netduinoでロボットコントロール(後編) ― 16個のサーボモーターを一度に制御する

連載最終回。前回に引き続き、Netduinoを使ってPLEN.Dを制御してみる。今回は16個のサーボモーターを制御できるボードを使って複雑なロボットの動きを実現する。

サイトからのお知らせ

Twitterでつぶやこう!