ORANGE pico + RTC でカレンダー付き時計

[ORANGE pico](http://www.picosoft.co.jp/orange/) と [GROVE - 高精度RTC](https://www.switch-science.com/products/3134) でカレンダー付き時計を作ってみた。

# プログラム

```
10 mptr 16
20 mdata 64,160,160,224,160,160,64,0,64,192,64,64,64,64,224,0,64,160,32,64,64,128,224,0,64,160,32,64,32,160
30 mdata 64,0,32,96,160,160,224,32,32,0,224,128,192,160,32,160,64,0,64,160,128,192,160,160,64,0,224,160,32,64
40 mdata 64,64,64,0,64,160,160,64,160,160,64,0,64,160,160,96,32,160,64,0
50 yearx=10:yeary=45:monthx=75:monthy=45:datex=10:datey=75:dayy=60:datedx=12:datedy=12
60 clockcx=220:clockcy=100:clockor=90:clockir=80:clocksr=70:clockmr=50:clockhr=30:clockmw=6:clockhw=10
70 white=rgb(255,255,255):yearcolor=white:monthcolor=white:dim daycolor(6)=[white,white,white,white,white,white,white]:ccolor=white:scolor=white:mcolor=white:hcolor=white
80 dim xydrawn(8,3):dim xytodraw(8,3):ydrawn=-1:mdrawn=-1:ddrawn=-1:csdrawn=-1:cmdrawn=-1:chdrawn=-1 
90 cls:dpi!=atan(1)*8:dim daychars(6)=[&H93FA,&H8C8E,&H89CE,&H9085,&H96D8,&H8BE0,&H9379]
100 circle clockcx,clockcy,clockor,ccolor
110 for i=0 to 11
120 angle! = dpi!*i/12:cosine! = cos(angle!):sine! = sin(angle!)
130 odx=floor(clockor*sine!+0.5):ody=floor(clockor*cosine!+0.5):idx=floor(clockir*sine!+0.5):idy=floor(clockir*cosine!+0.5)
140 line clockcx+odx,clockcy-ody,clockcx+idx,clockcy-idy,ccolor
150 next
160 for i=0 to 6
170 cpeek daychars(i) & &HFFFF:gput datex+datedx*i,dayy,8,8000,daycolor(i) & &HFFFF
180 next
190 if i2cw(2,&H51,"\x00",0)<>0 then pause 500:goto 190
200 if i2cr(2,&H51,11,1)<>0 then pause 500:goto 190
210 rawyear=mpeek(10):year=2000 + ((rawyear & &HF0) >> 4) * 10 + (rawyear & &H0F)
220 rawmonth=mpeek(9):month=((rawmonth & &H10) >> 4) * 10 + (rawmonth & &H0F)
230 rawdate=mpeek(7):date=((rawdate & &H30) >> 4) * 10 + (rawdate & &H0F)
240 rawhour=mpeek(6):if mpeek(0) & 2 then hour=(((rawhour & &H10) >> 4) * 10 + (rawhour & &H0F) + ((rawhour & &H20) >> 5) * 12)%12 else hour=(((rawhour & &H30) >> 4) * 10 + (rawhour & &H0F))%12
250 rawminute=mpeek(5):minute=((rawminute & &H70) >> 4) * 10 + (rawminute & &H0F)
260 rawsecond=mpeek(4):second=((rawsecond & &H70) >> 4) * 10 + (rawsecond & &H0F)
270 if year==ydrawn && month==mdrawn && date==ddrawn && hour==chdrawn && minute==cmdrawn && second==csdrawn then goto 190
280 clt
290 if year==ydrawn && month==mdrawn && date==ddrawn then goto 530
300 zelleryear=year:zellermonth=month:if month<=2 then zelleryear=zelleryear-1:zellermonth=zellermonth+12
310 zellerc=zelleryear/100:zellery=zelleryear%100:zellergamma=5*zellerc+zellerc/4
320 day=(1+26*(zellermonth+1)/10+zellery+zellery/4+zellergamma+6)%7
330 if month==2 then finalday=28+(year%400==0 || (year%4==0 && year%100<>0)) else finalday=30+((month%2)^(month>=8))
340 for i=0 to 7
350 line yearx,yeary+i,yearx+32,yeary+i,0:line monthx,monthy+i,monthx+16,monthy+i,0
360 next
370 for i=-2 to datedy*5+8
380 line datex-2,datey+i,datex+datedx*7+8,datey+i,0
390 next
400 gprint yearx,yeary,format$("%4d",year),yearcolor:gprint monthx,monthy,format$("%2d",month),monthcolor
410 currenty=datey
420 for i=1 to finalday
430 currentx=datex+datedx*day:currentc=daycolor(day) & &HFFFF
440 if i>=10 then gput currentx,currenty,8,16+8*(i/10),currentc
450 gput currentx+4,currenty,8,16+8*(i%10),currentc
460 if i<>date then goto 490
470 line currentx-2,currenty-2,currentx+8,currenty-2,currentc:line currentx-2,currenty-2,currentx-2,currenty+8,currentc
480 line currentx+8,currenty+8,currentx+8,currenty-2,currentc:line currentx+8,currenty+8,currentx-2,currenty+8,currentc
490 day=day+1
500 if day>=7 then day=0:currenty=currenty+datedy
510 next
520 ydrawn=year:mdrawn=month:ddrawn=date
530 for i=0 to 8
540 line xydrawn(i,0),xydrawn(i,1),xydrawn(i,2),xydrawn(i,3),0
550 next
560 sangle! = dpi!*second/60:mangle! = dpi!*(minute+second/60.0)/60:hangle! = dpi!*(hour+minute/60.0+second/3600.0)/12
570 ssin! = sin(sangle!):scos! = cos(sangle!):msin! = sin(mangle!):mcos! = cos(mangle!):hsin! = sin(hangle!):hcos! = cos(hangle!)
580 sine! = hsin!:cosine! = hcos!:dx! = -clockhw/2.0:dy! = clockhr:gosub 910
590 xytodraw(0,0)=ox:xytodraw(0,1)=oy:xytodraw(1,0)=ox:xytodraw(1,1)=oy
600 dx! = clockhw/2.0:gosub 910
610 xytodraw(0,2)=ox:xytodraw(0,3)=oy:xytodraw(3,2)=ox:xytodraw(3,3)=oy
620 dy! = 0:gosub 910
630 xytodraw(2,0)=ox:xytodraw(2,1)=oy:xytodraw(3,0)=ox:xytodraw(3,1)=oy
640 dx! = -clockhw/2.0:gosub 910
650 xytodraw(1,2)=ox:xytodraw(1,3)=oy:xytodraw(2,2)=ox:xytodraw(2,3)=oy
660 sine! = msin!:cosine! = mcos!:dx! = -clockmw/2.0:dy! = clockmr:gosub 910
670 xytodraw(4,0)=ox:xytodraw(4,1)=oy:xytodraw(5,0)=ox:xytodraw(5,1)=oy
680 dx! = clockmw/2.0:gosub 910
690 xytodraw(4,2)=ox:xytodraw(4,3)=oy:xytodraw(7,2)=ox:xytodraw(7,3)=oy
700 dy! = 0:gosub 910
710 xytodraw(6,0)=ox:xytodraw(6,1)=oy:xytodraw(7,0)=ox:xytodraw(7,1)=oy
720 dx! = -clockmw/2.0:gosub 910
730 xytodraw(5,2)=ox:xytodraw(5,3)=oy:xytodraw(6,2)=ox:xytodraw(6,3)=oy
740 sine! = ssin!:cosine! = scos!:dx! = 0:dy! = clocksr:gosub 910
750 xytodraw(8,0)=clockcx:xytodraw(8,1)=clockcy:xytodraw(8,2)=ox:xytodraw(8,3)=oy
760 for i=0 to 3
770 line xytodraw(i,0),xytodraw(i,1),xytodraw(i,2),xytodraw(i,3),hcolor
780 next
790 for i=4 to 7
800 line xytodraw(i,0),xytodraw(i,1),xytodraw(i,2),xytodraw(i,3),mcolor
810 next
820 line xytodraw(8,0),xytodraw(8,1),xytodraw(8,2),xytodraw(8,3),scolor
830 for i=0 to 8
840 for j=0 to 3
850 xydrawn(i,j)=xytodraw(i,j)
860 next
870 next
880 t=tick():if t<55 then pause 1000*(55-t)/55
890 chdrawn=hour:cmdrawn=minute:csdrawn=second
900 goto 190
910 ox=clockcx+floor(dx!*cosine!+dy!*sine!+0.5):oy=clockcy+floor(dx!*sine!-dy!*cosine!+0.5):return
```

このプログラムは、[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.ja) でライセンスする。
このプログラム (改造したものを含む) を公開の場で利用する際は、出典を示していただけると嬉しい。
これは、Qiitaの利用規約に基づくプログラムの利用を禁止するものではない。


# 実行結果例

![カレンダー付き時計](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/82272c52-fa2f-9871-5608-4555bf55b7e4.png)

# 解説

## 10～40行目：フォント定義

カレンダーで使用する[美咲フォント](https://littlelimit.net/misaki.htm) (美咲ゴシック第2) の半角数字のフォントを用意している。
メモリー配列の先頭部分はRTCとのI2C通信で使用するので、少し後からデータを格納している。

## 50～70行目：パラメータ定義

描画に使用する座標や色を定義している。
パラメータの意味は以下の通りである。

|パラメータ|意味|
|---|---|
|yearx, yeary|カレンダーの「年」を描画する左上の座標|
|month, monthy|カレンダーの「月」を描画する左上の座標|
|datex, datey|カレンダーの「日」を描画する左上の座標|
|dayy|カレンダーの「曜日」を描画する左上のy座標 (x座標はdatex)|
|datedx, datedy|カレンダーの「日」1個あたりの横と縦の幅|
|clockcx, clockcy|時計の中心の座標|
|clockor|時計の外側の円の半径|
|clockir|時計の目盛の内側の半径|
|clocksr|時計の秒針の長さ|
|clockmr, clockmw|時計の分針の長さと幅|
|clockhr,clockhw|時計の時針の長さと幅|
|yearcolor|カレンダーの「年」の描画色|
|monthcolor|カレンダーの「月」の描画色|
|daycolor|カレンダーの各曜日とその「日」の描画色|
|ccolor|時計の外枠と目盛の描画色|
|scolor|時計の秒針の描画色|
|mcolor|時計の分針の描画色|
|hcolor|時計の時針の描画色|

## 80行目：描画状態を格納する変数の定義

描画を行った日時、および時計の針の描画の座標を格納する変数を用意する。

## 90～180行目：固定のUIの描画

画面の日時によって変化しない部分を描画する。

* 90行目：画面をクリアし、円周率の2倍 `dpi!` および各曜日の文字コードを用意する。
* 100～150行目：時計の外枠を描画する。
* 160～180行目：カレンダーの曜日を描画する。

## 190～260行目：RTCからの日時の取得

I2C2バスに接続された高精度RTCから時刻データを取得する。

* 190～200行目：RTCと通信を行い、レジスタのデータを取得する。
* 210～260行目：取得したデータをデコードし、時刻情報を取り出す。

## 270～910行目：日時の描画

### 270～280行目：描画の準備

* 270行目：時刻が変わっていない場合、描画せず時刻のポーリングに戻る。
* 280行目：無駄な通信を減らすための待機のため、`tick()` の時刻を初期化する。

### 290～520行目：カレンダーの描画

* 290行目：日付が変わっていなければ、描画をスキップする。
* 300～320行目：[ツェラーの公式](https://ja.wikipedia.org/wiki/%E3%83%84%E3%82%A7%E3%83%A9%E3%83%BC%E3%81%AE%E5%85%AC%E5%BC%8F)を用いて、現在の月の最初の日の曜日を求める。
* 330行目：現在の月が何日まであるかを求める。
* 340～390行目：現在描画されているカレンダーを消去する。
* 400行目：年と月を描画する。
* 410～510行目：カレンダーを描画する。
  * 430～450行目：日を描画する。
  * 460～480行目：現在の日を四角で囲む。
  * 490～500行目：描画位置を進める。
* 520行目：描画した日付を記録する。

### 530～910行目：時計の描画

* 530～550行目：前回描画した針を消去する。
* 560行目：それぞれの針の角度を求める。
* 570行目：それぞれの針の角度に対応する正弦と余弦を求める。
* 580～650行目：時針の描画位置を計算する。
* 660～730行目：分針の描画位置を計算する。
* 740～750行目：秒針の描画位置を計算する。
* 760～780行目：時針を描画する。
* 790～810行目：分針を描画する。
* 820行目：秒針を描画する。
* 830～870行目：描画した座標を記録する。
* 880行目：無駄な通信を減らすため、1秒弱待機する。
* 890行目：描画した時刻を記録する。
* 900行目：時刻のポーリングに戻る。
* 910行目：針の描画位置を計算するためのサブルーチン。