先日、I2C接続のRTCが動いたのだが、対象のデバイスがローカルすぎて、
誰も追試できるような状況ではなかったです。
それで、I2Cとしては、もっと汎用的なデバイスでも試してみたかったので、
EEPROM用の入出力を実装してみたが、どうも、動作しない。
読み込みは出来てるようだけど、書き込みが出来ない・・・
どうも、I2C の STOP コンディションが、正しく機能していないようで、
調査したら、ACK を受け取った後に、SDA を「0」レベルに保つ必要があっ
たのに、抜けてたようだった。
※SDA を「0」に戻さないと、STOP コンディションが正しく機能しない。
それで、これを修正したら、無事書き込みが出来るようになった。
EEPROM は、STOP コンディションをトリガーにして、実際に書き込む為
動作しなかったようだー。
i2c_io クラスでは、なるべくシンプルにした方が良いだろうと思って、単純に
データを転送する関数と、受け取る関数のみ用意していたけど・・
//-----------------------------------------------------------------// /*! @brief 受信(リード) @param[in] address スレーブアドレス(7ビット) @param[out] dst 先 @param[in] num 数 @return 失敗なら「false」が返る */ //-----------------------------------------------------------------// bool recv(uint8_t address, uint8_t* dst, uint8_t num) const; //-----------------------------------------------------------------// /*! @brief 送信(ライト) @param[in] address スレーブアドレス(7ビット) @param[in] src 元 @param[in] num 数 @return 失敗なら「false」が返る */ //-----------------------------------------------------------------// bool send(uint8_t address, const uint8_t* src, uint8_t num) const;
内部動作では、EEPROMにデータを書き込む場合は、データアクセスのアドレス
を、指定する必要がある。
通常の実装だと、テンポラリーを用意して、アドレスとデータをコピーしてから、
送らないとならない。
しかし、それでは、効率が悪いので、冗長ではあるけど、関数を追加する事にした。
アドレス指定は、1バイトと2バイト用に二種類がある。
//-----------------------------------------------------------------// /*! @brief 送信(ライト) @param[in] address スレーブアドレス(7ビット) @param[in] first ファーストデータ @param[in] src 元 @param[in] num 数 @return 失敗なら「false」が返る */ //-----------------------------------------------------------------// bool send(uint8_t address, uint8_t first, const uint8_t* src, uint8_t num) const; //-----------------------------------------------------------------// /*! @brief 送信(ライト) @param[in] address スレーブアドレス(7ビット) @param[in] first ファースト・データ @param[in] second セカンド・データ @param[in] src 元 @param[in] num 数 @return 失敗なら「false」が返る */ //-----------------------------------------------------------------// bool send(uint8_t address, uint8_t first, uint8_t second, const uint8_t* src, uint8_t num) const;
内部で行っている事は単純だけど、EEPROMへの書き込みはシンプルとなった。
又、EEPROMの書き込みでは、最後のSTOPコンディションがトリガーとなり、
ページバッファにあるデータを実際に書き込む、書き込み時間は比較的長く、5ミリ
秒とかかかる、その為、書き込みが終了するまで、待機する必要があり、今回はポー
リングで待つ事にした。
ポーリングは、比較的簡単で、書き込んだアドレスを読み出しに行けば、書き込み中
なら、正規のACKが返らないので、読み出しが失敗する、それを利用している。
もし、規定時間以上、ACKが返らない場合は、「書き込みが失敗した」として、
ストールさせる。
ポーリングで待つ場合だと、EEPROMの持っている性能を最大限生かせて、
スループットは最大となる。
あと、重要なのは、ページ境界をまたがった書き込みの場合で、start 関数で指定した
ページサイズを超えた書き込みが発生した場合は、2回に分けて書き込むように配慮
した。
さらに、もう一つの仕様として、ページサイズ以内で、ページを超えない書き込みの
場合は、書き込み終了を待たずに、関数は、直ぐに終了する。
アプリ側で、ページサイズ以内で、書き込み待ちが起こらないような間隔で書き込むよ
うに配慮してあれば、「待ち」の為にポーリングしなくて済むので、CPU時間を有効
に利用できる。
この場合、書き込みの発行を分けて、複数回行うようなコードを書くと、二回目の
書き込みで「失敗」してストールする。
※これは、回避する事も出来るけど、余分なワークを消費するので、仕様とした。
一応、書き込み中か、判定する関数も用意してある。
//-----------------------------------------------------------------// /*! @brief 書き込み状態の検査 @param[in] adr 検査アドレス @return 「false」なら、書き込み中 */ //-----------------------------------------------------------------// bool get_write_state(uint32_t adr) const;
I2C_EEPROM_test
実際のテストでは、ターミナルとの対話形式で、書き込み、読み出しのテストを行えるようにした。
ボーレートは19200、8ビット、1ストップビット
Start R8C EEPROM monitor # help speed KBPS (KBPS: 10 to 1000 [Kbps]) read ADRESS [LENGTH] write ADRESS DATA ... fill ADDRESS LENGTH DATA ...
EX:
# speed 1000
1000Mbps (1M bps) の指定
# read 1000
1000 番地から16バイト表示
# read 1000 10ff
1000 番地から、10ff 番地までダンプ
# write 127e fc ba 95 67 8e
127E 番地より、「FC BA 95 67 8E」を書き込み
※最大8バイトまで
# fill 0 1ff 1 23 45 6 78 9a b
00000 ~ 001FF 番地までを、「01 23 45 06 78 9A 0B」で埋める。
※最大8バイトまで