電子ガジェットいろいろ

電子系のガジェットやアプリ開発等の話題を書いてます

Vana'diel Clock 制作3

このエントリーは2006年6月頃『0から独学ではじめる電子工作』に書いたエントリーを再構成してます。当時とは現在と状況が違う事がありますので、所々補足しています。

時計表示

今回からプログラムの説明になります。
C言語自体の説明はしません。
プログラムリストの公開は予定してませんが、反響によっては完成後に考えます。

・I2C
I2Cはデータとクロックの2本の線でデータ通信を行う規格です。
RTCからはI2Cのインターフェイスを使って時間を取得することができます。
今回使用するPIC18F252などいくつかのPICではI2Cインターフェイスの機能があり、
指定したピンを接続することにより、簡単に通信ができるようになっています。

・コンフィグレーションの設定
 PIC自体の動作の設定をします。各項目の意味はデータシートを確認してください。(自分でもよく理解してません^^;)
 今回は以下のような設定をしました。

 FOSC HS
 BORV 2.0V
 BOREN OFF(disabled)
 PWRT ON(enabled)
 WDTEN OFF(disabled)
 LVP OFF(disabled)
 上記以外はデフォルト設定です

・時間の取得
 RTCから時間を取得するときはI2C通信でRTCのレジスタの値を取得します。
 レジスタは8ビット幅で、4ビット毎にBCD形式で格納されています。
 レジスタのアドレスは以下の通りです。
  00 Control1 (時計のオン・オフ等)
  01 Control2 (アラームのオン・オフ等)
  02 Seconds
  03 Minutes
  04 Hours
  05 Days
  06 Weeks (0:日 1:月 2:火・・・)
  07 Months
  08 Years
 他にもアラーム用のレジスタがあるのですが、省略します。

時間を取得するのに、I2C通信を行うのですが、FED-Cのライブラリ関数が上手く動かずに苦戦しました。
FED-Cのシュミレータで動かしても"clock too short"と表示されてしまいます。
そこで、PIC自体にI2C通信の機能があるのですから、そちらを直接使うことにしました。
RTCのデータシートにあった通信手順とネットで見つけた他の人のアセンブラのソースをにらめっこしながら以下の関数を作成しました。
マイクロチップテクノロジー純正のコンパイラでは、このままでは動かない可能性があります。)

const BYTE SDA = 4;
const BYTE SCL = 3;

// I2C初期化
void I2CInit()
{
  TRISC |= (1 << SCL);
  TRISC |= (1 << SDA);
  PIR1 &= ~(1 << PSPIF);
  PIE1 &= ~(1 << PSPIE);
  SSPCON1 |= (1 << SSPEN);    // シリアルポート動作
  SSPCON1 &= ~(1 << CKP);    // シリアルポート動作
  SSPCON1 |= 0x08;        // I2Cマスターモード Fosc/(4*SSPADD+1)
  SSPADD = 4;          // 400kHz=8mHz/(4*(4+1))
  SSPSTAT &= ~(1 << SMP);    // 高速モード(400kHz)
  SSPSTAT &= ~(1 << CKE);    // I2C仕様
  PORTC |= (1 << SCL);
}

// 指定したアドレスからデータを読み込みます。
BYTE RTCRead(BYTE address)
{
  BYTE returnvalue;

  SSPCON2 |= (1 << SEN);  // スタートビット
  while(SSPCON2 & (1 << SEN)) {
  }
  
  SSPBUF = 0xa2;      // コントロールデータ送信
  while(SSPSTAT & (1 << R)) {
  }
  
  SSPBUF = address;    // アドレス送信
  while(SSPSTAT & (1 << R)) {
  }

  SSPCON2 |= (1 << RSEN);  // 繰り返しスタートビット
  while(SSPCON2 & (1 << RSEN)) {
  }

  SSPBUF = 0xa3;      // コントロールデータ送信
  while(SSPSTAT & (1 << R)) {
  }
  
  SSPCON2 |= (1 << RCEN);  // データ受信
  while(SSPCON2 & (1 << RCEN)) {
  }

  returnvalue = SSPBUF;

  SSPCON2 |= (1 << PEN);  // ストップビット
  while(SSPCON2 & (1 << PEN)) {
  }
  
  Wait(10);
  return returnvalue;
}

// 指定したアドレスへデータを書き込みます
void RTCWrite(BYTE address, BYTE data)
{

  SSPCON2 |= (1 << SEN);  // スタートビット
  while(SSPCON2 & (1 << SEN)) {
  }
  
  SSPBUF = 0xa2;      // コントロールデータ送信
  while(SSPSTAT & (1 << R)) {
  }
  
  SSPBUF = address;    // アドレス送信
  while(SSPSTAT & (1 << R)) {
  }

  SSPBUF = data;      // 書込データ送信
  while(SSPSTAT & (1 << R)) {
  }

  SSPCON2 |= (1 << PEN);  // ストップビット
  while(SSPCON2 & (1 << PEN)) {
  }
  
  Wait(10);
  
}

これで前準備が終わりました。
次に時計の表示ですが、RTCの初期化等はデータシート参照でお願いします。
あとどこかで見たのですが、電源投入後はRTCにアクセスするまでに700ms程度のウェイトを入れた方が良いそうです。

FED-CにはLCDを操作する関数があります。これは非常に便利です。
マイクロチップテクノロジー純正のコンパイラにも、同等の関数が用意されてます。)

  LCDPrintAt(0,0); // 表示位置指定
  LCDString("Hello World!"); //表示文字列指定

こんな感じで書けちゃいます。
RTCから取得した時間を"/"をつけたり、":"をつけたりと加工して表示することに成功しました。

時刻合わせですが、この時点ではPORTA6に接続したボタン(小さい方のボタン)を押すと時計設定機能に飛ぶようにしました。(後に約5秒押すと飛ぶように変更しました。)
でループ処理しているので以下の様に書くと良いです。
通常プログラムはwhile(1)でループ処理していますので、ボタンを離すまで待つようにしないとループが回ってくるたびにボタンを押したときの処理をしようとします。

  if (!(PORTC & (1 << 6))) {
    while(!(PORTC & (1 << 6))){
    }
    // ボタンを押したときの処理
  }

日付を設定しても、曜日は自動計算されません。
年月日から計算で求めるか、曜日の入力処理も追加してください。
ちなみに年月から求めるロジックを考えてみました
1・2月は前年の13・14月として計算してください。

  if (month == 1 || month == 2) {
    week = (char)(((year - 1) + (year - 1) / 4 - (year - 1) / 100 +
    year / 400 + (13 * (month + 12) + 8) / 5 + day) % 7);
  } else {
    week = (char)((year + year / 4 - year / 100 + year / 400 + (13 * month + 8) / 
    5 + day) % 7);
  }

曜日は外字を使って漢字表示するようにしました。
外字データはPIC内のEEPROMに格納しました。
EEPROMのデータはReadEEData()とWriteEEData()関数で読み書きができます。

無事に表示されました。ちょっと感動です

最後の行は名前(実名)を表示したので消しています。

ヴァナ・ディール時間表示

・ヴァナディール時間
ヴァナディールの時間は地球の25倍の速さで進みます。
時間の単位は60秒で1分、60分で1時間、24時間で1日、30日で1ヶ月、12ヶ月で1年です。
現在時刻からヴァナ時間に変換する簡単な方法は、基準日を決め、現在までの秒を求め、25倍します。

・計算方法
ネットを探して見つけた2002年1月1日にを基準日にします。
2002年1月1日00時00分00秒はヴァナ時間で886年1月1日00時00分00秒(火曜日)となります。
基準日から現在時間までの秒を求め、25倍した秒をヴァナ時間の基準日に足せば現在のヴァナ時間を求めることができます。
現在時間はグレゴリオ暦計算アルゴリズムを使うと便利です。

unsigned long greDay(ulong y, ulong m, ulong d)
{
  ulong ret;
  
  if(m < 3){
    y--;
    m += 12;
  }
  ret = (365 * y + y / 4 - y / 100 + y / 400 + 306 * (m + 1) / 10 + d - 428);
  return ret;
}

この関数に現在の日付を渡した結果から2002年1月1日を渡した結果を引くと、時準備からの日数が求められます。
その日数と現在の時間を以下の式で計算すると秒が得られます。
((ulong)days * 86400) + ((ulong)hour * 3600) + ((ulong)minutes * 60) + ((ulong)second)
これを25倍してヴァナ時間の秒に変換してヴァナ時間を計算すれば楽なのですが、long型ではオーバーフローする可能性がありますので、
計算時に25倍にすることにしました。
実際に計算してみると、long型の最大桁数になります。まだ余裕があるのですが、いつオーバーフローするかわかりません。
ヴァナの各時間は以下の計算で求められます。
  日数 = (seconds / 3456); // 3456=24*60*60/25 ヴァナ一日は地球3456秒
  年 = (vanadays / 360) + 886;
  月 = ((vanadays / 30) % 12) + 1;
  日 = (vanadays % 30) + 1;
  時 = (((seconds % 3456) * 25) / 3600) % 24;
  分 = (((seconds % 3456) * 25) / 60) % 60;
  秒 = ((seconds % 3456) * 25) % 60;
  曜日 = (char)(vanadays % 8);
  月齢 = (char)((vanadays / 7) % 12);

曜日
  0:火
  1:土
  2:水
  3:風
  4:氷
  5:雷
  6:光
  7:闇

月齢
  0:新月
  1:三日月
  2:七日月
  3:上弦の月
  4:十日夜
  5:十三夜
  6:満月
  7:十六夜
  8:居待月
  9:下弦の月
  10:二十日余
  11:二十六夜

この結果を整形して表示させましょう。
曜日でまた外字を使おうとしたのですが、5×7ドットではどうやってもかけませんでした;;