このブログは、サーバー代など、運営費のため広告やアフェリエイトがあります。
大変申し訳ありませんが、ご理解よろしくお願いします。

このブログは、サーバー代など、運営費のため広告やアフェリエイトがあります。
大変申し訳ありませんが、ご理解よろしくお願いします。

CH32V203C8T6 RTCを使う (バックアップ電池も使用)

スポンサーリンク

こんにちは。Yukiです。

今回は、CH32V203C8T6でRTCを使ってみたいと思います。
ちなみにですが、CH32V203K8T6でもRTCは使用できますが、色々成約があるので注意です。(下の方で書きます)

RTCの簡単な説明

RTCモジュールはReal Time Clockの略で、日付や時間を刻み続けてくれる機能です。

外付けだと、EPSONのRTCモジュール(RX8901やRA8000)などが比較的有名です。

今回は、マイコンの内部に機能として入っているということで、使ってみたいと思います。

CH32V203のRTC

CH32V203シリーズのマイコンにはRTC機能が搭載されています。

機能としては

  • 220 のプリスケーラ(分周器)がついていて、自由に選択可能
  • 32bitのカウンタ
  • いろんなクロックを入力可能
  • 割り込み可能
  • RTCのみでのリセット可能
  • VBATの電源供給によりRTC動作

まあ普通ですね。使いにくいなと思ったのは、カウンタしか用意されていないことでしょうか。

(たしかSTM32シリーズとかだと日付のレジスタと、時間のレジスタが別れていたので、うるう年とかそういうめんどくさいことを考えなくて良かった気がします)

ちなみにですが、CH32V203K8T6などのCH32V203F6, F8, G6, G8, K6, K8はVBAT端子とOSC32端子がないため、バックアップ動作(メイン電源を消しても、VBATに接続されている電源でRTCのみ省電力動作)はできません。

回路例

CH32V203C8T6で、RTCを使い場合の参考回路その1

RTCを使って時計を作ろう的なやつのマイコン部分のみ切り取って持ってきました。

OSC32INとOSC32OUTに水晶振動子とC0G特性があるセラコンをつければOKです。
(MEMS発振器とかでもいいと思います)

3.3V電源に抵抗を挟んで、スーパーキャパシタを充放電する回路

回路図上のシンボルが異なっていますが、BT1はスーパーキャパシタです。
本当は3.3Vから抵抗の間にダイオードを入れたほうが、漏れ電流が少なくて良いのかもしれないです。

基本的に回路はこれだけです。これだけでRTCのバックアップ動作をします。

ちなみにですが、CH32V203K8T6等のバックアップ動作ができないマイコンは内部発振器LSIを使っても大丈夫です。(ただし精度は若干落ちます)
ちなみにですが、LSI(内部低クロック発振器)を使うと、バックアップ動作はできなくなります。

初期化プログラム例

#include <ch32v20x.h>

//RTCをLSI(OSC32)かつバックアップ動作で初期化
void RTC_LSI_init(void){
    RCC->APB1PCENR |= RCC_PWREN; //Powerインターフェイス有効
    RCC->APB1PCENR |= RCC_BKPEN; //Backupインターフェイス有効

    PWR->CTLR |= PWR_CTLR_DBP; //Backup RTC有効

    RTC->PSCRL = 0x7FFF; //32.768kHzを1sに分周
    RCC->BDCTLR |= RCC_LSEON; //LSE有効
    RCC->BDCTLR |= (1 << 8); //RTCクロックをLSEに設定
    RCC->BDCTLR &= ~(1 << 9);
    RCC->BDCTLR |= RCC_RTCEN; //RTCクロック有効
}

int main(void){
    RTC_LSI_init(void);
}

上が初期化例です。

ちなみに、分周器は220 用意されているので、1sカウントさせたいのであれば、1.048576MHzまではいけるはずです。(OSC32IN/OUT端子で使えるかは知らないですけど。まあ安価なので32.768kHzで良いと思いますが)

LSI(内部低クロック)や外部からクロック源を入れるとかってことであれば、1MHz以下にしたほうがいいと思いますね。(そうじゃないと1sごとにカウントできない)

日付/時間読み取りのプログラム例

RTCを使うということは、日付や時間にアクセスする必要があるということだと思います。
(時計やアラームを作る場合であれば、日付や時間はアクセス必須です)

ここでは、日付/時間読み取りのプログラム例を提示します。

#include <ch32v20x.h>
#include <time.h>

//RTC 年を取得
int RTC_Year_Read(void){
    unsigned long RTC_val = (RTC->CNTH << 16) | RTC->CNTL; //すべてくっつける

    time_t RTC_time = RTC_val; //Time型に変換(いらない気もする)

    struct tm *utc_time = gmtime(&RTC_time); //UNIX時間から変換

    int Year = utc_time->tm_year + 1900; //年 1900年ズレてるらしい

    return (Year);
}

//RTC 月と日付を取得 (3月28日なら戻り値0328)
int RTC_Month_Day_Read(void){
    unsigned long RTC_val = (RTC->CNTH << 16) | RTC->CNTL; //すべてくっつける

    time_t RTC_time = RTC_val; //Time型に変換(いらない気もする)

    struct tm *utc_time = gmtime(&RTC_time); //UNIX時間から変換

    int Month = utc_time->tm_mon + 1; //月
    int Day = utc_time->tm_mday; //日付

    return (Month *100 + Day);
}

//RTC 時間+分数で取得 (12時34分なら戻り値1234)
int RTC_HourMin_Read(void){
    unsigned long RTC_val = (RTC->CNTH << 16) | RTC->CNTL; //すべてくっつける

    time_t RTC_time = RTC_val; //Time型に変換(いらない気もする)

    struct tm *utc_time = gmtime(&RTC_time); //UNIX時間から変換

    int Hour = utc_time->tm_hour; //時
    int Min = utc_time->tm_min; //分

    return (Hour*100 + Min);
}

//RTC 秒数を取得
int RTC_Sec_Read(void){
    unsigned long RTC_val = (RTC->CNTH << 16) | RTC->CNTL; //すべてくっつける

    time_t RTC_time = RTC_val; //Time型に変換(いらない気もする)

    struct tm *utc_time = gmtime(&RTC_time); //UNIX時間から変換

    int Sec = utc_time->tm_sec; //秒

    return (Sec);
}

もともと7セグに表示する目的で作っていたため、ややこしくなっていますが、ほぼどれも要領は同じです。

このマイコンのRTCモジュールは、日付や時間が別々のレジスタに入っている構造ではないので、(ただカウンタにカウントされていくだけ)UNIX時間で保存したほうが楽です。ということで、これからやることはUNIX時間→時間に変換しているだけです。

まず、RTC->CNTHRTC->CNTLでレジスタが16bitずつ別れているため、
unsigned long RTC_val = (RTC->CNTH << 16) | RTC->CNTL;
でRTCのカウンタに入っているデータを合体させます。 

次に、
time_t RTC_time = RTC_val;
でtime_t変数にします。(実はtime_t ってlong long int型なので、わざわざする必要ない気もしますが、一応やっておきます)

次に、
struct tm *utc_time = gmtime(&RTC_time);
で、tm構造体に変換します。gmtime関数で年や日付、時間などに分解されます。
このtm構造体に日付だの時間だの入っています。

tm_sec秒 0~61が入る (60,61はうるう秒考慮用)
tm_min分 0~59が入る
tm_hour時 0~23が入る
tm_mday日 1~31が入る
tm_mon月 0~11 (0が1月なので注意)
tm_year年 (1900年から始まっている)
tm_wday曜日 (0:日曜日 1:月曜日 … 6:土曜日)

なんでか知らないですが、tm構造体では1900年から始まるのですが、UNIX時間では1970年から始まっていることに注意が必要です。(ややこしいですが、とにかくtm構造体から取り出したり、代入するときは1900を足したり引いたりすることを忘れないでください)

int Sec = utc_time->tm_sec;
あとは、上のような要領で取り出せます。

(これでやればうるう年とかも考慮しなくていいので楽ですね)

RTCのカウンタに書き込む場合

RTCのカウンタに書き込む場合、RTCをカウンタ書き込みモードにする必要があります。

void RTC_Set_UNIX(time_t tm){
    RTC->CTLRL |= RTC_CTLRL_CNF; //Settingモード(レジスタに書き込みできるモード)

    RTC->CNTH = (tm >> 16);
    RTC->CNTL = (tm & 0xFFFF);

    RTC->CTLRL &= ~RTC_CTLRL_CNF; //クロック動作開始
}

int main(void){

    static unsigned int timer_cnt_hour = 0;
    static unsigned int timer_cnt_min = 0;
    static unsigned int timer_cnt_sec = 0;
    static unsigned int timer_cnt_time = 0;

    struct tm time_info = {0};
    time_info.tm_year = time_set_year - 1900; //年 1990年から始まるので-1900
    time_info.tm_mon = time_set_month - 1; //月 0から始まるので-1
    time_info.tm_mday = time_set_day; // 日
    time_info.tm_hour = time_set_hour; // 時
    time_info.tm_min = time_set_min; // 分
    time_info.tm_sec = time_set_sec; // 秒

    time_t unix_time = mktime(&time_info); //UNIX時間に変更

    RTC_Set_UNIX(unix_time); //UNIX時間を書き込み
}

RTC->CTLRL |= RTC_CTLRL_CNF;
RTC->CTLRLレジスタのCNFビットを1にすることで、カウンタに書き込みできるモードになります。

RTC->CNTH = (tm >> 16);
RTC->CNTL = (tm & 0xFFFF);

カウンタは16bitずつ別れているので、ハイ側(32~17bit側)は16bitシフトダウンして代入しています。
ロー側(16~1bit側)は下位16bit分マスクして代入しています。

(PIC(XCコンパイラ)とかならRTC_CNTとかでH側とL側まとめて操作できるのに、この辺不便ですよね)

最後にCNFビットを0に戻して終了です。(0に戻した時点でカウント動作が開始されます)

ちなみに

上のプログラムでは忘れていましたが、mktimeとかで生成に失敗した場合は、NULLが返されるらしいです。なので本当は、

time_t unix_time = mktime(&time_info);

if(unix_time == NULL){
    //エラー処理
    return -1;
}

みたいにしたほうが安全だと思います。

時計のサンプル

時計についてはアメブロの方で公開しようと思っています。
記事を書き次第、だしますので、少々お待ち下さい。

余談

UNIX時間は、32bitだと2038年1月19日3時14分7秒に32bit目に繰り上がるので、負の値になり誤動作すると言われています。詳しくは2038年問題で調べてみてください。

ちなみに、このマイコンだと32bit分あるので、符号をないものと考えると、2106年2月7日6時28分ごろにカウントできなくなります。この日を超えると、カウンタがオーバーフローするので1970年に戻りますね。

まあこのマイコンが後80年後動いているとは思えないですし、その頃には新しい時間の数え方の方法が出ているでしょう。(そもそも80年後はRTCも64bit化されているかもしれないですし)

参考になったら、コーヒー1杯奢ってくれるとうれしいです
Buy Me A Coffee
電子工作
スポンサーリンク
シェアする
denshi1996をフォローする
スポンサーリンク

コメント

タイトルとURLをコピーしました