ESP-WROOM-02(ESP8266 コア)はアナログ入力(計測範囲 0.0〜1.0V が 1ch のみ)が弱いので,i2c 接続でセンシングすることを考えます.ここでは安価な6軸モーションセンサ(加速度3軸+ジャイロ3軸)として MPU-6050(を使った GY-521 という中華ボード)を対象とします.調達時は Amazon で 210円でした.
GY-521 付属のピンポストを4極分だけ使います.ブレッドボードに差せるように,VCC, GND, SCL, SDA の4極に基板裏側から下向きピンポストをハンダ付けしてください.
i2c は通信方式のひとつです.基準となる GND が共有できていれば,クロック(SCL)とデータ(SDA)の2線だけで,1台のマスター(多くの場合 ESP-WROOM-02 や Arduino など)と,最大 100 台以上のスレーブ(センサやディスプレイなど)の双方向通信が可能です.
ここでは上述の GY-521(MPU-6050)で捉えたモーション情報(加速度・角加速度)を ESP-WROOM-02 で読み出す方法を解説します.まず,ESP-WROOM-02 と GY-521 を次のように接続します.このうち,IO4〜SDA のデータ線と IO5〜SCL のクロック線は,GY-521 の内部でプルアップ(2.2kΩ)されているようですので,外付けのプルアップ抵抗は不要です.
すでに他の i2c デバイスが接続されている場合は,GY-521 を 3V3, GND, IO4, IO5 に並列に接続してください.その場合,プルアップ抵抗は SDA, SCL に1本ずつ入っていればよいので,別段追加する必要はないでしょう.
Arduino 開発環境を立ち上げ,つぎのサンプルプログラムを打ち込んでください.6軸センサから加速度や角速度を読み出し,Arduino 開発環境のシリアルモニタ(115200 baud に設定)に表示するものです.
// // MPU6050 on ESP8266 #include <Wire.h> #define MPU6050_ADDR 0x68 #define MPU6050_AX 0x3B #define MPU6050_AY 0x3D #define MPU6050_AZ 0x3F #define MPU6050_TP 0x41 // data not used #define MPU6050_GX 0x43 #define MPU6050_GY 0x45 #define MPU6050_GZ 0x47 short int AccX, AccY, AccZ; short int Temp; short int GyroX, GyroY, GyroZ; void setup() { // serial for debugging Serial.begin(115200); Serial.println("*** started"); // i2c as a master Wire.begin(); // wake it up Wire.beginTransmission(MPU6050_ADDR); Wire.write(0x6B); Wire.write(0); Wire.endTransmission(); } void loop() { // send start address Wire.beginTransmission(MPU6050_ADDR); Wire.write(MPU6050_AX); Wire.endTransmission(); // request 14bytes (int16 x 7) Wire.requestFrom(MPU6050_ADDR, 14); // get 14bytes AccX = Wire.read() << 8; AccX |= Wire.read(); AccY = Wire.read() << 8; AccY |= Wire.read(); AccZ = Wire.read() << 8; AccZ |= Wire.read(); Temp = Wire.read() << 8; Temp |= Wire.read(); // (Temp-12421)/340.0 [degC] GyroX = Wire.read() << 8; GyroX |= Wire.read(); GyroY = Wire.read() << 8; GyroY |= Wire.read(); GyroZ = Wire.read() << 8; GyroZ |= Wire.read(); // debug monitor Serial.print(" "); Serial.print(AccX); Serial.print(" "); Serial.print(AccY); Serial.print(" "); Serial.print(AccZ); Serial.print(" "); Serial.print(GyroX); Serial.print(" "); Serial.print(GyroY); Serial.print(" "); Serial.print(GyroZ); Serial.println(""); delay(20); }
IO4(SDA)と IO5(SCL)には複数の i2c デバイスを接続することができますが,アドレス 0x68 によって MPU-6050 を選択しています.MPU-6050 には多数のレジスタがあり,番号で区別してアクセスすることができます.たとえば 0x3B, 0x3C の2バイトにはX軸加速度が格納されていて,この番号を指定して読み出しています.0x6B はセンサの電源を制御するレジスタです.初期値はオフなので,0 を書き込むことでセンサを動作開始させています.(加えて,温度もゲットできるんですが,あくまでデータ校正用にセンサ温度を測定しているようなので,ここでは扱いません.)
このプログラムを動作させると,シリアルモニタ(115200 baud)につぎのような表示が出力されます.最初の3つの数値は,各軸方向の加速度で,符号つき 16 ビット整数で表現されています.フルスケール +32767〜−32768 が,およそ +2g〜−2g に対応します.それに続く3つの数値が角速度で,フルスケール +32767〜−32768 が,およそ +250°/s〜−250°/s に対応するようです.
2128 -592 16500 23 5 -9 2136 -588 16472 -13 -6 1 2108 -656 16388 -15 22 6 1388 -112 16580 -207 5621 808 584 -124 16544 75 4142 3171 1408 -952 16360 -3 6079 3341 2704 -1512 15456 -280 11240 2731 680 -964 16964 -482 13041 1024 -11452 -1224 29504 -45 -2055 -331 7076 336 9324 -75 1084 381 188 128 15832 8 479 732 1208 -2724 16228 23 -37 1126 332 -1652 17564 463 -11399 -72 -5932 -3852 21836 890 -21182 -2844 -3060 -576 22152 -3005 -3359 950 5308 2780 16656 -3070 2343 2086 4520 4700 17972 -4190 5058 -1351 4004 -1208 12012 -2934 22060 -3944
最初の数行は,センサチップの刻印面を上向きに静止させた状態に対応します.刻印面に垂直上方向がZ軸となるため,AccZにのみ約 1g が観測されています.直観的には −1g が観測されるはずですが,逆符号になっているようです.刻印文字の右(東)方向がX軸,上(北)方向がY軸となり,それぞれの軸方向の加速度(やはり逆符号)と,それぞれの軸を右ネジ方向に回転した場合の角速度(こちらは直観どおりの符号)が得られています.
シリアルモニタ上の数値をよく見ると,静止状態でも多少のオフセット(定常偏差)とノイズが含まれていることがわかります.加速度オフセットをキャンセルするには,ある軸を鉛直上方向したときの値と鉛直下方向にしたときの中間値をゼロ点とするか,あるいは重力加速度と測定軸を直交させたときの値をゼロ点とするなど,いろいろ工夫してみてください.角速度オフセットのキャンセルは,静止状態の値をそのまま引いてやればよいはずです.ノイズの除去には,適切な時定数のローパスフィルタを適用すればよいでしょう.つぎにあげる loop() を参考にしてください.なお,ACC_X_OFS などは,オフセット補正値(整数定数)です.
float AccX_f = 0.0f, AccY_f = 0.0f, AccZ_f = 0.0f; float GyroX_f = 0.0f, GyroY_f = 0.0f, GyroZ_f = 0.0f; void loop() { // send start address Wire.beginTransmission(MPU6050_ADDR); Wire.write(MPU6050_AX); Wire.endTransmission(); // request 14bytes (int16 x 7) Wire.requestFrom(MPU6050_ADDR, 14); // get 14bytes AccX = Wire.read() << 8; AccX |= Wire.read(); AccY = Wire.read() << 8; AccY |= Wire.read(); AccZ = Wire.read() << 8; AccZ |= Wire.read(); Temp = Wire.read() << 8; Temp |= Wire.read(); GyroX = Wire.read() << 8; GyroX |= Wire.read(); GyroY = Wire.read() << 8; GyroY |= Wire.read(); GyroZ = Wire.read() << 8; GyroZ |= Wire.read(); // filtering AccX_f = 0.9 * AccX_f + 0.1 * (AccX - ACC_X_OFS); AccY_f = 0.9 * AccY_f + 0.1 * (AccY - ACC_Y_OFS); AccZ_f = 0.9 * AccZ_f + 0.1 * (AccZ - ACC_Z_OFS); GyroX_f = 0.9 * GyroX_f + 0.1 * (GyroX - GYRO_X_OFS); GyroY_f = 0.9 * GyroY_f + 0.1 * (GyroY - GYRO_Y_OFS); GyroZ_f = 0.9 * GyroZ_f + 0.1 * (GyroZ - GYRO_Z_OFS); // debug monitor Serial.print(" "); Serial.print(AccX_f, 5); Serial.print(" "); Serial.print(AccY_f, 5); Serial.print(" "); Serial.print(AccZ_f, 5); Serial.print(" "); Serial.print(GyroX_f, 5); Serial.print(" "); Serial.print(GyroY_f, 5); Serial.print(" "); Serial.print(GyroZ_f, 5); Serial.println(""); delay(20); }
最新の Arduino 開発環境には「シリアルプロッタ機能」が組み込まれています.Tools から Serial Plotter を開くと,データフィールドごとに色分けされたグラフが表示されます.赤がZ軸加速度です.横1マスは約 2s です.おおよそ安定したデータが取れていることがわかります.
デフォルトの状態では,加速度は ±2g,角速度は ±250°/s がフルスケールとなっています.より広い測定範囲を得るには,setup() をつぎのように改変してください.
void setup() {
// serial for debugging
Serial.begin(115200);
Serial.println("*** started");
// i2c as a master
Wire.begin();
// wake it up
Wire.beginTransmission(MPU6050_ADDR);
Wire.write(0x6B);
Wire.write(0);
Wire.endTransmission();
// range of accel
Wire.beginTransmission(MPU6050_ADDR);
Wire.write(0x1C);
Wire.write(0x18); // 0x00:2g, 0x08:4g, 0x10:8g, 0x18:16g
Wire.endTransmission();
// range of gyro
Wire.beginTransmission(MPU6050_ADDR);
Wire.write(0x1B);
Wire.write(0x18); // 0x00:250, 0x08:500, 0x10:1000, 0x18:2000deg/s
Wire.endTransmission();
}
2つの GY-521 を i2c 接続するには,一方の GY-521 の AD0 端子を VCC に接続してください.これで i2c アドレスが 0x69 になり,もとのアドレス 0x68 の GY-521 と区別して通信することができます.3個以上は... ムリっぽいです.
このセンサチップについてのより詳しい情報については,メーカーのウェブページをご覧ください.また,プログラミングについてのより詳しい情報は Arduino Playground が参考になると思います.
タッチパネルは,ふつう液晶ディスプレイの表面に置かれ,ディスプレイに表示された画像コンテンツへのタッチ(クリック)を捉えることで,さまざまなインタラクションを実現するためのデバイスです.ここでは抵抗膜式タッチパネル(Aitendo で調達したもの)を SPI 通信によって Dongbeino に接続することを解説します.(TFT 液晶ディスプレイに付属したタッチパネルの読み取りについても参考になると思います.)

抵抗膜式タッチパネルは,下図に示すように,平行な2枚の抵抗膜(ほぼ透明)からなり,上側の抵抗膜の両端には電極 X+ / X− が,下側の抵抗膜にはそれと直交する軸の両端に電極 Y+ / Y− が取り付けられていて,これらが4つの線として引き出されています.

X+ / X− 間に電圧(V)をかけると,上側の抵抗膜に電圧の勾配が現れます.このとき,外力(タッチ)により上下の抵抗膜がある場所(X 方向の位置 x)で接触すると,その位置における上側抵抗膜の電位 V × x/d を,下側抵抗膜の Y+ から読み出せます.下側抵抗膜には電流はほとんど流れないので,接触点の電位がほぼそのまま Y+ から読み出せ,位置 x を求めることができます.上下の役割を入れ替えれば,Y 方向の位置 y も求められます.
この原理からわかるように,基本的に同時に取得できるタッチは1点のみです.複数の接触点がある場合は,それらの中央付近の「1点」として認識されます.ゆえに複雑なジェスチャを読み取ることはできませんが,簡単なインタフェースで安価にタッチパネルを実現できるので,自作派には重宝するデバイスです.
タッチパネルと Dongbeino(ESP-WROOM-02)との接続には,ADS7843(または XPT2046)という専用 IC を使います.抵抗膜での電圧勾配の生成,接触点の電圧計測と ADC,上下抵抗膜の役割入れ替え,通信の制御などをすべて自動的に行ってくれます.

Dongbeino へのデータ取り込みは SPI(Serial Pheripheral Interface)というシリアル通信を使います.SPI はマスター(ここでは Dongbeino)とスレーブ(ここでは ADS7843)の間をつなぐ4線(CS, CLK, MOSI, MISO)で双方向の通信をするものです.スレーブが複数となる場合もあり,共通の CLK, MOSI, MISO に「ぶら下がる」ように接続します.ESP-WROOM-02 では,CLK は IO14,MOSI は IO13,MISO は IO12 のピンに配置されています.CS(Chip Select;または CE: Chip Enable, SS: Slave Select)は,マスターから各スレーブに1本ずつ接続します.CS には余っているディジタル出力端子を使えばよいでしょう.CS はアクティブ LOW です.

マスターは,CS を LOW にしたスレーブにバイト列を送り出し,それと同時に同じ長さのバイト列をそのスレーブから受け取ります.つまり,同じ長さのバイト列をマスターとスレーブが同時交換するわけです.交換されるデータの長さ(ビット数)は CS が LOW の間のクロック数と同じになります.ひとかたまりのバイト列(パケットなど)の送受信が終われば,CS を HIGH に戻します.

ここで使用する ADS7843 は,下図のような SPI 通信をマスター(Dongbeino)との間で行います.詳しい説明は ADS7843 のデータシートに譲りますが,ここではマスターから A2:0=B001 (X 位置),A2:0=B101 (Y 位置),M=0 (12bit モード),S/D=1 (single-ended),PD1:2=B00 (power down with PEN) からなる 3 バイトデータを送ると,ADS7843 からは B000001110 に続いて 12 bit のデータとそれに後続する B000 がマスターに返されます.つまり,X 位置を調べるには {0x94, 0x00, 0x00} を,Y 位置を調べるには {0xd4, 0x00, 0x00} を送り,ADS7843 からの返答(3 バイト)のうち,下位 2byte を 3bitだけ右シフトすれば,12bit の整数値(0〜4095)が得られるわけです.

Dongbeino と ADS7843 および抵抗膜式タッチパネルの接続は,およそ下図のようになります.PENIRQ はタッチされているときに GND に接続されます.10kΩ でプルアップし,IO5 から読み取るようにしています.なお,MOSI (IO13) には LED が接続されていますが,SPI 通信には影響ありません.SPI 通信中は LED がやや暗く点灯するはずです.また,IO15 は 10kΩ でプルダウンされていますが,こちらも SPI 通信には影響ありません.

実際の配線は,横30穴のブレッドボードの場合,かなりキチキチです.下図を参考にしてください,ADS7843 のピン配列はデータシートを参照してください.ADS7833 は 0.65mm ピッチの SSOP16 というパッケージになっているため,ここでは 2.54mm ピッチへの変換基板(緑色の部品)を使っています.細かいハンダづけになりますね.また,タッチパネルからのフィルムケーブル(コネクタ部分は 1mm ピッチ)を,この変換基板の裏側(1.27mm ピッチの SOP16 用のパターン)にハンダづけしています.(ちょっと無理やり感ありです.)

タッチ位置を読み取りシリアルモニタに表示するプログラム例をつぎにあげます.ここでは 1000 サンプリングごとに 12bit 整数(0〜4095)を成分とする X-Y 座標を表示します.シリアルプロッタも利用できます.
#include <SPI.h> #define CS 15 #define PEN 5 int Count = 0; void setup() { // debug output Serial.begin(115200); delay(100); Serial.println(""); Serial.println("*** esp_touthPanel ***"); // SPI settings (SCK, MISO, MOSI, CS=IO15) pinMode(CS, OUTPUT); digitalWrite(CS, HIGH); SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); SPI.setFrequency(1000000ULL); // max 2MHz // D5 for PEN pinMode(PEN, INPUT); } void loop() { int x, y; if (touchPanel(x, y)) { if (Count++ % 1000 == 0) { Serial.print(x), Serial.print(", "), Serial.println(y); } } } bool touchPanel(int &x, int &y) { if (digitalRead(PEN) == HIGH) { // if not pen-down, return false return false; } else { // get data from ADS7843 unsigned char dataOut[3], dataIn[3]; // X-axis (X+) dataOut[0] = 0x94; dataOut[1] = 0x00; dataOut[2] = 0x00; digitalWrite(CS, LOW); dataIn[0] = SPI.transfer(dataOut[0]); dataIn[1] = SPI.transfer(dataOut[1]); dataIn[2] = SPI.transfer(dataOut[2]); digitalWrite(CS, HIGH); x = (dataIn[1] * 256 + dataIn[2]) >> 3; // Y-axis (Y+) dataOut[0] = 0xD4; dataOut[1] = 0x00; dataOut[2] = 0x00; digitalWrite(CS, LOW); dataIn[0] = SPI.transfer(dataOut[0]); dataIn[1] = SPI.transfer(dataOut[1]); dataIn[2] = SPI.transfer(dataOut[2]); digitalWrite(CS, HIGH); y = (dataIn[1] * 256 + dataIn[2]) >> 3; // return true return true; } }
ここでは SPI のクロックを 1MHz にしています."MSBFIRST" や "SPI_MODE0" はオマジナイと思ってください.SPI.transfer() はスレーブとのバイト交換を実行するメソッドで,引数にスレーブへの送信バイトを指定し,実行後の返値がスレーブからの受信バイトとなります.ここでは3バイトを交換しています.このバイト列交換の前後で CS(アクティブ LOW)を操作しています.これが SPI の基本的な使い方です.