JavaScript
IoT
education
Raspberrypi3
chirimen

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

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 チュートリアル一覧