R8C I2C で EEPROM アクセス

先日、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;

IMG_0751s
先日、実験したLCD基板で、実験中~

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バイトまで

R8C GitHub