はじめに
業務にてH8マイコンを使用してI2Cデバイスを制御する経験をしましたので、備忘録も兼ねI2Cデバイスの制御方法をまとめたいと思います。
I2Cとは
I2C(アイ・スクエアドー・シー)とは、PHILIPS社で開発されたシリアルバスのことです。比較的低速な周辺機器をメインボードに接続し、デバイス間の情報をやり取りするための規格です。
I2Cでは、抵抗にてプルアップされた2本の信号線のみを使い複数のデバイスを接続できるという利点があります。信号線としては、シリアルデータ(SDA)、シリアルクロック(SCL)からなります。SPI通信やUART通信よりも必要な信号線の数が少なくて済むため、組み込み機器に広く採用されてきました。
#H8マイコンとは
H8マイコンシリーズは、日立製作所(現:ルネサス エレクトロニクス)が開発したマイクロコントローラです。
利用するハードウェア
- H8/36064搭載マイコンボード
- ADX345(3軸加速度センサモジュール)
ADX345から加速度の取得
ADX345からはX、Y、Zの3軸の加速度が取得できます。また、これらを利用した各軸のシングル・ダブルタップを検出することも可能です。
ADX345をI2C通信を行い、ADX345のレジスタからデータを取得するには、上記の手順に則って取得する必要があります。
電源投入とともにセンサの値が取得できるものと、はじめにモジュールの動作モードを設定する必要があるものがあります。ADX345は後者にあたり、いきなりセンサ値を取得することはできません。
詳細は、下記のADX345のデータシートP.23以降のレジスタ定義を参照してください。
http://akizukidenshi.com/download/ds/freescale/ADXL345_jp.pdf
主な手順は以下の通りです。
- データフォーマットを設定
- 測定モードを設定
- 加速度の取得
実装
H8マイコンのI2C初期化
void i2cInit(void) {
unsigned temp;
// I2Cを停止、リセット
// I2Cバスインタフェース2イネーブル
IIC2.ICCR1.BIT.ICE = 0; // 0:機能停止状態 1:転送動作可能状態
IIC2.ICCR2.BIT.BBSY = 0; // 0:停止状態 1:開始状態
IIC2.ICCR2.BIT.SCP = 0; // 0:開始状態 1:初期値
Wait(1);
IIC2.ICCR2.BIT.IICRST = 1;// I2C通信のコントロール部のみリセットを行う(ポート設定・レジスタの初期化は行わない)
temp = IIC2.ICSR.BYTE; // マスターモードウェイトの制御、転送ビット数の選択を実施
IIC2.ICSR.BYTE = 0; // MSBファーストモード、ウェイト無し、BCライトプロテクト0を設定
//初期化
// I2Cの転送レートΦ/128(Φ=14.7456MHz, 115.2kHz)
// およそ100khzに速度を設定する
IIC2.ICCR1.BIT.CKS = 0x0F;
IIC2.ICMR.BIT.MLS = 0; // MSB First
IIC2.ICMR.BIT.WAIT = 0; // WAIT無し
IIC2.ICMR.BIT.BCWP = 0; // ビットカウンタライトプロテクト解除
IIC2.ICMR.BIT.BC = 0; // 9ビット
IIC2.ICMR.BIT.BCWP = 1; // ビットカウンタライトプロテクト開始
IIC2.ICCR1.BIT.ICE = 1; // 動作可能状態
}
I2CのStartBitの送信
void i2cSendStartBit() {
// 開始状態発行、SDAを出力端子に設定、ライトプロテクトオン、SCL:High出力、リセット無し
// BBSY :1
// SCP :0
// SDAO :1
// SDAOP :1
// SCLO :1
// リザーブビット :1
// IICRST:0
// リザーブビット :1
IIC2.ICCR2.BYTE = 0xBD;
}
I2CのStopBitの送信
void i2cSendStopBit() {
unsigned int i = 0;
// 停止条件を発行
IIC2.ICSR.BIT.STOP = 0;
// BBSY : 0, SCP : 0
IIC2.ICCR2.BYTE = 0x3D;
// Wait
while(IIC2.ICSR.BIT.STOP != 1){
i++;
if(i > 0xFFFE){
errcnt++;
break;
} //TimeOut wait
};
}
I2Cのデータ書き込み
BYTE i2cWriteByte(unsigned char data) {
unsigned int i = 0;
// バスが開放されるまで待機
while (IIC2.ICSR.BIT.TDRE != 1){
i++;
if(i > 0xFFFE){
errcnt++;
break;
}
};
// i2Cバス送信データレジスタに、送信したいデータを代入
IIC2.ICDRT = data;
// ICDRTに書き込まれたデータがICDRSに転送完了するまで待機
while (IIC2.ICSR.BIT.TEND != 1){
i++;
if(i > 0xFFFE){
errcnt++;
break;
}
};
// ACKを確認
// データがセンサに辿り着き正常に設定された場合はACKが帰ってくる
return(IIC2.ICIER.BIT.ACKBR); // ACKを返す
}
I2Cのデータ読み込み
BYTE i2cReadByte(unsigned char ack, unsigned char rcvd) {
unsigned int i = 0;
unsigned char rdt;
IIC2.ICIER.BIT.ACKBT = ack; // データ受信後に送信するACKフラグを設定する
IIC2.ICCR1.BIT.RCVD = rcvd; // 最終バイト読み込み時は1
rdt = IIC2.ICDRR; // 受信開始
while (IIC2.ICSR.BIT.RDRF != 1){
i++;
if(i > 0xFFFE){
errcnt++;
break;
}
};
rdt = IIC2.ICDRR;
return(rdt);
}
H8マイコンのマスタースレーブモードの切り替え
// I2C送受信モードの設定
// 0: スレーブ受信
// 1: スレーブ送信
// 2: マスタ受信
// 3: マスタ送信
void i2cSetTransmitMode(unsigned char mode) {
mode <<= 4;
IIC2.ICCR1.BYTE = ((IIC2.ICCR1.BYTE & 0xCF) | mode); // 送受信モード設定
}
これで、I2C通信を行う準備は整いました。では、データシートに乗っ取りADX345から加速度を取得したいと思います。
int main(){
// ADX345のスレーブアドレス
Addr = 0x53
// I2Cの初期化
i2cInit();
// ADX345初期設定
// マスター送信モードに設定
i2cSetTransmitMode(3)
// スタートビットを送信
i2cSendStartBit()
if(i2cWriteByte(Addr) == 1){ // スレーブアドレスを送信
if(errcnt>0){errcnt++;break;}
i2cWriteByte(0x2d); // レジスタアドレス
if(errcnt>0){errcnt++;break;}
i2cWriteByte(0x08); // 設定値:測定モードオン
if(errcnt>0){errcnt++;break;}
I2C_sendStopBit(); // i2c バスを停止条件にする
if(errcnt>0){errcnt++;break;}
errcnt ++;
}
// ADX345から値取得
// マスター送信モードに設定
i2cSetTransmitMode(3)
// スタートビットを送信
i2cSendStartBit()
if(i2cWriteByte(Addr) == 1){ // スレーブアドレスを送信
if(errcnt>0){errcnt++;break;}
i2cWriteByte(0x32); // レジスタアドレス:X軸
if(errcnt>0){errcnt++;break;}
}
// スタートビットを送信:受信用リスタート
i2cSendStartBit();
if(i2cWriteByte(Addr) == 1){ // スレーブアドレスを送信
if(errcnt>0){errcnt++;break;}
}
// マスター受信モードに設定
i2cSetTransmitMode(2);
IIC2.ICSR.BIT.TDRE = 0;
Wait(0.5);
int flg = 0;
int count = 0;
unsigned int array[6];
while (flg == true) {
count += 1;
if (count != 5) {
array[count] = i2cReadByte(0, 0);
} else if (count == 5){
// 最終ByteのみACKを設定する
array[count] = i2cReadByte(1, 1);
count = 0;
// 受信データをビットシフトし、加速度の値へ整形
unsigned int x = (((unsigned int)array[1]) << 8) | array[0];
unsigned int y = (((unsigned int)array[3]) << 8) | array[2];
unsigned int z = (((unsigned int)array[5]) << 8) | array[4];
I2C_sendStopBit(); // i2c バスを停止条件にする。
if(errcnt>0){errcnt++;break;}
IIC2.ICCR1.BIT.RCVD = 0;
}
}
return 0;
}
ADX345のX、Y、Z軸は、それぞれ2つのレジスタに格納され、Xであれば、DATAX0、DATAX1の2つに格納されます。これは、加速度のデータが10bitであるため、10bitのデータのうちの上位2bitをDATAX0に格納し、下位8bitをDATAX1に格納します。
おわりに
ArduinoマイコンやRaspberry PiにてI2CやSPIなどは比較的簡単に利用できる実装になっています。現に私も学生時代に、便利に利用していました。しかし、組み込みマイコンでは、送受信モードの切り替えや、レジスタの読み書きなどもすべて設定する必要がありました。しかしながら、Arduinoなどは、利用者が簡易に利用できる反面、実行速度が組込マイコンに比べ遅いというデメリットがあります。プロトタイピングはArduinoで行い、パフォーマンスを必須とする場合には、組み込みマイコンへ移行するなどのされると良いのかと思います。
組み込みの世界に入ってまだ日が浅いですが、昨今のプログラミング言語のモダンさはあまりなく、データシートとにらめっこしながら一つずつ設定していく世界です。実行パフォーマンスを落とすことなく、モダンなコードを記述できるようなフレームワークを少しずつ作っていきたいと思いました。
シングルタップや、ダブルタップに関しては追って追記したいと思います。