こんにちは、ENGかぴです。
Arduino UNO R4 WiFiはRA4M1マイコンをメインに搭載しているためRTCを実装することができます。RTCライブラリを実装してシリアルモニターから電文を送信して時刻を書き込んで表示する方法をまとめました。
Arduino UNO R4 WiFiのボードを追加する方法は下記記事にまとめています。
Arduino UNO R4 WiFiのLEDマトリックスを使用する
以下ではArduino UNO R4 WiFiをUNOR4-WiFiと表記します。Arduinoのライブラリを使用して動作確認したことをまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
RTCライブラリを使用する
メインマイコンのRA4M1(ルネサス)は内蔵のRTCを搭載しています。Arduino環境ではRTCライブラリを使用するとRTCによる時刻の管理ができるようになります。
RTCライブラリのスケッチ例など使用方法については下記リンクに詳細があります。
Arduino UNO R4 WiFi Real-Time Clock | Arduino Documentation
本記事ではSerialライブラリと組み合わせて自作の電文を送信してRTCに時刻を書き込んで任意の時刻からRTCを動作させます。電文の構成は下記記事のものを流用します。
以下ではRTClockクラス(RTCでインスタンス化されている)のメンバー関数を赤文字で表記します。
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
初期化処理を実装する
#include "RTC.h"
void setup() {
RTC.begin();
RTCTime init(21, Month::SEPTEMBER, 2025, 21, 30, 30, DayOfWeek::SUNDAY, SaveLight::SAVING_TIME_INACTIVE );
RTC.setTime(init);
}
最初にRTC.hをインクルードします。begin()関数でRTCに関する初期化を行います。
RTCTimeクラスの変数に日時のデータを指定してインスタンス化します。第1引数に日を指定します。第2引数にMonthクラスのenumでJANUARYからDECEMBERまでの月を指定します。
第3引数に年を指定します。第4引数に時、第5引数に分、第6引数に秒を指定します。
第7引数にDayOfWeekクラスのenumでMONDAYからSUNDAYまでの曜日を指定します。
第8引数にSaveLightクラスのenumでSAVING_TIME_INACTIVEまたはSAVING_TIME_ACTIVEを指定します。サマータイムを使用する場合はSAVING_TIME_ACTIVEを指定します。
setTime()関数の引数にRTCTimeクラスの変数を指定してRTC時刻を書き込みます。例ではインスタンス化したinitを指定しています。
存在しない時刻を入力するとRTCのカウントの動作不良の原因になるため注意が必要です。
RTCから時刻の取得
RTCTime now;
if( RTC.getTime(now) ){
WeekSet(DayOfWeek2int(now.getDayOfWeek(),false));
sprintf(datestr,"%d/%02d/%02d %s %02d:%02d:%02d",
now.getYear(),
Month2int(now.getMonth()),
now.getDayOfMonth(),
week,
now.getHour(),
now.getMinutes(),
now.getSeconds() );
Serial.println(datestr);
}
2行目のgetTime()関数を使用すると引数で指定したRTCTime型の変数に取得した時刻が格納されます。
3行目のWeekSet()関数は取得した曜日から文字列を生成するための自作の関数です。
DayOfWeek2int()関数は取得した曜日の値を整数に変換するRTCライブラリの関数です。第1引数にgetDayOfWeek()関数で取得した値を指定します。第2引数は日曜日を先頭にするかのフラグです。trueを指定するとMONDAYが先頭でfalseを指定するとSUNDAYが先頭になります。
4行目は時刻を表示する文字列を生成します。sprintf()関数で取得した時刻のデータと曜日のデータから「2025/08/10 SUN 12:00:00」などの文字列を生成します。
getYear()関数、getMonth()関数、getDayOfMonth()関数、getHour()関数、getMinutes()関数、getSeconds()関数はそれぞれ、年月日時分秒を取得します。月はMonthの型をMonth2int()関数を使って1~12の値に変換しています。
本記事では、ログ用の時刻で多少ずれても問題ない用途を想定しています。そのため割り込みを使用せず、メインループで管理するソフトウェアタイマを使って時刻の更新を行っています。
自作の電文による時刻補正

シリアルモニターを使ってRTCの時刻を自作の電文を送信して書き込みます。電文の構成はヘッダーに「RTC」の文字列に年から秒を「-」(ハイフン)で繋げたものを電文として生成します。
例のようにシリアルモニターで「RTC2025-08-10-00-12-00-00」を文字列で送信するとUNOR4-WiFiで電文を判断して時刻を書き込みます。曜日は00が日曜日とします。
/* 時刻のチェック */
bool chkdata(void){
int d;
Month m;
int y;
int h;
int min;
int s;
DayOfWeek dotw;
SaveLight su;
bool ret = false;
//RTC2025-08-10-00-12-00-00
y = strtol((const char*)&Rcvdata[3], NULL, 10);
m = (Month)(strtol((const char*)&Rcvdata[8], NULL, 10)-1);
//日から秒は省略
if( y >= 2000 && y < 2100 ){
if( m >= Month::JANUARY && m <= Month::DECEMBER ){
if( d >= 1 && d <= 31 ){
if( h >= 0 && h <= 23 ){
if( min >= 0 && min <=59 ){
if( s >= 0 && s <= 59 ){
if( dotw >= DayOfWeek::SUNDAY && dotw <= DayOfWeek::SATURDAY ){
RTCTime setT(d, m, y, h, min, s, dotw, SaveLight::SAVING_TIME_INACTIVE);
RTC.setTime(setT);
Serial.println("RTCSET OK!");
ret = true;
}
}
}
}
}
}
}
return ret;
}
シリアルモニターで取得した電文の文字列から年~秒のデータを生成します。
14行目は年を生成していますが、strtol()関数を使用しています。第1引数に変換する文字列の先頭アドレスを指定します。例では年の文字列の配列を指定しています。
第2引数は文字変換が終了した位置を格納するポインタアドレスを指定します。使用しない場合はNULLとします。
第3引数は数値の基数を指定します。10進数なら10、16進数なら16を指定します。
電文は年~秒を-(ハイフン)で区切っているためハイフンまでの文字列を変換した結果を格納します。
格納した結果を年から秒の範囲チェックを行う最低限のチェック(18~24行目)をしてRTCTimeクラスの変数のsetTを引数で指定してインスタンス化します。setTime()関数でRTCの電文の時刻を書き込みます。
PR:RUNTEQ(ランテック )- マイベスト4年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール
動作確認
UNOR4-WiFiの電源を投入すると初期のRTC時間を1秒ごとにシリアルモニターに表示します。シリアルモニターで電文の文字列を送信します。
シリアルモニターでポートを選択します。USBモジュールを挿入して生成されたCOMポートを選択します。ボーレートは115200bpsを選択します。

シリアルモニターの結果(電文の送信)
メッセージ欄に「RTC2025-09-22-01-20-33-30」(2025年9月22日(月)20:33:30)を送信します。電文が受け付けられると「RTCSET OK!」を表示して書き込んだ時刻からスタートします。電文によってRTC時刻が更新されていることが確認できました。
次に、電文がNGの場合の動作を確認しました。例えば9月51日とありえない時刻を送信すると、「RTCSET NG!」の文字列を表示します。電文が無効となることが確認できました。
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。
#include "RTC.h"
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIM_RTC 100
#define RTC_READ 100
#define RING_SZ 128
#define TIM_RX_WAIT 5 //50ms
#define OFFSET 3
struct RING_MNG{
uint8_t wp;
uint8_t rp;
uint8_t buf[RING_SZ];
};
uint32_t beforetimCnt = millis();
int16_t timrtc;
int16_t timrcv = TIME_OFF;
char datestr[32];
char str[] = {"2025/09/22-00-21:30:30"};
char week[] = {"sun"};
uint8_t Rcvdata[sizeof(str) + OFFSET];
RING_MNG rcv;
void mainTimer(void);
void mainApp(void);
void WeekSet(int wk );
void rcvdatechk(void);
void ReadPointerAdd(void);
bool chkdata(void);
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
RTC.begin();
RTCTime init(21, Month::SEPTEMBER, 2025, 21, 30, 30, DayOfWeek::SUNDAY, SaveLight::SAVING_TIME_INACTIVE );
RTC.setTime(init);
}
void loop() {
mainTimer();
mainApp();
rcvdatechk();
}
/* タイマ管理 */
void mainTimer(void){
if ( millis() - beforetimCnt > BASE_CNT ){
beforetimCnt = millis();
if( timrtc > TIME_UP ){
--timrtc;
}
if( timrcv > TIME_UP ){
timrcv--;
}
}
}
/* メイン処理関数 */
void mainApp(void){
while(Serial.available()){
rcv.buf[rcv.wp] = Serial.read();
if( ++rcv.wp >= RING_SZ ){
rcv.wp = 0;
}
}
if( timrtc == TIME_UP ){
timrtc = TIM_RTC;
RTCTime now;
if( RTC.getTime(now) ){
WeekSet(DayOfWeek2int(now.getDayOfWeek(),false));
sprintf(datestr,"%d/%02d/%02d %s %02d:%02d:%02d",
now.getYear(),
Month2int(now.getMonth()),
now.getDayOfMonth(),
week,
now.getHour(),
now.getMinutes(),
now.getSeconds() );
Serial.println(datestr);
}
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
}
/* 曜日文字列生成 SUNDAY が先頭の場合 */
void WeekSet( int wk ){
switch(wk){
case 1:
sprintf(week,"MON");
break;
case 2:
sprintf(week,"TUE");
break;
case 3:
sprintf(week,"WED");
break;
case 4:
sprintf(week,"THU");
break;
case 5:
sprintf(week,"FRI");
break;
case 6:
sprintf(week,"SAT");
break;
default:
sprintf(week,"SUN");
break;
}
}
/* 受信データから時刻データを生成 */
void rcvdatechk(void){
int8_t rxsz;
uint8_t allsz;
uint8_t rp = rcv.rp;
uint8_t i;
uint8_t dat[3];
if( timrcv == TIME_UP){
timrcv = TIME_OFF;
ReadPointerAdd();
}
rxsz = rcv.wp - rcv.rp; //受信データ数の算出
if( rxsz < 0 ){
rxsz = rxsz + sizeof(rcv.buf);
}
if( rxsz == 0 ){
timrcv = TIME_OFF;
}
else{
if( timrcv == TIME_OFF ){
timrcv = TIM_RX_WAIT;
}
if( rxsz >= 3 ){
for( i = 0; i < 3; i++){//データサイズ算出のため仮おき
dat[i] = rcv.buf[ rp ];
if(++rp >= sizeof(rcv.buf) ) rp = 0;
}
if( dat[0] == 'R' && dat[1] == 'T' && dat[2] == 'C'){
allsz = sizeof(str) + OFFSET - 1;
if( rxsz >= allsz){
timrcv = TIME_OFF;
for(i=0; i < sizeof(Rcvdata); i++){
Rcvdata[i] = 0;
}
for( i=0; i < allsz; i++ ){
Rcvdata[i] = rcv.buf[rcv.rp];
ReadPointerAdd();
}
if( chkdata() == false ){
Serial.println("RTCSET NG!");
}
}
}
}
}
}
/* 読み込み位置の更新 */
void ReadPointerAdd(void){
if(++rcv.rp >= sizeof(rcv.buf) ){
rcv.rp = 0;
}
}
/* 時刻のチェック */
bool chkdata(void){
int d;
Month m;
int y;
int h;
int min;
int s;
DayOfWeek dotw;
SaveLight su;
bool ret = false;
//RTC2025-08-10-00-12-00-00
y = strtol((const char*)&Rcvdata[3], NULL, 10);
m = (Month)(strtol((const char*)&Rcvdata[8], NULL, 10)-1);
d = strtol((const char*)&Rcvdata[11], NULL, 10);
dotw = (DayOfWeek)strtol((const char*)&Rcvdata[14], NULL, 10);
h = strtol((const char*)&Rcvdata[17], NULL, 10);
min = strtol((const char*)&Rcvdata[20], NULL, 10);
s = strtol((const char*)&Rcvdata[23], NULL, 10);
if( y >= 2000 && y < 2100 ){
if( m >= Month::JANUARY && m <= Month::DECEMBER ){
if( d >= 1 && d <= 31 ){
if( h >= 0 && h <= 23 ){
if( min >= 0 && min <=59 ){
if( s >= 0 && s <= 59 ){
if( dotw >= DayOfWeek::SUNDAY && dotw <= DayOfWeek::SATURDAY ){
RTCTime setT(d, m, y, h, min, s, dotw, SaveLight::SAVING_TIME_INACTIVE);
RTC.setTime(setT);
Serial.println("RTCSET OK!");
ret = true;
}
}
}
}
}
}
}
return ret;
}
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
PR:企業で求められる即戦力技術を身に付ける テックキャンプエンジニア転職
最後まで、読んでいただきありがとうございました。