RTCモジュールとI2Cで通信して時計を作る。
I2Cは前回よくわかってないって書いたけど、前よりはちょっと理解した。
USARTは、2本の線を使って、1本が送信、1本が受信の役割をするけど、I2Cは同じ二本でも、1本がクロック、1本がデータになっていて、複数の機器を制御できるバス通信らしい。
複数の機器は、それぞれがマスターにもスレーブにもなれて、それぞれの状態で送信受信ができるらしい。
AVRと秋月のRTCモジュールで、このI2Cという方式でデータのやり取りをすることにより、時刻の設定、読み出し等が可能になると。
前回書いたように、UART(USART)はRXD(PD0)とTXD(PD1)をクロスに繋いだけど、I2CはSCL(PC5)とSDA(PC4)は、それぞれ同じもの同士を繋ぐ。
SCLとSDAは、RTCモジュールのジャンパをショートすれば、内部でプルアップ抵抗が有効になるらしいけど、なんとなくそのままにしておきたかったので、自前で内部に持っているのと同じ2.2kΩの抵抗をつけてあります。
プルアップ抵抗値の決め方とかも、理由があるんだろうけど、まだよくわかってません。
とりあえず、読み出しだけはできるようになったデバッグコード。
#include <stdio.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; 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 ) { uint8_t data[16]; // 受信データ格納用 uint8_t i = 0; // ループ変数 char str[5] = " "; // 16進コード格納用 Lcd_init(); // LCD初期化 Lcd_setstr( "Receive Data:" ); // 初期表示 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" ); continue; } if( 0 > I2c_start() ){ Usart_sndstr( "I2c_start_err_2" ); continue; } if( 0 != I2c_setaddr( RTC_ADDR_READ, TWI_MODE_RECEIVE )){ Usart_sndstr( "I2c_setaddr(receive)_err" ); continue; } for( i = 0; i < 15; i++ ){ if( 0 > I2c_rcvdata( &data[i], TWI_ACKMODE_ACK )) break; } if( 15 != i ){ continue; } if( 0 > I2c_rcvdata( &data[i], TWI_ACKMODE_NACK )){ continue; } I2c_stop(); 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 ); } return 0; }