ちょっと前に、2つのWROOM-02をそれぞれサーバー/クライアントとして構成して相互接続しようとしたら、なかなかうまくいかなかった、という話を書いた。回避策が見つからない間の気晴らしとして、サーボ雲台の操作用に、WROOM-02にA/Dコンバータを介してジョイスティックを接続してみた。
構成
A/Dコンバータとジョイスティック以外はSTA-ESPという名前でWIFIステーション用として構成中のブレッドボードを使い、追加した2つの部品を別のブレッドボードに載せた。おもな構成品は以下のとおり (Arduino ADKは、+5Vの供給用にのみ使用)。
- AE-ESP-WROOM-02
秋月電子のWROOM-02 DIP化キット。 - MCP3002
Microchip Technology社の、2チャネル10bit 逐次比較型A/Dコンバータ。SPI方式のシリアルI/F付。
秋月電子の通販で180円だった。回路作りやプログラミングには、秋月の通販ページにリンクされているデータシートを参考にした。 - TA48033S
東芝の3.3V三端子レギュレータ。Arduino ADKの5V出力を接続し、WROOM-02、MCP3002、ジョイスティックに3.3Vを供給する。 - PARALLAX 2-axis Joystick
秋月電子の通販で購入した2軸アナログジョイスティック。センターリターン用のスプリング入りで手を離すと勝手に中央に復帰する。780円だったが、AE-ESP-WROOM-02が650円なので高く感じてしまう。
説明書きや仕様はパッケージの紙の裏側に書いてある。重要なポイントは、各軸には10KΩのボリューム抵抗が入っており3.3~5Vで動きますよ、ということだろう。親指で操作するのにちょうどよい程度の大きさだが、リターンスプリングがちょっと強め。
ジョイスティックの値の読み取り
アナログ式のジョイスティックでは、スティックの倒し加減に応じて可変抵抗による電源(+3.3V)の分圧のされ方が変わるので、LEFT/RIGHT方向およびUP/DOWN方向の各出力には、それぞれおよそ0~3.3Vの電圧が現れる。
今回の構成では、A/Dコンバータでそれぞれの電圧を読み取り、デジタル値に変換して利用する。mcp3002が10bit出力なので、倒し具合に応じて0~1023の範囲のデジタル値が現れる。回路図にあるように、ジョイスティックの2つの出力(L/RおよびU/D)は、A/Dコンバータのch0とch1に接続している。
mcp3002は、SPI方式のシリアルI/Fを備えているので、WROOM-02との接続線は4本で済む。そのうち2本が(mcp3002から見て)データ入力用(Din)および出力用(Dout)で、残りが変換動作の許可用(CS)と、データ入出力時の同期信号(CLK)になる。WROOM-02にはあまりGPIOポートに余裕がないので、4本で済むのはとても助かる。今回は以下のように接続した (左側がWROOM-02で右側がMP3002)。
- IO2 — CS/SHDN
- IO4 — Din
- IO5 — Dout
- IO16 — CLK
※ 今回の接続では、WROOM-02の備えるSPI機能は利用しておらず、GPIOポートのHIGH/LOWの読出し/書込みによってI/Fを実装した。
A/D変換シーケンス
WROOM-02側から見たA/D変換の実施指示と値の取得は以下のような手順で行う。
- CS(Chip Select)をアクティブ化
IO2をLOWとする。初期化時や変換終了後はHIGHにしておく。 - 変換対象のチャネルを指定
ch0ならば1101B 、ch1ならば1111Bの4ビットを、最上位ビットから順に書き込む。書込みが終わるころにはADC内部のS/Hコンデンサに電荷がたまり逐次比較が開始する。 - 変換結果の読出し
スタートビット(0)を含めて11ビットを読み出す。A/Dコンバータはスタートビットの後、MSBから順にデータを送り出してくる。 - CSを非アクティブ化
IO2をHIGHとする。これによって、mcp3002はスタンバイ状態に戻る。
対象チャネルの書込み、結果データの読出し共によく似た操作になっていて、
- 書込み時には、DinをHIGHまたはLOWに設定して、CLKの立ち上がりエッジを与え、規定時間待った後に下げる。
- 読出し時には、CLKの立ち上がりエッジを与え、規定時間待った後にDoutがHIGHかLOWかを読み取ってCLKを立ち下げる。
- 以下に載せたスケッチでは、CLKの上げ下げやポートとのやり取りを分かりやすく書いたので、「規定時間の待ち」を delayMicroseconds(1); で表現した。しかしながら、データシートに書かれているタイミングチャートから分かるように、チップ自体はもっと高速に変換動作を行える。より高速なデータ収集が必要なら別の書き方になる。
スケッチ
いろいろなテストや検証の意味で、ジョイスティックのUP/DOWN方向の倒し具合により、LEDの点滅速度が変わるスケッチを作ってみた。また、UP/DOWN、LEFT/RIGHTそれぞれが直前の読み取り値と10以上変化した値を返した場合、シリアルモニタに読み取り値を送信する。
ESP8266 CommuinityのTickerライブラリを使用していて、各チャネルの値の読出しは10msec単位に行っている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
#include <Ticker.h> #define TICK_CONST 10 #define RED_LED 12 #define ADC_CS 2 #define ADC_DIN 4 #define ADC_DOUT 5 #define ADC_CLK 16 #if defined(HIGH) #undef HIGH #define HIGH 1 #endif Ticker ticker; volatile int data_ch0 = 0; volatile int data_ch1 = 0; #define ADC_READ_CH0 0xd #define ADC_READ_CH1 0xf int adc_read(byte read_channel) { int i; // enable ADC digitalWrite(ADC_CS, 0); // specify MUX channel. for(i = 0; i < 4; i++) { delayMicroseconds(1); digitalWrite(ADC_DIN, (read_channel<<= 1) & 0x10 ? 1 : 0); digitalWrite(ADC_CLK, 1); delayMicroseconds(1); digitalWrite(ADC_CLK, 0); } // read conversion result. int data = 0; for(i = 0; i < 11; i++) { data = data << 1; delayMicroseconds(1); digitalWrite(ADC_CLK, 1); delayMicroseconds(1); data += digitalRead(ADC_DOUT); digitalWrite(ADC_CLK, 0); } // shutdown ADC digitalWrite(ADC_CS, 1); return (data & 0x03ff); } void ticker_func() { int n = adc_read(ADC_READ_CH0); if (abs(data_ch0 - n) > 10) { data_ch0 = n; Serial.println("ch0: " + String(data_ch0)); } n = adc_read(ADC_READ_CH1); if (abs(data_ch1 - n) > 10) { data_ch1 = n; Serial.println("ch1: " + String(data_ch1)); } } void setup() { delay(500); Serial.begin(115200); pinMode(RED_LED, OUTPUT); digitalWrite(RED_LED, 0); pinMode(ADC_CS, OUTPUT); pinMode(ADC_DIN, OUTPUT); pinMode(ADC_CLK, OUTPUT); pinMode(ADC_DOUT, INPUT); digitalWrite(ADC_CS, 1); digitalWrite(ADC_CLK, 0); digitalWrite(ADC_DIN, 0); ticker.attach_ms(TICK_CONST, ticker_func); } void loop() { byte led = digitalRead(RED_LED); delay(data_ch1); digitalWrite(RED_LED, led ^= 1); } |
シリアルモニタの様子を見てみると、UP/DOWN、LEFT/RIGHTともに、中央に自動リターンしたときは、500~510の範囲の値を示し、両端を含めて操作すると0~1023の値が出てくる。使い始めたばかりだからかもしれないが、中立時にも値がふらついたりはしなかった。
変換速度について
adc_read() という関数で指定チャネルの変換と読出しを行っている。いかにも遅そうなので、loop()を以下のようにして時間を測ってみた。なお計測時には、ticker.attach_ms() はコメントにしておいた。
1 2 3 4 5 6 7 8 9 |
... void loop() { int i; unsigned long start = millis(); for(i = 0; i < 10000; i++) adc_read(ADC_READ_CH0); Serial.println("elapsed: " + String(millis() - start)); delay(500); } |
シリアルモニタには、10000回のループに要する時間がミリ秒単位で表示され、常に877という結果だった。素直に受け取ると、adc_read()の呼び出し一回につき87.7μ秒となる。10ksps程度の変換レートまでなら、こんな具合ののんびりしたコードでよさそうである。
操作例
ジョイスティックを操作しているところを動画にしてみた。撮影しやすいようにLEDを手前のブレッドボードに持ってきている。
きょうのまとめ
クロックを15回上げ下げすることでチャネル指定とデータ読み出しができた。SPI用のライブラリ関数は使わなかったのだけど、ジョイスティックが相手ならこんなもんでいいだろうし、例えばサーボの駆動を含んだ ticker_func()に追加しても、さしたる影響はないだろう。ということは、サーボを駆動する側の回路にジョイスティック周りを載せて、wifiではなく有線でコントロールというのもアリかもしれない。
サクラの季節も近づいてきたので、ちょっと頑丈な自撮棒の先にサーボ雲台を載せ、ふだんとは違う視点でAIR A01を使ってみるのも面白いかも、とか思い始めているところ。