簡易ロジアナを作り、Raspberry PiのI2Cアクセスを見る
どんなもの
ロジアナ(Logic Analyzer)は、デジタル信号のHIGH/LOWを記録し、表示・解析する測定器です。昔は非常に高価で手に入らないものでしたが、今は、サンプリング周波数が低いものは非常に安価になりました。また、Pulseview(sigrok)のオープンソースを使うと、市販されているUSBシリアル変換モジュール(USB UART/FIFO IC)で簡単にロジアナができます。
市販のFT232H USBシリアル変換モジュール
市販されている上のモジュールでもよかったのですが、今回、自分で基板を作りたかったのでFTDI FT232Hを使ったPCBを作成し、また、ブレッドボードの電源ラインにも直接させるようにしました。このロジアナで、Raspberry PiのI2Cインタフェースで接続したEEPROMへのアクセス信号を見てみました。
自作シリアル変換モジュールをブレッドボードに差した状態
回路図、ガーバーデータ、テストコードはこちらにあります
PulseView(sigrok)
PulseView(sigrok)は、ロジアナのアダプタから信号をキャプチャし、それを表示解析してくれるオープンソースのソフトウェアです。以下のように、信号線からプロトコル解析もしてくれます。
I2Cのプロトコル解析表示
このソフトウェアのインストールは、sigrokのダウンロード先から、ビルド済みのインストーラをインストールできますが、FT232HのUSBシリアル変換モジュール場合、パルス幅の表示が正しくされません。そのため、コードを修正し、ビルドしてインストールしました。
なぜパルス幅の時間が合わないか
PulseViewの機能に、Show Cursorがあり、パルス幅を計測できますが、既知のパルス幅を測定したところ短い時間で表示されます。(1MHzクロックで1.42MHz/700.000nsと表示される、正しくは1MHz/1000ns)
デジタルオシロで、確かめたところ、入力されている信号は正しく、PulseViewの表示する数値だけが誤っていました。そこで、sigrokのFT232Hのドライバコードを調べたところ、FT232Hのデバイスのサンプルレートデバイダsamplerate_divの値がFT232Rと同じ30でした。これをFT232Hの20にすれば修正できます。ただし、コードから再ビルドが必要になります。(ビルド、インストール方法は後述)
ビルド後、再インストールして、1MHzのクロック幅を測定したところ、正しく表示されました。
どこまで測定できるか
どこまでの周波数が測定できるか試してみました。信号の周波数を上げてみたところ、2MHzまでなんとか測定できました(Duty比がおかしいですが長さは正しい)。それ以上は、サンプリングとデータ転送が間に合わず、取りこぼしました。この簡易ロジアナは最大2MHzまで、正しく測定するなら1MHzまでとなりました。
これでも、いろいろ測定できますので、重宝します。
IC2アクセスを測定してみる
実際に、Raspberry PiからEEPROMへのI2Cアクセスをこのロジアナで取得してみます。
接続は以下です。
IC2の信号は、SDA,SCLの2本のインタフェースしかありません。SDAをADBUS0(D0)に、SCLをADBUS1(D1)に接続します。
Raspberry PiからI2Cアクセスするためのコードを書きます。今回はRustを使用しました。
テストコードはこちら(2023.8.7追記)
まずは、RustコンパイラをRaspberry Piにインストールします。
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>1
ここで1を選択Enterキーを押し、インストール
そのあと、環境変数をセットする
$ source "$HOME/.cargo/env"
次に、Raspberry PiのI2Cインタフェースを有効にし、I2Cツールをインストールします。
$ sudo raspi-config nonint do_i2c 0
$ sudo apt-get -y install i2c-tools
Raspberry PiからI2C経由でEEPROMデバイスを認識できるかI2Cツールで確認します。
$ sudo i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: 30 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- 58 -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
EEPROMのA0~A2PinをGNDに接続していますので、30, 50, 58のアドレスのデバイスが見えています。デバイスが1つしかないのになぜか、3つ出て来ています。それは、30hは、Write Protectionレジスタ、50hがEEPROMメモリへのレジスタ、58Hがシリアルナンバー読み込み用レジスタになっています。EEPROMにデータを書込みするレジスタは50hになります。
次に、I2CでアクセスするRustコードを置く、Rustのひな型を作ります。
$ cargo new eeprom
以下のソースコードを書いていきます。
.
└── eeprom
├── Cargo.toml
├── src
│ ├── at24mac.rs
│ └── main.rs
はじめに、アドレス50hからデータをget, writeする部分です。rppalクレートを使用します。DEVADDRはI2Cのアドレスで50hと固定にしています。
use rppal::i2c::I2c;
use std::error::Error;
const DEVADDR: u16 = 0x50;
pub struct AT24EEPROM {
bus: I2c,
}
impl AT24EEPROM {
pub fn new(bus: I2c) -> Self {
Self { bus }
}
pub fn get_data(&mut self, addr: u8) -> Option<u8> {
self.bus
.set_slave_address(DEVADDR)
.expect("failed to set slave address");
match self.bus.smbus_read_byte(addr) {
Ok(n) => {
Some(n)
},
Err(e) => {
eprintln!("{e}");
None
}
}
}
pub fn write_data(&mut self, addr: u8, value: u8) -> Result<(), Box<dyn Error>>{
self.bus
.set_slave_address(DEVADDR)
.expect("failed to set slave address");
match self.bus.smbus_write_byte(addr, value) {
Ok(()) => {
Ok(())
},
Err(e) => {
eprintln!("{e}");
Err(Box::new(e))
}
}
}
}
mainの部分です。デバイスアクセスするインスタンスを作成し、write_dataで255から0までを0番地から255番地まで書込み、それぞれ、書込毎に5msecの待ち時間を入れます。書込みには5msecかかり、この待ち時間がないと、書込みがエラーとなります。次に、0から255番地までデータを読み、書き込んだ値と一致するか確認します。
use rppal::i2c::I2c;
use std::{error::Error, thread, time::Duration};
mod at24mac;
fn main() -> Result<(), Box<dyn Error>> {
let i2c_bus = I2c::new()?;
let mut at24eeprom_dev = at24mac::AT24EEPROM::new(i2c_bus);
let mut value = 255;
for addr in 0..=255 {
// Write data
match at24eeprom_dev.write_data(addr, value){
Ok(()) => {},
Err(e) => {
println!("{e}");
}
}
value -= 1;
thread::sleep(Duration::from_millis(5));
}
value = 255;
for addr in 0..=255 {
// Read data
if let Some(ret_value) = at24eeprom_dev.get_data(addr) {
let mut flag = "NG";
if value == ret_value {
flag = "OK";
}
println!("addr {addr} value: {value} {ret_value} {flag}");
}
value -= 1;
}
Ok(())
}
読み込むクレートを指定します。
[package]
name = "eeprom"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rppal = "0.14.1"
ビルド&実行してみます。
$ cargo run --release
Compiling eeprom v0.1.0 (/home/pi/eeprom)
Finished release [optimized] target(s) in 1.14s
Running `target/release/eeprom`
addr 0 value: 255 255 OK
addr 1 value: 254 254 OK
:
addr 254 value: 1 1 OK
addr 255 value: 0 0 OK
EEPROMへの書込み、読み込みは成功しました。
今度は、PulseViewを起動し、1G sammples/10MHzでRun
を押しキャプチャします。先ほどのプログラムを実行します。
キャプチャできたらstop
し、記録したパルスの部分を拡大します。Raspberry PiのI2Cアクセスは100KHzのクロックでした。
プロトコルの解析をしてみます。ラインの左端のADBUS0
のラベルをクリックし、Name
をADBUS0
からSDA
に変更します。同様にADBUS1
をSCL
に設定します。
赤枠のadd protocol decoder
をクリックします。
右側のリストにデコードするプロトコルのリストが出てきます。リストのMemory
の24xx I2C EEPROM
を選択し、ダブルクリックします。
ダイアログが出てきますので、i2c (Inter-Integrated Circuit)
を選択してOKを押します。
SCL,SDAの信号をデコードして表示しました。I2Cアドレス50hにWriteでメモリアドレス00hにFFhのByteデータを書き込むコマンドであることがわかります。パルスを見てコマンドを判断するのは大変ですので、このデコード機能は非常に助かります。
データをランダムリードした場合のデコードです。I2Cアドレス50hにメモリアドレスF3アドレスをWriteし、I2Cアドレス50からデータを0ChをReadする手順となっています。
PulseView(sigrok)のビルド・インストール
最後に、PulseViewのFT232Hのドライバを修正のうえ、ビルドする方法をまとめておきます。
Linux用
Ubuntu 22.04 LTS
$ sudo apt-get install git-core gcc make autoconf automake libtool
$ sudo apt-get install git gcc g++ make autoconf autoconf-archive \
automake libtool pkg-config libglib2.0-dev libglibmm-2.4-dev libzip-dev \
libusb-1.0-0-dev libftdi1-dev libieee1284-3-dev libvisa-dev nettle-dev libavahi-client-dev \
libhidapi-dev check doxygen python3-numpy python3-dev python-gi-dev python-setuptools swig default-jdk
$ sudo apt-get install git-core g++ make cmake libtool pkg-config \
libglib2.0-dev libboost-test-dev libboost-serialization-dev \
libboost-filesystem-dev libboost-system-dev libqt5svg5-dev qtbase5-dev\
qttools5-dev sdcc
$ git clone git://sigrok.org/sigrok-util
FT232H用のドライバftdi-la
にパッチをあてます。
$ git clone https://github.com/hnz1102/ft232hbrkout.git
$ cp ft232hbrkout/doc/patch.ftdi-la-api.linux sigrok-util/cross-compile/linux
$ cd sigrok-util/cross-compile/linux
$ patch < patch.ftdi-la-api.linux
ビルドします。
$ ./sigrok-cross-linux
$ cd sigrok-util/cross-compile/linux/build/libsigrok
$ ./autogen.sh
$ ./configure
インストールします。
$ sudo make install
$HOME/srのところにインストールされます。
Windows用のビルド
Windows用のビルドもLinux上で行います。
$ git clone git://sigrok.org/sigrok-util
$ git clone https://github.com/mxe/mxe.git mxe-git
$ git clone https://github.com/hnz1102/ft232hbrkout.git
$ cp ft232hbrkout/doc/patch.ftdi-la-api.mingw sigrok-util/cross-compile/mingw
$ cp ft232hbrkout/doc/patch.sigrok-cross-mingw.* igrok-util/cross-compile/mingw
$ cd mxe-git
$ patch -p1 < ../sigrok-util/cross-compile/mingw/libusb1_upgrade.patch
$ sudo ln -s /usr/bin/python3 /usr/bin/python
$ sudo apt install p7zip-full autopoint intltool libtool lzip python3-mako ruby libtool-bin bison flex gperf libssl-dev nsis ruby-asciidoctor-pdf
$ make MXE_TARGETS=i686-w64-mingw32.static.posix MXE_PLUGIN_DIRS=plugins/examples/qt5-freeze gcc glib libzip libusb1 libftdi1 hidapi glibmm qtbase qtimageformats qtsvg qttranslations boost check gendef libieee1284 qtbase_CONFIGURE_OPTS='-no-sql-mysql'
$ cd sigrok-util/cross-compile/mingw
$ patch < patch.sigrok-cross-mingw.1
一度、FT232Hのドライバの修正をせずにコードをビルドします。
$ ./sigrok-cross-mingw
ビルドが成功した後、FT232Hのドライバにパッチをあてます。
$ patch < patch.ftdi-la-api.mingw
2回目のビルドをします
$ patch < patch.sigrok-cross-mingw.2
$ ./sigrok-cross-mingw
以下にWindows用のインストーラが出来ますので、これをWindowsPCにインストールします。
sigrok-util/cross-compile/mingw/build_release_32/pulseview/contrib/pulseview-0.5.0-git-3903edb-installer.exe
Windows用のビルドはたいへんややこしいので、ビルドが大変な方は、ここに公式ビルドではないですが、おいておきます。
また、Windowsではドライバの設定が必要です。上記のインストーラで同時にZadigというツールがインストールされます。
メニューのsigrokの下にZadigがあり、それを起動します。
Zadigを起動後、Options
のList All Devices
を選択します。
デバイスの一覧からUSBシリアル変換モジュールを選択し、USBIDが0403/6014
であることを確認し、DriverをWinUSB
にしてReplace Driver
でインストールされます。
これで、PulseViewから認識できるようになります。
PulseViewのデバイスを選択(Linux/Windows共通)
PulseViewを起動後、キャプチャするUSBシリアル変換モジュールを選択します。
ダイアログが表示されたら、Step 1でFTDI LA(ftdi-la)
を選択し、Step 2はUSB
にチェック、Step 3でScan for device using driver above
をクリックします。USBシリアル変換モジュールが検出されるとStep 4にデバイスが表示されますのでそれを選択してOKを押します。
これでサンプリング数を適切なものに選択して、Run
を押すとキャプチャがスタートします。Stop
でキャプチャをストップできます。
さいごに
基板を作らなくてもFT232Hのモジュールはいろいろなところで市販されていて、こんなに簡単にロジアナでができてしまうことに驚きます。PulseViewの使った安価なロジアナもいくつか市販されているのを見つけます。ドライバを用意すれば独自のキャプチャモジュールも作れそうです。FPGAでさらに周波数の高いサンプリングができるものを作ってみるのもいいかもしれません。
次回は、ESP32とK型熱電対ICを使って300℃程度まで測定できる温度測定ロガーを作りましたので、紹介したいと思います。ここまで読んでくださり、ありがとうございました。
Discussion