ラベル プログラミング の投稿を表示しています。 すべての投稿を表示
ラベル プログラミング の投稿を表示しています。 すべての投稿を表示

2020年1月8日水曜日

ESP8266を用いたwifi位置偽装装置の電流値測定

ESP8285を用いてモバイル電源で動作する位置偽装装置の開発を予定している

モバイル電源の使用にはESP系チップの高い消費電流がネックとなる
定格とは別に位置偽装装置システムにおける実消費電流を調査する

ESP8266の動作と消費電流

パワーオン時に200~300mAのピークがあると言われている
いったん起動してしまえばデータシートどおりの動作をする
起動時に電源の電流容量が不足すれば電圧降下によって起動できないケースがでてくる
自己復旧できないのでこれは問題

測定対象システムの概要

位置偽装ユニット
ESP8266を6基、RN-41-SMを1基使用
ESP8266はwifiサーバとして動作し、30秒おきに設定が変更されリスタート
RN-41は30秒おきに接続先からマクロを受け取る

実測定結果(起動時)

5回測定のピーク電流が1200mA弱
300mA×6を覚悟していたが実動作上はタイミングが重複するわけではない模様
(元からサーバをbeginするタイミングはズラしている/効いているかは不明)
測定モレするようなスパイク状のピークがあった場合は見逃してしまうので注意

電源端子のあたりで測定
測定例

測定結果(動作中)

動作を10分継続した場合のピーク電流は600mA弱
定常電流との振れ幅は100mA未満
こちらは想定よりも高め

考察

起動時のピーク電流さえやり過ごせば1Aもあれば良い
・ESP8266を二群に分けてディレイ電源供給
 →ピーク電流は900mA程度?
・ESP8266をリレー形式で1つずつディレイ電源供給
 →ピーク電流は700mA程度?
剛腕戦略をとるならば2Aのモバイル電源を用意すればいい

消費電力は定常電流に3.0Vをかければいいので定常ピークで考えても1800mW程度
モバイル運用しても問題ない程度の消費電力と言える

INA219ブレイクアウトボード+M5Stackで簡易電流測定

以下は簡易電流測定器を作成したメモ

動機は電流測定無しでESP8266系チップを弄るのに限界を感じたため

INA219ブレイクアウトボード

今回はAdafruit製のブレイクアウトボードを使用
測定した電流をI2C経由で読み取り可能
(同様の模造品が沢山あり、そちらでも問題無いハズ)

電流測定したい箇所にVin+とVin-を使って挟み込む(GND側がVin-)
電圧も測定したければ測定対象と更にGNDを接続する
困ったらAdafruit

M5Stack

ディスプレイがあり、microSDカードが読み書きできるため使用
(ただし今回はまだSDカード出力は実装していない)
GPIO21(SDA)、GPIO22(SDL)を用いればI2Cライブラリを使用可能
(SDA、SDLポートが別途あるが、内部で結線されている)

作成方法(配線)

M5Stackの上部端子(メス/2.54mmピッチ)に
・GND
・3V
・GPIO21(SDA)
・GPIO22(SDL)
が並んでいるのでこれをINA219に結線すればよい

INA219の各端子に2.54mmピッチのピンを立てる
都合の悪いことにSDAとSDLの位置が逆
今回は極細配線で強引にクロス接続した
(本当は中継基板を作ってそこでクロスさせた方が楽で賢い)

Vin+とVin-にアクセスしやすいように端子台とメス端子をハンダ付けして終了

INA219(4ピンオス)を上部端子(8ピンメス)に挿して運用すると事故原因になる
このためダミー4ピンオスを追加して8ピンオスとしている
(ダミーは結線せず、根本で切断)

結線:SDAとSDLのクロスが強引
結線:SDAとSDLをこね回した結果のコゲが痛ましい

作成方法(プログラミング)

INA219ブレイクアウトボードはArduino用ライブラリが存在
(Adafruit_INA219.h)
機種依存が無いためM5Stackでもそのまま流用できる
Adafruit社ページからDLしてArduinoライブラリにインポート
(あるいは『ライブラリ管理』から『INA219』で絞り込んで導入)

M5Stackの画面表示はサンプルスケッチ参照のこと

SDカードに吐き出す部分を作ればロガーになるがひとまず測定のみ実装
ピーク電流を比較・更新する以外はAPIの結果をそのまま表示しているだけ

測定回路のGNDをM5StackのGNDに接続する必要あり/写真の使い方では電圧測定値は無効
マルチメータで確認

2016年2月9日火曜日

EPS-WROOM-02サンプルプログラムのメモ(ESP8266SSDP)

前回の続き

EPS8266SSDP:

 SSDP(Simple Service Discovery Protocol)のライブラリ(ESP8266SSDP.h)のサンプルプログラム

・SSDP

 SSDPプロトコルを用いてフィリップスのIoT照明機器hueを制御するスケッチ…らしいが
 いかんせんhueもブリッジも現物が無いので動作確認ができない

 このプロトコルを用いたWebサービスを使うときのためにSSDPって言葉だけ頭の片隅に残しておこう

2016年2月8日月曜日

ESP-WROOM-02サンプルプログラムのメモ(ESP8266mDNS)

前回の続き

サンプルスケッチ呼び出し方法


ESP8266mDNS:

 マルチキャストDNSライブラリ(ESP8266mDNS.h)のサンプルプログラム

・DNS_SD_Arduino_OTA

 無線(OTA:Over The Air)経由でESP-WROOM-02のファームアップデートを行うスケッチ
 (※ArduinoIDEからOTA経由のアップデートを行う場合はセットアップが必要(ここでは割愛))

 接続したい無線アクセスポイントのssidとpassにソースを修正すること

 OTA経由で別のスケッチにアップデートした場合、OTA経由でアップデートする機能は失われる
 (当たり前だが)
 

・mDS_Web_Server

 ウェブサーバのサンプルスケッチ
 接続してきたクライアントに対して簡単なhtmlを生成して返す
 接続したい無線アクセスポイントのssidとpassにソースを修正すること

 起動後、シリアル出力にて割り当てられたipアドレスを表示するので
 同一ネットワーク上に接続されたPC(あるいはスマホ等)のブラウザにこのアドレスを入力する

 ブラウザに
   Hello from ESP8266 at 192.168.XXX.YYY
 という表示がされたら成功

 html形式に変換するだけで自由に出力可能なので使い道が多いサンプルスケッチ
 ブラウザで接続するだけなのでアクセスも簡単 

2016年2月5日金曜日

ESP-WROOM-02サンプルプログラムのメモ

Arduino for ESP8266環境ではArduinoIDEからESP-WROOM-02の開発が可能
そしてArduino同様にESP-WROOM-02のサンプルプログラム(スケッチ)が多数収録済である

以下はこのサンプルプログラムに関するメモ


使用方法:
 
 ArduinoIDEからサンプルプログラムを使う方法
 (Arduinoでの使用方法と同じなので読み飛ばしてもよい)

 
ArduinoIDEでのスクショ
1.ArduinoIDEの上部メニューから『ファイル』→『スケッチの例』→『(分類)』→『(スケッチ名)』を選択

 2.新しいウィンドウに選択したサンプルプログラム(サンプルスケッチ)がロードされる

 3.変更が必要な個所を修正したのちビルド

  (※スケッチを保存する場合は別名で保存すること)


サンプルスケッチのメモ:
 
 以下はサンプルスケッチの解説メモ
  動作確認はすべてスイッチサイエンス製ESP-WROOM-02開発ボード

esp8266:

 ・Blink
  毎度おなじみLED点滅スケッチ
  LEDが接続先のBUILTIN_LEDピンはTXDピン(ピン番号1)
  
  スイッチサイエンスのボードはUSB-シリアル変換が接続されているため挙動は以下のようになる
   ・PCとUSB接続している場合はRX_LEDが点滅する
    (※TX/RXでクロス配線されているため/シリアルターミナル等で接続する必要は無い)
   ・USB経由で給電のみ行う場合はRX_LEDは消灯したまま

  よってUSB接続前提ならばオンボード上のLEDがコントロール可とわかる
   (通信時のLED点滅は抑制できないが)
  USB-シリアル変換機が働いていないと駄目っぽい

  同様にRXDピン(ピン番号3)を指定すればTX_LEDが光るのでは?と思ったがこれは失敗


 ・BlinkWithoutDelay
  millis()を用いたLED点滅スケッチ
   (※millis()/起動してからの経過時間をmsec単位で取得する関数)


 ・CallSDKFunctions
  ESP-WROOM-02のホスト名を取得するテストプログラム
   (※wifi_station_get_hostname()/ホスト名を取得する関数)
  ホスト名はMACアドレスから自動生成される

  生成されるホスト名:
   ESP_XXYYZZ (XXYYZZはMACアドレスの下位3バイト)

  サンプルにはないがwifi_station_set_hostname()関数でホスト名の変更が可能
  
  ホスト名の変更方法:
   以下の関数を実行
   この情報は保存されないため、ホスト名はリセットすると再度自動生成される

   wifi_station_set_hostname("hostname");


本日ここまで

2015年8月31日月曜日

ESP-WROOM-02プログラミング:スリープのテスト

前回のメモ:3つのSleepの使い分けについての検証用プロジェクト


プロジェクト概要:
 無線LANルータに対して接続を確立した後、処理を行わずスリープを誘発する

 Sleepの設定を切り替えてビルドし、各Sleepの挙動をチェックするために用いる
 Deep_Sleepについては大きく使用方法が異なるのでここでは割愛
 ※外部機器による電流値の測定が必要/表面上の動作にはまったく差が無い


データシートより/通信およびスリープ中の電流値


準備すること:
 ・ArduinoIDE上でのESP8266開発環境構築
 ・ESP-WROOM-02を接続してもよい無線LANルータのSSIDとパスワードをメモ
 ・電流値を測定できる外部機器


作成方法:
 ・ソースをコピペして以下の情報を修正する
   ・SSID
   ・SSIDに対するパスワード
   ・測定したいスリープモード設定行のみを有効にする
    (SLEEP無効/MODEM_SLEEP/LIGHT_SLEEP)
 ・ビルドしてダウンロードモードのESP-WROOM-02に書き込み
  (→過去メモ参照)


実行方法:
 ・外部機器でESP-WROOM-02の電流値を測定開始
 ・ESP-WROOM-02をブートモードで起動
 ・デバッグ出力より無線LANルータとの接続を確認
  (『WiFi connected』の表示を確認)
 ・起動~ルータとの接続~スリープ状態までの電流値を測定する


うまくいかない場合は以下をチェック:
 無線LANルータの規格
   →ESP-WROOM-02が対応できるのは802.11b/g/n

 ・チップへの給電が十分か
   →通信時は80mA以上消費する

 ・ダウンロードモードからの設定変更は忘れていないか
   →GPIO0ピンをL→Hに戻し忘れることが多い
    ブートモードにしないと当然ながら起動しない


注意点:
 ・無線LANルータの設定(ステルスID/接続先限定など)によっては動かない


測定結果:

 グラフはこちら

 802.11gにて無線LANルータと接続した
 10msecの分解能でESP-WROOM-02の電流値を測定
 (電流センサモジュールArduinoUno、SDカードシールドを使用/精度は参考程度)
 以下はすべてloop()内での電流値について

 ・NONE_SLEEP(スリープ無効)は常に70mA
 ・MODEM_SLEEPは定常状態が15mA
  150msec前後の周期でパルス状の電流変化(ピーク70mA程度)あり
・LIGHT_SLEEPは定常状態が1mA程度
  MODEM_SLEEPよりも高い頻度で15mAと70mAの変化が混じる

 スリープ時に定期的に消費される70mAは無線LANルータとの接続を維持するための通信と思われる
 測定の分解能が不足しているためこの環境でこれ以上の測定はあまり意味が無い

おまけ:
 setup()実行前の初期化段階で200mA程度の電流値を測定
 測定時の分解能の低さを考えるとピークはもっと高いかも
 状態別電流値の代表値を参考に定格電流を決めると起動でしくじるかも


#include <esp8266wifi.h>

extern "C" {
#include "user_interface.h"
}

//無線LANルータのIDおよびパスワード
const char* ssid     = "XXXXXXXX";
const char* password = "YYYYYYYY";

void setup() {  
//スリープモードの選択
//    wifi_set_sleep_type(NONE_SLEEP_T);
//    wifi_set_sleep_type(MODEM_SLEEP_T);
  wifi_set_sleep_type(LIGHT_SLEEP_T);

  Serial.begin(115200);
  delay(10);

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}
void loop() {
  delay(5000);
}

2015年8月27日木曜日

ESP-WROOM-02プログラミング:スリープしながら定期的にツイート

ESP-WROOM-02のDeep-Sleepを使ったツイッター操作プロジェクトのメモ

プロジェクト概要:
 スリープしながら定期的にWakeUPしてツイートを行うプロジェクト

 ツイートにはStewGateUのWebAPIを使用
 ネタ元はESP8266WIFI/WiFiClientサンプルコードとマイクロテクニカESP-WROOM02簡単マニュアル


Deep-Sleep機能とは?:
 低消費電力(消費電流平均10uA)でスリープする機能
 
 スリープ中は以下の機能以外のすべてを停止する
  ・リセット信号の監視と受理
  ・スリープ時間カウント用のタイマ
  ・ウェイクアップ用信号の出力(IO16)

 スリープからの復旧方法はリセットのみ
 リセットするとプログラムの最初から実行される(通常時にリセットをかけたのと同じ)
 
 スリープというとなんだか特定の状態で休止してそこから復旧するように思えるが
 このDeep-Sleepはリセット信号の受理と目覚まし時計以外のすべてを終了してしまう
 目覚まし時計が鳴るとIO16ピンにLパルスが出力されるので
 IO16ピンを自身のリセットピンに接続しておけば、自身のリセットをかけることが出来る

 Arduinoのアドオンなどに用いる場合は、Arduino側からリセットをかけることも可能 

データシートより/最終行がDeep-Sleep

準備すること:
 →基本的に前回までと同じ

 ・ArduinoIDE上でESP8266の開発環境構築(ググろう!)
  ・3V3とGNDに十分な電流が供給可能な3.3V電源を接続
 ・IO16ピンとRSTピンを接続
 ・TX/RXにはデバッグ用にシリアル―USB変換機を接続する
 ・ツイートに用いるTwitterアカウントの準備(新規アカウント推奨)
 ・StewGateUにてTwitterアカウント登録およびトークン取得
 ・ESP-WROOM-02を接続してもよい無線LANルータのSSIDとパスワードをメモ


作成方法:
 ・ソースをコピペして以下の情報を修正する
   ・SSID
   ・SSIDに対するパスワード
   ・StewGateUで取得したトークン
 ・ビルドしてダウンロードモードのESP-WROOM-02に書き込み
  (→過去メモ参照)


実行方法:
 ・ESP-WROOM-02をブートモードで起動
 ・起動→接続→ツイート→(指定時間スリープ)→再起動→接続→ツイート…
  が反復することを確認
  (※止めない限りずっと反復するので注意


うまくいかない場合は以下をチェック:
 無線LANルータの規格
   →ESP-WROOM-02が対応できるのは802.11b/g/n

 ・ダウンロードモードからの設定変更は忘れていないか
   →GPIO0ピンをL→Hに戻し忘れることが多い
    ブートモードにしないと当然ながら起動しない

 ・チップへの給電が十分か
   →今回これで大ハマリした
    当初はaitendoのVBUSキットを用いてノートPCのUSBポートから電源を取っていたのだが
    何回やってもリセット動作に失敗していた
    (リセット後、ブートローダーの段階でハングアップする)

    原因はどうもノートPC側の省電力機能らしい
    スリープから復旧する際の電流が不足するために起動に失敗する
    (ネゴシエーション時の100mA制限に引っかかる?)
    定常状態では十分な電流が供給できていたため気づかなかった

    電源をACアダプタに変更して解決


注意点:
 ・ソースの大半はESP8266WIFI/WiFiClientサンプルコードからの流用
 ・無線LANルータの設定(ステルスID/接続先限定など)によっては動かない

 ・ソース内でanalogRead(17)しているのは
  『結線していないアナログピンの値(不定値)を取得して乱数の種にする』
  ため
  Arduinoで良く使われている処理を真似してみた
  今回はリセット時にカウンタがクリアされてしまうため、これが無いと乱数の値が重複する

 ・ESP.deepSleep()コール後からスリープされるまでに遅延がある
  このため
   loop()末尾でESP.deepSleep()コールするとスリープに入る前にループ先頭に飛んでしまい動作がおかしくなる場合がある
  ESP.deepSleep()後に適当なdelay()を入れること


 
#include <esp8266wifi.h>

//接続先のIDおよびパスワード
const char* ssid     = "XXXXXXXX";
const char* password = "YYYYYYYY";
 
const char* host = "stewgate-u.appspot.com";
//http://stewgate-u.appspot.com/より取得したトークン
const char* token = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
//スリープ時間60秒
const int sleepTimeSec = 60;

void setup() {
  Serial.begin(115200);
  delay(10);

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  
  String text;
  randomSeed(analogRead(17));
  text += String(random(0,9999), DEC);
  text += ":Hello!ESP-WROOM02";

  postMsg(text);
  
  ESP.deepSleep(sleepTimeSec * 1000 * 1000, WAKE_RF_DEFAULT);
}

bool postMsg(String msg)
{
  Serial.print("connecting to ");
  Serial.println(host);
  
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return false;
  }
  
  client.println("POST /api/post/ HTTP/1.0");

  client.print("Host: ");  
  client.println(host);
  
  int msgLength = 40;
  msgLength += msg.length();
  client.print("Content-length:");
  client.println(msgLength);
  client.println("");

  client.print("_t=");
  client.print(token);
  client.print("&msg=");
  client.println(msg);

  delay(10);
  
  // Read all the lines of the reply from server and print them to Serial
  while(client.available()){
    String line = client.readStringUntil('\r');
    Serial.print(line);
  }
  
  Serial.println();
  Serial.println("closing connection");

  return true;  
}

void loop() 
{
}



2015年8月17日月曜日

ESP-WROOM-02のメモ(8):モード切替GPIOポートについての検証2

前回に引き続きモード切替GPIOポートについての懸賞
以下はモード切替GPIOポートが起動後も入力ポートとして働くかどうか確認するためのプロジェクト

データシートより
プロジェクト概要:
 GPIO0、GPIO2、GPIO15が入力ポートとして動作することを確認する
  各ポートに接続されたタクトスイッチの状態を読み取りLEDを点滅させる

準備すること:
 ・3V3とGNDに電源を接続
 ・LEDと適当な抵抗を3つ用意(まだ接続しないこと
 ・タクトスイッチを3つ用意(まだ接続しないこと

作成方法&実行方法:
 ・ソースをコピペ
 ・ダウンロードモードでESP-WROOM-02を起動する
 ・ビルドして書き込み
 ・GPIO0、GPIO2、GPIO15にタクトスイッチを接続(→もう片側をGNDへ)
  (※ブート用の接続は外すこと/ジャンパなど)
 ・GPIO12、GPIO13、GPIO14に抵抗とLEDを直列に接続(→もう片側をGNDへ)
  (※すでにフラッシュブートしているので注意)

 実行するとGPIO0、GPIO2、GPIO15が入力ピンとして機能するのがわかる


 
#define CONFIG_PIN_1 0
#define CONFIG_PIN_2 2
#define CONFIG_PIN_3 15

#define LED_PIN_1 12
#define LED_PIN_2 13
#define LED_PIN_3 14

void setup() {
  pinMode(CONFIG_PIN_1, INPUT_PULLUP);
  pinMode(CONFIG_PIN_2, INPUT_PULLUP);
  pinMode(CONFIG_PIN_3, INPUT_PULLUP);  
  
  pinMode(LED_PIN_1, OUTPUT);  
  pinMode(LED_PIN_2, OUTPUT);  
  pinMode(LED_PIN_3, OUTPUT);    
}

void loop() {  
  if(digitalRead(CONFIG_PIN_1) == LOW)
  {
    digitalWrite(LED_PIN_1, HIGH);
  }
  else
  {
    digitalWrite(LED_PIN_1, LOW);    
  }

  if(digitalRead(CONFIG_PIN_2) == LOW)
  {
    digitalWrite(LED_PIN_2, HIGH);
  }
  else
  {
    digitalWrite(LED_PIN_2, LOW);    
  }

  if(digitalRead(CONFIG_PIN_3) == LOW)
  {
    digitalWrite(LED_PIN_3, HIGH);
  }
  else
  {
    digitalWrite(LED_PIN_3, LOW);    
  }
  
  delay(10);
}

 



ESP-WROOM-02のメモ(7):モード切替GPIOポートについての検証1

ESP-WROOM-02は起動時にGPIOポートを参照することで起動するモードの切替を行う

データシートより

モードは以下の2つ
 ・ダウンロードモード
  内蔵フラッシュROMへファームウェアを書き込む場合に用いる

 ・フラッシュブートモード
  内蔵フラッシュROMに書き込まれているファームウェアを実行する


参照されるGPIOポートはGPIO0、GPIO2、GPIO15の3ポート
このポートはブート後は普通のGPIOポートとして使用できる
以下は上記ポートが出力ポートとして働くかどうか確認するためのプロジェクト


プロジェクト概要:
 GPIO0、GPIO2、GPIO15が出力ポートとして動作することを確認する
  各ポートに接続されたLEDを点滅させる


準備すること:
 ・3V3とGNDに電源を接続
 ・LEDと適当な抵抗を3つ用意(まだ接続しないこと


作成方法&実行方法:
 ・ソースをコピペ
 ・ダウンロードモードでESP-WROOM-02を起動する
  その後、GPIO0、GPIO2、GPIO15をオープン(何も接続しない)にする
  (例:マイクロテクニカボードの場合はENピン以外のジャンパを取り外す)
  ※ファーム書き込み終了後、強制的にフラッシュブートするため
   出力ポートに直接3.3VとGNDが接続されるのを防ぐ
 ・ビルドして書き込み
 ・GPIO0、GPIO2、GPIO15に抵抗とLEDを直列に接続(→もう片側をGNDへ)
  (※すでにフラッシュブートしているので注意)

 実行するとGPIO0、GPIO2、GPIO15が出力ピンとして機能するのがわかる


注意点:
 ・ファーム書き込み後フラッシュブートモードで起動しないこと
  理由は前述のとおり出力ポートに3.3VとGNDが接続されてしまうため
 ・もしやるなら電気的な安全を確保するかブート後接続を外すための十分なウェイトをいれること 

入力ポートの検証は次回

 
#define CONFIG_PIN_1 0
#define CONFIG_PIN_2 2
#define CONFIG_PIN_3 15

void setup() {
  pinMode(CONFIG_PIN_1, OUTPUT);
  pinMode(CONFIG_PIN_2, OUTPUT);
  pinMode(CONFIG_PIN_3, OUTPUT);  
}

int counter = 0;
void loop() {
  digitalWrite(CONFIG_PIN_1, counter % 2);
  digitalWrite(CONFIG_PIN_2, counter % 2);
  digitalWrite(CONFIG_PIN_3, counter % 2);

  delay(500);
  counter++;
}

 



2015年8月15日土曜日

ESP-WROOM-02プログラミング:ボタンを押したら最新のメンションを取得

前回に続きESP-WROOM-02 を使ったツイッター操作プロジェクトのメモ

プロジェクト概要:
 ボタンを押すと最新のメンション(返信)を取得するプロジェクト
 
 ツイートにはStewGateUのWebAPIを使用
 ネタ元はESP8266WIFI/WiFiClientサンプルコードとマイクロテクニカESP-WROOM02簡単マニュアル

動作のイメージ/Web APIを用いた最新メンションの取得

準備すること:
 →基本的に前回と同じだが、タクトスイッチ周りが変更されているので注意

 ・ArduinoIDE上でESP8266の開発環境構築(ググろう!)
  ・3V3とGNDに十分な電流が供給可能な3.3V電源を接続
 ・GPIO5にタクトスイッチを接続
  (プルアップ設定がOKだとわかったので抵抗不要/もう片側はGNDへ)
 ・TX/RXにはデバッグ用にシリアル―USB変換機を接続する
 ・ツイートに用いるTwitterアカウントの準備(新規アカウント推奨)
 ・StewGateUにてTwitterアカウント登録およびトークン取得
 ・ESP-WROOM-02を接続してもよい無線LANルータのSSIDとパスワードをメモ


作成方法:
 ・ソースをコピペして以下の情報を修正する
   ・SSID
   ・SSIDに対するパスワード
   ・StewGateUで取得したトークン
 ・ビルドしてダウンロードモードのESP-WROOM-02に書き込み
  (→過去メモ参照)


実行方法:
 ・ESP-WROOM-02をブートモードで起動
 ・デバッグ出力より無線LANルータとの接続を確認
  (『WiFi connected』の表示を確認)
 ・タクトスイッチを押すと最新のメンションがデバッグ出力される
  (※取得日時やホスト名などの付加情報も同時に出力される)


うまくいかない場合は以下をチェック:
 無線LANルータの規格
   →ESP-WROOM-02が対応できるのは802.11b/g/n

 ・チップへの給電が十分か
   →通信時は80mA以上消費する

 ・ダウンロードモードからの設定変更は忘れていないか
   →GPIO0ピンをL→Hに戻し忘れることが多い
    ブートモードにしないと当然ながら起動しない

 ・レスポンス取得前のdelay()を増やしてみる
   →リクエストを出してから返事が来るまでに若干の待ち時間があるため
    即座に取得しようとすると失敗する様子


注意点:
 ・ソースの大半はESP8266WIFI/WiFiClientサンプルコードからの流用
 ・無線LANルータの設定(ステルスID/接続先限定など)によっては動かない
 ・マイクロテクニカESP-WROOM02簡単マニュアルのパスは尼のマイクロテクニカショップでESP-WROOM-02を買ったら付いてきた
  各機能の使い方が日本語で簡潔にまとめられておりおススメ
  (ただし、デフォルトファームをATコマンドで操作する前提での解説)

 
#include <esp8266wifi.h>

#define KEY_PIN 5

//接続先のIDおよびパスワード
const char* ssid     = "XXXXXXXX";
const char* password = "YYYYYYYY";

const char* host = "stewgate-u.appspot.com";
//http://stewgate-u.appspot.com/より取得したトークン
const char* token = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
void setup() {
  Serial.begin(115200);
  delay(10);

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  
  //プルアップが使えるのでそちらに変更
  pinMode(KEY_PIN, INPUT_PULLUP);
}

bool getLastMention(void)
{
  Serial.print("connecting to ");
  Serial.println(host);
  
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return false;
  }

  //ツイートとはアドレスが異なるので注意  
  client.println("POST /api/last_mention/ HTTP/1.0");

  client.print("Host: ");  
  client.println(host);
  
  int msgLength = 35;
  client.print("Content-length:");
  client.println(msgLength);
  client.println("");

  //トークンのみ
  client.print("_t=");
  client.println(token);

  //この待ち時間が短すぎるとclient.available()がうまく働かずレスポンスがうけとれない
  delay(500);
  
  while(client.available()){
    String line = client.readStringUntil('\r');
    Serial.println(line);
  }
  
  Serial.println();
  Serial.println("closing connection");

  return true;  
}

void loop() {
  delay(100);

  //プルアップにしたため極性を変更
  if(digitalRead(KEY_PIN) == LOW)
  {
    getLastMention();  
  }
}


2015年8月10日月曜日

ESP-WROOM-02プログラミング:ボタンを押したらツイート

以下ESP-WROOM-02 を使ったプロジェクトのメモ

プロジェクト概要:
 ボタンを押すと定型文をツイートするプロジェクト
 
 ツイートにはStewGateUのWebAPIを使用
 ネタ元はESP8266WIFI/WiFiClientサンプルコードとマイクロテクニカESP-WROOM02簡単マニュアル

動作のイメージ/Web APIを用いたtweetを行う

準備すること:
 ・ArduinoIDE上でESP8266の開発環境構築(ググろう!)
  ・3V3とGNDに十分な電流が供給可能な3.3V電源を接続
 ・GPIO5にタクトスイッチ+プルダウン抵抗を接続
  (押すとHが入力されるように3.3VとGNDに接続すること)
 ・TX/RXにはデバッグ用にシリアル―USB変換機を接続する
  (接続しなくても動作自体は行われる)
 ・ツイートに用いるTwitterアカウントの準備(新規アカウント推奨)
 ・StewGateUにてTwitterアカウント登録およびトークン取得
 ・ESP-WROOM-02を接続してもよい無線LANルータのSSIDとパスワードをメモ


作成方法:
 ・ソースをコピペして以下の情報を修正する
   ・SSID
   ・SSIDに対するパスワード
   ・StewGateUで取得したトークン
 ・ビルドしてダウンロードモードのESP-WROOM-02に書き込み
  (→過去メモ参照)


実行方法:
 ・ESP-WROOM-02をブートモードで起動
 ・デバッグ出力より無線LANルータとの接続を確認
  (『WiFi connected』の表示を確認)
 ・タクトスイッチを押すと定型文(『XXXX:HelloWorld!』)がツイートされる


うまくいかない場合は以下をチェック:
 無線LANルータの規格
   →ESP-WROOM-02が対応できるのは802.11b/g/n

 ・チップへの給電が十分か
   →通信時は80mA以上消費する

 ・ダウンロードモードからの設定変更は忘れていないか
   →GPIO0ピンをL→Hに戻し忘れることが多い
    ブートモードにしないと当然ながら起動しない

 ・メッセージ長の設定は正しいか
   →Content-length:の設定値以上の情報は無視される

 ・ツイート内容が重複していないか
   →同じ内容のツイートをした場合はTwitterにキックされる

注意点:
 ・ソースの大半はESP8266WIFI/WiFiClientサンプルコードからの流用
 ・無線LANルータの設定(ステルスID/接続先限定など)によっては動かない
 ・マイクロテクニカESP-WROOM02簡単マニュアルのパスは尼のマイクロテクニカショップでESP-WROOM-02を買ったら付いてきた
  各機能の使い方が日本語で簡潔にまとめられておりおススメ
  (ただし、デフォルトファームをATコマンドで操作する前提での解説)

 
#include <esp8266wifi.h>

#define KEY_PIN 5

//無線LANルータのIDおよびパスワード
const char* ssid     = "XXXXXXXX";
const char* password = "YYYYYYYY";

const char* host = "stewgate-u.appspot.com";
//http://stewgate-u.appspot.com/より取得したトークン
const char* token = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";

void setup() {
  Serial.begin(115200);
  delay(10);

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  
  pinMode(KEY_PIN, INPUT);
}

bool postMsg(String msg)
{
  Serial.print("connecting to ");
  Serial.println(host);
  
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return false;
  }
  
  client.println("POST /api/post/ HTTP/1.0");

  client.print("Host: ");  
  client.println(host);

  //メッセージ以外の長さ(トークン含む)にメッセージ長を加算  
  int msgLength = 40;
  msgLength += msg.length();
  client.print("Content-length:");
  client.println(msgLength);
  client.println("");

  client.print("_t=");
  client.print(token);
  client.print("&msg=");
  client.println(msg);

  delay(10);
  
  while(client.available()){
    String line = client.readStringUntil('\r');
    Serial.print(line);
  }
  
  Serial.println();
  Serial.println("closing connection");

  return true;  
}

int value = 0;
void loop() {
  delay(100);
  ++value;

  if(digitalRead(KEY_PIN) == HIGH)
  {
    String text;
    //同じ内容のツイートはキックされるためユニークな値を付加
    text += String(value, DEC);
    text += ":HelloWorld!";
    
    postMsg(text);
  }
}

2015年7月29日水曜日

ESP-WROOM-02についてのメモ(2)

以下、ESP-WROOM-02にスケッチをArduinoIDEから書き込む際のメモ

・IDEの環境構築は省略
 githubのESP8266コミュニティから環境を頂戴してくる
 IDEのBoardsManagerからインストールすればボードの選択肢にESP8266が
  
・USB-シリアル変換(3.3Vレベル)が必要
例:FTDI USBシリアル変換アダプター ・シリアル変換機のTX/RXとESP-WROOM-02のRX/TXを接続
・シリアル変換機のGNDとESP-WROOM-02のGNDを接続
・シリアル変換機をArduinoIDEと同じPCにUSB接続

・GPIO0をGND(L)に
・GPIO2を3.3V(H)に
・GPIO15をGND(L)に

・ESP-WROOM-02の3V3に3.3V電源を接続
・ESP-WROOM-02のGNDに電源のGNDを接続

・ArduinoIDE(環境構築済)を起動
・ツール→ボードからGeneric ESP8266 Moduleを選択
 (選択肢が無い場合は環境構築に失敗している)
・ツール→ポートからシリアル変換機のポートを選択
 (ポートがわからなければデバイスドライバ等からCOM番号を確認)

・適当なスケッチを選んで書き込みボタン(→)を押す
・『マイコンボードへの書き込みが完了しました。』表示がでれば成功
・自動的にリセットがかかりスケッチが動作

・GPIO0を3.3V(H)にしてリセット
・スケッチが動作することを確認

注意点:
 GPIO0をGND(L)で起動すると書き込みモードで起動するためにスケッチは動作しない
 GPIO0をGND(L)でスケッチ動作するのは書き込み直後の自動的なリセット時のみ

※※追記:
 GPIO0,2,15をHにする場合はプルアップ抵抗があった方が安全


書き込み環境/マイクロテクニカのボードを使用

2mmジャンパによる書き込み時設定

2mmジャンパによる動作時設定

スケッチ書き込み成功時

2015年7月24日金曜日

ゲームの自動操作装置(12):キー入力ウェイトの実装

連続するキー入力のタイミング調整などにスリープ(ウェイト)処理が必要になる
以下はそれの簡単な実装について

Thread.Sleep()を使うのが一番簡単なのだが、スレッド自体が止まるのでUIも固まってしまう
(逆にこれを許容するならばThread.Sleep()でよい)
UIとキー入力部分を別スレッドにすればよいのだがこれもなかなか面倒くさい

以下のコードはThread.Sleep()の代わりに用いるスリープ用処理の抜粋
Timerクラスを用いて指定された時間だけ待つ
この間Application.DoEvents()メソッドを呼び続けることでUIが固まることを防ぐ

スリープの精度はイマイチだと思うが今回の用途には問題ないものとした

        static System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
        static bool timerEndFlag = false;

        //フォームのロード時にイベントハンドラを仕込んでおく
        private void FormLoad(object sender, EventArgs e)
        {
            //…
            myTimer.Tick += new EventHandler(TimerEventProcessor);
            //…
        }
        private static void TimerEventProcessor(Object myObject,
                                                EventArgs myEventArgs)
        {
            myTimer.Stop();
            timerEndFlag = true;
        }

        //Thread.Sleep()の代わりに使用
        private void sleep(int msec)
        {
            if (msec <= 0)
                msec = 1;
            myTimer.Interval = msec;
            timerEndFlag = false;
            myTimer.Start();

            // Runs the timer, and raises the event.
            while (timerEndFlag == false)
            {
                Application.DoEvents();
            }
        }

2015年7月23日木曜日

ゲームの自動操作装置(7):画像解析部のコード

ゲームの自動操作装置(6):画像解析部 からの続き


以下は画像解析部のコードの抜粋
いずれも簡単な方法で実装しており動作は低速である

・PC画面のスクリーンキャプチャ処理部分

 キャプチャの取得はSystem.Drawing.Graphicsクラスを使用している
 画面の左上にゲーム画面キャプチャソフトのウィンドウ(720x480ドット)がある前提
 サブディスプレイで動かすためにプライマリ/セカンダリスクリーンの切り替えが仕込んである
 PC画面の左上(0,0)から720x480ドット分のビットマップ(=ゲーム画面)を切り出す

  
        public static Bitmap GetDesktop()
        {
            Rectangle rect;
            if (outputDisplayNum == 0)
            {
                rect = Screen.PrimaryScreen.Bounds;
            }
            else
            {
                rect = Screen.AllScreens[1].Bounds;
            }
            //※※高速化のために全画面取得は廃止
            rect.Width = 720;
            rect.Height = 480;

            Bitmap bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);

            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.CopyFromScreen(rect.X, rect.Y, 0, 0, rect.Size, CopyPixelOperation.SourceCopy);
            }
            return bmp;
        }

・画像の解析処理部分

 ゲーム画面の任意座標にオブジェクトがあるかどうかの判定を行う
 オブジェクトチェック用にキャプチャ画面から矩形に画像を切り出しておく

 前述のゲーム画面とオブジェクトチェック用の画像のマッチングを行う
 マッチングの指標は各ピクセルの輝度の差の二乗和をピクセル数で割ったもの
  (3プレーンともチェックする場合は、更にプレーン分を足した後3で割る)

  実行時間はチェック用画像のサイズに依存する
  highSpeedフラグがある場合はRプレーン以外は無視する
 (処理は速くなるが当然精度は相応に落ちる)


ゲームの自動操作装置(8):メインプログラム へ続く

        private Bitmap gameBitmap;
        private int imgMuchValue;

        public bool checkMenuBase(Bitmap checkBitmap, int x, int y, bool highSpeed)
        {
            bool ret = false;
            int testVal;

            gameBitmap = Win32APICall.GetDesktop();

            testVal = cmpBitmapSSD(gameBitmap, checkBitmap, x, y, highSpeed);
            if (testVal < imgMuchValue)
            {
                ret = true;
            }
            else
            {
                ret = false;
            }
            return ret;
        }

        private int cmpBitmapSSD(Bitmap lb1, Bitmap lb2, int x, int y, bool highSpeed)
        {
            int xBuf, yBuf;
            int muchCountR = 0;
            int muchCountG = 0;
            int muchCountB = 0;

            int buf;
            int retbuf;

            if (highSpeed)
            {
                for (yBuf = y; yBuf < (y + lb2.Height); yBuf++)
                {
                    for (xBuf = x; xBuf < (x + lb2.Width); xBuf++)
                    {
                        buf = lb1.GetPixel(xBuf, yBuf).R - lb2.GetPixel(xBuf - x, yBuf - y).R;
                        muchCountR += buf * buf;
                    }
                }
                retbuf = muchCountR / lb2.Height / lb2.Width;
            }
            else
            {
                for (yBuf = y; yBuf < (y + lb2.Height); yBuf++)
                {
                    for (xBuf = x; xBuf < (x + lb2.Width); xBuf++)
                    {
                        buf = lb1.GetPixel(xBuf, yBuf).R - lb2.GetPixel(xBuf - x, yBuf - y).R;
                        muchCountR += buf * buf;

                        buf = lb1.GetPixel(xBuf, yBuf).G - lb2.GetPixel(xBuf - x, yBuf - y).G;
                        muchCountG += buf * buf;

                        buf = lb1.GetPixel(xBuf, yBuf).B - lb2.GetPixel(xBuf - x, yBuf - y).B;
                        muchCountB += buf * buf;
                    }
                }
                muchCountR = muchCountR / lb2.Height / lb2.Width;
                muchCountG = muchCountG / lb2.Height / lb2.Width;
                muchCountB = muchCountB / lb2.Height / lb2.Width;

                retbuf = (muchCountR + muchCountG + muchCountB) / 3;
            }
            lastCheckResult = retbuf;
            return retbuf;
        }

2015年7月21日火曜日

ゲームの自動操作装置(6):画像解析部

ゲームの自動操作装置(5):キャプチャ環境 からの続き


画像の解析処理は以下のような流れになる

・スクリーンキャプチャ
 Windowsの標準APIを用いてPC上の画面をキャプチャする
 これで画面のビットマップイメージが取得できる
 キャプチャソフトの配置座標、ウィンドウサイズをあらかじめ決めておけば
 このビットマップイメージを切り取ることでゲーム画面の情報を取得できる


・画像の解析/状態の取得
 取得したゲーム画面を解析して現在の状態の取得を行う
 各状態(画面)特有のオブジェクトに対してその有無をチェックする方法をとった
 
 例:
  ・フィールド移動中にミニマップが表示される
  ・戦闘中には必ずHP表示ウィンドウが出る
  ・カジノで定型メッセージによって次の操作が促される
   Etc

 チェックしたいオブジェクトをあらかじめ準備しておく
 ゲーム画面のプリントスクリーンから矩形に切り取ってビットマップファイル化しておけばOK
 このとき、切り取ったオブジェクトの座標、矩形の縦横ドット数をメモしておく
 
 キャプチャ画面のビットマップに対して、上記オブジェクトのビットマップとの比較を行う
 比較の方法は何でもよい
 一番簡単なのは各ピクセルのRGB成分の値を取得し、その差の二乗の平均値をとる方法である
 まったく同じ画像ならばこの値は0となり、画像の差が大きいほど値は大きくなる
 マッチングのしきい値未満ならばオブジェクトを検知とする


※注意点
 ・キャプチャソフトの設定について必ずメモしておくこと
 ・できればバッチファイル等で設定/特定の座標へのウィンドウ表示を再現可能にしておくこと
  アスペクト比や明るさ・コントラストの設定が変わってしまうと機能しなくなる

 ・マッチングのしきい値を適正な値にすること
   高くしすぎれば誤検知が増える
   低くしすぎれば未検知が増える


ゲームの自動操作装置(7):画像解析部のコード へ続く

2015年7月18日土曜日

ゲームの自動操作装置(4):アプリ/シリアル通信部分

ゲームの自動操作装置(3):USBキーボードスケッチ からの続き


以下は自動操作アプリケーションのシリアル通信部の抜粋
開発言語はVC#

.NET FrameworkではSerialPortクラスが準備されており、簡単にシリアル通信が実現できる
今回のプロジェクトでは送信側しか用いないので特に簡単になる

ミニマムな構成は以下になる

・接続部
 任意パラメータでシリアルポート接続を行う

・通信部(送信部)
 シリアル通信で任意データを送信する

・切断部
 接続済のシリアルポート接続を切断する


ここではすべてにボタンが割り振られており、以下はそれぞれをクリックした際のハンドラである
送信するコマンド入力用にnumericUpDownを設けている
抜粋なので定義していない変数も使っているがそこはがんばって類推すること


ゲームの自動操作装置(5):キャプチャ環境 に続く


  
        private void buttonConnect_Click(object sender, EventArgs e)
        {
            try
            {
                if (serialPort1.IsOpen)
                {
                    MessageBox.Show(this.textBoxSerialPort + " はすでに開かれています。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else
                {
                    // ポート番号文字列=this.textBox1.Text, 115200bps/N81/ハードウェアフロー/UTF8/改行=CR
                    this.serialPort1.PortName = this.textBoxSerialPort.Text;
                    this.serialPort1.BaudRate = 9600;
                    this.serialPort1.Parity = System.IO.Ports.Parity.None;
                    this.serialPort1.DataBits = 8;
                    this.serialPort1.StopBits = System.IO.Ports.StopBits.One;
                    this.serialPort1.Handshake = System.IO.Ports.Handshake.RequestToSend;
                    this.serialPort1.Encoding = Encoding.UTF8;
                    this.serialPort1.NewLine = "\r";
                    serialPort1.Open();

                    connectFlag = true;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        private void buttonSend_Click(object sender, EventArgs e)
        {
            try
            {
                sendBuffer[0] = (byte)this.numericUpDownCommand.Value;
                this.serialPort1.Write(sendBuffer, 0, 1);

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

        }
        private void buttonDisConnect_Click(object sender, EventArgs e)
        {
            if (serialPort1.IsOpen)
            {
                serialPort1.Close();
                connectFlag = false;
            }
        }

ゲームの自動操作装置(3):USBキーボードスケッチ

ゲームの自動操作装置(2):USBキーボードシールド からの続き

以下は前述のUSBキーボードシールドのためのスケッチ

シリアルポートを監視してPCから打鍵情報(1byte)が来るのを待つ
打鍵情報が来たら対応するGPIOへの出力を行う

ピンの最大数は17、打鍵は3種類(ON/OFF/HIT(一瞬ONしたのちOFF))なので1byteで足りる
(スケッチ内では簡単化のために17ではなくキリのよい20で計算している)

また、暴走対策のためにすべてのキーをOFFにするALL_OFF命令を設けている
どのピンに何のキーが対応しているかはこのスケッチでは扱わない

シリアル出力へデバッグ出力を行っているがアプリケーション側はこれを無視してもかまわない


ゲームの自動操作装置(4):アプリ/シリアル通信部分 へ続く

  
#define  PIN_MAX  17
#define  KEY_BASE  20

#define  KEYTYPE_HIT  0
#define  KEYTYPE_ON  1
#define  KEYTYPE_OFF  2
#define  KEYTYPE_MAX  2

#define  ALL_OFF  0
#define  KEY_DELAY  100

char command = 0;
int pin;
int commandType;
int commandNum;

void setup()
{
  Serial.begin(9600);
  for(pin = 2; pin <= 17; pin++)
  {
    pinMode(pin, OUTPUT);
    digitalWrite(pin, HIGH);
  }
}

void loop()
{
  if (Serial.available() > 0) 
  {
    command = Serial.read();
    if(command == ALL_OFF)
    {
      Serial.print("command:");
      Serial.print(command, DEC);

      Serial.print("ALL OFF");
      for(pin = 2; pin <= 17; pin++)
      {
        digitalWrite(pin, HIGH);  
      }      
      delay(KEY_DELAY);
    }
    else
    {
      commandType = (int)(command / KEY_BASE);
      if(commandType <= KEYTYPE_MAX)
      {
        pin = (int)(command - (KEY_BASE * commandType));
        if(pin <= PIN_MAX)
        {
          Serial.print("command:");
          Serial.print(command, DEC);
          Serial.print(" commandType:");
          Serial.print(commandType);
          Serial.print(" pin:");
          Serial.println(pin);
           
          switch(commandType)
          {
            case KEYTYPE_HIT:
              Serial.print("HitKey");          
              digitalWrite(pin, LOW);
              delay(KEY_DELAY);
              digitalWrite(pin, HIGH);
              break;
            case KEYTYPE_ON:
              Serial.print("OnKey");          
              digitalWrite(pin, LOW);
              delay(KEY_DELAY);            
              break;
            case KEYTYPE_OFF:
              Serial.print("OffKey");          
              digitalWrite(pin, HIGH);
              delay(KEY_DELAY);
              break;
            default:
              Serial.println("UnknownType");
              break;         
          }
        } 
      }
    }
  }
}

ゲームの自動操作装置(1):全体の構成

過去にコンシューマTVゲームにのめり込んでいた時期があり
「他の作業中でもレベルアップできないか?」
とArduinoとWindowsアプリケーションで自動操作装置を作った
(止めた今となってはなぜそこまで傾倒していたのかがわからない・・・)

以下はその記録
なお、ネットワークゲームサービスなどで自動操作を用いると確実に処分対象になるため
使用はおススメしない


・全体構成
  
  ゲーム機からの画像情報をUSBキャプチャ/キャプチャソフトを用いてPCに表示させる
  これらはすべて市販のものでかまわない

  PC上の自動操作アプリケーションはPCに表示される画像を認識できる
  この情報を用いて状態を判断し、次にすべき操作をArduinoに出力する
  アプリケーションは自作

  Arduinoを使ったUSBキーボードプロジェクトをゲーム機にUSB接続する
  これはゲーム機側からはUSBキーボードとして認識される
  このプロジェクトも自作

  ArduinoはシリアルポートでPCとも接続する
  アプリから送られる操作情報をUSBキーボードの打鍵としてゲーム機に伝達する

  
  このプロジェクトではゲーム機への入力にUSBキーボード入力を用いたが
  ゲーム機のコントローラーとフォトリレー等を組み合わせればコントローラー入力も可能

 
システムの構成

2015年5月22日金曜日

単純シリアル出力装置のコード

以下コード表示テスト
以前に書いた単純シリアル出力装置プロジェクトのサンプルコード
EEPROMへのコマンド書き込みが可能

プロジェクトの作例


短いコードなので説明は割愛
どこかからのコピペが混じってると思う

  
#include <EEPROM.h>

const int buttonPin = A0;
const int ledPin1 = 13;

int eepWriteFlag = 0;
int eepAddr = 0;

int buttonState = 0;
int setupFlag = 0;

char str[64]; // 数字(文字列)の受信用配列  
char str2[64];

void setup(){
  int i;
  
  pinMode(ledPin1, OUTPUT);      
  pinMode(buttonPin, INPUT_PULLUP);    
  
  delay(100);
  
  //ボタン押しっぱなし起動→セットアップモード
  if(digitalRead(buttonPin) == LOW)
  {
    //セットアップモード:9600pps固定
    setupFlag = 1;
    Serial.begin(9600);
    Serial.println("Setup Mode");
  }
  else
  {
    //非セットアップモード
    //先頭に記述されている#pps XXXX よりシリアル接続
    eepAddr = 0;

    for(i = 0; i < 64; i++)
    {
      str[i] = (char)EEPROM.read(eepAddr);
      eepAddr++;
  
      if(str[i] == '\r')
        break;
    }
    if(strEq(str, "#pps"))
    {
      long ppsBuf;
      for(i = 0; i < 64; i++)
      {
        str2[i] = 0;
      }
      for(i = 0; i < 64; i++)
      {
        if(str[i+5] == '\r')
          break;

        str2[i] = str[i+5];      
      }
      ppsBuf = atol(str2);
      Serial.begin(ppsBuf);

      Serial.print("pps:");
      Serial.print(ppsBuf);
      Serial.println();        
    }
    else
    {
      Serial.begin(9600);
      Serial.println("pps readError");
    }
  }
}

void loop(){
  int i, j;
  if(setupFlag == 0)
  {
    buttonState = digitalRead(buttonPin);

    if (buttonState == LOW) {     
    // turn LED on:    
    digitalWrite(ledPin1, HIGH);
    
    serialOut(0);
    
    digitalWrite(ledPin1, LOW);
    }
  } 
  else
  {
    //ミソはバッファクリア―!
    for(i = 0; i < 64; i++)
     str[i] = 0;
     
    recvStr(str);  
    Serial.println(str);
    
    if(strEq(str, "#start"))
    {
      Serial.println("detect #start");
      eepWriteFlag = 1;
    }
    else if(strEq(str, "#end"))
    {
      Serial.println("detect #end");
      eepWriteFlag = 0;
  
      EEPROM.write(eepAddr, (byte)'\n');
      eepAddr = 0;
    }
    else if(strEq(str, "#read"))
    {
      Serial.println("detect #read");
      eepAddr = 0;
  
      for(j = 0; j < 512; j++)
      {
        for(i = 0; i < 64; i++)
        {
          str[i] = 0;
        }
        for(i = 0; i < 64; i++)
        {
          str[i] = (char)EEPROM.read(eepAddr);
          eepAddr++;
      
          if(str[i] == '\r')
            break;
        }
        Serial.println(str);
        if(str[0] == '\n')
           break;
      }
    }
    else if(strEq(str, "#clear"))
    {
      Serial.println("detect #clear");
      for(i = 0; i < 1024; i++)
      {
        eepWriteFlag = 0;
        eepAddr = 0;
    
        EEPROM.write(i, 0);
      }
    }
    else if(strEq(str, "#go"))
    {
      //#readとの違いはディレイやppsなど一部パースすること
      Serial.println("detect #go");
      eepAddr = 0;
      
      serialOut(1);
    }  
    else if(eepWriteFlag)
    {
      for(i = 0; i < 64; i++)
      {
        EEPROM.write(eepAddr, (byte)str[i]);
        eepAddr++;
        if(str[i] == '\r')
          break;
      }
    }
  }
}

//※文字列*baseStrの冒頭が*headStrと等しいかどうかをチェックする
//等しければ1、そうでなければ0
int strEq(char *checkStr, char *headStr)
{
  int size = sizeof(headStr) / sizeof(headStr[0]);
  int i;
  int ret = 1;
  
  for(i = 0; i < size; i++)
  {
    if(checkStr[i] != headStr[i])
    {
      ret = 0;
      break;
    }
  }
  
  return ret;
}

void recvStr(char *buf)
{
  int i = 0;
  char c;
  while (1) {
    if (Serial.available()) {
      c = Serial.read();
      buf[i] = c;
      if (c == '\r') break;
      i++;
    }
  }
  buf[i] = '\r';
} 

void serialOut(int debugFlag)
{
  int i,j;
  eepAddr = 0;

  for(j = 0; j < 16; j++)
  {
    for(i = 0; i < 64; i++)
    {
       str[i] = 0;
    }

    for(i = 0; i < 64; i++)
    {
      str[i] = (char)EEPROM.read(eepAddr);
      eepAddr++;
  
      if(str[i] == '\r')
      {
        break;
      }
    }
    
    if(strEq(str, "#delay"))
    {
      int delayBuf;
      for(i = 0; i < 64; i++)
      {
        str2[i] = 0;
      }
      for(i = 0; i < 64; i++)
      {
        if(str[i+7] == '\r')
          break;

        str2[i] = str[i+7];      
      }
      delayBuf = atoi(str2);
      
      if(debugFlag)
      {
        Serial.print("delay:");
        Serial.print(delayBuf);
        Serial.println();
      }
      else
      {
        delay(delayBuf);
      }
    }
    else if(strEq(str, "#pps"))
    {
      if(debugFlag)
      {
        Serial.println("pps Skip");        
      }
    }
    else
    {
      Serial.println(str);
    }
    if(str[0] == '\n')
       break;
  }
}



2015年5月20日水曜日

//**********↓このコメントを消すと何故か動かなくなる!絶対消すな!↓*********

以下は実際に遭遇したトラブル案件

症状:
 コメント行を消すとプログラムが正常に動作しなくなる

環境:
 言語はC
 IDEは存在せずエディタを使用
 組み込み用マイコンのコンパイラを用いてコマンドラインからビル

原因:
 コメントに2バイト文字(日本語)を使用したため、コンパイラのバグでコメント行の改行記号が無視された

 ・エディタ上はこういう記述
   //**********↓このコメントを消すと何故か動かなくなる!絶対消すな!↓*********
        //※※ほげほげ関数※※
        ret = hogefunc(hoge1, hoge2);

 ・コンパイラの理解
   //**********↓このコメントを消すと何故か動かなくなる!絶対消すな!↓*********
        //※※ほげほげ関数※ 蝦ret = hogefunc(hoge1, hoge2);
 (※も蝦も実際には別の文字だが失念)

 蝦は※の2バイト目と改行記号\nが合体してしまった無意味な2バイト文字
  ・※の1バイト目が単体で有意な1バイト文字として解釈された
  ・※の2バイト目+改行文字(\n)が有意な2バイト文字(蝦)として解釈された

 コメント行の次の行で呼んでいるhogefunc()にバグがあり、この関数が呼ばれると正常動作しなかった
 コメント行に巻き込まれたことによってhogefunc()が呼ばれなくなり、表面上正常動作していた
 (実際は設計上の動作ではない/たまたまhogefunc()が重要度の低い処理であったので表面上わからなかっただけ)


備考:
 コンパイラのバグと設計のバグが重なって原因をわかりにくくした
 デバッガが使えない環境だったので泥沼になった