[avr][raspberrypi][ham] Raspberry Pi を中心にやってみようとしているところ | Mon, Jan 20. 2014 - 氾濫原 について、まず電源をつくった。

手元にサンハヤトのICB-90という基板がたくさんあるので、これに実装した。

とりあえずこれで Raspberry Pi を動かしてみたけど、スイッチング部分はちょっとあたたかいぐらいだった。

Eagle で配置を検討したら Print を Mirror とUpside down にチェックをいれて行うと裏面から見た感じがつくれるので配線が楽だった。あとなんか単位 (inch, mm) を切り替えながらやっていたらグリッドからずれることがあって困ったけど、これは Cmd を押しながら移動を行うことでグリッドにフィットするようだった。

  1. トップ
  2. tech
  3. 12V, 5V, 3.3V 電源
  1. トップ
  2. ham
  3. 12V, 5V, 3.3V 電源

土曜日午後から頭痛がひどくなり寝た。翌日日曜日も頭痛がひどく、体温を計ると37度台後半でずっと寝てた。月曜日もまだ微熱で頭痛で会社休んで寝てた。

  1. トップ
  2. 体調不良
  3. 風邪

電子部品の足の間隔とかは 0.1inch == 100mil で作られている。ユニバーサル基板のピッチが 0.1inch == 100mil。mil は1000分の1 inch

  1. トップ
  2. tech
  3. mil

アルゴリズムを原理から実装するみたいなのが相変らずできないわけだけど、そうでないものなら比較的すぐに実装を書けるようになってはきているので、普段の生活でちょっとしたハックみたいなのはコストパフォーマンスが非常に高く、やっていて面白い。

なんとなく以下らへんのスキルを Lv 7/10 ぐらい (Lv5 でギリギリ実用、みたいな) まで上げれたら上から下までできます!!って言えそうだけど、だいぶ遠く感じる。書いたレベルは現状の甘めの自己評価です。スキルポイントが足りてないんで、スキルシミュレーターとかが欲しいですね。

  • ウェブのフロントエンド (JS, HTML, CSS)
    • Lv7
  • ウェブのサーバーサイド (Ruby, Perl, JS)
    • Lv7
  • サーバーミドルウェア (httpd, memcached etc...)
    • Lv5
  • ネットワーク (ルーティングとか、ネットワーク冗長性とか)
    • Lv3
  • OS (マルチタスクとかメモリ管理とか)
    • Lv2
  • ハードウェアドライバ (OS 上で USB のドライバを自力で書くとか)
    • Lv2
  • ハードウェア組み込み (OS なしでハードウェアIOを叩くとか)
    • Lv2
  • 電子回路 (特にデジタル回路・デジタル回路実装に必要なアナログ回路の知識)
    • Lv2

ネットワークとかミドルウェアあたりは、会社にいると専門の人がいるから自分でほぼやることはなくて、あんまりスキルあがっていかない。それと独学だと解決すべき大きな問題が発生しないので、スキル上げしにくい。パーティプレイ必須はぼっちプレイヤーにはつらい。

ハードウェア以下の低いレイヤーは、全く仕事の役には立たないけどモチベーションは比較的高く、解決したいちょうどいい問題が結構ころがっているのでソロプレイでもスキル上げしやすい感じがする。ただ、一定以上からパーティプレイ必須で、ソロだと決して上がらなそう。

ウェブよりのレイヤーはソロプレイでスキル上げしやすい。わからないことは結局ソース見たら解決するし、バグってるの見つけたら pull-req する、みたいなのでレベルが上がっていく。

なので、ソロで上げれる限界的には Lv10, Lv10, Lv7, Lv5, Lv6, Lv5, Lv5, Lv6, Lv7 みたいな感じになりそう。

現状、あんまり効率的な開発手法みたいなことに興味が沸いていない。自分が楽できるように効率的にすることには興味があるけど、チーム的にこう進めたほうがいいみたいなことには興味がない。自分でいくらそれを考えても、どうも意味がないようだという感じだからだと思う。

[avr][raspberrypi][ham] Raspberry Pi を中心にやってみようとしているところ | Mon, Jan 20. 2014 - 氾濫原 を実現しようとケース加工して、ひとまず収めた。ただしもやもやと考えていた以下の機能を実現せず、基本機能に絞って実装した。

  • センサー類
    • 外に出すべきか悩んで結局やめた
  • 無線機の操作性改善的なこと
    • たとえば width を変えられるボタンをつけるとか

モデリング

どのぐらいの大きさのケースが必要か? というレベルから検討が必要だったので、Raspberry Pi 及び周辺回路のくみあがりをおおよそで SketchUp でモデリングして配置を検討した。

これはそこそこ意味があったと思う。組みあがった基板をちゃんと計ってフロントパネルの穴開け部分を事前に書いたりしてる。

回路全体を実装

一部ブレッドボードになっていた部分をユニバーサル基板に全ての機能を実装しなおした。一発で動かなかったけど回路図を間違えて作っていて、回路図を間違えていると実体配線図も自動的に間違えるので残念な感じの間違いになっていた……

ケース加工

モデリングしたデータを等倍印刷して、普通の紙用のスティック糊ではりつけて、穴開けをした。

フロント

ボタンとLEDあたりが一番位置決めがシビアでうまくいくか不安だったけど、モデリングして印刷作戦がうまくいってピッタリにできた。

四角の穴をあけるのも初めてだったので不安だったけどなんとかあけられた。



ぴったりサイズの線ではなく、1m ぐらい大きい枠線を書くべきだったのが反省点だった。しかし四角の穴あけるの大変すぎて二度とやりたくない感が強い。

バック

裏側は Raspberry Pi の USB 端子を出す穴 (Wi-Fi があるので外に出す必要がある) と、RS-232 コネクタを取り付ける穴、DC ジャックの穴が主で、Raspberry Pi の USB 端子の穴だけ、位置が決まっているので気をつける必要があった。結果的にはうまくいったけど、取り付け穴がぎりぎりの位置になってしまって作業しにくかった。

RS-232 コネクタは最終的に穴の形がわからなくなるので適当にあけた。

完成

液晶の表示内容は考え中で、ソフトウェア側は (だいたいできてはいるけど) ちょいちょいいい感じにする。

  1. トップ
  2. tech
  3. ケース加工してハードウェア部分をフィックス
  1. トップ
  2. ham
  3. ケース加工してハードウェア部分をフィックス

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

  • 上側の「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型発振回路

概要

連続信号を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 とローパスフィルタについてのメモ

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 という温湿度センサーを読む

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

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

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 のテスト

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 を別の場所に置く

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 ライブラリ

普通の 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 でスキャンだけする。

ちょっとググってもいい感じのドキュメントが見つからず難儀したので、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

テストとかでやりたくなると思うけど、こんなくだらないことに 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

Guard オブジェクト

Perl だと Guard オブジェクトとかいうハックがあって、スコープを出るタイミングで必ず呼ばれるファイナライザを使って、あるスコープでだけ有効な処理を書けたりします。

例えば、DB のトランザクションや、あるいは以下のように依存するプロセスをあるスコープでだけ起動して終了するような用途で使われています。

{
    my $guard = Proc::Guard->new(command => [ "memcached", "-p", "12321" ]);
    # do something ...
};

# memcached has been killed

適当なメソッドにブロック(サブルーチン)を渡せばええやん、という気もしますし、実際 Ruby の transaction の場合そういう感じになります (Perl でももちろん同じようなサブルーチンを書くことはできます)。

ActiveRecord::Base.transaction do
    ....
end

しかし Perl の Guard オブジェクトと同じようなことをしたい! と思うと、Ruby の場合 finalizer の呼ばれるタイミングが GC が動いたタイミングなので、finalizer は使えません。

なので、多少妥協する必要があります。

複数のブロックのネストをフラットにする

上記のように依存プロセスを起動して勝手に終了してほしい、みたいな場合、ドンドコ他のプロセスも増やしていきたくなります。Ruby で書くなら以下のような感じでしょうか (実際このケースだと process メソッドなんて作らず単に begin / ensure / end を纏めて書いちゃうと思いますけど)

def process(cms, &block)
    fork { exec *cmd }
    yield
ensure
   Process.kill(:INT, pid)
end

process( [ "memcached", "-p", "12321" ]) do
    process(["foo"]) do
        process(["bar"]) do
             ....
        end
    end
end

ドンドコ作るとドンドコネストしまくっていきます。しかし、1個増えるたびにいちいち中身全部インデントしなおしていったらダルくて嫌な感じです。

Enumerator を使う

ブロックを持つ process をフラットにしようとしてみます、とりあえず Enumerator を使うのがお手軽そうです。

begin
    e1 = to_enum(:process,  [ "memcached", "-p", "12321" ])
    e2 = to_enum(:process, ["foo"])
    e3 = to_enum(:process, ["bar"])
    e1.next; e2.next; e3.next

    ....
ensure
    [e1, e2, e3].each do |e|
        begin
        e.next
        rescue StopIteration
        end
    end
end

フラットにはなりましたが、何をやっているのか大変謎になりました。イテレーション (繰替えし) してないのに next とか StopIteration とか出てきて意味不明ですし、処理が上下にちらばっているのが嫌な感じです。これならネストが増えるほうがマシな感じがします。

Enumerator をラップする

そこで Enumerator を適当にラップしてみます。以下のようなメソッドを定義します。

def guard_scope(&block)
	context = Object.new

	enums = []

	context.define_singleton_method(:guard) do |obj, name, *args|
		enum = obj.to_enum(name, *args)
		enums.unshift(enum)
		enum.next
	end

	ret = context.instance_eval(&block)

	enums.each do |enum|
		enum.next rescue StopIteration
	end

	ret
end

そして使う場合

guard_scope do
	g1 = guard(Kernel, :process,  [ "memcached", "-p", "12321" ])
	g2 = guard(Kernel, :process, ["foo"])
	g3 = guard(Kernel, :process, ["bar"])

	p :in_block
end

これでかなりマシになった気がします。

(ただ、これだと instance_eval を使っている関係でインスタンス変数のスコープが変わるのでよくありません)

継続を使う

Enumerator をラップすれば十分そうですが、どうも自分はメソッドの名前を Symbol にして渡して呼ぶのが好みではないので、以下のように書けたらいいなと思いました。

guard_scope do
	g1 = guard { process([ "memcached", "-p", "12321" ], &block)  }
	g2 = guard { process([ "foo" ], &block)  }
	g3 = guard { process([ "bar" ], &block)  }

	p :in_block
end

こうなると、Enumerator を使いにくいのでちょっと面倒です。継続を使って実装することにしました。spec コード付き

require 'continuation'
 
class Guard
	attr_reader :args
	attr_reader :called
	attr_reader :result
 
	def initialize(&before_finish)
		@args         = nil
		@guard_cc     = nil
		@block_cc     = nil
		@finish_cc     = nil
		@before_finish = before_finish
	end
 
	def finish(result=nil)
		@before_finish.call(self)
		cc = callcc {|cc| cc }
		if cc.is_a? Continuation
			@finish_cc = cc
			@result = result
			@called = true
			@block_cc.call(@result) # return finish block
		else
			cc
		end
	end
 
	private
 
	def return_guard
		@guard_cc.call(self)
	end
 
	def return_finish(ret)
		@finish_cc.call(ret)
	end
end
 
def guard_scope(&block)
	context = self
 
	guards = []
 
	current_guard = nil
 
	orig_guard = begin context.singleton_class.method(:guard) rescue NameError; nil end
	orig_block = begin context.singleton_class.method(:block) rescue NameError; nil end
 
	context.define_singleton_method(:guard) do |&b|
		cc = callcc {|cc| cc }
		if cc.is_a? Continuation
			current_guard = Guard.new do |g|
				current_guard = g
			end
			current_guard.instance_variable_set(:@guard_cc, cc)
 
			# Expect &block syntax and 
			# A method in block call block's lambda to get args
			# and return guard
			ret = b.call
 
			if current_guard.instance_variable_get(:@block_cc)
				# After finish of a guard
				# current_guard is set by finish (block passed to Guard.new)
				g = current_guard
				current_guard = nil
				g.send(:return_finish, ret)
			else
				raise "guard {} without calling &block"
			end
		else
			current_guard = nil
			cc
		end
	end
 
	context.define_singleton_method(:block) do
		count = 0
		lambda {|*args|
			raise "block is called more than once" if count > 0
			count += 1
			cc = callcc {|c| c }
			if cc.is_a? Continuation
				current_guard.instance_variable_set(:@args, args)
				current_guard.instance_variable_set(:@block_cc, cc)
				guards.unshift(current_guard)
				current_guard.send(:return_guard)
			else
				cc
			end
		}
	end
 
	block_value = context.instance_eval(&block)
 
	if orig_guard
		context.define_singleton_method(:guard, &orig_guard)
	else
		context.singleton_class.send(:remove_method, :guard)
	end
 
	if orig_block
		context.define_singleton_method(:block, &orig_block)
	else
		context.singleton_class.send(:remove_method, :block)
	end
 
	guards.each do |g|
		g.finish(nil) unless g.called
	end
 
	block_value
end

なんかだいぶ長くなりましたが、とりあえずこれで動くようになりました。

やってることはただの辻褄あわせで、一度ブロック付きで普通に呼んで、すぐ継続を作って guard {} を返してから、最後にまた保存した継続を実行しているだけです。

(この例は instance_eval を使わず、呼び出し元 self に一時的にメソッドを生やしているのでインスタンス変数のスコープが変わったりしないようになっています)

set_trace_func を使ったメソッド単位の Guard

set_trace_func を使えばメソッド単位の突入・脱出はとれるので、それで Scope::Guard 的に、先にブロックを登録しておくとスコープをはずれるときに自動実行するのを実装してみました。インターフェイス的には以下のようになります。

instance_eval do # 任意の適当なメソッド
    guard { p "called as leaving this method 1" }
    guard { p "called as leaving this method 2" }
end
module Guard
	@@guards = []
	def guard(&block)
		set_trace_func(lambda {|event, file, line, id, binding, klass|
			# p [event, file, line, id, binding, klass]
			case event
			when "call", "c-call"
				for g in @@guards
					g[:count] += 1
				end
			when "return", "c-return"
				out_of_scope = @@guards.each {|g|
					g[:count] -= 1
				}.select {|g| g[:count] < 0 }
 
				@@guards -= out_of_scope
 
				for g in out_of_scope
					g[:block].call
				end
				set_trace_func(nil) if @@guards.empty? && id != :set_trace_func
			end
		}) if @@guards.empty?
		@@guards << {
			block: block,
			count: 1
		}
	end
end
 
include Guard
 
require "rspec"
RSpec::Core::Runner.autorun
 
describe Guard do
	before do
		@events = []
	end
 
	it "should execute guard block as it is out of scope" do
		instance_eval do # new scope
			@events << :enter
			guard {  @events << :end1 }
			guard {  @events << :end2 }
			@events << :leave
		end
		expect(@events).to eq([
			:enter,
			:leave,
			:end1,
			:end2
		])
	end
 
	it "should works correctly with nested scope" do
		instance_eval do # new scope
			@events << :enter1
			guard {  @events << :end1_1 }
			guard {  @events << :end1_2 }
			instance_eval do
				@events << :enter2
				guard {  @events << :end2_1 }
				guard {  @events << :end2_2 }
				@events << :leave2
			end
			@events << :leave1
		end
		expect(@events).to eq([
			:enter1,
			:enter2,
			:leave2,
			:end2_1,
			:end2_2,
			:leave1,
			:end1_1,
			:end1_2,
		])
	end
end

割とシンプルに書けるんですが、set_trace_func が1個しか登録できないので、他のところで登録されると壊れるのと、スコープ単位ではなくメソッド単位にしかならないのが微妙なところです。良いところは、任意のメソッド内でいきなり guard を呼べるところで、これのためにブロックをつくる必要がない。

Scope::Guard 的なのの一番シンプルな実装

単に Scope::Guard 的に先にブロックを出たときの処理を登録するだけなら、もっと簡単に以下のように書けます。

イテレータ的なメソッドでなければ、ブロック引数をとる形式以外に、File.open のようにブロックを与えないときは自分で close するメソッドも提供されているので、大抵の場合はこれでもよさそうな気がします。

def guard_scope(&block)
	context = Object.new
	guards = []
	context.define_singleton_method(:guard) do |&b|
		guards << b
	end

	context.instance_eval(&block)
ensure
	guards.each {|g| g.call }
end

guard_scope do
	p :enter
	guard { p :end1_1 }
	guard { p :end1_2 }
	guard_scope do
		p :enter
		guard { p :end2_1 }
		guard { p :end2_2 }
		p :leave
	end
	p :leave
end

まとめ・感想

Guard オブジェクトのやりかたは無闇にインデントが増えず、なおかつ関連する処理を近くに配置して確実なコードを書けます。そんなこんなで自分は好きなのですが、簡単にできなそうなので、なんとかしていい感じにやる方法を考えてみました。

基本は Enumerator をラップして使うのがよさそう、あるいはもっと別の方法を考えたほうがよさそうです。継続を使う方法が一番カッコいいと思いますが、予期せぬことがありそうなので、できれば組込みクラスでやったほうがいいですね。なにかもっといい方法があれば教えてください。

  1. トップ
  2. tech
  3. Ruby でスコープをはずれたときに自動で何かをする

bare metal という言葉を最近知って、おもしろそうだなあと思ったので Raspberry Pi 上で試してみた。bare metal とは OS レスで直接動かす (つまりリッチなマイコン的に使う) ことのようです。bare metal で動かすだけだと目標が低い感じなので、まず mruby を動かすところまでやろう、という感じです。

yamanekko さんというかたが既にやって日本語で書いているので、それを見てコピペしながらやってみつつという感じです。ちょいちょいうまくいかなかったのでかなりハマった……

ARM クロスコンパイル環境の構築

https://launchpad.net/gcc-arm-embedded にあるバイナリをインストールするのが一番良い。これを入れれば以下にあるような fpu 問題にも悩まない。

大変めんどう。ググるといろいろでてくるけど、今流行りの全部うまいことやってくれる感じのスクリプトがあったので試してみた。

https://github.com/jsnyder/arm-eabi-toolchain

git clone git@github.com:jsnyder/arm-eabi-toolchain.git
cd arm-eabi-toolchain
make install-cross

大変時間がかかる。Makefile を開いて、--enable-languages="c,c++" となってるところから c++ を消したほうがいいっぽい。使わないし…

入るライブラリ

  • arm-none-eabi-
    • gcc
    • binutils (objdump とか)
    • gdb
  • GMP
    • 数値演算ライブラリ
  • MPFR
    • 浮動小数点ライブラリ
  • MPC
    • MPFR ベースのさらに複雑な浮動小数点ライブラリ
  • newlib
    • 組み込み環境用のライブラリ。 libc とかを提供

少し古いバージョンが入る

FPU 問題

最初

arm-none-eabi-ld: error: main.elf uses VFP register arguments, /Users/cho45/arm-cs-tools/arm-none-eabi/lib//libc.a(lib_a-errno.o) does not

とか出て ld が通らなかった。

yamanekko さんのレポジトリにある Makefile だと fpu ライブラリへのパスが書いてあるけど、上のスクリプトでクロスコンパイル環境を構築してもでてこない……

gcc の configure オプションか何かなのかもしれないけど、時間がかかるしむずかしいので諦めた。

よくわからなすぎるのでググったらARM EABIでの浮動小数点演算というが参考になった。

結局 -mfloat-abi=soft にして完全にソフトウェア命令しか出さないようにしたら動くようになった。-mfloat-abi=soft=softfp だと浮動小数点演算が走ったタイミングで死んだりしてよくわからない。

オプションの種類は arm-none-eabi-gcc --target-help で見ることができる。-mfpu=vfp のところもいろいろ種類があるけどよくわからない。

未だ完全には解決してない。

リンカスクリプト

memmap というファイルになってる。面倒だった。間違えるといろいろよくわかないことになる。今回遭遇したのは

  • .ARM.exidx を定義してなかったせいでおこられる
    • C++ 用のセクションらしいけど 特定の関数を使おうとすると必要っぽい
    • 定義の順番がおかしいとできるバイナリがデカくなったり、動かなくなったりする
  • アラインメントがおかしかったせい?で newlib の mALLOc が無限ループする
    • 当然ヒープを実装せず使わないなら再現しないので簡単なLチカだと気付かない

newlib が要求してくるシステムコール類は syscalls.c でモック的に定義されている(コピペしたのものをクリーンアップした)。だいたいはどうでもいい関数だけど、_sbrk が大変重要で、これによってヒープが実現されている。

メモリ割当のマッピングとかはリンカスクリプトで定義したものを vectors.s (アセンブラ) で C 側から見れるようにして、それを使ってヒープとかの実装をしているみたい。

gdb でシミュレーションデバッグ

.gdbinit に書いてあるけど

file main.elf
target sim
load

とすることで、gdb 上でシミュレーションして動かすことができるので、周辺機器が絡まない、とりあえずのデバッグはこれでも十分できる。

ただ、一部の演算、確認したのは % (余剰)で変なエラーがでておかしい。

実際やった手順

mruby なしでLチカ

まず mruby なしでLチカができないとどうしようもないので、まずそこまでできるプロジェクトツリーを作って動くことを確認するまでやった。

https://github.com/cho45/raspberrypi-bare-metal

この過程だけでも上記の通りかなりハマった……

mruby ありでLチカ

次に mruby ありでLチカに挑戦した。yamanekko さんのmruby-rs-led を参考に、Linux の gpio 的な GPIO module gem を実装した。

GPIO のアドレスとかはデータシート記載のものを手動で変換せずにできるだけそのまま書いておきたいと思うので、そうしてる。バスアドレスというのと、物理アドレスというのがあって、データシートにはバスアドレスで書いてあって、しかし実際使うのは物理アドレスなので、変換 (固定値) が必要みたい。

これによって以下のように書けるように (完全にちゃんと動いているか微妙だけど) なった。Ruby にすると一気に簡単になった感じがして落差がウケます。ほぼ Linux 上で適当にラッパー書いて書くのと同じコードになった。うまくラップできれば主要コードは Ruby レベルでテストが書けてよさそう。

pin = 16

GPIO.direction(pin, :low)
bool = false
loop do
	bool = !bool
	GPIO.write(pin, bool)
	10000.times { }
end

しかしここまで動かすのも大変で、何時間もハマった。ここでハマったのは特にリンカスクリプトでアラインメントがおかしかったせいで、mrb_open() がそもそも返ってこないというのがつらかった。

gdb で追ってもなぜか newlib の中で無限ループにになるので意味不明だった。

そしてそれがようやくなおったと思っても mrb_open() の最中に浮動小数点演算もしていて、ここでハングするということが起きてつらかった。最終的には上記の通り、とりあえず常にソフトウェアにしてなんとか動かした。

sleep の実装を書いてないので Ruby 側でビジーループしてる。16ピンにしてるのは、Raspberry Pi の基板上に実装済みの LED だからで、デバッグ用に新規配線をしなくてすむからです。

sleep の実装

さすがにビジーループをRuby側でやるのはダサいので次に sleep を実装した。

system timer と arm timer というのがあるけど、今回は system timer を使った。arm timer はシステムの状態によってクロックが変わるみたい。system timer は独立したクロックを持ってるようだ。

ただ、肝心の system timer clock がよくわからなかった。試行錯誤したかんじだと、1Mhz ぐらいで動いてる。分周比設定とかがなくて謎。要追試

Cレベルではビジーループしてる。うまくCPUスリープさせたいけど、まだよくわかってない。

mruby でハマったところ

mruby ではあまりハマらなかった。mrbgem 作るのは大変簡単になっていておもてなし感がある。

ただ、build_conf.rb で gem を指定せずにビルドすると、あとから gem を指定しても、clean するまで反映されないのが罠ぽかった。深くおってないけど、DISABLE_GEMS が永続されてしまうぽい?

この件は再現させて1行パッチ送ったらマージされたのでたぶん大丈夫です。build_config.rb を書きかえたら .c ソースのリビルドが走るはず…

レポジトリ

これも習作だけど別レポジトリにおいてる

課題点

  • fpu の問題の解決
  • GPIO モジュールの拡張
    • TIMER や割込みなどを使えるように
    • ハードウェア I2C SPI の実装
    • bit banging での I2C 実装
  • JTAG を試す
  • ブートローダーを使うように
    • SDカードいちいち抜き差ししてるとつらいので

感想

mruby は夢が広がる。mruby で OS とか作ったらかっこよさそう。

  1. トップ
  2. tech
  3. mruby を Raspberry Pi 上で bare metal で動かすまで

Perl 5.19.9 で実装された signatures の構文をためしてみる - tokuhirom blog を見てそんなのできたのか〜と思ったので、いろいろ試してみた。なんとこれは、今までになく直感的に引数が書けてしまう革命的構文です。

use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";

use feature 'lexical_subs';
no warnings "experimental::lexical_subs";

# 引数の数チェックをしてくれる。便利
eval {
	say "check length of arguments";
	state sub hello ($foo) {
		say "hello $foo";
	}


	hello();
	#=> Too few arguments for subroutine at pl.pl line 9.
}; if ($@) { warn $@ }

# デフォルト値も普通に書けます
eval {
	say "default value";
	state sub hello ($foo="world") {
		say "hello $foo";
	}


	hello();
	#=> "hello world"

	hello("yunotti");
	#=> "hello yunotti"
}; if ($@) { warn $@ }

# デフォルト値に式も書けます。Ruby なんかでも書けますね。引数として指定されない場合だけ氷菓されます
eval {
	say "expression in default value (evaluated same as Ruby)";
	my $i = 0;
	state sub hello ($foo=$i++) {
		say "hello $foo";
	}


	hello();
	#=> hello 0

	hello();
	#=> hello 1

	hello("yunotti");
	#=> "hello yunotti"

	hello();
	#=> hello 2
}; if ($@) { warn $@ }

# @foo を指定したら可変引数にできます。%hash とかもできます。
eval {
	say "variable length arguments";
	state sub hello ($foo, @rest) {
		say "hello $foo and " . join(", ", @rest);
	}


	hello("yunotti", "miyachan", "sae", "hiro");
	#=> hello yunotti and miyachan, sae, hiro
}; if ($@) { warn $@ }

# @_ の挙動は今までどおり変数渡しです。
eval {
	say 'with @_: @_ is passed by variable (same as prev perls)';
	state sub hello ($foo, $bar) {
		$foo = 'xxx';
		$_[1] = 'xxx';
	}


	my $foo = 'foo';
	my $bar = 'bar';
	hello($foo, $bar);
	say "$foo, $bar"; #=> foo, xxx

}; if ($@) { warn $@ }

ただ引数の名前とかは外からとることができない。せっかく構文に組込まれたのなら、とれてもよさそうだなと思った。すこしコード追ってみたけど、基本、今までで同じようなコードが内部的に生成されるだけっぽい。サブルーチンリファレンスに附属する形でなんかメタデータを入れれたらいいんだけど、よくわからなかった。

あと、Smart::Args みたいなのは使えるかと思って試してみたけど、既存コードと同じ感じなので、当然普通に使える。ただ、signatures を活用した感じにはできなそう。うまいやりかたあるのかな。

use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";

use feature 'lexical_subs';
no warnings "experimental::lexical_subs";


{
	# Smart::Args は普通に使える
	use Smart::Args;
	{
		# % を書けば呼ばれる時点でハッシュかどうかのチェックは入る
		state sub hello (%) {
			args(my $foo);
			say "hello $foo";
		}

		hello(foo => "foobar");
		eval {
			hello(1);
		}; if ($@) { warn $@ }; #=> Odd name/value argument for subroutine at pl.pl line 20.
	};

	{
		# % を書かなければ今までどおり Smart::Args 側でエラる
		state sub hello {
			args(my $foo);
			say "hello $foo";
		}
		eval {
			hello(1);
		}; if ($@) { warn $@ }; #=> Odd number of elements in anonymous hash at lib/site_perl/5.19.9/Smart/Args.pm line 39.
	}
};

{
	# せっかくなら signatures を活用したい感あるけどいい感じにならない (引数名を2度書かないといけない)

	use PadWalker qw/var_name/;
	sub validate ($var, $rule) {
		my $name = var_name(1, \$_[0]) =~ s{^\$}{}r;
		require Smart::Args;
		$_[0] = Smart::Args::_validate_by_rule($_[0], 1, $name, $_[1]);
	}

	{
		state sub hello ($foo) {
			validate($foo, { isa => 'Int' });
			say "hello $foo";
		}
		eval {
			hello("xxx");
		}; if ($@) { warn $@ }; #=> Validation failed for 'Int' with value xxx
	}
};
  1. トップ
  2. tech
  3. perl 5.19.9 の signatures 構文 (普通に引数を書ける構文) を試す

x86 で asm 書く機会ってほんとなくて、むしろ ARM の命令セットを学んだほうがいろいろモチベーションがでてきそう、ということで、とりあえず ARM Linux 上で簡単なのを書いてみます。

ARM

ARM は伝統的に OABI ("arm"), EABI ("armeb")という2つのインターフェイスがあるらしいです。OABI が古いほうで汎用 (Old)。EABI は組込み (Embeded) 用。ABI の違いということはシステムコールの呼びかたとか、サブルーチンの呼びかたが違いということです…… すなわちアセンブリで書くときはどっちを書いてるか明確にする必要があります。

ここでは EABI だけでやってみます。EABI の場合システムコールは、r7 にシステムコール番号を指定して、SVC 命令を発行 (オペランドは 0) すれば良いようです。

環境は GCC でやります。

その前に

アセンブリ書くとき、そもそも命令セット (instruction set) がわからないとどうにもならない。リストはARM のサイトから探す。

x86 とかも、とにかく命令セットがわからないと何もしようがない。x86 instruction set とかでググったらよさそう。x86 は命令いっぱいありすぎて pdf が4つぐらいにわかれてる。

ただ、GCC で書く場合 gas (gnu as) のフォーマットで書かないといけないので、データシートと形式がちがうのが罠い。

GCC as (gas) での書きかたは、公式のドキュメントを参照する必要がある。x86 と arm でも書きかたが違う。

exit するだけのコード

まず exit するだけのコードを書いてみます。

/*#!as -o sketch.o sketch.s && ld -o sketch -e _start sketch.o && ./sketch
 */

.global _start
_start:
        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */
$ as -o sketch.o sketch.s
$ ld -o sketch -e _start sketch.o
$ ./a.out

簡単!!!

r7 にシステムコール番号を入れ、r0 には exit の唯一の引数である終了ステータスを入れ、svc 命令でシステムコールが実行されます。当然 exit なので返ってきません。

obj-dump するとそのままのコードが出てきます

Disassembly of section .text:

00008054 <_start>:
    8054:       e3a00000        mov     r0, #0
    8058:       e3a07001        mov     r7, #1
    805c:       ef000000        svc     0x00000000

Hello, World

こっちは微妙に面倒です。普通に同じようにシステムコールを呼ぶだけですが、文字列をどこに置くか、どうやってアドレスを参照するかがいろいろあります。

.text に一緒に置く

たぶん一番シンプルな例

/* text section */

.section .text
.global _start

_start:
        mov r0, $0x01 /* set file descripter to 1 (stdout) */
        adr r1, msg   /* adr is GNU as's special instruction. This is translated to add */
        mov r2, $0x0d /* set msg length to 0x0d (13) */
        mov r7, $0x04 /* set system call number to 4 (write) */
        svc $0x00     /* supervisor call  */

        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */

msg:
        .asciz "Hello, World\n"
        .align 2

1行ごとにコメントつけてますが、この例では "Hello, World\n\0" という文字列が text セクションに置かれ、それを adr 命令で参照して r1 に書きこんでいます。adr 命令は GNU as が解釈して、指定したラベルを示すように add 命令に書きかえられます。試しにこれをアセンブルしたものを objdump すると

Disassembly of section .text:

00008054 <_start>:
    8054:       e3a00000        mov     r0, #0
    8058:       e28f1014        add     r1, pc, #20
    805c:       e3a0200d        mov     r2, #13
    8060:       e3a07004        mov     r7, #4
    8064:       ef000000        svc     0x00000000
    8068:       e3a00000        mov     r0, #0
    806c:       e3a07001        mov     r7, #1
    8070:       ef000000        svc     0x00000000

00008074 <msg>:
    8074:       6c6c6548        .word   0x6c6c6548
    8078:       57202c6f        .word   0x57202c6f
    807c:       646c726f        .word   0x646c726f
    8080:       000a            .short  0x000a
        ...

とかなってます。_start の中の adr があったところは add 命令に書きかわってます。.text はプログラム用のセクションですが、普通にデータも置けます。

.data セクションに置く

もう少しまともな例です。

/* text section */

.section .text
.global _start

_start:
        mov r0, $0x01   /* set file descripter to 1 (stdout) */
        ldr r1, msg     /* load memory address of msg to register  */
        ldr r2, msg_len /* load memory address of msg_len to register  */
        mov r7, $0x04   /* set system call number to 4 (write) */
        svc $0x00       /* supervisor call  */

        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */

msg:
        .word data_msg /* write data_msg address to here */
msg_len:
        .word data_msg_len

/* data section */
.section .data
data_msg:
        .asciz "Hello, World\n"
data_msg_len = . - data_msg
        .align 2

テキストデータ本体は data セクション (初期化される・書き換え可能・Cで書いた場合グローバル変数の領域) において、text セクションにはポインタのアドレスを置いています。ldr 命令でそのアドレスをレジスタの読みこみ、write を呼んでいます。

ついでに文字列の長さもアセンブラで計算させています。. は現在のアドレスを示していて、. から data_msg のアドレスを引くことで文字列の長さを求められます。

これを objdump -d -j .text -j .data sketch すると

Disassembly of section .text:

00008074 <_start>:
    8074:       e3a00000        mov     r0, #0
    8078:       e59f1014        ldr     r1, [pc, #20]   ; 8094 <msg>
    807c:       e59f2014        ldr     r2, [pc, #20]   ; 8098 <msg_len>
    8080:       e3a07004        mov     r7, #4
    8084:       ef000000        svc     0x00000000
    8088:       e3a00000        mov     r0, #0
    808c:       e3a07001        mov     r7, #1
    8090:       ef000000        svc     0x00000000

00008094 <msg>:
    8094:       0001009c        .word   0x0001009c

00008098 <msg_len>:
    8098:       0000000e        .word   0x0000000e

Disassembly of section .data:

0001009c <data_msg>:
   1009c:       6c6c6548        .word   0x6c6c6548
   100a0:       57202c6f        .word   0x57202c6f
   100a4:       646c726f        .word   0x646c726f
   100a8:       000a            .short  0x000a
        ...

みたいな感じになります。

ちなみに、ld に --verbose オプションをつけるとリンカスクリプトが表示されます。これはコードやデータを実際にメモリ上にどのように配置するかを定義したもので、ld はこの定義に従って、シンボルを解決 (アドレスを決めるということ) オブジェクトを配置します。

  1. トップ
  2. tech
  3. ARM アセンブリ Hello, World on Raspberry Pi

[tech] perl 5.19.9 の signatures 構文 (普通に引数を書ける構文) を試す | Mon, Feb 24. 2014 - 氾濫原 で「ただ引数の名前とかは外からとることができない。せっかく構文に組込まれたのなら、とれてもよさそうだなと思った」と書いたので、すこしだけ実装を書いてみた。

blead perl ecb4de39577c95734821743685366f6fe7f59a2d へのパッチです。

#!./perl -Ilib
use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";
use Test::More;
package signatures {
sub args ($sub) {
$signatures::subs{$sub+0};
}
sub arity ($sub) {
$signatures::arities{$sub+0};
}
}
subtest "signatures" => sub {
package _test1 {
sub foo ($xxx, $yyy) {
}
sub bar ($zzz, $aaa=1) {
}
sub baz ($zzz, @rest) {
}
}
is_deeply signatures::args(_test1->can('foo')), [qw(
$xxx
$yyy
)];
is signatures::arity(_test1->can('foo')), 2;
is_deeply signatures::args(_test1->can('bar')), [qw(
$zzz
$aaa
)];
is signatures::arity(_test1->can('bar')), 1;
is_deeply signatures::args(_test1->can('baz')), [qw(
$zzz
@rest
)];
is signatures::arity(_test1->can('baz')), -2;
};
subtest "no signature subs" => sub {
package _test2 {
sub foo {
}
}
is signatures::args(_test2->can('foo')), undef;
is signatures::arity(_test2->can('foo')), undef;
};
TODO: subtest 'lexical_subs' => sub {
local $TODO = "FAIL";
use feature 'lexical_subs';
no warnings "experimental::lexical_subs";
state sub foo ($foo) {
}
is_deeply signatures::args(\&foo), [qw(
$foo
)];
is signatures::arity(\&foo), 1;
};
done_testing;
view raw 01-signatures-args.t hosted with ❤ by GitHub
diff --git a/toke.c b/toke.c
index 88524b4..b517e37 100644
--- a/toke.c
+++ b/toke.c
@@ -12432,6 +12432,48 @@ S_parse_opt_lexvar(pTHX)
return var;
}
+void
+Perl_parse_subsignature_remember_sub_arg(pTHX_ OP* var)
+{
+ HV* signaturesubmap;
+ SV* subid;
+ SV* name;
+ HE* signatures;
+ AV* list;
+
+ signaturesubmap = get_hv("signatures::subs", 0);
+ if (!signaturesubmap) signaturesubmap = get_hv("signatures::subs", GV_ADD);
+
+ subid = newSViv((IV)(PL_compcv));
+ name = newSVsv(PAD_COMPNAME_SV(var->op_targ));
+ signatures = hv_fetch_ent(signaturesubmap, subid, 0, 0);
+ if (signatures) {
+ list = (AV*)SvRV(HeVAL(signatures));
+ } else {
+ list = newAV();
+ }
+ av_push(list, name);
+ hv_store_ent(signaturesubmap, subid, newRV_inc((SV*)list), 0);
+}
+
+void
+Perl_parse_subsignature_remember_sub_arity(pTHX_ int min_arity, int max_arity) {
+ HV* signaturearitymap;
+ SV* subid;
+ int arity;
+
+ signaturearitymap = get_hv("signatures::arities", 0);
+ if (!signaturearitymap) signaturearitymap = get_hv("signatures::arities", GV_ADD);
+
+ subid = newSViv((IV)(PL_compcv));
+ if (max_arity == -1) {
+ arity = -(min_arity + 1);
+ } else {
+ arity = min_arity;
+ }
+ hv_store_ent(signaturearitymap, subid, newSViv(arity), 0);
+}
+
OP *
Perl_parse_subsignature(pTHX)
{
@@ -12488,7 +12530,10 @@ Perl_parse_subsignature(pTHX)
prev_type = 0;
min_arity = pos + 1;
}
- if (var) expr = newASSIGNOP(OPf_STACKED, var, 0, expr);
+ if (var) {
+ expr = newASSIGNOP(OPf_STACKED, var, 0, expr);
+ Perl_parse_subsignature_remember_sub_arg(var);
+ }
if (expr)
initops = op_append_list(OP_LINESEQ, initops,
newSTATEOP(0, NULL, expr));
@@ -12539,6 +12584,7 @@ Perl_parse_subsignature(pTHX)
initops = op_append_list(OP_LINESEQ, initops,
newSTATEOP(0, NULL,
newASSIGNOP(OPf_STACKED, var, 0, slice)));
+ Perl_parse_subsignature_remember_sub_arg(var);
}
prev_type = 2;
max_arity = -1;
@@ -12590,6 +12636,7 @@ Perl_parse_subsignature(pTHX)
newSVpvs("Too many arguments for subroutine"))))),
initops);
}
+ Perl_parse_subsignature_remember_sub_arity(min_arity, max_arity);
return initops;
}

やってることは signatures::subs みたいな名前でハッシュをつくって放りこんでるだけです。perl のコード難しくてあってるかわからないのと、たぶん参照カウントがおかしかったりしてそうですが、とりあえず簡単には動く。

ただ、lexical_subs に対してはうまくとれない (とってるサブルーチンのアドレスがちがう) ので、そこが TODO かな、というのと、サブルーチンとか大量に定義されるので、結構メモリ食ったりしそうだな、ってのが怪しい感じです。

  1. トップ
  2. tech
  3. perl 5.19.9 の signatures 構文に引数リストをとれる機能を足す

mruby で Raspberry Pi の GPIO をいじるというやつで、sleep をビジーループにしていたのがどうしてもひっかかっていた。

どうも ARM には割込みが起きるまで眠る命令があるみたいなので、それを使ってみることにした。コード全体

static mrb_value mrb_mruby_raspberrypi_gpio_gem_delay_us(mrb_state* mrb, mrb_value self) {
	mrb_int delay;
	mrb_get_args(mrb, "i", &delay);

	// Reset timer flags
	PUT32(ARM_TIMER_CONTROL, 0x3E0020);
	// Load count down timer value
	PUT32(ARM_TIMER_LOAD, delay-1);
	PUT32(ARM_TIMER_RELOAD, delay-1);
	// predevider = (apb_clk - freq) / freq
	PUT32(ARM_TIMER_PRE_DIVIDER, 250 - 1);
	PUT32(ARM_TIMER_IRQ_CLEAR_ACK, 0);
	PUT32(ARM_TIMER_CONTROL,
		(0x3E<<16) | // default free-running pre-scaler
		(1<<7)     | // timer enabled
		(1<<5)     | // timer interrupt enabled
		(1<<1)       // 23-bit counter
	);

	// Enable ARM Timer IRQ
	PUT32(ARM_INTERRUPT_ENABLE_BASIC_IRQS, 1);
	
	while ((GET32(ARM_INTERRUPT_IRQ_BASIC_PENDING) & 1) == 0) {
		// Waiting For Interrupt
		asm volatile ("wfi");
	}

	// Disable ARM Timer IRQ
	PUT32(ARM_INTERRUPT_DISABLE_BASIC_IRQS, 1);

	return mrb_nil_value();
}

コメントにある通りだけど、割込みを設定して、ARM_INTERRUPT_IRQ_BASIC_PENDING のフラグを見つつ、セットされるまでは wfi 命令で継続的に眠る、というようにしてみた。他に割込みを設定していないので、while は1回で抜けるつもり……

wfi 命令はオプショナルな命令らしく、ハードによっては nop として解釈されるらしい。なのでこのような実装の場合、フラグをポーリングするようなコードを併用したほうが安全そう。

というか、実際 wfi 命令がちゃんと動いているかを確かめる方法が面倒くさい。電流を測るしかなさそう。電流を今回測るところまでやってないので、ちゃんと動いてないのかもしれない。ただ、挙動として割込みをポーリングで待つ、というのはできいてるっぽい。

どうでもいいけど wfi で検索しても wifi 扱いされてだいぶウザい。

  1. トップ
  2. tech
  3. 割込みと WFI 命令を使った sleep の実装

//#!gcc -O0

#include <stdio.h>

int main (int argc, char *argv[]) {

        int a;
        int b;

        a = 0; b = 3;

        asm volatile (
                "mov r0, %[x];"
                "mov %[x], %[y];"
                "mov %[y], r0;"
                : [x] "+r" (a), [y] "+r" (b)
                :
                : "r0"
        );

        printf("a=%d, b=%d\n", a, b);

        return 0;
}

asm でやる必要は全然ないけど inline asm の文法が意味不明なのでちょっと書いてみた。

asm( code : output list : input list : clobber list)

という文法らしいけど、パっと見さっぱりわからない。上記の例(値の交換)だと

  • a と b どちらも読み書きが必要なので、output リストのほうで + (read/write) をつける。r はレジスタの意味らしい。
  • operand の指定は名前で行っている。角括弧を使うと名前で指定できるみたい
  • input list は空 (output list で read/write を指定しているので input として指定する必要はない)
  • clobber list というで、このコードによって上書きされるレジスタを指定する
    • これを指定しないと、意図せず他の変数のレジスタを破壊してしまったりする (GCC はどのレジスタが破壊されるかがわからないから、破壊されるレジスタに変数を割り当ててしまう)
    • 指定することで、GCC はそのレジスタを一旦退避させたりできる

このページが比較的わかりやすい。

  1. トップ
  2. tech
  3. ARM inline asm で値の交換を書いてみる

[tech] 割込みと WFI 命令を使った sleep の実装 | Wed, Feb 26. 2014 - 氾濫原 で WFI 命令があるのでそれ使えばよさそうみたいなことを書いたけど、Which architectures support the WFI instruction? を読んでいたら、Raspberry Pi が WFI 命令をサポートしていないことに気付いてしまった……

Raspberry Pi は ARMv6K というアーキテクチャの ARM1176JZ-F というプロセッサらしい。無印の ARMv6 は WFI 命令をサポートしない。ただ、ARM1176JZ-F は別の方法で使うことができる。

いろいろ書いてあってややこしいが、重要なのはここ

ARMv6K and ARMv6T2 include the WFI instruction, meaning that these processors do not cause an undefined instruction exception when the WFI instruction is executed. However, the ARM1136J(F)-S rev 1 and ARM1176JZ(F)-S (architecture ARMv6K), as well as the ARM1156T2(F)-S (architecture ARMv6T2) treat the WFI as a NOP, and implement the CP15 method for entering "wait for interrupt" mode.

なので、WFI 命令は NOP として扱われてた…… 気を使って NOP でも普通に動くコードを書いた結果気付きにくいバグを作っていた。

mov r1, #0;
mcr p15, #0, r1, c7, c0, #4;

とすれば WFI 相当のことができるみたい。コプロセッサってなんだよって感じだけど、書いてある通り書いたらエラーはでなかった。

mcr 命令は ARM レジスタからコプロセッサへデータを転送する命令らしい。上の mcr 命令の場合

  • C15 (p15) コプロセッサの
  • レジスタ7 (c7) = レジスタ 7: キャッシュ管理機能 に対し
  • 割込み待ち (c0, #4 ) を転送する

転送する値はなんでもいい?のかな。r1 に転送する値を入れるけど 0 にしてる。mcr の第2引数もコプロセッサのオペコードっっぽいけどよくわからない。

  1. トップ
  2. tech
  3. 続・割込みと WFI 命令を使った sleep の実装

この GCC (ビルド済み) を使えば fpu がどうとかも何も問題なくうまくいく。

ただし、gdb の simulator がついてないのが不便…… だけど、むしろ gdb でやるより、qemu とかでエミュレーションしたほうがよさそう…… 環境つくるのめんどいけど

  1. トップ
  2. tech
  3. arm-none-eabi クロスコンパイル環境

Raspberry Pi で bare metal をやっているこのコードを読んで理解したいと思います。README に殆ど書いてありますが、ちょっとよくわからないところがあったのでさらに詳しくしてみます。

前提として、ld スクリプト の通り、このプログラムの冒頭は 0x8000 からはじまっています。

割込みハンドラの実行

まず _start の最初には割込みハンドラの定義が書いてあります。

_start:
    ldr pc,reset_handler
    ldr pc,undefined_handler
    ldr pc,swi_handler
    ldr pc,prefetch_handler
    ldr pc,data_handler
    ldr pc,unused_handler
    ldr pc,irq_handler
    ldr pc,fiq_handler

reset_handler:      .word reset
undefined_handler:  .word hang
swi_handler:        .word hang
prefetch_handler:   .word hang
data_handler:       .word hang
unused_handler:     .word hang
irq_handler:        .word irq
fiq_handler:        .word fiq

これは、README によるとちょっとしたハックになっていて、若干トリッキーな動きをします。

まず最初に _start が実行されはじめると、最初にあるのが ldr pc,reset_handler なので、すぐに reset: に実行が移り、後続のコードは実行されません。

そして reset: の冒頭では以下のようになっています (コメントはこちらでつけたものです)

reset:
    /* Set interrupt handler to radical address from 0x8000
     */
    mov r0,#0x8000
    mov r1,#0x0000
    /* copy machine code 32bytes bytes at once */
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9} /* load machine code from 0x8000 */
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9} /* store code to 0x0000 */
    /* more 32 bytes */
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}

割込みハンドラは本来、0x0000 から初まるアドレスに指定通り配置する必要があります (すなわり、割込みが入ると、決め打ちのアドレスが実行される)。これは ARM のアーキテクチャマニュアルに書いてあるマジックナンバーです。なので、0x0000 へ、割込みハンドラをコピーする必要があり、それがこの部分になっています。

ldmia は LDM 命令 + IA (インクリメントアフター) という命令で、指定されたメモリアドレス (ここでは r0!) から、指定したレジスタリストに値をロードします。r0 はロードされるごとにインクリメントされます。

そして stmia は STM 命令 + IA という命令で、指定したメモリアドレス (ここでは r1!) に、指定したレジスタリストを書きこみます。ここでは一括して1度にレジスタ8個に対し処理が行われているので、8 * 4bytes = 32bytes がコピーされます。1セット目の ldmia/stmia で割込みハンドラ8個分がコピーされ、2セット目でその後に続くハンドラのアドレスリストがコピーされています。

C的には memcpy ですかね。

なぜこのようなハックが必要か?というと、プログラムが必ず 0x8000 にロードされて、直接 0x0000 には書きこめないからみたいです。

スタックポインタの初期化

続いて以下のような似たような3つの塊がでてきます。

    /* IRQ Mode (0b11010010),   */
    mov r0,#0xD2
    msr CPSR_c,r0
    mov sp,#0x8000

    /* FIQ Mode (0b11010001) */
    mov r0,#0xD1
    msr CPSR_c,r0
    mov sp,#0x4000

    /* Supervisor Mode (0b11010011) */
    mov r0,#0xD3
    msr CPSR_c,r0
    mov sp,#0xF000000

msr 命令はプログラムステータスレジスタに値を書きこむ命令で、ここでは特にモードの切り替えを行っています。CPSR_c は カレントプログラムステータスレジスタの下位 8bit に書きこむという意味です。

カレントプログラムステータスレジスタの下位 8bit は以下のような構造になっています。

    /* 
     *  CPSR register lowest 8bit: (page A2-11)
     *      I    [7]   -> IRQ disabled (set 1 to disable)
     *      F    [6]   -> FIQ disabled (set 1 to disable)
     *      T    [5]   -> Always set 0 in ARM state
     *      MODE [4:0] -> Mode bit
     */

下位4bitでモードを指定するようになっており、それぞれコメントにあるようなモードに遷移します。

ARM では例外モードと呼ばれるモードそれぞれに対して sp レジスタは別々に存在しています。ここでは sp をそれぞれ別のアドレスを示すように初期化しているようです。

notmain の呼び出し

その後、スーパーバイザーモードのまま、C で定義された notmain 関数が呼ばれて終わりです。

本来、.bss の初期化などがあるはずですが、この例では使ってないので書いてないようです。

  1. トップ
  2. tech
  3. Raspberry Pi で bare metal している blinker05 の ARM ブートコードを読む