I2Cマスタのソフトウェアエミュレーション for H8-300h

 概要
H8/3664に搭載されているI2Cモジュールは使い物になりません。
そこで汎用ポートをソフトウェアで叩くことでI2Cマスタの機能を実現する方法を説明します。
*現在では改良版のH8/3694が出ているようですが、筆者はこちらを使用した経験が無いのでコメントを控えます。

 キーワード
H8-300h, H8tiny, I2C, IIC, ソフトウェアエミュレーション

 注記
きっちりとしたライブラリ化はあえてしていません。
動作実績のある1コード片として参考にしていただけたら幸いです。
HOS上で動かすために書いたので一部HOS依存です。
  • UB型はunsigned charのtypedefです。
  • エラーハンドリングは未実装です。
  • h83664f.hはHOS添付のものです。秋月コンパイラに付属のものとは変数名が若干異なります。
著者への質問・つっこみはこちらまで。

 ソールファイル
i2c.h
i2c.c

 コンパイル・動作確認済み環境
ハードウェア:AKI−H8/3664F(QFP) タイニーマイコンキット(秋月電子)
開発環境: windows XP + cygwin + gcc3.3 + hosv4
動作実績:第11回川崎ロボット競技大会 リミットカズラ で使用。

 I2Cとは
I2C busとはInter-Integrated-Circuit busの略で、IC間のデータ転送のために Philips社により提案された規格です。 I2Cでは2本の信号線:SCL(クロック)とSDA(データ)に複数のモジュールがオープンコレクタによって 接続されます。両信号線は抵抗をはさんで電源ラインに接続されます。 バス上には基本的にひとつのマスタと複数のスレーブがつながります。 複数のマスタを許可するマルチマスタモードもありますが本稿では対象外です。 マスタはSCL線を通じて各スレーブにクロックを供給します。 各スレーブは重複しないアドレスを持ちます。I2C規格には7ビットアドレスモードと 10ビットアドレスモードがありますが、後者は本稿の対象外です (趣味用途なら127個(厳密には予約アドレスがあるため112個)もつなげられれば十分でしょう)。 より詳しく知りたい方はMicrochip社のアプリケーションノート 辺りを参考にされると良いでしょう。

 周辺回路
周辺回路
例ではマスタがH83664F、スレーブがPIC16F873となっています。 この組み合わせはリミットカズラで採用して動作が確認されています。 VDDは5Vです。 バスラインのプルアップ抵抗は図では2.2kΩとしてあります。 バス駆動用のTrは一応2SC1815としましたがI/Oポートで駆動できるものならなんでもいいでしょう。 電源電圧やスレーブ数、クロック周波数などが異なる場合は最適な抵抗値が前後するそうです。 そもそも本稿のソフトウェアエミュレーションだとそれほどスピードが出ないので、 あまり神経質になることもないでしょう。

*注意*
ポート割り当ては上の図と異なっていても問題ありません(その場合はi2c.hのマクロを書き変えてください) が、ハードウェアI2Cに使われるP56,P57は他のポートと電気的特性が異なるので使用しないことを お勧めします。筆者が最初使おうとしたときにうまく動作しなかったことを記憶しています (昔のことなので確証はありませんが・・・)。
 使い方

	#include<i2c.h>
	#define SLAVE_ADDR 0x01

	static unsigned char g_i2cbuff[64];
	
	int main(void){
		/* I2C初期化 */
		i2c_init();

		/* スレーブから10バイト受信 */
		i2c_startbit();
		i2c_read(SLAVE_ADDR, g_i2cbuff, 10);
		i2c_stopbit();

		/* スレーブへ5バイト送信 */
		i2c_startbit();
		i2c_write(SLAVE_ADDR, g_i2cbuff, 5);
		i2c_stopbit();
	}
			

 リファレンス
 SCL_IN
SCLの入力ポートを指定します(デフォルトはPB4)。 ここで指定したポートをSCL駆動トランジスタのコレクタにつなぎます。

 SDA_IN
SDAの入力ポートを指定します(デフォルトはPB5)。 ここで指定したポートをSDA駆動トランジスタのコレクタにつなぎます。

 SCL_REL
SCLをロジックHとするマクロ。 デフォルトでは

	#define SCL_REL IO.PDR1.BYTE &= ~0x02
			
となっています。違うポートを使用する場合はポート名とビットマスクを 書き変えてください。ここで指定したポートをSCL駆動トランジスタの ベースにつなぎます。

 SCL_PUL
SCLをロジックLとするマクロ。SCL_RELと合うように設定してください。

 SDA_REL
SDAをロジックHとするマクロ。 デフォルトでは

	#define SDA_REL	IO.PDR1.BYTE &= ~0x04
			
となっています。違うポートを使用する場合はポート名とビットマスクを 書き変えてください。ここで指定したポートをSDA駆動トランジスタの ベースにつなぎます。

 SDA_PUL
SDAをロジックLとするマクロ。SDA_RELと合うように設定してください。

 WAIT
i2c.c内で定義されているマクロです。何回ループを回すかによって クロック周波数が決まります。適宜微調整してください。

 void i2c_init(void)
初期化処理を実行します。といっても使用する汎用ポートの入出力を設定しているだけです。 ハードコーディングしているので使用するポートに応じて書き換えてください。

 void i2c_startbit(void)
スタートビットを発行します。

 void i2c_stopbit(void)
ストップビットを発行します。

 BOOL i2c_send_byte(unsigned char data)
 data  送信するデータ
 戻り値  ACKビット
dataをMSB(最上位ビット)から順にSDAに乗せ、SCLの8クロックにより送信し、9回目のクロックで ACKビットを取り込みます。
内部関数ですので、細かな処理をする必要が無い限りは直接呼び出す必要はありません。
*I2C仕様ではスレーブ側がACKビット送信までSCLを引き下げるclock arbitrationという機能がありますが、 これに対する実装が不完全ですので気をつけてください。

 unsigned char i2c_recv_byte(BOOL ack)
 ack  ACKビット
 戻り値  受信データ
SCLから8クロックを送信し、SDAラインよりMSBから順番に1バイトを取り込みます。9回目のクロックでackで指定された ACKビットを送信します。
内部関数ですので、細かな処理をする必要が無い限りは直接呼び出す必要はありません。

 BOOL i2c_ping(unsigned char addr)
 addr  スレーブアドレス
 戻り値  スレーブが反応したかどうか
addrで指定されたスレーブが生きているかチェックします。 具体的には、
スタートビット発行→(addr + readビット)送信、ACKを取り込む→ストップビット発行
の一連のシーケンスを実行し、ACKが0(スレーブが反応した)ならばtrue、そうでないならばfalseを返します。

 BOOL i2c_write(unsigned char addr, unsigned char* data, size_t nbytes)
 addr  スレーブアドレス
 data  送信データを保持する変数へのポインタ
 nbytes  送信データ長
 戻り値  送信に成功したらtrue。それ以外はfalse。
addrで指定されたスレーブアドレスへdataに格納されたデータをnbytesバイト送信します。 送信途中でNACKが返された場合、送信を中断してfalseが返されます。 i2c_writeの呼び出し前後にi2c_startbitとi2c_stopbitを呼び出してください。

 BOOL i2c_read(unsigned char addr, unsigned char* data, size_t nbytes)
 addr  スレーブアドレス
 data  受信データを格納する変数へのポインタ
 nbytes  受信データ長
 戻り値  受信に成功したらtrue。それ以外はfalse。
addrで指定されたスレーブアドレスかだnbytesバイトのデータを受信し、dataへ格納します。 アドレス送信時にNACKが返された場合、受信を中断してfalseが返されます。 i2c_readの呼び出し前後にi2c_startbitとi2c_stopbitを呼び出してください。