PR

Arduino UNO R4 WiFiのRTC時刻をLCDに表示する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

Arduino UNO R4 WiFiはRA4M1マイコンをメインに搭載しているためRTCを実装することができます。RTCライブラリで時刻を取得し、Wireライブラリ(I2C通信)でLCDに時刻を表示する方法をまとめました。

RTCライブラリで時刻を取得する方法を下記記事にまとめています。

Arduino UNO R4 WiFiのRTCを使用する

取得した時刻をWireライブラリ(I2C通信)でLCDに表示します。LCDはAQM1602XA-RN-GBW(秋月電子)を使用しています。

以下ではArduino UNO R4 WiFiをUNOR4-WiFiと表記します。Arduinoのライブラリを使用して動作確認したことをまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Wireライブラリを使用する

Wire(I2C通信)はSCLとSDAの2本の信号線で行う通信です。プルアップ抵抗と信号線の長さによって通信速度が決まります。プルアップ抵抗を大きくすると信号線のL分と抵抗により信号の波形がなまってしまい通信不良の原因となるため注意が必要です。

プルアップ抵抗は1kΩから10kΩで使用され、Arduino環境のデフォルトである100kHzで通信できることが多いですが、通信相手の仕様を確認する必要があります。

I2C通信は基本的にマスタ(クロックを出す側)がスレーブ(クロックを受けて動作する側)に対してクロックを供給する関係になります。

本記事では、UNOR4-WiFiがマスタでLCDがスレーブになります。以下ではWireのメンバー関数を赤文字で表記します。

Wireライブラリの初期化

#include <Wire.h>

void setup() {

  Wire.begin(); //begin()内にアドレスを入れるとスレーブになる
  //Wire.setClock(I2C_MASTER_RATE_FAST); //400kHzに変更する場合
}

Wire(I2C)を使用するためにWire.hをインクルードします。begin()関数でI2C通信の初期化を行います。引数を指定しない場合はマスタで動作します。引数を指定するとスレーブで動作します。

setClock()関数でクロックを指定することができます。I2C_MASTER_RATE_FAST(直接400000を指定してもよい)を指定すると400kHzに変更することができます。

Arduino環境ではデフォルトのクロックは100kHzです。引数に指定した値によっては誤差が大きくなり通信エラーの原因になるため注意が必要です。

PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!

Wire(I2C)の送信と実装例

Wireライブラリを使った送信の手順は以下の通りです。

  1. beginTransmission()関数で初期化とスレーブアドレスをセット
  2. write()関数で書き込み対象のアドレスをセット
  3. write()関数で書き込むデータをセット
  4. endTransmission()関数で送信

1~3まではデータの準備です。4のendTransmission()関数はスタート・コンディションの発行やコントロール・バイト(7ビットのスレーブアドレスを左詰めにして最下位ビットにWriteフラグ(0)をセット)の処理を行いデータを送信した(書き込んだ)後、ストップ・コンディションの発行を行います。

/* LCDへのコマンド処理 */
void LcdComand(uint8_t cmd){

    Wire.beginTransmission(SLAVE_ADRS);
    Wire.write(0x00);
    Wire.write(cmd);
    Wire.endTransmission();
    delay(20);
}

LCDの初期化を行うコマンドの実装例を示します。最初にbeginTransmission()関数で初期化とスレーブアドレスをセットします。

次にコントロール・バイトの0x00をセットします。続けて初期化するコマンドをwrite()関数でセットします。

最後にendTransmission()関数でスタート・コンディションからストップ・コンディションまでを含めたデータを送信します。

PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!

LCDの初期化

引用:LCD資料(参考資料)ー秋月電子HP(AQM1602XA-RN-GBW)
引用:LCD資料(参考資料)ー秋月電子HP(AQM1602XA-RN-GBW)

Wireでコマンドを送信しLCDの初期化を行います。LCDの初期化の例は下記記事にまとめています。

Seeeduino XIAOを使ってBME280のデータをLCDに表示する

LCDで文字を表示するために初期化を行います。秋月電子のHPに公開されているAQM1602XA-RN-GBWのLCD資料(参考資料)の例を引用します。

LcdComand()の引数にコマンドを指定してLCDを初期化する例は以下の通りです。

/* LCD初期化 */
void LcdInit(void){
 
    LcdComand(FUNC1_SET); //8ビットバス・2LINE表示
    LcdComand(FUNC2_SET); //拡張コマンド
    LcdComand(INT_OSC); //内部周波数調整
    LcdComand(CONST_SET); //コントラスト1
    LcdComand(PWR_ICON_SET); //コントラスト2
    LcdComand(FOLLOWER_SET); //フォロワー制御
    LcdComand(FUNC1_SET); //拡張コマンドをオフ
    DspClear();
    LcdComand(DISP_ONOFF_SET);
    //初期の文字を表示
    sleep_ms(2000);
}

初期化の例に従ってコマンドを送信して初期化を行います。UNOR4-WiFiはDC5Vで動作しているためコントラストデフォルトが濃くなってしまうことがあります。

コントラスト設定を薄くしようとC5を0にしてみましたが文字が見えなくなるほど薄くなってしまうためContrast Setに0x70、Power/ICON/Contrast control(Bonをセットするとブリンクでセルが黒くなってしまうので0をセット)に0x52をセットしました。

ST7032のデータシートを確認するとI2C使用時のレイアウトではOPF1、OPF2は0とするためFonに1をセットすると内部フォロアが有効になります。コントラストは内部フォロアも関係するためFollower controlのRab0~Rab3の調整が有効なので、Follower controlに0x6Bをセットしました。

LCDに文字を表示

/* LCDに文字を表示 */
void LcdWriteData(uint8_t *data, uint8_t sz){

    Wire.beginTransmission(SLAVE_ADRS);
    Wire.write(0x40);
    Wire.write(data, sz);
    Wire.endTransmission();
}

LCDに文字を書き込むためLcdWriteData()関数を実装します。第1引数に表示する文字を示すアドレス、第2引数にサイズを指定します。

引用:ST7032のデータシート(2-line Interface protocol)
引用:ST7032のデータシート(2-line Interface protocol)

beginTransmission()関数でスレーブアドレスをセットした後にcontrol byteを指定しますが2-line interface protocolに従って書き込みアドレスと文字データを指定します。

control byte以降に制御コードを書き込まないのでCoに0をセットしRSがHになるので0x40になります。R/WはW固定なのでLになります。

control byteの後に文字列をセットしてサイズ分書き込み、最後にSTOP状態の通知のためendTransmission()関数で送信します。

datestr[]配列の文字列を表示する例は以下の通りです。

datestr[]={"2025/08/10 SUN 12:00:00"}; //生成した時間の文字列
/* LCDの表示生成 */
void despset(void){

  DspClear(); //LCDをクリア
  LcdWriteData( (uint8_t*)&datestr[0], 14); //1段目の表示
  DspLine2Top(); //2段目にカーソル移動
  LcdWriteData( (uint8_t*)&datestr[15], 8); //2段目の表示
}

RTCから読み出した時刻から文字列を生成してdatestr[]配列に格納しています。

LcdWriteData()関数の第1引数にdatestr[]のアドレスを指定し、第2引数に表示する文字数を指定します。LCDの1段目は、datestr[0]から14バイトの文字を指定して表示します。LCDの2段目はdatestr[15]から8バイトの文字を指定して表示します。

PR:RUNTEQ(ランテック )- マイベスト4年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール

動作確認

動作確認用の回路図
動作確認用の回路図

UNOR4-WiFiのWireライブラリではA4とA5ピンがSDA、SCLに割り振られているためLCDのSDA、SCLに接続します。I2CはSCLとSDAはプルアップ(1kΩ~10kΩ)する必要がありますが、AQM1602XA-RN-GBWに付属しているプルアップを使用しています。

LCDは最大400kHzに対応した通信ができますが、Arduino環境のデフォルト値の100kHzとしています。

電源を投入すると2秒間LCDの1段目に「UNO R4 WiFi RTC」、2段目に「Ver0.01」を表示します。その後、初期のRTC時間をLCDとシリアルモニターに1秒ごとに表示しながら電文を待機します。RTCから時刻が取得できた時はボード上のLED(シルク印刷でL)をブリンクさせて通知します。

自作の電文の構成
自作の電文の構成

時刻変更の電文をシリアルモニターで送信します。電文の構成はヘッダーに「RTC」の文字列に年から秒を「-」(ハイフン)で繋げたものです。

シリアルモニターで電文を送信して初期の時刻を変更します。シリアルモニターでポートを選択します。USBモジュールを挿入して生成されたCOMポートを選択します。ボーレートはデフォルトの115200bpsを選択します。

シリアルモニターの結果(電文の送信)
シリアルモニターの結果(電文の送信)

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

LCDの表示(電文で時刻を書き込んだ結果)
LCDの表示(電文で時刻を書き込んだ結果)

シリアルモニターで送信した電文の時刻からスタートし、LCDの表示が更新されていることが確認できました。

スポンサーリンク

ソースコード全体

ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。

#include "RTC.h"
#include "Wire.h"

#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIM_RTC 90 //1秒ごとに表示するため遅れ分を調整
#define RTC_READ 100
#define RING_SZ 128
#define TIM_RX_WAIT 5   //50ms
#define OFFSET 3

#define SLAVE_ADRS 0x3E
#define LINE1_ADRS 0x40
#define LINE2_TOP (0x40 +0x80)
#define FUNC1_SET 0x38
#define FUNC2_SET 0x39
#define INT_OSC 0x14
#define CONST_SET 0x70 
#define PWR_ICON_SET 0x52
#define FOLLOWER_SET 0x6B
#define CLR_DISP 0x01
#define DISP_ONOFF_SET 0x0C

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;
uint8_t initmoji[2][16] ={"UNO R4 WiFi RTC","        Ver0.01"};

void mainTimer(void);
void mainApp(void);
void WeekSet(int wk );
void rcvdatechk(void);
void ReadPointerAdd(void);
bool chkdata(void);
void LcdInit(void);
void LcdComand(uint8_t cmd);
void LcdWriteData(uint8_t *data, uint8_t sz);
void DspLine2Top(void);
void DspClear(void);
void ShowData(void);

void setup() {

  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
  Wire.begin();
  //Wire.setClock(I2C_MASTER_RATE_FAST); //400kHz
  LcdInit();
  
  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);
      ShowData();
    }
    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;
}

/* LCD初期化処理 */
void LcdInit(void){
 
    LcdComand(FUNC1_SET); //8ビットバス・2LINE表示
    LcdComand(FUNC2_SET); //拡張コマンド
    LcdComand(INT_OSC); //内部周波数調整
    LcdComand(CONST_SET); //コントラスト1
    LcdComand(PWR_ICON_SET); //コントラスト2
    LcdComand(FOLLOWER_SET); //フォロワー制御
    LcdComand(FUNC1_SET); //拡張コマンドをオフ
    DspClear();
    LcdComand(DISP_ONOFF_SET);
    LcdWriteData( &initmoji[0][0], sizeof(initmoji[0])); //1段目の表示
    DspLine2Top(); //カーソル移動
    LcdWriteData( &initmoji[1][0], sizeof(initmoji[1])); //2段目の表示
    delay(2000);
}
/* LCDへのコマンド処理 */
void LcdComand(uint8_t cmd){

    Wire.beginTransmission(SLAVE_ADRS);
    Wire.write(0x00);
    Wire.write(cmd);
    Wire.endTransmission();
    delay(20);
}
/* LCDに文字を表示 */
void LcdWriteData(uint8_t *data, uint8_t sz){

    Wire.beginTransmission(SLAVE_ADRS);
    Wire.write(0x40);
    Wire.write(data, sz);
    Wire.endTransmission();
}
/* 2段目にカーソル移動 */
void DspLine2Top(void){
    
    LcdComand(LINE2_TOP);
    delayMicroseconds(40);
}
/* ディスプレイクリア */
void DspClear(void){
  
    LcdComand(CLR_DISP);
    delayMicroseconds(40);
}

/* ディスプレイ表示 */
void ShowData(void){

    DspClear();
    LcdWriteData( (uint8_t*)&datestr[0], 14); //1段目の表示
    DspLine2Top(); //2段目にカーソル移動
    LcdWriteData( (uint8_t*)&datestr[15], 8); //2段目の表示
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

Raspberry Pi Picoで学べるソフト開発

【クリエイターズファクトリー】卒業がない!挫折する心配なし!Webスクール説明会申し込み

最後まで、読んでいただきありがとうございました。

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