KZ80マイコン(Z80+Z80 PIO)で、ソフトウェアでI2CをしゃべってI2C RTC(DS3231)の日時データを取得してみたかったのです。またRTCか!
やってみたかったこと
先日買った専門書ではH8とかPICで信号線をパタパタしてI2Cをしゃべる例が出ていました。I2Cマスターがクロック信号を生成しているので、I2CスレーブとなるRTCとかROMとかのデバイスをZ80でもコントロールしやすそう。
Z80だとスピードはでないと思いますが、多様なデバイスがつながったりして楽しそうです。ほぼ「できるかな?」ノリです。いつものことですが…
Z80 PIOを使ったサンプルをみつけた
ネットをいろいろと見ていて、z80.info(http://z80.info/)さんからリンクされている以下の資料で、Z80 PIOから2本の信号線を伸ばして、10kΩでプルアップし、PIOの端子のモードを入力/出力を切り替えてI2Cするする回路図とZ80アセンブラのサンプルコードがありました。
http://www.blunk-electronic.de//train-z/pdf/howto_program_I2C.pdf
回路図
上記の参考資料に従って、KZ80-ZilogI/O(https://github.com/kuninet/KZ80_PIOSIO)を使ってI2C RTC(DS3231)を接続することを画策しました。
左側の14ピン端子は、KZ80-ZilogI/O(KZ80-PIOSIO)の2つついている外部入出力端子で、14ピンのMILコネクタです。Z80 PIOのポートB(8bit分)につながってます。
I2Cのマスターとなって他の機器をコントロールするためには、シリアルクロック(SCL)とシリアルデータ(SDA)の2本があれば良いらしいです。2本の電線に1つとか2つのマスターと複数のスレーブ機器がぶら下がります。(マスターは複数あっても良いらしい。今回はKZ80マイコンがマスター)
信号が衝突することがあるので、接続するポートはオープンドレイン出力が望ましいらしいけど、PPIとか8255はそうなっていないので、信号線をプルアップしておいて、ポートを入力状態にしたときにHiZ(ハイインピーダンス=無接続)となるためプルアップ抵抗でHレベルになるようにして、ポートを出力状態にしたときにはLレベルを出力するかたちで通信するみたいです。
参考にしたPIOの回路図もそんな感じでした。
もう一つの方法として、汎用I/Oポートの先にオープンコレクタ(オープンドレイン)のバッファ74HC07などをつないであげるという手がある模様。中日電工さんがDS1307+ RTCをマイコンにつなぐときの回路がそれでした。
I2Cのプロトコル
<参考URL>
I2Cのスレーブ機器はアドレスをもってて7ビットとか10ビットらしいです。(8bitちょっきりではないのですね)
今回のI2C RTCモジュールは0x68でした。EEPROMとかは端子を2〜3本持っててアドレスを設定できたりするらしいです。なんかLANのMACアドレスみたいですね。
I2CではSCL(シリアルクロック)をマスター(今回はZ80マイコン)が出すことで通信の主導権を握ります。なので、ちょっとぐらい不安定なソフトウェアなクロックでもいけそうです。
注意点としては、SCL(シリアルクロック)がHレベルのときにSDA(シリアルデータ)のH/Lを基本的には変えちゃダメ。ただし、通信開始、通信終了のときだけOK、というかその信号変化で通信開始、通信終了をスレーブ側は判断します。
SCL(シリアルクロック)がHレベルのときにSDAをH→Lへ変化させるのが「スタートコンディション(通信開始)」、SDAをL→Hへ変化させるのが「ストップコンディション」といいます。
以下に、DS3231のデータシートの一部を引用します。
DS3231 データシート
SDAに乗せるデータは基本は8bit通信でMSB(上位ビット)からLSB(下位ビット)の8ビットを乗せます。ソフトウェアで送信するとしたら、わりと素直に1ビットづつ引っ張り出して送ればよさそうです。
I2C RTCとの通信
I2C RTCとの通信で、日時情報を読み出す例はPICなど他マイコンで実装されている方々が多数いらっしゃいまして。ソースを読んでみると以下のような感じでした。
- スタートコンディション
- I2C RTCアドレス(7ビット)+書き込みフラグ Lレベル(1ビット) ★機器アドレスは1ビットビット左シフトが必要
- スレーブアドレス 0x00を送信
- リピート・スタートコンディション
- I2C RTCアドレス(7ビット)+読み込みフラグ Hレベル(1ビット) ★機器アドレスは1ビットビット左シフトが必要
- ここから7バイト分の日時データを秒→分→時→曜日→日→月→→年とBCDで読み取る。たとえば 2020/03/04 水 21:12:13 だったら 0x13、0x12、0x21、0x04、0x04、0x03、0x20と届く。1バイト分(8ビット)のデータを受信するたび、スレーブ側にACK(SDAがL)を返す。
DS3231 データシート
実はプログラム作成&テスト前にちゃんとデータシートを読んでなかったんですが(いつもの悪い癖)データシートにこの読み出し手順がでてました。上記のシーケンスです。
「リピーテッドスタート」というのはストップコンディション発行前に再度スタートコンディションを発行することのようで、SCL(クロック)をHレベルに保った状態でSDA(データ)をHレベル→Lレベルと変化させます。
最初スレーブ機器のアドレスが7bitというところが腹に落ちてなくて、0x68(8ビット)に1ビットのR/Wを追加して9ビットで送ったりして、うんともすんとも言わなくてハマりました。(データシートを良く読まないとw)
Z80 PIOのサンプルをもとにI2C RTCとの通信プログラムを作成
Z80 PIOのサンプルプログラムをもとに、I2Cの個々の部品はI2CドライバーとしてASMファイルにまとめました。ソースは以下のgithubにUPしています。
上記の部品を、I2Cのプロトコルに従って以下のソースのように使っていきました。内容を順に説明します。
- Z80 PIOの初期化
- Z80 PIOのポートBのB0をSCL(シリアルクロック)、B1をSDA(シリアルデータ)として使います。
- PIOのポートBはビットモード(1ポートずつパタパタ変更できる)にして、最初はB0、B1ともにINPUTモードに設定します(つまり、どちらもHレベルへ)
- B0、B1が出力となったときにLレベルを出してほしいので、初期化のときにB0、B1をLにした0xFCをポートBに出力します
- I2C バスリセット
SCL(シリアルクロック)を空で10回送信して、最後にSCLをHへ - I2Cバスへスタートコンディション発行
2.でSCLをHにした状態ですので、ここでSDA(シリアルデータ)をLへ遷移。つづいてSCLもLへ遷移。 - スレーブアドレスを「書き込み(Lレベル 1ビットつき)」で出力したのち、0x00を送信
- まずはI2C RTCへ1バイト分のデータ(0x00)を送信するため、スレーブアドレスを1ビット左シフト+1ビットのL(書き込み)を足して8ビット分送信。
- その後、RTCがもっているレジスタNoの0x00を8ビット分送信
- I2Cのスタートコンディションを再度実施(リスタートコンディション)
つぎに読み取りに入るので、I2C へ再度スタートコンディション(SCL=Hの間にSDAをH→L)を実施。 - スレーブアドレスを「読み込み()」で出力したのち、データ受信に入る
- スレーブアドレスを1ビット左シフト+1ビットのH(読み取り)を足して8ビット分送信。
- つづいてスレーブ側から日時データが届くので7バイト分のデータを読みます。ちなみに、8ビット分の受信が終わるたびに、SDA(シリアルデータ)をLにしてSCL(クロック)をL→H→LしてACKを返し受信完了したことを知らせます。
- もともとのZ80 PIOサンプルI2C 部品だと1バイト受信したら続いてNAK(受信終了)を返していたため1バイト分(秒だけ)取得できて悩んでいたりしました。^^) 注意です。
データシートにも書いてますが、本来は6.の処理の流れで最終バイトの7バイト目を受信したときにはNAK(SDAをH)を知らせる必要があるらしいがACKでもいまのところ動いています。 - 最後にストップコンディションを発行します
ちなみに、信号線をHにするときは該当ポートをINPUTモードへ変更のためPIOへコマンド。Lにするときは該当ポートをOUTPUTモードへ変更です。
Z80 PIOの各ポートのモードを変えているだけで、H/Lの信号を出力しているわけではないことに注意です。
できた!
Z80アセンブラプログラムをThe Macroassembler ASマイコンへでアセンブルしてIntelHexファイルを作成し、シリアル経由でKZ80マイコンへ流し込んで実行してみました。
配線は以下のような感じで14ピンフラットケーブルで接続。テスト用のユニバーサル基板とブレッドボードを使用しました。
上記のように、最初動いたときに色々とテストしたときにデータを壊しちゃったのか、RTC側のデータが壊れていてヘンテコなデータが読めてハマりました。このテストの前に、ArduinoUNOでI2C RTCモジュールの読み書きができることを確認していたので、再度 その環境で読んでみたら日時データが壊れてました。しばらく、Z80 PIOで読めてないと思ってソースをウンウン言いながら眺めてました。
8255PPIでも動くかも
Z80 PIOはビット単位に入力/出力を変更できるため今回のような構成がとりやすくなっています。KZ80-IOBで装備しているような、8255PPIはポートA、ポートB、ポートCの上位ビット4bit、ポートCの下位ビット4bitという4グループ単位でしか入力出力を変更できないのですが、ちょっと贅沢に ポートCの上位ビット4bit、ポートCの下位ビット4bitのうちそれぞれ1本づつをSCL、SDAに割り当てればPIOと同じことができそうです。
というか….できました。そのお話はまた別途まとめたいと思います。
Z80マイコンでI2Cが喋れるようになると、温度センサーやいろんなデバイスをつないで遊ぶことができそうで夢が広がります。使うのを諦めていたI2C EEPROMなどもつながりそう。(速度は度外視してですが ^^) )
コメント