avr/avrlib/I2Cを使う(スレーブ編)
2021-05-02
FrontPage
2008/10/21 からのアクセス回数
I2Cマスターが正常に動作したので、今度はI2Cスレーブの動作確認をします。
マスターのところで、つまずいてHCS08マイコンでIC2マスターを組み立てたので
これを利用してI2C通信のテストをすることにしました。(災い転じて福となす)
ATmega88とHCS08の接続は、以下の通りです。
スレーブの処理は、TWIによる割り込み処理で行われ、AVRlibでは割り込み時に呼ばれる
コールバックルーチンを登録します。
i2cSetSlaveReceiveHandlerでスレーブに送られたデータを読み込むコールバックを登録
i2cSetSlaveTransmitHandlerでマスターに送るデータをセットするコールバックを登録
します。
当然のことですが、やはり間違えてしました。割り込み処理は、できるだけ短時間で割り込み
元にリターンする必要があるので、UARTを使ったデバッグプリントが使えません。
そこで、グローバル変数cmdにデバッグプリントの命令をセットし、mainの無限ループで
データのチェックプリントを出力することにしました。
I2Cスレーブの処理は、
I2Cの初期化
スレーブのアドレスをセット
I2C通信の受信と送信時のコールバック関数を登録
だけです。
受信コールバック関数では、受信データをローカルバッファにコピー
送信コールバック関数では、LM73の温度データと同じ2バイトのデータを送信
することにします。
プログラムソースは以下の通りです。
char cmd=0;
u08 localBuffer[20];
u08 localBufferLength;
void i2cSlaveReceiveService(u08 receiveDataLength, u08* receiveData)
{
int i;
cmd = 'r';
// copy the received data to a local buffer
for(i=0; i<receiveDataLength; i++)
{
localBuffer[i] = *receiveData++;
}
localBufferLength = receiveDataLength;
}
u08 i2cSlaveTransmitService(u08 transmitDataLengthMax, u08* transmitData)
{
cmd = 't';
transmitData[0] = 0x0D;
transmitData[1] = 0xE0;
localBufferLength = 2;
return localBufferLength;
}
int main(void)
{
int i;
int c;
uartInit();
uartSetBaudRate(9600);
rprintfInit(uartSendByte);
rprintf("Start I2C Salve\n");
i2cInit();
i2cSetLocalDeviceAddr(LOCAL_ADDR, FALSE);
i2cSetSlaveReceiveHandler( i2cSlaveReceiveService );
i2cSetSlaveTransmitHandler( i2cSlaveTransmitService );
rprintf("wait master call\n");
while(1) {
if (cmd == 'r') {
rprintf("recieve data: ");
for(i=0; i<localBufferLength; i++)
{
rprintf("%x", localBuffer[i]);
}
rprintf("\n");
cmd = 0;
}
else if (cmd == 't') {
rprintf("transmit dummy data\n");
cmd = 0;
}
}
return 0;
}
}}
実際に接続して動かしてみると
$jerm -b 9600 -p none -s 1 -d 8 /dev/u.usbserial-A5002yHm
途中省略
Start I2C Salve
wait master call
recieve data: 000400E0
recieve data: 0000
transmit dummy data
}}
と出力される。rprintfの%xは、2バイト分を出力するので、実際には04E0を受信しています。
この記事は、
皆様のご意見、ご希望をお待ちしております。
分かりづらいとのご指摘を受けたので、変更しますので、しばらくお待ち下さい。 -- 竹本 浩
I2C 通信は、(start)+Slave address + offset address + data ...(stop) という並びが Write で、(start) - Slave + offset +(re-start) + (slave+1) + data ... (stop) が読み出しです。 このサンプルコードはどちらにも合わないと思いますが、対処するにはI2C write の data の最初の 1byte を常にメモリすることでよいのでしょうか? -- kanmei
ご指摘の部分は、別の記事「I2Cを使う(マスター編)」のオシロスコープのところで確認しています。この例は、I2Cを割り込みによって処理する時のスレーブの例題で、i2cSetSlaveReceiveHandler( i2cSlaveReceiveService );、i2cSetSlaveTransmitHandler( i2cSlaveTransmitService );で割り込み処理関数をセットしている部分がポイントです。自分のI2Cアドレスはi2cSetLocalDeviceAddr(LOCAL_ADDR, FALSE);でセットしています。ご指摘のとおり、どのように通信が行われているか分かりずらいので時間ができたら、もう少し整理して説明します。 -- 竹本 浩
コメントありがとうございます。マスター編のオシロスコープの画面を見ても Start + (Slave+readbit 1) + data + stop というシーケンスになっています。このオシロスコープの画面の前に start - slave - offset というシーケンスがあり、画面で示されているのは start ではなく re-start ということでしょうか? AVR の i2c.c を見ても対応していないように見えます。switch 文の TW_SR_DATA_ACK のところで offset address をメモリしておかなければならないと思います。今回のサンプルコードでも常に先頭バイトからの出力のみしか対応できないのではないかと思いますが、如何でしょうか? -- kanmei