#norelated
#contents
* RTCサンプル [#o86fce4c]

RTCモジュールとI2Cで通信して時計を作る。~
I2Cは前回よくわかってないって書いたけど、前よりはちょっと理解した。~

USARTは、2本の線を使って、1本が送信、1本が受信の役割をするけど、I2Cは同じ二本でも、1本がクロック、1本がデータになっていて、複数の機器を制御できるバス通信らしい。~
複数の機器は、それぞれがマスターにもスレーブにもなれて、それぞれの状態で送信受信ができるらしい。~

AVRと[[秋月のRTCモジュール:http://akizukidenshi.com/catalog/g/gI-00233/]]で、このI2Cという方式でデータのやり取りをすることにより、時刻の設定、読み出し等が可能になると。~

** 回路図 [#s17638b1]

前回書いたように、UART(USART)はRXD(PD0)とTXD(PD1)をクロスに繋いだけど、I2CはSCL(PC5)とSDA(PC4)は、それぞれ同じもの同士を繋ぐ。

SCLとSDAは、RTCモジュールのジャンパをショートすれば、内部でプルアップ抵抗が有効になるらしいけど、なんとなくそのままにしておきたかったので、自前で内部に持っているのと同じ2.2kΩの抵抗をつけてあります。~
プルアップ抵抗値の決め方とかも、理由があるんだろうけど、まだよくわかってません。~

#ref(RtcTest.png,left,nowrap,RTCサンプル)

** ソースコード [#lfaf1c0f]

//まず、メインで参照する各種ライブラリ。~
//&ref(util.h,left,nowrap,ユーティリティヘッダ);&ref(util.c,left,nowrap,ユーティリティソース);&ref(LcdLib.h,left,nowrap,LCDライブラリヘッダ);&ref(LcdLib.c,left,nowrap,LCDライブラリソース);~
今回、RTC-8564NBモジュールを使った時計表示を行うために、LCDライブラリとUSARTライブラリに、多少の手を加えてあります。~
LCDライブラリは、カーソル表示とカーソルブリンクのON/OFFを、初期化時に設定できるように。~
USARTライブラリは、関数ポインタを登録することで、受信完了時、送信完了時の割り込み処理ができるように。~

とりあえず、読み出しだけはできるようになったデバッグコード。~
&ref(LcdLib.h,left,nowrap,LCDライブラリヘッダ);&ref(LcdLib.c,left,nowrap,LCDライブラリソース);&ref(UsartLib.h,left,nowrap,USARTライブラリヘッダ);&ref(UsartLib.c,left,nowrap,USARTライブラリソース);&ref(util.h,left,nowrap,ユーティリティヘッダ);&ref(util.c,left,nowrap,ユーティリティソース);~

今回は長くなるので、分割しながら載せていきます。~
プログラムサイズは、sprintf使用で4.8kbくらいです。~

*** 定義部分ソースコード [#b78da7d4]

 #include <stdio.h>
 #include <avr/interrupt.h>
 #include "LcdLib.h"
 #include "UsartLib.h"
 #include "util.h"
 
 #define RTC_ADDR_WRITE 0xA2
 #define RTC_ADDR_READ  0xA3
 
 #define I2C_BPS	2	// ボーレート設定値
 
 // TWIモード列挙型定義
 typedef enum _tagTWIMode{
 	TWI_MODE_SEND,		// 送信モード
 	TWI_MODE_RECEIVE,	// 受信モード
 } TWIMode;
 typedef enum _tagTWIACKMode{
 	TWI_ACKMODE_ACK,	// ACK送信モード
 	TWI_ACKMODE_NACK,	// NACK送信モード
 } TWIACKMode;
 
 // USART受信バッファ
 #define USART_BUFF_SIZE 16
 char	usart_buff[USART_BUFF_SIZE];
 uint8_t	usart_buff_cnt;

*** I2C(TWI)部分ソースコード [#e1945366]

 void I2c_init( void )
 {
 	TWSR &= ~0x03;		// BPSの前置分周は等倍にする
 	TWBR = I2C_BPS;		// CPUクロックが1MHzなのに対して、50KHzで通信する
 	TWCR = 0b00000100;	// ピンをTWI(SCL/SDA)として使用する
 }
 
 int8_t I2c_start( void )
 {
 	TWCR = 0b10100100;	// スタート設定
 	while( !( TWCR & 0b10000000 )){
 		;	// 送信開始OKになるまで待つ
 	}
 
 	if( 0x08 == ( TWSR & 0xF8 )){
 		return 0;
 	} else if( 0x10 == ( TWSR & 0xF8 )){
 		return 1;
 	}
 	return -1;
 }
 
 int8_t I2c_setaddr( uint8_t addr, TWIMode mode )
 {
 	TWDR = addr;
 	TWCR = 0b10000100;	// 送信設定
 	while( !( TWCR & 0b10000000 )){
 		;	// 相手の応答まで待つ
 	}
 	if( TWI_MODE_SEND == mode ){
 		if( 0x18 == ( TWSR & 0xF8 )){
 			return 0;
 		} else if( 0x20 == ( TWSR & 0xF8 )){
 			return 1;
 		}
 	} else {
 		if( 0x40 == ( TWSR & 0xF8 )){
 			return 0;
 		} else if( 0x48 == ( TWSR & 0xF8 )){
 			return 1;
 		}
 	}
 	return -1;
 }
 
 int8_t I2c_snddata( uint8_t data )
 {
 	TWDR = data;
 	TWCR = 0b10000100;	// 送信設定
 	while( !( TWCR & 0b10000000 )){
 		;	// 相手の応答まで待つ
 	}
 	if( 0x28 == ( TWSR & 0xF8 )){
 		return 0;
 	} else if( 0x30 == ( TWSR & 0xF8 )){
 		return 1;
 	}
 	return -1;
 }
 
 int8_t I2c_rcvdata( uint8_t *data, TWIACKMode mode )
 {
 	if( TWI_ACKMODE_ACK == mode ){
 		TWCR = 0b11000100;
 	} else {
 		TWCR = 0b10000100;
 	}
 	while( !( TWCR & 0b10000000 )){
 		;	// データを受信するまで待つ
 	}
 	if( 0x50 == ( TWSR & 0xF8 )){
 		*data = TWDR;
 		return 0;
 	} else if( 0x58 == ( TWSR & 0xF8 )){
 		*data = TWDR;
 		return 1;
 	}
 	return -1;
 }
 
 void I2c_stop( void )
 {
 	TWCR = 0b10010100;	// ストップ設定
 }
 
 int main( void )

*** RTC部分ソースコード [#u40a33a4]

 uint8_t Rtc_setdata( uint8_t start, uint8_t *data, uint8_t num )
 {
 	uint8_t data[16];	// 受信データ格納用
 	uint8_t	i	= 0;	// ループ変数
 	char str[5] = "  ";	// 16進コード格納用
 
 	Lcd_init();						// LCD初期化
 	Lcd_setstr( "Receive Data:" );	// 初期表示
 	if( 16 < start + num || NULL == data ) return -1;
 
 	Usart_init();	// シリアル通信初期化
 	I2c_init();		// I2C通信初期化
 
 	while( 1 ){
 		// 書き込みアドレス設定
 		if( 0 > I2c_start() ){
 			Usart_sndstr( "I2c_start_err_1" );
 			continue;
 		}
 		if( 0 != I2c_setaddr( RTC_ADDR_WRITE, TWI_MODE_SEND )){
 			Usart_sndstr( "I2c_setaddr(send)_err" );
 			continue;
 		}
 		if( 0 != I2c_snddata( 0 )){
 			Usart_sndstr( "I2c_snddata_err" );
 		if( 0 != I2c_snddata( start )){
 			continue;
 		}
 		// データ書き込み
 		for( i = 0; i < num; i++ ){
 			if( 0 > I2c_snddata( data[i] )) break;
 		}
 		I2c_stop();
 		break;
 	}
 	return 0;
 }
 
 uint8_t Rtc_getdata( uint8_t start, uint8_t *data, uint8_t num )
 {
 	uint8_t	i	= 0;	// ループ変数
 
 	if( 16 < start + num || NULL == data ) return -1;
 
 	while( 1 ){
 		// 読み出しアドレス設定
 		if( 0 > I2c_start() ){
 			Usart_sndstr( "I2c_start_err_2" );
 			continue;
 		}
 		if( 0 != I2c_setaddr( RTC_ADDR_WRITE, TWI_MODE_SEND )){
 			continue;
 		}
 		if( 0 != I2c_snddata( start )){
 			continue;
 		}
 		// データ読み出し
 		if( 0 > I2c_start() ){
 			continue;
 		}
 		if( 0 != I2c_setaddr( RTC_ADDR_READ, TWI_MODE_RECEIVE )){
 			Usart_sndstr( "I2c_setaddr(receive)_err" );
 			continue;
 		}
 
 		for( i = 0; i < 15; i++ ){
 		for( i = 0; i < num - 1; i++ ){
 			if( 0 > I2c_rcvdata( &data[i], TWI_ACKMODE_ACK )) break;
 		}
 		if( 15 != i ){
 		if( num - 1 != i ){
 			continue;
 		}
 		if( 0 > I2c_rcvdata( &data[i], TWI_ACKMODE_NACK )){
 			continue;
 		}
 		I2c_stop();
 		break;
 	}
 	return 0;
 }
 
 		Lcd_setpos( 0, 0 );
 void Rtc_allread( uint8_t *data )
 {
 	Rtc_getdata( 0, data, 16 );
 }
 
 void Rtc_allwrite( uint8_t *data )
 {
 	Rtc_setdata( 0, data, 16 );
 }
 
 void Rtc_init( uint8_t *data )
 {
 	Rtc_allread( data );	// 全データ読み込み
 
 	// 初期データ設定
 	data[0]		= 0;
 	data[1]		= 0;
 	data[2]		= data[2] & ~0x80;
 	data[3]		= data[3] & ~0x80;
 	data[4]		= data[4] & ~0xC0;
 	data[5]		= data[5] & ~0xC0;
 	data[6]		= data[6] & ~0xF8;
 	data[7]		= data[7] & ~0xE0;
 //	data[8]		= data[8];
 	data[9]		= data[9] & 0x80;
 	data[10]	= ( data[10] & ~0xC0 ) | 0x80;
 	data[11]	= ( data[11] & ~0xC0 ) | 0x80;
 	data[12]	= ( data[12] & ~0xF8 ) | 0x80;
 	data[13]	= 0;
 	data[14]	= 0;
 	data[15]	= 0;
 
 	Rtc_allwrite( data );	// 全データ書き込み
 }
 
 uint8_t Rtc_settime( char* time )
 {
 	uint8_t		i		= 0;	// ループ変数
 	uint8_t		data[7];		// 設定データ
 	uint16_t	year	= 0;	// 年(数字格納用)
 	uint16_t	month	= 0;	// 月(数字格納用)
 	uint16_t	day		= 0;	// 日(数字格納用)
 	uint8_t		uitmp	= 0;	// 数字格納用
 
 	// 入力チェック
 	for( i = 0; i < 14; i++ ){
 		if( '0' > time[i] || '9' < time[i] ){
 			return -1;	// 数字以外のデータが混ざっているので失敗
 		}
 	}
 
 	// 現在データ取得
 	if( 0 > Rtc_getdata( 2, data, 7 )){
 		return -1;
 	}
 
 	// 年のチェック
 	year = ( time[0] - '0' ) * 1000 + ( time[1] - '0' ) * 100 + ( time[2] - '0' ) * 10 + ( time[3] - '0' );
 	data[6]	= (( time[2] - '0' ) << 4 ) | ( time[3] - '0' );
 
 	// 月のチェック
 	month = ( time[4] - '0' ) * 10 + ( time[5] - '0' );
 	if( 12 < month ){
 		return -1;
 	}
 	data[5]	&= ~0x7f;
 	data[5]	|= (( time[4] - '0' ) << 4 ) | ( time[5] - '0' );
 
 	// 日のチェック
 	day = ( time[6] - '0' ) * 10 + ( time[7] - '0' );
 	if( 31 < day ){
 		return -1;
 	}
 	data[3]	= (( time[6] - '0' ) << 4 ) | ( time[7] - '0' );
 
 	// 曜日の計算(ツェラーの公式)
 	data[4]	= ( year + ( year >> 2 ) - year / 100 + year / 400 + ( 13 * month + 8 ) / 5 + day ) % 7;
 
 	// 時のチェック
 	uitmp = ( time[8] - '0' ) * 10 + ( time[9] - '0' );
 	if( 24 < uitmp ){
 		return -1;
 	}
 	data[2]	= (( time[8] - '0' ) << 4 ) | ( time[9] - '0' );
 
 	// 分のチェック
 	uitmp = ( time[10] - '0' ) * 10 + ( time[11] - '0' );
 	if( 59 < uitmp ){
 		return -1;
 	}
 	data[1]	= (( time[10] - '0' ) << 4 ) | ( time[11] - '0' );
 
 	// 秒のチェック
 	uitmp = ( time[12] - '0' ) * 10 + ( time[13] - '0' );
 	if( 59 < uitmp ){
 		return -1;
 	}
 	data[0]	&= ~0x7f;
 	data[0]	|= (( time[12] - '0' ) << 4 ) | ( time[13] - '0' );
 
 	// 現在時刻設定
 	if( 0 > Rtc_setdata( 2, data, 7 )){
 		return -1;
 	}
 
 	return 0;
 }

*** main動作部分ソースコード [#m38fad0d]

 void usart_buff_init( void )
 {
 	uint8_t	i	= 0;	// ループ変数
 
 	usart_buff_cnt = 0;
 	for( i = 0; i < USART_BUFF_SIZE; i++ ){
 		usart_buff[i] = 0;
 	}
 }
 
 void usart_rcv_callback( char data )
 {
 	if( 0x2e == data ){
 		// 入力を間違った時のリセット「.」
 		usart_buff_init();
 		Usart_sndstr( usart_buff );
 		return;
 	}
 	if( 0x0d == data ){
 		// ターミネータの処理
 		Rtc_settime( usart_buff );
 		Usart_sndstr( usart_buff );
 		usart_buff_init();
 		return;
 	}
 
 	usart_buff[usart_buff_cnt] = data;
 	usart_buff_cnt++;
 	if( USART_BUFF_SIZE <= usart_buff_cnt ){
 		// オーバーフロー時はターミネータが来るまで最終バッファを書き換えるだけにする
 		usart_buff_cnt = USART_BUFF_SIZE - 1;
 	}
 	Usart_sndstr( usart_buff );
 }
 
 void print_time( uint8_t *data )
 {
 	uint8_t sec = data[2];
 	uint8_t min = data[3];
 	uint8_t hour = data[4];
 	uint8_t day = data[5];
 	uint8_t week = data[6];
 	uint8_t month = data[7];
 	uint8_t year = data[8];
 	char date[17];
 	char weekstr[7][4] = {
 		"Sun",
 		"Mon",
 		"Tue",
 		"Wed",
 		"Thr",
 		"Fri",
 		"Sat",
 	};
 	sec = (( sec >> 4 ) & 0x07 ) * 10 + ( sec & 0x0F );
 	min = (( min >> 4 ) & 0x07 ) * 10 + ( min & 0x0F );
 	hour = (( hour >> 4 ) & 0x03 ) * 10 + ( hour & 0x0F );
 	day = (( day >> 4 ) & 0x03 ) * 10 + ( day & 0x0F );
 	week = week & 0x07;
 	month = (( month >> 4 ) & 0x01 ) * 10 + ( month & 0x0F );
 	year = (( year >> 4 ) & 0x0F ) * 10 + ( year & 0x0F );
 	sprintf( date, "%02d/%02d/%02d %s", year, month, day, weekstr[week] );
 	Lcd_setpos( 0, 0 );
 	Lcd_setstr( date );
 	sprintf( date, "%02d:%02d:%02d", hour, min, sec );
 	Lcd_setpos( 1, 0 );
 	Lcd_setstr( date );
 }
 
 int main( void )
 {
 	uint8_t data[16];	// 受信データ格納用
 //	uint8_t	i	= 0;	// ループ変数
 //	char str[5] = "  ";	// 16進コード格納用
 
 	usart_buff_init();	// 受信バッファ初期化
 
 	Lcd_init( 0 );	// LCD初期化
 
 	Usart_init( usart_rcv_callback, NULL );	// シリアル通信初期化
 
 	I2c_init();	// I2C通信初期化
 
 	Rtc_init( data );	// RTCモジュール初期化
 
 	sei();	// 割り込み許可
 
 	while( 1 ){
 		Rtc_allread( data );	// 全データ読み込み
 
 		print_time( data );
 /*		Lcd_setpos( 0, 0 );
 		for( i = 0; i < 8; i++ ){
 			sprintf( str, "%02X", data[i] );
 			Lcd_setstr( str );
 		}
 		Lcd_setpos( 1, 0 );
 		for( i = 8; i < 16; i++ ){
 			sprintf( str, "%02X", data[i] );
 			Lcd_setstr( str );
 		}
 
 		{
 			uint8_t sec = data[2];
 			uint8_t min = data[3];
 			uint8_t hour = data[4];
 			uint8_t day = data[5];
 			uint8_t week = data[6];
 			uint8_t month = data[7];
 			uint8_t year = data[8];
 			char date[24] = "                       ";
 			char weekstr[7][4] = {
 				"Sun",
 				"Mon",
 				"Tue",
 				"Wed",
 				"Thr",
 				"Fri",
 				"Sat",
 			};
 			sec = (( sec >> 4 ) & 0x07 ) * 10 + ( sec & 0x0F );
 			min = (( min >> 4 ) & 0x07 ) * 10 + ( min & 0x0F );
 			hour = (( hour >> 4 ) & 0x03 ) * 10 + ( hour & 0x0F );
 			day = (( day >> 4 ) & 0x03 ) * 10 + ( day & 0x0F );
 			week = week & 0x07;
 			month = (( month >> 4 ) & 0x01 ) * 10 + ( month & 0x0F );
 			year = (( year >> 4 ) & 0x0F ) * 10 + ( year & 0x0F );
 			sprintf( date, "20%02d/%02d/%02d %02d:%02d:%02d %s", year, month, day, hour, min, sec,  weekstr[week] );
 			Usart_sndstr( date );
 		}
 		delay_ms( 500 );
 		}*/
 		delay_ms( 100 );
 	}
 
 	return 0;
 }

*** 基本動作 [#v775d1bd]
** 基本動作 [#k26822dd]

まず、電源を入れてPCとUSB通信ができるようにします。~
この辺は、前回の[[USBシリアル通信サンプル]]を参照のこと。~

繋がったら、PCから設定したい日時を、以下のように打ち込んで、Enterを押すと、日時がRTCに設定されます。~

 20100418123456

この例では、2010/04/18の、12:34:56に設定されるわけです。~
1回キーを押す度に、その時点のバッファの内容がエコーバックされるようになっているので、入力内容を確認することができます。~
間違ったキーを入力してしまった場合、「.」(ピリオド)の入力で、バッファの内容がリセットされるので、再度最初から打ち込んでください。~

設定した時刻は、LCDに二段に分かれて表示されます。~
上段は、日付と曜日、下段は時刻。~
曜日は、PCからの入力時に、[[ツェラーの公式:http://www.google.com/search?hl=ja&lr=lang_ja&ie=UTF-8&oe=UTF-8&q=%E3%83%84%E3%82%A7%E3%83%A9%E3%83%BC%E3%81%AE%E5%85%AC%E5%BC%8F]]で算出しています。

PCから入力した西暦の1000の位と100の位は、この曜日計算にしか使用していません。~
RTCモジュールは、西暦の下2桁しか気にしないので、RTC内にも保存できないし、今回のプログラム中でも保存していません。~
なので、表示時にも下2桁しか表示出来ない仕様となります。~


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS