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