Raspberry Pi とモーターを繋げて Ruby で動かす

 今までは Raspberry Pi と他のセンサー類を組み合わせてセンサー値を読み取り、データとして使える形にはしていましたが、ハードウェアのアウトプットとしては LED を光らせるぐらいでした。今回はモーターを動かすことに挑戦してみたいと思います。

電池ボックスとモーターの配線準備

 今回は下記のモーターと電池ボックスを使用します。

akizukidenshi.com

akizukidenshi.com

 いずれもリード線はついていますが、そのままではブレッドボードにさすには不便なので、下記サイトを参考にリード線を加工しました。

ブレッドボードで電子回路の実験をするために乾電池ホルダーのリード線をジャンパワイヤのように端子化するmatsumotoyoshio.wordpress.com

 加工に使った材料は下記の二つです。

f:id:akanuma-hiroaki:20171001150412j:plain:w450

 最終的に下記のようにリード線の先端にピンがついた形になりました。

f:id:akanuma-hiroaki:20170930084942j:plain:w300

ブレッドボードで配線する

 今回モーターを回すためにさらに必要なものとして、下記のモータードライバ(TA7291P)を使用しました。

f:id:akanuma-hiroaki:20170930085035j:plain:w300

 これらを動作確認用の LED と組み合わせて、下記の図のように配線します。ちなみに TA7291P の Fritzing 用の画像は下記サイトのものを使用させていただきました。

無料ダウンロード:Fritzing 部品一覧

f:id:akanuma-hiroaki:20171001203824p:plain

 モータードライバの各PINは下記のように接続しています。

  1. GND
  2. モーターの片方の端子
  3. 未接続
  4. Vref. Raspberry Pi の 18番PIN
  5. IN1. Raspberry pi の 27番PIN
  6. IN2. Raspberry Pi の 4番PIN
  7. Raspberry Pi の 5V 電源出力
  8. 電池ボックスの+側
  9. 未接続
  10. モーターのもう一方の端子

 TA7291P の4番PINはモーターへの出力調整用なので、電圧を調整できるように Raspberry Pi の PWM対応ピンに接続しています。

モーターを回すサンプルコード

 Ruby で GPIO を操作するために pi_piper を使っていますが、 PWM を使う場合は pi_piper のPwmクラスを使用します。

http://www.rubydoc.info/github/jwhitehorn/pi_piper/PiPiper/Pwm

 まずはサンプルコードの全体を掲載します。

require 'bundler/setup'
require 'pi_piper'

PIN_VREF = 18
PIN_IN_1 = 27
PIN_IN_2 =  4
PIN_LED  = 17 

class MotorSample
  def initialize
    puts 'Initializing MotorSample...'
    @pin_led  = PiPiper::Pin.new(pin: PIN_LED,  direction: :out)
    @pin_in_1 = PiPiper::Pin.new(pin: PIN_IN_1, direction: :out)
    @pin_in_2 = PiPiper::Pin.new(pin: PIN_IN_2, direction: :out)
    @pin_vref = PiPiper::Pwm.new(pin: PIN_VREF)
    @pin_vref.value = 0
  end

  def stop_motor
    puts 'Stopping.'
    @pin_led.off
    @pin_in_1.off
    @pin_in_2.off
    sleep 1
  end

  def toggle_led(i)
    i % 2 == 0 ? @pin_led.on : @pin_led.off
  end

  def speeding_up
    puts 'Speeding Up...'
    0.step(1.0, 0.1) do |v|
      toggle_led((v * 10).to_i)
      @pin_vref.value = v
      sleep 2
    end
  end

  def slowing_down
    puts 'Slowing Down...'
    1.0.step(0, -0.1) do |v|
      toggle_led((v * 10).to_i)
      @pin_vref.value = v
      sleep 2
    end
  end

  def execute
    puts 'Rolling Forward.'
    @pin_in_1.on
    @pin_in_2.off
    speeding_up
    slowing_down

    stop_motor

    puts 'Rolling Backward.'
    @pin_in_1.off
    @pin_in_2.on
    speeding_up
    slowing_down
  end
end

if $0 == __FILE__
  motor_sample = MotorSample.new
  motor_sample.execute
end

 上記のサンプルコードを動かすと、モーターが順方向へ回転し始めて徐々に速度を上げ、最高速度に到達すると徐々に速度を下げていきます。そして今度は逆方向へ同様のことを行って終了します。

 ON/OFF の切り替えで良いピンは PiPiper::Pin を new していますが、 PWM を使いたいピンについては PiPiper::Pwm を new しています。

  def initialize
    puts 'Initializing MotorSample...'
    @pin_led  = PiPiper::Pin.new(pin: PIN_LED,  direction: :out)
    @pin_in_1 = PiPiper::Pin.new(pin: PIN_IN_1, direction: :out)
    @pin_in_2 = PiPiper::Pin.new(pin: PIN_IN_2, direction: :out)
    @pin_vref = PiPiper::Pwm.new(pin: PIN_VREF)
    @pin_vref.value = 0
  end

 TA7291P ではモーターの ON/OFF/回転方向 の切り替えは IN1 と IN2 の ON/OFF の組み合わせて決定します。

  • IN1: OFF IN2: OFF => 停止
  • IN1: ON IN2: OFF => 順方向へ回転
  • IN1: OFF IN2: ON => 逆方向へ回転

 例えば順方向へ回転させるには下記のように指定しています。

    @pin_in_1.on
    @pin_in_2.off

 PWM を使用しているピンについてはその値を 0 〜 1.0 で指定することで出力を調整できます。今回は例えば下記のように徐々に値を増加させ、モーターの回転速度を上げていったりしています。

  def speeding_up
    puts 'Speeding Up...'
    0.step(1.0, 0.1) do |v|
      toggle_led((v * 10).to_i)
      @pin_vref.value = v
      sleep 2
    end
  end

Beacon の電波強度に応じてモーターの回転速度を調節する

 指定した速度で動かすだけでは面白くないので、今度は BLE と組み合わせ、 Beacon の電波強度(RSSI)に応じてモーターの回転速度を変化させてみたいと思います。

 まずはモーターの動作をまとめた Motor クラスを作成します。内容としては単純に各ピンの初期化と、 ON/OFF、速度調節の値を設定する機能をまとめたものです。

require 'bundler/setup'
require 'pi_piper'

class Motor
  def initialize(pin_vref, pin_in_1, pin_in_2)
    @pin_in_1 = PiPiper::Pin.new(pin: pin_in_1, direction: :out)
    @pin_in_2 = PiPiper::Pin.new(pin: pin_in_2, direction: :out)
    @pin_vref = PiPiper::Pwm.new(pin: pin_vref)
    @pin_vref.value = 0
  end

  def stop
    @pin_in_1.off
    @pin_in_2.off
  end

  def forward
    @pin_in_1.on
    @pin_in_2.off
  end

  def backward
    @pin_in_1.off
    @pin_in_2.on
  end

  def adjust(value)
    @pin_vref.value = value
  end
end

 BLE で Beacon の Advertisement を受け取って Motor を操作するコードは下記のようにしました。今回は Beacon として以前にも使ったことのある、 Texas Instruments の SensorTag を使用しています。

require 'bundler/setup'
require 'dbus'
require './motor.rb'

class MotorTest
  SERVICE_NAME = 'org.bluez'
  SERVICE_PATH = '/org/bluez'
  ADAPTER = 'hci0'
  DEVICE_IF = 'org.bluez.Device1'
  DBUS_PROPERTIES_IF = 'org.freedesktop.DBus.Properties'
  PROPERTIES_CHANGED_SIGNAL = 'PropertiesChanged'
  DEVICE_NAME = 'CC2650'

  PIN_VREF = 18
  PIN_IN_1 = 27
  PIN_IN_2 =  4
  PIN_LED  = 17

  def initialize
    @bus = DBus::system_bus
    @bluez = @bus.service(SERVICE_NAME)

    @adapter = @bluez.object("#{SERVICE_PATH}/#{ADAPTER}")
    @adapter.introspect

    @motor = Motor.new(PIN_VREF, PIN_IN_1, PIN_IN_2)
    @motor.forward

    @led = PiPiper::Pin.new(pin: PIN_LED, direction: :out)
  end

  def execute
    @adapter.SetDiscoveryFilter({'Transport' => 'le'})
    @adapter.StartDiscovery

    @led.on

    target_device = nil

    while(target_device.nil?)
      @adapter.introspect
      @adapter.subnodes.each do |node|
        device = @bluez.object("#{SERVICE_PATH}/#{ADAPTER}/#{node}")
        device.introspect

        next unless device.respond_to?(:GetAll)

        properties = device.GetAll(DEVICE_IF).first
        name = properties['Name']
        rssi = properties['RSSI']

        next if name.nil?
        next unless name.downcase.include?(DEVICE_NAME.downcase)

        target_device = device
        break
      end

      sleep(0.1)
    end

    target_device.default_iface = DBUS_PROPERTIES_IF
    target_device.on_signal(PROPERTIES_CHANGED_SIGNAL) do |_, v|
      rssi = v['RSSI']
      vref_value = [[(100 + rssi).to_f / 100, 1.0].min, 0].max
      puts "RSSI: #{rssi} VREF: #{vref_value}"
      @motor.forward
      @motor.adjust(vref_value)
    end

    main = DBus::Main.new
    main << @bus

    main.run
  end
end

if $0 == __FILE__
  motor_test = MotorTest.new
  motor_test.execute
end

 BLE の操作については DBus と BlueZ を使い、 initialize で Bluetooth アダプタの初期化と Motor クラスの初期化を行っています。

  def initialize
    @bus = DBus::system_bus
    @bluez = @bus.service(SERVICE_NAME)

    @adapter = @bluez.object("#{SERVICE_PATH}/#{ADAPTER}")
    @adapter.introspect

    @motor = Motor.new(PIN_VREF, PIN_IN_1, PIN_IN_2)
    @motor.forward

    @led = PiPiper::Pin.new(pin: PIN_LED, direction: :out)
  end

 execute メソッドの中でBLEデバイスのスキャンを行っていますが、検索対象を絞るために SetDiscoveryFilter で BLE デバイスに限定しています。

    @adapter.SetDiscoveryFilter({'Transport' => 'le'})
    @adapter.StartDiscovery

 RSSI が変わると PropertiesChanged シグナルが送られてくるので、シグナルを受け取った場合にモーターの速度を変更するようにしています。 PWM ピンに設定できる値は 0 〜 1.0 なので、その範囲を出ないようにしています。今回は RSSI を厳密に取り扱うことが目的ではないので、上限と下限を超える場合は超える分をカットしている形です。

    target_device.default_iface = DBUS_PROPERTIES_IF
    target_device.on_signal(PROPERTIES_CHANGED_SIGNAL) do |_, v|
      rssi = v['RSSI']
      vref_value = [[(100 + rssi).to_f / 100, 1.0].min, 0].max
      puts "RSSI: #{rssi} VREF: #{vref_value}"
      @motor.forward
      @motor.adjust(vref_value)
    end

 このコードを動かすと、 SensorTag を Raspberry Pi に近づけるとモーターの回転速度が上がり、遠ざけると回転速度が下がります。SensorTag を動かしてからシグナルが送られるまでにタイムラグがあるのですが、とりあえずイメージした感じにはなっているので、今後調べられそうであればシグナル発行のタイミングも調べてみたいと思います。

まとめ

 今回初めて外部電源(電池ボックス)を使ってモーターを動かすことに挑戦してみました。実は途中で一度、横に置いておいた電池ボックスのリード線がいつの間にかショートして、電池ボックスを一つ焦がしてダメにしてしまい、やっぱり電気は怖いなぁと改めて思ったのですが、モーターなどを動かすことができると色々とやれる幅も広がって楽しいですね。モータードライバなどは物によって使い方が違うので、データシートなどから読み解くのは少し大変ですが、色々触って試してみたいと思います。