Linux

【Zynq7】実践1.LinuxにRTCを認識させる【I2Cデバイス編】

1.記事一覧

記事は複数回に分けて投稿します。XilinxのARM SoC (Zynq7020SoC)で動作する、YoctoベースのPetaLinuxを使わない素のLinuxをソースコードから構築する記事の続編です。

ZynqMP(arm64版)はこちら

エッジArm Linux構築編(Armv7)

エッジArm Linux実践編(Armv7)

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カラー液晶に変えました】

今回使用する環境です。

開発PCUbuntu20.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デバイス
有機EL(OLED)液晶
(当記事では未使用)
【追記:OLED液晶からTFTカラー液晶に変えました】
SSD1306液晶コントローラ(128×64ドット、SolomonSystech製)
液晶モジュールは秋月電子で購入しました。(白色タイプ)
データシート
ロジックアナライザZeroplus社 LAP-C 16064 USB接続タイプ

前編の流れです。

  1. FSBLでI2Cを有効化し、SCL、SDAの端子をコネクタに設定
  2. Linuxカーネルの再コンフィグとビルド
  3. Devicetreeの再生成とRTCデバイスの定義
  4. デバイスの配線
  5. RTCの接続チェック
  6. 再起動後に時刻が生きているか確認
  7. i2c-toolsでデバイスを操作
  8. ロジックアナライザで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構築時の設定がそのまま残っている場合を想定しています。

Bash
$ export CROSS_COMPILE=arm-linux-gnueabihf-
$ make ARCH=arm menuconfig

Device Drivers > Real Time Clock > Epson RX-8025SA/NBを有効”*”にします。

リビルドして完了です。

Bash
$ 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ソースコードを見てみます。

C
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を確認します。

Bash
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″とあるのでデバイスファイルを確認します。

Bash
zynq@zyboz7:~$ ls /dev |grep rtc
rtc
rtc0
zynq@zyboz7:~$

デバイスが認識しているようなので、sysfsでrtc0のデバイス情報を確認します。

Bash
zynq@zyboz7:~$ ls /sys/class/rtc/rtc0
date device max_user_freq power subsystem uevent
dev hctosys name since_epoch time
zynq@zyboz7:~$

初めて見るのもあり、よくわからないので、もう少し詳しく見てみます。

Bash
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はこうなっているんですね。リンク先で同じように見てみます。

Bash
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ファイルにデバイスを識別できる情報があると聞いたことがあるので見てみます。

Bash
zynq@zyboz7:~$ cat /sys/class/rtc/rtc0/name
rtc-rx8025 0-0032
zynq@zyboz7:~$

RX8025としてLinuxで認識されているようですね。まずはひと安心して良さそうです。
時刻が読めそうなdateとtimeファイルを覗いてみます。

Bash
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時刻を見てみます。

Bash
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オプションつけてみてとのことなので詳細を見てみます。

Bash
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をつけます。

Bash
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

試しに先程のコマンドで詳細を見てみましょう。

Bash
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はバックアップ電源で動作し続けるはずです。以下をやってみます。

  1. Etherを外してNTPデーモンで時刻同期しないようにする
  2. シリアルコンソールでログインし、シャットダウンする
  3. zybo z7の電源を落とす
  4. 10分程度待った後、電源を投入する(youtubeでも見て待ちます)
  5. シリアルコンソールでログインし、時刻を確認する

実行結果です。時刻は問題なく生きてました。(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の組み合わせのせいかもしれません。サポート終了か別のところに移ったかのどちらかでしょうか。

Bash
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ボード上でセルフビルドできます。(組込み畑に居ると組込みボードでセルフビルドをすることに抵抗感が…)

ビルドツールのインストール

Bash
$ sudo apt install git build-essential

ソースコードの準備

Bash
$ mkdir i2ctools_wk
$ cd i2ctools_wk
$ git clone https://github.com/mozilla-b2g/i2c-tools.git
$ cd i2c-tools

ビルド(セルフで10秒ほど)

Bash
$ make

インストール(soキャッシュも念の為更新)

Bash
$ sudo make install
$ sudo ldconfig

9-2. i2cdetectでバス上のデバイスをスキャン

i2c識別子を確認します。1項目のi2c-*を確認します。”0″ですね。i2cポートが複数有効にしてある場合は2行以上表示されます。

Bash
zynq@zyboz7:~$ sudo i2cdetect -l
i2c-0 i2c Cadence I2C at e0004000 I2C adapter
zynq@zyboz7:~$

write要求でスキャンを行います。引数の”0″は先程のi2c識別子です。
しかし、アドレスがスキップされました。SMBus Quickに対応していないのが原因のようです。理由はよくわかりません。

Bash
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要求でやってみます。うまく言ったようです。

Bash
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で強制送信します。
注意点ですが、これは実験なので、システムの挙動がおかしくなることがあります。

Bash
$ 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レジスタ内容

以下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(時+分)設定時刻の一致=なし
以下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内容の確認読出し
systemd-timesyncdの時刻更新パケット

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 時計精度調整レジスタ]
水晶発振器精度調整値読出し
sysfsのRTC読出し

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〜1621~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端子しかないぞ…)

ABOUT ME
sh-goto
組込エンジニア. 最近の遊びはLinuxの低レイヤいじりとXilinxのZynqとFPGAを使った電子工作
関連記事