/****************************************************************** * メイン・プログラム:PIC24F_I2C-RTCモジュールテスト * * PIC24Fトレーニング基板使用 * 2.4インチQVGA液晶モジュール(aitendo): UL024TF * I2C-RTCモジュール:RTC8564NB(今回は電池BUP無しの時刻合わせ付き) * * 原典参考先: * @ 後閑氏著書「PIC24F活用ハンドブック」の記事 * A 「初めてのPIC」の記事 * * Condition: * 8MHz Internal RC oscillator, 4x PLL (8MHzx4= 32MHz) * Fcy=32MHz/2=16MHz, Tcy=62.5ns * * CPU: PIC24FJ64GA002 * * 2017.3.1 N.Ishii *******************************************************************/ #include "p24FJ64GA002.h" #include "colorlcd_libdsPICVH.h" #include "skI2C_PIC24F_lib.h" /// コンフィギュレーション ビットの設定 _CONFIG1( JTAGEN_OFF // JTAGの使用:無効 & GCP_OFF // コードプロテクト:しない & GWRP_OFF // 書き込みプロテクト:しない & BKBUG_OFF // バックグラウンドデバッグ:無効 & COE_OFF // クリップオンエミュレーション:無効 & ICS_PGx1 // EMUC/EMUDをPCG1/PGD1と共用 & FWDTEN_OFF // WDT:無効 ) /// Fosc = FRC (Fosc=32MHz) _CONFIG2( IESO_OFF // 2速度スタートアップ:無効 & FNOSC_FRCPLL // 8MHz Internal RC oscillator, 4x PLL-> 8MHzx4=32MHz & FCKSM_CSDCMD // クロック切り替え・クロックモニタ:供に無効 & OSCIOFNC_ON // OSCOピン機能:RA3ポートとする 161118 & IOL1WAY_OFF // RPレジスタプロテクト:プロテクトしない & I2C1SEL_PRI // I2C1のピン選択:主ピンを使用する & POSCMOD_NONE // 主発振器:使用しない ) //------------------------------------------------------------------------------------- // initialize RTC8564 Side Reg Write Data unsigned char rtc_write_data_tbl[] = { 0x20, // [0]:Control1REG: Time Stop 0x00, // [1]:Control2REG: ALM INT.TIMMER INT Desable 0x00, // [2]:SecondsREG: Set 0 Set 0x00, // [3]:MinutesREG: Set 0 Min 0x00, // [4]:HourREG: Set 0 Hour 0x15, // [5]:DayREG 0x02, // [6]:WeekdaysREG: TUE 0x06, // [7]:MonthsREG 0x10, // [8]:YearsREG 0x80, // [9]:Minute_AlamREG: Not Used ALM Interrupt 0x80, // [10]:Hour_AlamREG 0x80, // [11]:Day_AlamREG 0x80, // [12]:Weekday_AlamREG 0x83, // [13]:CLKOUT_frequencyREG: Set 1Hz Output ON 0x00, // [14]:Timer_contorolREG: Not Used Timer Interrupt 0x01 // [15]:Timer_downcount Set Vule }; //#define RTC8564_ADRS 0xA2 // RTC8564のI2Cアドレス #define RTC8564_ADRS 0x51 //----------------------------------------------------------------------------------- /// メッセージデータ定義 char MsgDate[] = "xx/xx/xx"; char MsgTime[] = "xx:xx:xx"; //---------------------------------------------------------------------------------- /// 受信データバッファ unsigned char rec_data[16]; unsigned char year, month, day, week, hour, min, sec; //------------------------------------------------------------------------- /// LEDのポート宣言定義 #define LED LATAbits.LATA3 /// SWのポート宣言定義 #define SW1 PORTBbits.RB0 // 桁送り(項目選択) #define SW2 PORTBbits.RB1 // 数値設定 //------------------------------------------------------------------------ /// プロトタイピング void WriteRTC(char, char); void ReadRTC(char, char); char getNum(char num,char top,char y,char x); char getDigit(char num,char top,char y,char x); //------------------------------------------------------------------------- /// Function Main int main(void) { //// 電池バックアップ無しなので、P_ON後は必ず、時刻合わせから開始 /// 時計初期値 BCDで設定する。(時刻合わせ画面用) year = 0x17; // 2017年 month = 0x02; // 2月 day = 0x05; // 5日 week = 0x00; // 表示には使わない hour = 0x23; // 23時 min = 0x59; // 59分 sec = 0x00; // 0秒 /// CPU Clock Pre Scalere 1:1 CLKDIV = 0; /// I/O設定 AD1PCFG = 0xFFFF; // A/D Digi 選択:全デジタルI/Oに設定 /// Setup PORT In/Out TRISB = 0x001F; // RB0,1:時刻合わせ時の項目選択・数値セットSW入力、他(QVGAポート)は出力 // RB2 is SDA2 (Hi), RB3 is SCL2 (Hi), RB4 INT1入力として使用 // RPINR0= 0x0004; // RB4を INT1入力ピンとして使用 RPINR0bits.INT1R = 4; // INT1をRP4(RB4 pin)に割り当てる 170301 INTCON2bits.INT1EP=0; // 立上りエッジ割込み TRISA = 0x0007; // RA4(LCD_RST/),RA3(LED) is Out, Other is Input(未使用) /// Inittalize LED OFF LED = 1; /// Set Pull Up CNPU1 = 0x0030; // Pull Up Port is RB0(SW2), RB1(SW1) /// Initialize I2C2 I2C2BRG = 0x9E; // 100kHz I2C2CON = 0x8000; // Enable I2C2 /// 液晶表示器の初期化と開始メッセージ表示 lcd_Init(); lcd_Clear(BLACK); lcd_Str(0, 0, "Start!!", CYAN, BLACK); delay_ms(1000); lcd_Clear(BLACK); /// 年月日・曜日/時分設定画面表示 lcd_Str(0, 0, "Time Ajust Mode", CYAN, BLACK); lcd_Str(0, 2, "17/02/05", CYAN, BLACK); lcd_Str(0, 4, "23:59:oo", CYAN, BLACK); /// PushSWでの初期値設定(年月日・曜日) year = getNum( year,99,2,0); // 年の設定 month = getNum(month,19,2,3); // 月の設定 day = getNum( day,39,2,6); // 日の設定 /// PushSWでの初期値設定(時分) hour = getNum(hour,29,4,0); // 時の設定 min = getNum( min,59,4,3); // 分の設定 //// ここまでの設定(SW1で最後の項目の分まで送って、SW2で合わせ(或いはそのままで))、SW1で送ると //// 年月日曜日・時刻レジスタの設定を行い、00秒から時計が動き始める。 lcd_Clear(BLACK); // 設定終了直後の表示をクリア /// 年月日曜日・時刻レジスタの設定 rtc_write_data_tbl[2] = sec; // SecondsREG rtc_write_data_tbl[3] = min; // MinutesREG rtc_write_data_tbl[4] = hour; // HourREG rtc_write_data_tbl[5] = day; // DayREG rtc_write_data_tbl[6] = week; // WeekdaysREG rtc_write_data_tbl[7] = month; // MonthsREG rtc_write_data_tbl[8] = year; // YearsREG delay_ms(1000); // RTC8564内部水晶振動子が、発振するまで待つ WriteRTC(0,15); // Write RTC_Reg 16bytes: adrs = 0 to 15 -> Time Stop /// RTC8564始動 rtc_write_data_tbl[0] = 0x00; // コントロールレジスタ1の、bit5:STOPビット=0にして、時計動作をスタート WriteRTC(0,0); // IFS1bits.INT1IF = 0; IEC1bits.INT1IE = 1; // INT1割込み許可 /// Main Loop while(1) { } } /************************************ * INT1外部割込み処理関数:1秒周期 *************************************/ void __attribute__((__interrupt__, no_auto_psv)) _INT1Interrupt(void) { //Reset INT1 interrupt flag IFS1bits.INT1IF = 0; LED = !LED; ReadRTC(2,8); // Read SecondsREG - YearsREG /// Set to LCD Buffer MsgDate[0] = ((rec_data[8] >> 4) & 0x0F) + 0x30; // 年 MsgDate[1] = (rec_data[8] & 0x0F) + 0x30; MsgDate[3] = ((rec_data[7] & 0x1F) >> 4) + 0x30; // 月:01〜12 ※ 読出し時、b7は、Cビット、b6-5は不定なので、0x1Fでマスクが必要 MsgDate[4] = (rec_data[7] & 0x0F) + 0x30; MsgDate[6] = ((rec_data[5] & 0x3F) >> 4) +0x30; // 日:01〜31 ※ 読出し時、b7-6は不定なので、0x3Fでマスクが必要 MsgDate[7] = (rec_data[5] & 0x0F) + 0x30; MsgTime[0] = ((rec_data[4] & 0x3F) >> 4) + 0x30; // 時:00〜23 ※ 読出し時、b7-6は不定なので、0x3Fでマスクが必要 MsgTime[1] = (rec_data[4] & 0x0F) + 0x30; MsgTime[3] = ((rec_data[3] & 0x7F) >> 4) + 0x30; // 分:00〜59 ※ 読出し時、b7は不定なので、0x7Fでマスクが必要 MsgTime[4] = (rec_data[3] & 0x0F) + 0x30; MsgTime[6] = ((rec_data[2] & 0x7F) >> 4) + 0x30; // 秒:00〜59 ※ 読出し時、b7は、VLビットなので、0x7Fでマスクが必要 MsgTime[7] = (rec_data[2] & 0x0F) + 0x30; /// カレンダ時刻表示 lcd_Str(0, 0, MsgDate, CYAN, BLACK); // 年月日表示 lcd_Str(0, 1, MsgTime, CYAN, BLACK); // 時分秒表示 } //---------------------------------------------------------------------------------------------------------- /********************************************************************* * RTC I2C書込み関数 * * <使い方> * @ 連続した、レジスタアドレスに、データを書込みたい時は、 *   その範囲の、レジスタにデータをセットした後に、その *   開始アドレスから終了アドレスまでを指定して、この関数を *   実行する。 *   但し、全16個のレジスタに初期値(テーブル)を書込む時は、 *   WriteRTC(0,15); *   を実行するだけでよい。 *   その配列名は、rtc_write_data_tblとする。([0]〜[15]) * * A 任意のレジスタに、1バイトのデータを書込みたい時には、 *   前もって、希望のレジスタ番地(配列の要素)に、データをセット *   してから、開始と終了アドレスを同じアドレスにして、この関数 *   を実行する。 ***********************************************************************/ void WriteRTC(char start_adrs, char end_adrs) { unsigned char i; I2C_Start(RTC8564_ADRS,RW_0); // スタートコンディションを発行〜 I2Cアドレス + Wビットを送信〜 ACK応答が、'0' になるまで待つ I2C_Send(start_adrs); // 書込み開始番地送信〜 ACK応答が、'0' になるまで待つ /// 当該RTCレジスタへデータを書き込む。 (アドレスは自動インクリメント) for (i = start_adrs; i != end_adrs + 1; ++i) { I2C_Send(rtc_write_data_tbl[i]); } I2C_Stop() ; // ストップコンディションを発行する delay_us(200); // Delay for 200uS (次のスタートまで、1.3uS以上必要) } /*********************************************************************************** * RTC I2C読込み関数 * * <使い方> * 書込み関数と、同様です。(書込みのところを、読込みに置換えて説明文をみてください) ************************************************************************************/ void ReadRTC(char start_adrs,char end_adrs) { unsigned char i; I2C_Start(RTC8564_ADRS,RW_0); // スタートコンディションを発行〜I2Cアドレス + Wビットを送信〜ACK応答が、'0' になるまで待つ I2C_Send(start_adrs); // 読出し開始番地送信〜 ACK応答が、'0' になるまで待つ I2C_rStart(RTC8564_ADRS,RW_1) ; // リピート・スタートコンディションを発行する〜I2Cアドレス + Rビットを送信〜ACK応答が、'0' になるまで待つ //// 当該RTCレジスタのデータを読出す。 (アドレスは自動インクリメント) for (i = start_adrs; i != end_adrs; ++i) { rec_data[i] = I2C_Receive(ACK); // 最終アドレスの前までは、読出し後、マスタへ、ACK送信 } /// i= 最終アドレスになって抜けて来る rec_data[i] = I2C_Receive(NOACK); // 最終アドレスの読出し後、マスタへ、NACK送信 I2C_Stop() ; // ストップコンディションを発行する delay_us(200); // Delay for 200uS (次のスタートまで、1.3uS以上必要) } //------------------------------------------------------------------------------------------------------------ /****** 2桁の初期値を入力する関数 ****************************** * num 入力される初期数 top 値の最大値 * y 十の位のLCD縦位置 x 十の位のLCD横位置 ****************************************************************/ char getNum(char num,char top,char y,char x){ char tens, ones; lcd_Char(x, y+1, 0x5E, CYAN, BLACK); // 10の位の下に、カーソル"^"表示 top = top/10; tens = num >> 4; ones = num & 0xF; tens=getDigit(tens,top,y,x); // 10位設定 lcd_Char(x, y+1, 0x20, CYAN, BLACK); // 現在のカーソルクリアし lcd_Char(x+1, y+1, 0x5E, CYAN, BLACK); // 1位桁に、カーソル移動 ones=getDigit(ones,9,y,x+1); // 1位設定 lcd_Char(x+1, y+1, 0x20, CYAN, BLACK); // 現在のカーソルクリア return ((tens << 4) + ones) ; // 値計算 } /****** 1桁の初期値を入力する関数 ******************************** * num 入力される初期数 top 値の最大値 * y 十の位のLCD縦位置 x 十の位のLCD横位置 *******************************************************************/ char getDigit(char num,char top,char y,char x){ char OnSW; top++; while(1){ OnSW = 5; // SWがOnでないことを確認する do{ if(SW1 & SW2)OnSW--; else OnSW = 5; // 1mSecおきに5回確認 // →1mS周期でのセンスはチャッタと見なす delay_ms(1); // 1mSec遅延 }while(OnSW); while(SW1 & SW2); // SWのPushを待つ // → 押されてない状態を確認してから初めてSW ON待ちを行っている if(!SW1)return num; // SW1で終了 if(!SW2){ // SW2で数値を+1 num++; num %= top; lcd_Char(x, y, num+0x30, CYAN, BLACK); } } }