CH32V203 には RTC(Real-Time Clock)が搭載されています。時刻を利用するような電子工作をしたいなと思い、この RTC の使い方を調べてみました。
RTC(Real-Time Clock)とは現実世界の時間を扱うための機能のことです。マイコンの世界で単に「クロック」というと CPU を駆動する(通常は MHz 単位の)クロックを意味しますが、RTC は 1 秒ずつ時を刻むことに特化したクロックです。
RTC を使うと正確な日時や時刻を扱うことが容易になります。CPU のクロックを利用したタイマを使っても正確な 1 秒をカウントすることはできるのですが、通常、RTC の方が長期間にわたり時間を数え続けることに特化した作りになっています。RTC はバックアップ用電池(コイン電池のような小型の電池)で長期間(数ヶ月や数年)動作させつづけることが想定されている点が、マイコンに搭載されるタイマ機能とは異なります。RTC に一旦正確な日時を設定すれば、バックアップ電池が生きている限り、ずっと時を刻み続けてくれるのです。
マイコンを動作させるときだけ大きな電源で駆動し、普段は主電源を切ってバックアップ電池で RTC だけを動作させておく、という使い方をします。CH32V203 の場合、VBAT という端子で RTC 用の電源を供給します。データシートによれば RTC のみ動作した状態では 1.7μA ほどの消費電流ですので、コイン電池 CR2032(240mAh)で 16 年以上持つ計算になります。
CH32V203 のリファレンスマニュアル1によれば、VDD(主電源)がオフになると、電圧低下検出器によって自動的に VBAT へと切り替わるとのことです。VBAT はマイコンの「バックアップ領域」に電源を供給します。この状態では PC13~PC15 は GPIO としての動作はできず、次の目的にのみ利用できるそうです。
タンパとは:7.4.1. タンパーとは
LSE は典型的には 32.768kHz の水晶振動子を接続します。
CH32V203 の電源ピンの接続回路の例として、リファレンスマニュアルの図 4-1 を引用します。
これによれば、VBAT にはバックアップ電池をそのまま接続し、VDD、VDDA には主電源(2.4V~3.6V)を接続すれば良さそうです。データシートによれば、VBAT は VDD を超えてはいけないので、VDD=3.3V なら VBAT≦3.3V でなければなりません。CR2032 の公称電圧は 3V なので、ちょうど使えそうですね。
RTC レジスタは通常の周辺機能のレジスタとは読み書きのタイミングが異なるようです。読み込みは特に難しいことはないようですが、読み込もうとしてから実際に値が読み込めるまで少し時間がかかることがあるようです。書き込みは次のステップに従う必要があります。
このように面倒なことになっているのは、RTC 関連の機能は RTC 専用のクロック(典型的には 32.768kHz の発振子)によって動いているからだと思います。このクロックはマイコンの主回路を動かすクロックと別なので、互いに非同期に動きます。そのため、レジスタの読み書きを非同期に行う必要があるのでしょう。非同期に書き込みを行う仕組みとして、上記のステップは納得感があります。
スタンバイモードに入るためには、事前に次の設定を行います。
これらの設定をしたうえで、実際にスタンバイモードに入りたい場所で WFI か WFE という命令を実行します。ch32v003fun の場合、__WFI()
と __WFE()
という関数が用意されていますので、それを呼び出せば OK です。
WFIとWFEの違い によれば、次の通りです。
WFIはWait For Interrupt(割り込み待ち)の略です。
WFEは、Wait For Event(イベント待ち)の略です。
WFIの場合、マイコンが低消費電力モードから復帰する際に、ベクタ割込みコントローラ(NVIC)によって、割込みが認識されて、割込み処理が始まります。
WFEの場合は低消費電力モードから復帰する際に、割込み処理が行われずに、低消費電力モードからの復帰だけが行われます。
RTC の文脈でいえば、事前に設定した時刻になったときに RTC Alarm が作動し、スタンバイが終了しますが、その際に Alarm 割り込みハンドラを実行するか、スタンバイに入る直前に実行していた箇所から続きを実行するかの違いです。
RISC-V の仕様では WFI 命令だけが機械語命令として用意されているようです。WFE は単体の命令としては存在せず、事前に「WFI 命令が実行されたら WFE として解釈する」という設定をしておくことで、WFE 命令を模擬するのだそうです。その設定は PFIC_SCTLR レジスタの WFITOWFE ビットに 1 を書くことです。ch32v003fun の __WFI()
と __WFE()
の実装は ch32v003fun.h#L12379-L12404 にあります。
スタンバイモードに入るようなプログラムを書き込んでしまうと、次から WCH-LinkE でプログラムを書き込めなくなります。書き込めないどころか、WCH-LinkE 経由でマイコンの種別さえ取得できない状態になります。実際に筆者はこの状況に遭遇し、WCH-LinkE やマイコンが壊れたのではないかと焦りました。
どうやら、スタンバイモードに入ってしまうと MCU と WCH-Link 間が接続できなくなるようなのです。解決策を CH32V203のブートローダーを使ってプログラムを書き込む にまとめました。
RTC を初期化するには、まずバックアップ領域(VBAT で駆動される回路)に対するアクセスを許可する必要があります。PWR_CTLR レジスタの DBP ビットを 1 にします。その後で RCC 系のレジスタにアクセスできるようになります。
void rtc_init() {
printf("Initializing RTC\n");
// Enable clock for backup interface
RCC->APB1PCENR |= RCC_APB1Periph_BKP | RCC_APB1Periph_PWR;
// Enable to write to backup domain
PWR->CTLR |= PWR_CTLR_DBP;
// Enable external low-speed oscillator and select RTC clock source
RCC->BDCTLR = RCC_LSEON | RCC_RTCSEL_LSE;
// Enable RTC (must be RTCSEL != 0)
RCC->BDCTLR |= RCC_RTCEN;
// Wait for LSE clock gets ready
while ((RCC->BDCTLR & RCC_LSERDY) == 0);
}
筆者は最初、RCC_BDCTLR レジスタの設定を 1 行で済まそうとして RCC_BDCTLR = RCC_LSEON | RCC_RTCSEL_LSE | RCC_RTCEN;
と書いていました。しかしこれでは正常に設定できない場合があるとわかり、2 行に分けて設定するコードに落ち着きました。
RTC Alarm を使えば、目覚まし時計のアラームと同じように、指定した時刻になったら割り込みやイベントを発生させられます。RCC のレジスタへ書き込む際は#RTC レジスタの読み書きで説明したように、CNF ビットを使った同期をします。
void rtc_set_alarm() {
// Ensure the last RTC operation is completed
while ((RTC->CTLRL & RTC_CTLRL_RTOFF) == 0);
// Start configuration
RTC->CTLRL |= RTC_CTLRL_CNF;
// Set alarm value
RTC->ALRML = RTC_ALARM;
RTC->ALRMH = 0;
// Enable alarm/second interrupt
RTC->CTLRH |= RTC_CTLRH_ALRIE | RTC_CTLRH_SECIE;
// Finish configuration
RTC->CTLRL &= ~RTC_CTLRL_CNF;
// RTC registers will start being written
// RTCAlarm event is assigned to EXTI17
EXTI->EVENR |= (1 << 17);
EXTI->RTENR |= (1 << 17);
}
WFE でスタンバイモードに移行する場合、スタンバイから復帰するために割り込みを有効にする必要はありません。ただ、イベントが発生する必要はあるため、EXTI_EVENR と EXTI_RTENR を適切に設定します。