テストとかでやりたくなると思うけど、こんなくだらないことに gem とか使いたくないので、簡単に書ける方法をさがしてる。

class Foo
	def hello
		puts "Hello"
	end
end

みたいなクラスがあったとして、hello メソッドを一時的に上書きしたい (そして戻したい) とき

あるオブジェクトのメソッドだけを上書き

これは singleton class を使ったらすぐできるのでかんたん

foo = Foo.new

orig = foo.method(:hello) # 元のメソッドを呼びたいときだけ必要
foo.define_singleton_method(:hello) do
	puts "before hello"
	orig.call
	puts "after hello"
end

foo.hello #=>
# before hello
# Hello
# after hello

# 元に戻す
foo.singleton_class.send(:remove_method, :hello)

foo.hello #=>
# Hello

この方法は define_singleton_method だけでほぼ終わるし、元に戻すのも元のメソッドを保存しておく必要がないので (継承ツリーで上書きするので)、簡単

class << foo; alias ....; end

みたいなやりかたもあるけど alias でかぶったら嫌だし、alias の引数の順番覚えられないし好みではない

グローバルにあるクラスに属するメソッドを上書き

これはちょっと面倒な感じがする

# klass に定義された method メソッドをブロックで上書きする。
# ブロックには第1引数にオリジナルのメソッドが渡される
# 返り値は元に戻すための Proc インスタンス
def localize_method(klass, method, &block)
	unbound = klass.instance_method(method)
	klass.send(:define_method, method) do |*args|
		block.call(unbound.bind(self), *args)
	end
	lambda {
		klass.send(:define_method, method, unbound)
	}
end

みたいなのを定義して

foo = Foo.new
bar = Foo.new

puts "===="
foo.hello
puts "----"
bar.hello
# ====
# Hello
# ----
# Hello

release = localize_method(Foo, :hello) do |orig|
	puts "before hello"
	orig.call
	puts "after hello"
end

puts "===="
foo.hello
puts "----"
bar.hello
# ====
# before hello
# Hello
# after hello
# ----
# before hello
# Hello
# after hello

# 元に戻す
release.call

puts "===="
foo.hello
puts "----"
bar.hello
# ====
# Hello
# ----
# Hello

みたいに使えばいいのかな。もっと簡単にできたらいいけどできなそう…

  1. トップ
  2. tech
  3. Ruby でメソッドの一時的な上書き

見るたびにいちいちコードを読まないと安心して使えないライブラリなんてライブラリの価値がない。ライブラリ自体はブラックボックスになっていても、インターフェイスからその挙動を想像できるべきだ。

ライブラリを使っている側のソースを読む場合ではなく、あるライブラリを使おうとするときは、一度はライブラリの読んだほうがいいとは思うが、その目的は、そのライブラリが信頼できるかを計るためで、使いかたや、細かい挙動を確認するためではない。そして「どの程度信頼できるか」ということ、すなわちイケてるかイケてないかだけを覚えていればよい。

あるライブラリを使ったコードを読むとき、そのライブラリのソースまで毎回を読んで挙動を調べたりしなくてはいけないライブラリはインターフェイスがイケてない。ライブラリのインターフェイスが説明的になっていない。適切なインターフェイス設計がされていない。

適切なインターフェイス設計がされていなくても便利な「ライブラリ」は存在するけど、それはようするに便利なコピペ関数の寄せ集めである。あくまで書くとき便利なだけであって、読むときは害になる。

書くときに便利になるだけのものにはあまり意味がない。簡潔に書けることの目的はタイプ数を減らすことではなく、読むときに理解しやすくするためだ。

  1. トップ
  2. tech

ちょっとググってもいい感じのドキュメントが見つからず難儀したので、Perl でやるならこれで!! って感じで、コピペして使えるようなサンプルコードを書きました

社内でちょっと喋る必要があったので資料もあります (公開しても良さそうなように資料をつくってあります)

  1. トップ
  2. tech
  3. Perl で Thrift を扱うサンプルプログラム

なんかうまく AVR だと動かなかったりしたので、カっとなってテストを充実させた。(動くようになった)

とはいえ、コード上でテストだけ書いてもよくわからないので、可視化させながらやった。つまり時系列にバスの動きをシミュレーションできるようなコードを書いた。Ruby で cairo が結構かんたんに使えたので良かった。Time.now によってグラフを書いているので、多少ゆらぎがある。しかしこうすることで、意図せず状態を変えてしまっているのが一目でわかるようになったので大変役にたった。

例えば、i2cset のテストの場合

i2cget のテストの場合

見ての通り get のほうが複雑、というのも get するためにどのアドレスを get するかを書かないといけないから……

オシロスコープでの実際の波形

テストと同じデータの送受信 (対 AVR)。波形を綺麗にするために速度を 1kHz 程度に制限して (周辺処理に時間がかかるので最終的に400Hz程度だけど) sleep をちゃんと入れるようにしている。速度は下がるけど波形は綺麗になる。時間の単位さえ気にしなければ I2C 動いてる感がある。

i2cset

i2cget

AVR での repeated start condition in slave

AVR 向けだと repeated start condition を発行したとき AVR がハングする問題があって、解決方法がわからないので stop condition を常に挟むように実装を変えた。MPL115A2 だと普通に動くんだけど……

  1. トップ
  2. tech
  3. 続・GPIO (sysfs) を使ったソフトウェア I2C

普通の 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

実装

コードは以下の通りだけど、けっこう時間がかかった割にはコードサイズはそうでもない。

普通に仕様通りに実装するだけだけど、いくつか問題があった

  • sysfs 経由のピン状態書きかえが非常に遅い
    • C + sysfs だと5KHz 程度 (Raspberry Pi 上)
    • Ruby + sysfs だと 1.2kHz 程度 (Raspberry Pi 上)
  • GPIO export 直後に EACCESS (パーミッションエラー) がでる
  • GPIO direction 直後での初期状態
遅い

I2C は Standard-mode でも 100kbit/s を規定してるので、全く届いていない。ただ、I2C はマスターが常に生成するクロックラインを持っていて、クロックが High のときだけデータが有効という感じで、大抵のデバイスはデータシート上では対応クロックを 0 〜 100kHz とか 0 〜 400kHz とかで書いてあるので、動くものは動くはず…… MPL115A2 というデバイスでは動いてくれた。

もちろんマスターとしてしか動かない。また、調停とかも実装してない。

ループ展開とかいろいろやったけど、多少改善はするものの、上記の通りのスピードは超えることができないので、諦めて可読性優先で富豪的に書いてある。

CPU 特化のネイティブなライブラリを使えば当然早くはなるんだけど、今回 sysfs 経由で抽象化された gpio でなければやる意味がないなと思ってやってない。

EACCESS

GPIO export 直後の EACCESS はよくわからなくて、pi を gpio グループに入れてやってるせいかもしれない。ただ、時間が経てばアクセスできるようになるので (たぶん sysfs がバグってる感じがするって人のせい的に書いといたら、カっとなって誰か調べてくれそう)、EACCESS を rescue して retry かけている。

direction 直後の状態

direction を単純に "out" に設定すると、たぶん active_low に従って初期状態が設定されると思うんだけど、うっかり SCL ラインを High にしてしまうとその後困ったことになるので、アトミックに direction 切り替え + ピンの High 又は Low 設定を行う必要がある。

しばらくわからなかったんだけど、普通に direction に対して "high" や "low" を書きこむと、初期状態が指定した状態の "out" になるみたいだった。

  1. トップ
  2. tech
  3. GPIO (sysfs) を使ったソフトウェア I2C

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); # 見つかった場所がとれる
}
  1. トップ
  2. tech
  3. Locale::Maketext::Extract でスキャンだけする。
  1. トップ
  2. perl
  3. Locale::Maketext::Extract でスキャンだけする。

Ruby から Linux の i2c-dev 経由でデバイスを操作するライブラリを適当に書いた。

比較的汎用的な I2CDevice クラスと、サブクラスとして特定デバイス用のものをいくつかまとめた。汎用クラスはそんな重要ではなくて、特定デバイスの操作を纏めるのを主な目的とする感じ……

デバイスごとのドライバを必要になり次第書いておいていく感じにするつもりなので (そんなに増えないと思うけど)、rubygems としてはアップロードしてない。

GPIO でのインターフェイスを追加して、gem にあったほうが便利な気がしてきたので、gem push しました。https://rubygems.org/gems/i2c-devices

今のところあるのは

  • ACM1602NI
    • 秋月で売ってる I2C 16x02 液晶デバイス
  • AQM0802A
    • 秋月で売ってる I2C 8x2 液晶デバイス
  • HD44780
    • 液晶ドライバチップの HD44780 コンパチっぽいコマンドを I2C にインターフェイス変換した感じのものに使えるやつ
    • ACM1602NI, AQM0802A の親クラスとして実装してあるけど、実際は ACM1602NI 以外で動作確認はしていない
  • MPL115A2
    • 気圧計センサー
  • ADT7410
    • 高精度温度センサ (±0.5℃) データシートを元に実装しただけで、まだ動作確認してない
  1. トップ
  2. tech
  3. Ruby の I2C ライブラリ

CI とかで、あるディレクトリが毎回削除されて作りなおされ、そのディレクトリに package.json がある場合、毎回 npm install をすると大変非効率なので、別のディレクトリに node_modules ディレクトリを置いてそれを指定して使いたい。

しかし package.json に書かれた依存はそのディレクトリの node_modules ディレクトリにしか置けないようなので、以下のようにした。

  • 任意の場所に node_modules ディレクトリをつくる
  • npm install する前に、package.json があるディレクトリに ln -sf で、上記で作った node_modules ディレクトリへ symlink を貼る
  • npm install する

これで、インストール済みモジュールはそのまま使われるようになり、毎回 npm install で何もかもが install されなおされるということがなくなった。

  1. トップ
  2. tech
  3. node_modules を別の場所に置く

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 を定義してやる。

  1. トップ
  2. tech
  3. AngularJS 依存を持つ filter のテスト

アナログ回路めちゃくちゃ難しい。考える要素が多すぎる。素子の誤差が結果にどの程度影響を及ぼすかを予測するためには、理論をしっかりわかってないと難しい。そしてそれがわかっていないと、結果誤差がなぜ発生しているかの原因をさぐることができない。設計範囲内の誤差なのか、あるいは何かしら他の要素 (寄生容量など物理現象) が関係しているのか、わかってこない。

あるいはそこまでやらなくても、カットアンドトライでなんとかできるケースは多いのかもしれない。どの程度まで机上で考えるのかというのがよくわからなくて難しい。シミュレータを活用したほうがいいのかもしれない。

DHT11 という比較的価格の安い(ただし精度はいまいちな)デジタル温湿度センサーがあるので読んでみた。1-wire ライクな (1-wireではない) プロトコルで、データシートを読みながら頑張って読む感じ。

  1. 18ms 以上バスをローにすることでデータ送信を行わせる
  2. 18ms のローを送信したあと、入力状態に変えて 50us ロー + 50us ハイを待つ (初期メッセージ)
  3. そのあと 40回 50us ロー + (28us or 70us ハイ) のセットが送られてくる。70us のほうが「1」のビット

だいたいすぐできるんだけど、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 というプロトコル互換デバイスについてのものがあったのでコードほぼそのままです。

DHT22を使った温度計・湿度計の製作 - henteko.org

  1. トップ
  2. tech
  3. DHT11 という温湿度センサーを読む

概要

連続信号を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 未満であれば完全に問題にならないが、リプルの大きさと減衰傾度はトレードオフの関係にある。

回路構成などによっては妥協する必要がある。妥協点は

  • 分解能
  • 通過帯域

そして、フィルタの実現方法として

  • パッシブ型
    • CR, LCR のみ
  • アクティブ型
    • オペアンプを使う

がある。パッシブ型は減衰のみしかできない。アクティブ型は同時に増幅もでき、特性を改善できる、という特徴があるらしい。あとどうも L を含む回路は好まれないらしく (誤差が多いから?) あまり使われないみたいだ。

わからないこと

  • 応答特性
  • 位相変化特性

フィルタ特性としてこれらもでてくるけどどのように影響していくるかわかってない。

また、ADC の場合で、特に例えば後段で FFT する場合、減衰傾度をゆるやかにしてデジタルでフィルタ特性にあわせたレベル補正をかけたり、という小手先テクニックも使えそうだけど、それがうまくいくかわかっていない。

  1. トップ
  2. tech
  3. ADC とローパスフィルタについてのメモ

これを試しにブレッドボードでやってみた。

  • 上側の「T型」がローパス、下側の「T型」がハイパスフィルタとして働き、ツインT型部分でバンドリジェクトフィルタを構成する?
  • 上側は位相が遅れ、下側は位相が進む
  • トランジスタのコレクタ側から負帰還させて発振させてる

定数決定がむずかしそう。いまいちよくわからない。

Wikipedia によると

R1 = R2, C2 = C3, R3 = R1 / x, C1 = C2 * x で x 倍以上増幅すれば発振器になるとのことらしい。このとき、周波数は以下の式

手元のやつでやって実際計ってみた。電源電圧は 3V ぐらい。電圧をあげると周波数もあがるっぽい…

R1=10k, C2=0.012u

C1 は 0.1u

R = 10e3; C = 0.012e-6; 1 / (2 * Math.PI * R * C )
//=> 1326.2911924324612

実際は 1650Hz ぐらいで発振してる。このぐらい誤差なのかがよくわからない。

R1=22k, C2=0.012u

C1 は 0.1u

R = 22e3; C = 0.012e-6; 1 / (2 * Math.PI * R * C )
//=> 602.859632923846

しかし発振せず。

R1=10k, C2=0.027u

R = 10e3; C = 0.027e-6; 1 / (2 * Math.PI * R * C )
//=> 589.4627521922049

786Hz

R1=10k, C2=0.1u

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 付近に発振周波数がさがる。

  1. トップ
  2. tech
  3. ツインT型発振回路
  1. トップ
  2. ham
  3. ツインT型発振回路
  1. トップ
  2. 発振回路
  3. ツインT型発振回路