前回から引き続き、現在の気象情報と時刻を表示する「天気時計」の設計を進めて完成させたいと思います。
本HPでは何度目かになりますが、実時間時計(RTC;Real Time Clock)をソフトウェアで実装します。
まず、汎用16ビットタイマTIM162ユニットを使って、1秒毎に割り込みを起こします。
TIM162ユニットI/Oレジスタの設定値:
レジスタ | 設定 | 備考 |
プリスケーラレジスタ | timpsc=fcpu/1,000−1 | 1ms毎にカウントアップ。 |
周期レジスタ | timprd=1,000−1 | 1秒でオーバフロウ。 |
タイマ制御レジスタ | timctl=timr_cnte | カウント開始(イネーブル)。 |
割り込みフラグレジスタ | timflg=timr_ovff|timr_ovfe |
オーバフロウフラグクリア、 オーバフロウ割り込み有効。 |
TIM162ユニットで1秒毎にオーバフロウ割り込みを起こすので、割り込みサービスルーチン(ISR;Interrupt Service Routine)内で秒カウンタrtc_secondを+1インクリメントします。
結果、rtc_second≧60だった場合、rtc_second=0にクリアして、同時にrtc_minuteを+1インクリメントします。
この調子で、秒→分→時→日→月→年と桁上げを実行します。
この時、使い道のありそうな、秒、分、時、日を更新(インクリメント)した時に、イベントフラグ(rtc_evt_sec、rtc_evt_min、rtc_evt_hou、rtc_evt_day)を立てておくことにしました。
以下表にRTCの各カウンタ値を保持する変数名と動作を示しておきます。
カウンタ | 変数名 | 備考 |
秒 | rtc_second | 0〜59。60になったら0に戻して桁上がり。 |
分 | rtc_minute | 0〜59。60になったら0に戻して桁上がり。 |
時 | rtc_hour | 0〜23。24になったら0に戻して桁上がり。 |
曜日 | rtc_week | 0〜6。7になったら0に戻す。 |
日 | rtc_day | 1〜31。月の最終日+1になったら1に戻して桁上がり。 |
月 | rtc_month | 1〜12。13になったら1に戻して桁上がり。 |
年 | rtc_year | 西暦年(A.D.)。16ビット符号無整数。 |
曜日カウンタrtc_weekは、時カウンタrtc_hourからの桁上がりで+1インクリメントします。数値0〜6と、曜日との対応は任意です。今回のプログラム(weather_clock.asm)では、0〜6を月曜日〜日曜日に割り当てました。
月の日数については「西向く士」と、うるう年判定(次節)結果を使って求めます。
年カウンタrtc_yearは、西暦年を表します。普通は西暦0年と言わないので、1以上の数値となります。rtc_yearを西暦年と見なして、うるう年判定を実行するので、相対的な数値にしてしまうと、うるう年判定を間違う場合があります。ちゃんと、西暦2022年をrtc_year=2022と設定すれば問題ありません。
年カウンタが更新されたら、うるう年判定を実行してrtc_leapフラグを設定しておきます。うるう年かどうかが判らないと、2月の最終日が28日か29日か判断できないからです。
うるう年の定義は:
400で割り切れる年、または、
4で割り切れて、100で割り切れない年
実際の判定コードを以下に示します。
// ================================
// is r1 leap year ; disturbed r1,Sr[DR]
// return: r0=true / false
// ================================
is_leap:
pushw r1
andi r1,4-1
popw r1
ldbiu r0,false // %4!=0
bne isl_return
lsfti r1,-2
ldbiu r0,100/4
sesr sreg_b_dr
divu r1,r0 // 除算は1回だけ
cmpi r0,0
ldbiu r0,true // %4==0
bne isl_return
// %100==0
andi r1,4-1
beq isl_return
ldbiu r0,false // %100==0 && %400!=0
isl_return:
rtn
定義通りに処理すると、4と100と400の剰余算(除算)計3回が必要となります。上に示したコードでは、除算1回だけで済ませています。残りは、2冪数(=4)の除算(シフト演算)と剰余算(AND演算)で決着しています。ご確認ください。
表示データの更新も含めて検討しておきます。
秒まで表示する時計なら、毎秒の更新イベント(rtc_evt_sec)で時計表示を更新する必要があります。
秒を表示しない時計なら、毎分の更新イベント(rtc_evt_min)で時計表示を更新すれば事足ります。
日付表示がある場合は、少なくとも、日カウンタの更新イベント(rtc_evt_day)で表示を更新しなくてはなりません。
気象情報を表示する場合、一定時間毎にYahoo!天気・災害サイトから新しい情報を吸い上げる必要があります。
Yahoo!天気・災害ページは、3時間毎に更新されている様です。普通なら、HTMLデータの受信は3時間毎で良いのだけど、3時間毎の何時何分に更新されるのか判らないので、1時間毎に気象情報を取り込むことにしました。
気象情報の更新と同時に、SNTPで時刻を問い合わせて、時計も一緒に合わせることにしておきます。
1時間毎の処理は、時カウンタの更新イベント(rtc_evt_hou)で実行することが可能です。今回は、別な条件を使いました。
時刻合わせと気象情報の取得は、毎時10分(=wck_getw_minute)に、それぞれSNTPとHTTP-GETで取得します。「毎時10分」のイベントは作っていないので、分カウンタの更新イベント(rtc_evt_min)で分カウンタrtc_minuteを参照して、rtc_minute=10(=wck_getw_minute)だった時に実行する様、コーディングしました。
OLEDディスプレイ(下図)を使って、時計と気象情報を表示する「天気時計」にしようと思います。
OLEDディスプレイの表示領域は8×16[ドット]フォントを使った場合、16桁×2行です。時刻と日付を2行に表示してしまうと、もう、気象情報を表示する隙間はありません。
OLEDディスプレイのドライバチップSSD1306には16桁×4行分のグラフィックRAMが搭載されていて、表示開始位置をドット単位で指定可能となっています。よって、4行を使って、時刻、日付、気象情報を描画しておき、一定時間毎に表示開始位置を変更して、スクロールさせることにします。
以下表に、16桁×4行分の描画内容をまとめます。
\桁→ ↓行\ |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 備考 |
0 | 0 | 1 | : | 2 | 3 | : | 4 | 5 | 時刻 | ||||||||
1 | 2 | 0 | 2 | 2 | 年 | 0 | 3 | 月 | 2 | 1 | 日 | ( | 月 | ) | 日付 | ||
2 | 伊丹市の天気 (地域名) | 晴れ (天気予報、横2倍角) |
気象 情報 |
||||||||||||||
3 | − | 1 | 9 | ℃ | 1 | 0 | 0 | % | 降 | 水 | 量 | 1 | 9 | 9 | m | m |
後ほど動画で確認していただきますが、何だかんだ、「時計」表示がメインなので、時計の表示時間が長くなるように工夫してあります。
気温は負号を含めて数字3桁(−99〜999[℃])、湿度は数字3桁(0〜100[%])、降水量は数字3桁(0〜999[mm])の範囲にあることを想定しています。範囲を超えた場合は表示が崩れる場合がありますが、この範囲を超えるようだと、生命維持が困難でしょう。
時刻や天気予報などの重要な情報は、目立つ様に横2倍角表示にしてあります。
後ほど実験で、実際の表示をご確認いただけます。
ここまでの詳細設計結果に従って作成したプログラムを以下にリンクしておきます。
完成版プログラム:weather_clock.asm
アセンブルする前に、皆様のお住まいの地域に対応した、Yahoo!天気・災害サイトのURL(Uniform Resource Locator)を指定する必要があります。以下手順で、目的のURLを調べてください。
例)兵庫県伊丹市のピンポイント天気ページ
https://weather.yahoo.co.jp/weather/28/6310/28207.html
以下の様に切り分けて、プログラム(weather_clock.asm)先頭付近で定義しているマクロに設定してください。
マクロ名 | esp_http | esp_serv | esp_path |
URL | https:// | weather.yahoo.co.jp | /weather/28/6310/28207.html |
def esp_ctyp,"SSL" // TCP or SSL
def esp_port,"443" // 80 or 443
def esp_http,"https://" // http or https
def esp_serv,"weather.yahoo.co.jp" // サーバ名
def esp_path,"/weather/28/6310/28207.html" // データのファイルパス
「$(esp_http)$(esp_serv)$(esp_path)」と連結すると元のURLを復元できる様に、しっかり切り分けてください。
esp_ctyp,esp_port,esp_httpの3つは、Yahoo!天気・災害サイトでは、上のまま固定で良いはずです。もし、URLの先頭が「https://」ではなくて、「http://」のページを取得する場合には、以下の様にセットで変更してください。
def esp_ctyp,"TCP" // TCP or SSL
def esp_port,"80" // 80 or 443
def esp_http,"http://" // http or https
この様(↑)に、お住まいの地域のURLを設定できたら、ようやく完成となります。
プログラム(weather_clock.asm)をアセンブルしてFPGAボードに書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。
シリアルモニタコマンド仕様:
基本的には全て、デバッグ用のコマンドですので、シリアルモニタ無しでも自立します。
あ〜、でも、ESP32との通信に失敗した時とか、回復不能なエラーが起きた時の表示は、シリアルモニタにしか出していないな〜。「正常に動作する限り、自立可能」と、言い直してみる……。
アセンブル時にマクロdを適切に設定しておくと、デバッグ用のメッセージを出力することができます。
def d,"#" // show debug="" / not="#"
ESP32へ発行するコマンドや、そのレスポンス、取得したHTMLデータ等が大量にシリアルモニタへ送られてきます。Yahoo!天気・災害サイトのHTML仕様が変わるなど、うまく気象情報を取得できなくなった場合には、「デバッグ」するのに必要な機能だと思われます。
執筆時点では、ちゃんと取得できていますので、必要のない機能です。
※本HPの回路やプログラムなどは全て、サポート無しの無保証です。Yahoo!天気・災害サイトの仕様変更などによって、将来、気象情報を取得できなくなってもサポートはしませんので、各自、対応ください。
今回のアプリケーションを想定したデザインは、既に「#22-1 文字コード変換回路」で作成してありました(1^o^)v。
MCOC115US0816:
以下リンク先のダウンスケールプロジェクトの中から、MCOC115US0816マイコンのプロジェクト(MCOC_USと、mcoc115us0816.v)を使ってコンパイル(論理合成・配置配線)して、FPGAボードに書き込んでください。
FPGAボード(Sipeed Lichee Tang Primer)への書き込み手順はこちらを参照してください。
プログラム(weather_clock.asm)と共に、こちらのデザインを書き込んだFPGAボードと、ESP32、OLEDディスプレイを以下接続表に従って接続してください。
OLEDディスプレイ | 接続 | FPGAボード | 接続 | ESP32 |
GND | ⇔ | GND | ⇔ | GND |
VCC | ← | 5V | ||
SCL | ⇔ | stws_scl | ||
SDA | ⇔ | stws_sda | ||
uar1_txd | → | RX2 | ||
uar1_rxd | ← | TX2 | ||
uar1_rts | → | D15(CTS) |
※FPGAボード(Moscoviumマイコン)のピン配置図はこちら。
上の接続に加えて、FPGAボードにFTDI等のUSB⇔UART変換モジュールを接続してください。
ESP32とのUART通信では、ハードウェアフロウ制御(RTS,CTS)を使います。
HTTPプロトコルでHTMLデータを取得すると、大量のデータが一気に送られてくるので、ハードウェアフロウ制御を使って取りこぼしを回避します。
実は、ESP32のATコマンドファームウェア自体は、5M[baud]までのUART通信に対応しているみたいです。ESP32は240MHz動作だから、余裕なのかもしれませんね……。
対するMoscovium側のUART8N1ユニットは24MHz動作(ESP32の1/10!)なので、「1M[baud]とか、大丈夫かな〜?ダメかもな〜。5M[baud]は絶対無理!」ってな具合です。
それでも、現在の設定値115,200baudよりは、もう少し高速通信させることも可能なはずなので、そんな時にも、ハードウェアフロウ制御があれば、安心です。
以下図に実験中の様子を示し、実験を進めます。
実験サンプル:【 】内はシリアルモニタからの送信データ
【sntp】
OK: sntp 2022年08月31日(水) 20:57:12
【getw】
OK: getw (伊丹市の天気),(晴れ),31,70,0
日本語文字をS-JISコードで送信してくるので、Tera Term等、S-JISコードを送受信可能な通信ソフトウェアを使ってください。
起動すると、既に天気時計として、時刻、日付、気象情報をOLEDディスプレイに表示しています。SNTPによる時刻合わせと、HTTPによる気象情報の取得は、起動時と毎時10分に自動取得するようになっています。
上の実験サンプルでは、手動でSNTPによる時刻合わせと、HTTPによる気象情報の取得を行いました。
SNTPによる時刻合わせ(【sntp】コマンド)は、ほぼ一瞬で終わります。が、気象情報の取得(【getw】コマンド)には、結構な時間がかかります。なにしろ、80Kバイト程のHTMLデータをESP32からUART通信速度115,200baudで受信しながら解析して、必要な情報を抜き出しているので。
80Kバイト×10[ビット/バイト]÷115,200baud≒7s
単純計算で、最速受信したとしても7sかかりますから。ESP32側の通信速度を変更するのが面倒だったので、115,200baudで強行したのですが、たぶん、もう少し速い通信速度でも通信できると思いますので、取得時間を短縮したい方は、トライして見てください。
以下に、天気時計動作中の様子を撮影した動画をリンクします。
動画で確認すると、気象情報の取得には10sほどかかっていますね。上の計算結果と符合します。
ESP32を使ってインターネット上のHPにアクセス、HTMLを取得できるようになったことで、天気予報に留まらず、株価情報📈や為替情報📉、最新のニューストピックス、災害・台風情報、なども取得可能です。アプリケーション(応用)の可能性が広がりましたでしょう?