SyntaxHighlighter

2015年12月27日日曜日

FlashAirでLuaスクリプトを組む時のコツ

※本情報は、参考のために残しており、古い情報を含みます。
 現在主に更新しているのはwikiですので、最新の情報はwikiを参照してください。

---
FlashAirでLuaスクリプトを組む時のコツ

※FlashAir本体をアップデートすること!
 W3.00.01にしないと、さまざまなバグに悩まされます。

・メモリは少ない。
 スクリプト含めて16KBくらいと思っておくこと。
 なので、配列確保できるのは大抵10KBくらい。
 大きなデータを処理するのなら、マイコンのようにちまちま小分けで処理しないといけない。

・メモリのお掃除は頻繁に。
 Luaは本来メモリが潤沢にある環境で使うものだと思うのだが、
 そうでないので仕方がない。
 メモリ不足になると、メモリ確保に時間を食うので、処理速度が落ちる。
 また、メモリ不足のメッセージが出力に混ざりこむ。
 これを避けるには、collectgarbage()をプログラムの随所に入れておくこと。
 そうすると速度が落ちない。
 ただ、collectgarbage()自体が時間を食うので、頻繁に呼びすぎてもいけない。

・ライブラリは小さく。
 ライブラリのコードサイズがおもいっきりメモリを食い潰す。
 ライブラリ読み込み時にcollectgarbage()する必要が出ることもある。

・コメント少なく。
 コメントもコードの一部。コメントが大きすぎるとそれだけでメモリエラーになる。
 リリース時にはコメントを全削除するなり、別ファイルにするなりする方がいい。

・変数名・関数名は短く。別名を使うのが良い。
 変数名も多分食ってる。関数名も。
 なので、組み込み関数には別名を割り振って使うと多分軽くなる。
 ただ、これは自分がそう思っているだけで、実際には違うかもしれない。

・argの制限
 arg[0]で自身のパスが分かる。
 arg[1]で引数がわかる。
 引数はrun.lua?Helloのように与えることができる。
 file=A&name=Bのように与えても、分解されることはなくそのままでてくる。
 引数がなければnil。
 URLエンコードしたものは解かれる。バイナリを渡すとバグる。
 URLは(たしか全長含めて)128文字しか受け付けない。
 超えると414が帰る(3.00.01)。古いバージョン(3.00.00)だとバグる。
 末端にはLFがくっついているので注意。

・mathが無いので
 数学関数mathがない。ので、浮動小数点を切り捨てることすら出来ない。
 ビット演算は適当に丸めやがるので、切り捨てたい時には使えない。
 解決方法: block = tonumber(string.format ("%d",num));
 アホみたいだが、こういうやり方になる。

・RTCあるって知ってた?
 地味にあるんです。


・ファイルの書き込みという悪魔の所業
 ファイルの書き込みは色々と問題があるので、正直やりたいものではない。
 ホスト機器に差し込まれているときは尚更だ。
 外部からuploadやPUTをした時は、FATにロックが掛かることを忘れるな。
 Luaスクリプトから書き換えるときは、FATにロックがかからないことを忘れるな。
 FATにロックを掛けたければ
 fa.request("http://127.0.0.1/upload.cgi?WRITEPROTECT=ON")
 逆に、ファイルシステム破壊を恐れず解除したければ(再マウントすれば問題ない)
 fa.request("http://127.0.0.1/upload.cgi?WRITEPROTECT=OFF")


・共有メモリというスクリプト間共通変数
 一旦終了したスクリプトから、次に起動するスクリプトに情報を渡すには
 ファイルに書き込む方法を思いつくだろうが、それは上記の理由でおすすめしない。
 W3.00.01から、Luaで共有メモリにアクセスできるようになったので、
 こちらを使うことをおすすめする。512バイトまで記録できる。
 ブラウザからアクセスすることもできる。


・fa.requestの罠
 まず、3KBまでしか受信できない。
 タイムアウトがない。
 エラーが起きると最悪応答がなくなる。
 ポート番号も指定できない。
 ただ、TLS(https)はできる。
 せめてタイムアウト指定とか、なければタイマー割り込みとかほしい...


・隠し関数は積極的に
 ファイルの削除とか、ファイル名変更とか、ping飛ばしたりとか、できるんですよ実は。
 将来的に削除される可能性もありますが。 

・処理速度は20kHz?
 fa.pioで色々測ってみた限り、1処理に500usくらい食ってる感がある。
 また、処理中にWi-Fi関係とかの割り込みが掛かっている感がある。
 

・Wi-Fi起動電流に注意
 かなり食います。電流測定用にテスターかませるだけでダメになることも。
 起動時に200mA、定常120mA程度食うようです。
 Wi-fiをオフにすると数mA~数十mAで収まります。
 参考。

・pioとホスト
 fa.pioはホストとの通信を妨害します。
 ホストが初期化する前にpioを呼ぼうものなら、信号が衝突しますし、
 初期化後に拒否られた状態でpio呼んでも不安定になるようです。

 対策:スクリプトの冒頭に下のような行を入れる。
 sleep(15000);
 if(fa.pio(0,0) == 0)then return; end;

・結果を手早く見れるのはLEDと液晶
 ブラウザからLuaスクリプトを起動すると、CGIなので
 スクリプトが終了するまで結果が出てきません。
 なので、途中状態を知りたいのなら、LEDなり液晶なりを繋ぐか、
 fa.requestを受け取るサーバーでも用意するか、な必要があります。


・デバッグ面倒くさいならFTLEがおすすめ(ステマ)
 ブラウザからLuaスクリプトの書き換え・デバッグができるエディターあります。
 デバッグが楽になること間違いなし。


・IOポートの動作確認から始めるといいよ(ステマ)
 IOポートでなにかするのなら、まずはIOポートの出力が対象に伝わっているか
 からチェックするのがいいと思います。便利なツールが有ります。

・ライブラリを使いましょう
 悩む時間のほうがもったいないので。
 Airioを使うならこれ。
 AirioRPをつかうならこれ
 秋月FlashAir DIP IOボードを使うならこれ。

・ブリッジモードオススメ
 APモードでデバッグというのはやりづらいです。(ネットから切断されるので)
 なので、ネットが使えるSTAモードやブリッジモードを使うことをおすすめします。

 STAモードだとIPアドレス変わるからデバッグしにくいじゃないか、という方は
 こちらのIPアドレス固定方法を御覧ください。
 APモードやブリッジモードのFlashAir側のIPアドレスも同様に固定できます。

・古いファームウェアでスクリプトが動かない問題は、以下の様なコードでバージョンチェック
 してしまうことをおすすめします。
 特に新しい関数を使っている場合は読み込み時に落ちるので、
 その場合はブートストラップと本体にスクリプトを分けましょう。

if(fa.md5 ~= nil) then
print("FlashAir firmware version is too old!");
error("error() has been called.");
end;

FlashAirにA/Dコンバータ(MCP3008)をつなぐ

FlashAirにA/Dコンバータをつなぐのは非常に簡単。
MCP3008を用意し、以下のようにつなぐ。
(可変抵抗は観察用で、実際には必要な電圧入力を設定する)



次のようなスクリプトを書く。

fa.spi("init",1000);
fa.spi("cs",0);

fa.spi("write",0x01);
a = fa.spi("write",0x80);
b = fa.spi("write",0xFF);

fa.spi("cs",1);
fa.spi("read");

s = bit32.bor(bit32.lshift(bit32.band(a,0x03),8),b)
v = 3.3*s/1023;

print("CH0:"..v.."[V]");

一応解説を入れておくと、
0x01はスタートビット
0x80は測定指示(シングルエンド・CH0・ダミー)
0xFFはダミーである。

a,bに合計10bitのデータが入るので、sで結合し、vで電圧にしている。
MCP3008は厳密にはSPIではない(8bit刻みではない)のだが、
SPIでも互換で使えるように設計されており、
データシートにあるSPI機能を用いた使い方そのままである。

cs=1の後、空読みしているのは、前回の記事に載せた仕様(orバグ)ためである。

センサー情報をWi-Fiで飛ばしたい、あるいはスマホから読み取りたい時には便利なので
試してみてほしい。

より多くのセンサーを繋ぎたいのであれば、I2C変換機能を持ったボード
(秋月FlashAir DIP IOボードもしくはAirioRP)を使い、
I2Cで繋がるA/DコンバータICを使うことをおすすめする。

(細工をすれば、あまった1ピンで2つのSPIスレーブを制御することもできるが)



2015年12月24日木曜日

fa.spiの挙動(秋月 FlashAir DIP IOボードキットでfa.spiが動かない問題)

※FlashAir本体をアップデートすること!
 W3.00.01にしないと、fa.spi関数は正常に動作しません!
 (W3.00.00にも関数が存在はしていますが、動作不良のようです)

秋月DIPボードの挙動が、fa.spiを使った時だけ妙 + mode切り替えをすると動くことがある
という話を聞いたので、mode切り替えだけで出力が切り替わるのかを調査。


初期状態
SCK  = HIGH(Hi-Z)
MOSI = HIGH(Hi-Z)
INT  = HIGH(Hi-Z)
SS   = HIGH(Hi-Z)
MISO = HIGH(Hi-Z)

fa.spi("init")
SCK  = LOW
MOSI = LOW
INT  = HIGH(Hi-Z)
SS   = HIGH
MISO = HIGH(Hi-Z)

→defaultではモード3とリファレンスに書いてあるが、どうみてもmode3ではない。
 ただ、1度空readをするとmode3らしき挙動になる。
 また、モードを切り替えてもinitするとまた戻される。

fa.spi("mode",0)
SCK  = LOW
MOSI = LOW
INT  = HIGH(Hi-Z)
SS   = HIGH
MISO = HIGH(Hi-Z)

fa.spi("mode",1)
SCK  = LOW
MOSI = LOW
INT  = HIGH(Hi-Z)
SS   = HIGH
MISO = HIGH(Hi-Z)

fa.spi("mode",2)
SCK  = HIGH (時折LOWのまま)
MOSI = LOW
INT  = HIGH(Hi-Z)
SS   = HIGH
MISO = HIGH(Hi-Z)

fa.spi("mode",3)
SCK  = HIGH (時折LOWのまま)
MOSI = LOW
INT  = HIGH(Hi-Z)
SS   = HIGH
MISO = HIGH(Hi-Z)

・結論
切り替わった。

ただし、時折切り替わり不良を起こしているらしき状態がある。
(mode3なのにSCKがLなど。cs切り替えでも発生する)

その場合、いくら切り替えても治らない。(極稀に治る。)
ただ、fa.spi("read")とでもして送信処理をさせれば、必ず切り替わる。


ちなみに、このページの情報を元にどんなに頑張っても
AE-FAIO(秋月 FlashAir DIP IOボードキット)は思い通りに動かないと思います。
サンプル通りソフトSPIで使うことを強くおすすめします。

※動きました。
CSの前、もしくは後に空読みを入れると、不安定さが解消するようです。
CSが反映されないことがあるバグ(or仕様)が影響し、
連続したパケットとして認識されていた可能性が高いです。
ソフトSPIで動いていたのは、このバグ(or仕様)がないためだと考えられます

function write_reg(addr,data)
--fa.spi("read"); --こちらでも良い。
fa.spi("cs",0)
fa.spi("write",0x20)
fa.spi("write",addr)
fa.spi("write",data)
fa.spi("cs",1)
fa.spi("read");
end

if(fa.md5 ~= nil) then
print("FlashAirのファームウェアが古すぎます");
error("error() has been called.");
end;

fa.spi("init",300000);
fa.spi("mode",3);
write_reg(0x00,0xaa) -- IOCONFIG

write_reg(0x01,0x00) -- IOSTATE
sleep(100)
write_reg(0x01,0x0F)
sleep(100)
write_reg(0x01,0x00) -- IOSTATE
sleep(100)


この情報を元に、秋月FlashAir DIP IOボード用のライブラリを作りました。

2015年12月16日水曜日

FlashAirのLuaスクリプトでtarアーカイブを展開する

--展開にはめっちゃ時間かかります(1ファイル最低5秒以上)
--Tiny Tar Extracter.lua
--超簡易版です。ファイル名と、ファイル種別(フォルダorファイル)、サイズ、チェックサムしか見てません。
--(しかもチェックサムおかしくても展開続けます)
--フォルダ作成処理のところを入れ替えればWindows上でも動きます。

MastarPath="/";

f = io.open ("/FlashAir_Tiny_Lua_Editer.tar","rb");
if(f==nil)then
print "Err";
return;
end
print "OPEN";
next_file_head = 0;

while true do
collectgarbage();
now_file_head = next_file_head;
--FileName
f:seek("set",now_file_head);
fname = f:read(100);
--tar末端検出
fname = string.match (fname,"[^\0]+");--null文字削除
if(fname == nil)then break; end;
print("name:"..fname);

--FileSize
f:seek("set",now_file_head+0x7C);
size = f:read(12);--Tar形式は文字列
size = string.match (size,"[^\0]+");--null文字削除
size = tonumber(size,8);--(ただし8進数!)
print("size:"..size);
block = tonumber(string.format ("%d",((size+511)/512))); --ビット演算ダメ
print("blocks:"..block);
next_file_head = now_file_head + ((1+block)*512); -- HEAD+BODY
print("next_file_head:"..next_file_head);

--chksum
f:seek("set",now_file_head+0x94);
chksum = f:read(8);
chksum = string.match (chksum,"[^\0]+");--null文字削除
chksum = tonumber(chksum,8); --これも8進数
print("chksum:"..chksum);

sum=0;
for pos=0,511 do
f:seek("set",now_file_head+pos);
if((pos >= 0x94) and (pos < (0x94+8)))then
sum = sum+0x20;
else
sum = sum+string.byte(f:read(1));
end
end
print("sum:"..sum);

if(sum == chksum)then
print("chksum OK");
else
print("chksum NG");
end

--typeflag
f:seek("set",now_file_head+0x9C);
typeflag = f:read(1);
if(typeflag == "0")then
type = "File";
elseif(typeflag == "5")then
type = "Directory";
else
type = "Unsupported";
end

print("typeflag:"..typeflag.."/"..type);

--出力
if(type == "Directory")then
--windows用
--fname = string.gsub (fname, "/","\\")
--os.execute("mkdir "..fname)
print(string.sub("mkdir:"..MastarPath..fname,1,-2));
    if(lfs.mkdir(string.sub(MastarPath..fname,1,-2)) == true)then --末端/は削る
      print("mkdir OK");
      else
      print("mkdir fail");
      end
end
if(type == "File")then
fo = io.open (MastarPath..fname,"wb");
if(fo==nil)then
print "Output Err";
return;
end

collectgarbage();--メモリのお掃除


    --debug限界:8192+4096+1024+384 = 13696
    --release限界:8192+4096+1024+540 = 13852
Trans_Block =8192; --転送ブロックサイズ。メモリの余裕によって変更。
    pos_save=0;
    if((size-1) > Trans_Block)then --サイズが小さいならループしない
for pos=0,(size-1-Trans_Block),Trans_Block do
collectgarbage();--メモリのお掃除

      --メモリの関係もあるので64バイトづつ書き込む
f:seek("set",now_file_head+512+pos);
fo:write(f:read(Trans_Block));

--if((pos % 10240) == 0)then
io.write("\rputting..."..pos.."/"..size.."    ");
--end
pos_save=pos;
        end
pos_save=pos_save+Trans_Block;--最後の転送分を加算
end
print(size-pos_save)
f:seek("set",now_file_head+512+pos_save);
fo:write(f:read(size-pos_save));

fo:close();
end

print("\n---");
end


f:close()
print "CLOSED";

Copyright (c) 2015, GPS_NMEAAll rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

2015年12月14日月曜日

FlashAirの設定ファイルを自動で書き換えたい人向けLuaスクリプト

FlashAirの設定ファイルを自動で書き換えたい人向けLuaスクリプト。
今までの手抜き置換実装ではなく、本格的にCONFIG丸ごと書き換えられるように作ってみた。
配布Luaスクリプトで設定を自動で書き換えたい場合なんかにどうぞ。
動作確認はしていますが、必ず動作する・設定ファイルを破壊しない保証はありません。

三条項BSDライセンス。

--設定データテーブル。無くてもいいっちゃいい。
ini={
    APPAUTOTIME="",
    APPINFO="",
    APPNAME="",
    APPNETWORKKEY="",
    APPSSID="",
    BRGNETWORKKEY="",
    BRGSSID="",
    CID="",
    CIPATH="",
    DELCGI="",
    LUA_RUN_SCRIPT="",
    LUA_SD_EVENT="",
    MASTERCODE="",
    PRODUCT="",
    STA_RETRY_CT="",
    TIMEZONE="",
    UPDIR="",
    VENDOR="",
    VERSION="",
    IP_Address="",
    Subnet_Mask="",
    Default_Gateway="",
    DNS_Server1="",
    DNS_Server2="",
    Proxy_Name="",
    Port_Number="",
    APPMODE="",
    DNSMODE="",
    IFMODE="",
    LOCK="",
    NOISE_CANCEL="",
    UPLOAD="",
    WEBDAV="",
    DHCP_Enabled="",
    Proxy_Enabled="",
 
--[[
      APPCHANNEL="",
    HTTPDMODE="",
    HTTPDUSER="",
    HTTPDPASS="",
    APPMINTIME="",
    APPMAXTIME="",
    REDIRECT="",
    APPEXT="",
    APPTYPE="",
    AGINGTIME="",
    WLANSTAMODE="",
    DOMAINNAME="",
    COMMAND="",
    APMODE="",
    NOGATEWAYMODE="",
    SCRIPT="",
]]--
}

-- CONFIG READER ---
print "HTTP/1.1 200 OK\nPragma: no-cache\nCache-Control: no-cache\n";

--READ START
f = io.open("/SD_WLAN/CONFIG","r");
if(f==nil) then print ("Not Found.");return; end;

--ANALYZE
for l in f:lines() do
    s = string.gsub(l," ","");
    h = string.sub(s,1,1);
    print(s);
    if (h ~= "[") then
        for key, value in string.gmatch(s, "([^=]+)=([^=]+)") do
            ini[key] = string.gsub(value,"\r","");
        end
    end
end

f:close();

--CONFIG...
--ここで書き換えたい設定を記入する。
--ここで変更しなかったデータはそのまま残る。(順序はシャッフルされるが)

ini["DNSMODE"]="0";

--DELETE VOID KEY
for key,value in pairs(ini) do
    if(value == "")then
        ini[key]=nil;  
    end
end

--OUTPUT a-z
fa.rename("/SD_WLAN/CONFIG","/SD_WLAN/CONFIG.bak");

fo = io.open("/SD_WLAN/CONFIG","w");

print("-----")
print("[Vendor]\n");
fo:write("[Vendor]\r\n\r\n");
for i=0,26,1 do
    n = 0x61 + i;
    for key,value in pairs(ini) do
        h = string.byte(string.lower(key));
        if(h == n) then
            print(key.."="..value.."");
            fo:write(key.."="..value.."\r\n");
          end
    end
end
fo:close();

print "Done";

2015年12月6日日曜日

FlashAirのLuaスクリプトで得たファイル更新時間を文字列に直す

FlashAirデベロッパーフォーラムで「ファイルの更新日時を取得したい」という話があった。
どうせUNIX時間か何かだろう、と思ったが、試してみるとハマったので書いておく。

FlashAirに搭載されたLuaFileSystemライブラリからファイルの更新時間を
取得する(
lfs.attributes→modification)謎の数値が帰ってくる。例えば、1199991528など。

UNIX時間かな?と思い、変換してみると、今さっき作ったファイルなのに
2008/01/11 03:58:48とかになってしまう。

日付がおかしいのならともかく、時間単位でも合わない。

UNIX時間との差をとっても、次のファイルはまたおかしな値になる。

さっき作ったファイルと、今作ったファイルの値を比較してみても、秒単位ではないなにかで

数値が増えていることがわかる。単純に二倍や1\2でもない。

そう、これは秒数ではない。

では正体は何かというと、FATの時間情報そのものだ。
形式は以下。

01000111100001100110101011101000
YYYYYYYMMMMDDDDDHHHHHMMMMMMSSSSS

FATファイル システムのしくみと操作法 - ELM
http://elm-chan.org/docs/fat.html
こちらのサイトで言うところの、

DIR_WrtDateとDIR_WrtTimeの各16bitを結合した32bitのバイナリ情報だ。

ということで、これらを変換して、人間に読める形式に変換するスクリプトを書いた。

Lua5.2にビット演算機能が実装されていて本当に良かったと思った。

ライセンスはWTFPLなので、自由に使って欲しい。


--FAT形式のファイルアクセス時間を分解する
function GetFileModificationTime(Fat_binary_time)
   Year = bit32.band (bit32.rshift(Fat_binary_time, 9+16),0x7F) + 1980
   Month = bit32.band (bit32.rshift(Fat_binary_time, 5+16),0x0F)
   Day = bit32.band (bit32.rshift(Fat_binary_time,0+16),0x1F)

   Hour = bit32.band (bit32.rshift(Fat_binary_time, 11),0x1F)

   min = bit32.band (bit32.rshift(Fat_binary_time, 5),0x3F)
   sec = bit32.band (Fat_binary_time,0x1F)*2; --FAT時間は秒数が2秒刻み

   return Year,Month,Day,Hour,min,sec

end

--yyyymmdd_hhmmss.jpgを生成する

function TimeToFileName(Year,Month,Day,Hour,min,sec)
   return string.format("%04d%02d%02d_%02d%02d%02d.jpg",Year,Month,Day,Hour,min,sec)
end

-------------------サンプル----------------------

--Hello.txtの更新時間を取得する
a = lfs.attributes("Hello.txt")

--文字列で得る

Year,Month,Day,Hour,min,sec = GetFileModificationTime(a.modification)
print(Year.."年"..Month.."月"..Day.."日"..Hour.."時"..min.."分"..sec.."秒")

--文字列で得たものをファイル名形式にする

print(TimeToFileName(GetFileModificationTime(a.modification)))

2015年11月24日火曜日

FlashAirなどの外部ストレージを、物理的に取り外さずに再接続する(Windows)

FlashAirを使っていて、カードを抜き差しする場面は2つある。

1つは、設定の更新や、起動時Luaスクリプト実行のテストなどで
 FlashAirの電源を入れ直す必要がある場合。

もう1つは、LuaスクリプトなどでFlashAirの中身が書き換わったが
 Windows側のキャッシュが更新されていないため、見えないという状態を解決するために
 読み込み直しの手段が再接続しかなく、しかたなく行う場合。


割合としては、後者のほうが多いだろう。
SDカードスロットやUSBコネクタは、頻繁な抜き差しに耐えうる構造になっているとはいえ、
大規模なプログラムになるに連れ、デバッグにはかなりの回数の抜き差しが発生するというのは
容易に想像できる。

そこで、どこかのサイトで見かけた方法を使用することにした。

SDカードリーダーは大抵の場合、USB経由で接続されている。(外付け・内蔵問わず)
そのため、デバイスマネージャから、一度無効にし、有効にすれば、
USB機器が再接続されたものと認識され、外部記憶装置の再読み込みが行われる。

しかし、それを手動でやるのはダルい。

そこで、Windowsのドライバー作成キット、WDKに内蔵されている
Windows Device Console (devcon.exe)を使う。

devcon.exeは、デバイスマネージャでできることを、コマンドライン上で実行できるようにする
公式のツールで、バッチファイルからデバイスの操作が可能になる。

詳しくは他サイトを参照して欲しいが、以下の様なバッチファイルを作成すれば良い。

devcon disable "USB\VID_0000&PID_0000&REV_0000"
devcon enable "USB\VID_0000&PID_0000&REV_0000"

USB\VID_0000&PID_0000&REV_0000の部分については、USB機器固有の情報であり、
デバイスマネージャで対象機器(ここではSDカードリーダー)のプロパティを開き、
ハードウェアIDを参照すれば、そのままのものが載っている。

この時、バッチファイルは管理者権限で実行する必要がある。
簡単にやるのであれば、バッチファイルを右クリックし、管理者権限で実行すればよいが、

バッチファイルが起動された時、管理者権限でなければ管理者権限で実行するような
バッチを組むと、さらに便利になる。

バッチファイルからUAC昇格ダイアログを表示する by @hymtk7 on @Qiita http://qiita.com/hymtk7/items/b3b77ad3375095dcd7eb

また、この操作では、USB機器をぶち抜き、ぶっ挿していることになる。
他のツール等を組み合わせ、安全な取り外しをしてから
再接続するようにしても良いかもしれない。


自分の使っているバッチファイルを以下に示す。
動作の保証はしない。

昇格部分には、上記のページのものを参考にさせていただいた。

------ remount.bat -------

@set @temp=0/*
@echo off

whoami /groups | find "S-1-16-12288" > nul
if "%errorlevel%"=="0" (
    echo 管理者権限で実行しています。
) else (
    echo 管理者権限が必要です。
    echo Relaunching Elevated: "%~dpnx0" %*

    if '%1'=='ELEV' (
        shift
    ) else (
        cscript.exe //e:jscript //nologo "%~f0" "%~0"
        exit /B
    )
)


devcon disable "USB\VID_0000&PID_0000&REV_0000"
devcon enable "USB\VID_0000&PID_0000&REV_0000"

goto :EOF
*/
var UAC = new ActiveXObject("Shell.Application");
UAC.ShellExecute(WScript.Arguments(0), "ELEV", "", "runas", 1);

2015年9月20日日曜日

#SONY_MESH SDKで遊ぶ

Sony MESH という、無線電子ブロック的なものがある。

http://meshprj.com/

Tag同士が無線で繋がり、各Tagにはバッテリーが内蔵されている。
iPadを中心としてプログラムが組め、Tag同士の提携で電子ブロック的に遊べる。

GPIOタグという、いかにもな名前のやつは、3.3Vの入出力、アナログ入力、
サーボが動かせる125HzのPWM出力を持っている。

今までは、値段は高いし、物の割にはできることが少なそうで、気にもしなかった。
確かに、プログラミングとしては非常に簡単に使え、無線で遊べるというのは面白い。

しかし、iPad上のプログラムが組める幅というのがかなり狭い。
GPIOタグの先にArduinoとかRas Piを繋げればなんでもできるが、それでは
正直315MHz帯の適当な無線リモコンをつなげるのとほとんど変わらないし。

しかし、SDKが使えるようになったと聞き、眼の色が変わった。
お高いTagを買わなくとも、iPadといろいろな機器の提携ができるとなれば、便利な環境だ。
Tagが使えればなおいい。

https://meshprj.com/sdk/

会員登録して読んでみたところ、javascriptでiPad上のTagを拡張できるようだ。

iPad上には、AND、タイマー、カウンター、マイク、カメラ、音。
あとはHueの操作ができるくらいしか機能がなかった(最近Gmailが追加された)が、
これが拡張できるのだ。

つい最近まで、一度作成したTagを編集・削除できない不具合があったが、
連絡したらさくっと翌日には直してくれた。

しかもなんと、Ajaxで外部とHTTP通信ができる。
...というか、これ以外は任意のintervalが設定できる程度の機能しかない。

しかし、単純でもHTTP/HTTPS通信が使えるなら、入出力はいくらでも思いつく。
というわけで、いくつか実装してみた。


Tweetする、IFTTTのトリガにする、端末にPush通知する。ついでに、RS-FF的な何か。
大体、HTTPリクエストを1発投げれば動き、それでいて必要そうななものはこの辺だろう。

サイトの方にJSONデータを上げておいたので、使ってみたい人はどうぞ使ってみて欲しい。
https://sites.google.com/site/gpsnmeajp/tools/mesh-custom-tags

ちなみに、天気情報を取得する、カメラを遠隔操作するとかの、より実用的なものは、
すでに公式のサンプルに存在するので、そちらを参考にして欲しい。


しかし、複数の同一Tagで通信する方法がないのかな、とか。
intervalで呼ばれるのは、inputではなくExecuteなのか、とか。
色々とクセもあるのが悩ましい。

おそらく、完成した時にはオープンで使えるようにするのだろう。その時に期待だ。

2015年7月2日木曜日

Windows上に、Eclipseを用いたRaspberry PiのC言語クロスコンパイル開発環境を作る。(ついでにBoost)

参考文献(Linux版の手順)
http://www.sadaji.net/Firmware/eclipse/index.htm

本ページでは、初心者をターゲットに説明している。
多少不快な表現があるかも知れないが、流して欲しい。


Windowsでの手順を以下に示す。

1.以下のものをダウンロードする。

・Pleiades All in One 日本語ディストリビューション [必須]
C/C++ 64bit Full Edition
http://mergedoc.osdn.jp/

バージョンは4.5 Marsで確認済み。32bit環境では当然32bit版が必要。Ultimateでもよい。
日本語が嫌いであれば、Eclipse公式でも良い。

・Windows toolchain for Raspberry/PI [必須]
GCC 4.6.3である、raspberry-gcc4.6.3.exeで動作確認済み。
http://gnutoolchains.com/raspberry/

・Boost C++ Libraries [おまけ]
Version 1.58.0で確認している。
http://www.boost.org/

・TeraTerm [おまけ]
SCPでファイル転送、コンソールアクセスする。
他に転送手段がある、SSHアクセスしないでUSBなどで渡す場合はなくても良い。
http://www.forest.impress.co.jp/library/software/utf8teraterm/


2.インストール手順

2-1.TeraTerm
 適当にインストールしてください。これが出来ないなら以下読まないほうがいいです。

2-2. Eclipse - Pleiades All in One
 適当なところに解凍すればそれでインストール完了です。
 ただ、デスクトップなどに置くのは非常におすすめしません。

 ここてば、C:\Pleiades\ に解凍したと仮定します。
 C:\Pleiades\Eclipse\ に、Eclipse.exeがあればOKです。

 初回起動時は、Eclipse.exeではなく、eclipse.exe -clean.cmd」で起動します。
 キャッシュのクリーンアップが行われます。

 ワークスペースの選択は、「../workspace」のままで構いません。
 C:\Pleiades\workspace\に保存されます。
 
 「この選択をデフォルトとして使用し、今後この質問を表示しない」にチェックを付けてOK。
 しばらくして、起動が完了したら閉じましょう。

2-3 Windows toolchain for Raspberry/PI
 ダウンロードしてきたEXEファイルを起動すると、すぐにインストール直前の画面になります。
 デフォルトでは「C:\SysGCC\Raspberry」にインストールするようになっています。
 利用規約(GPLライセンス)を読み、同意できる場合は、
 そのまま、「I accept the terms of liscense agreement」にチェックを付け、
 Installボタンを押すとインストール完了です。

2-4 Boost C++ Libraries
 入れる人だけ。ここではヘッダーオンリーでBoost asioを使う解説しかしないので
 その場合のみ説明します。
 http://sourceforge.net/projects/boost/files/boost/1.58.0/
 ここからダウンロード。zipでもよいが、個人的には7zの方がオススメ。
 
 ダウンロードしたら、解凍すれば完了。ヘッダーオンリーで使用するなら、
 Bootstrapもb2も不要。

 C:\boost_1_58_0\に解凍したと仮定する。

3.プロジェクトの作り方(Hello Worldをする)
3-1. まずはじめに
 もうセットアップは終わっているので、プロジェクトを作りましょう。
 まず、Eclipse.exeを起動。

 起動したら、「ファイル(F)」→「新規(N)」→「Cプロジェクト」



3-2. 「Cプロジェクト」
 プロジェクト名は適当に。ここでは「ccHello」とする。
 ここで、プロジェクト・タイプは「実行可能」→「空のプロジェクト」。
 ツールチェーンを「Cross GCC」とすること。
 設定したら次へ。

 

3-3 「構成の選択」
特に何も変更せず次へ。

3-4 「クロスGCCコマンド」
重要です。
クロス・コンパイラー接頭部に「arm-linux-gnueabihf-
クロス・コンパイラー・パスに「C:\SysGCC\Raspberry\bin」と入力。
そして完了をクリック。



3-5 ソースファイル入れよう
 プロジェクトは生成されましたが、空っぽです。
 ので、プロジェクトccHelloを右クリック。
 「新規(N)」→「ソース・ファイル」をクリック。


「新規ソース・ファイル」というダイアログが開くので
ソース・ファイルの欄に、main.cと入力。(.cのファイル名であれば何でも良いが)
そして「完了(F)」をクリック。


3-6 Hello Worldしよう。
 ソースが打ち込めるようになるので、HelloWorldを打ちましょう。

3-7 ビルドしよう
 打ち込み終わったら、「ファイル(F)」→「すべて保管(Ctrl + Shift + S)」をクリック。

できたら、「プロジェクト(P)」→「すべでビルド(A)」をクリック。
ビルド結果は下に出ます。無事ビルドできましたかね?

ここで出来たバイナリは、ARM-Linux専用です。Windows上では実行できません。

3-8 ファイルを見に行こう
 特にビルド設定を変えていなければ、出来上がったファイルは
 「C:\pleiades\workspace\ccHello\Debug」に「ccHello」として生成されています。

 これをTeraTerm等でRaspberryPiに転送。
 chmod 777 ccHello
 などとして、実行権限を付与した後、
 ./ccHello
 と実行し、
 「Hello World\n」と表示されれば万歳です。

4-1 C++で作る場合の注意点。
  プロジェクトを上記の「Cプロジェクト」で作ってしまった場合、もれなくC++言語は
 コンパイルやビルドに失敗します。

 「新規(N)」→「C++ プロジェクト」から生成したプロジェクトでは、正常に行きます。


力尽きたのでここまで。
Boostを使う場合などの注意点は、あとで追記するかもしれません。


Boost aisoを使う場合のヒント。

ヒント1
当然インクルードパスの設定は必要です。ただちょっと罠がある。
GCCではなく、G++の方に設定する。

(.text+0x34): undefined reference to `main'
mainなんて関数ないヨとエラーを吐かれる時は、インクルードパスを直してると治ります。
というか、多分、ソースを保存してないだけだと思います。

デフォルトでは、ビルド時自動で保存はしてくれないです。

ヒント2
Boost Asioをヘッダオンリーで使う場合、「ライブラリがない」エラーを吐きます。

C:\boost/boost/system/error_code.hpp:221: undefined reference to `boost::system::generic_category()'
C:\boost/boost/system/error_code.hpp:222: undefined reference to `boost::system::generic_category()'
C:\boost/boost/system/error_code.hpp:223: undefined reference to `boost::system::system_category()'
./main.o: In function `error_code':
C:\boost/boost/system/error_code.hpp:322: undefined reference to `boost::system::system_category()'
./main.o: In function `boost::asio::error::get_system_category()':
C:\boost/boost/asio/error.hpp:230: undefined reference to `boost::system::system_category()'


ソースの先頭に以下の記述をすれば治ります。

#define BOOST_DATE_TIME_NO_LIB
#define BOOST_REGEX_NO_LIB
#define BOOST_ERROR_CODE_HEADER_ONLY
#define BOOST_SYSTEM_NO_LIB

ヒント3
それでもpthreadが無いエラーを吐くので

./main.o: In function `boost::asio::detail::posix_tss_ptr_create(unsigned int&)':
C:\boost/boost/asio/detail/impl/posix_tss_ptr.ipp:34: undefined reference to `pthread_key_create'
./main.o: In function `~posix_tss_ptr':
C:\boost/boost/asio/detail/posix_tss_ptr.hpp:48: undefined reference to `pthread_key_delete'
./main.o: In function `boost::asio::detail::posix_tss_ptr<boost::asio::detail::call_stack<boost::asio::detail::task_io_service, boost::asio::detail::task_io_service_thread_info>::context>::operator boost::asio::detail::call_stack<boost::asio::detail::task_io_service, boost::asio::detail::task_io_service_thread_info>::context*() const':
C:\boost/boost/asio/detail/posix_tss_ptr.hpp:54: undefined reference to `pthread_getspecific'
./main.o: In function `~posix_tss_ptr':
C:\boost/boost/asio/detail/posix_tss_ptr.hpp:48: undefined reference to `pthread_key_delete'
collect2: ld returned 1 exit status
make: *** [ccpHello] Error 1

ライブラリの設定にpthreadを追加してやればOKです。


以上。

2015年7月1日水曜日

仮想シリアルポートcom0comって知ってる?

仮想シリアルポートcom0comって知ってますか?

クロスケーブルで繋いだCOMポートを2つ用意したような感じのを作れるもので、
シリアル通信するアプリケーションのデバッグに非常に便利です。



が、デバイスドライバなので、いわゆる署名問題が気になるところ。
私もVistaまでは使っていたのですが、それ以降、テストモードに切り替えるのが面倒で使わなくなっていました。

最新版だと、署名されてないため、やっぱり面倒です。
しかし、あったんですね、署名済みドライバ。1つ前のバージョンです。

公式のこちらから
http://sourceforge.net/projects/com0com/files/com0com/2.2.2.0/

com0com-2.2.2.0-x64-fre-signed.zipをダウンロード。
普通にsetuoすれば、使えます。

使い方は簡単。

スタートメニューの「com0com」フォルダに「Setup Command Prompt」ができてるので起動。

>install PortName=COM10 PortName=COM11
と入力してEnter。

ドライバのインストールが走り、シリアルポートが生成されます。

listコマンドで現在あるポートのリストが出ます。

削除はremove 0とか。消えなければ、1や2などの数字を試してください。
CNCA0,CNCB0なら、remove 0
CNCA1,CNCB1なら、remove 1です。多分。

Chrono::Engineで、球体や振り子が高速回転する、妙なところでエラーを吐く、発散する場合の対処法。

Chrono::Engineで、球体や振り子が高速回転する、妙なところでエラーを吐く、発散する場合の対処法。
(glitch or bug: ball and pendulum fast spinning ,moving,or clash in Chrono::Engine.)

・ボールが突然妙な加速とともに回転しはじめませんか?
・動力のない振り子が、宇宙からエネルギーをもらいながら高速回転しませんか?
・Print Screen キーを押して連続写真が撮れるはずが、エラーで落ちませんか?
・反発係数0にするとプログラムが落ちませんか?
・Visual Studio 2010 Expressを使っていませんか?



Visual Studio 2013 Express/Community/Professionalを使いましょう。
治ります。原因不明。

近藤科学のFTDI FT232RL搭載USBアダプターをRaspberry Piで使うのに苦労した話。

近藤科学のFTDI搭載USBアダプターをRaspberry Piで使うのに苦労した話。

おそらく、他のFTDIチップ搭載のUSBアダプタ、変換器でも利用できるだろう。

ただし、内容は保証しない。USBアダプターやOS、ハードウェアが恒久的に破損する危険性を
理解した上で自己責任で行うこと。

・はじめに

 近藤科学のICSサーボをPCで制御するにはどうするか。
Dual USBアダプターHSという、アダプターが近藤科学から出ているので使う。

終わり。

しかし、それはWindowsの話である。
Linuxから使うにはどうするか。

1.シリアルポートおよびUSB-シリアルアダプタを加工する。
 
 近藤科学のページ
 シリアルサーボ制御方法(1) 回路編 (近藤科学)
 http://kondo-robot.com/faq/serial-servo-method-tech
 にて、マイコンと接続する場合の解説が乗っている。
 同様に、FT232RLなどを使って接続すれば良い。

 まっとうで手早い方法だ。

2. Dual USBアダプターHS を無理やり使う。

 Dual USBアダプターHSは、そもそもが中身はFTDI社のFT232である。
 ただ、発売元も製品も違うことになるので、独自のベンダーIDとプロダクトIDが割り当てられ
 FTDI社のチップではない独自のチップとしてPCからは認識される。

 Windowsでは、そのベンダーIDとプロダクトIDの違うドライバを入れれば済む話。
 Linuxではどうするかというと、FTDIのドライバモジュールに
 ベンダーIDとプロダクトIDを登録すれば良い。
 というのは、このページに書いてある。

 シリアルUSBアダプターをLinuxで使うには (近藤科学)
 http://kondo-robot.com/faq/usb-adapter-for-linux

 上記のページに従い、いくつかのコマンドを打てば、
 通常のシリアルポートとして使えるはずである。

それでうまく行っていたらこのページは生まれない。

何が起きたかを説明する。

・前提として

Raspberry Pi + Raspbianを用いた場合、
内臓のシリアルポートは /dev/ttyAMAn
USB経由のシリアルポートは /dev/ttyUSBn として認識される。

そのため、USB経由のシリアルポートを探すには
ls /dev/ttyUSB*
と打てば良い。

無事見つかった後、単純に動作確認するなら、
echo A > /dev/ttyUSB0
とか実行すれば、出力される。

そして、事前に、FTDI社公式のICを積んだUSB-シリアル変換アダプタ

超小型USBシリアル変換モジュール (秋月電子通商) @ FT234X
http://akizukidenshi.com/catalog/g/gM-08461/

を用いて、USBシリアルポートが認識&出力できることは確認していた。

・認識されない

同様に、
まず、近藤科学のページの通り
sudo modprobe ftdi_sio vendor=0x165C product=0007
を発行した。

Dual USBアダプターHSをさし込むが、シリアル・ポートして認識されない。

USB接続された機器を調べる
lsusb
というコマンドを使うと、社名不明で認識されていることがわかった。

しかし、ベンダーIDこそ0x165Cであるが、プロダクトIDが0008であった。
(これは、先ほどのページの最後の方に記載されている。)

「ああ、これが原因か」と思い、
sudo modprobe ftdi_sio vendor=0x165C product=0008
を発行するも、やはり認識されない。

近藤科学のページの通り、dmesgコマンドを実行すると、
どうやら、USB機器としては認識されているが、FTDIの機器として認識されていないことがわかった。

さらにログをたどると、なにやら変なメッセージが。
FTDIのドライバが
「vendorなんてオプションはない、拒否する」(意訳)
「productなんてオプションはない、拒否する」(意訳)
とエラーを吐いているのだ。

タイミング的に、sudo modprobe ftdi_sio vendor=0x165C product=0008を
打ち込んだ結果らしい。

どうも、ftdi_sio vendor=0x165C product=0008という形式のコマンドは
そもそもサポートされていないようだ。

「近藤科学のページに嘘が書かれていたのか?」
と思い検索してみると、どうもこのページが書かれたのは2009年の話。
現在は2015年。

これは怪しいぞ、と思い検索してみると、案の定英語のフォーラムで話があった。

modprobe ftdi_sio errors / Kernel &amp; Hardware / Arch Linux Forums

[SOLVED] ttyUSB not showing up, modprobe unknown parameter 'vendor' & 'product'

どうやら、FTDIのこのコマンドは、デバッグ用か何かだったらしく、
すでに使用できなくなっているとのこと。

対処法は
echo vvvv pppp > /sys/bus/usb-serial/drivers/ftdi_sio/new_id
のように、/sys/bus/usb-serial/drivers/ftdi_sio/new_idに
ベンダーIDとプロダクトIDを書き込んでやることらしい。

もちろん、suでなければならない。

今回の近藤科学のUSBアダプターをRaspberry Piに接続するには以下のようにする。

$ sudo su
# modprobe ftdi-sio 
# echo 165C 0008 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id
# exit

これで再度dmesgすると、FTDIドライバの認識メッセージが表示されるだろう。
ls /etc/ttyUSB* をすればどの番号に接続されたかわかるはずだ。

近藤科学のすべてのUSBアダプターを使用するには、以下のようにすればよいだろう。

$ sudo su
# modprobe ftdi-sio 
# echo 165C 0001 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id
# echo 165C 0002 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id
# echo 165C 0006 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id
# echo 165C 0007 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id
# echo 165C 0008 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id
# exit

ちなみに、このままだと再起動等すると接続できなくなるので
/etc/rc.localなどに追記しておくと良い。

以上、参考になれば幸いである。

2015年5月30日土曜日

Arduino Leonardで、JRプロポのトレーナー信号をUSBジョイスティックに変換するアダプタ作った。(ついでにFMS Interface互換の出力付き)

Arduino Leonardで、JRプロポのトレーナー信号をUSBジョイスティックに変換するアダプタ作った。

(ついでにFMS Interface互換の出力付き)


作り方を以下に紹介しますが。注意事項。
警告:ここでは、プロポの本来の使いからから逸脱したものを紹介しています。
    一応安全に気を付け、問題のないように紹介を行っているつもりですが、
    結果として、プロポの破損・発煙・炎上・データ破壊・ノーコンなどが発生する恐れがあります。
    また、ここで作成した回路をPCPCなどに差し込んだ際、
    PCなどが破損・発煙・炎上・データ破壊し、最悪の場合、身体の損傷や火災などを招く危険性があります。
    これらリスクを理解もしくは同意のどちらか、もしくはその両方ができない場合は、行わないでください。
    筆者は、生じた事故・損害・損傷・データ等に対する賠償・謝罪等の責任を一切負いません。

0. Arduino Leonardを購入します。(いわゆる32U4系ボードやAVRでも可)

1. 以下を参考に、Arduino LeonardでUSBジョイスティック扱いの操作ができるようにします。

 Arduino LeonardをUSBジョイスティックにする
 http://gpsnmeajp.blogspot.jp/2015/05/arduino-leonardusb.html


2.以下の回路を組み立てます。(ブレッドボードで十分だが、組み込むなら小さめに)

 ただし、この回路はJRプロポの出力用なので、他のものを使う場合は
 別途回路を組み替えてください。(100k抵抗をGNDに落とす程度)

 コンデンサはなくても動きますし、最悪トランジスタ無しで直結した上で
 プログラム上の論理を反転すれば動く気がしますが、
 JRプロポは最低1万円位すると思うので、万が一のことを考え、おすすめしません。

 100kの抵抗は、200kでも多分動きます。
 トランジスタはNPNであれば何でも良いと思います。
 私は1815を使ってますが、2001でも問題はないかと。
 コンデンサも、大きすぎたり小さすぎたりしなければ問題ないと思います。
 ブレッドボードで組むとこんな感じ。
 

3.Arduino Leonardに繋ぎます。

4.以下のプログラムを書き込みます。
JoyState_t joySt;

void setup()
{
  Serial.begin(19200);
  pinMode(7,INPUT_PULLUP);
  pinMode(13,OUTPUT);

  joySt.xAxis = 0;joySt.yAxis = 0;joySt.zAxis = 0;joySt.xRotAxis = 0;joySt.yRotAxis = 0;
  joySt.zRotAxis = 0;joySt.throttle = 0;joySt.rudder = 0;joySt.hatSw1 = 0;
  joySt.hatSw2 = 0;joySt.buttons = 0;
}

void loop()
{
  static int ch=0;
  unsigned long cnt = pulseIn(7,LOW,3000);
  if(cnt == 0){
    interrupts(); //USB通信許可
    Joystick.setState(&joySt);
    delay(2); //通信時間確保

    ch=0;
    Serial.write((byte)0xFF);
    
    while(digitalRead(7) == LOW); //次のリーダーパルスまで待つ
    noInterrupts(); //通信禁止
  }else{
    switch(ch)
    {
      case 0:joySt.throttle = map(cnt,700,1500,0,255);break;
      case 1:joySt.xAxis = map(cnt,700,1500,0,255);break;
      case 2:joySt.yAxis = map(cnt,700,1500,0,255);break;
      case 3:joySt.rudder = map(cnt,700,1500,0,255);break;
      case 4:joySt.xRotAxis = map(cnt,700,1500,0,255);break;
      case 5:joySt.zAxis = map(cnt,700,1500,0,255); break;
    }    
    
    ch++;
    Serial.write((byte)map(cnt,700,1500,0,254));
  }
}

5. USBポートに繋げば完成です。
 ジョイスティックとしても動作しますし、FMSのシリアルインターフェースとしても動作するので
 どちらでも構いません。

Arduino Unoしか持ってない、USBジョイスティックとしての機能はいらないから、
FMSで動けばいい、という方はこちら。
少々バタつきはあるかもしれませんが、動くと思います。
void setup()
{
  Serial.begin(19200);
  pinMode(7,INPUT_PULLUP);
}

void loop()
{
  static int ch=0;
  unsigned long cnt = pulseIn(7,LOW,3000);
  if(cnt == 0){
    ch=0;
    Serial.write((byte)0xFF);
    
    while(digitalRead(7) == LOW); //次のリーダーパルスまで待つ
  }else{
    ch++;
    Serial.write((byte)map(cnt,700,1500,0,254));
  }
}


ついでに、パルス幅を観測したい人向け。ターミナルで開けば数字で出ます。
void setup()
{
  Serial.begin(19200);
  pinMode(7,INPUT_PULLUP);
}

void loop()
{
  static int ch=0;
  unsigned long cnt = pulseIn(7,LOW,3000);
  if(cnt == 0){
    ch=0;
    Serial.write((byte)0xFF);
    
    while(digitalRead(7) == LOW); //次のリーダーパルスまで待つ
  }else{
    ch++;
    Serial.print(ch);
    Serial.print(':');
    Serial.println(cnt);
  }
}

Q.なんでLeonard限定なの?
A.USB通信機能を使っているからです。
  USB通信機能がないと、まずジョイスティックとして認識させられないというのがひとつ。
  もう一つは、超高速な仮想シリアル通信が使えるからです。

Q.ジョイスティックが6chしか反応しないんだけど?
A.私の持ってるプロポが6chだからです。ご自分でプログラムを改造すればすぐ解決します。
 多分1,2行程度。ただし、ジョイスティックの入力の数には限界があるので、
 8chが限界でしょう。

2015年5月28日木曜日

JRのプロポのトレーナー端子のPPM出力について(波形追加)

JR(日本遠隔制御)のプロポのトレーナー端子(トレーナージャック/トレーナーケーブル)のPPM出力について、
調べても調べてもなんか情報が混乱しているというか、微妙に出てこないのでまとめる。

というのも、JRのXG6 NET-R116Gを買ったので、それを色々使おうと思ったら
てんで情報が出てこないので、見つけた情報と説明書を参考に考えてみたものだ。

1.トレーナー信号の波形と振幅、極性
 JRのプロポのトレーナー端子の出力は、率直に言ってヘンらしい。

 様々なブログやサイトを見てみたが、どこも-1.2~+0.3[V]らへんの波形を出している。
 代表として以下のブログを張っておく。

 Lightbridge用 8ch→11ch基板の製作1 XG8のPPM信号をオシロで見てみる - 空の履歴 - Yahoo!ブログ
 http://blogs.yahoo.co.jp/helichallenge/35056811.html


 実際にオシロスコープで取得した波形を示す。
 確かに、-1.2V ~ +0.2V程度の波形である。振幅Vp-pは1.5Vほどであるらしい




 プロポがこんな波形を出す理由がわからない。
 ICから直接出すのなら、フタバなどのように、0 ~ +5[V]になるはずである。
 そもそもとして、ICなどからこんな正負に振れた波形を出すのは至極面倒なはずだ。
 
 ありえるとするなら、コンデンサでデカップリングしているくらい... と考えていると、
 以下のブログが引っかかった。

 JRのDSC信号にできるかも - とりあえず「何でもぶろぐ」Ⅱ
 http://oteru0106.exblog.jp/22049137/

 抵抗+コンデンサで、CMOS出力をJRプロポ風の出力に出来た、
 また、JRのプロポ自体分解してみると出力にコンデンサがつながっていた、ということだった。
 
 実際、オシロスコープで観測した際、最初の観測だけ、徐々に電圧が正から負へと
 推移していく様子が見られた。

 また、波形が明らかに訛っている。



 これで謎が解ける。
 JRのトレーナー端子は、ミニプラグ。
 ミニプラグには弱点があり、それは「抜き差しした際に信号が短絡する」という問題だ。
 中途半端な状態で差し込むと特に起きる。

 JRはこの状態でプロポが破損するのを避けるために、デカップリングコンデンサを挟んだのだろう。
 PPM波形は交流なので、コンデンサを通しても問題なく伝わる。
 また、外部から変な直流(特に、コンデンサマイク用直流電圧)が加わっても遮断されるし、
 さらに、この電圧かつ交流であれば、外部のマイク入力などにつないでも破損を起こしにくい。

 そういう目的があったのだろう。
 
2.トレーナー端子には4chしかでてこない?
 「JRのプロポはスレーブモードにすると4chしかでないのでダメだ」など、
 2chなどで勘違いしている記述を見かける。
 どうも、説明書を見る限り、SLAVE MODE(スレーブモード)と、PPM MODE(標準モード)の違いらしい。
 
 結論から先に言うと、「JRのプロポはトレーナー端子に差し込んだ時点でDSCモード
  (PPMスレーブモード)になってるので、SLAVEモードに切り替える必要はない」だ。
 
 説明書でも、「親機がノーマルトレーナー (NORMAL) の場合、特に設定として項目があるわけではありませんが、トレーナー のモードを ”SLAVE” とはしないでください。」と書いてある。

 SLAVEモードという名前が混乱のもとだと思うが、言い換えるなら、
 無設定モード → 一般的なPPM出力スレーブモード・DSCモード
 SLAVEモード → 生4chスティックPPM出力モード
  らしい。

いうところ、親機と子機の設定の組み合わせが2通りあるようで、

1.親機が通常モード(chをすべてMASTERにする)
 子機は何もしない(PPM MODE)

→どういう状態か?
 親機は、子機から受け取った信号をそのまま無線に飛ばす。
 そのため、トレーナースイッチで親機操作か子機操作かを切り替える。
 子機からは、子機で設定されたすべての情報が適用された信号が出ている
 結果、子機はDSCモードと等価。全チャネル出力。故に子機側は切り替えが不要。

 親機のプロポのSLAVE: PPM MODE というのは、子機はPPM(DSC)モードにしろ、という意味。
 XG6などの最近の機種は、トレーナーケーブルを差しこみ自動で電源が入った時点で
 PPM(DSC)モードとなっている。

 親が通常モードのまま、子をSLAVEモードにするとうまく動かない。

2.親機がプログラムモード(chごとにMASTER、SLAVEを選択)
 子機はスレーブモード(SLAVE MODE)

→どういう状態か?
 親機は、子機の操作を一旦解釈し、親機の設定を適用、スティックの置き換えをして無線送信。
 子機でミキシング等の適用をされると、スティックを推定できずめちゃくちゃになる。
 そのため、子機をスレーブモードにし、4chの生のスティック情報だけ出力させることで
 後処理は全部親機でやる。
 結果、スレーブモードでは子機から4ch分の情報しか出ない。

 親機のプロポのSLAVE: SLAVE MODE というのは、子機はSLAVEモードにしろ、という意味。
 これは、XG6などの機種の場合はSLAVEモードに設定する必要があり、
 他の場合は余計な適用が行われない状態にしなければならない、ということだろう。


実際に波形を観測してみたものを以下に示す。
まず、DSCモード。

次に、SLAVEモード。



どちらも、8ch分の出力がされていることがわかる。
しかし、XG6なので、実際に操作可能なのは6chである。

操作してみたとこ、説明書の説明とは違い、SLAVEモードでも
6chすべてが動作していることがわかった。

しかし、SLAVEモードでは、トリムやジャイロ設定は無効となっていることも確認できた。
おそらく、ミキシングなども同様に無効になっているのだろう。

3.トレーナー端子はモノラルじゃなきゃダメ?
 写真を見ればわかるが、トレーナーケーブルはモノラルミニプラグである。

 ステレオミニプラグを使って色々やっているブログを見ると、
 「接触不良なのか、電源が入らないことがある」、「中途半端に差し込むと良い」と書いてあることが多い。
 これは、モノラル端子のGND部分で電源を入れる処理をしているのが、
 ステレオだとLとRに分離されていて、接触が挿入時の一瞬しかされないための誤動作と考えられる。
 ただし、自分はプロポを分解していないので不明である。
 素直にモノラルプラグを買ったほうが良いのでは。

 マルツなどでは、ケーブル付きのL字型モノラルケーブルが売られている。
 トレーナーケーブル同様、コンパクトで良い。

 [マルツオンライン] 3.5mm モノラルプラグ付き 1.8m
 http://www.marutsu.co.jp/pc/i/107911/


おまけ。

JRのプロポ XG6で観測した全波形を以下に示す。
なにかの参考になるかもしれない。

通常モード


SLAVEモード時。



波形の周期は、通説の通り、22.00msであった。

出力コンデンサの影響か、訛りが見られる。



ホールドオフをこのくらいの値にすると、波形が暴れにくい(トリガがきちんと掛かりやすい)。


PPMなので、パルス幅は一定の400us。これも通説通り。


パルス間はニュートラルで1.5ms。


最小値で1.1ms。


最大値で1.9ms。


パルス-パルス間時間は、ニュートラルで1.1ms。


最小値で700us。


最大値で1.5ms。


信号。


最後のパルスから次の周期までの時間は、全最大(8ch中6ch最大)で、7.2ms

全最小(8ch中6ch最小)で、12ms。


以上。