えべす屋工房

色々なモノを作ります

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コードの生成と処理が出来ればソフト側は完成だと思います。少しずつですが進んではいます。

乞うご期待( ´ ▽ ` )ノ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です