30 April 2018

Raspberry PiでMicrochip製EEPROMを読み書きする

Raspberry PiでMicrochip製EEPROM 24LC32, 24LC64, ... 24LC256を読み書きする。

Cでアクセス可能なのは当然のこととして、Perlでもなんとかアクセスできることを確かめた。

読み書きのデータ構造

Microchip社のマニュアルより引用すれば、次のようなパケットをやり取りして読み書きを行う。ピンクで着色した所が、プログラム側で送信するデータ。緑で着色した所が受信されるデータ。

(着色していない部分の) I2Cアドレスの送信部分やR/Wビットなどは、システム側で自動的に処理される。

20180430-eeprom-write-packet.jpg
書き込みパケットの構造

20180430-eeprom-read-packet.jpg
読み込みパケットの構造

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 は今回の用途では全く使い物にならない…

20180430-eeprom-perl-write-rpii2c.jpg

#!/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側がどんどんデータを送ってくる。もちろん、こういう使い方をしたいわけではないので、問題ありだ。

20180430-eeprom-perl-read-error1.jpg
bufferd read/printを用いた場合

ちょっと意地悪に、unbufferd sysread/buffered printを用いた場合。スクリプト上のコードの順序ではなく、まずunbufferedがいきなり処理され、続いてbufferedが処理された。これも、もちろん期待された動作ではない。

20180430-eeprom-perl-read-error2.jpg
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 $@;
}