I2Cとシリアルメモリ:

  1. シリアルメモリの構成

    1. 必要性

      ミドルレンジのPIC等では、内部のメモリは多くても1KB程度で、大量のデータを記録できません。大量の記憶が必要な場合外付けのメモリを使用しますが、複数のアドレスやデータ線を必要とするメモリは直接接続出来ません。そこで、クロック信号と双方向のデータ信号の2本だけで、読み書きを行うシリアル接続のフラッシュメモリがよく利用されます。
       フラッシュメモリは、電源を切っても記憶を保持可能で、書き込みには数mSの時間が必要です。読み出しは連続アドレスの場合、続けて読み出し可能ですから、クロックの1/10程度の速度で読み出し可能です。ランダムなアクセスには、アドレス指定等が必要なため、連続の場合の4倍のアクセス時間が必要です。

    2. シリアルメモリ仕様

       ここで紹介するシリアルメモリはフラッシュメモリで、書き込みをすると電源を切っても値を保持できます。書き込みの保証回数は、10万回から100万回となっています。バイト単位または64バイト単位での書込みが可能です。
       以下にATMEL社の AT24Cシリーズのメモリを紹介します。

      容量 書込み速度 最大クロック 電圧
      24LC128 128kbits 5ms 400kHz 2.5-5.5V
      24LC256 256kbits 5ms 400kHz 2.5-5.5V
      24C1024 1024kbits 5ms 400kHz 2.5-5.5V

      詳細は http://www.atmel.com/dyn/resources/prod_documents/doc0670.pdf をご覧下さい。

    3. 構成

      A0,A1,A2はチップアドレスで、アクセス時に指定したアドレスのチップのみが応答します。この信号を適当に設定することで、8個のメモリを並列接続することができます。ただし、24C1024はA1のみが指定可能ですから、最大2個までしか接続できません。
       SCLがクロック、SDAがデータ信号です。シリアル信号はI2Cタイプです。WPはWriteProtectで書き込み保護に利用します。WPが H(1) のとき書き込みできません。この端子は接続しないときL(0)に成りますが、雑音対策の意味では、H、または、Lに接続する必要があります。


  2. 接続法(I2C)

    1. I2Cによる接続

       I2Cは基板内の短距離での接続を目的とした、汎用のシリアル接続です。SDAとSCLの2本の信号を用いて指定したアドレスへの書き込み、読み出しを行うことができます。SDAとSCLはバス信号で、複数の素子を接続できます。
       バス信号を利用してデータの読み取り、書き込みを要求行う素子をマスタ、マスタに応じてデータを受け取り、送出を行う素子をクライアントといいます。ここでは、PICがマスタ、メモリがクライアントになります。

    2. バス接続

      PICのSDA信号とSCL信号をメモリのそれと接続します。メモリはこのライン上に複数接続可能で、A2,A1,A0 の端子でチップアドレスを設定できます。ここで使用する24LCシリーズは3本の端子でチップアドレスを指定できますから、最大8個までこのライン上に並列に接続できます。
       PIC側では、RB1(SDA/DT)とRB2(SCL/CK)に対応する信号を、5kオームの抵抗を通して5Vに接続します。高い電圧で信号線を吊り上げますから、この抵抗をプルアップ抵抗と呼ぶことがあります。

    3. チップアドレス

       I2Cには複数のI2Cクライアントを接続できます。クライアントはチップアドレスで指定します。24LCxxでは A2,A1,A0 端子にチップアドレスを設定します。この実験では、接続するメモリは一つだけですから、A2,A1,A0 をすべてグランド(0V)に接続し、アドレスを 0 とします。
       チップ内部のメモリアドレスは、マスタがクライアントに送るコマンドで指定します。ただし、24C1042では指定できるチップアドレスはA1のみです。

    4. 注意

       同じ I2C のライン上に同じチップアドレスのデバイスは接続できません。RTCはチップアドレスが 001  固定ですから、24C1042とRTCを同時に接続する場合、24C1024のA1を1にして接続する必要があります。A1を0にして接続すると、RTCと24C1024が衝突します。

  3. I2Cのアクセス法

    1. スタート・ストップコンディション

       SDAとSCLは1とします。SCLを1にしたまま、SDAを1>0に変化させます。これが、スタートコンディションです。続けてデータを送り、最後のSCLを1にしたまま。SDAを 0>1 に立ち上げます。これがストップコンディションです。

    2. 応答

       1バイトのデータ書込み後、SCLにパルスを送ります。このとき、書込みへの応答としてメモリは SDA を0に下げます。これを応答と言います。バイトデータの書込み後はこの応答を確認する必要があります。

    3. バイト単位書込み手順

       スタートコンディションを設定し、次に 1010 aaa0 のコントロールバイトを順に送ります。aaaはチップアドレスで、外部端子のA2,A1,A0 に対応します。次に、アドレス上位、アドレス下位バイト、書込みデータを送り、最後にストップコンディションを送ります。各バイト毎に 応答の確認を行います。
       ただし、24L1024の場合128Kバイトのメモリが利用できますが、指定できるアドレスは16bitですから64Kバイトまでです。64KBを超える場合は、チップアドレスを1010 0a10 とします。つまり、24L1024 は512kBのチップが二つ内臓されたメモリとして扱うことになります。a は 外部端子のA1の値で定まります。

    4. 64バイト単位の書込み

      スタートコンディションを送り、続けて 1010 aaa0 のコントロールバイトを送ります。aaaはチップアドレスです。
       次に、先頭のアドレス上位、アドレス下位バイト、書込みデータを64バイト(以内)続けて送り、最後にストップコンディションを送ります。各バイト毎に応答信号が返ります。1バイト送るごとにメモリの下位アドレスは増加しますが、上位アドレスは自動増加しません。したがって、256バイトの境界をまたいで、連続書き込みをすることはできません。連続書き込みをするバイト数を2のべき乗にするか、ページ境界をまたがる場合に2回の書き込みに分割する必要があります。ページ境界をまたいで書き込んだ場合、同じページの先頭バイトを書き換えてしまいます。

    5. 書き込み速度

       20MHzの外部クロックの16F88をマスターにして書き込みを行うと64バイトの書き込みに、7.5mS が必要です(実測値)。その後、ブロックの書き込みに5mSの時間が必要ですから、合計12.5mSが必要です。5kサンプル/秒で64バイト読み込み時間が12.8mSですから、これが16F88で64バイト単位でフラッシュメモリに書き込むときの最大速度になります。

    6. バイトデータ読みだし

      スタートコンディションを設定し、続けて 1010 aaa0 のコントロールバイトを送ります。aaaはチップアドレスです。
       次に、アドレス上位、アドレス下位バイト、を送ります。ここまでは、書込みと同じです。
       次に 1010aaa1 の読み出し用コントロールバイトを送ります。 応答を受け取ったらデータ受信を待ちます。データを受け取ったら NACK を返し、最後にストップコンディションを送ります。

    7. 連続データ受信

      読み取り開始まではバイトデータ読みだしと同じです。データを受け取ったとき ACK を返すと、次のデータを送ってきます。最後のデータを受け取ったら NACK を返し、最後にストップコンディションを送ります。

  4. 回路

    1. 接続

       PIC648をI2Cで接続する場合の回路は次のようになります。PICのRB2とメモリのSCL(6ピン),RB3とメモリのSDA(5ピン)を接続します。メモリのA2,A1,A0ピンはGNDに固定します。I2Cの2本の信号は2kの抵抗を通して電源に接続(プルアップ)する必要があります。24C1024の場合、A2,A0 ピンは接続しても意味のない端子になります。

       ここでは、PIC873を用いた下図の回路で実験します。microLoaderバーLEDの接続回路は省略しています。microLoaderの回路は、ブートローダを利用してプログラムを書き込むときのみ必要です。結果の確認はバーLEDで行います。

  5. プログラミング

    1. I2cプログラム

      先頭でi2cの利用とそのピン番号を宣言します。sdaとsclはデータとクロック信号のピン番号です。force_hw はI2cをソフトでなく組込みのハードで行う宣言です。この場合、ピン番号は変更できません。

      #use i2c(MASTER,sda=PIN_C4,scl=PIN_C3,force_hw)

      実際の読み書きは関数で行います。

      void writeEXrom(int chip,long address,int data) で書込みを行います。chipはチップアドレスですが、先頭に1010を付記するため、A2,A1,A0によるチップアドレスが0の場合、chipは oxa0 とします。他に書き込むaddress:アドレスと data:データを指定します。読み出しは

      int readEXrom(int chip,long address)
      で行います。chipとaddress:アドレスを指定すると、メモリの値が戻り値となります。

    2. プログラム

      このプログラムではバイト単位の読み書きを行います。0番地から順に0,1,2 の値を書き込み、その後、0番地から読み出し、Bポートに接続したバーLEDで表示します。

      #include <16f873A.h>
      
      #fuses HS,NOWDT,NOLVP //内部クロック、WDT,LVPなし
      #use delay(CLOCK=20000000)
      #use i2c(MASTER,sda=PIN_C4,scl=PIN_C3,force_hw)//use delayの後に配置する
      #use fast_io(C)
      
      void writeEXrom(int chip,long address,int data);
      int  readEXrom(int chip,long address);
      void dispBar(int var);
      
      int i;
      
      void main()
      {
       long ad;
       int data;
      
       output_float(pin_c4);
       output_float(pin_c3);
       
       //バーをクリアする
       output_high(PIN_B5);
       output_low(PIN_B5);
       output_low(PIN_B4);
       output_high(PIN_B5);
        
       delay_ms(100);
       for(ad=0;ad<10;ad++){
        writeEXrom(0xa0,ad,(int)ad);//書込み
        }
        
      while(1){
      
        for(ad=0;ad<10;ad++){
           data=readEXrom(0Xa0,ad);//読み出し
           dispBar(data);
           delay_ms(500);
         }
       }     
        
      }
      
      void writeEXrom(int chip,long address,int data)
      {
        //chipのメモリのaddress番地にdataを書く
        i2c_start();
        i2c_write(chip);
        i2c_write(address>>8 );
        i2c_write(address);
        
        i2c_write(data);
        
        i2c_stop();
        delay_ms(10);
        
      }
      
      int readEXrom(int chip,long address)
      {
        //chipのメモリのaddressを読み、その値を返す
        int data;
        i2c_start();
        i2c_write(chip);
        i2c_write(address>>8);
        i2c_write(address);
        
        i2c_start();
        i2c_write(chip | 0x01);
        data = i2c_read(0);
        
        i2c_stop();
        return data;
      }
      
      void dispBar(int var)
      {
      //
      //クリアする
        output_low(PIN_B5);
        output_high(PIN_B5);
               
        //ht個点灯する       
        for(i=0;i<var;i++){
            output_high(PIN_B4);
            output_low(PIN_B4);
            delay_us(10);  
         }
         
       }