1.記事一覧
記事は複数回に分けて投稿します。XilinxのARM SoC (Zynq7020SoC)で動作する、YoctoベースのPetaLinuxを使わない素のLinuxをソースコードから構築する記事の続編です。
エッジArm Linux構築編(Armv7)
- 【Zynq7】Yoctoを使わずに素のLinuxをソースから構築する
- 【Zynq7】1.FSBL(1stStageBootLoader)の構築
- 【Zynq7】2.U-bootの構築
- 【Zynq7】3.Linuxカーネルの構築
- 【Zynq7】4.Zyboz7ボードのデバイスツリー構築
- 【Zynq7】5.RootFSのマウント(Ubuntu18.04)
- 【Zynq7】6.カーネルモジュールの配置
- 【Zynq7】番外編1.u-bootが途中で止まる問題
- 【Zynq7】番外編2.MACアドレスをQSPIFlashから読み出して設定する
エッジArm Linux実践編(Armv7)
- 【Zynq7】実践1.LinuxにRTCを認識させる【I2Cデバイス編】 ←本ページ
- 【Zynq7】実践2. 温度センサをPython+Dockerで使う【I2Cデバイス】
- 【Zynq7】実践3. 組込みLinuxでコンテナ導入に苦労した話【Docker Engine編】
- 【Zynq7】実践4. LinuxでSPIを使う【液晶編1】
- 【Zynq7】実践5. ZynqPLのGPIOをLinuxで使う【液晶編2】
- 【Zynq7】実践6. PythonでSPI液晶を使う【液晶編3】
- 【Zynq7】閑話. SPI バス周波数が上がらない問題
2.【概要】LinuxでI2Cデバイスを使ってみる【前編】
このテーマでは2回に分けてArm LinuxでI2Cデバイスを使うことに挑戦してみたいと思います。前編ではI2CをLinuxで有効化して、RTC(リアルタイムクロック)デバイスを認識させます。
今までのLinux構築ではZynqSoCのI2C周辺機能IPがそもそも有効になっていないため、使うには以下が必要です。
- I2CでIPを有効化したBitstreamを生成する
- Linuxデバイスツリーを更新し、I2Cがシステムから使えるようにする
Raspberrypiだと簡単に使えてしまいますが、それはラズパイ開発チームの開発レベルの高さゆえですね。今回I2Cをセッティングしてみて改めてRaspberrypiの開発者のレベルの高さを感じました。
今回使用するRTC(RX-8025NB)はLinux Kernelでデバイスドライバが既にサポートされています。Kernelにドライバを含めるように再コンフィグして再ビルドします。また、起動時にProbeされるようにデバイスツリーにもRTCを定義します。
後編ではI2Cの温度センサと有機EL(OLED)液晶モジュールを使ってPythonで何かアプリを作ってみたいと思います。【追記:OLED液晶からTFTカラー液晶に変えました】
今回使用する環境です。
開発PC | Ubuntu20.04 |
開発ツール | Xilinx社 Vivado・Vitis v2022.2 |
ターゲットボード | Digilent社製 Zybo z7-20 SoC:Xilinx社 Zynq7020 armv7hf (Cortex-A9×2、666MHz) SDRAM:DDR3 1GB u-boot:v2022.01 Linux Kernel: v5.15 RootFs:Ubuntu22.04 jammy armv7l |
I2Cデバイス :RTC | RTC(リアルタイムクロック):RX-8025NB(EPSON製) 秋月電子で購入しました。 秋月RTCモジュール取説 データシート Linux デバイスドライバソースコード |
I2Cデバイス :温度センサ (当記事では未使用) | ADT7410(Analog Devices製) 秋月電子で購入しました。 秋月温度センサモジュール取説 データシート |
I2Cデバイス : (当記事では未使用) | 【追記:OLED液晶からTFTカラー液晶に変えました】 液晶モジュールは秋月電子で購入しました。(白色タイプ) データシート |
ロジックアナライザ | Zeroplus社 LAP-C 16064 USB接続タイプ |
前編の流れです。
- FSBLでI2Cを有効化し、SCL、SDAの端子をコネクタに設定
- Linuxカーネルの再コンフィグとビルド
- Devicetreeの再生成とRTCデバイスの定義
- デバイスの配線
- RTCの接続チェック
- 再起動後に時刻が生きているか確認
- i2c-toolsでデバイスを操作
- ロジックアナライザでI2C通信内容を読む
3.I2Cを有効化
VivadoでI2Cを設定していきます。プロジェクトは以前のLinux構築で作成したものを改変します。デフォルトでI2Cが無効化されているので、有効にしていきます。
3-1. VivadoでI2C周辺機能IPの有効化
IP INTEGRATOR>Open Block Designでブロック図を開きます。
PSブロックをダブルクリックし、設定を開きます。Peripheral I/O Pinsを開き、I2C0を探し、チェックをつけ有効にします。
右にスクロールし、端子を”EMIO”に設定します。PSとPL(FPGA)で割り当てられているIO端子が別れているので、使用したいPmodコネクタによって分けます。PS側端子はPmodのJFのみ、PLはJA、JB、JC、JD、JEになるので、EMIO(ExtendedMIO)に接続して、PSの外の配線をして、PL側に出します。最後にOKを押します。
3-2. I2C信号と端子の紐づけ
PSブロックのIIC_0ポートが出現されるので、ポートを右クリックし、”Make External”を押して、外部信号を引き出します。
信号名が分かりづらいので”I2C_0″にしておきます。ご自由にどうぞ。
ここで保存します。”Ctrl+S”。グルグルマークが出て少し時間かかります。
次にプロジェクトの上位階層(wrapper)を再生成します。design_1.bdを右クリックし、”Create HDL wrapper”を実行します。optionsはデフォルトでOKしてください。グルグルマーク出て少し時間かかります。
design_1_wrapperをダブルクリックし、verilog(.v)ファイルを開きます。
I2Cの新しい信号がSCL、SDAの2本追加されています。この信号名をメモしておきます。
最後に制約ファイル(.xdc)でI2Cの信号とSoCの端子を紐づけます。制約ファイルはzyboz7ボードメーカのDigilentが配布しているものをベースに使用しています。
今回はJBコネクタのjb[2]=SCL、jb[3]=SDA端子に割り当てます。
端子割当ての決め方ですが、PmodのJBコネクタを選んだのは適当です(単にボードの端から使いたかった)。2・3番端子にしたのは市販のPmodモジュールのピンアサインと同じにしたかったからです。例えばPmod ToFなどです。
<出典:https://digilent.com/reference/pmod/pmodtof/start>
vivadoの設定はこれで終了したので、FSBL構築記事を参考にBitstreamを生成して、Vitisにプラットフォーム情報をインポートしてFSBLをリビルドし直します。それができたらboot.binで再結合です。(面倒ですね…)
4.LinuxカーネルでRTCドライバ有効化
Linuxカーネル構築記事を参考にmenuconfig画面を使用して有効化していきます。
Linux構築時の設定がそのまま残っている場合を想定しています。
$ export CROSS_COMPILE=arm-linux-gnueabihf-
$ make ARCH=arm menuconfig
Device Drivers > Real Time Clock > Epson RX-8025SA/NBを有効”*”にします。
リビルドして完了です。
$ make ARCH=arm UIMAGE_LOADADDR=0x8000 uImage
5.デバイスツリー再生成
デバイスツリー構築記事を参考にVivadoプラットフォームファイルから再生成します。
system-top.dtsにi2cが追加されています。
個別ボード用はdtsiファイルでincludeするようにしていたので、そこにRTCの追記をします。
I2Cバスの下に連なるようにツリー構造で記述します。
RX-8025のデータシートを読んでスレーブアドレスを調べます。”0x32″ですね。
“rtc@32″はRX-8025のスレーブアドレス0x32を指します。
また、reg = <0x32>;も同じくです。
compatible はデバイスドライバの種別名を記述します。一致するドライバがprobeされます。種別名に何を入れるかわからないのでLinux driverソースコードを見てみます。
static const struct i2c_device_id rx8025_id[ ] = {
{ "rx8025", model_rx_8025 },
{ "rx8035", model_rx_8035 },
{ }
};
おそらくですが、ここの文字列と一致するとprobeしてくれるのだと思います(違ったらすみません)。
rtcの記述が終わったらdtcコンパイルします。
6.デバイスの配線
これまでで起動用SDカードの準備が終わりました。I2Cデバイスを配線します。
【追記:OLED液晶のSSD1306はやめました。TFTカラー液晶を今後使います】
6-1. I2Cバスプルアップ抵抗について
I2Cはオープンドレイン双方向バスのため、バスを電源にプルアップする必要が有ります。
プルアップ抵抗はRTCモジュール、温度センサモジュールの基板上に実装されていて、ジャンパをはんだづけでショートすると使えます(デフォは無接続)。液晶モジュールは回路図がどこかわからなかったのですが、基板上に抵抗が搭載されテスタで確認するとプルアップされていました。
液晶モジュールが固定でプルアップされているので、RTCと温度センサのプルアップは無効(デフォルトのジャンパは無接続)のままにします。
6-2. RTCバックアップ電源
RTCは基板やマザーボード電源がOFFになってもボタン電池などで駆動し続けるようにするのが一般的です。今回は安定化電源で代用します。Linuxをシャットダウンしても発振状況がわかるように、INTA端子にLEDを接続して1Hz割込み出力を設定してLED点滅させてみます。
実際の様子
7.RTCの接続チェック
準備ができたので、RTCバックアップ電源を投入後、Zyboz7ボードの電源を投入しLinuxを起動します。今回はEthernetでインターネットに接続して、NTPd(NTPデーモン)による時刻同期が動作するようにします。
7-1. RTCが認識されているか確認する(dmesg, /dev/rtcN, /sys/class/rtc)
dmesgを確認します。
zynq@zyboz7:~$ dmesg | grep rtc
[ 0.935179] rtc-rx8025 0-0032: power-on reset detected, date is invalid
[ 0.940671] rtc-rx8025 0-0032: registered as rtc0
[ 0.944235] rtc-rx8025 0-0032: power-on reset detected, date is invalid
[ 0.949543] rtc-rx8025 0-0032: hctosys: unable to read the hardware clock
zynq@zyboz7:~$
パワーオンリセットフラグを検出して、RTC内部の時刻が無効であると言っています。先ほどバックアップ電源を投入したので正しいですね。ただ2回出ているのはよくわかりません。
“hctosys”はhardware clock to system clockの略でハードウェアクロック=RTC、システムクロック=OSの時刻を指します。RTC時刻が使えないので起動後にハードウェアクロックからシステムクロックの時刻を初期化できない、ということだと思います。バックアップ電源を投入して初めての起動だと考えると正しい動作ですね。NTP時刻で補正後に再起動すると、おそらくRTCからシステムクロックが設定されます。
“rtc-rx8025 0-0032: registered as rtc0″とあるのでデバイスファイルを確認します。
zynq@zyboz7:~$ ls /dev |grep rtc
rtc
rtc0
zynq@zyboz7:~$
デバイスが認識しているようなので、sysfsでrtc0のデバイス情報を確認します。
zynq@zyboz7:~$ ls /sys/class/rtc/rtc0
date device max_user_freq power subsystem uevent
dev hctosys name since_epoch time
zynq@zyboz7:~$
初めて見るのもあり、よくわからないので、もう少し詳しく見てみます。
zynq@zyboz7:~$ ls -l /sys/class/rtc/rtc0
lrwxrwxrwx 1 root root 0 Apr 8 2022 /sys/class/rtc/rtc0 -> ../../devices/soc0/axi/e0004000.i2c/i2c-0/0-0032/rtc/rtc0
zynq@zyboz7:~$
シンボリックリンクでした。なるほど、sysfsはこうなっているんですね。リンク先で同じように見てみます。
zynq@zyboz7:~$ ls -l /sys/devices/soc0/axi/e0004000.i2c/i2c-0/0-0032/rtc/rtc0/
total 0
-r--r--r-- 1 root root 4096 Mar 3 22:29 date
-r--r--r-- 1 root root 4096 Mar 3 22:29 dev
lrwxrwxrwx 1 root root 0 Mar 3 22:29 device -> ../../../0-0032
-r--r--r-- 1 root root 4096 Apr 8 2022 hctosys
-rw-r--r-- 1 root root 4096 Mar 3 22:29 max_user_freq
-r--r--r-- 1 root root 4096 Mar 3 22:27 name
drwxr-xr-x 2 root root 0 Mar 3 22:29 power
-r--r--r-- 1 root root 4096 Mar 3 22:29 since_epoch
lrwxrwxrwx 1 root root 0 Apr 8 2022 subsystem -> ../../../../../../../../class/rtc
-r--r--r-- 1 root root 4096 Mar 3 22:29 time
-rw-r--r-- 1 root root 4096 Apr 8 2022 uevent
zynq@zyboz7:~$
“0-0032″がおそらくI2Cスレーブアドレスを指していそうです。
nameファイルにデバイスを識別できる情報があると聞いたことがあるので見てみます。
zynq@zyboz7:~$ cat /sys/class/rtc/rtc0/name
rtc-rx8025 0-0032
zynq@zyboz7:~$
RX8025としてLinuxで認識されているようですね。まずはひと安心して良さそうです。
時刻が読めそうなdateとtimeファイルを覗いてみます。
zynq@zyboz7:~$ cat /sys/class/rtc/rtc0/date
2023-03-03
zynq@zyboz7:~$ cat /sys/class/rtc/rtc0/time
13:43:59
zynq@zyboz7:~$
時刻が設定されてリアルタイムで動いているみたいです。ここで現在時刻を確認するとUTC時刻(協定世界時)と一致していました。インターネット接続してNTPサーバに時刻同期がデフォルトで働いているので正確な時刻同期をしてくれたようです。
【注意】ページ最後でロジックアナライザによるI2Cバス通信ログを取りますが、sysfsのRTC時刻読出しでは実際にRTCデバイスから読み出しせず、キャッシュされた時刻が返るようです。
7-2. hwclockコマンドによるRTC時刻確認
hwclockコマンドでRTCデバイスに時刻を設定したり、読み出したりできます。
よく利用されるコマンドらしいので、しっかり使えるか確認します。
Ubuntuのマニュアルでマニュアルを見ながら実行してみます。
まずはRTC時刻を見てみます。
zynq@zyboz7:~$ hwclock --show --utc
hwclock: Cannot access the Hardware Clock via any known method.
hwclock: Use the --verbose option to see the details of our search for an access method.
zynq@zyboz7:~$
あら…残念ながらだめだったみたいです。–verboseオプションつけてみてとのことなので詳細を見てみます。
zynq@zyboz7:~$ hwclock --show --verbose
hwclock from util-linux 2.37.2
System Time: 1677852018.507379
Trying to open: /dev/rtc0
hwclock: cannot open /dev/rtc0: Permission denied
No usable clock interface found.
hwclock: Cannot access the Hardware Clock via any known method.
zynq@zyboz7:~$
なるほど、デバイスファイルopenにroot権限が必要でした。sudoをつけます。
zynq@zyboz7:~$ sudo hwclock --show
2023-03-03 23:03:10.544260+09:00
zynq@zyboz7:~$
しっかり読めました。しかし、なぜかUTC時刻ではなくローカル時刻(Asia/Tokyoに設定済)が表示されています。Ubuntuのマニュアルによると、RTC時刻がUTCかローカル時刻かはRTCデバイス上の情報では判別不可なようです。つまりシステムがUTCかローカル時刻かどちらかあらかじめ決めて設定したということです。以下抜粋(機械翻訳)。
--utc --localtime ハードウェア・クロックを協定世界時 (Universal Coordinated Time: UTC) と ローカルタイムのどちらにするか (しているか) を指定する。 UTC にするかローカルタイムにするかはユーザの選択しだいだが、 時計の内部にはどちらを選択したかを記録する場所はない。 したがって、ユーザーはこのオプションで自分の選択を hwclock に伝えなければならない。 これらの指定を間違ったほうにしたり (あるいはデフォルトを勘違いして 両方とも指定しなかったり) すると、ハードウェア・クロックの設定や クロックへの問い合わせの結果はめちゃめちゃになってしまうだろう。 --utc も --localtime も指定しなかった場合のデフォルトは、最後に hwclock を使って時計を合わせたとき (つまり --set, --systohc, --adjust オプションを指定しての実行が成功したとき) に指定していた方になる。 このときの選択は adjtime ファイルに記録されている。 adjtime ファイルがなかったときのデフォルトはローカルタイムになる。https://manpages.ubuntu.com/manpages/jammy/ja/man8/hwclock.8.html
試しに先程のコマンドで詳細を見てみましょう。
zynq@zyboz7:~$ sudo hwclock --show --verbose
hwclock from util-linux 2.37.2
System Time: 1677852763.707650
Trying to open: /dev/rtc0
Using the rtc interface to the clock.
Assuming hardware clock is kept in UTC time. #<---hwclockがUTC時刻で保持していると仮定
Waiting for clock tick…
ioctl(3, RTC_UIE_ON, 0): Invalid argument
Waiting in loop for time from /dev/rtc0 to change
…got clock tick
Time read from Hardware Clock: 2023/03/03 14:12:44
Hw clock time : 2023/03/03 14:12:44 = 1677852764 seconds since 1969
Time since last adjustment is 1677852764 seconds
Calculated Hardware Clock drift is 0.000000 seconds
2023-03-03 23:12:43.712913+09:00
zynq@zyboz7:~$
“ハードウェア クロックが UTC 時間で保持されていると仮定します。(機械翻訳)”だそうです。つまりシステムはUTCとして時刻設定したはずです。その結果Tokyoのローカル時刻(+9時間ですね)に計算されて表示されたと考えるとRTC自体はUTC時刻が動いているということで問題無さそうです。とりあえず正常に動いてそう…
余談ですが、上記に”ioctl(3, RTC_UIE_ON, 0): Invalid argument”とあります。manページにはRTCの1Hz定周期割込みを有効化するための関数とあります。コマンドを実行した瞬間の時刻ではなく、時刻更新直後の時刻までブロッキングするために割込みを有効化するようです。確かにそうしないと最大1秒未満の誤差が発生しますよね。
今回RX-8025には割込み出力端子と定周期割込み機能がありますが、割込み信号線を配線していませんので当然使えないです。ZynqSoCはPL側からPS側に発火させることができる割込み番号と信号線が用意されていますので、機会があればチャレンジしてみたいと思います。課題となるのはデバイスツリーの書き方がわからないことですね。
8.再起動後に時刻が生きているか確認
さて、RTCの主目的である、システム再起動後に時刻が生きているか確認します。
RTCはバックアップ電源で動作し続けるはずです。以下をやってみます。
- Etherを外してNTPデーモンで時刻同期しないようにする
- シリアルコンソールでログインし、シャットダウンする
- zybo z7の電源を落とす
- 10分程度待った後、電源を投入する(youtubeでも見て待ちます)
- シリアルコンソールでログインし、時刻を確認する
実行結果です。時刻は問題なく生きてました。(last loginから1時間半…ついyoutubeが)
Welcome to Ubuntu 22.04 LTS (GNU/Linux 5.15.0-zyboz7_custom armv7l)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Last login: Sat Mar 4 00:44:19 JST 2023 on ttyPS0 #<---ログアウト時刻
zynq@zyboz7:~$ date
Sat Mar 4 02:07:48 JST 2023
zynq@zyboz7:~$
zynq@zyboz7:~$
zynq@zyboz7:~$ sudo hwclock --show
2023-03-04 02:08:12.520256+09:00 #<--ちゃんと時刻生きとる
zynq@zyboz7:~$
9.i2c-toolsでデバイスを操作
ひとまずRTCはしっかり認識して動作をしていることが確認できました。しかし、何らかのトラブルで動かなかったときのために、解析用のツールを使ってみます。
今回は有名なi2c-toolsを使用します。
aptでインストールしようとすると以下のようにパッケージが見つからないので、いろいろ原因を探していたのですが、どうやらarmv7hfでUbuntu22.04の組み合わせのせいかもしれません。サポート終了か別のところに移ったかのどちらかでしょうか。
zynq@zyboz7:~$ sudo apt-get install i2c-tools
Reading package lists… Done
Building dependency tree… Done
Reading state information… Done
E: Unable to locate package i2c-tools
zynq@zyboz7:~$
9-1. ソースビルドによるi2c-toolsのインストール
Githubにi2c-toolsのソースコードとビルド方法、インストール方法が公開されていたので、ソースからインストールしていきます。ホストPCでクロスビルドの必要はありません。zybo z7ボード上でセルフビルドできます。(組込み畑に居ると組込みボードでセルフビルドをすることに抵抗感が…)
ビルドツールのインストール
$ sudo apt install git build-essential
ソースコードの準備
$ mkdir i2ctools_wk
$ cd i2ctools_wk
$ git clone https://github.com/mozilla-b2g/i2c-tools.git
$ cd i2c-tools
ビルド(セルフで10秒ほど)
$ make
インストール(soキャッシュも念の為更新)
$ sudo make install
$ sudo ldconfig
9-2. i2cdetectでバス上のデバイスをスキャン
i2c識別子を確認します。1項目のi2c-*を確認します。”0″ですね。i2cポートが複数有効にしてある場合は2行以上表示されます。
zynq@zyboz7:~$ sudo i2cdetect -l
i2c-0 i2c Cadence I2C at e0004000 I2C adapter
zynq@zyboz7:~$
write要求でスキャンを行います。引数の”0″は先程のi2c識別子です。
しかし、アドレスがスキップされました。SMBus Quickに対応していないのが原因のようです。理由はよくわかりません。
zynq@zyboz7:~$ sudo i2cdetect -y 0
Warning: Can't use SMBus Quick Write command, will skip some addresses
0 1 2 3 4 5 6 7 8 9 a b c d e f
00:
10:
20:
30: -- -- UU -- -- -- -- --
40:
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60:
70:
zynq@zyboz7:~$
read要求でやってみます。うまく言ったようです。
zynq@zyboz7:~$ sudo i2cdetect -r -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- UU -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
zynq@zyboz7:~$
フォントサイズがズレて見にくいのでスクショも貼ります。
読み方ですが縦軸+横軸=デバイスが存在するスレーブアドレス(Hex)です。
0x32と0x3cと0x48の3つでそれぞれRTC、液晶、温度センサです。
“UU”と表示されているのはシステムが使用中を示します。RTCはデバイスツリーに定義してシステムと深く連携しているので簡単にアクセスできないように保護されているのだと思います。
9-3. RTCの定周期割込みでLED点滅実験
i2csetコマンドを使用するとデータをi2cデバイスに書き込むことができます。INTA端子に確認用LEDを接続したので、1Hz定周期点滅をさせるように設定して遊んでみます。
【注意】RTC関連のシステムサービスに影響が出る可能性があるので、最後に再起動してください。
まずRX-8025のアプリケーションノートで定周期割込み信号設定のレジスタを確認します。
Control1レジスタにCT2=0、CT1=1、CT0=1を設定すれば良いので、Control1=0x03を書き込めば良さそうです。
次にI2C転送データを組んでいきます。書込み転送です。
- 1byte目:スレーブアドレス=0x32
- 2byte目:[上位4bit]レジスタアドレス=0xE(Control1)、[下位4bit]転送モード=0x0
- 3byte目:Control1レジスタ設定値=0x03
転送コマンドを実行してRTCのControl1レジスタを設定します。
RTCはシステムで予約されているため、-fで強制送信します。
注意点ですが、これは実験なので、システムの挙動がおかしくなることがあります。
$ sudo i2cset -f -y 0 0x32 0xe0 0x03 i
LEDが点滅し始めました。(GIF変換で点滅早くなってますが実際は1Hzです。)
10.ロジックアナライザでI2C通信内容を読む
電子工作用に持っているロジックアナライザがあるので、実際の通信の様子を見てみたいと思います。RTC水晶発振器の誤差やクロストークによる通信エラーも観測できたので、そこにも触れます。
ロジックアナライザ | Zeroplus社 LAP-C 16064 USB接続タイプ |
トリガ条件 | SCL信号を立ち下がりエッジに設定 |
サンプリング周波数 | 10MHz (SCL=340kHz) |
トレースメモリ | 64KB(MAX) 圧縮機能有効 |
プロトコルアナライザ | I2C mode |
10-1. 何もしないアイドル時の通信状況を見る(NTP時刻補正)
アイドル時にどのくらいの頻度でRTCの通信があるかを見るため、トリガをかけて放置します。
結果:11分弱(10:50くらい)の一定周期で通信が発生
定周期通信が発生していました。11分弱の中途半端な時間周期にしているのは、おそらく他の定周期通信プロセスとI2C通信が衝突する可能性を下げるためだと思います。最小公倍数時間周期にアクセスが集中してバスが輻輳するのを防ぐためですね。
調べると定周期通信しているのはNTPデーモンが時刻合わせを11分周期で行うからのようです。RedHatさんによれば通称”11分モード”と呼ぶらしいです。
参考<RedHat 19.18. ハードウェアクロック更新の設定>
ちなみにUbuntu22.04の場合、NTPデーモンはsystemd-timesyncdです。
続いてNTPデーモンの定期通信と思われるバス通信内容を読んでみます。
初めて知ったのですが、RTC時刻再設定だけでなく、RTC水晶発振器の周波数精度補償をしていました(これはすごい)。NTPデーモンが使えないオフラインのスタンドアローン環境だった場合時刻がかなりズレそうですね。
パケット番号 | RTCレジスタ | 内容 |
---|---|---|
1 | 以下write Seconds Minutes Hours Days Months Years Digital Offset | [Seconds – Years 時計カウンタレジスタ] 日時と時刻をNTP時刻から更新 [Digital Offset 時計精度調整レジスタ] 水晶発振器32768Hzの精度補正を行う 0x23を設定しているので進み補正 ※RTC発振器がどのくらいずれているか逆算 調整量は0x23= -103.7×10-6≒ 誤差約 – 0.01% 搭載水晶発振周波数=↓ =調整量×基準発振周波数+基準発振周波数 =-103.7×10-6×32768+32768 =32764.6Hz → 誤差0.01%だと無補正で24時間で約8秒ずれる計算です。 【追記】さすがに誤差大き過ぎる気がするので、 データシートもう一回確認したらRTCはもっと高精度でした。 RX-8025は±5×10-6(25℃、月差13秒)の精度補償をされて 出荷されていますので、誤差20倍になっていることになります。 NTPデーモンの精度調整アルゴリズムもある程度誤差があり、 むしろ精度悪化してる?可能性もあります。 時間があったらこの辺詳しく調べてみたいです。 |
2、3 | 以下read Control2 | [Control2 制御2レジスタ] 読出し値=0x20 bit7:電圧低下検出基準電圧設定値=2.1V bit6:電圧低下検出結果=検出なし bit5:発振停止検出結果=検出なし bit4:パワーオンリセット検出結果=検出なし bit3:FOUT出力=OFF bit2:INTA定周期割込み端子出力=OFF bit1:アラームW(曜日+時+分)設定時刻の一致=なし bit0:アラームD(時+分)設定時刻の一致=なし |
4 | 以下write Control2 | [Control2 制御2レジスタ] 書込み値=0x20 →パケット2のbit6~4、2~0の検出フラグのリセットと思われる |
5、6 | 以下read Control2 | [Control2 制御2レジスタ] パケット3の書込みが正確に行われたかの確認読出し |
7、8 | 以下read Seconds Minutes Hours Days Months Years Digital Offset | パケット1内容の確認読出し |
10-2. SysfsのRTC読出し
トリガ後、以下のsysfsのファイル読出しします。
$ cat /sys/class/rtc/rtc0/date
$ cat /sys/class/rtc/rtc0/time
結果:コマンドで時刻表示した後の約1秒後に通信が発生する
どうやらリアルタイムで更新される時刻はキャッシュされた時刻のようです。実際にRTCの値を読んでいないことに注意しないといけません。sysfsをもっと勉強しないとですね。
バス通信内容を読んでみます。
パケット番号 | RTCレジスタ | 内容 |
---|---|---|
以下の通信はSysfs読み出し終了後に行われる | ||
1、2 | 以下read Control2 | [Control2 制御2レジスタ] 読出し値=0x20 bit7:電圧低下検出基準電圧設定値=2.1V bit6:電圧低下検出結果=検出なし bit5:発振停止検出結果=検出なし bit4:パワーオンリセット検出結果=検出なし bit3:FOUT出力=OFF bit2:INTA定周期割込み端子出力=OFF bit1:アラームW(曜日+時+分)設定時刻の一致=なし bit0:アラームD(時+分)設定時刻の一致=なし |
3,4 | 以下read Seconds Minutes Hours Days Months Years Digital Offset | [Seconds – Years 時計カウンタレジスタ] 日時と時刻を読出し [Digital Offset 時計精度調整レジスタ] 水晶発振器精度調整値読出し |
10-3. hwclockコマンド実行
トリガ後、hwclockコマンドで時刻表示してみます。
$ sudo hwclock --show
結果:I2Cバス通信発生。RTCから時刻更新直後の値を読み出している模様
時刻更新直後のタイミングで読み出すために、定周期割込みを有効にしようとして失敗します。理由は割込み信号の配線とデバイスツリー定義をしていないためです。そのためポーリングで時刻が変わるまで通信し続けるようです。これはシステム負荷が高そうです。割込みを実装したくなります。
バス通信内容です。
パケット番号 | RTCレジスタ | 内容 |
---|---|---|
1,2 | 以下 read Control2 | [Control2 制御2レジスタ] 読出し値=0x20 bit7:電圧低下検出基準電圧設定値=2.1V bit6:電圧低下検出結果=検出なし bit5:発振停止検出結果=検出なし bit4:パワーオンリセット検出結果=検出なし bit3:FOUT出力=OFF bit2:INTA定周期割込み端子出力=OFF bit1:アラームW(曜日+時+分)設定時刻の一致=なし bit0:アラームD(時+分)設定時刻の一致=なし |
3,4 | 以下 read Seconds Minutes Hours Days Months Years Digital Offset | [Seconds – Years 時計カウンタレジスタ] 日時と時刻を読出し [Digital Offset 時計精度調整レジスタ] 水晶発振器精度調整値読出し |
5〜162 | 1~4パケットの内容を繰り返しポーリング | |
163,164 | 以下 read Seconds Minutes Hours Days Months Years Digital Offset | Seconds値=0x44 BCD形式に変換すると44秒 |
165 | 以下 write? Control2 Seconds | このパケットは配線のクロストークによりおかしくなっている 0x99,0x88の2バイトを送信 理由がわからない書込みをしているが、信号波形を見たところ、スパイク状の明らかに短いパルスがSCLに載っていたので、プロトコルアナライザが誤解析していた。 |
166,167 | 以下 read Seconds Minutes Hours Days Months Years Digital Offset | Seconds値=0x45 ←値が更新された BCD形式に変換すると45秒 |
170,171 | 以下 read Seconds Minutes Hours Days Months Years Digital Offset | Seconds値=0x45 BCD形式に変換すると45秒 念の為2回読み出して一致することを確認している |
パケット165の通信がおかしい原因について(クロストーク)
通信内容がおかしかった信号波形を載せます。SDA信号立ち下がりのときにSCL信号が引きずられるように一瞬パルスが発生しています。SCLのクロック周期の約4%の短さなので明らかにI2Cマスタが出力した信号ではありません。
このように配線が悪く、誘導性、容量性の結合ができてしまうと、他の信号の影響を受けることがあります。これを”クロストーク”と呼びます。今回は340kHzの速さの信号をブレッドボード上でジャンパ配線したのが原因です。
対策としてはSCL、SDA信号線にGND線を巻きつける(ツイストペア)などがあります。また、クロックを低く設定します。デバイスツリーでクロックを定義できるかもしれないので、今後の課題にします。
今回のログでは171回の通信のうち、クロストーク数回ほどありました。私が実際に観測したのは初めてです。低い確率で出る不具合となるので、より一層気をつけることにします。(再現性のないバグは本当に困ります…)
クロストークについてはここがわかりやすいです。
【追記】今回使用したPmodのJB端子は高速Pmodという端子だったようです(公式リファレンス“16.4 High-Speed Pmod”)。つまり、差動信号線としてプリント基板上で配線されています。ペア信号の間の距離を最小限にしてインピーダンス整合を取っているわけです。
今回この高速信号向けPmod端子を使用してしまったので、SDA、SCL信号がペア信号扱いになり、クロストークの原因になった可能性があります。リファレンスにもシングルエンド(単線信号)として使うときは気をつけろとあります。今回は私の端子選定ミスですね。勉強になりました。(しかし、標準Pmod使おうにもzyboz7ボードには1端子しかないぞ…)