ちょっとググってもいい感じのドキュメントが見つからず難儀したので、Perl でやるならこれで!! って感じで、コピペして使えるようなサンプルコードを書きました
社内でちょっと喋る必要があったので資料もあります (公開しても良さそうなように資料をつくってあります)
ちょっとググってもいい感じのドキュメントが見つからず難儀したので、Perl でやるならこれで!! って感じで、コピペして使えるようなサンプルコードを書きました
社内でちょっと喋る必要があったので資料もあります (公開しても良さそうなように資料をつくってあります)
なんかうまく AVR だと動かなかったりしたので、カっとなってテストを充実させた。(動くようになった)
とはいえ、コード上でテストだけ書いてもよくわからないので、可視化させながらやった。つまり時系列にバスの動きをシミュレーションできるようなコードを書いた。Ruby で cairo が結構かんたんに使えたので良かった。Time.now によってグラフを書いているので、多少ゆらぎがある。しかしこうすることで、意図せず状態を変えてしまっているのが一目でわかるようになったので大変役にたった。
例えば、i2cset のテストの場合
i2cget のテストの場合
見ての通り get のほうが複雑、というのも get するためにどのアドレスを get するかを書かないといけないから……
テストと同じデータの送受信 (対 AVR)。波形を綺麗にするために速度を 1kHz 程度に制限して (周辺処理に時間がかかるので最終的に400Hz程度だけど) sleep をちゃんと入れるようにしている。速度は下がるけど波形は綺麗になる。時間の単位さえ気にしなければ I2C 動いてる感がある。
AVR 向けだと repeated start condition を発行したとき AVR がハングする問題があって、解決方法がわからないので stop condition を常に挟むように実装を変えた。MPL115A2 だと普通に動くんだけど……
普通の GPIO 経由で I2C できたらなんとなく嬉しいかなと思って作ってみた。つまりソフトウェアでピン状態をいじって I2C 通信するというもので、いわゆる bit banging というやつです。
[tech] Ruby の I2C ライブラリ | Sun, Feb 16. 2014 - 氾濫原 で I2C ライブラリを書いたけど、これは i2c-dev という Linux 組込みのドライバを使ったもので、ハードウェアが持ってる I2C の機能を使うので、もしハードウェアに I2C がない場合 (普通あるけど) で、GPIO はあるという場合に使えることがあるかもしれません。
最終的には以下のような感じで使えるようになった。もちろん Raspberry Pi には I2C ついてるのでこんなことする必要は全くない。
require "i2c/device/mpl115a2"
require "i2c/driver/gpio"
mpl = MPL115A2.new(address: 0x60, driver: I2CDevice::Driver::GPIO.new(
sda: 23, # pin 16 in raspberry pi
scl: 24, # pin 18 in raspberry pi
))
p mpl.calculate_hPa コードは以下の通りだけど、けっこう時間がかかった割にはコードサイズはそうでもない。
普通に仕様通りに実装するだけだけど、いくつか問題があった
I2C は Standard-mode でも 100kbit/s を規定してるので、全く届いていない。ただ、I2C はマスターが常に生成するクロックラインを持っていて、クロックが High のときだけデータが有効という感じで、大抵のデバイスはデータシート上では対応クロックを 0 〜 100kHz とか 0 〜 400kHz とかで書いてあるので、動くものは動くはず…… MPL115A2 というデバイスでは動いてくれた。
もちろんマスターとしてしか動かない。また、調停とかも実装してない。
ループ展開とかいろいろやったけど、多少改善はするものの、上記の通りのスピードは超えることができないので、諦めて可読性優先で富豪的に書いてある。
CPU 特化のネイティブなライブラリを使えば当然早くはなるんだけど、今回 sysfs 経由で抽象化された gpio でなければやる意味がないなと思ってやってない。
GPIO export 直後の EACCESS はよくわからなくて、pi を gpio グループに入れてやってるせいかもしれない。ただ、時間が経てばアクセスできるようになるので (たぶん sysfs がバグってる感じがするって人のせい的に書いといたら、カっとなって誰か調べてくれそう)、EACCESS を rescue して retry かけている。
direction を単純に "out" に設定すると、たぶん active_low に従って初期状態が設定されると思うんだけど、うっかり SCL ラインを High にしてしまうとその後困ったことになるので、アトミックに direction 切り替え + ピンの High 又は Low 設定を行う必要がある。
しばらくわからなかったんだけど、普通に direction に対して "high" や "low" を書きこむと、初期状態が指定した状態の "out" になるみたいだった。
Locale::Maketext::Extract::Plugin 以下にはいろいろ対応してるフォーマットがあったりする。まぁ大抵一緒なので頑張って使う必要もないけど、これらを使って .po ファイルを作らないまでも、msgid の抽出だけ行いたいという場合があったりします。そんなときは直接 LME インスタンスを作って extract_file をかけて compile すれば、とれるようになるみたいです。
use Locale::Maketext::Extract;
use Locale::Maketext::Extract::Plugin::Xslate;
use File::Zglob;
my $lme = Locale::Maketext::Extract->new(
plugins => {
perl => [ 'pm' ],
xslate => {
syntax => 'TTerse',
extensions => [qw/ tt /],
},
generic => [ 'js' ]
},
warnings => 1,
verbose => 0,
);
for (zglob('lib/**/*.pm'), zglob('template/**/*.tt'), zglob('static/**/*.js')) {
$lme->extract_file($_);
}
$lme->compile(1); # これをしないと msgids がとれない
for my $msgid ($lme->msgids) {
say $msgid;
$lme->msg_positions($msgid); # 見つかった場所がとれる
}
Ruby から Linux の i2c-dev 経由でデバイスを操作するライブラリを適当に書いた。
比較的汎用的な I2CDevice クラスと、サブクラスとして特定デバイス用のものをいくつかまとめた。汎用クラスはそんな重要ではなくて、特定デバイスの操作を纏めるのを主な目的とする感じ……
デバイスごとのドライバを必要になり次第書いておいていく感じにするつもりなので (そんなに増えないと思うけど)、rubygems としてはアップロードしてない。
GPIO でのインターフェイスを追加して、gem にあったほうが便利な気がしてきたので、gem push しました。https://rubygems.org/gems/i2c-devices
今のところあるのは
CI とかで、あるディレクトリが毎回削除されて作りなおされ、そのディレクトリに package.json がある場合、毎回 npm install をすると大変非効率なので、別のディレクトリに node_modules ディレクトリを置いてそれを指定して使いたい。
しかし package.json に書かれた依存はそのディレクトリの node_modules ディレクトリにしか置けないようなので、以下のようにした。
これで、インストール済みモジュールはそのまま使われるようになり、毎回 npm install で何もかもが install されなおされるということがなくなった。
myApp = angular.module('myApp', [ 'myApp.filters', 'myApp.services' ]);
myApp.services = angular.module('myApp.services', []);
myApp.services.factory('loc', function () {
return Location.parse(location.href);
});
myApp.filters = angular.module('myApp.filters', []);
myApp.filters.filter('page', function (loc) {
return function (page) {
return loc.params(angular.extend(loc.params(), { page : page })).href;
};
}); services と filters のモジュールをわけて以上のようなのを実装した場合で、page filter をテストしたい場合、以下のようにするとエラーになる。
describe('filter', function () {
beforeEach(module('myApp.filters'));
describe('page', function () {
it('should append page param', function () {
inject(function (pageFilter) {
expect(pageFilter(1)).toBe('foobar');
});
});
});
});
//=> Error: [$injector:unpr] Unknown provider: locProvider <- loc <- pageFilter loc の定義が別モジュールで、beforeEach で該当モジュールを読みこんでいないからだけど、そもそも DI で loc を注入してテストしたいところだと思う。
とりあえず以下のようにしたうまくいった。
describe('filter', function () {
beforeEach(module('myApp.filters'));
describe('page', function () {
it('should append page param', function () {
module(function ($provide) {
$provide.factory('loc', function() {
return Location.parse('http://localhost/foo');
});
});
inject(function (pageFilter) {
expect(pageFilter(1)).toBe('http://localhost/foo?page=1');
});
});
it('should append page param', function () {
module(function ($provide) {
$provide.factory('loc', function() {
return Location.parse('http://localhost/foo?foo=bar');
});
});
inject(function (pageFilter) {
expect(pageFilter(1)).toBe('http://localhost/foo?foo=bar&page=1');
});
});
});
}); $provide でテスト用の loc factory を定義してやる。
アナログ回路めちゃくちゃ難しい。考える要素が多すぎる。素子の誤差が結果にどの程度影響を及ぼすかを予測するためには、理論をしっかりわかってないと難しい。そしてそれがわかっていないと、結果誤差がなぜ発生しているかの原因をさぐることができない。設計範囲内の誤差なのか、あるいは何かしら他の要素 (寄生容量など物理現象) が関係しているのか、わかってこない。
あるいはそこまでやらなくても、カットアンドトライでなんとかできるケースは多いのかもしれない。どの程度まで机上で考えるのかというのがよくわからなくて難しい。シミュレータを活用したほうがいいのかもしれない。
DHT11 という比較的価格の安い(ただし精度はいまいちな)デジタル温湿度センサーがあるので読んでみた。1-wire ライクな (1-wireではない) プロトコルで、データシートを読みながら頑張って読む感じ。
だいたいすぐできるんだけど、waiting for slope up と書いてあるところのディレイを入れないと、バスがハイになる前に loop_until_bit_is_clear してしまって状態がズレてしまうので、立ちあがる時間を適当に待つ必要があった。
湿度・温度それぞれ16bitが送られてくるんだけど、下位8bitは、このモジュールの場合、どちらも全部0しか返ってこない。のでコードでは無視している。下位 8bit はAM2302とか精度が良いがコストが高いというモジュールがあって、そちらの場合使われるっぽい。
あと、送られてくるデータは前回測定時のデータなので、2回読み出しを行って2回目を採用しないと、正しいデータにならない。連続して読むなら関係ない。
int main(void) {
logger_init(9600);
setup_io();
printf("initializing...\r\n");
sei();
printf("initialized\r\n");
uint8_t i, j, us;
int16_t humidity, temperature;
uint8_t res[5];
set_input(DDRD, PD7); clear_bit(PORTD, PD7); // hi-z
for (;;) {
set_output(DDRD, PD7); clear_bit(PORTD, PD7);
_delay_ms(20);
set_input(DDRD, PD7); // hi-z
// waiting for slope up
_delay_us(5);
// waiting for slave initial response
loop_until_bit_is_clear(PIND, PD7);
loop_until_bit_is_set(PIND, PD7);
loop_until_bit_is_clear(PIND, PD7);
for (i = 0; i < 5; i++) {
for (j = 0; j < 8; j++) {
us = 0;
// 50us
loop_until_bit_is_set(PIND, PD7);
while (bit_is_set(PIND, PD7)) {
us++;
_delay_us(1);
}
res[i] <<= 1;
if (us > 50) { // 0: <28us 1: <70us
res[i] |= 1;
}
}
}
printf("%02x %02x %02x %02x %02x\r\n", res[0], res[1], res[2], res[3], res[4]);
if (( (res[0] + res[1] + res[2] + res[3]) & 0xff) == res[4]) {
humidity = res[0];
temperature = res[2];
printf("%d%%RH %dC\r\n", humidity, temperature);
} else {
printf("invalid data\r\n");
}
_delay_ms(5000);
}
} 最初の長いロー部分が、AVR 側からのローで 18ms ある。その後、ちょっとハイになってるところは、センサー待ちの部分、そして 50us ロー・50us ハイに続いてデータが流れてくる。目で見てビット読める感じでたのしい。
これは 18ms 待ちあとで AVR 側を入力に切り替えてハイインピーダンス状態にした直後の立ちあがり。立ちあがりにちょっとかかっていて、その後11usぐらい待ち時間がある。
DHT22 というプロトコル互換デバイスについてのものがあったのでコードほぼそのままです。
連続信号をADCでサンプリングする場合、前段にローパスフィルタが必要だが、それをどう設計したらいいかわからない、という話。
ADC する場合、サンプリング周波数の2分の1 (ナイキスト周波数) 以上の周波数はローパスで完全に遮断する必要がある。さもなければ折り返し雑音が入り、これは後段では正常なデータと区別がつかないので対処しようがない。
ある周波数で「遮断」するというのは、その周波数において、分解能ないしノイズレベル未満まで減衰するということ。最大 1V の信号を 10bit ADC (1024段階)する場合、0.97mV 未満であれば分解能未満になる。この場合 20 * Math.log10(1024) = 60.2dB 減衰すれば、遮断したといえる。
そしてこの場合、サンプリング周波数が 10kHz であれば、5kHz 時点で 60.2dB 減衰させるようにフィルタを設計する。次に、どの程度まで完全に通過させるかでフィルタの性能 (次数) が決定する。当然通過帯域がナイキスト周波数に近ければ近いほどADCの性能を生かせるが、そのためには性能の良いフィルタが必要になる。
フィルタの特性にはいくつか種類がある。(同じぐらいの回路規模での比較)
ローパスフィルタの次数はそのまま減衰傾度になる。1次のバターワースフィルタは周波数が倍で出力が半減 (=-6dB/octave)。次数が高いほど減衰が強くなる (2次=-12dB/octave, 3次=-18dB/octave)。高い性能のフィルタが必要な場合は高次のフィルタ (部品点数の増加) が必要になる。
ADCの前段に入れる場合、できる限り急激な減衰が欲しくなるので、まずチェビシェフを検討することになる。そしてチェビシェフ特性の場合、リプルがどの程度発生するかというのも設計で考慮する必要がある。もしリプルが分解能ないしノイズレベル未満にできれば、それは問題にはならない。
10bitの場合リプルは 20 * Math.log10(1 / (1 - 1/1024.0)) = 0.0085dB 未満であれば完全に問題にならないが、リプルの大きさと減衰傾度はトレードオフの関係にある。
回路構成などによっては妥協する必要がある。妥協点は
そして、フィルタの実現方法として
がある。パッシブ型は減衰のみしかできない。アクティブ型は同時に増幅もでき、特性を改善できる、という特徴があるらしい。あとどうも L を含む回路は好まれないらしく (誤差が多いから?) あまり使われないみたいだ。
フィルタ特性としてこれらもでてくるけどどのように影響していくるかわかってない。
また、ADC の場合で、特に例えば後段で FFT する場合、減衰傾度をゆるやかにしてデジタルでフィルタ特性にあわせたレベル補正をかけたり、という小手先テクニックも使えそうだけど、それがうまくいくかわかっていない。
というのがウェブ上で使える。TI のやつのほうが応用範囲が広い。
これを試しにブレッドボードでやってみた。
定数決定がむずかしそう。いまいちよくわからない。
Wikipedia によると
R1 = R2, C2 = C3, R3 = R1 / x, C1 = C2 * x で x 倍以上増幅すれば発振器になるとのことらしい。このとき、周波数は以下の式
手元のやつでやって実際計ってみた。電源電圧は 3V ぐらい。電圧をあげると周波数もあがるっぽい…
C1 は 0.1u
R = 10e3; C = 0.012e-6; 1 / (2 * Math.PI * R * C ) //=> 1326.2911924324612
実際は 1650Hz ぐらいで発振してる。このぐらい誤差なのかがよくわからない。
C1 は 0.1u
R = 22e3; C = 0.012e-6; 1 / (2 * Math.PI * R * C ) //=> 602.859632923846
しかし発振せず。
R = 10e3; C = 0.027e-6; 1 / (2 * Math.PI * R * C ) //=> 589.4627521922049
786Hz
C1 も C2 も 0.1u
R = 10e3; C = 0.1e-6; 1 / (2 * Math.PI * R * C ) 159.15494309189535
244Hz
この状態で C1 に 0.1u 並列でつないで 0.2u にすると、210Hz 付近に発振周波数がさがる。
現状、あんまり効率的な開発手法みたいなことに興味が沸いていない。自分が楽できるように効率的にすることには興味があるけど、チーム的にこう進めたほうがいいみたいなことには興味がない。自分でいくらそれを考えても、どうも意味がないようだという感じだからだと思う。
Raspberry Pi を中心にやってみようとしているところ | avr | raspberrypi | ham - 氾濫原 を実現しようとケース加工して、ひとまず収めた。ただしもやもやと考えていた以下の機能を実現せず、基本機能に絞って実装した。
どのぐらいの大きさのケースが必要か? というレベルから検討が必要だったので、Raspberry Pi 及び周辺回路のくみあがりをおおよそで SketchUp でモデリングして配置を検討した。
これはそこそこ意味があったと思う。組みあがった基板をちゃんと計ってフロントパネルの穴開け部分を事前に書いたりしてる。
一部ブレッドボードになっていた部分をユニバーサル基板に全ての機能を実装しなおした。一発で動かなかったけど回路図を間違えて作っていて、回路図を間違えていると実体配線図も自動的に間違えるので残念な感じの間違いになっていた……
モデリングしたデータを等倍印刷して、普通の紙用のスティック糊ではりつけて、穴開けをした。
ボタンとLEDあたりが一番位置決めがシビアでうまくいくか不安だったけど、モデリングして印刷作戦がうまくいってピッタリにできた。
四角の穴をあけるのも初めてだったので不安だったけどなんとかあけられた。
ぴったりサイズの線ではなく、1m ぐらい大きい枠線を書くべきだったのが反省点だった。しかし四角の穴あけるの大変すぎて二度とやりたくない感が強い。
裏側は Raspberry Pi の USB 端子を出す穴 (Wi-Fi があるので外に出す必要がある) と、RS-232 コネクタを取り付ける穴、DC ジャックの穴が主で、Raspberry Pi の USB 端子の穴だけ、位置が決まっているので気をつける必要があった。結果的にはうまくいったけど、取り付け穴がぎりぎりの位置になってしまって作業しにくかった。
RS-232 コネクタは最終的に穴の形がわからなくなるので適当にあけた。
液晶の表示内容は考え中で、ソフトウェア側は (だいたいできてはいるけど) ちょいちょいいい感じにする。