0

IRKitのブートローダーを破壊したので直した

ファームウェアを改造していたら、ブートローダーを壊した。
その結果、新しいファームウェアを書き込めなくなり、文鎮と化した。
AVRライターかArduinoがあれば直せる。

壊した原因

プログラムが28672byteを超えたため、Arduino互換機であるIRKitのブートローダー領域を上書きした。Arduinoはブートローダーと実行プログラムが同じプログラムメモリ上に共存しているのだ。
普通のArduino IDEを使っていれば書き込み前にサイズチェックして、こういう事にはならないのだが、inoだと考慮てくれないらしい。

githubでissueを立てたら助けてもらえた。


28672 byte以上書き込むとbootloader領域破壊する

書き込む前に.hexのサイズをチェックする
% /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/avr-size -B .build/irkit/firmware.hex | tail -n 1 | cut -b 11-15
Arduino IDEの中にあるavr-sizeを使う。


直し方


3手必要

  1. IRKitにArduinoのブートローダーを書き込む
  2. IRKitにEEPROMを初期化するツールを書き込む
  3. IRkitのファームウェアを書き込む

前提としてIRKitのファームウェアを改造してセンサー読めるようにするに書いた、inoのインストールやboards.txtへパッチを当てる、などの作業が必要。
また、IRKitの基板上のAVRマイコンに書き込む「AVRライター」も必要。

1. IRKitにArduinoのbootloaderを書き込む


Arduino IDEのバージョンは1.0.5(現在の最新版)

基盤を取り出す


ケース背面のネジを4つ外せば取り出せる。
AVRライターを接続するためのピンヘッダ(オス)を付けた。ケースの蓋を閉めるためにピンヘッダのハンダ付けした側を少し削った。

IRKit

AVRライターを作る


純正のライターが今家に無いので、Arduino UNOをAVR書き込み器にした。
純正のSTK500やAVR-ISP、あるいはusbaspを持ってるなら必要なし。

Arduino – ArduinoISPを参考にした。

  • [ファイル]→[スケッチの例]→[ArduinoISP]

を適当なArduinoに書き込むだけ。


これでSTK500と同等のISPライターになる。
In System Programmingライター、つまり回路からターゲットCPUを取り出さずにプログラムを書き込む事ができる。

うまく書き込めない場合がある

このままライターとして使ってもうまく動かなかった。
"avrdude: stk500_getparm(): (a) protocol error, expect=0x14, resp=0x14"
というエラーがでた。
Arduino UNOだからだろうか?他のArduinoだと大丈夫かもしれない。


のパッチをArdunoISPに当てる。empty_reply()を1回だけしか送らないようにする。
-
+int initSent=0;
int error=0;
int pmode=0;
// address for reading and writing, set by 'U' command
@@ -399,7 +399,8 @@
uint8_t ch = getch();
switch (ch) {
case '0': // signon
- empty_reply();
+ if(! initSent) empty_reply();
+ initSent = 1;
break;
case '1':
if (getch() == CRC_EOP) {
これをライター用Arduinoに書き込み直した。


IRKitとAVRライターを接続


IRKitの回路図を見ると、基板上のICPSコネクタにMISO/MOSI/SCK/RESET/5V/GNDの6ピンがある。これは上から見た図。

ICSPコネクタ
Arduino – ArduinoISPを参考に、オス-メスのジャンパワイヤ

  • Arduinoのピン13 → IRKitのSCK
  • ピン12 → MISO
  • ピン11 → MOSI
  • ピン10 → RESET
  • GND → GND
  • VCC → 5V
と接続する。

IRKit


ブートローダーを書き込む

  • [ツール]→[マイコンボード]→[IRKit]
  • [ツール]→[書込装置]→[Arduino as ISP]
  • [ツール]→[ブートローダを書き込む]

で「ブートローダの書き込みが完了しました。」と出ればok。
書き込み中はArduinoのRXとTXのLEDが点滅する。



2. IRKitにEEPROMを初期化するツールを書き込む

このままファームウェアを書き込んでも、IRKitがWiFiに接続できない。
ブートローダの書き込みにより、EEPROMが初期化されてしまっている為。(これはArduinoISPの設定でなんとかできるかもしれない)

EEPROMにはiOSアプリで設定した接続先のWiFiアクセスポイントの情報だけでなく、IRKit本体の設定用アドホックWiFiアクセスポイントの情報や、赤外線データの解析用のデータ構造も含まれている。

EEPROM初期化ツールをビルドしてIRKitに書き込む

前提としてIRKitのファームウェアを改造してセンサー読めるようにするに書いたinoのインストールやboards.txtへパッチを当てる、などの作業が必要。

IRKitのリポジトリからファームウェアやEEPROM初期化ツールのソースコードを持ってくる

% git clone git@github.com:irkit/device.git
% cd device/firmware/t/initialize-eeprom
% ino build -m irkit
% ino upload -m irkit

電源入れてすぐuploadすると書き込める。

設定用のアドホックWiFi APのパスワードはfirmware/t/initialize-eeprom/src/initialize.inoのsaveLimitedAPPassword関数で指定されている。

フルカラーLEDが赤→青に変わるとEEPROMの設定は終了。


3. IRKitにファームウェアを書き込む

IRKitのリポジトリからファームウェアをビルドする

% cd device/firmware
% ino build -m irkit
% ino upload -m irkit

なおmasterブランチが動かなければ
% git tag
% git checkout v1.3.5
などして安定版のファームウェアを書き込むと良い。


これで出荷時状態に戻る(パスワードはsaveLimitedAPPassword関数で指定したものだが)
iOSアプリで自宅のWiFiに接続する設定をして完了。

0

IRKitのファームウェアを改造してセンサー読めるようにする

読めるようにソフトウェアは改造してみたけど、肝心のセンサー自体はまだ何もつないでない。

IRKitはArduino派生のハードウェアなので、ファームウェアの改造もできる。
ファームウェアに関しては特にドキュメント読まなかった(というか何も無かった)けど、Arduinoの作法に従ってやればさくさくといじれた。プラットフォームは偉大。

とりあえずGET /messagesすると赤外線データだけでなくADコンバータ0〜5を読んでJSONに混ぜて返すようにできた

freqの次のsensorの配列がそれ。



Macでやる。多分Linuxでもできる。Windowsでは使ってるツール(ino)が動かなくて無理な気がする。


ソースをgithubからclone

https://github.com/irkit/device/wiki

% git clone git@github.com:irkit/device.git
% cd device

firmwareディレクトリ以下がソレ。

なお今いじってるリビジョンはMar 18 16:03:26 の 992a4a58b4d73d213f4083c285c8d7bac39533b3

なんか動かなくなったら
% git checkout v1.3.5
して安定版でビルドして書き込めば治せると思う。多分。


inoとは

ビルドとファームウェアの書き込みにはinoを使う。そういえば以前紹介してた。
ino使うとCUIでArduinoをビルドできて便利
inoはArduino.appを参照して使うpythonスクリプトなのでArduino.appもインストールしておく


update boards.txt

inoはboards.txtを読んでビルドや書き込み時の設定とするので、boards.txtにirkitを追加する。

それぞれ以下にある

Mac
/Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/boards.txt

Ubuntu
/usr/share/arduino/hardware/arduino/boards.txt

% patch -u /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/boards.txt < firmware/boards.txt.patch

irkitが使えるようになってるかチェック
% ino list-models


build


firmware/build.shを参考にした。しかし中でなんかrmしてたり、~/src/ino/bin/inoを使っていたりとハードコーディングしていたのでbuild.shを使わず手動でやった。
% cd firmware
% VERSION=`git describe --tags --long | sed -e "s/^v//" | sed -e "s/\-/./g"`
% sed -e "s/__VERSION__/$VERSION/" src/IRKit/version.template > src/IRKit/version.c
% ino build -m irkit
これでうまくいけばfirmware.hexができる。


upload hex

IRKitをMacにUSB接続してすぐ書き込む。
Arduinoは電源入ってから数秒間だけファームウェア書き込みを受け付ける為。
% ino upload -m irkit

※注意:ブートローダー破壊する可能性あり(追記)

28672 byte以上書き込むとbootloader領域破壊するので、書き込み前にhexのサイズを確認するべき。詳しくは→IRKitのブートローダーを破壊したので直した

serial monitor

なおシリアルモニタで見るとIRKitがDNSをlookupしたり色々通信しているのが見れる。
% ino serial
ctrl+a ctrl+xで終了。


ファームウェア改造


return analog sensor values on HTTP-GET /messages · 8a89128 · shokai/irkit-device


firmware/src/IRKit/IRKitHTTPHandler.cpp がHTTPのハンドラなので、
static int8_t on_get_messages_request関数にanalogReadを追加するなどした。
     gs.write("{\"format\":\"raw\",\"freq\":"); // format fixed to "raw" for now
gs.write(IrCtrl.freq);
+
+ gs.write(",\"sensor\":[");
+ for(char i = 0; i < 6; i++){
+ gs.write((uint8_t) analogRead(i));
+ if(i < 5) gs.write(",");
+ }
+ gs.write("]");
+
gs.write(",\"data\":[");
for (uint16_t i=0; i gs.write( IR_get() );

gs.write関数はGSwifi.cppに書かれている。

1

895円の超小型Ardunoクローン DigiSparkを買った

とにかく小さくて安いのに、ほぼArduinoとして使える。
そしてズボンのコインポケットに入れて持ち歩けるので電車内hackに便利。

DigiSpark


ソースコードはここに置いておいた。
デジタル出力・アナログ出力・アナログ入力・Mac上のRubyとのUSB通信を試した。
https://github.com/shokai/digispark-study

「webサービスと連動したちょっとしたハードウェア」にはArduinoはオーバースペックだと思うので、DigiSparkちょうどいいと思う。





普通のArduinoとの違い

詳しくは digispark:tutorials:basics [Digistump Wiki]

多少制限はある。
  • IOピン数が6本、PCとUSB通信する場合は4本しか無い
  • プログラムを書き込めるメモリ領域が6Kbyteしかない
  • PCからは仮想シリアルポートではなくただのUSB-HID(ヒューマンインタフェースデバイス)として認識される

内部のソースコード読んだらすごかった。
DigiSparkはマイコン上のプログラムを工夫する事で部品点数を減らし、価格と基盤サイズを抑えている。

ここの一番下にある回路図を見ればわかるが、DigiSparkはATTiny85という安い8ピンマイコンと、抵抗・ダイオード・三端子レギュレータしか使っていない。部品点数が少なく基板も片面実装なので安い。

ATTiny85にはUSB機能は付いていないが、micronucleusというDigiSparkのファームウェアがArduinoブートローダとソフトウェアUSBを同時に実装しているから、たったこれだけの部品でArduinoになれる。

micronucleusはV-USBというUSB1.1のソフトウェア実装を取り込んでいる。

V-USBは、10年ぐらい前に流行った「12MHz以上のAVRマイコンならUSB-HIDになれる」というhackを綺麗に書きなおしてオープンソースで公開されているライブラリ。

実際に使う上で一番大きい違いは仮想シリアルポートではなくUSB-HIDとして使わなければならない所だけど、これはDigiSparkを単体で使う場合は関係ないし、PCと通信させて使う場合もRubyならdigiusbというgemを使えば普通のシリアルデバイスの様に使える。
digiusbの実装を見ると普通にlibusbを使っているだけなので、他の言語でもたぶんなんとかなるんじゃないかと思う。

誰かnode.jsのC拡張くわしい人、digiusb npm作ってください


購入

注文して1週間ぐらいで届いた。

Digispark USB Development Board – Digistump

日本円でも決済できる。
単体で9ドル、現時点で895円。
発送方法は色々選べるけど安いUSPS First Class Package Insuredの965円にした。14+ daysって書いてあったけど1週間ぐらいで来た。

最低でもこのUSB Development Boardだけあれば良いが、シールドなども色々あるのでまとめていろいろ買った。
DigiSparkをUSBポートに接続し、PC/Mac上のソフトで自作プログラムを書き込めば動くので特に別途マイコン書き込み器等は必要ない。

他に頼んだもの

Stackable Headersはシールドを2階建以上積むる時に使う。
Starter KitはUSB Development boardにRGB Shield KitPrototype Shield Kitが付いたもので、壊した時の為にもう一枚DigiSpark欲しかったから買った。バラで買うより100円安い。
Temperature Sensor Shieldは1-wireで温度が計れる。


ボードへのプログラムの書き込み

DigiSpark用のIDEを使う。

Arduino IDEはJavaで実装されていて、Mac/PC/Linuxで使える。それにDigiSparkへの書き込み機能とDigiSprak用のマイコン側のライブラリを追加したものがこれ。

タイトルバーにArduino IDEって書いてあるけど、[ツール]→[書込装置]にDigiSparkが追加されている。
また、スイッチサイエンスが作って本家Arduino IDEに取り込まれた日本語化も有効になっている。


とりあえず使う

プログラムの書き込みはArduinoと同じを押すだけ。
しかしボード上にリセットボタンが無いのでUSBポートを抜き差しする事で代替する。
Arduinoのファームウェアは電源が入ってから数秒はプログラム書き込み待ち状態になり、特にプログラムが送られてこない場合は今持っているプログラムを実行するようになっている。

基板上のLEDを点滅させる


基板上にテスト用のLEDがあるが、基盤のリビジョンによりpin0にあったりpin1にあったりする。とりあえず500ミリ秒ごとに両方点滅させた。

led_blink.ino
bool led_stat = false;

void setup(){
pinMode(0, true);
pinMode(1, true);
}

void loop(){
digitalWrite(0, led_stat);
digitalWrite(1, !led_stat);
led_stat = !led_stat;
delay(500);
}


LEDをぼんやり点滅させる

analogWriteを使った。なおanalogWriteにはpinModeの設定はいらない(arduino.cppが勝手にやってくれる)


led_fade.ino
void setup(){
}

void loop(){
for(char i = 0; i < 26; i++){
analogWrite(0, i*10);
analogWrite(1, i*10);
delay(20);
}
for(char i = 25; i > 0; i--){
analogWrite(0, i*10);
analogWrite(1, i*10);
delay(20);
}
}


Macと通信する

Rubyでやる場合、libusbのラッパーのdigiusb gemをインストールする
% brew install libusb
% gem install digiusb
digitermという実行コマンドも付いている。


アナログセンサーを読んでMacに送る

analogReadしてUSBでMacに送る

SerialライブラリのかわりにDigiUSB.hを使う。delayもDigiUSB.delayにしないと通信できなくなる。
adb_usb.ino
#include <DigiUSB.h>

void setup(){
DigiUSB.begin();
}

void loop(){
DigiUSB.println( analogRead(1) );
DigiUSB.delay(100);
}

なおLEDを点滅させながらanalogReadしたら値がブレまくるけど、キャパシタを入れれば大丈夫だと思う(まだ試してない)

digitermで見る


センサーつないでないけどanalogReadは取れてる。


Rubyで受信する

adc_usb.rb
require 'digiusb'

spark = DigiUSB.sparks.first

loop do
recv = spark.gets.strip
puts "analog value: #{recv}"
end

digiusb gemは普通にIOっぽく使えるので、serialport gemからの移行も楽だと思う。


Macから基盤上のLEDを点灯・消灯する

今度はMacからDigiSparkに命令を送る。

‘o’が来たら点灯、’x’が来たら消灯するプログラムを書いた
usb_led.ino
#include <DigiUSB.h>

void setup(){
pinMode(1, true);
DigiUSB.begin();
}

void loop(){
if(DigiUSB.available()){
char recv = DigiUSB.read();
switch(recv){
case 'o':
digitalWrite(1, true);
break;
case 'x':
digitalWrite(1, false);
break;
}
}
DigiUSB.delay(10);
}

digitermを起動して、oやxを入力しenterキーで送信で操作できる。


RubyからLED点灯・消灯命令を送る


1秒ごとに点灯消灯

usb_led.rb
require 'digiusb'

spark = DigiUSB.sparks.first

led_stat = false

loop do
puts led_stat
spark.write( led_stat ? 'o' : 'x' )
led_stat = !led_stat
sleep 1
end


このように簡単に使えるし、安いし、ズボンのコインポケットにピッタリはいるから電車内hackに便利。
arduino_firmataのdigispark版を作ろうと思う。けど6Kbyteのプログラムメモリに入れるために機能を削らないとならないかもしれない。
node用のdigiusb npmも欲しい。

あと、AndroidとArduinoを接続したりした時も思ったけど、もうUSB上で仮想シリアルポート使うのやめたほうがいいんじゃないかと思う。DigiSparkのようにUSB-HIDならドライバいらないし、libusb使えば自作プログラムとマイコンの通信も簡単だし、シリアル通信を115200bpsとかいう速度で読み書きするのもなんかおかしい気がする。

0

node-arduino-firmataを古いArduinoに対応させた

nodeにarduinoのコードを埋め込めるarduino-firmata npmを古いArduino(diecimila、duemillanove、Seeduino等)に対応させた。

arduino-firmataを使うとnodeとarduinoのそれぞれのコードを書いて通信させるのではなく、nodeの中にarduino.digitalWrite(13, true)とかarduino.analogRead(3)とか書けるのでコードが綺麗になって大変便利です。ご利用ください。
実装としては古来からあるMIDIをベースにしたFirmataというプロトコルを使っている。


現行最新のArduino LeonardやUNOとdiecimila/duemillanove等の違いは、USBシリアル通信変換機能が新しいArduinoのAVRマイコンには内蔵されているけど古いArduinoでは外部のFTDI等のチップを使っている事で、つまりPCから使う時にドライバが違う。

ドライバの差はserialport npmがどうにかしてくれるので良いが、node内でのシリアルポート関連のイベントのタイミングが違うので個別に処理をわける必要がある。
ちなみに同様の処理をRubyの方のarduino_firmata gemでも実装している。


古いArduinoと新しいArduinoを見分ける方法

USBのデバイス名で見分ける事もできるけど、それだと無数にあるArduinoクローンに対応できない。
古いArduinoを見つけたいだけなのでデバイスファイル名で判定した。

Debian/Ubuntu/Raspberry pi(raspbian)など
  • /dev/ttyACM0 -> Leonard, Micro, UNO
  • /dev/ttyUSB0 -> Decimilla, Duemillanove

Mac OSX
  • /dev/cu.usbmodem1234 -> Leonard, Micro, UNO
  • /dev/cu.usbserial-A1234 -> Decimilla, Duemillanove
という風にデバイスファイル名が違うので、正規表現 /usbserial|USB/ にマッチするボードは古いArduinoである事がわかる。


古いArduinoでやらなければならない処理

古いArduinoだとシリアルポートが開いてから実際に通信が可能になるまで2〜5秒程度待たなければならない。この時間は一定ではない。これはFTDIチップとそのドライバのせいだと思う。

また、nodeやRubyからREPORT_VERSIONをリクエストして、返答がArduinoボードから返ってきた後に、IOの初期化命令を送るのをだいたい3秒ぐらい待たなければならない。
新しいArduinoだとノータイムでIOの初期化命令を送って良い。
こっちはCPUの性能だと思う。REPORT_VERSIONが往復しているわけだからArduino側でFirmataのプログラムは起動して通信できているんだろうけど、何かが遅いっぽい。


FirmataにおけるArduinoボード初期化処理の流れ

IOの初期化とは、アナログピン0〜5番は逐次アナログ値を計測して送ってくれとか役割を指示する処理のこと。

シリアルポート開く→REPORT_VERSIONをボードに送る→versionが返ってくる→IOを初期化する
という流れだが、古いボードの場合
シリアルポート開く→(2〜5秒待つ)→REPORT_VERSIONをボードに送る→versionが返ってくる→(3秒待つ)→IOを初期化する
という風になる。IO初期化はarduinoから値が返ってこないのでちゃんと初期化できるタイミングまで待ってやる必要がある。

古いArduinoで使われているFTDIのチップは、RubyでもNodeでも、各言語のシリアルポートライブラリが発行するopenイベントより数秒経ってから実際の通信ができるようになるのと、ちょっとよくわからないけど古いAVRマイコンだとFirmata自体が動き始めるまで時間がかかるみたいでversionが返されてからも待たないといけないらしい。

この辺はFirmata内部のコードも全部把握してるけど、特に変な事はしてないみたいなので、マイコンの世代差だと思う。



という処理をしている。
主にこの辺でやってる
https://github.com/shokai/node-arduino-firmata/blob/v0.3.0/src/arduino-firmata.coffee#L58-L95

1

NodeにArduinoのコードを埋め込む arduino-firmata npm

作った。

インストール

% npm install arduino-firmata

https://npmjs.org/package/arduino-firmata


以前作ったruby版android版と同じ実装なのと、coffee-scriptがほとんどRubyなので一瞬でできた。
JSのビット演算子の優先順位がrubyなどより低いらしくて、より過剰にカッコつける必要があった。


既存のライブラリとの違い

johnny-fiveが有名だけど、より普通のfirmata(proce55ing版のオリジナルのやつ)っぽい書き方ができるようにした。関数名がarduinoと同じなので学習コストが低い。
firmataはdigitalRead/analogReadがコールバック返してくるけど、それは使いにくいので(例えばピン1がHIでピン2がLOWの時〜という条件式が書きにくい)値を返す普通の関数として実装した。

ようするにarduino使ったことがあるならすぐ使えるようにした。


使い方


まず
Arduino IDE -> [File] -> [Examples] -> [Firmata] -> [StandardFirmata]
をArduinoに書き込む。これでnodeからの命令を受けて動くarduinoになる。

くわしくは
https://github.com/shokai/node-arduino-firmata#readme


12, 13番ピンのLEDを点滅させる例
ArduinoFirmata = require 'arduino-firmata'
arduino = new ArduinoFirmata().connect()

arduino.on 'connect', ->
stat = true
setInterval ->
console.log stat
arduino.digitalWrite 13, stat
arduino.digitalWrite 12, !stat
stat = !stat ## blink
, 500

なおconnectはArduinoっぽいデバイスを適当に探して接続するが、引数にデバイスへのパスを入れて指定もできる。connect(“/dev/tty.usb-device”)

他にdigitalRead, analogRead, analogWrite, servoWrite, sysex、入力が変化した時のanalogChange, digitalChangeなどが実装してある。
https://github.com/shokai/node-arduino-firmata#io



socket.ioと一緒に使う例

samples/serverに入れておいた物の抜粋

  • センサー読んで値が変わったらsocket.ioでクライアントに送る
  • クライアント側でボタン押したらLED点灯/消灯

server.js
var io = require('socket.io').listen(app);

var ArduinoFirmata = require('arduino-firmata');
arduino = new ArduinoFirmata().connect();


// センサー(A0)の値が変化していたらHTML側に送る
arduino.on('analogChange', function(e){
if(e.pin != 0) return;
console.log(e);
io.sockets.emit('analogRead', e.value);
});

io.sockets.on('connection', function(socket) {

// 初回接続してきたクライアントに最新のセンサーの値を送る
socket.emit('analogRead', arduino.analogRead(0));

// HTML側のボタンをクリックしたら、LED点灯/消灯
socket.on('digitalWrite', function(stat) {
console.log("pin13:"+stat);
arduino.digitalWrite(13, stat);
});

});