Raspberry PiでMicrochip製EEPROM 24LC32, 24LC64, ... 24LC256を読み書きする。
Cでアクセス可能なのは当然のこととして、Perlでもなんとかアクセスできることを確かめた。
読み書きのデータ構造
Microchip社のマニュアルより引用すれば、次のようなパケットをやり取りして読み書きを行う。ピンクで着色した所が、プログラム側で送信するデータ。緑で着色した所が受信されるデータ。
(着色していない部分の) I2Cアドレスの送信部分やR/Wビットなどは、システム側で自動的に処理される。
C言語版 EEPROMに書き込み
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define I2C_ADDR 0x51
int main(int argc, char **argv){
int i2c; // i2cデバイスのファイルディスクリプタ
int addr = 0; // EEPROM上のアドレス
unsigned char padding = 0xb0; // 書き込むデータ(サンプル値)
unsigned char buf[3]; // I2C 通信バッファ
// I2CポートをRead/Write属性でオープン
if ((i2c = open("/dev/i2c-1", O_RDWR)) < 0){
exit(1);
}
if (ioctl(i2c, I2C_SLAVE, (int)I2C_ADDR) < 0){
exit(1);
}
buf[0] = addr >> 8; // アドレス上位ビット
buf[1] = addr & 0xff; // アドレス下位ビット
buf[2] = padding; // 書き込むデータ
// アドレス 2Bytes + データ 1Byte送信(Master -> Slave)
if (write(i2c, buf, 3) != 3){
exit(1);
}
close(i2c);
}
C言語版 EEPROMから読み込み
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <linux/i2c-dev.h> #include <fcntl.h> #include <sys/ioctl.h> #define I2C_ADDR 0x51 int main(int argc, char **argv){ int i2c; // i2cデバイスのファイルディスクリプタ int addr = 0; // EEPROM上のアドレス unsigned char buf[3]; // I2C 通信バッファ // I2CポートをRead/Write属性でオープン if ((i2c = open("/dev/i2c-1", O_RDWR)) < 0){ exit(1); } if (ioctl(i2c, I2C_SLAVE, (int)I2C_ADDR) < 0){ exit(1); } buf[0] = addr >> 8; // アドレス上位ビット buf[1] = addr & 0xff; // アドレス下位ビット // アドレス 2Bytes送信(Master -> Slave) if (write(i2c, buf, 2) != 2){ exit(1); } // 1Byte受信 (Slave -> Master) if (read(i2c, &buf, 1) != 1) { exit(1); } printf("%02X ", buf[0]); close(i2c); }
実際の信号のやり取りを、ロジックアナライザでキャプチャした結果は次のようなものだ
この記事内で、Perl版のFile::IOを用いたときのロジック・アナライザのキャプチャ画面と比べると、writeとreadの間隔(時間)が違うのが分かる。Perlはインタープリター言語なので、どうしても処理時間が掛かるのだろう。大量のデータをさばくときには、これが結構処理時間の長時間化の原因となってしまうだろう。
Perl版 EEPROMに書き込み (RPi::I2C)版
バイト列のブロック送信で、データ先頭になぜかNULLが1Byte勝手に付加される。 MicrochipのEEPROMでは最初の1Bytesがメモリ・アドレスの上位Byte指定なので、0xff以上のアドレスにはアクセスできないことになる。
つまり、RPi::I2C は今回の用途では全く使い物にならない…
#!/usr/bin/perl
use strict;
use warnings;
use RPi::I2C;
use Fcntl;
my $i2c_addr = 0x51;
my $start_addr = 0x127; # 書き込み開始アドレス
my $process_bytes = 1; # 書き込みByte数
my $padding = 0xb0; # 書き込む文字
eval {
my $i2c = RPi::I2C->new($i2c_addr, "/dev/i2c-1");
# 書き込み開始アドレスは、RPi::I2Cの特性(?)により上位1Bytesはゼロ固定
my @bytes = ($start_addr & 0xff, $padding);
$i2c->write_block(\@bytes);
};
if($@){
die $@;
}
Perl版 EEPROMに書き込み (File::IO)版
この方法は、今のところ不具合無い。 writeはバッファーを用いるprintでも、バッファーを用いないsyswriteでも結果は変わらないように見える。
#!/usr/bin/perl
use strict;
use warnings;
use IO::File;
use Fcntl;
my $i2c_addr = 0x51;
my $start_addr = 0x127; # 書き込み開始アドレス
my $process_bytes = 1; # 書き込みByte数
my $padding = 0xb0; # 書き込む文字
eval {
my $fh = IO::File->new("/dev/i2c-1", O_RDWR);
$fh->ioctl(I2C_SLAVE_FORCE, $i2c_addr);
$fh->binmode();
# アドレス 2Bytes + データ 1Byte をバイナリ形式の変数に格納
my $buffer = pack("C*", $start_addr >> 8, $start_addr & 0xff, $padding);
# アドレス 2Bytes + データ 1Byte 送信
#(bufferdのprintではなく、unbufferdのsyswrite利用)
$fh->syswrite($buffer);
$fh->close();
};
if($@){
die $@;
}
Perl版 EEPROMから読み込み (File::IO)版
バッファーを用いない syswrite, sysread を使うことで、問題なく動いている
unbuffered sysread/syswriteを用いた場合
バッファーを用いる print , read を使うと、読み込み時に一定の受信データがたまるまで待ち続けるため、EEPROM側がどんどんデータを送ってくる。もちろん、こういう使い方をしたいわけではないので、問題ありだ。
ちょっと意地悪に、unbufferd sysread/buffered printを用いた場合。スクリプト上のコードの順序ではなく、まずunbufferedがいきなり処理され、続いてbufferedが処理された。これも、もちろん期待された動作ではない。
unbufferd sysread/buffered printを用いた場合
#!/usr/bin/perl use strict; use warnings; use IO::File; use Fcntl; my $i2c_addr = 0x51; my $start_addr = 0x127; # 書き込み開始アドレス my $process_bytes = 1; # 書き込みByte数 my $padding = 0xb0; # 書き込む文字 eval { my $fh = IO::File->new("/dev/i2c-1", O_RDWR); $fh->ioctl(I2C_SLAVE_FORCE, $i2c_addr); $fh->binmode(); # アドレス 2Bytes をバイナリ形式の変数に格納 my $buffer = pack("C*", $start_addr >> 8, $start_addr & 0xff); # アドレス 2Bytes 送信 #(bufferdのprintではなく、unbufferdのsyswrite利用) $fh->syswrite($buffer); # データ 1Byte 受信 #(bufferdのreadではなく、unbufferdのsysread利用) $fh->sysread($buffer, $process_bytes); $fh->close(); # 受信したEEPROMデータを画面表示 printf "%X\n", unpack("C", substr($buffer,0,1)); }; if($@){ die $@; }