PICマイコンによるI2C-EEPROM読み出しサブルーチン:アセンブラ記述

ホームページで紹介している「音声告知クロック」では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は小さくて可愛く、ハードは簡単ですが、制御は少し面倒です。

回路図集インデックスページに戻る