NEC ELECTRONICS NEC ELECTRONICS
NEC electronics NEC electronics NEC
ホーム
アプリケーション
製品情報
先端技術
サポート
WEBショップ
ニュース&イベント
会社案内
header
GO
詳細検索機能/特性検索
サイトマップ お問い合わせ

78K0S用ソフトウェアI2Cバス通信プログラム

目次

    
FAQ-ID = i2c-nnnn
0001: 78K0S用ソフトウェアI2Cバス・マスタ通信プログラム(78K0S/Kx1+共通、プログラム例はKY1+をターゲット)
0002: ソフトウェアでI2Cバスのスレーブ機能の検討(78K0S/Kx1+)
0003: ソフトウェアでI2Cバスのスレーブ機能の実現について(78K0S/Kx1+)
0004: ソフトウェアでI2Cバスのスレーブ機能の実現について2(78K0S/Kx1+)
i2c
-0001
78K0S用ソフトウェアI2Cバス・マスタ通信プログラム(78K0S/Kx1+共通、プログラム例はKY1+をターゲット)
[はじめに]
I2CバスはマイコンにEEPROM、表示コントローラ、A/DやD/Aを接続するためのシリアル・バスとして幅広く使用されています。マイコンの中には、78K0/Kx2のように、専用のインタフェース回路を内蔵して、比較的容易にI2Cバスを使用できる製品もあります。
ここでは、専用インタフェースを内蔵していない78K0Sで通常のポートを用いて、シングルマスタモードでI2Cバスを制御するプログラムについて説明します。

[I2Cバスの概要]
I2Cバスはオープン・コレクタまたはオープン・ドレイン出力のクロック・ライン(SCL)とデータ・ライン(SDA)の2本の信号を用いて通信を行ないます。2つの信号線は抵抗でプルアップされており、未使用時にはハイ・レベルになっています。I2Cバスには、通信を制御するマスタと、マスタからの制御により通信を行なうスレーブが存在します。マスタがI2Cバスを使用する時には、以下の手順となります。

スタート・コンディションを発行してバスの使用を宣言
最初にアドレスを送信して、通信相手のスレーブと通信方向を指定
指定したスレーブが応答すると、通信を開始
データを受信した方は1つ(バイト)毎に応答を戻す
通信が完了したらストップ・コンディションを発行してバスを開放

I2Cバスの詳細については、ホームページにFAQ「I2Cバスについての概要」があるので、こちらを参照してください。ここでは、最大400kbpsまで対応したファースト・モードに準拠した動作を対象とします。

[使用するポート]
I2Cバスはオープン・ドレイン出力でドライブする必要があります。しかし、通常のCMOS出力しかない場合にはポート使い方に工夫が必要です。具体的には、ポートの出力ラッチは0に固定しておき、入出力を切り替えることで処理します。つまり、以下のような処理を行ないます。
  • PMレジスタに0をセット → 出力ポート → ロウ・レベル
  • PMレジスタに1をセット → 入力ポート → ハイ・レベル
このような処理を行なうことで、バスへの出力とバスにハイ・レベルを出力した状態でのバスの状態の読み出しが可能になります。

[I2Cバスの主な動作/処理]
I2Cバスの主な動作を以下に示します。ここで、送信はマスタからスレーブへ、受信はスレーブからマスタへの通信を示します。

(1)バスの状態の確認
バス使用中にリセットがかかってCPUが処理をやり直した場合などで、スレーブがバス(SDA)をロウ・レベルに引いている場合があります。このような状態ではマスタはバスを使用することができません。従って、最初にバスが開放されているかを確認する必要があります。

(2)バスの開放
もし、バスが開放されていなければ、SDAがハイ・レベルになるまでSCLにダミー・クロックを出力します。通常は9クロック(データ+ACK分)以下でSDAはハイ・レベルになります。実際のプログラムではスレーブがウェイトをかけていた場合(この可能性は殆どないと考えられますが)にいつまでもバス開放を待つのはまずいので、通常のパルス幅のダミー・クロックを256個出力するまでにSDAがハイ・レベルになるかで判断します。SDAがハイ・レベルになったことを確認したら、ストップ・コンディションを発行して、バスを開放させます。
(たまたま、スレーブがハイ・レベルを出力したが、次のデータがロウ・レベルであった場合、SCLをロウ・レベルにすることで、スレーブが次のデータ(ロウ・レベル)を出力する可能性が考えられます。これを避けるため、ここではSDAをロウ・レベルにしてから再度ハイ・レベルに立ち上げます。SDAの立ち上げはスタート・コンディションの発行と解釈されますが、その後にストップ・コンディションを発行するので、タイミング規格を満足させます。)

(3)スタート・コンディションの発行
バスが開放されたら、スタート・コンディションを発行して、バスの使用をスレーブに通知します。

(4)スレーブ・アドレスの送信
実際の通信の先頭では、スレーブのアドレス(7ビット)と通信方向(1ビット)の合計8ビットのデータを送信します。送信したアドレスに合致したスレーブがACKを戻してこない場合には、該当するアドレスのスレーブが存在しないので、通信はそこで終了し、ストップ・コンディションを発行して通信を終了します。ACKが戻ってきたら、通信方向に従って通信を継続します。

(5)サブ・アドレスの送信
I2Cバスとしてはスレーブ・アドレスを送信することで通信相手のスレーブを指定できますが、スレーブの中にも内部のアドレス情報をもつものがあります。たとえば、EEPROMでは内部のアドレスを指定するために、スレーブ・アドレスの送信に引き続いて内部のアドレスを指定します。スレーブ内部のアドレスは容量が256バイト以下なら1バイトの指定です。ここで指定したアドレスは通常は保持され、読み書きを行なうごとに更新されるようになっています。

(6)データの送信
サブ・アドレスの送信に引き続いてデータを送信すると、そのデータが指定したサブ・アドレスに書き込まれます。(EEPROMではストップ・コンディションを受けてから実際の書き込みが行なわれます。)
サブ・アドレスの送信を含めて、データを送信し、スレーブがデータを正しく受け取るとACKを戻してきます。ACKが戻れば、次の処理に移りますが、ACKが戻らない場合には通信を終了し、ストップ・コンディションを発行して通信を終了します。

(7)データの受信
通信方向の指定で受信を指定した場合には、スレーブ・アドレスの送信の次からはデータ受信を行ないます。データを受信したマスタは、引き続いてデータを受信したい場合には、ACKを戻します。受信したデータが最後のデータで、それ以上のデータ受信を行なわない場合にはNACKを戻して、受信が完了したことをスレーブに示す必要があります。

(8)ストップ・コンディションの発行
通信を完了してバスを開放する場合には、SCLがハイ・レベルの状態でSDAをハイ・レベルに立ち上げることで、ストップ・コンディションを発行します。

(9)リスタート・コンディションの発行
バスの使用権を確保している状態でデータ転送方向を切り替える場合に再度スタート・コンディションを発行します。これがリスタートで、この直後はスレーブ・アドレスの送信となり、通信方向を再指定できます。
一般的な使い方としては、内部のアドレスを指定してスレーブからデータを読み出す場合に使用します。通常は、最初に送信方向でスレーブ・アドレスを送信し、スレーブ内のアドレスをサブ・アドレスとして指定します。次にリスタート・コンディションを発行し、続いて、受信方向を指定してスレーブ・アドレスを送信します。これで、スレーブ内の指定したアドレスからの読み出しが可能となり、以降は指定したアドレスから順次読み出すことができます。

[バスの主な状態(参考)]
バスの主な状態は以下のようになります。

状態SCLSDA
(1)バスは未使用HH
(2)データ変化時Lデータは変化
(3)データは安定HH/L
(4)スタート・コンディション発行後HL
(5)アドレス送信直後(9クロック・ウェイト状態)LACK→H
(6)9クロック・ウェイト状態(次の通信待ち状態)L次データ
(7)アドレス送信後(受信指定で通信開始)L受信データ
(8)アドレス送信後(送信指定で通信開始)L送信データ
(9)8クロック・ウェイト状態LL=ACK/H=NACK
(10)ストップ・コンディション発行後(バスは未使用)HH

  • バスが使用されていない状態(1)や(10)では、2つの信号線SCLとSDAはハイ・レベル状態です。
  • 基本的にSDAはSCLがLのときに変化して、SCLがHの状態ではSDAは安定しています。
  • スタート・コンディションの発行(SCLがHの状態でSDAを立ち下げる)後にはSCLがHでSDAがLの状態となります。この後で、スレーブのアドレスを送信するには、まずSCLを立ち下げます。
  • アドレスの送信やデータ通信でSCLが9クロック送られた後の(5)や(6)ではSCLがLの状態で次の通信の準備を行ないます。次の通信がない場合には、SDAはHとなっています。そうでない場合(7)や(8)には次のデータの先頭ビットが出力されます。
  • 8ビット・データの送受信後(9)にSCLはLとなりACK応答待ちの状態となります。
  • (6)や(9)で、スレーブはSCLをロウ・レベルに引くことで通信を待たせることができます。従って、マスタはSCLがハイ・レベルになったことを確認してから次の処理に移る必要があります。
[プログラム例]
基本的な制御はアセンブラのプログラムで実現しますが、これらはC記述のプログラムから呼び出せるようにしてあります。(このプログラムはあくまで参考用です。)
このプログラムでは、78K0S/KY1+は高速内蔵発振器で動作させ、P2.2をSDA信号用に、P2.3をSCL信号用に使用します。なお、P2のほかのビットを操作すると、P2.2及びP2.3の出力ラッチが書き換わってしまいます。P2の他のビットを出力ポートとして使用する場合には、出力ラッチのデータのイメージを内蔵RAMに準備しておきます。ポートの出力を操作したい場合にはRAMのデータを操作し、結果をポートに書き込むようにしてください。

ひとこと注意1

処理速度が要求されるような場合には、プログラムはアセンブラで記述し、大きな制御ではC言語で記述することがよくあります。この場合には、C言語のプログラムからアセンブラ記述のサブルーチンを関数としてコールします。そのときに、サブルーチンの名前の付け方に注意が必要です。アセンブラ記述のサブルーチンの名前は必ず_ _(2個のアンダースコア)で始まる必要があります。C言語のプログラムからこれを呼び出すときには_(1個のアンダースコア)で始まるように指定します。
たとえば、I2Cで使用するポートの初期化プログラムはアセンブラ記述部分では「__setup_i2c_port」の名前が付けられていますが、これをC言語のプログラムで呼び出すときには「_setup_i2c_port();」と記述します

また、内蔵RAMには実行結果のエラー・ステータス用のバイト変数(i2c_error)、スレーブに対してACKを戻すかどうかを指定するビット変数(acke)を準備します。以下に宣言の例を示します。これらの変数は全て専用のプログラムで制御するものとします。
comdata DSEG     SADDR      ; saddr領域に変数を確保します。
i2c_error:  DS  1           ; エラー・フラグ
    BSEG                    ; ビット変数領域であることを示します。
acke    DBIT                ; ACK制御フラグ
(1)ポートの定義
PMSW_SDAがSDA信号用で使用する名称です。これを1にすると端子は入力ポートとなり、PSW_SDAを読むことで、SDA信号の状態を知ることができます。スレーブが出力していなければSDA信号はハイ・レベルになります。これを0にすると、出力ポートとなり、出力ラッチが0なのでSDA信号をロウ・レベルにします。
PMSW_SCLがSCL信号用で使用する名称です。これを1にすると端子は入力ポートになりPSW_SCLを読むことで、SCL信号の状態を知ることができます。通常はSCLをハイ・レベルにするときに、1に設定します。スレーブがウェイトをかけてなければ、外部のプルアップ抵抗によりハイ・レベルに引かれます。これを0にすると出力ポートとなり、SCL信号をロウ・レベルにできます。
・アセンブラでの定義例(使用ポートの定義)
PMSW_SDA    equ PM2.2       ; SDA信号制御で使用
PSW_SDA     equ P2.2        ; SDA信号の読出しで使用
PSW_I2C     EQU P2          ; 使用するのはポート2

PMSW_SCL    equ PM2.3       ; SCL信号制御で使用
PSW_SCL     equ P2.3        ; SCL信号の読出しで使用
実際のポートの操作(モードレジスタの操作)は意味を分かりやすくするために、以下のようなマクロを定義しています。
・ポート操作の定義
_scl_hi         macro       ; _scl_hiと言うマクロの定義を示す
        set1    PMSW_SCL    ; PM2.3をセットして入力にする
        endm                ; マクロ定義の終了を示す


_scl_lo         macro
        clr1    PMSW_SCL    ; PM2.3をクリアしてSCLにロウ出力
        endm


_sda_hi         macro
        set1    PMSW_SDA    ; PM2.2をセットして入力にする
        endm

_sda_lo         macro
        clr1    PMSW_SDA    ; PM2.2をクリアしてSDAにロウ出力
ひとこと1

プログラムを作成する場合にマクロ機能を用いることで、定型的な処理を効率的に記述したり、プログラムを分かりやすくしたりできます。ここでは、処理そのものはポートモード・レジスタに対するビット操作命令です。効率的な記述よりも処理の分かり易さのためにマクロ機能を使用しています。CLR1 PMSW_SCLと記述するよりも、_scl_loと記述する方がどのような処理を行なうかが直感的に意味を捉えることができます。

(2)ポートの初期化(__setup_i2c_port)
使用するポートの出力ラッチを0にします(ポートに0をバイト単位で書き込む)。ポートのモードは入力にしておきます。このプログラムはC言語記述部分から呼び出せるように__で始まる名前をつけてpublic宣言します。
    public  __setup_i2c_port    ; 外部から使えるようにpublic宣言します
__setup_i2c_port:
    MOV PSW_I2C,#0              ; 端子をオープン・ドレインと等価に
                                ; 使用するのでデータは0に固定
    _scl_hi                     ; PMを入力にする
    _sda_hi
    RET
(3)バスのビジー・チェック(__i2c_busy)
バスが空き状態かをチェックします。結果はキャリー・フラグで戻ります。自分自身で使用中を含めて、SCLかSDAがロウ・レベルであれば、ビジーと判断し、キャリー・フラグはセット(戻り値はtrue)されます。バスが開放されていればキャリー・フラグはクリア(戻り値はfalse)されます。ポートの状態を変えるような処理は行ないません。ここではアセンブラ記述で参照しやすいように専用に名前(i2c_busy)を追加します。
;bit    _i2c_busy(void)
    public  __i2c_busy
__i2c_busy:
i2c_busy:
    SET1    CY                  ; ビジーフラグをセットしておく
    BF  PSW_SDA,$busbusy        ; SDAラインのチェック
    BF  PSW_SCL,$busbusy        ; SCLラインのチェック
    CLR1    CY                  ; 開放されていればフラグリセット
busbusy:
    RET
ひとこと2

78K0Sで1ビットのポートの状態をチェックするときには、上のプログラム例に記述されているように条件分岐命令(BF命令やBT命令)を使用します。BF命令を使用すると、1ビットのポートが0ならば分岐し、1ならば分岐しません。上記のプログラムではPSW_SDAとPSW_SCLが両方1ならば2つの条件分岐命令では分岐せず、CY(キャリー・フラグ)がクリアされます。

(4)バスの開放(__free_i2c_bus)
バスの状態を確認しバスが開放されていない場合にはダミー・クロックを送ってバスが開放されるのを待ちます。256パルスを送ってもバスが開放されない場合にはエラーとしてキャリー・フラグをクリア(戻り値はfalse)して戻ります。
SDA信号とSCL信号がハイ・レベルになったならストップ・コンディションを発行します。

ひとこと注意2

この状態で、SCLをロウ・レベルにすると、スレーブが次のデータとしてロウ・レベルを出力する可能性があります(スレーブが出力していたデータがたまたま1になったが、次のデータが0の場合です)。そこで、ここではSCLがハイ・レベルの状態で、SDAをロウ・レベルにしてから再度ハイ・レベルに立ち上げます。SDAの立ち下げはスタート・コンディションの発行と解釈されますが、その後にSDAを立ち上げてストップ・コンディションを発行するので、無視されてしまいます。

ストップ・コンディションを発行して、バスを開放したら、キャリー・フラグをセット(戻り値はtrue)して戻ります。
ここで使用するダミー・クロック送信では、SCLのロウ・レベル幅1.3μ秒を確保するため、動作クロック12クロックの時間を確保しています。ハイ・レベル幅はサブルーチン・コ-ル他のオーバーヘッドがあるので意識して確保はしません。スレーブがウェイトをかけている場合には、マスタが立ち上げてもバス上ではロウ・レベルのままになってしまいます。この状態が継続した場合には、I2Cバスは全く使えません。そのため、ここではSCLの立ち上がりを待つことはしません。マージンをもたせた256回で処理を終了します。
;bit    _free_i2c_bus(void)
    public  __free_i2c_bus
__free_i2c_bus:
free_i2c_bus:
    _sda_hi                 ; 念のためにSDAを立ち上げる
    _scl_hi                 ; 念のためにSCLを立ち上げる
    CALL    !i2c_busy       ; バスの状態確認
    NOT1    CY
    BC  $free_exit1         ; バスが開放されていれば抜ける
    MOV A,#0                ; 限度を256回に設定
dumyclkloop:
    CALL    !scl_pulse      ; SCLにダミークロックを出力
    CALL    !i2c_busy       ; バスの状態確認
    NOT1    CY
    BC  $free_exit1         ; バスが開放されていれば抜ける
    DEC A                   ; 限度を超えていないか?
    BNZ $dumyclkloop        ; 限度以内なら継続
    RET                     ; エラーならキャリーをセットして戻る
free_exit1:
    PUSH    PSW             ; 念のためにフラグをセーブ
    CALL    !I2C_STPR       ; ストップ・コンディション発行
    POP PSW
    RET
・I2Cバスの規格に準拠したダミー・クロックを生成します。
scl_pulse:
    _scl_lo                 ; 6:SCLを立ち下げる
    NOP                     ; 2:ロウ・レベル幅1.3μ秒を
    NOP                     ; 2:確保するために、11クロック
    NOP                     ; 2:以上時間を確保して立ち上げる
    _scl_hi                 ; 6:SCLを立ち上げる
    RET                     ; 6:これだけでハイ・レベルは十分
(5) スタート・コンディションの発行(__I2C_STR)
スタート・コンディション発行の準備として、SDAを立ち上げてからSCLを立ち上げます(SCLが既に立ち上がっていればこれはストップ・コンディションと同じです。リスタートの場合にはSCLがロウ・レベルになっている必要があります)。
その後にSCLが立ち上がっていることをBF命令で確認してからSDAを立ち下げることでスタート・コンディションを発行します(これは、スレーブが9クロック目でウェイトをかけていたときに解除されるのを待つために入れています。殆どの場合では不要と考えられます。BF命令を削除する場合にはNOP命令2個に置き換えてください)。

ひとこと3

I2CバスではスレーブがSCLをロウ・レベルに引くことで、通信を一時的に待たせる機能があります。そのため、マスタはSCLを立ち上げた後で、本当にSCLが立ち上がったかを確認します。その際に「BF PSW_SCL,$$」を使用しています。この命令により、SCLライン(PSW_SCLで読める)がロウ・レベルであれば、自分自身に分岐して命令を繰り返します。これにより、SCLが立ち上がると次に進むことができます。
    public  __I2C_STR
__I2C_STR:
I2C_STR:
    _sda_hi                 ; 6:準備のためSDAをハイに設定する
    NOP                     ; 2:
    _scl_hi                 ; 6:SCLをハイに設定する
    BF  PSW_SCL,$$          ;10:SCLの立ち上がり検出
    _sda_lo                 ; 6:スタート・コンディション発行
    RET                     ; 6:
(6)ストップ・コンディションの発行(__I2C_STPR)
最初にSCLを立ち下げると次の転送がスタートするかもしれないので、SCLが既にロウ・レベルとして処理を行ないます。もし、SCLがハイ・レベルであれば、SDAをロウ・レベルにするとスタート・コンディションの発行と同じになります。しかし、次にストップ・コンディションを発行することで、おかしな状態にはならないと考えられます。
ここでも、SCLが立ち上がっていることをBF命令で確認してから次の処理を行ないます。これは上のスタート・コンディションの発行と同じ理由によるものです。
    public  __I2C_STPR
__I2C_STPR:
I2C_STPR:
    _sda_lo                 ; 6:SDAを立ち下げる。これでスタート
    NOP                     ; 2:コンディションと解釈されても、
    NOP                     ; 2:直ぐにストップ・コンディションを
                            ;   発行するので構わない。
    _scl_hi                 ; 6:念のためにSCLをハイに設定する
    BF  PSW_SCL,$$          ;10:SCLの立ち上がり検出
    _sda_hi                 ; 6:ストップ・コンディション発行
    NOP                     ; 2:
    NOP                     ; 2:
    RET                     ; 6:
(7) I2C バスに8ビットデータを出力(__put_i2c)
C言語のノーマルモードで1バイトの引数の受け渡しに使用されるXレジスタで渡された8ビットのデータをI2Cバスに出力するルーチンです。SCLがロウ・レベルのときにMSBから順番に出力し、ACK応答を確認し、ACK応答があったときにはキャリー・フラグをセット(戻り値はtrue)して戻ります。ACK応答がないときにはキャリー・フラグをクリア(戻り値はfalse)して戻ります。
処理が終わった時点ではSCLはロウ・レベルになった状態です(9クロック・ウェイト状態)。

ひとこと4

送信開始時(前回の通信での9クロック・ウェイト状態)にウェイト解除待ちを行ない、確実にSCLが立ち上がったことを確認するためにSCLの立ち上がり待ち処理を行なっています。(この処理はなくてもいいかもしれないが念のために全てのビットに対して入るようになっています。本来ウェイトの入らない部分も確認しているので無駄と考えたら削除してNOP2個に置き換えてください。)

(プログラム・フロー)
以下にプログラムのフローを示します。



(プログラム例)
;bit    _put_i2c(u8 data);
    public  __put_i2c
__put_i2c:
put_i2c:
    PUSH    BC              ; ループカウンタで使用のためセーブ
    MOV B,#8
    MOV A,X                 ; 入力データ(引数)は8ビット
;
;
OUTLOOP:
    _scl_lo                 ; 6:SCLを立ち下げてデータ出力準備
    NOP                     ; 2:立下りの時間調整用
    ROL A,1                 ; 2:MSBよりCYにシフトアウトする
    BNC $OUTLO              ; 6:データが0なら分岐
    _sda_hi                 ; 6:1を出力
    BR  $CLKHI              ; 6:SCLの立ち上げへ
OUTLO:  _sda_lo             ; 6:0を出力
    NOP                     ; 2:セットアップ時間確保
CLKHI:
      _scl_hi               ; 6:
    BF  PSW_SCL,$$          ;10:SCLの立ち上がり検出(念のため)
    DBNZ    B,$OUTLOOP      ; 6:

    POP BC                  ; 6:
    _scl_lo                 ; 6:8クロック目のSCLを立ち下げる
    NOP                     ; 2:
    _sda_hi                 ; 6:SDA を入力に切替え
    NOP                     ; 2:
    NOP                     ; 2:
    NOP                     ; 2:
    _scl_hi                 ; 6:9クロック目のSCLを立ち上げ
;
;   ウェイト解除を待つ
;
    SET1    CY              ; 2:

    BF  PSW_SCL,$$          ; SCLの立ち上がり検出待ち

    BF  PSW_SDA,$ACKOK      ;10:/ACK信号を取得
    NOT1    CY              ; 2:
ACKOK:

    _scl_lo                 ; 6:SCLを立ち下げ(9クロック目)
    NOP
    RET
(8) I2C バスに8ビットデータを出力。エラーでバスを開放(__put_i2c2)
これは上記(7)のルーチンを使って、データを出力し、エラーが発生した場合には、ストップ・コンディションを発行して戻るものです。入出力条件は(7)と同じです。
;static bit  _put_i2c2(u8 data)
    public  __put_i2c2
__put_i2c2:
    CALL    !put_i2c            ; 1バイト出力
    BNC $PUTERROR               ; エラーなら終了処理へ
    RET                         ; 正常なら終了
PUTERROR:
    CALL    !I2C_STPR           ; ストップ・コンディション発行
    CLR1    CY                  ; falseにする(念のため)
    RET
(9) I2C バスから8ビットデータを入力(__get_i2c)
I2Cバスの選択されたスレーブから8ビットのデータを受信し、受信データをCレジスタに格納して戻ります。スレーブからのデータはMSBから順にSCLがHのときに読み込みます。受信完了後に、ackeビットが1であればACK応答を行ない次のデータがあることをスレーブに通知します。ackeビットが0であればNACK応答を行ない、最後のデータを受信したことをスレーブに通知します。
処理が終わった時点ではSCLはロウ・レベルになった状態です(9クロック・ウェイト状態)。
(プログラム・フロー)
以下にプログラムのフローを示します。



(プログラム例)
;u8 _get_i2c(void);
    public  __get_i2c
__get_i2c:
    MOV A,#11111110b        ; シフトしてCY=0で終了
INLOOP:
    NOP                     ; 2:
    SET1    CY              ; 2:初期値として1をセット
    _scl_hi                 ; 6:SCLの立ち上げ
    BF  PSW_SCL,$$          ; SCLの立ち上がり検出

    BT  PSW_SDA,$DATAIS1    ;10:
    NOT1    CY              ; 2:データが0ならキャリーを反転
DATAIS1:
    ROLC    A,1             ; 2:読み込んだ結果をAレジスタにシフトイン
    _scl_lo                 ; 6:SCLの立ち下げ
    BC  $INLOOP             ; 6:8ビット分ループ

;******************************************************
;*  ACK応答の制御                                  *
;******************************************************
    BF  _acke,$ACKEND           ;10:ACK応答するかをチェック
    _sda_lo                     ; 6:ACKを戻す
ACKEND:
    _scl_hi                     ; 6:9クロック目の立ち上げ
;
;     ここではスレーブが8クロック・ウェイトしていたときの対応で
;   SCL信号の立ち上がりを待っている。通常は不要か。
;
    BF  PSW_SCL,$$              ; SCLの立ち上がり検出(念のため)
    NOP                         ; 2:
    _scl_lo                     ; 6:9クロック目の立ち下げ

    MOV C,A                     ; 4:return (u8)
    _sda_hi                     ; 6:終わったらSDAは入力にしておく
    RET
ひとこと5

ここで、ループをカウントするためにAレジスタを使用しています。Aレジスタへの受信データ(ビット)の取り込み(右からのシフトイン)処理とループカウント動作を兼用させています。Aレジスタに初期値として11111110bを設定しておくと、シフトインごとに1111110xb→111110xxb→11110xxxbと変化していきます。7回目が完了した段階では0xxxxxxxbとなり、ここまでのシフト結果ではキャリー・フラグはセットされています。8回目のシフトを行なうと、初期値の最後のビットである0がキャリーに送られます。従って、キャリーが0になったら8回が完了したことになります。

(10)エラー・フラグのクリア(__clear_i2c_error)
これは内部の変数をクリアするだけの処理です。エラー・フラグの状態を戻り値とします。
;static u8 _clear_i2c_error(void)
    public  __clear_i2c_error
__clear_i2c_error:
clear_i2c_error:
    MOV i2c_error,#I2C_NO_ERROR ;
    MOV C,#I2C_NO_ERROR
    RET
(11) エラー・フラグのセット(__set_i2c_error)
内部の変数にエラーの状態をセットするものです。エラーは以下のように定義しています。
I2C_NO_ERROR        = 0x00:エラー無し
I2C_BUS_NOT_FREE    = 0x10:バスが開放されていなかった
I2C_ADDR_NACK       = 0x11:スレーブがアドレスに反応しなかった
I2C_RADDR_NACK      = 0x12:スレーブがREADアドレスに反応しなかった
I2C_REG_ADDR_NACK   = 0x13:スレーブから予期せぬ NACK が帰って来た(レジスタ)
I2C_DATA_NACK       = 0x14:スレーブから予期せぬ NACK が帰って来た(データ)

;static u8 _set_i2c_error(u8 error)
    public  __set_i2c_error
__set_i2c_error:
set_i2c_error:
    XCH A,X                     ; エラー・データを取り込む
    MOV i2c_error,A             ; エラー・フラグにセット
    MOV C,A                     ; 戻り値をセット
    XCH A,X
    RET
(12) エラー・フラグの読み出し(__get_i2c_error)
エラー・フラグの内容を読み出します。エラーは上(11)のように定義しています。
;static u8 _get_i2c_error(void)
    public  __get_i2c_error
__get_i2c_error:
get_i2c_error:
    PUSH    AX
    MOV A,i2c_error             ; エラー・フラグの読み出し
    MOV C,A                     ; 戻り値にセット
    POP AX
    RET
(13)ACK応答許可フラグのセット(__ACK_EN)
マスタ受信でのACK応答を許可する設定をおこないます。通常は許可にしておき、最後のデータ受信時には禁止します。
;void _ACK_EN(void)
    public  __ACK_EN
__ACK_EN:
ACK_EN:
    SET1    acke                ; ACK応答フラグセット
    RET
(14) ACK応答フラグの禁止セット(__ACK_DS)
マスタ受信の最後のデータでNACK応答させる場合に使用します。
;void _ACK_DS(void)
    public  __ACK_DS
__ACK_DS:
ACK_DS:
    CLR1    acke                ; ACK応答フラグクリア
    RET
(15) マスタ送信処理(__write_i2c_block)
I2Cバスを用いて256バイトのEEPROMへの書き込みを行ないます。このサブルーチンはC言語(ノーマルモード)からの利用を考慮して、必要なパラメータはC言語の引数と同じ形式で渡すものとします。引き渡すパラメータは以下の形式となります。

第1の引数:Xレジスタ :I2Cバスのスレーブのアドレス
第2の引数:スタックの1段目 :スレーブのアクセスしたい内部アドレス
第3の引数:スタックの2段目 :データを保存するバッファのアドレス
第4の引数:スタックの3段目 :書き込むデータのバイト数

これらのパラメータを2バイト単位で第4の引数〜第2の引数までこの順番でスタックにプッシュしてからこのサブルーチンをコールします。
このサブルーチンは、第1の引数で示されたスレーブ・アドレスのEEPROMの、第2の引数で示された内部アドレス以降に、第3の引数で示されたバッファメモリから、第4の引数で示されたバイト数のデータを書き込みます。正常に書き込みが完了したら、バスを開放して、エラー・フラグはクリアして戻ります。エラー・フラグと同じ値を戻り値としてCレジスタに格納して戻るので、戻り値をチェックすることで、エラーを確認できます。(エラーの内容は(11) エラー・フラグのセットを参照してください。)
(プログラム・フロー)
以下にプログラムのフローを示します。



バイト単位の送信処理にはアセンブラ記述の__put_i2c2関数を使用し、エラーが発生した場合にはバスを開放し、発生した状況に応じたエラーを戻します。エラーが発生しなかった場合には、バスを開放して、エラーなしを戻します。(EEPROMはストップ・コンディションを検出して、送信されたデータを内部のセルに書き込みます。)
このプログラム例と同じ処理を行なうプログラムをC言語で記述した例を[プログラムの使用例]の(1)に記載しておきます。細かな処理の順番は異なりますが、処理の内容は同じです。関数としての呼び出し方法と戻り値も同じです。

(プログラム例)
;**************************************************************
;*                                                            *
;*  I2C デバイスにブロックデータ送信                          *
;*  対象は256バイト以下のサブアドレスもつデバイス          *
;*                  ADR(W), SUB_ADR, DATA...                  *
;**************************************************************
;*  [i] : u8 i2c_adr    ... I2C スレーブのアドレス            *
;*  [i] : u8 reg_adr    ... スレーブ内部のアドレス            *
;*  [i] : u8 *data      ... 書き込みデータのアドレス          *
;*  [i] : u8 size       ... バイト数(0x00 = 256)              *
;*  [r] : u8            ... error code                        *
;*                                                            *
;*    実際の引数はスタックに16ビット単位で積まれて          *
;*  渡されるので、SPの値をHLに持ってきて、HL相対に      *
;*  より必要な引数を得る。                                    *
;*                                                            *
;**************************************************************
;u8 _write_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size)

    public  __write_i2c_block
__write_i2c_block:
    PUSH    HL                  ; レジスタをセーブ
    PUSH    AX
;
;  このサブルーチンはC(ノーマル・モード)から関数コールされる。
;  そのため、この段階でのスタックのデータは以下のようになっている
;
;   sp   : Xレジスタ(第1引数) :スレーブ・アドレス
;   sp+1 : Aレジスタ            :未使用(レジスタのセーブのみ)
;   sp+2 : Lレジスタ            :未使用(レジスタのセーブのみ)
;   sp+3 : Hレジスタ            :未使用(レジスタのセーブのみ)
;   sp+4 : PCの下位           :戻りアドレス下位
;   sp+5 : PCの上位           :戻りアドレス上位
;   sp+6 : 第2引数             :スレーブ内アドレス
;   sp+7 :                      :未使用
;   sp+8 : 第3引数の下位       :書き込みデータの格納アドレス
;   sp+9 : 第3引数の上位
;   sp+10: 第4引数             :書き込みデータ数

    MOVW    AX,SP               ; 2つ目以降の引数を取り出す準備
    MOVW    HL,AX
    MOV X,#I2C_BUS_NOT_FREE
    CALL    !set_i2c_error      ; ダミーでエラーをセットしておく
    CALL    !free_i2c_bus       ; バスが開放できるか
    BNC $EXITSUB                ; 開放できなければ戻る

    CALL    !I2C_STR            ; スタート・コンディション発行

    MOV X,#I2C_ADDR_NACK
    CALL    !set_i2c_error      ; ダミーでエラーをセットしておく
    MOV A,[HL]                  ; スレーブ・アドレスを取り出す
    AND A,#11111110b            ; 方向フラグを送信に
    MOV X,A
    CALL    !put_i2c2           ; 送信方向でスレーブアドレスを送信
    BNC $EXITSUB                ; スレーブから応答なければエラーで戻る

    MOV X,#I2C_REG_ADDR_NACK    ;
    CALL    !set_i2c_error      ; ダミーでエラーをセットしておく
    MOV A,[HL+6]                ; スタックから第2引数を取り出す
    MOV X,A
    CALL    !put_i2c2           ; スレーブ内アドレスを送信
    BNC $EXITSUB                ; スレーブから応答なければエラーで戻る

    MOV A,[HL+10]               ; スタックから第4引数を取り出す
    MOV B,A                     ; データ数をBレジスタに設定
    MOV A,[HL +8]               ; スタックから第3引数を取り出す。
    MOV X,A                     ; 第3引数(書き込みデータ格納アドレス)
    MOV A,[HL+9]                ; を
    MOVW    HL,AX               ; HLレジスタに設定する
    MOV X,#I2C_DATA_NACK        ; ダミーでエラー・フラグにデータへの
    CALL    !set_i2c_error      ; ACK応答無しを設定
WBLOOP:
    MOV A,[HL]                  ; 書き込みデータをバッファから読み出す
    MOV X,A
    CALL    !put_i2c2           ; データを書き込む
    BNC $EXITSUB                ; エラーが発生したら戻る

    INCW    HL                  ; データアドレスを更新
    DBNZ    B,$WBLOOP           ; データ数分繰り返す

    CALL    !I2C_STPR           ; バスを開放
    CALL    !clear_i2c_error    ; エラー・フラグをクリア
EXITSUB:
    MOV B,#0                    ; 戻り値の上位をクリア(不要?)
    POP AX
    POP HL
    RET
(16) マスタ受信処理(__read_i2c_block)
I2Cバスを使用して、256バイトのEEPROMからデータを読み出します。
第1の引数で示されたスレーブの第2の引数で示された内部アドレス以降から、第3のパラメータで示されたバッファに、第4のパラメータで示されたバイト数読み出します。
スレーブ・アドレスやスレーブ内部のアドレスの送信処理にはアセンブラ記述の__put_i2c2関数を使用し、エラーが発生した場合にはバスを開放し、発生した状況に応じたエラーを戻します。処理でエラーが発生しなかった場合には、バスを開放して、エラーなしを戻します。

(プログラム・フロー)
以下にプログラムのフローを示します。



(プログラム例)
;****************************************************************
;*                                                              *
;*  I2C デバイスからブロックデータを受信                        *
;*  対象は256バイト以下のサブ・アドレスをもつデバイス        *
;*                  ADR(W), SUB_ADR, DATA...                    *
;****************************************************************
;*  [i] : u8 i2c_adr    ... I2C スレーブのアドレス              *
;*  [i] : u8 reg_adr    ... スレーブ内部のアドレス              *
;*  [i] : u8 *data      ... 書き込みデータのアドレス            *
;*  [i] : u8 size       ... バイト数(0x00 = 256)                *
;*  [r] : u8            ... error code                          *
;*                                                              *
;*    実際の引数はスタックに16ビット単位で積まれて            *
;*  渡されるので、SPの値をHLに持ってきて、HL相対に        *
;*  より必要な引数を得る。HLからのアクセス時の相対            *
;*  アドレスは書き込みの方を参照のこと。                        *
;****************************************************************
;u8 _read_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size)

    public  __read_i2c_block
__read_i2c_block:
    PUSH    HL
    PUSH    AX
    MOVW    AX,SP               ; 2つ目以降の引数を取り出す準備
    MOVW    HL,AX               ; ポインタをHLレジスタに設定
    MOV X,#I2C_BUS_NOT_FREE
    CALL    !set_i2c_error      ; ダミーでエラーをセットしておく
    CALL    !free_i2c_bus       ; バスが開放できるか
    BNC $EXITSUBR               ; 開放できなければ戻る
    CALL    !I2C_STR            ; スタート・コンディション発行

    MOV X,#I2C_ADDR_NACK
    CALL    !set_i2c_error      ; ダミーでエラーをセットしておく
    MOV A,[HL]                  ; スレーブ・アドレスを取り出す
    AND A,#11111110b            ; 方向フラグを送信に
    MOV X,A
    CALL    !put_i2c2           ; 送信方向でスレーブアドレスを送信
    BNC $EXITSUBR               ; スレーブから応答なければエラーで戻る

    MOV X,#I2C_REG_ADDR_NACK
    CALL    !set_i2c_error      ; ダミーでエラーをセットしておく
    MOV A,[HL+6]                ; スタックから第2引数を取り出す
    MOV X,A
    CALL    !put_i2c2           ; スレーブ内アドレスを送信
    BNC $EXITSUBR               ; スレーブから応答なければエラーで戻る

    CALL    !I2C_STR            ; リスタート・コンディション発行

    MOV X,#I2C_RADDR_NACK
    CALL    !set_i2c_error      ; ダミーでエラーをセットしておく
    MOV A,[HL]                  ; スレーブ・アドレスを取り出す
    OR  A,#00000001b            ; 方向フラグを受信に
    MOV X,A
    CALL    !put_i2c2           ; 受信方向でスレーブアドレスを送信
    BNC $EXITSUBR               ; スレーブから応答なければエラーで戻る

    SET1    acke                ; ACK応答を設定
    MOV A,[HL+10]               ; データ数を
    MOV B,A
    MOV A,[HL+8]                ; スタックから第3引数を取り出す。
    MOV X,A                     ; 第3引数(書き込みデータ格納アドレス)
    MOV A,[HL+9]                ; を
    MOVW    HL,AX               ; HLレジスタに設定する

BRLOOP:
    MOV A,#1                    ; 最後のデータかをチェックする
    CMP A,B
    BNZ $BRNEXT                 ; 最後のデータ(残り1バイト)なら
    CLR1    acke                ; NACK応答を設定
BRNEXT:
    CALL    !get_i2c            ; データをリード
    MOV A,C                     ;
    MOV [hl],a                  ; 読み込んだデータをバッファに書き込む

    INCW    HL                  ; ポインタ更新
    DBNZ    B,$BRLOOP           ; データ数分繰り返す

    CALL    !I2C_STPR           ; バスを開放
    CALL    !clear_i2c_error    ; エラー・フラグをクリア
EXITSUBR:  
    MOV B,#00H                  ; 余分?
    POP AX
    POP HL
    RET
[プログラムの使用例]
以上で説明したアセンブラで記述したプログラムの使い方の例としてC言語で記述したプログラムから呼び出して使用する例を示します。
下記の(1)及び(2)の関数はそれぞれ、アセンブラでのプログラム例の(15)および(16)で示したプログラムと同じ処理を実現させるためのものです。これらの例では、アセンブラで記述した基本的な制御ルーチンを使用してI2Cバスを用いた256バイトのEEPROMの制御を行ないます。また、(3)はアセンブラでのプログラム例の(15)および(16)や下記の関数の例(1)及び(2)を使用する例となります。
なお、以下の例を使用する際には次に示すようなアセンブラで記述されたプログラムを外部から参照するための宣言や" unsigned char"を"u8"と記述するための宣言などを行なっておく必要があります。

#pragma SFR
#pragma  EI
#pragma  DI
#pragma  NOP
typedef unsigned char   u8, uint8,  /* same as "byte"   */
            *u8Ptr, *uint8Ptr;  /* same as "bytePtr"    */
/*  エラー・フラグ操作関係処理関数  */

extern      u8  _set_i2c_error(u8 error);
extern      u8  _get_i2c_error(void);
extern      u8  _clear_i2c_error(void);

/*  エラー・フラグ状態定義          */
enum{
    I2C_NO_ERROR        = 0x00, // エラー無し(0)
    I2C_BUS_NOT_FREE    = 0x10, // バスが開放されていなかった
    I2C_ADDR_NACK       = 0x11, // スレーブがアドレスに反応なし
    I2C_RADDR_NACK      = 0x12, // スレーブがREADアドレスに反応なし
    I2C_REG_ADDR_NACK   = 0x13, // 予期せぬ NACK が帰って来た(レジスタ)
    I2C_DATA_NACK       = 0x14  // 予期せぬ NACK が帰って来た(データ)
};

/*  ACK応答制御関係処理関数      */

extern      void    _ACK_EN(void);
extern      void    _ACK_DS(void);

extern      void    _setup_i2c_port(void);
extern      bit _i2c_busy(void);
extern      bit _free_i2c_bus(void);

/*  I2Cバス初期化関係処理関数    */

extern      void    _I2C_STR(void);
extern      void    _I2C_STPR(void);

/*  I2Cバスバイト転送基本関数    */

extern      bit _put_i2c(u8 data);
extern      bit _put_i2c2(u8 data);
extern      u8  _get_i2c(void);

/*  I2Cバスブロック転送処理関数  */

extern      u8 _write_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size);
extern      u8 _read_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size);
ひとこと注意3

これらのプログラムは8MHzの内蔵発振器の出力をCPUのクロックとして動作させるものです。従って、78K0S/KY1+のPOCが解除されても、そのままでは8MHzでの動作ができませんので、電源電圧が4V以上になるのを待つ必要があります。そのためには初期化のためのhdwinit関数に以下のような処理を記述しておく必要があります。
    LVIS = 0x00;        /* select VLI=4V */
    LVIM = 0b10000000;  /* Start VLI detection*/
    PCC = 0x00;         /* CPU clock is fx/4 */
    _clear_i2c_error(); /* I2C flag clear */
    _setup_i2c_port();  /* Initialize I2C port*/
/*
    wait for 0.17ms(=0.2ms-(32+30)*52/1000)
*/
    for (work1 = 0; work1 < 10; work1++){
        NOP();
    }
    WDTM = 0b01110000;  /* Stop WDT */
    LSRSTOP = 1;        /* Stop Low Speed OSC */
    while ( LVIF ){
        NOP();
    }   /* wait VDD > 4V */

    PPCC = 0;       /* CPU clock is fx=fR */
    IF0 = 0x00;     /* Clear interrupt */

ひとこと6

ここでは、WDTや低速内蔵発振器を停止させたり、X1,X2端子をポートとしてI2Cバスで使用したりするためにオプションバイトで指定する必要があります。そのためには以下の内容のようなアセンブラ記述の定義ファイルを作成し、リンクしておく必要があります。
    @@OPTB  CSEG    AT  0080H
    DB  10011100b
    DB  11111111b
    END

(1)マスタ送信処理(u8 write_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size))
I2Cバスを用いて256バイトのEEPROMへの書き込みを行なう関数です。
第1の引数で示されたスレーブの第2の引数で示された内部アドレス以降に、第3のパラメータで示されたバッファの内容を、第4のパラメータで示されたバイト数書き込みます。
バイト単位の送信処理にはアセンブラ記述の__put_i2c2関数を使用し、エラーが発生した場合にはバスを開放し、発生した状況に応じたエラーを戻します。
エラーが発生しなかった場合には、バスを開放して、エラーなしを戻します。処理の手順は以下の通りです。
  • 最初にI2Cバスが開放状態であることを確認/バスを開放します。
  • 開放できなければ、エラーで戻ります。
  • スタート・コンディションを発行してバスの使用を開始します。
  • EEPROMのアドレスを指定して送信方向でアドレスを送信します。
  • 次にEEPROM内部の書き込みたいアドレスを送信します。
  • 以降は指定されたバイト数のデータを送信します。
  • 送信が完了してストップ・コンディションを発行します。(EEPROMはストップ・コンディションを検出して、送信されたデータを内部のセルに書き込みます。)
u8 write_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size)
{
    i2c_adr &= 0xfe;
    if (!_free_i2c_bus())         // バスを開放させる。
        return  _set_i2c_error(I2C_BUS_NOT_FREE);  // バスが開放されない場合にはエラー・フラグをセットして戻る

    _I2C_STR();                   // スタート・コンディション発行

    if (!_put_i2c2(i2c_adr))      // I2C アドレス送信
        return  _set_i2c_error(I2C_ADDR_NACK); // 対象デバイスがノーリアクションならエラー・フラグをセットして戻る

    if (!_put_i2c2(reg_adr))      // レジスタアドレス送信
        return  _set_i2c_error(I2C_REG_ADDR_NACK); // サブ・アドレスでNACK発生ならエラー・フラグをセットして戻る


    do{
        if (!_put_i2c2(*data++))  // データ送信
            return  _set_i2c_error(I2C_DATA_NACK); // データ送信でNACK発生
    }while(--size);
    _I2C_STPR();                  // 正常終了でバスを開放
    return  _clear_i2c_error();   // エラー・フラグをクリアして戻る
}
(2) マスタ受信処理(u8 read_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size))
I2Cバスを使用して、256バイトのEEPROMからデータを読み出します。
第1の引数で示されたスレーブの第2の引数で示された内部アドレス以降から、第3のパラメータで示されたバッファに、第4のパラメータで示されたバイト数読み出します。
スレーブ・アドレスやスレーブ内部のアドレスの送信処理にはアセンブラ記述の__put_i2c2関数を使用し、エラーが発生した場合にはバスを開放し、発生した状況に応じたエラーを戻します。処理でエラーが発生しなかった場合には、バスを開放して、エラーなしを戻します。通信の手順は以下の通りです。
  • 最初にI2Cバスが開放状態であることを確認/バスを開放します。
  • 開放できなければ、エラーで戻ります。
  • スタート・コンディションを発行してバスの使用を開始します。
  • EEPROMのアドレスを指定して送信方向でアドレスを送信します。
  • 次にEEPROM内部の読み出したいアドレスを送信します。
  • リスタートをかけます。
  • EEPROMのアドレスを指定して受信方向でアドレスを送信します。
  • 指定したアドレスからのデータの読み出しを行ないます。
  • 読み出しの際に、最後のデータでなければ、ackeをセットしてACK応答するようにしてアセンブラの__get_i2cを呼び出して処理します。
  • 最後のデータについてはackeをクリアしてNACK応答する設定で__get_i2cを呼び出して処理します。
u8 read_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size)
{
    u8  d;
    i2c_adr &= 0xfe; 
    if (!_free_i2c_bus())
        return  _set_i2c_error(I2C_BUS_NOT_FREE);

    _I2C_STR();                     // スタート・コンディション発行

    if (!_put_i2c2(i2c_adr))        // I2C アドレス送信
        return  _set_i2c_error(I2C_ADDR_NACK); // 対象デバイスがノーリアクションなら、エラー・フラグを設定して戻る

    if (!_put_i2c2(reg_adr))        // レジスタアドレス送信
        return  _set_i2c_error(I2C_REG_ADDR_NACK); // サブ・アドレスでNACKならエラー・フラグを設定して戻る

    _I2C_STR();                     // リスタート

    if (!_put_i2c2(i2c_adr | 0x01)) // I2C アドレス(READ)送信
        return  _set_i2c_error(I2C_RADDR_NACK);  // 対象デバイスがノーリアクションならエラー・フラグを設定して戻る

    _ACK_EN();              // ACK 許可
    do{
        if (size == 1)      // 最後のバイト?
            _ACK_DS();      // マスタ受信最後のバイトならNACKに設定
        d = _get_i2c();     // データの読み込み
        *data++ = d;        // 読み込みデータをバッファにセット
    }while(--size);

    _I2C_STPR();            // バスを開放

    return  _clear_i2c_error(); // エラー・フラグをクリアして戻る
}
(3)EEPROMアクセス例
この使用例では受信用に32バイト、送信用に16バイトを準備してアセンブラのプログラム例(15)及び(16)や上記の(1) マスタ送信処理や(2) マスタ受信処理を使用します。使用するために必要な定義は以下の通りです。
u8      eep_read_buf[32];   // read buffer
u8      eep_write_buf[16];  // write buffer
u8      result;             // for result flag

#define     ADR_EEP     0xa0    // slave address
//              1010xxxyB
//              ||||||||
//              |||||||+--  R/W#
//              |||||++---  P1/P0 (fixed 00)
//              ||||+-----  A2 (fixed 0)
//              ++++------  I2C Addr (fix)
  • アセンブラ記述の関数を使用して、バッファ中の16バイトのデータをEEPROMの00〜0F番地に書き込みます。
    result = _write_i2c_block(ADR_EEP, 0x00, eep_write_buf, 16);
  • アセンブラ記述の関数を使用して、EEPROMの00〜1F番地のデータをバッファに読み出します。
    result = _read_i2c_block(ADR_EEP, 0x00, eep_read_buf, 32);
  • C言語記述の関数を使用して、バッファ中の16バイトのデータをEEPROMの00〜0F番地に書き込みます。
    result = write_i2c_block(ADR_EEP, 0x00, eep_write_buf, 16);
  • C言語記述の関数を使用して、EEPROMの00〜1F番地のデータをバッファに読み出します。
    result = read_i2c_block(ADR_EEP, 0x00, eep_read_buf, 32);

(2006/03)

この情報はお役にたちましたか?
back to top  
(2006/03)

i2c
-0002
ソフトウェアでI2Cバスのスレーブ機能の検討(78K0S/Kx1+)
ソフトウェアでI2Cバスのスレーブ機能を実現する場合のハードルとなるのは、以下の点が考えられます。
@スタート・コンディション/ストップ・コンディションの検出
ASCLに対するリード/ライトのタイミング遅延
Bビット処理時間
Cバイトの切れ目(9クロック目の後)での処理
D複数スレーブで非選択時のバスの無視

@については、専用のハードウェアがないので、常に監視することはできません。転送を起動するときにスタート・コンディションを検出するのと、処理途中のデータとデータの切れ目のところ(転送の9クロック目が完了したタイミング)で確認する程度になります。転送開始を検出する場合にも、ベクタ割り込み応答になってしまうので、割り込み応答時間+その後の確認までの時間を合わせて最低でも3μS(8MHzで動作するとした場合)程度必要になります。このことは、他の割り込みを処理していて1μS以上割り込みが保留になった場合には間に合わなくなることを示します。転送中のデータの切り目(通常のストップ・コンディションやリスタートのタイミング)についてもほぼ同じような時間が必要と考えると、どんな条件をつけてもファースト・モードの0.6μSの規格には対応は不可能で、標準モードのみへの対応となります。ファースト・モードに対応できる可能性があるのはV850ES以上の製品と考えられます。



AについてはSCLの立ち上がりからデータを取り込む時間の制限が厳しいと考えられます。これについては、同じポートの異なるビットを用いて同時に読み出すことで、SDAとSCLの関係は確保できますが、処理時間でのネックが予想(1回の確認を行なうループがポートのリード+ビット判定での分岐で12クロック必要、2回目で確認できたとすると24クロック:3μS必要となる)されます。これに関しては、通信速度を落とすことでSCLの幅を確保する必要があります。さらに、スタート・コンディションだけを割り込みで検出し、その後のデータのやり取りはシーケンシャルに処理し、全ての処理が完了したら割り込みから復帰するような対応が必要となります。これらの対策により、標準モードには何とか対応可能と考えられます。

Bビットデータの処理についてはAの条件が満足できれば処理できる可能性があります。ただし、単純に処理したのでは、アドレスに対するACK応答に関してはウェイトを使って処理する可能性があるので、スレーブからのウェイト機能をサポートしていない78K0R/Kx3のシリアル・アレイ・ユニットの簡易I2C機能では単純には接続できない可能性があります。この場合には、ウェイトをかけなくて済む程度の転送速度(標準モードには何とか対応可能)にしたり、必要な処理をいくつかに分割したりしてSCLの変化確認の間に分散して処理する必要があります。

C基本的にはここで以下に示すように、次のデータ/リスタート(スタート・コンディション)/ストップ・コンディションの3つの状態が考えられます。これらの判定を行う必要があるので、一番のボトルネックになると考えられるところです。信号の確認がソフトウェア処理で、確認間隔が広い(10〜12クロック)ため、リスタートの場合(と判断した場合)には、その後にストップ・コンディションが来ることも考えられます。



これらを考慮して、その前のウェイト状態(78K0R/Kx3のシリアル・アレイ・ユニットの簡易I2C機能を使っても通信と通信の切れ目なので次の通信を起動しない限りはウェイト状態になるはず)で何処まで前処理を行えるかが鍵になります。従って、スレーブからのウェイト機能をサポートしていない78K0R/Kx3のシリアル・アレイ・ユニットの簡易I2C機能でも単純にデータとデータの間隔がスレーブからウェイトをかけなくて済む程度にする必要があります(通常、マスタ側では、9クロック目のSCLが立ち下がったタイミングで割り込みが発生し、そこからACKを確認したり、受信したデータの処理をしたりするので、ここで1クロック分は間が空くと考えられます)。

Dこれが、全体の処理のボトルネックになる可能性の高い部分です。SCL毎に割り込み処理を行うと、他の処理ができなくなる可能性が十分にあります。そのため、SCLでの割り込みは行わず、スタート・コンディション検出のためにSDAの立ち下がりエッジを検出します。この場合には他のスレーブとの通信中のデータの変化で割り込みが入ることから通信状況をうまく管理できなくなります。そのため、シングル・マスタ、シングル・スレーブでの動作に制限する必要があります。

以上の結果をまとめると、以下のようになります。

・標準モード(通信クロックを100kHz以下)

・シングル・マスタ(想定外のタイミングでのストップ・コンディションが出ない)

・シングル・スレーブ(通信中以外に不要な信号変化がない)

・通信途中(8ビット目まで)でスタート・コンディションやストップ・コンディションが発行されない(通信前か9クロック目が完了した後のみで発行可能)

・他の割り込みは使用しない(割り込み応答の余裕が1μSしかないため)


以上の条件をつけることで、実現は可能と考えられます。

この条件を加味した具体的な検討については、「ソフトウェアでI2Cバスのスレーブ機能の実現について」を参照してください。

(2008/04)

この情報はお役にたちましたか?
back to top  
(2008/04)

i2c
-0003
ソフトウェアでI2Cバスのスレーブ機能の実現について(78K0S/Kx1+)
ソフトウェアでI2Cバスのスレーブ機能の検討」の結果の

・標準モード(クロックが100kHz以下)

・シングル・マスタ

・シングル・スレーブ(通信中以外に不要な信号変化がない)

・通信途中(8ビット目まで)でスタート・コンディションやストップ・コンディションが発行されない(通信前か9クロック目が完了した後のみで発行可能)

・他の割り込みは使用しない(割り込み応答の余裕が1μSしかないため)

の条件でのI2Cバスのスレーブ機能をソフトウェアによるポート制御で実現してみます。

(1)処理概要
以下に実際のプログラム例を示しながら、説明します。ここで対象にするプログラムはスタート・コンディションを割り込みで検出(実際には、単にSDAの立下りで割り込みが発生するので、その後にソフトで判定)して処理を開始します。処理を開始すると、通信完了するまではスタート・コンディションを検出した割り込みの中で処理を行い、スレーブ送信の場合にはマスタからのNACK応答で、スレーブ受信の場合にはデータ受信後のストップ・コンディション検出で通信を完了して割り込み処理から抜け出します。
データ転送の方向(受信か送信か)はI2Cバスの規格に従い、アドレスのビット0で判断しています。I2Cバスに関しては、FAQの「I2Cバスについての概要」を参照してください。データの送受信は内部に用意された送信/受信用バッファに対して行っています。このプログラムで使用する変数は以下のようになります。このプログラムを使用する場合には、これらを前もって設定しておく必要があります。
RXPOINT:受信データを格納するアドレスを保持している。データを受信することで自動的に更新される。
TXPOINT:送信するデータの格納アドレスを保持している。データを送信することで、自動的に更新される。
SAV0:スレーブとしてのアドレスを保持しています。マスタからのアドレス(上位7ビット)がこの値と一致すると、実際の通信を行います。ビット0は必ず0にしておく必要があります。アドレスが一致しなかったときには下記のACKEをクリアすることで、ACK応答をしなくなります。
IICC0:ビット2がACK応答するかどうかのフラグ(ACKE)です。このビットがセットされていると、マスタからのアドレスがSAV0の値と一致したらACK応答します。マスタからデータを受信したとき、受信したデータを処理する時間は次の通信を行わないように、フラグ(ACKE)をクリアします。そのため、受信データの処理が完了して、次のデータ転送が可能になったら、フラグ(ACKE)をセットする必要があります。

(2)全体の処理
ここで考えている通信処理全体の概略状態遷移図を以下に示します。



処理の開始はSDA信号の立ち下がり(INTP3)とします。SCL信号のとの関係をチェックしてスレーブ・アドレス受信に移ります。受信したスレーブ・アドレスが一致して応答許可状態のときにACK応答を行います。何か内部処理を実行中で通信処理したくないときには応答禁止状態にしておき、アドレスが一致してもACK応答しないようにします。このようにACK応答をしないことで、マスタに処理中であることを知らせます。スレーブ受信の場合には、受信準備をして、「バイト切れ目での処理」になります。ここで、ストップ・コンディションを検出すると、通信処理を完了します。スレーブ送信の場合には、マスタからACK応答があれば次のデータを送信し、NACK応答の場合(ACK応答がなければ)通信を完了します。
以下では詳細な処理について説明します。

(3)通信の起動(スタート・コンディション検出)
通信を開始するタイミング(スタート・コンディション)はSDA信号の立下りをエッジ検出したベクタ割り込みで検出するものとします。このとき、以下に示すプログラム例では、SCL信号がハイでなければ処理は行わないようにしても十分にスタート・コンディション検出が可能です。スタート・コンディションを検出したら、SCL信号が一旦立ち下がったことを確認してから、SCL信号の立ち上がりを待ちます

__INTP3:
                BF      P4.0,$NOTSTART          ;SCLがハイか確認
                PUSH    AX                      ;AXレジスタをセーブ
                MOV     IIC0,#00000000B         ;データ初期値を設定
LOOP0:

                MOV     A,P4                    ;SDAとSCL読み込み
                BF      A.0,$ADDRESS0           ;SCLの立下りで分岐
                BF      A.1,$LOOP0              ;SDAがロウなら継続
NOTACK:
                POP     AX                      ;AXレジスタを復帰
NOTSTART:
                RETI                            ;ストップコンディション検出



(4)アドレスの受信
SCL信号がロウになったことを確認してから、アドレス受信のためにSCL信号の立ち上がりを待ちます。このとき、MOV命令でSCL信号とSDA信号を一緒に取り込むことで、立ち上がりの確認間隔は12クロックとBF命令に比べて長くなります。しかし、SDA信号も同時に取り込めるので、タイミングずれを気にする必要がなくなります。
このときの処理プログラムは以下の例のようになります。SCL信号がハイのときにSDA信号が有効として、そのデータがハイの場合には変数IIC0の該当するビットを1にします。
ここでの最悪のケースとして、読み込みの直後にSCL信号が立ち上がったとしても、36クロック(4.5μS)でビットの受信処理は完了できます。その後のSCL信号の立ち下がり確認を含めて46クロック(約6μS)となり、ビットの周期10μSに対して十分なマージンで処理可能です。

ADDR7:          MOV     A,P4                    ;SDAとSCL読み込み
                BF      A.0,$ADDR7              ;SCLの立ち上がり待ち
                BF      A.1,$lowdata7           ;SDAのチェック
                SET1    IIC0.7                  ;SDAがハイならビットをセット
lowdata7:
                BT      P4.0,$$                 ;SCLの立下りを待つ



次のビット以降についても、同様にSCL信号の立ち上がりを待って処理を行います。上記の処理はビット位置を変えて処理する必要があるので、同じような処理を繰り返し記述する必要があります。ここでは記述を簡単にするためにマクロ機能を利用し、以下のように1ビットの受信処理を行うマクロsin1を定義します。

sin1    macro   bitno
        LOCAL   DATAis0,LOOP
LOOP:
                MOV     A,P4                    ;SCLとSDAを読み込む
                BF      A.0,$LOOP               ;SCL立ち上がり待ち
                BF      A.1,$DATAis0            ;SDAを確認
                SET1    IIC0.bitno              ;1ならビットをセット
DATAis0:
                BT      P4.0,$$                 ;SCLの立下り待ち
        endm

これで、アドレスの受信処理は、マクロ名とビット番号(マクロの引数)を組み合わせて以下のように記述することができます。

ADDRESS0:
                PUSH    HL
ADDRESS:
                sin1    7                       ;ビット7受信
                sin1    6                       ;ビット6受信
                sin1    5                       ;ビット5受信
                sin1    4                       ;ビット4受信
                sin1    3                       ;ビット3受信
                sin1    2                       ;ビット2受信

これで、ビット7〜ビット2までの受信処理を行います。ビット1(アドレスの最後のビット)についてはアドレスの比較が必要なためにできるだけ無駄な待ちループが発生しないよう最後のSCL信号の立ち下がり待ちの前にアドレス比較を行います。以下の処理を実行しても、ウェイトをかけることなくSCL信号のロウ期間に処理を完了して最後の転送方向受信処理に間に合います。

LOOP1:
                MOV     A,P4                    ;SCLとSDAを読み込む
                BF      A.0,$LOOP1              ;SCL立ち上がり待ち
                BF      A.1,$bit1is0            ;SDAを確認
                SET1    IIC0.1                  ;1ならビットをセット
bit1is0:
                MOV     A,SVA0                  ;アドレスを確認
                SUB     A,IIC0                  ;結果をゼロ・フラグに
                BZ      $RESACK                 ;アドレスが一致しないと
                CLR1    ACKE                    ;ACK応答禁止に設定
RESACK:
                BT      P4.0,$$                 ;SCL立下り待ち



(5)アドレスの受信完了からACK応答
アドレスの8ビット目(転送方向)を受信したら、アドレスが一致してなおかつACK応答可能なときにSCL信号の9クロック目でACK応答を行います。アドレスの一致は最初の7ビットでチェックが完了しているので、ここではその結果のACKEフラグを参照して応答するかどうかを判定します。アドレスが一致していても、ACKEフラグがクリアされている場合(別の処理を行っていて、応答できない場合)にはACK応答しません。従って、スレーブからACK応答がなかったときには、マスタはある程度時間を置いて再度スタート・コンディションから開始する必要があります。ACK応答しないときにはスレーブの処理はこれで完了となります。
転送方向を受信したSCL信号の立ち上がりから約7μSでACK応答を戻すことができます。これは次のSCL信号の立ち上がりに対して十分なマージンを確保できています。

                sin1    0                       ;ビット0受信
                BF      ACKE,$NOTACK0           ;ACK応答しないなら抜ける
                CLR1    PM4.1                   ;ACK応答



(6)ACK応答完了
ACK応答が完了したら、ACKの転送完了(SCL信号の立ち上がりと立ち下がり)を待ち、ACK応答を切ります。これでアドレス受信処理は完了したので、後は転送方向に応じた処理となります。SCL信号の変化待ちでは処理に余裕があるので、前もって転送方向で処理を分けておきます。

                MOV     A,IIC0                  ;アドレスをAレジスタに
                ROR     A,1                     ;CY=0:受信、1:送信
                BNC     $RXDATA                 ;受信なら分岐

(7)スレーブ送信でのアドレス9クロック目以降の処理(送信準備)
スレーブ送信では、事前にデータを準備する必要があります。その処理時間が必要なので、前サイクルのACK応答後に、すぐ処理を開始しておきます。SCL信号の立ち上がり周期10μsに対して、ACK応答は7.7μsで完了しているので、2.3μsの余裕があります。そこで、ACK応答完了処理のMOV A,IIC0命令から下記MOVW HL,AX命令までの実行後、SCL信号が立ち上がったかどうかを確認します。これらの処理に22クロック(2.8μs)必要なので、SCL信号の立ち上がりから0.5μsで、立ち上がり確認のBF P4.0,$$命令を実行することになります。これで、SCL信号の立ち上がりは1回だけの確認で次に進めます。この部分の処理は、7.5μsで完了します。

TXDATA:
                MOVW    AX,TXPOINT              ;データ・ポインタを読み出し
                MOVW    HL,AX                   ;HLにポインタをセット
                BF      P4.0,$$                 ;SCL立ち上がり確認
TXLOOP:
                MOV     A,[HL]                  ;送信データ読み出し
                INCW    HL                      ;ポインタ更新
                MOV     TXCOUNT,#8              ;ビット数をセット
TXBLOOP:
                ROL     A,1                     ;MSBをキャリーへ
                BT      P4.0,$$                 ;SCL立ち下がり待ち
                SET1    PM4.1                   ;ACK応答解除
                BC      TXBIT                   ;データが1なら分岐
                CLR1    PM4.1                   ;SDAをロウに
TXBIT:



(8)データ送信処理〜ACK確認
スレーブ送信処理では、SCL信号の変化を待つだけなので、ループで処理しても十分に処理可能です。そこで、ループ・カウンタ(変数TXCOUNT)を用いて制御します。8ビットの送信が完了すると、SDA信号を開放してACK応答を待ちます。マスタからACK応答があった場合には次のデータに進みます。ACK応答がなかった場合には更新されたデータ・ポインタを保存して、処理を終了します。

TXBIT:
                BF      P4.0,$$                 ;SCL立ち上がり待ち
                DBNZ    TXCOUNT,$TXBLOOP        ;8ビット分繰り返し
                BT      P4.0,$$                 ;SCL立ち下がり待ち
                SET1    PM4.1                   ;SDA開放
NOTACK0:
                BF      P4.0,$$                 ;SCL立ち上がり待ち
                BF      P4.1,$TXLOOP            ;ACKなら次データへ
                MOVW    AX,HL                   ;ポインタをAXへ
                MOVW    TXPOINT,AX              ;ポインタを保存する
                SET1    STATUSFLAG.6            ;送信完了フラグをセット
                BT      P4.0,$$                 ;SCL立ち下がり待ち
                POP     HL                      ;レジスタを復帰
                POP     AX                      ;AXレジスタ復帰
                CLR1    PIF3                    ;割り込み要求をクリア
                RETI                            ;処理完了

スレーブ送信処理では、マスタからのNACK応答を確認した段階で処理を完了しています。この後、マスタはストップ・コンディションかスタート・コンディションを発行することになります。ここで抜けてしまうと、ストップ・コンディションを発行するときには、以下のような信号の動きとなり、SDAを立ち下がりだけを見ているので、スタート・コンディションを誤検出します。しかし、INTP3割り込み処理の中でSCLがロウなら何もしないで抜けるようになっていることと、SCLがハイならSDAの立ち上がり(ストップ・コンディション)で抜けるので、問題はありません。



(9)スレーブ受信での9クロック目以降の処理

RXDATA:
                BF      P4.0,$$                 ;SCL立ち上がり待ち
                MOVW    AX,RXPOINT              ;データ・ポインタを読み出し
                MOVW    HL,AX                   ;HLにポインタをセット
                BT      P4.0,$$                 ;SCL立ち下がり待ち
                SET1    PM4.1                   ;ACK応答解除
                MOV     IIC0,#00000000B         ;データ初期値を設定

9クロック目のSCL信号が立ち下がったら、ACK応答を解除してSDA信号を解放します。引き続いて、データの受信処理に移ると以降は繰り返し処理となります。

ここでは以下の3つの場合があり、タイミング的に一番苦しくなります。
@次のデータを受信する
Aスタート・コンディション(リスタート)を受け付ける
Bストップ・コンディションを受け付ける

@の場合にはSCL信号がロウの状態でSDA信号が変化して、SCL信号が立ち上がります。その後SDA信号は保持したままでSCL信号が立ち下がります。SCL信号が立ち下がって始めてデータだと分かります。この場合にはSCL信号のハイ幅が4μS以上で、SDA信号は250nSのセット・アップ時間と0nSのホールド時間で変化する可能性があります。この時間は判別できる時間間隔よりはるかに小さいので、単独または、SDA信号立ち下がりと同時にSCL信号の立ち下がりが検出できたらデータ受信と判断することになります。



Aの場合にはSCL信号がロウの状態でSDA信号が立ち上がり、SCL信号がハイの状態でSDA信号が立ち下がります。SDA信号の立ち下がりでスタート・コンディションであることは分かりますが、SCL信号が立ち下がるまでは次には移れません。



Bの場合にはSDA信号をどのタイミングで立ち下げるかで、さらに2つの場合が考えられます。SCL信号がロウの状態で立ち下がれば、単純にストップ・コンディションの発行だけとなります。SCL信号の立ち上げと同時またはその後でSDA信号が立ち下がった場合には、そこがスタート・コンディションと同じになります。その後でストップ・コンディションが発行されることになります。つまり、スタート・コンディションを検出した場合にはSCL信号が立ち下がるか、SDA信号が立ち上がるかを待つ必要があります。



この部分の状態遷移図を以下に示します。



この処理のプログラム例を示します。

@SDA信号がハイであったときの処理
まず、SCL信号が立ち上がったときに、SDA信号がハイの場合の処理を行います。青がデータ受信の場合で、赤がスタート・コンディションの場合です。SCL信号の立ち上がりとSDA信号の組み合わせでの場合分け(下記プログラムのLOOP21以降の3命令)は、SCL信号の立ち上がりから30クロック(3.7μs)以内に処理できるので、SCL信号の周期が短い(4μs)データ転送の場合でも、タイミングに問題はありません。STCLOOP以降の3命令で、データ受信開始かスタート・コンディション(リスタート)を待っています。データ受信では、SCL信号の立ち下がりから30クロック(3.7μs)以内に検出できるので、SCL信号がロウの期間(4.7μs)に処理可能です(以降の処理はD)。

;
;       アドレス受信、データ受信した後の処理の判断を行う。
;
LOOP21:
                MOV     A,P4                    ;SCLとSDAを読み込む
                BF      A.0,$LOOP21             ;SCL立ち上がり待ち
                BF      A.1,$SDALOW             ;SDAがロウなら分岐
;
;       スタート・コンディションかデータ待ち
;  SCLが立ち下がれば通常データ受信(D)
;  SDAが立ち下がればスタート・コンディション(A)
;  ここは必ずSCLの確認してからSDAを確認する必要がある。
;
STCLOOP:
                MOV     A,P4                    ;SCLとSDAを読み込む
                BF      A.0,$DATAREAD           ;SCLが立ち下がれば受信へ
                BT      A.1,$STCLOOP            ;変化なければループ



Aリスタート
リスタートの場合、信号の読み取りが20クロックのループになり、1回は確実に確認できますが、確認処理完了に最大38クロックかかります。このとき、次データのためにSCL信号が立ち下がっている可能性があります。その後、4クロックで信号を確認します(次図の)が、このときの状態で、次が何かを判断する必要があります。
SCL信号が立ち下がれば(実線)、リスタート後のアドレス受信(B)の開始です。SCL信号がハイのままSDA信号が立ち上がれば(破線)、ストップ・コンディション(C)です。ここでは、どちらかの信号が変化するまで待ちます。



;
;       スタート・コンディション検出
;  スタート・コンディションを検出したらSCLの立下り(通信開始)か
;SDAの立ち上がりを待つ
;
                MOV     IIC0,#0                 ;受信ワークをクリア
STCLOOP2:
                MOV     A,P4                    ;SCLとSDAを読み込む
                BF      A.0,$ADDRESS2           ;SCLの立下りでアドレス受信
                BF      A.1,$STCLOOP2           ;ストップ・コンディション以外

Bアドレス受信
アドレス受信時の分岐先のADDRESS2は、本来ADDRESSですが、ここからの条件分岐では直接飛べず、また、いくつかの処理が必要なためのクッションです。RXDATAの直前に配置しておく必要があります。

;
;       リスタート時にアドレス受信へ戻る処理
;
ADDRESS2:
                MOVW    AX,HL
                MOVW    RXPOINT,AX              ;データ・ポインタを保存
                BR      ADDRESS

Cストップ・コンディション
受信を完了して、ストップ・コンディションを検出したら、処理を完了します。ここでは、その後の受信データ処理を考慮して、ACKEをクリアして、以降はマスタが通信を開始し、そのアドレスが一致してもACK応答しないようにしています。受信データの処理が完了したら、ACKEをセットしておく必要があります(変数IICC0に4を設定)。

;
;       ストップ・コンディション検出
;  通信完了フラグをセット、ACK応答禁止にして処理を完了します。
;
STPCND:
                SET1    STATUSFLAG.7            ;通信完了フラグをセット
                CLR1    ACKE                    ;ACK応答禁止に設定
                MOVW    AX,HL
                MOVW    RXPOINT,AX              ;データ・ポインタを保存
                POP     HL                      ;HLレジスタ復帰
                POP     AX                      ;AXレジスタ復帰
                CLR1    PIF3                    ;割り込み要求をクリア
                RETI                            ;処理完了

Dデータ受信処理(SDA=Hのとき)
次データ受信の処理です。最初のビットは受信完了済みなので、残り7ビットの受信でACK応答し、受信データをバッファに書き込みます。その後、(9)スレーブ受信での9クロック目以降の処理の最初に戻ります。

;
;       データ受信処理
;  最上位ビットは既に受信済みのため、残り7ビット分を受信する。
;受信が完了して、ACK応答後9クロック目でウェイトして処理を抜ける。
;
DATAREAD:
                MOV     IIC0,#10000000B         ;ビット7は1であった
DATAREAD2:
                sin1    6                       ;ビット6を受信
                sin1    5                       ;ビット5を受信
                sin1    4                       ;ビット4を受信
                sin1    3                       ;ビット3を受信
                sin1    2                       ;ビット2を受信
                sin1    1                       ;ビット1を受信
                sin1    0                       ;ビット0を受信
                CLR1    PM4.1                   ;ACK応答
                MOV     A,IIC0                  ;受信データ読み出し
                MOV     [HL],A                  ;受信データの保存
                INCW    HL                      ;ポインタ更新
                BF      P4.0,$$                 ;SCL立ち上がり待ち
                SET1    STATUSFLAG.6            ;受信フラグをセット
                BT      P4.0,$$                 ;SCL立下り待ち
                SET1    PM4.1                   ;ACK解除
                BR      !LOOP21                 ;継続処理へ



上記のように、ビット6以降の処理へ分岐してきた場合も、SCL信号の立ち上がり前に処理を開始できるので、タイミングの問題はありません。また、SDA信号がロウの場合に、この処理を共通で使用しようとすると、分岐命令で6クロック分追加されますが、それでも十分にSCL信号がハイの状態でサンプリング可能で、処理でのタイミングの問題もありません。
最後のビット(ビット0)を受信したら、ACK応答して、受信データをバッファに保存します。この処理も、SCL信号の変化に対して十分にマージンがあります。



ESDA信号がロウであったときの処理
ここでは、SCL信号が立ち上がったときに、SDA信号がロウの場合の処理を行います。このときの次の状態は、データ受信(F)かストップ・コンディション(C)かのどちらかです。

;
;       ストップ・コンディションかデータ待ち
;  SCLが立ち下がれば通常データ受信(F)
;  SDAが立ち上がればストップ・コンディション(C)
;  ここは必ずSCLを確認してからSDAを確認する必要がある。
;
SDALOW:
STCLOOP3:
                MOV     A,P4                    ;SCLとSDAを読み込む
                BF      A.0,$DATAREAD3          ;SCLが立ち下がれば受信へ
                BF      A.1,$STCLOOP3           ;変化なければループ
                BR      $STPCND                 ;ストップ・コンディションへ

Fデータ受信処理(SDA=Lのとき)
次データ受信の処理です。最初のビットは受信完了済みなので、残り7ビットの受信でACK応答し、受信データをバッファに書きこみます。この例では、最初のビットを0にして、Dのビット6受信に分岐させています(共通の処理)。

DATAREAD3:
                MOV     IIC0,#00000000B         ;ビット7は0であった
                BR      $DATAREAD2              ;受信処理継続

(10)各種の宣言
このプログラムを使用する上で使用する変数や定数は以下のように定義しています。

;************************************************************************
;                                                                       *
;               データ領域(変数やフラグ)の定義                        *
;                                                                       *
;                                                                       *
;************************************************************************
PUBLIC          TXDATABUFF                      ;送信データ用
PUBLIC          RXDATABUFF                      ;受信データ用
PUBLIC          _RXDATABUFF                     ;受信データ用(C言語用)
PUBLIC          TXPOINT                         ;送信ポインタ
PUBLIC          _TXPOINT                        ;送信ポインタ(C言語用)
PUBLIC          RXPOINT                         ;受信ポインタ
PUBLIC          _RXPOINT                        ;受信ポインタ(C言語用)
PUBLIC          SVA0                            ;アドレス
PUBLIC          _SVA0                           ;アドレス(C言語用)
PUBLIC          IICC0                           ;動作指定
PUBLIC          _IICC0                          ;動作指定(C言語用)
PUBLIC          STATUSFLAG                      ;動作結果フラグ
PUBLIC          _STATUSFLAG                     ;動作結果フラグ(C言語用)
PUBLIC          __INTP3                         ;割り込み処理部

;
;       データ・バッファ
;  送信用、受信用で各々16バイト分を確保しておく
;
        DSEG
TXDATABUFF:
                DS      16                      ;送信データ用バッファ
RXDATABUFF:
_RXDATABUFF:
                DS      16                      ;受信データ用バッファ

;
;       データ・ポインタ
;  送受信のデータ・バッファをアクセスするポインタの保存用
;
        DSEG            saddrp
TXPOINT:
_TXPOINT:
                DS      2                       ;送信用ポインタ
RXPOINT:
_RXPOINT:
                DS      2                       ;受信用ポインタ
;
;  作業用のフラグ、変数
;
        DSEG            saddr
IIC0:
_IICC0:
                DS      1                       ;受信データ作業用
SVA0:
_SVA0:
                DS      1                       ;スレーブ・アドレス保持用
IICC0:
_IICC0:
                DS      1                       ;動作指定用
ACKE            EQU     IICC0.2                 ;ACK応答制御用
_ACKE           EQU     IICC0.2                 ;ACK応答制御用
      &nbs