ホームページで紹介している「音声告知クロック」ではI2C-EEPROMにWAVデータを書き込んでPICマイコンでコントロールしています。
その中のI2C-EEPROMの読み出しについてのみ詳しく解説致します。
ここではPICの持つI2Cモジュールを使わないで、ソフトのみでI2C-EEPROMデータを読み込むサブルーチンです。
このため、I2C通信で用いるSDA端子とSCL端子のポート端子レイアウトは自由であり、ハードに合わせることができます。
EEPROMは電源を切ってもデータを保持しているROMであり、EEPROMとはElectronically
Erasable and Programmable Read Only Memoryの略で、電気的にデータ書き換えができるものです。従って、紫外線で全てのデータを一旦消去するEPROMとは違って、部分的なデータ書き換えができる便利なROMです。
I2C-EEPROMのI2CとはI2C通信と呼ばれる手順でデータの送受信を行なうものです。
I2C通信はマスター(主人)と、スレーブ(奴隷)の関係があり、マイコンとI2C-EEPROMでは、マイコン側がマスター、EEPROMがスレーブとなります。
I2C通信はSDA(データ)、SCL(クロック)の2本線によって行なわれ、接続例を下図に示します。
図では複数のEEPROMが接続されています。
これは、マイコンがどのEEPROMにアクセスするのか、コントロール信号の中にデバイスアドレスを含んでいるからです。
このデバイスアドレスはI2C-EEPROMのデバイスアドレス端子のロジックレベルの設定で取り決めておきます。
I2C-EEPROMのデバイスアドレス端子は1本(拡張は2個まで)〜3本(拡張は8個)存在するでしょう。これは使うI2C-EEPROMのデータシートで確認します。
1つしか使わない場合はデバイスアドレス端子はLレベル(デバイスアドレスは0)にしておくとデバイスアドレスを変更することなく読み出しができますが、これはハードに合わせるべきで勝手でしょう。
I2C-EEPROMの例を下図に示します。
WP端子はライトプロテクトでHレベルにすると書き換え不可となるので、読み出し専用ならばVccと同電位にしておくと安心です。
上図のI2C-EEPROMではデバイスアドレスの端子は2つあることが判ります。
また、私の経験で、高速動作させる場合はVcc-GNDのパスコンはできるだけ接近して接続しないと誤動作を起しますから、ブレッドボードで実験・試作する場合は気を付けて下さい。
ここで、I2C-EEPROMからデータを読み込むまでのフローを記述してみます。
手順1 | スタートシーケンス | SDAを"L"レベル |
手順2 | コントロールシーケンス (デバイスアドレス+書き込みビット) |
1010***0 ***にデバイスアドレスが入る |
手順3 | アドレス上位8ビット | 00h〜FFh |
手順4 | アドレス下位8ビット | 00h〜FFh |
手順5 | スタートシーケンス | |
手順6 | コントロールシーケンス (デバイスアドレス+読み出しビット) |
1010***1 ***にデバイスアドレスが入る |
手順7 | データリード 継続読み出し |
データ受信後にACK信号を送出すると 次のアドレスのデータを受信できる。 |
手順8 | データリード 最終読み出し |
データ受信後にACK信号を送出しない。 |
手順9 | ストップシーケンス | SDA,SCL共に"H"レベル |
アドレスの指定は上位1バイト下位1バイトですから、0000h〜FFFFhとなり、512Kビット留まりとなることは理解できますね?
ところが、1MビットI2C-EEPROMの24C1024というデバイスがあります。
この場合のアドレス範囲は1Mビットですから、00000h〜1FFFFhとなり、アドレス3バイト目の1ビット(17bit目)が指定できないと思いませんか?
24C1024では、アドレスの最上位17ビット目が、コントロールシーケンスの中:2bitに入り込んでいるのです。
このことは通常は判らないことですから1MビットI2C-EEPROMに限らずデータシートの確認は必須です。
下記はI2C-EEPROMからデータ受信するためのサブルーチンです。手順1〜9が備わっているでしょう?
ポートやSCL端子、SDA端子の番号は任意に変更して下さい。
尚、スタックメモリは3つ使います。
;レジスタ定義------------------------------------------------------------------------------------------------------------- ROM_CHIP EQU h'00**' ;デバイスアドレスの指定で使う ROM_ADD_H EQU h'00**' ;アドレス上位1バイトの指定に使う ROM_ADD_L EQU h'00**' ;アドレス下位1バイトの指定に使う BUFFER EQU h'00**' ;各種状態ビットを格納 DATA_IN EQU h'00**' ;EEPROM受信データ DATA_OUT EQU h'00**' ;EEPROMに送信するデータ BITCOUNT EQU h'00**' ;クロック数のレジスタ「8」 ;定数定義---------------------------------------------------------------------------------------------------------------- SCL EQU 1 ;SCL端子とする端子番号(ポートの指定は下のサブルーチンで指定) SDA EQU 2 ;SDA端子とする端子番号(ポートの指定は下のサブルーチンで指定) DO EQU 0 DI EQU 1 ACK_BIT EQU 2 ;ACK信号の有無 ;------------------------------------------------------------------------------------------------------------------------ MAIN 貴方の処理 MOVWF ROM_CHIP ;デバイスアドレスを指定 貴方の処理 MOVWF ROM_ADD_H ;アドレス上位1バイトを指定 貴方の処理 MOVWF ROM_ADD_L ;アドレス下位1バイトを指定 CALL ROM_READ ;ROM読み出しルーチンへ 貴方の処理 ;I2CEEPROM読み出し------------------------------------------------------------------------------------------------------ ROM_READ CALL SDA_IN ;SDA端子を入力モードにする CALL START_CON ;スタートシーケンスへ CALL ROM_TIM ;タイミング合わせ MOVLW h'00A0' ;コントロールビット+書き込みビット IORWF ROM_CHIP,W ;上記にデバイスアドレスを加える MOVWF DATA_OUT ;DATA_OUTレジスタに移動 CALL BYTE_OUT ;コントロールシーケンス(1バイト)の送出 CALL ROM_TIM ;タイミング合わせ MOVF ROM_ADD_H,W MOVWF DATA_OUT ;アドレス上位をDATA_OUTレジスタに移動 CALL BYTE_OUT ;アドレス上位送信 CALL ROM_TIM ;タイミング合わせ MOVF ROM_ADD_L,W MOVWF DATA_OUT ;アドレス下位をDATA_OUTレジスタに移動 CALL BYTE_OUT ;アドレス下位送信 CALL ROM_TIM ;タイミング合わせ CALL START_CON ;スタートシーケンスへ CALL ROM_TIM ;タイミング合わせ MOVLW h'00A1' ;コントロールビット+読み込みビット IORWF ROM_CHIP,W ;上記にデバイスアドレスを加える MOVWF DATA_OUT ;DATA_OUTレジスタに移動 CALL BYTE_OUT ;コントロールシーケンス(1バイト)の送出 CALL ROM_TIM ;タイミング合わせ SEQ_READ BSF BUFFER,ACK_BIT ;ACKビットを立てて連続読み出しにする CALL BYTE_IN ;1バイトを受信(受信後にACKを送出する) CALL ROM_TIM ;タイミング合わせ(下部のデータ処理ルーチンによっては削除して下さい) データ処理ルーチン 受信データはDATA_INレジスタにある。ここでDATA_INをデータ処理する。 または別のレジスタに蓄えておく。 ここで、必要なデータを受信するまでSEQ_READをループする。 最終読み出しとなればLAST_READへジャンプさせる。 GOTO SEQ_READ ;※ループさせる場合は記述のこと。 LAST_READ BCF BUFFER,ACK_BIT ;ACKビットを送出しないで最終読み出し CALL BYTE_IN ;1バイトを受信(受信後にACKは送出しない) CALL ROM_TIM ;タイミング合わせ ここでもDATA_INレジスタには最終データが入る。 上記のデータ処理ルーチンによってはダミーデータだといえる。 CALL STOP_CON ;ストップシーケンスへ RETURN ;このサブルーチンから抜ける ;I2CEEPROMスタートシーケンス--------------------------------------------------------------------------------------------- START_CON BSF PORTA,SCL ;ポートの指定は任意 CALL ROM_TIM ;タイミング調整 CALL SDA_OUT ;SDA端子を出力モードにする:"H"レベル出力 BCF PORTA,SDA ;ポートの指定は任意 CALL ROM_TIM ;タイミング調整 CALL SDA_IN ;SDA端子を入力モードにする(SDAはプルアップ抵抗により"H"レベル) RETURN ;I2CEEPROMストップシーケンス--------------------------------------------------------------------------------------------- STOP_CON CALL SDA_OUT ;SDA端子を出力モードにする:"H"レベル出力 BCF PORTA,SDA ;ポートの指定は任意 BSF PORTA,SCL ;ポートの指定は任意 CALL ROM_TIM ;タイミング調整 CALL SDA_IN ;SDA端子を入力モードにする(SDAはプルアップ抵抗により"H"レベル) RETURN ;I2CEEPROM1バイト送信--------------------------------------------------------------------------------------------------- BYTE_OUT MOVLW h'0008' ;1バイト分だから8回ループ MOVWF BITCOUNT BYTE_OUT_2 BSF BUFFER,DO ;1ビットづつ渡す BTFSS DATA_OUT,7 BCF BUFFER,DO CALL BIT_OUT RLF DATA_OUT,F DECFSZ BITCOUNT,F GOTO BYTE_OUT_2 CALL BIT_IN ;面倒なので受信すべきACKは完全無視 RETURN ;I2CEEPROM1バイト受信--------------------------------------------------------------------------------------------------- BYTE_IN CLRF DATA_IN MOVLW h'0008' MOVWF BITCOUNT BCF STATUS,C BYTE_IN_2 RLF DATA_IN,F ;1ビットづつ受ける CALL BIT_IN BTFSC BUFFER,DI BSF DATA_IN,0 DECFSZ BITCOUNT,F GOTO BYTE_IN_2 BSF BUFFER,DO BTFSC BUFFER,ACK_BIT ;ACK信号を返すか判断 BCF BUFFER,DO CALL BIT_OUT RETURN ;I2CEEPROM1ビット送信--------------------------------------------------------------------------------------------------- BIT_OUT BCF PORTA,SCL ;ポートの指定は任意 BTFSS BUFFER,DO GOTO BIT_OUT_3 BIT_OUT_2 BSF PORTA,SCL ;ポートの指定は任意 CALL ROM_TIM ;タイミング調整 BCF PORTA,SCL ;ポートの指定は任意 CALL SDA_IN RETURN BIT_OUT_3 CALL SDA_OUT BCF PORTA,SDA ;ポートの指定は任意 GOTO BIT_OUT_2 ;I2CEEPROM1ビット受信--------------------------------------------------------------------------------------------------- BIT_IN BCF PORTA,SCL ;ポートの指定は任意 CALL SDA_IN BSF BUFFER,DI BSF PORTA,SCL ;ポートの指定は任意 CALL ROM_TIM ;タイミング調整 BTFSS PORTA,SDA ;ポートの指定は任意 BCF BUFFER,DI BCF PORTA,SCL ;ポートの指定は任意 RETURN ;I2CEEPROMSDA入力端子設定--------------------------------------------------------------------------------------------- SDA_IN BSF STATUS,RP0 ;BANK1 BSF TRISA,SDA ;SDA端子を入力設定、ポートの指定は任意 BCF STATUS,RP0 ;BANK0 RETURN ;I2CEEPROMSDA出力端子設定--------------------------------------------------------------------------------------------- SDA_OUT BSF STATUS,RP0 ;BANK1 BCF TRISA,SDA ;SDA端子を出力設定、ポートの指定は任意 BCF STATUS,RP0 ;BANK0 BSF PORTA,SDA ;ポートの指定は任意 RETURN ;I2CEEPROMタイミング調整用ディレイ(少なくすると高速だが、配線により読み出し不良となる場合は長めに設定すること)------------------- ROM_TIM GOTO $+1 ;2サイクル GOTO $+1 ;2サイクル GOTO $+1 ;2サイクル GOTO $+1 ;2サイクル GOTO $+1 ;2サイクル GOTO $+1 ;2サイクル GOTO $+1 ;2サイクル GOTO $+1 ;2サイクル GOTO $+1 ;2サイクル GOTO $+1 ;2サイクル ;GOTO $+1 ;2サイクル ;GOTO $+1 ;2サイクル ;GOTO $+1 ;2サイクル ;GOTO $+1 ;2サイクル ;GOTO $+1 ;2サイクル ;GOTO $+1 ;2サイクル ;GOTO $+1 ;2サイクル ;GOTO $+1 ;2サイクル ;GOTO $+1 ;2サイクル ;GOTO $+1 ;2サイクル RETURN
実際にI2C-EEPROMにアクセスしようとする時は、
・デバイスアドレス:ROM_CHIP
・アドレス上位8ビット(1バイト):ROM_ADD_H
・アドレス下位8ビット(1バイト):ROM_ADD_L
の3つのレジスタに代入して、当サブルーチンを実行するだけです。
続けて受信するか、最終受信するかは勝手ですが、I2C-EEPROMによっては正しい手順で終了させないと低消費電力モード(待機状態)に移行してくれません。
扱うデータも少なく、データ処理も高速を望まなければアクセスする都度にデバイスアドレス、アドレス上位、下位を指定してLAST_READを行ない、1バイトづつ丁寧に読み込めばいいでしょう。(ランダム読み出し)
扱うデータが音声や画像など容量が膨大な場合はデバイスアドレスと、開始アドレス上位、下位を指定してSEQ_READをループすることで連続してデータを読めるので高速に処理できます。(シーケンス読み出し)
総評して、I2C-EEPROMは小さくて可愛く、ハードは簡単ですが、制御は少し面倒です。