Edited at

CHIRIMEN for Raspberry Pi 3 チュートリアル 1. GPIO編

本記事は、CHIRIMEN for Raspberry Pi Ver.2018/03/08以前のVersionに対応しています。

新しいCHIRIMEN for Raspberry Piをお使いの方は、新しいCHIRIMENチュートリアルを参照ください。

CHIRIMEN for Raspberry Pi 3 チュートリアル 1. GPIO編


  • 概要


  • 1.準備


  • 2.マウスクリックでLEDのON/OFFを制御してみる


  • 3.マウスクリックのかわりにタクトスイッチを使ってみる


  • 4.LEDのかわりにCPUファンを回してみる

  • まとめ



概要

CHIRIMEN for Raspberry Pi 3 を使ったプログラミングを通じて、Web GPIO APIの使い方を学びます。


本チュートリアルを進める前に「CHIRIMEN for Raspberry Pi 3 Hello World」でCHIRIMEN for Raspberry Pi 3 の基本的な操作方法を確認しておいてください。

CHIRIMEN for Raspberry Pi 3 Hello World


(※1) CHIRIMEN for Raspberry Pi 3とは

Raspberry Pi 3(以下「Raspi3」)上に構築したIoTプログラミング環境です。


Web GPIO API (Draft)や、Web I2C API (Draft)といったAPIを活用したプログラミングにより、WebアプリからRaspi3に接続した電子パーツを直接制御することができます。

CHIRIMEN Open Hardware コミュニティにより開発が進められています。


1. 準備


用意するもの

このチュートリアル全体で必要になるハードウエア・部品は下記の通りです。



  • CHIRIMEN for Raspberry Pi 3 Hello World に記載の「基本ハードウエア」と「Lチカに必要となるパーツ」

  • タクトスイッチ x 1

  • [ジャンパーワイヤー (オス-メス)] x 5

  • Nch MOSFET (2SK4017)

  • [リード抵抗 (1KΩ)] x 1

  • [リード抵抗 (10KΩ)] x 1


  • DCファン x 1 ※ブレッドボードに接続できるようにケーブルを加工しておいてください。


CHIRIMEN for Raspberry Pi 3の起動とLチカの確認



  • CHIRIMEN for Raspberry Pi 3 Hello World の 「3. CHIRIMEN for Raspberry Pi 3 を起動してみよう」を参照して、CHIRIMEN for Raspberry Pi 3を起動してください。

  • ついでにCHIRIMEN Hello World の 「4. Lチカをやってみよう」を実施して、Lチカが正しく行えることを確認しておいてください。


Lチカでのおさらい


  • CHIRIMEN for Raspberry Pi 3 では、各種exampleが ~/Desktop/gc/配下においてある。配線図も一緒に置いてある

  • CHIRIMEN for Raspberry Pi 3 で利用可能なGPIO Port番号と位置は壁紙を見よう

  • LEDには方向がある。アノードが足が長い方。こちらをGPIOポートに繋ぐ。反対の足が短い方をGND側に繋ぐ。抵抗はどちらかに繋ぐ

  • CHIRIMEN for Raspberry Pi 3 ではWebアプリからのGPIOの制御にWeb GPIO API を利用する。


2. マウスクリックでLEDのON/OFFを制御してみる

それでは、実際にプログラミングをやってみましょう。

CHIRIMEN for Raspberry Pi 3 Hello World では、JS Bin を使ってLチカのexample コードを少し触ってみるだけでしたが、今度は最初から書いてみることにします。

せっかくですので、このチュートリアルでは他のオンラインエディタ JSFiddle を使ってみることにします。


Web上のオンラインサービスは便利ですが、メンテナンスや障害、サービス停止などで利用できなくなることがあります。

ローカルでの編集も含め、いくつかのサービスを使いこなせるようにしておくと安心です。

各サービスにはそれぞれ一長一短がありますので、利用シーンに応じて使い分けると良いかもしれません。



a. 部品と配線について

このパートではCHIRIMEN Hello World で実施したLチカの配線をそのまま利用します。必要な部品も同じです。

部品一覧

LEDは、26番ポートに接続しておいてください。

回路図


b. HTML/CSSを記載する

さて、今回は、ボタンとLEDの状態インジケータを画面上に作ってみましょう。

HTMLに <button><div> 要素を1つづつ作ります。

JSFiddle にアクセスすると、初期状態でコード編集を始めることができます。

この画面のHTMLペインに下記コードを挿入します。

<button id="onoff">LED ON/OFF</button>

<div id="ledview"></div>

※JSFiddleのHTMLペインにはHTMLタグの全てを書く必要はなく、<body>タグ内のみを書けばあとは補完してくれます。

ledviewには下記のようなスタイルを付けておきましょう。こちらはCSSペインに記載します。

#ledview{

width:60px;
height:60px;
border-radius:30px;
background-color:black;
}

最後に、HTMLに戻って、Web GPIO APIを利用可能にするためのPolyfillをロードする記述を行なっておきましょう。

先ほど追加したledviewのすぐ下に下記<script>タグを記載します。

<script src="https://mz4u.net/libs/gc2/polyfill.js"></script>


c. ボタンに反応する画面を作る

GPIOを実際に使う前に、まずは「ボタンを押したらLEDのON/OFF状態を表示する画面を切り替える」部分を作ってみます。

早速JavaScriptを書いて行きましょう。

(()=>{

var onoff = document.getElementById("onoff");
var ledview = document.getElementById("ledview");
var v = 0;
onoff.onclick = ()=>{
v ^= 1;
ledview.style.backgroundColor = (v == 1)? "red" : "black";
};
})();

書けたら JSFiddleの▷ Runをクリックします。

これで、LED ON/OFF ボタンが表示されるはずですので、ボタンをクリックしてみてください。

赤→黒→赤→黒→赤→黒→とクリックする都度切り替えできるようになったら成功です。

LED On/Offをブラウザ画面上のボタンクリックで実施


d. ボタンにLEDを反応させる

画面ができましたので、いよいよ Web GPIO を使ったLED制御コードを入れていきます。

一度Lチカの時に学んだことを思い出せばできるはずですが、まずは書き換えてみましょう。

(()=>{

var onoff = document.getElementById("onoff");
var ledview = document.getElementById("ledview");
var v = 0;
navigator.requestGPIOAccess().then((gpioAccess)=>{
var port = gpioAccess.ports.get(26);
port.export("out").then(()=>{
onoff.onclick = ()=>{
v ^= 1;
port.write(v);
ledview.style.backgroundColor = (v)? "red" : "black";
};
});
});
})();

これで、画面のボタンクリックに反応してLEDのON/OFFができたら成功です。

CHIRIMEN for Raspberry Pi 3 Hello World のLチカのパートでも簡単に説明しましたが、ここでもういちどWeb GPIO API の流れをおさらいしておきましょう。

navigator.requestGPIOAccess()

Web GPIOを利用するためのGPIOAccess インタフェースを取得するための最初のAPI呼び出しです。

正しくインタフェースが取得されたらPromisethenに指定したコールバック関数がGPIOAccessパラメータ付きでコールされます。

gpioAccess.ports.get()

GPIOAccess.ports は利用可能なportオブジェクトのリスト(Map)です。

var port = gpioAccess.ports.get(26);

上記コードで利用可能なportオブジェクトの一覧から、GPIOポート番号 26を指定してportオブジェクトを取得しています。

port.export()

port.export("out")により取得したGPIOポートを「出力モード」で初期化しています。

GPIOポートかける電圧をWebアプリ側から切り替えたい時には「出力モード」を指定する必要があります。

GPIOポートはもうひとつ「入力モード」があります。これはGPIOポートの状態を読み込みたい時に利用します。入力モードについてはスイッチのパートで説明します。

port.write()

port.write()は、出力モードに指定したGPIOポートの電圧を切り替える指定を行うAPIです。

port.write(1)で、指定したポートからHIGH(Raspberry Pi 3では3.3V)の電圧がかかり、port.write(0)で、LOW(0V)になります。


e. ボタンにLEDを反応させる (ES2017 async function版)

さきほどのコードを見ていただいたらお気づきかもしれませんが、GPIOのアクセス、そしてこれから出てくるI2C対応パーツのDriverの処理などでは、非同期処理が頻繁に出てきます。

Promise〜then()の連鎖的な書き方でもコードを書き進めることはできますが、上記のような深い入れ子が続くことでどんどん読みにくいコードになってしまいます。

こうした問題への改善アプローチの一つとして、ES2017で提案されているのが async function です。

async functionを使うことで、先ほどのコードを下記のように記述することができます。

(async ()=>{

var onoff = document.getElementById("onoff");
var ledview = document.getElementById("ledview");
var v = 0;
var gpioAccess = await navigator.requestGPIOAccess();
var port = gpioAccess.ports.get(26);
await port.export("out");
onoff.onclick = ()=>{
v ^= 1;
port.write(v);
ledview.style.backgroundColor = (v)? "red" : "black";
};
})();

CHIRIMEN for Raspberry Pi 3 で利用するRaspi3にプリインストールされている Chromium (v60) では async functionをサポートしているため、上記のように記述することも可能です。

以降本チュートリアルでは async functionを使って進めていきたいと思いますが、利用するオンラインエディタによっては未サポートの場合があります。

(JSFiddleではサポートされているようです。)

ここまでのJSFiddleの画面


3. マウスクリックのかわりにタクトスイッチを使ってみる

それでは、さきほどまで書いたコードをベースに、マウスの替わりにスイッチを利用してみます。

今回は、一般的に「タクトスイッチ」 と呼ばれるものを利用します。


タクトスイッチについて

「タクトスイッチ」はアルプス電気の商標のようです。

電子部品屋さん等では、アルプス電気製ではないスイッチも、同様の形状のものは「タクトスイッチ」として売られています。

秋月電気の「タクトスイッチ」一覧

今回の作例ではこのように「電気部品屋さん等でタクトスイッチとして売られてるスイッチ」を使います。


ようするに、下記のような仕様の「タクトスイッチっぽい」スイッチです。


  • SPST (1回路1接点)

  • プッシュボタン1つ

  • プッシュボタンの押し込みでスイッチON、プッシュボタンを離すとスイッチOFF (モーメンタリ動作)


a. 準備:画面のボタンをモーメンタリ動作に変えておく

これまでに作成したプログラムは「ブラウザ画面のボタンをクリックしたらLEDのHIGH/LOWを切り替える」というものでした。

クリック後は変更後の状態が維持されます。これは「オルタネート」のスイッチと同じ動きです。

一方で、今回用意したタクトスイッチは「モーメンタリ」のものです。

スイッチの動作:オルタネートとモーメンタリ


  • オルタネート : 状態をトグルします。一度ボタンを押すとONになりボタンから手を離してもOFFに変わりません。次にボタンを押すとOFFになります。ボタンから手を離してもONに変わることはありません。

  • モーメンタリ : 押している間だけONになります。スイッチから手を離すとOFFに戻ります。

この2つの動作が混在すると画面とスイッチで状態が一致せず、面倒なことになります。

一旦、ブラウザ画面のボタンを「モーメンタリ」に合わせておきましょう。

下記のように、最初はonclick イベントで切り替えています。

clickイベントは、「マウスのボタンを押して離す」ことで発生します。

 :

onoff.onclick = ()=>{
v ^= 1;
port.write(v);
ledview.style.backgroundColor = (v)? "red" : "black";
};
:

これを、下記のように変更します。


  • マウスのボタンを押す → LEDをON

  • マウスのボタンを離す → LEDをOFF

 :

onoff.onmousedown = ()=>{
port.write(1);
ledview.style.backgroundColor = "red";
};
onoff.onmouseup = ()=>{
port.write(0);
ledview.style.backgroundColor = "black";
};
:

これで、思った通りの動作になったはずです。

後でスイッチを追加したときに、同じ処理を呼ぶことになるので、LEDのON/OFFとledviewのスタイル切り替えをまとめて関数化しておきましょう。

下記のようになりました。

(async ()=>{

var onoff = document.getElementById("onoff");
var ledview = document.getElementById("ledview");
var gpioAccess = await navigator.requestGPIOAccess();
var port = gpioAccess.ports.get(26);
await port.export("out");
onoff.onmousedown = ()=>{
ledOnOff(1);
};
onoff.onmouseup = ()=>{
ledOnOff(0);
};
function ledOnOff(v){
if(v === 0){
port.write(0);
ledview.style.backgroundColor = "black";
}else{
port.write(1);
ledview.style.backgroundColor = "red";
}
}
})();


b. 部品と配線について

今回追加するのは下記部品です。


  • 前述のタクトスイッチ × 1

  • ジャンパーワイヤー(オスーメス)× 2

追加する部品

下図のように、さきほどのLEDの配線にタクトスイッチを追加しましょう。

スイッチを追加した配線


今回のスイッチは「プルアップ」回路で接続

上記回路ではスイッチが下記のように接続されています。


  • Port 5にスイッチを接続

  • GNDにスイッチの反対側を接続

これでどのようになるかというと、下記のようになります。


  • スイッチを押す前は、Port 5は HIGH (3.3V)

  • スイッチを押している間、Port 5は LOW (0V)

どうしてこうなるのでしょうか。

実は、Raspi3のGPIOポートのいくつかは、初期状態で「プルアップ」されています。

プルアップとは、回路を初期状態で「HIGHにしておく」ことですが、CHIRIMEN for Raspberry Pi 3で利用可能なGPIOポートのうち、下記ポート番号がプルアップ状態となっています。

初期状態でPullupされているPortの一覧

今回の回路では、このうち、Port 5を利用しています。

さきほどの動作となるメカニズムは下記の通りです。

スイッチの動作

この動作を頭に入れておきましょう。


c. スイッチに反応するようにする (port.read()を使ってみる)

いよいよ、スイッチに対応させてみましょう。

まずは、単純に「GPIOポートの状態を読み込む」 port.read()を使ってみたいと思います。

port.read()でGPIOを読み込むコードは下記のようになります。

  var gpioAccess = await navigator.requestGPIOAccess(); // writeと一緒。

var port = gpioAccess.ports.get(5); // Port 5 を取得
await port.export("in"); // Port 5 を「入力モード」に。
var val = await port.read(); // Port 5の状態を読み込む

こんな流れになります。

port.export()

port.export("in")により取得したGPIOポートを「入力モード」で初期化しています。

GPIOポートにかかる電圧をWebアプリ側から読み取りたい時に使います。

port.read()

port.export("in") で入力モードに設定したGPIOポートのデータを任意のタイミングで読み取ります。

読み取りは非同期になるので、port.read().then((data)=>{}); のように受け取るか、上記コードのように awaitで待つようにしてください。

上記コードでGPIOポートの読み取りが1度だけ行えますが、今回は「スイッチが押され状態を監視する」必要がありますので、定期的にport.read()を行ってGPIOポートを監視する必要があります。

下記は setInterval()でポーリングする例です。

  var gpioAccess = await navigator.requestGPIOAccess(); // writeと一緒。

var port = gpioAccess.ports.get(5); // Port 5 を取得
await port.export("in"); // Port 5 を「入力モード」に。
setInterval(()=>{
var val = await port.read(); // Port 5の状態を読み込む
// switchの状態による処理
},100);

これでも良いのですが、上記方式ですとsetInterval()のポーリング間隔を短くするとport.read()の読み取り結果が返ってくる前に、次のIntervalで読み取り要求してしまうようなケースも発生します。


場合によっては、こうした「順序の乱れ」が意図しない不具合を招くことも考えられます。

順序の乱れを発生させたくない場合は、下記のような一定時間待つ関数 を1つ定義し、port.read()と次のport.read()の間に挟んだループを形成することで順序通りのポーリングができるようになります。

  // 一定時間待つ関数

var sleep = (ms)=>{
return new Promise((resolve)=>setTimeout(resolve,ms));
};

var gpioAccess = await navigator.requestGPIOAccess(); // writeと一緒。
var port = gpioAccess.ports.get(5); // Port 5 を取得
await port.export("in"); // Port 5 を「入力モード」に。
while(1){
var val = await port.read(); // Port 5の状態を読み込む
// switchの状態による処理
await sleep(100);
}

LEDの処理と組み合わせた全体のコードは下記のようになりました。

(async ()=>{

var sleep = (ms)=>{
return new Promise((resolve)=>setTimeout(resolve,ms));
};

var onoff = document.getElementById("onoff");
var ledview = document.getElementById("ledview");
var gpioAccess = await navigator.requestGPIOAccess();
var ledPort = gpioAccess.ports.get(26); // LEDのPort
await ledPort.export("out");
onoff.onmousedown = ()=>{
ledOnOff(1);
};
onoff.onmouseup = ()=>{
ledOnOff(0);
};
function ledOnOff(v){
if(v === 0){
ledPort.write(0);
ledview.style.backgroundColor = "black";
}else{
ledPort.write(1);
ledview.style.backgroundColor = "red";
}
}
var switchPort = gpioAccess.ports.get(5); // タクトスイッチのPort
await switchPort.export("in");
while(1){
var val = await switchPort.read(); // Port 5の状態を読み込む
val ^= 1; // switchはPullupなのでOFFで1。LEDはOFFで0なので反転させる
ledOnOff(val);
await sleep(100);
}
})();

さて、出来たらスイッチを押してみてください。

LEDが押してる間だけ点灯したら成功です。

ただ、このコードでブラウザ画面上の「LED ON/OFF」ボタンを押すと正しく点灯しなくなってしまいました。

スイッチを読み込む処理がポーリング動作しているため、スイッチが押されていないとすぐLEDが消えてしまいます。


d. スイッチに反応するようにする (port.onchange())

これまで一通りport.read()を使ったスイッチの制御方法を見てきましたが、実は Web GPIO APIには「入力モード」のGPIOポートの状態を取得する方法がもうひとつ用意されています。それがport.onchange()です。

port.onchange()の説明は後回しにして、さきほどのサンプルをport.onchange()を使ったコードに書き換えてみましょう。

(async ()=>{

var onoff = document.getElementById("onoff");
var ledview = document.getElementById("ledview");
var gpioAccess = await navigator.requestGPIOAccess();
var ledPort = gpioAccess.ports.get(26); // LEDのPort
await ledPort.export("out");
onoff.onmousedown = ()=>{
ledOnOff(1);
};
onoff.onmouseup = ()=>{
ledOnOff(0);
};
function ledOnOff(v){
if(v === 0){
ledPort.write(0);
ledview.style.backgroundColor = "black";
}else{
ledPort.write(1);
ledview.style.backgroundColor = "red";
}
}
var switchPort = gpioAccess.ports.get(5); // タクトスイッチのPort
await switchPort.export("in");
switchPort.onchange = (val)=>{
// Port 5の状態を読み込む
val ^= 1; // switchはPullupなのでOFFで1。LEDはOFFで0なので反転させる
ledOnOff(val);
}
})();

コードを見ていただけたらお気づきかもしれません。 port.onchange()は入力モードのGPIOポートの「状態変化時に呼び出される関数を設定する」ための機能です。

port.read()を使ったコードと異なりポーリングする処理が不要になったので、今回のケースでは簡潔に書けるようになりましたね。

また、ポーリングによるLED制御処理を行なっていないので、ブラウザ画面のボタンも正しく反応できるようになりました。


4.LEDのかわりにCPUファンを回してみる

Web GPIO APIの機能が一通り確認できましたので、本パートのしめくくりに違う部品も制御してみましょう。

ここでは、MOSFETを使ってDCファンの単純なON/OFFを制御してみましょう。


MOSFETとは

MOSFETは電界効果トランジスタ (FET) の一種で、主にスイッチング素子として利用される部品です。

MOSFET(Wikipedia)

今回は、Nch MOSFET「2SK4017」を利用します。

mosfet


DCファンとは << 11/21:追記 >>

DCファンは、CPUの冷却等に利用される部品です。

小型のモーター、モータードライバ、そしてファンがセットになっており、通電するだけでファンを回して送風することができます。

今回は、5V 50mAで回転させることができる小型のDCファン を利用します。

DCファン


11/21 追記: DCファンには極性があるので注意してください!!

5V 50mAで回転させることができる小型のDCファンには極性があります。通常販売している状態では赤黒のケーブルが付属しており、赤い方が5V、黒い方がGNDに接続する仕様ですが、今回のチュートリアルのように違う色のジャンパー線が繋がっている場合は色で判別ができませんので、下記を確認のうえ接続するようにしてください。

dcfan

接続方法を誤るとDCファンが発熱し故障や事故の原因になる可能性があります。必ず上記確認のうえ配線をお願いします。



DCファンをブレッドボードで利用するために

今回利用するDCファンには細い電線が付属していますが、もともと基板へのハンダ付けを想定した電線であり、このままの状態ではブレッドボードで利用できません。

下記いずれかの方法でブレッドボードで利用できるようにしましょう。

1. ワニ口クリップでジャンパー線とDCファン付属の線を中継する

下記のように一応やれます。あまり綺麗ではないです。

DCファン

2. ジャンパケーブルをハンダ付けする

オスピンのジャンパー線の反対側を切ってDCファンに直接ハンダづけすればブレッドボードで扱いやすいDCファンが簡単に作成できます。


こちらの方法をおススメめします。


a. 部品と配線について

これまでに使った部品に下記を加えましょう。

DCファンは前述の通りジャンパーケーブルをハンダ付けしたものをご用意ください。

部品一覧

つぎに、先ほどの「タクトスイッチを押したらLEDをつけたり消したり」する回路を下記のように変更します。


LEDとLED用の抵抗を一旦外して、MOSFETと抵抗、DCファンを配置します。

タクトスイッチの場所も多少調整していますが、Raspi3側への接続ピン等は変えないでください。

DCファンの回路図

さて、それでは遊んでみましょう。


b. コードは.... 書き換えなくて良い

実は、この回路は先ほどまでのコード「d. スイッチに反応するようにする (port.onchange())」と同じコードで動きます。

LEDが点灯する替わりにファンが回るようになりました。

DCFan-Movie


c. しかし (オチw)

スイッチを押してDCファンが回るだけなら、5V→タクトスイッチ→DCファン→GND と繋げば プログラムを書かなくても出来る!!!!

...... スイッチじゃないのでやりましょう。(布石w)


まとめ

このチュートリアルでは、実際にコードを書きながらWeb GPIO API の基本的な利用方法を学びました。


  • Web GPIO APIを使ったGPIO出力ポートの設定と出力処理までの流れ(navigator.requestGPIOAccess()port.write()


  • Web GPIO APIを使ったGPIO入力ポートの設定と読み出し処理の流れ(navigator.requestGPIOAccess()port.read()


  • Web GPIO APIを使ったGPIO入力ポートの設定と変化検知受信の流れ (navigator.requestGPIOAccess()port.onchange())

次回『CHIRIMEN for Raspberry Pi 3 チュートリアル 2. I2C 基本編(ADT7410温度センサー)』ではWeb I2C APIの学習を進める予定です。


CHIRIMEN for Raspberry Pi 3 チュートリアル一覧