AVRマイコンでTWI(I2C)通信
マイコン同士の通信をする為にTWI(I2C)通信を使ってみました。
マスタ側から送られたデータに対して受け取ったスレーブ側を動作させたいと思います。
使用するマイコンはマスタ側もスレーブ側もATMega88です。PC4(SDA)とPC5(SCL)の2線を使用します。
LEDに抵抗を入れていません。ブレッドボードのスペースの都合で取り付けられなかった為です。
回路図
AVRのVCCやGNDの配線は見やすくする為に省略しています。
R1とR2は4.7kΩ程度で良いそうなので、今回は手持ちにあった5.1kΩを使用しました。
マスタ側のPB0にタクトスイッチを付け、押されている時と押されていない時で異なるデータを送信します。
PB1、PB2、PB3のLEDは通信処理時にエラーが発生した時に点灯させます。PB4は通信処理中に点灯させます。
マスター側
#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/sfr_defs.h>
/********************/
/* TWI(I2C)通信関連 */
/********************/
#define TWI_START 0x08
#define MT_SLA_W_ACK 0x18
#define MT_DATA_ACK 0x28
unsigned char s_addr;
void port_init(void);
void twi_init(void);
void MasterTransmit(unsigned char tdata);
void ERROR(unsigned char i);
int main(void)
{
port_init();
twi_init();
PORTB |= 0b00011110;
_delay_ms(1000);
PORTB = 0x01;
s_addr = 0b10000010; // スレーブのアドレス
while (1)
{
PORTB |= (1 << PB4);
_delay_ms(1000);
if (bit_is_clear(PINB, PB0))
MasterTransmit(0x01);
else
MasterTransmit(0x00);
_delay_ms(1000);
PORTB = PORTB & ~(1 << PB4);
_delay_ms(1000);
}
}
void port_init(void)
{
/* ポート設定 */
DDRB = 0b00011110; // PB0にタクトスイッチ、PB1,2,3,4はLED
PORTB = 0x01; // PB0プルアップ
}
void twi_init(void)
{
TWCR = 0x00;
TWBR = 0xFF; // SCL周波数
TWSR = 0x00; // 約2KHz
TWAR = 0x00; // スレーブアドレス
TWCR = 0x04; // PC4,5をSDA,SCLに
}
void MasterTransmit(unsigned char tdata)
{
/* 通信スタート */
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != TWI_START)
ERROR(1);
/* スレーブのアドレス情報 */
TWDR = s_addr;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != MT_SLA_W_ACK)
ERROR(2);
/* データ送信 */
TWDR = tdata;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != MT_DATA_ACK)
ERROR(3);
/* 通信終了 */
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
}
void ERROR(unsigned char i)
{
PORTB |= (1 << i);
_delay_ms(500);
PORTB = PORTB & ~(1 << i);
}
剣菱Pさんの動画や本を参考にしました。のでTWIの詳細は省きます。
PB4のLEDを点灯させ1秒待ち通信処理を行い、処理後にも1秒待ちLEDを消灯します。また1秒待ち処理を繰り返します。
タクトスイッチを押している間は「0x01」というデータがスレーブ側に送られます。押していない場合は「0x00」が送られます。
スレーブ側
#include <avr/io.h>
/********************/
/* TWI(I2C)通信関連 */
/********************/
#define SR_SLA_W_ACK 0x60
#define SR_DATA_ACK 0x80
#define TWI_STOP 0xA0
unsigned char s_addr, rdata; // データ受信用
unsigned char SlaveReceive(void);
void twi_init(void);
void port_init(void);
void ERROR(void);
int main(void)
{
port_init();
twi_init();
s_addr = 0b10000010;
while (1)
{
rdata = SlaveReceive();
switch (rdata)
{
case 0x01:
PORTB = 0x01;
break;
default:
PORTB = 0x00;
break;
}
}
}
void twi_init(void)
{
TWCR = 0x00;
TWBR = 0x00;
TWSR = 0x00;
TWAR = 0x00;
TWCR = 0x04;
}
void port_init(void)
{
/* ポート設定 */
DDRB = 0x01; // PB0にLED
}
void ERROR(void)
{
// 何らかの処理
}
unsigned char SlaveReceive(void)
{
TWAR = s_addr;
TWCR = (1 << TWEA) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
/* アドレス受信 */
if ((TWSR & 0xF8) != SR_SLA_W_ACK)
ERROR();
TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
/* データ受信 */
if ((TWSR & 0xF8) != SR_DATA_ACK)
ERROR();
rdata = TWDR;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
/* ストップコンディション受信 */
if ((TWSR & 0xF8) != TWI_STOP)
ERROR();
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
return rdata;
}
マスタ側から送られたデータによってPB0のLEDを点灯させたりします。
まとめ
最初は上手く行きませんでした。マスター側が1MHzになっておらず通信出来ていませんでした。その後、スレーブ側のLED点灯は出来ているのにマスタ側でエラー関数が働いていました。原因はTWSRというステータスレジスタを書き間違えて別のレジスタ名にしていました。そうこうして何とか実験は成功しました。
制作中の自作NC旋盤のファームウェアに組み込んでスレーブ側からのデータをLCDに表示できるようにしたいです。後はGコードの生成と処理が出来ればソフト側は完成だと思います。少しずつですが進んではいます。
乞うご期待( ´ ▽ ` )ノ