以前、PCA9685に接続したサーボモーターやLEDの制御方法について解説したことがありましたが、AdafruitのPythonライブラリを使ったやり方でした。
今回は、別のやり方としてWebIOPi経由でPCA9685を操作する方法について扱ってみたいと思います。WebIOPiはラズパイのGPIOピンの入出力をウェブサーバー経由でコントロールできるという大変便利なソフトウェアです。
以前にも記事で扱った通り、PCA9685にはサーボモーターやLEDを16個接続できるので、WebIOPiと合わせて使えば、それらをネットワーク経由でラジコンのように遠隔操作できてしまうというわけです。
ちなみに、WebIOPi経由でPCA9685を操作する場合は、AdafruitのPythonライブラリは不要で、WebIOPi側で用意されているクラスをimportして組むことになります。
それでは早速、やり方を解説していきたいと思います!
目次
- PCA9685の入手について
- PCA9685を使うための準備
- WebIOPiのセッティング
- 回路を組んでみよう
- オリジナルのUIでコントロールしてみよう
- まとめ
1. PCA9685の入手について
まずは、PCA9685を入手しましょう。amazonでは互換品となりますが格安で入手可能です。
サーボドライバー:PCA9685
自分が購入したのは下記の製品です。なんと2つで1,399円という破格の安さ。実は以前より若干値上がりしてしまったのですが、それでも一つあたり700円という破格さです。amazonの評価には不安になるものもありますが、自分が購入したものは2つとも動作的に問題ありませんでした!
VKLSVAN 2個 PCA9685 16チャンネル 12-ビット PWM Servo モーター ドライバー IIC モジュール Arduinoと互換
サーボモーター:SG90
使用するサーボモーターは、TAMIYA のラジコンカーなどでよく使用するレギュラーサイズのものでもOKですが、ここではマイクロサーボとして有名なSG90をお勧めします。電子工作では一般的ですし、なんといっても安い点が最高です。
LED
使用するLEDですが、3V(15mA)で使用可能な、ごく一般的なものを使用します。電子工作に興味を持っている人なら既に何個か持っているのではないでしょうか?
Raspberry Pi Zero W
国内では在庫が切れていることの多いRaspberry Pi Zero Wですが、amazonに在庫があることもあるようです。
Raspberry Pi Zero W – ヘッダー ハンダ付け済み – ラズベリー・パイ ゼロ W ワイヤレス
※上記のリンクはamazonへのリンクとなっています。
2. PCA9685を使うための準備
I2Cの有効化について
PCA9685、サーボモーター、LEDを用意できたら、今度はRaspberry Pi側の準備です。PCA9685はラズパイとシリアル通信(I2C)で接続する必要があるので、ラズパイ側の設定でI2Cが有効になっていなければなりません。ちなみに「I2C」は「アイ・スクエアド・シー」または「アイ・アイ・シー」と読みます。
ラズパイのI2Cの有効化についてはこちらの『サーボドライバー「PCA9685」を使って、複数の小型サーボ(SG90等)をRaspberry Piで動かす方法』という記事で解説していますので参照してみてください。
3. WebIOPiのセッティング
WebIOPiは冒頭で紹介した通りラズパイで電子工作を楽しむのに大変便利なソフトウェアですが、以下の手順で入手可能です。
オフィシャルサイト:
http://webiopi.trouch.com/
※ 残念ながら開発は2015年(Ver.0.7.1)で止まってしまっているようです。
WebIOPiのインストール
まずラズパイにログインしてWebIOPi一式をwgetでダウンロードし解凍してください。
wget http://sourceforge.net/projects/webiopi/files/WebIOPi-0.7.1.tar.gz
tar xvzf WebIOPi-0.7.1.tar.gz
WebIOPiのディレクトリ内に移動してからパッチをwgetでダウンロードして適用します。
cd WebIOPi-0.7.1
wget https://raw.githubusercontent.com/doublebind/raspi/master/webiopi-pi2bplus.patch
patch -p1 -i webiopi-pi2bplus.patch
パッチを当てることができたら、最後にセットアップを実行します。
sudo ./setup.sh
最後に「Do you want to access WebIOPi over Internet ? [y/n]」と聞いてきますので「n」を入力し終了します。
参考URL:
ラズベリーパイにWebIOPiをインストールする
WebIOPiの起動・停止・再起動とプロセスの確認方法について
以下の方法で起動・停止・再起動ができます。
起動:
sudo /etc/init.d/webiopi start
停止:
sudo /etc/init.d/webiopi stop
再起動:
sudo /etc/init.d/webiopi restart
以下の方法でプロセスを表示し起動を確認できます。
ps ax | grep webiopi
WebIOPiを自動起動させる方法
WebIOPiを常に起動させておきたい場合は、以下のコマンドで自動起動させることができます。
sudo update-rc.d webiopi defaults
自動起動を停止させる場合は下記コマンドを使用します。
sudo update-rc.d webiopi remove
ラズパイのローカルIPアドレスを固定に設定していない場合は、予め以下のコマンドでローカルIPを調べておきましょう。
ifconfig
「wlan0:」の項目に表示されているのがローカルIPです。
ブラウザでラズパイにアクセスしてみよう
ローカルIPをチェックしたら、下記のアドレスにPCブラウザからアクセスしてみてください。伏字になっている部分はご自身の環境に合わせてください。またポート番号の「:8000」を指定してアクセスします。
http://xxx.xxx.xxx.xxx:8000
id: webiopi
pw: raspberry
このように表示されれば正常に起動しています。

このメニューからラズパイの各GPIOを個別にコントロールしたり、接続したデバイスをコントロールしたりすることができます。
この記事ではオリジナルのUIでサーボモーターやLEDをコントロールすることを試みますが、このメニューの「Devices Monitor」からもPCA9685に接続したサーボモーターやLEDをコントロールできます。まずその前に回路を組んでみましょう。
4. 回路を組んでみよう
今回はテスト用として以下の回路を組んでみました。サーボモーター(SG90)を一つ、LEDを二つ繋いでみました。

上記の回路図では、サーボモーターの動作用電源(V+)として、乾電池を繋ぐことを想定してありますが、今回はSG90という電子工作ではお馴染みの小型サーボモーターを一個だけ繋いでいるので、電池ボックスと乾電池が手元にない場合は、ラズパイの5V出力を使用しても特に問題なく動作させることができます。
ただ、サーボモーターを複数繋ぐ場合や、消費電力の大きい標準サイズのサーボモーターを繋ぐような場合は、必ず乾電池やバッテリーなど、動作用電源を別途用意しましょう。
それと、接続するLEDについてですが、PCA9685のPWM出力ポートには220Ωの抵抗がついています。LEDの電源としてラズパイ側の3.3V出力(VCC)を利用することになるわけですが、220Ωの抵抗を通せば15mA(= 3.3 / 220 * 1,000)ということになるので、一般的なLEDであれば、概ね抵抗なしの直結で問題ないです。
動作テスト
回路ができたら、先ほどのWebIOPiのメインメニューを表示し「Devices Monitor」で動作テストしてみましょう。WebIOPiでPCA9685を使用するには、configを一部修正する必要があります。
sudo vi /etc/webiopi/config
94行目の「#pwm0 = PCA9685」から「#」を削除し、コメントアウトを解除します。
#gpio0 = MCP23017
#gpio1 = MCP23017 slave:0x21
#gpio2 = MCP23017 slave:0x22
pwm0 = PCA9685
#pwm1 = PCA9685 slave:0x41
#adc0 = MCP3008
#adc1 = MCP3008 chip:1 vref:5
#dac1 = MCP4922 chip:1
上記のようにして上書き保存しておきます。保存したら、WebIOPiを再起動してください。
sudo /etc/init.d/webiopi restart
[ ok ] Restarting webiopi (via systemctl): webiopi.service.
再起動コマンド実行後、2行目が表示されたら成功です。
では、改めてWebIOPiのメインメニューを表示し「Devices Monitor」に進んでみましょう。

「Channel-0」にはサーボモーター、「Channel-1」と「Channel-2」には、LEDが接続されているのでスライダーが上記のようになるはずです。
まずはサーボのテストですが、Channel-0の「Servo」のチェックを入れて、スライダーを動かしてみてください。サーボが動けば成功です!
今度は二つのLEDを点灯させてみましょう。Channel-1と2のスライダーを100%まで動かしてみて点灯すれば成功です!
問題なく動いたでしょうか?
5. オリジナルのUIでコントロールしてみよう
では今度は、オリジナルのUI(ユーザーインターフェース)を組んで、動作させることにチャレンジしてみましょう。オリジナルのUIを組むには、html、css、JavaScript等の相応の知識が必要になります。
このようなUIとしてみました。

サーボは規定の角度まで動作させるボタンと無段階で操作できるスライダーを用意しました。またLEDはトグルスイッチ風のスイッチとしてあります。
※無段階で操作できるスライダーはスマホのみで動作します。
このUIはどのように機能するのでしょうか?実はPCA9685を最終的にコントロールするのはPythonなのですが、htmlとJavaScriptによってPythonの関数をマクロで呼び出すという役割を負わせます。
何はともあれ、こちらで記事に連動したサンプルコードを用意しましたので、よかったらダウンロードして中身を確認してみてください。
WebIOPi オリジナルUI サンプルコード一式:
https://www.handsonplus.com/download/how-to-use-webiopi-with-pca9685_sample.zip
ファイル構成は下記のようになっています。

ここではhtmlやcssの解説は省略させていただき、JavaScriptとPythonの解説のみ扱っていきたいと思います。
assets/js/controller.js
UI側のイベントを検出して、Python側のマクロを呼ぶのが、この「controller.js」の役割です。
// サーボ動作最大角度
var rotationMax = 90;
function initialize_webiopi(){
// タッチエリアの設定
var touchArea = $("#touchArea")[0];
// サーボのイベントリスナーの登録
touchArea.addEventListener("touchstart", touchEvent, false);
touchArea.addEventListener("touchmove", touchEvent, false);
touchArea.addEventListener("touchend", touchEndEvent, false);
// GPIOの状態を監視しない
webiopi().refreshGPIO(false);
}
// サーボ:タッチ開始
function touchEvent(e){
e.preventDefault();
var touch = e.touches[0];
var width = $("#touchArea").width();
var posionX = touch.pageX - 12.5; // 左マージン分を差し引く
var angle = Math.round(rotationMax * (width / 2 - posionX) / (width / 2));
// 左右回転差の調整 ※個体差により左右の回転差を感じる場合に「1.0」ではなく適宜調整
if(angle > 1){
angle = Math.round(angle * 1.0);
}
else if(angle < -1){
angle = Math.round(angle * 1.0);
}
webiopi().callMacro("controlServoStop", 0);
webiopi().callMacro("controlServo", [0, angle]);
$("#touchArea").addClass("touch-action");
// デバッグ
// console.log("angle = " + angle);
}
// サーボ:タッチ終了
function touchEndEvent(e){
e.preventDefault();
webiopi().callMacro("controlServoStop", 0);
$("#touchArea").removeClass("touch-action");
}
// ボタン(サーボ:90°)
$("#button01").on("click", function(e) {
e.preventDefault();
$("#button01").addClass("button-action");
webiopi().callMacro("controlServo", [0, 90]);
setTimeout(function(){
$("#button01").removeClass("button-action");
webiopi().callMacro("controlServoStop", 0);
}, 500);
});
// ボタン(サーボ:45°)
$("#button02").on("click", function(e) {
e.preventDefault();
$("#button02").addClass("button-action");
webiopi().callMacro("controlServo", [0, 45]);
setTimeout(function(){
$("#button02").removeClass("button-action");
webiopi().callMacro("controlServoStop", 0);
}, 500);
});
// ボタン(サーボ:0°)
$("#button03").on("click", function(e) {
e.preventDefault();
$("#button03").addClass("button-action");
webiopi().callMacro("controlServo", [0, 0]);
setTimeout(function(){
$("#button03").removeClass("button-action");
webiopi().callMacro("controlServoStop", 0);
}, 500);
});
// ボタン(サーボ:-45°)
$("#button04").on("click", function(e) {
e.preventDefault();
$("#button04").addClass("button-action");
webiopi().callMacro("controlServo", [0, -45]);
setTimeout(function(){
$("#button04").removeClass("button-action");
webiopi().callMacro("controlServoStop", 0);
}, 500);
});
// ボタン(サーボ:-90°)
$("#button05").on("click", function(e) {
e.preventDefault();
$("#button05").addClass("button-action");
webiopi().callMacro("controlServo", [0, -90]);
setTimeout(function(){
$("#button05").removeClass("button-action");
webiopi().callMacro("controlServoStop", 0);
}, 500);
});
// トグルスイッチ(LED1)
$("#toggle01").on("click", function(e) {
e.preventDefault();
$("#toggle01").toggleClass("checked");
if(!$('#toggle01 input[name="check"]').prop("checked")) {
$("#toggle01 input").prop("checked", true);
webiopi().callMacro("controlLed", [1, "on"]);
} else {
$("#toggle01 input").prop("checked", false);
webiopi().callMacro("controlLed", [1, "off"]);
}
});
// トグルスイッチ(LED2)
$("#toggle02").on("click", function(e) {
e.preventDefault();
$("#toggle02").toggleClass("checked");
if(!$('#toggle02 input[name="check"]').prop("checked")) {
$("#toggle02 input").prop("checked", true);
webiopi().callMacro("controlLed", [2, "on"]);
} else {
$("#toggle02 input").prop("checked", false);
webiopi().callMacro("controlLed", [2, "off"]);
}
});
それぞれの関数内に「webiopi().callMacro()」という記述が幾つもありますが、これらの記述でPython側のマクロを呼んでいます。
冒頭でサーボ動作最大角度の設定がありますが、お好みの最大角度を指定してください。SG90の動作範囲は90°〜-90°のはずなので、このままで良いはずですが、動作がおかしいようなら、この値を少し小さく設定してみてください。
一つ注記しておきたいのが、引数の渡し方で、第一引数は必ずPython側の関数名となります。渡す引数が二つ以下の場合はそのまま渡しますが、渡す引数が三つ以上になる場合、第二引数には配列で渡すという特殊なルールがあるので注意が必要です。
assets/js/script.py
# -*- coding: utf-8 -*-
import webiopi
from webiopi import deviceInstance
# 設定周波数(Hz)
SET_FREQ = 50
# デバッグ出力を有効に
webiopi.setDebug()
# GPIOライブラリの取得
GPIO = webiopi.GPIO
# Adafruit_PCA9685初期化
PCA9685 = deviceInstance("pwm0")
# 動作角度からPCA9685に渡す値を変換
def convert_deg(deg, freq):
# 分解能(ステップ数)
step = 4096
# 接続サーボモーターの最大最小角度時のパルス間隔(ms)
max_pulse = 2.4 # 90°時
min_pulse = 0.5 # -90°時
# サーボモーターの0°時のパルス間隔(ms)
center_pulse = (max_pulse - min_pulse) / 2 + min_pulse
#サーボモーター1°あたりのパルス間隔(ms)
one_pulse = round((max_pulse - min_pulse) / 180, 2)
# 要求角度のパルス間隔(ms)を算出しPCA9685に渡す値を算出
deg_pulse = center_pulse + deg * one_pulse
deg_num = int(deg_pulse / (1.0 / freq * 1000 / step))
return deg_num
# WebIOPiの起動時に呼ばれる関数
def setup():
webiopi.debug("Script with macros - Setup")
pwmInit()
# WebIOPiにより繰り返される関数
def loop():
webiopi.sleep(5)
# WebIOPi終了時に呼ばれる関数
def destroy():
webiopi.debug("Script with macros - Destroy")
pwmInit()
# 接続されたサーボ、LEDの初期化
def pwmInit():
PCA9685.pwmWrite(0, 0)
PCA9685.pwmWrite(1, 0)
PCA9685.pwmWrite(2, 0)
# サーボコントロール
@webiopi.macro
def controlServo(port, angle):
pwm_num = convert_deg(int(angle), SET_FREQ)
PCA9685.pwmWrite(int(port), pwm_num)
# サーボ停止
@webiopi.macro
def controlServoStop(port):
PCA9685.pwmWrite(int(port), 0)
# LEDコントロール
@webiopi.macro
def controlLed(port, status):
if status == "on":
PCA9685.pwmWrite(int(port), 4095)
else:
PCA9685.pwmWrite(int(port), 0)
こちらは実際にPCA9685を制御するスクリプトです。冒頭で「webiopi」と「deviceInstance」をインポートしておきます。
また「convert_deg」というオリジナルの関数で、サーボモーターの動作させたい角度を12bit(2の12乗=4096段階)のpwm値に変換しています。
LEDの明るさは全て100%で点灯するようにしています。その場合、PCA9685は12bitですから「4095」となります。つまり50%で点灯させたい場合は「2047」となります。
前述のこの記事に連動したサンプルコード一式をお手持ちのラズパイに適用する場合ですが、まず「/usr/share/webiopi/htdocs/」内に移動して、サンプルコードをダウンロードしていただき、解凍してください。
cd /usr/share/webiopi/htdocs/
sudo wget https://www.handsonplus.com/download/how-to-use-webiopi-with-pca9685_sample.zip
sudo unzip how-to-use-webiopi-with-pca9685_sample.zip
上記のコマンドを順に実行してもらうと「/usr/share/webiopi/htdocs/」内に「sample」というディレクトリができますが、そちらがUIのパスということになります。
ついで、configを修正します。
sudo vi /etc/webiopi/config
21行目以降に「SCRIPTS」という記述があるのですが、その設定の末尾にサンプルコードの「script.py」のパスを追加しておきます。
[SCRIPTS]
# Load custom scripts syntax :
# name = sourcefile
# each sourcefile may have setup, loop and destroy functions and macros
#myscript = /home/pi/webiopi/examples/scripts/macros/script.py
myscript = /usr/share/webiopi/htdocs/sample/assets/js/script.py
上書き保存したら、WebIOPiを再起動します。
sudo /etc/init.d/webiopi restart
諸設定が終わったら下記にてアクセスしてみてください。
http://xxx.xxx.xxx.xxx:8000/sample/
id: webiopi
pw: raspberry
うまく動作したでしょうか?

サーボモーターを操作する無段階スライダーの操作方法ですが、両端がそれぞれ左右のサーボの最大角度となっており、特定の位置をタップすることで、中央(0度)からの距離に応じた角度までサーボが回転するようになっています。
実は、そのままずるずるとタッチ操作できるようにもしてあるのですが、どうしても動作にタイムラグが生じてしまいます。この辺は改良の余地がありますね。何はともあれ、参考にしていただけるようなら幸いです。
6. まとめ
今回はWebIOPiでPCA9685に接続したサーボモーターやLEDをコントロールする方法について扱ってみました。このようにWebIOPiはローカルネットワークを通じてサーボモーターやLEDを遠隔操作できるので、ラズパイを使ったロボットやラジコン製作にはうってつけというわけです。色々応用できるので、是非とも精通しておきたいところです。
応用例
今回解説したRaspberryPi Zero とWebIOPi、PCA9685をプラモデルに組み込んでラジコンに改造したものがこちらです。車内が機器で一杯になってしまいますが、このサイズになんとか組み込むことができました。