ITmedia NEWS > STUDIO >

Nゲージの模型をラズパイで動かす “らずてつ”その4――タイミングを合わせて車両を走らせる名刺サイズの超小型PC「ラズパイ」で遊ぶ(第55回)

» 2022年02月27日 07時00分 公開
[岩泉茂ITmedia]

 第52回から第53回第54回とラズパイを使って列車を動かし、ポイントを切り替える方法についてご紹介してきました。次はいよいよ、自動運転の“はじめの一歩”を踏み出してみましょう。

線路の上の模型を往復させる

 まずは写真のような線路を組みました。平行に線路が引かれており、その真ん中にポイントを設けて、上の線と下の線を行き来できるようにしてあります。

Raspberry Pi 並行して走る線路

 まずは前後に動かしてみましょう。第53回でも述べたように、列車の進む方向を決めてPWMを動作させればOKです。プログラムとしては以下のようになります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import RPi.GPIO as GPIO
# GPIOの設定
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
motor_r = 20 # 右向き
motor_l = 21 # 左向き
pwm = 12 # PWM
GPIO.setup(motor_r, GPIO.OUT) 
GPIO.setup(motor_l, GPIO.OUT) 
GPIO.setup(pwm, GPIO.OUT) 
pwm_m = GPIO.PWM(pwm, 100)
pwm_m.start(0)
GPIO.output(motor_r, 0)
GPIO.output(motor_l, 0)
time.sleep(1)
# 右向き
pwm_m.ChangeDutyCycle(0)
GPIO.output(motor_r, 1)
GPIO.output(motor_l, 0)
print ("go right")
pwm_m.ChangeDutyCycle(30) # ここの「30」を変えると最大速度が変化する
time.sleep(3) # ここの数値を変えると走る距離が変化する
pwm_m.ChangeDutyCycle(0)
# 動作停止
print ("stop")
GPIO.output(motor_r, 1)
GPIO.output(motor_l, 1)
time.sleep(3)
# 左向き
GPIO.output(motor_r, 0)
GPIO.output(motor_l, 1)
print ("go left")
pwm_m.ChangeDutyCycle(30) # ここの「30」を変えると最大速度が変化する
time.sleep(3) # ここの数値を変えると走る距離が変化する
pwm_m.ChangeDutyCycle(0)
GPIO.output(motor_l, 0)
# 動作停止
print ("stop")
GPIO.output(motor_r, 1)
GPIO.output(motor_l, 1)
pwm_m.stop()
GPIO.cleanup()
jidoutest.py

 円周の線路を回っている場合と違うのは、端点があることです。そこにぶつからないように稼働させる時間を調節して動かす必要があります。このため円周の時はデューティー比を40に設定しましたが、少し速すぎるので30に落としています。

 上のプログラムを記述したらコンソールから起動させます。

$ python jidoutest.py

 模型が左から右へと1往復します。

ポイントを使った移動

 続いてポイントを使った移動について進めていきます。基本的な考えは上の往復動作と同じですが、ポイントがあるので、それを切り替えつつ進むようにプログラムを記述する必要があります。そのためにはこれまでと同様に「進む」−「戻る」−「ポイントを切り替え」−「進む」……のように延々とプログラムを記述してもよいのですが、それだとあまりにも膨大な行数となりますし、もしミスがあった場合、どこに原因があるのか分かりにくくなってしまいます。

 そこで今回用いたのは「関数」です。右に進む、左に進むといった内容を関数として定義し、実行時にはその関数名だけ並べればよいようにしておくと、ミスがあったときのデバッグも楽になります。

 定義した関数は以下の7個です。

  • 右に進む
def go_right():
    pwm_m.ChangeDutyCycle(0)
    GPIO.output(motor_r, 1)
    GPIO.output(motor_l, 0)
    print ("go right")
    pwm_m.ChangeDutyCycle(30) # ここの「40」を変えると最大速度が変化する
    time.sleep(3.5) # ここの数値を変えると走る距離が変化する
    pwm_m.ChangeDutyCycle(0)
    return
  • 左に進む
def go_left():
    GPIO.output(motor_r, 0)
    GPIO.output(motor_l, 1)
    print ("go left")
    pwm_m.ChangeDutyCycle(30) # ここの「40」を変えると最大速度が変化する
    time.sleep(3.5) # ここの数値を変えると走る距離が変化する
    pwm_m.ChangeDutyCycle(0)
    GPIO.output(motor_l, 0)
    return
  • ポイント1をカーブ側に
def point1_curve():
    GPIO.output(straight_1,0)
    GPIO.output(curve_1,1)
    print("Point1 Curve")
    pwm1.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm1.ChangeDutyCycle(0)
    return
  • ポイント2をカーブ側に
def point2_curve():
    GPIO.output(straight_2,0)
    GPIO.output(curve_2,1)
    print("Point2 Curve")
    pwm2.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm2.ChangeDutyCycle(0)
    return
  • ポイント1を直線側に
def point1_straigh():
    GPIO.output(straight_1,1)
    GPIO.output(curve_1,0)
    print("Point1 Straight")
    pwm1.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm1.ChangeDutyCycle(0)
    return
  • ポイント2を直線側に
def point2_straight():
    GPIO.output(straight_2,1)
    GPIO.output(curve_2,0)
    print("Point2 Straight")
    pwm2.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm2.ChangeDutyCycle(0)
    return
  • 停止する
def stop():
    print ("stop")
    GPIO.output(motor_r, 1)
    GPIO.output(motor_l, 1)
    pwm_m.stop()
    return

 これを踏まえたプログラムが以下のようになります。右から左へスタートした模型が一度止まり、ポイントを切り替えて下の線路に移動。下の線路で左に行って右に戻ったらポイントを切り替えて上の線路へ。そこから右に走ってスタート地点に戻る、という動きにしました。なおポイントについては2つとも同じ動作となりますので、まとめてしまってもよいでしょう。このほか脱線や、プログラムを途中で止めたりした場合に備えて、ポイントをストレートに切り替える動作を最初に入れています。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import RPi.GPIO as GPIO
import threading
# GPIOの設定
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
# 模型
motor_r = 20 # 右向き
motor_l = 21 # 左向き
pwm = 12 # PWM
# ポイント1
straight_1 = 27  # pin11
curve_1 = 22  # pin13
pwm_1 = 17
# ポイント2
straight_2 = 24  # pin11
curve_2 = 25  # pin13
pwm_2 = 23
# 模型
GPIO.setup(motor_r, GPIO.OUT) 
GPIO.setup(motor_l, GPIO.OUT) 
GPIO.setup(pwm, GPIO.OUT) 
pwm_m = GPIO.PWM(pwm, 100)
pwm_m.start(0)
# ポイント1
GPIO.setup(straight_1, GPIO.OUT)
GPIO.setup(curve_1, GPIO.OUT)
GPIO.setup(pwm_1, GPIO.OUT)
pwm1 = GPIO.PWM(pwm_1, 100)
pwm1.start(0)
# ポイント2
GPIO.setup(straight_2, GPIO.OUT)
GPIO.setup(curve_2, GPIO.OUT)
GPIO.setup(pwm_2, GPIO.OUT)
pwm2 = GPIO.PWM(pwm_2, 100)
pwm2.start(0)
# 初期設定
GPIO.output(motor_r, 0)
GPIO.output(motor_l, 0)
GPIO.output(straight_1,0)
GPIO.output(curve_1,0)
GPIO.output(straight_2,0)
GPIO.output(curve_2,0)
time.sleep(1)
# 右に進む
def go_right():
    pwm_m.ChangeDutyCycle(0)
    GPIO.output(motor_r, 1)
    GPIO.output(motor_l, 0)
    print ("go right")
    pwm_m.ChangeDutyCycle(30) # ここの「40」を変えると最大速度が変化する
    time.sleep(3.5) # ここの数値を変えると走る距離が変化する
    pwm_m.ChangeDutyCycle(0)
    return
# 左に進む
def go_left():
    GPIO.output(motor_r, 0)
    GPIO.output(motor_l, 1)
    print ("go left")
    pwm_m.ChangeDutyCycle(30) # ここの「40」を変えると最大速度が変化する
    time.sleep(3.5) # ここの数値を変えると走る距離が変化する
    pwm_m.ChangeDutyCycle(0)
    GPIO.output(motor_l, 0)
    return
# ポイント1をカーブ側に
def point1_curve():
    GPIO.output(straight_1,0)
    GPIO.output(curve_1,1)
    print("Point1 Curve")
    pwm1.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm1.ChangeDutyCycle(0)
    return
# ポイント2をカーブ側に
def point2_curve():
    GPIO.output(straight_2,0)
    GPIO.output(curve_2,1)
    print("Point2 Curve")
    pwm2.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm2.ChangeDutyCycle(0)
    return
# ポイント1を直線側に
def point1_straigh():
    GPIO.output(straight_1,1)
    GPIO.output(curve_1,0)
    print("Point1 Straight")
    pwm1.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm1.ChangeDutyCycle(0)
    return
# ポイント2を直線側に
def point2_straight():
    GPIO.output(straight_2,1)
    GPIO.output(curve_2,0)
    print("Point2 Straight")
    pwm2.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm2.ChangeDutyCycle(0)
    return
def stop():
    print ("stop")
    GPIO.output(motor_r, 1)
    GPIO.output(motor_l, 1)
    pwm_m.stop()
    pwm1.stop()
    pwm2.stop()
    return
# 運転スタート
# ポイントを直線側に切り替え
point1_straigh()
point2_straight()
time.sleep(1)
# プログラムスタート
# 左に動く
go_left()
time.sleep(2)
# ポイント切り替え
point1_curve()
point2_curve()
time.sleep(1)
# 右に動く
go_right()
time.sleep(2)
# ポイント切り替え
point1_straigh()
point2_straight()
time.sleep(1)
# 左に動く
go_left()
time.sleep(2)
# 右に動く
go_right()
time.sleep(2)
# ポイント切り替え
point1_curve()
point2_curve()
time.sleep(1)
# 左に動く
go_left()
time.sleep(2)
# ポイント切り替え
point1_straigh()
point2_straight()
time.sleep(1)
# 右に動く
go_right()
time.sleep(2)
# 終了させる
stop()
GPIO.cleanup()
jidouunten.py

 このパターンで動かした例が以下の動画です。

 なおこれは1パターンしか動かしていませんが、「while True:」文を使って無限ループさせることもできます。その場合は以下のようにすればよいでしょう。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import RPi.GPIO as GPIO
import threading
# GPIOの設定
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
# 模型
motor_r = 20 # 右向き
motor_l = 21 # 左向き
pwm = 12 # PWM
# ポイント1
straight_1 = 27  # pin11
curve_1 = 22  # pin13
pwm_1 = 17
# ポイント2
straight_2 = 24  # pin11
curve_2 = 25  # pin13
pwm_2 = 23
# 模型
GPIO.setup(motor_r, GPIO.OUT) 
GPIO.setup(motor_l, GPIO.OUT) 
GPIO.setup(pwm, GPIO.OUT) 
pwm_m = GPIO.PWM(pwm, 100)
pwm_m.start(0)
# ポイント1
GPIO.setup(straight_1, GPIO.OUT)
GPIO.setup(curve_1, GPIO.OUT)
GPIO.setup(pwm_1, GPIO.OUT)
pwm1 = GPIO.PWM(pwm_1, 100)
pwm1.start(0)
# ポイント2
GPIO.setup(straight_2, GPIO.OUT)
GPIO.setup(curve_2, GPIO.OUT)
GPIO.setup(pwm_2, GPIO.OUT)
pwm2 = GPIO.PWM(pwm_2, 100)
pwm2.start(0)
# 初期設定
GPIO.output(motor_r, 0)
GPIO.output(motor_l, 0)
GPIO.output(straight_1,0)
GPIO.output(curve_1,0)
GPIO.output(straight_2,0)
GPIO.output(curve_2,0)
time.sleep(1)
# 右に進む
def go_right():
    pwm_m.ChangeDutyCycle(0)
    GPIO.output(motor_r, 1)
    GPIO.output(motor_l, 0)
    print ("go right")
    pwm_m.ChangeDutyCycle(30) # ここの「40」を変えると最大速度が変化する
    time.sleep(3.5) # ここの数値を変えると走る距離が変化する
    pwm_m.ChangeDutyCycle(0)
    return
# 左に進む
def go_left():
    GPIO.output(motor_r, 0)
    GPIO.output(motor_l, 1)
    print ("go left")
    pwm_m.ChangeDutyCycle(30) # ここの「40」を変えると最大速度が変化する
    time.sleep(3.5) # ここの数値を変えると走る距離が変化する
    pwm_m.ChangeDutyCycle(0)
    GPIO.output(motor_l, 0)
    return
# ポイント1をカーブ側に
def point1_curve():
    GPIO.output(straight_1,0)
    GPIO.output(curve_1,1)
    print("Point1 Curve")
    pwm1.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm1.ChangeDutyCycle(0)
    return
# ポイント2をカーブ側に
def point2_curve():
    GPIO.output(straight_2,0)
    GPIO.output(curve_2,1)
    print("Point2 Curve")
    pwm2.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm2.ChangeDutyCycle(0)
    return
# ポイント1を直線側に
def point1_straigh():
    GPIO.output(straight_1,1)
    GPIO.output(curve_1,0)
    print("Point1 Straight")
    pwm1.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm1.ChangeDutyCycle(0)
    return
# ポイント2を直線側に
def point2_straight():
    GPIO.output(straight_2,1)
    GPIO.output(curve_2,0)
    print("Point2 Straight")
    pwm2.ChangeDutyCycle(100)
    time.sleep(0.05)
    pwm2.ChangeDutyCycle(0)
    return
# 停止させる
def stop():
    print ("stop")
    GPIO.output(motor_r, 1)
    GPIO.output(motor_l, 1)
    pwm_m.stop()
    pwm1.stop()
    pwm2.stop()
    return
# ポイント切り替え
point1_straigh()
point2_straight()
time.sleep(1)
# 無限ループで走らせる
try:
    while True:
        # 左に動く
        go_left()
        time.sleep(2)
        # ポイント切り替え
        point1_curve()
        point2_curve()
        time.sleep(1)
        # 右に動く
        go_right()
        time.sleep(2)
        # ポイント切り替え
        point1_straigh()
        point2_straight()
        time.sleep(1)
        # 左に動く
        go_left()
        time.sleep(2)
        # 右に動く
        go_right()
        time.sleep(2)
        # ポイント切り替え
        point1_curve()
        point2_curve()
        time.sleep(1)
        # 左に動く
        go_left()
        time.sleep(2)
        # ポイント切り替え
        point1_straigh()
        point2_straight()
        time.sleep(1)
        # 右に動く
        go_right()
        time.sleep(2)
except KeyboardInterrupt:
    # 終了させる
    print ("stop")
    stop()
    GPIO.cleanup()
jidoumugen.py

 ここまでで何となく自動運転っぽいものが出来上がりましたが、適当な秒数を設定しているため、ずーっと運転していくと止まることができずにエンドレールへぶつかってしまうこともあるでしょう。そこで次回からは赤外線センサーを使って列車の位置を計測し、ポイントの切り替え動作や停止/折り返し動作ができるようにする方法を考えていきます。

Copyright © ITmedia, Inc. All Rights Reserved.