前回は環境を構築し、基板のLEDを光らせました。
今回はロボットを走行させてゆきましょう。
Lチカ(復習)
まずは前回の講義内容を思い出しながら、基板上のLEDを点灯させてみましょう。ここで注目するのが、Try-Finally
構文です。試してみることで、使う場合と使わない場合の違いを確認できます。以下のコードを比較してみてください。
Try-Finallyを使わない場合
from machine import Pin, Timer
import time
# オンボードLEDは「LED」というピン番号で制御します
led = Pin("LED", Pin.OUT)
tim = Timer()
def tick(timer):
global led
led.toggle()
print("term : 1")
# 周期を2Hzに設定し、tick()を呼び出す
tim.init(freq=2, callback=tick)
while True:
time.sleep(0.5) # 0.5秒ごとにスリープ
Try-Finallyを使う場合
from machine import Pin, Timer
import time
# オンボードLEDは「LED」というピン番号で制御します
led = Pin("LED", Pin.OUT)
tim = Timer()
def tick(timer):
global led
led.toggle()
print("term : 1")
# 周期を2Hzに設定し、tick()を呼び出す
tim.init(freq=2, callback=tick)
try:
while True:
time.sleep(0.5) # 0.5秒ごとにスリープ
finally:
# プログラム終了時にLEDを消灯する
time.sleep(1)
led.value(0)
解説
Try-Finally
構文を使うことで、プログラム終了時の処理が制御できることに注目してください。
一般的には、プログラムが終了した際にピンの出力も停止してほしい場合があります。
そのため、マイコン用のプログラムではよくTry-Finally
が使用されます。
通常のPythonプログラムではこのような問題はほとんどありませんが、マイコンでは入出力がそのまま残ることがありますので、終了処理が重要です。
ただし、RPi Pico
を制御する際にはExcept
を使わないように注意が必要です。
Except
に処理が入ると、RPi Pico
との接続が切れてしまうことがあるためです。
RPi Pico
は比較的手頃な価格の製品ですが、このような不具合があるため、Try-Finally
を使用してうまく対処しましょう。
モーター制御の概要
モーターには、一般的なDCモーター以外にも「サーボモーター」や「ステッピングモーター」など、いくつか種類があります。それぞれの特徴を以下の表にまとめました。
表1 各モーターの特徴(長所と短所の例)
モーター種類 | DCモーター | サーボモーター | ステッピングモーター |
---|---|---|---|
入力方法 | PWM(デューティー比を指定) | 角度や速度を指定 | ステップ数を指定 |
長所 | エネルギー効率が良い | 位置決めが簡単 | 位置決めの高精度 |
短所 | 消耗が激しい | 高コストで応答が遅い | 高速回転が苦手でエネルギー効率が悪い |
今回は位置を正確に制御する必要がないため、DCモーターを使用します。使用するモーターはこちらです: Amazonリンク
DCモーターの課題
DCモーターにはいくつかの課題があります。具体的には、駆動電圧や駆動電流、さらにはマイコンの損傷に関する問題です。例えば、今回使用するモーターの電圧範囲は3〜9Vですが、マイコンのピン出力電圧は3.3Vであるため、出力が足りています。しかし、モーターが大きくなると必要な電圧も高くなります。
さらに、DCモーターは起動時にマイコン側へ電流が流れることがあります(電磁誘導による逆起電力)。これらの問題を解決するために、通常は「モータードライバIC」と一緒にDCモーターを使用します。モータードライバICについて詳しく知りたい場合は、こちらを参考にしてください。
DCモータについては以下の記事とカテゴリーを参考にしてください。
https://sakigake-robo.com/category/circuit/motor-control/
DCモータの駆動
先ほどのコードを応用してモーターを回転させてみましょう。前のコードでは基板上のLEDを制御していましたが、今回はモーターに出力を変更します。まず、モーターがどのRPi Picoポートに接続されているかを確認する必要があります。モーターの配線は以下のとおりです。
使用するモータードライバはこちら: 秋月電子 モータードライバ
モータードライバの配線図
この図を見ると、MotorL+
、MotorL−
、MotorR+
、MotorR−
がモーターのIN1〜IN4に接続されています。
IN信号とOUT信号は対応しているため、この4つのピンへの入力を調整することでモーターの回転を制御できます。
また、MotorL+
、MotorL−
、MotorR+
、MotorR−
はそれぞれGPIO0〜GPIO3に接続されています。
左側のモーターを回転させる
まずは左側のモーターを回転させてみましょう。DCモーターは通常、Motor+
からMotor-
に電流が流れると順方向に回転します。ここで、モーターの回転方向に注意が必要です。まず、左右のモーターに順方向の電流を流して回転方向を確認しましょう。
from machine import Pin
# GPIOピン番号またはピン名を指定して左側のモーターを制御
motorL_plus = Pin(0, Pin.OUT)
motorL_minus = Pin(1, Pin.OUT)
# 順方向に電流を流す設定
motorL_plus.value(1)
motorL_minus.value(0)
このコードの基本的な動作はシンプルです。必要なライブラリをインポートし、ピンの入出力設定と出力値を指定しています。
PCから切り離しても動くようにする
今回はエディタのThonnyから実行を押すとプログラムが動きました。
ロボットはバッテリーで駆動するためPCから切り離した状態でも、電源を入れると自動で実行されてほしいです。
その場合はRasPiPicoにプログラムを保存します。
Raspberry Pi Picoを選択します。
main.pyのファイル名で保存します。
VSYSポートと3.3Vポートをジャンパー線でつなぎます。
PCからコードを抜き、電源を入れて、左のタイヤが駆動すれば完了です。
次に1秒前進と1秒後退するプログラムを試してみます。
from machine import Pin
import time
# モーター制御用のGPIOピン設定
motorL_plus = Pin(0, Pin.OUT)
motorL_minus = Pin(1, Pin.OUT)
motorR_plus = Pin(2, Pin.OUT)
motorR_minus = Pin(3, Pin.OUT)
# 前進の関数
def forward():
motorL_plus.value(1)
motorL_minus.value(0)
motorR_plus.value(1)
motorR_minus.value(0)
# 後退の関数
def back():
motorL_plus.value(0)
motorL_minus.value(1)
motorR_plus.value(0)
motorR_minus.value(1)
# 停止の関数
def stop():
motorL_plus.value(0)
motorL_minus.value(0)
motorR_plus.value(0)
motorR_minus.value(0)
try:
while True:
# 1秒前進
forward()
time.sleep(1)
# 1秒後退
back()
time.sleep(1)
# 1秒停止
stop()
time.sleep(1)
finally:
# プログラム終了時にモーターを停止
stop()
課題1
ロボットが1秒前進、1秒後退、1秒左旋回、1秒右旋回、1秒停止するプログラムを書いてください。
停止とブレーキ
今回のプログラムではすべての出力を0にすることでモータを停止させました。
これはモータドライバを経由してモータへの電流共有を停止させる命令をしています。
小型のロボットはすぐに停止しますが、大きいロボットでは慣性の法則により、モータが回転し、とまるまでに時間がかかります。
DCモータは端子同士を短絡させることで制動ブレーキをかけることが出来ます。
今回のモータドライバでは出力を両方1にすることでブレーキをかけられます。
以下は3秒前進、1秒停止と3秒前進1秒ブレーキを行うプログラムです。違いを観察してみてください。
from machine import Pin
import time
# モーター制御用のGPIOピン設定
motorL_plus = Pin(0, Pin.OUT)
motorL_minus = Pin(1, Pin.OUT)
motorR_plus = Pin(2, Pin.OUT)
motorR_minus = Pin(3, Pin.OUT)
# 前進の関数
def forward():
motorL_plus.value(1)
motorL_minus.value(0)
motorR_plus.value(1)
motorR_minus.value(0)
# 停止の関数
def stop():
motorL_plus.value(0)
motorL_minus.value(0)
motorR_plus.value(0)
motorR_minus.value(0)
# ブレーキの関数
def brake():
motorL_plus.value(1)
motorL_minus.value(1)
motorR_plus.value(1)
motorR_minus.value(1)
# 短絡による制動ブレーキをかける設定(ドライバ仕様に基づく場合)
try:
while True:
# 3秒走行
forward()
time.sleep(3)
# 1秒停止
stop()
time.sleep(1)
# 3秒走行
forward()
time.sleep(3)
# 1秒ブレーキ
brake()
time.sleep(1)
finally:
# プログラム終了時にモーターを停止
stop()
PWM制御
これまでモーターを動かしてみて、回転速度が速すぎると感じたり、速度が一定で不便だと思ったかもしれません。これを解決するのが「PWM制御」です。PWMは「Pulse Width Modulation(パルス幅変調)」の略で、デジタル信号を使ってアナログ的な出力を作る方法です。簡単に言うと、電流の流れる量を0か1ではなく、0から1の間で細かく調整できる仕組みです。
今回はmachine
モジュールに用意されているPWMを使ってみましょう。まず、左側のモーターを半分の速度で回転させる例を見てみましょう。
from machine import Pin, PWM
# 左側のモーターのピンを設定
motorL_plus = PWM(Pin(0))
motorL_minus = PWM(Pin(1))
# PWMの周波数を設定
motorL_plus.freq(1000)
motorL_minus.freq(1000)
# PWMのデューティ比を設定(0〜65535の値)
duty_common = 32767 # 半分の出力
motorL_plus.duty_u16(duty_common)
motorL_minus.duty_u16(0)
ここで注意が必要なのは、入力の分解能が16ビットであることです。つまり、値の範囲が0から65535の間で設定できます。最大出力をしたい場合はduty_common
を65535に設定します。
この仕組みを使って、前進、後退、旋回をPWMで制御するように変更してみましょう。やることは上の例と同じように書き換えるだけです。以下にコード例を示します。
from machine import Pin, PWM
import time # 時間を管理するためのモジュールをインポート
# モーターのピン設定
motorL_plus = PWM(Pin(0))
motorL_minus = PWM(Pin(1))
motorR_plus = PWM(Pin(2))
motorR_minus = PWM(Pin(3))
# PWMの周波数を設定
motorL_plus.freq(1000)
motorL_minus.freq(1000)
motorR_plus.freq(1000)
motorR_minus.freq(1000)
# 16ビットの最大値(フルパワー)
duty_common = 65535
# 前進の関数
def forward(duty):
motorL_plus.duty_u16(duty)
motorL_minus.duty_u16(0)
motorR_plus.duty_u16(duty)
motorR_minus.duty_u16(0)
# 後退の関数
def back(duty):
motorL_plus.duty_u16(0)
motorL_minus.duty_u16(duty)
motorR_plus.duty_u16(0)
motorR_minus.duty_u16(duty)
# 右旋回の関数
def right(duty):
motorL_plus.duty_u16(duty)
motorL_minus.duty_u16(0)
motorR_plus.duty_u16(0)
motorR_minus.duty_u16(duty)
# 左旋回の関数
def left(duty):
motorL_plus.duty_u16(0)
motorL_minus.duty_u16(duty)
motorR_plus.duty_u16(duty)
motorR_minus.duty_u16(0)
# 停止の関数
def stop():
motorL_plus.duty_u16(0)
motorL_minus.duty_u16(0)
motorR_plus.duty_u16(0)
motorR_minus.duty_u16(0)
try:
while True:
# 半分の速度で前進
forward(int(duty_common / 2))
time.sleep(1)
# 停止
stop()
time.sleep(1)
# 半分の速度で後退
back(int(duty_common / 2))
time.sleep(1)
finally:
# プログラム終了時にモーターを停止
stop()
このプログラムでは、PWMを使用してモーターの速度を調整しながら前進、後退、停止を行います。
duty_common
を変えることで速度を調整できます。試してみて、動きを確認してみましょう。
課題2
限界の速度を1とした時、0.5の速度で5秒前進、0.3の速度で3秒後退、0.4の速度で4秒左回転、1秒ストップを繰り返すプログラムを記載してください。
次回はこちら