フィジカルコンピューティング with Raspberry PI Pico
11 ポテンショメータでモーターを回す

フィジカルコンピューティングで面白いのは、やはり実際に目の前にある物を動かすことでしょう。今回はポテンショメータと呼ばれる電子機器のつまみを動かして、最終的にサーボモーターと言うモータを指定量だけ動かします。

アナログデジタル変換

デジタルの信号はオンとオフ、つまり1と0だけです。押しボタンを押すと1になり、放すと0になる。これに対しアナログはその間にすき間なく多くの数値を持つことができます。

アナログデジタル変換用チャンネル

Picoにはアナログデジタル変換器(ADC)というものが組み込まれているので、滑らかに連続するこのアナログ値を受け取ることができます。Picoには変換用のチャンネルが4つあり、GP26、GP27、GP28ピンが対応しています。もう1つは内蔵の温度センサーが使います。ポテンショメータなどからのアナログ値は、これらのピンに接続して取り込みます。

扱うのは0から65535までの数値

Picoは、ピンで受け取ったアナログ信号を12ビットの2進数値に変換できる能力を持っています。これは1か0が12個並んだもので、人間に分かりやすい10進数に直すと、0から4095までの数値になります(2の12乗)。

MicroPythonはこれを、自分が処理しやすい16ビットの2進数に変換します。これは1か0が16個並んだもので、人間に分かりやすい10進数に直すと、0から65535までの数値になります(2の16乗)。

アナログデジタル変換で扱うのは、この0から65535までのいかにも中途半端な数値です。

ポテンショメータの値の読み取り

ポテンショメータは、オーディオの音量つまみと同じもので、つまみを回すことで抵抗値を変えることができる可変抵抗器です。ポテンショメータを使うと、回路の電圧を変えることができ、Picoの場合には0Vから3.3Vの間の電圧に変えます。

配線

ポテンショメータからはピンが3つ出ています。1つをGP26ピン(ADC0チャンネル)に接続し、後の2本を電源とGNDに接続します。どのピンを何に接続すればよいかはキットの説明書に書かれています。もし書かれていない場合や単品を購入した場合には、製品名などで調べる必要があります。

下図ではポテンショメータのピンをブレッドボードに直接挿して、(OUT)ピンをGP26ピンに、(VCC)ピンを電源レールを通して3V3(OUT)ピンに、(GND)ピンをグランドレールを通してGNDピンに接続しています。

Raspberry Pi Pico ポテンショメータ
3V3(out) VCC
GND GND
GP26(ADC0) OUT
プログラムコード
from machine import Pin,ADC
import utime

# GP26ピンはADC0(0チャンネル)
potentiometer = ADC(Pin(26))

convertion_factor = 3.3 / 65535

while True:
    volt = potentiometer.read_u16()*convertion_factor
    print(volt)
    utime.sleep(2)

このプログラムでは、アナログデジタル変換に使用するADC()関数をインポートする必要があります。ADC()にPinオブジェクトを渡すと、ポテンショメータの値が得られるようになります。26はGP26ピンで、ADCの0チャンネルに当たります。

potentiometer = ADC(Pin(26))

つづく割り算は変換要因と呼ばれる変数を計算する式で、MicroPythonの扱う最高値で3.3Vを割った数値を、ポテンショメータの値に掛けることで通常のボルト数が計算できます。

whileループでは、ADCのread_u16()を使って、ポテンショメータの数値をMycroPythonが扱う0から65535の範囲の数値として読み取っています。これに変数convertion_factorを掛けると、見慣れたボルト数になります。

下図はポテンショメータのつまみを左いっぱいから右いっぱいにゆっくり回したときの出力値を示しています。ほぼ0から始まり、数値が3Vほどまで少しずつ大きくなっています。ポテンショメータを使うと、このように電圧を変えることができます(正確には分圧)。

3.3 / 65535を掛けるとなぜボルト数になるのか
65535はMycroPythonが扱う範囲の最高値で、3.3VはPicoで扱える電圧の最高値なので、両方とも100%だと言えます。これを数直線で示すと下図のようになる。上の数直線のひと目盛りは1ですが、その1が下の数直線ではいくつに相当するのかを考えます。これは比の方程式から、0.00005ほどだと計算できます。ひと目盛りが0.00005Vほど、つまり3.3 / 65535 V なので、これをread_u16()の値に掛けると、ボルト数になります。

パルス幅変調

ポテンショメータからのアナログ値は、Picoのアナログデジタル変換器(ADC)によってデジタル値に変換できます。これは、アナログの滑らかな曲線がデジタルのカクカクした近似値の直線に変わるということで、0と1の世界に送られるということです。

パルス幅変調という方法を使うと、そのデジタルの世界の値をアナログの値に疑似的に再現することができます。これはLEDの例でいうと、デジタルの世界では点くか点かないの二択しかないものを、点いているのかいないのか、あやふやな明かりが表現できるということです。

サーボモーターの回転もこの方法で行うことができます。以降では、パルス幅変調を使ったアナログ世界の再現を、まずはLEDを使って見ていくことにします。

LEDの明るさを変える

パルス幅変調(PWM)は、下図に示す矩形(パルス)の上辺の幅を変えていくことで、アナログ的な表現を可能にする方法です。1と0しかないデジタルの世界で、0.5のような中途半端な値を疑似的に作り出すことができます。

LEDの場合で言うと、ピンのオン/オフを高速で切り替えながら、ピンがオンである時間の比率を増やしていくと、だんだん明るくなっていき、逆にするとだんだん暗くなっていきます。このオン時間の周期に対する比率はデューティ比(またはデューティサイクル)と呼ばれます。

Picoのパルス幅変調ピン

PicoのGPIOピンはどれもパルス幅変調に対応できますが、パルス幅変調用のピン名が重複しているので、同じ名前のピンを使用しないよう気をつける必要があります。パルス幅変調用のピン名はよく使用されるPicoのピン配置図には記載されていませんが、Raspberry Pi Pico GPIO Pinoutページが参考にできます。

このページをブラウザで開いて、[Advanced]チェックボックスをクリックしてチェックを入れ、[PWM]チェックボックス以外のものを無効にすると、下図のように表示できます。これを見ると、たとえば左上隅のGP0ピンはパルス幅変調用のピン名がPWM0 Aで、右下隅のGP16も同じPWM0 Aなので、GP0とGP16は同時に使用できないことが分かります。

配線

配線は、抵抗をはさんでLEDを接続するだけです。下図ではGP0、つまりPWM0 Aピンを使用しています。

Raspberry Pi Pico 抵抗器 LED
GP0 <----> VCC(+、長い足)
GND GND(-、短い足)
プログラムコード
# PWM テスト
# LEDをだんだん明るく、だんだん暗くする

# machineライブラリからPinに加えてPWMもインポート
from machine import Pin, PWM
import utime

# 使用するのはGP0 = PWM0 A ピン
pwm_led = PWM(Pin(0))
pwm_led.freq(1000)	# 1秒間に発生させるパルスの数

# 3回繰り返す
for i in range(3):
    # 0から65535まで、変数duty1を1ずつ大きくする
    for duty1 in range(65536):
        # 0から65535までの整数値をduty_u16()に渡す
        # だんだん明るくなる
        pwm_led.duty_u16(duty1)
        #utime.sleep(0.0001)
    # 65535から0まで、変数duty2を1ずつ小さくする
    for duty2 in range(65535,-1,-1):
        # だんだん暗くなる
        pwm_led.duty_u16(duty2)
        #utime.sleep(0.0001)
        
print("終わり")   

このプログラムを実行すると、LEDがだんだん明るくなった後、だんだん暗くなり、これを3回繰り返します。この“だんだん”というのは、0でも1でもない、その間を滑らかに変化するアナログ的な表現です。

Picoのピンにパルス幅変調の機能を持たせるにはPWM()関数を使用するので、コードの冒頭では、PinとともにPWMもインポートしています(PWMはmachineライブラリに含まれています)。

パルス幅変調を設定したピンでは、周波数を設定する必要があります。これは1秒間に発生させる波の数で、今の場合には矩形で表されるパルスの数に当たります。

while Trueの無限ループでは、まずfor duty1 in range(65536)で、変数duty1を0から65535まで1ずつ大きくし、それをPWMピンのduty_u16()メソッドに渡しています。パルスのオン時間の幅はこれだけで大きくできます。これを高速で行うことで、LEDはだんだん明るくなります。

つづくfor duty2 in range(65535,-1,-1)では、変数duty2を65535から0まで1ずつ小さくし、duty_u16()メソッドに渡しています。これによってLEDはだんだん暗くなります。ここではLEDのオン/オフや、utime.sleep()も使用しなくても明るさが変わることにも注目してください。

range(stop)
range(start, stop[, step])

range()関数には、stopパラメータを指定する以外に、start、stop(これ自体は含まない)、オプションでstepパラメータを渡すこともできます。ここではduty2を65535からスタートし、-1の前、つまり0まで1ずつ小さくしたいので、range(65535,-1,-1)と指定しています。

duty_u16()

duty_u16()が受け取る数値はイメージしづらいので、図にしてみます。値の範囲は0から65535の間で、デューティ比は0%から100%です。

受け取る数値が16384なら、これは65535の25%に当たります。LEDは1周期の25%だけオンになり、残りの75%はオフになります。この高速の点滅によって、結果として25%の明るさで見えることになります。同様に、32678は50%に当たるので、LEDは50%の明るさで見えることになります。

デジタルアナログ変換器(ADC)とパルス幅変調(PWM)を組み合わせる

ADCとPWMを組み合わせるのは簡単で、ADCのread_u16()で得た数値を、PWMのduty_u16()に渡すだけです。ADCのポテンショメータのつまみで、PWMのLEDの明るさを変えるプログラムを作成します。

配線

前のLEDの配線にポテンショメータの配線を加えます。ポテンショメータの(OUT)はGP26ピン(ADC0)に接続します。

Raspberry Pi Pico 抵抗器 LED
GP0 <----> VCC(+、長い足)
GND GND(-、短い足)
Raspberry Pi Pico ポテンショメータ
3V3(out) VCC
GND GND
GP26(ADC0) OUT
プログラムコード
from machine import Pin, PWM, ADC
import utime

# ポテンショメータ GP26はADC0
potentiometer = ADC(Pin(26))
# LED GP0はPWM0 A
pwm_led = PWM(Pin(0))
pwm_led.freq(1000)

while True:
    duty = potentiometer.read_u16()
    print(duty)
    pwm_led.duty_u16(duty)
    utime.sleep(0.25)

プログラムを実行し、ポテンショメータのつまみを回します。左いっぱいに絞ると、[シェル]に表示される数値(read_u16()値)が小さくなり、LEDも暗くなります。つまみを右に回していくと、[シェル]の数値が大きくなり、LEDもだんだん明るくなります。

サーボモーターの羽を回す

ADCとPWMの使い方が分かったので、いよいよ最終のサーボモーターの羽を回すプログラムに移りましょう。

サーボモーター SG-90

ここで扱うのは、電子工作でよく使用される安価なSG-90というサーボモーターです。通常のモーターは2本出ている+と-の線に電池をつなぐと、シャフトがずっと回りつづけますが、サーボモーターからは+と-のほかに1本線が出ていて、シャフトの回る量を制御することができます。

SG-90のデータシート

以降では、SG-90のデータシートから得られるデータを使って、サーボモーターの制御方法を探っていきます。SG-90のデータシートはインターネットで検索すると、PDFファイルの形式ですぐに見つかります。下図はその一部です。

図の(1)からは、接続方法が分かります。オレンジの線がデータの入力線で、赤が電源、茶色がGNDです。
図の(2)からは、パルス幅変調で動作させるための基本的な数値が分かります。
・4.8V(~5V) Power and Signal は、電源に4.8Vが必要だということです。
・20ms(50Hz) PWM Period は、1周期の長さは20ミリ秒、周波数は50Hzだということです(1周期の長さが分かれば、周波数は計算できます。1000ms(1秒) / 20ms = 50Hz)。
・0.5~2.4ms Duty Cycle は、デューティ比は0.5msから2.4msの間で変更する、ということです。
図の(3)からは、角度とオン時間との関係が分かります。つまり、
0度 -> 1.45ms(真ん中)
90度 -> 2.4ms(右いっぱい)
-90度 -> 0.5ms(左いっぱい)
です。

データシートのデータを使って、サーボモーターの羽を回す

1周期の長さは20msで、0度のときオン時間は1.45msなので、この比率を使うと、サーボモーターのduty_u16()に渡す、0~65535の範囲の数値が手計算できます。

1.45msは1周期20msの0.025%(1.45/20)で、65535の0.025%は4751。サーボモーターの羽を0度にするには4751を渡せばよい、という計算ができます。同様に、90度にするには、65535 * (2.4/20) = 7864、-90度にするには、65535 * (2.4/20) = 1638と計算できます。

配線

SG-90のデータシートに書かれている色の線をPicoに配線します。オレンジの線はPWM:GP14ピン(PWM7 A)、赤はVBUS(5V必要)、茶色はGNDです。

Raspberry Pi Pico サーボモーター
VBUS VCC(赤)
GND GND(茶)
GP14(PWM7 A) PWM(オレンジ)
プログラムコード
from machine import Pin, PWM
import utime

servo_motor = PWM(Pin(14))
servo_motor.freq(50) # 周波数は50Hz

for i in range(3):
    servo_motor.duty_u16(4751) # 0度
    utime.sleep(1)
    servo_motor.duty_u16(7864) # 90度
    utime.sleep(1)
    servo_motor.duty_u16(4751) # 0度
    utime.sleep(1)
    servo_motor.duty_u16(1638) # -90度
    utime.sleep(1)
    
print("終わり")

SG-90のデータシートには、50Hzで動作すると書かれているので、忘れずにservo_motor.freq()には50を指定します。ここでは1秒おきに0度、90度、0度、-90度回転させています。この順番を変えると、羽を180度回転させることもできます。

ここまでずいぶんかかりましたが、ついに実際に存在する物をPicoから動かすことができました!

ポテンショメータのつまみの回転だけ、サーボモーターの羽を回す

いよいよ最終のプログラムです。つまみで回した量だけ、サーボモーターの羽を回します。

配線

ここまでの配線をまとめて行います。

Raspberry Pi Pico ポテンショメータ
3V3(out) VCC
GND GND
GP26(ADC0) OUT
Raspberry Pi Pico 抵抗器 LED
GP0 <----> VCC(+、長い足)
GND GND(-、短い足)
Raspberry Pi Pico サーボモーター
VBUS VCC(赤)
GND GND(茶)
GP14(PWM7 A) PWM(オレンジ)
プログラムコード
from machine import Pin, PWM, ADC
import utime

# ポテンショメータ GP26はADC0
potentiometer = ADC(Pin(26))
# LED GP0はPWM0 A
pwm_led = PWM(Pin(0))
pwm_led.freq(1000)
# サーボモーター GP14はPWM7 A
servo_motor = PWM(Pin(14))
servo_motor.freq(50)
while True:
    led_duty = potentiometer.read_u16()
    pwm_led.duty_u16(led_duty)
    # サーボモーターの特性に合わせて換算
    val = int(potentiometer.read_u16()/10.53+1638)
    servo_duty = round(val,-2)
    servo_motor.duty_u16(servo_duty)
    utime.sleep(1)

すんなりいけばありがたいのですが、残念ながらそうはいきません。ポテンショメータの読み取り値はLEDにはそのまま適用できますが、このサーボモーターには、0度で1638、180度で7864の数値を与える必要があるので、換算する必要があります。

1 : 7854 – 1638 (=6226) = x : 65536
6226x = 65536
x = 10.526 約10.53

ここでは、この10.526を10.53としてポテンショメータの読み取り値で割って換算し、そこに0度分の1638を足して、int()で整数に変えています。

val = int(potentiometer.read_u16()/10.53+1638)

さらに、羽がビリビリ動くのを防ぐために、valの下2桁を切り下げ、100単位にそろえています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


2023年7月
 12
3456789
10111213141516
17181920212223
24252627282930
31