こんにちは、ENGかぴです。
Arduino UNO R4 WiFiはRA4M1マイコンをメインに搭載しているためRTCを実装することができます。RTCライブラリで時刻を取得し、OLEDライブラリ(SPIライブラリ)を使って時刻を表示して動作確認を行いました。
LCDを使って時刻を表示する方法を下記記事にまとめています。本記事はLCDをOLEDに置き換えてSPI通信の動作を確認します。
ArduinoでRTCモジュールの情報をLCDを使って更新する
OLEDは0.95インチ(HiLetgo製)を使用しています。
以下ではArduino UNO R4 WiFiをUNOR4-WiFiと表記します。Arduinoのライブラリを使用して動作確認したことをまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
OLEDライブラリを使用する
HiLetgo製の0.95インチ7ピン 65kカラー対応のOLEDディスプレイモジュールを使用しています。通信方式は3線のSPI通信を使用します。下記リンクにモジュールに関する情報が記載されています。
0.95″ Inch 7 Pin Colorful 65K SPI OLED Display Module
OLEDのドライバーICにSSD1331を使用しているためArduinoのライブラリを使って文字や記号を表示することができます。
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
Adafruit SSD1331 OLED Driverライブラリを追加する

Arduino IDEのライブラリマネージャの検索欄にssd1331を入力するとライブラリの候補が表示されます。候補の中から「Adafruit SSD1331 OLED Driver Library for Arduino」をインストールします。
インストールする際にライブラリの依存関係をインストールするかを尋ねられることがあります。ライブラリの依存関係が不足しているとうまく動作しないことがあるためすべてをインストールすることをお勧めします。
OLEDライブラリの使用例
#include <Adafruit_SSD1331.h>
#define CS 10
#define RST 9
#define DC 8
#define BLACK 0x0000
Adafruit_SSD1331 oled = Adafruit_SSD1331(&SPI, CS, DC, RST);
void setup() {
oled.begin();
oled.fillScreen(BLACK);
}
Adafruit_SSD1331.hをインクルードします。Adafruit_SSD1331クラスの変数としてoled(任意でよい)をインスタンス化します。第1引数にSPIクラスの変数のアドレスを指定します。Arduino環境ではSPIが標準で定義されているのでSPIを指定します。
第2引数にチップセレクトで使用するピン番号を指定します。
第3引数にData/Command(DC)ピン番号を指定します。SPI通信を3線で行うためData/Commandピンでデータの方向を選択して送受信します。
第4引数にRESピン番号を指定します。RESピンはHIGHにするとモジュールのリセットを解除します。LOWにするとリセット状態になります。
setup()内でAdafruit_SSD1331クラスのメンバー関数であるbegin()関数でOLEDの初期化を行います。fillScreen()関数は画面全体のカラーを指定した色で塗りつぶします。例ではBLACK(0x0000)0指定しています。色の指定は16bitデータ(0~65535の範囲)で行います。
oled.setTextSize(1); //5×7フォント
oled.setCursor(0, 0);
oled.setTextColor(WHITE); //テキストの色を指定
oled.write(&datestr[0],14); //RTCデータを指定
oled.println("");
oled.write(&datestr[15],14);
setTextSize()関数でフォントの大きさを指定します。1を指定すると5×7のフォントになります。2以上を指定すると1のフォント基準に倍数したフォントになります。0を指定すると1と同じフォントになります。
setCursor()関数で文字表示する座標を指定します。第1引数にX座標、第2引数にY座標を指定します。座標はピン側を上に見た時、OLEDの左上の座標が(X,Y)=(0,0)になります。
setTextColor()関数はテキストの色を指定します。16ビットの値(0(白)~65535(黒))で色を指定します。
write()関数で文字を書き込みます。第1引数に文字列を格納した変数のアドレスを指定し、第2引数にサイズを指定します。
print()関数は指定した引数に対して文字列に変換して書き込みます。println()関数は文字列の最後に改行コードを挿入して書き込みます。
RTCライブラリを使用する
RTCライブラリの使い方を下記記事まとめています。
ここではライブラリの使用方法とRTC時刻を更新する電文について説明します。
初期化処理を実装する
#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のカウントの動作不良の原因になるため注意が必要です。
PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!
RTCから時刻の取得
RTCTime now;
if( RTC.getTime(now) ){
Serial.println(now);
}
2行目のgetTime()関数を使用すると引数で指定したRTCTimeクラスの変数に取得した時刻が格納されます。
例ではインスタンス化したnowに取得したデータが格納するようにしています。nowをシリアルモニターに表示すると「2025-09-21T21:38:16」のように時刻の表示ができます。
RTCTimeクラスの変数から文字列を生成してLCDやOLEDの表示用のデータを生成する場合は、以下のメンバー関数を使用すると便利です。
getYear()関数、getMonth()関数、getDayOfMonth()関数、getHour()関数、getMinutes()関数、getSeconds()関数はそれぞれ、年月日時分秒を取得します。月はMonthの型をMonth2int()関数を使って1~12の値に変換します。
DayOfWeek2int()関数は取得した曜日の値を整数に変換するRTCライブラリの関数です。第1引数にgetDayOfWeek()関数で取得した値を指定します。第2引数は日曜日を先頭にするかのフラグです。trueを指定するとMONDAYが先頭でfalseを指定するとSUNDAYが先頭になります。
自作の電文による時刻補正

シリアルモニターを使ってRTCの時刻を自作の電文を送信して書き込みます。電文の構成はヘッダーに「RTC」の文字列に年から秒を「-」(ハイフン)で繋げたものを電文として生成します。
例のようにシリアルモニターで「RTC2025-08-10-00-12-00-00」を文字列で送信するとUNOR4-WiFiで電文を判断して時刻を書き込みます。曜日は00が日曜日とします。
PR:RUNTEQ(ランテック )- マイベスト4年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール
動作確認

UNOR4-WiFiのSPIライブラリはD13にRSPCKA(SCL)、D11にCOPIA(SDA)、DOにSSLA0(CS)が割り振られています。CSはDOでON/OFFを切り替えて使用します。
OLEDに信号を送信するためDCピン用のDO、RES(リセット)用のDOを準備しています。これらのピンはライブラリでON/OFFします。
電源を投入すると初期のRTC時間をOLEDとシリアルモニターに1秒ごとに表示しながら電文を待機します。RTCから時刻が取得できた時はボード上のLED(シルク印刷でL)をブリンクさせて通知します。
シリアルモニターで電文を送信して初期の時刻を変更します。シリアルモニターでポートを選択します。USBモジュールを挿入して生成されたCOMポートを選択します。ボーレートはデフォルトの115200bpsを選択します。

シリアルモニターで「RTC2025-11-16-00-22-22-30」を文字列で送信するとUNOR4-WiFiで電文を判断して時刻を書き込みます。書き込みに成功すると2025年11月16日(日)22:22:30から時刻の更新がスタートします。

シリアルモニターで送信した電文の時刻からスタートし、OLEDの表示が更新されていることが確認できました。OLEDライブラリはSPIライブラリと連動して動作するためSPI通信がうまくいっていることが確認できました。
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。
#include "RTC.h"
#include <Adafruit_SSD1331.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
#define CS 10
#define RST 9
#define DC 8
// Color definitions
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
struct RING_MNG{
uint8_t wp;
uint8_t rp;
uint8_t buf[RING_SZ];
};
Adafruit_SSD1331 oled = Adafruit_SSD1331(&SPI, CS, DC, RST);
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);
oled.begin();
oled.fillScreen(BLACK);
}
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);
OledShow();
}
//digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
}
/* OLED表示*/
void OledShow(void){
oled.fillScreen(BLACK);
oled.setTextSize(1); //5×7フォント
oled.setCursor(0, 0);
oled.setTextColor(WHITE);
oled.write(&datestr[0],14);
oled.println("");
oled.write(&datestr[15],14);
}
/* 曜日文字列生成 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で学べるソフト開発と標準ライブラリの使い方
最後まで、読んでいただきありがとうございました。
