プログラムを参考までに記載しました。

自由に使ってください。

 

 

 

 

 

(ArduinoIDEを使ってマイコンに書き込みます)

//MP3 VOICE CLOCK(wifiで時刻自動取得)
//2026.1.26 konan0119
#include <WiFi.h>
#include <time.h>
#include <SPI.h>
#include <SD.h>
#include <RTClib.h>
#include <AudioFileSourceSD.h>
#include <AudioGeneratorMP3.h>
#include <AudioOutputI2S.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// ===== RTC / MP3 =====
RTC_DS3231 rtc;
AudioGeneratorMP3 *mp3 = nullptr;
AudioFileSourceSD *file = nullptr;
AudioOutputI2S *out = nullptr;
const int SD_CS = 5;
unsigned long lastPlayTime = 0;
unsigned long lastAlarmTime = 0;   // ★ アラーム後の時報抑制用
int sw;
const int VISIBLE_LINES = 4;  // OLED に表示できる行数
int startIndex = 1;           // 表示開始位置(1〜ALARM_SOUND_COUNT)

// ===== WiFi / NTP =====
const char* ssid     = "your ssid";
const char* password = "your password";
const char* ntpServer = "ntp.nict.jp";
const long  gmtOffset_sec = 9 * 3600;
const int   daylightOffset_sec = 0;
// ===== 曜日表示 =====
const char* weekJP[7] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
// ===== アラーム設定 =====
int alarmHour = 7;//初期時刻
int alarmMinute = 30;//初期分
bool alarmEnabled = true;
int alarmSound = 1;
const int ALARM_SOUND_COUNT = 8;//アラームMP3ファイル保存数
// ===== 音量設定 =====
float chimeVolume = 0.4;   // 時報音量
float alarmVolume = 0.4;   // アラーム音量
// ===== モード =====
enum Mode {
  NORMAL,
  SET_HOUR,
  SET_MIN,
  SET_ONOFF,
  SET_SOUND_LIST,
  SET_CHIME_VOL,
  SET_ALARM_VOL
};
Mode mode = NORMAL;
// ===== ボタン =====
#define BTN_MODE 32
#define BTN_UP   33
bool btnModePressed() { return digitalRead(BTN_MODE) == LOW; }
bool btnUpPressed()   { return digitalRead(BTN_UP) == LOW; }
// ===== 太字1文字描画 =====
void drawBoldCharFixed(int x, int y, char c) {
  oled.setCursor(x, y);
  oled.print(c);
  oled.setCursor(x + 1, y);
  oled.print(c);
}
// ===== 時・分(大きく太字)=====
void drawHourMinute(int x, int y, int hour, int minute) {
  oled.setTextSize(3);
  oled.setTextColor(SSD1306_WHITE);
  char buf[6];
  sprintf(buf, "%02d:%02d", hour, minute);
  int w = 20;
  int cursorX = x;
  for (int i = 0; i < 5; i++) {
    drawBoldCharFixed(cursorX, y, buf[i]);
    cursorX += w;
  }
}
// ===== 秒(小さく)=====
void drawSecondSmall(int x, int y, int second) {
  oled.setTextSize(2);
  oled.setTextColor(SSD1306_WHITE);
  char buf[3];
  sprintf(buf, "%02d", second);
  oled.setCursor(x, y + 6);
  oled.print(buf);
}
// ===== NTP → RTC =====
void syncRTCfromNTP() {
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  struct tm timeinfo;
  if (getLocalTime(&timeinfo)) {
    rtc.adjust(DateTime(
      timeinfo.tm_year + 1900,
      timeinfo.tm_mon + 1,
      timeinfo.tm_mday,
      timeinfo.tm_hour,
      timeinfo.tm_min,
      timeinfo.tm_sec
    ));
  }
}
//MP3 の再生時間(秒)を取得する関数
int getMP3Duration(String path) {
  if (!SD.exists(path)) return 0;
  File f = SD.open(path);
  if (!f) return 0;
  // MP3ヘッダを探す
  while (f.available()) {
    uint8_t b1 = f.read();
    if (b1 == 0xFF) {
      uint8_t b2 = f.read();
      if ((b2 & 0xE0) == 0xE0) {
        // MPEG1 Layer3 の場合
        uint8_t b3 = f.read();
        uint8_t b4 = f.read();
        int bitrateIndex = (b3 >> 4) & 0x0F;
        int sampleRateIndex = (b3 >> 2) & 0x03;
        static const int bitrates[] = {
          0, 32, 40, 48, 56, 64, 80, 96,
          112, 128, 160, 192, 224, 256, 320, 0
        };
        static const int samplerates[] = {
          44100, 48000, 32000, 0
        };
        int bitrate = bitrates[bitrateIndex] * 1000;
        int samplerate = samplerates[sampleRateIndex];
        if (bitrate == 0 || samplerate == 0) break;
        // ファイルサイズから再生時間を計算
        int fileSize = f.size();
        int durationSec = fileSize * 8 / bitrate;
        f.close();
        return durationSec;
      }
    }
  }
  f.close();
  return 0;
}
// ===== MP3アラーム再生用 =====
void playMP3(String path) {
  if (!SD.exists(path)) return;
  int duration = getMP3Duration(path);
  if (duration <= 0) duration = 5;  // 安全値
  unsigned long timeout = duration * 1000 + 500;  // 余裕を持たせる
  file = new AudioFileSourceSD(path.c_str());
  mp3 = new AudioGeneratorMP3();
  if (!mp3->begin(file, out)) {
    delete file; file = nullptr;
    delete mp3; mp3 = nullptr;
    return;
  }
  unsigned long start = millis();
  while (mp3->isRunning()) {
    mp3->loop();
    if (millis() - start > timeout) break;
  }
  mp3->stop();
  delete file; file = nullptr;
  delete mp3; mp3 = nullptr;
}
// ===== MP3時報再生用 =====
void playMP32(String path) {
  if (!SD.exists(path)) return;
  file = new AudioFileSourceSD(path.c_str());
  mp3 = new AudioGeneratorMP3();
  if (!mp3->begin(file, out)) {
    delete file; file = nullptr;
    delete mp3; mp3 = nullptr;
    return;
  }
unsigned long start = millis();
unsigned long timeout = 0;
switch (sw) {
  case 1:
    timeout = 1200;
    break;
  case 2:
    timeout = 5000;//アラーム設定時の再生時間
    break;
}
Serial.println(timeout);
Serial.println("");
  while (mp3->isRunning()) {
    mp3->loop();
    if (millis() - start > timeout) break;
  }
  mp3->stop();
  delete file; file = nullptr;
  delete mp3; mp3 = nullptr;
}
// ===== 設定画面 =====
void drawSettingScreen() {
  oled.clearDisplay();
  oled.setTextColor(SSD1306_WHITE);
  oled.setTextSize(2);
  if (mode == SET_HOUR) {
    oled.setCursor(0, 0);
    oled.print("Alarm Hour");
    oled.setCursor(0, 30);
    oled.printf("%02d", alarmHour);
  }
  else if (mode == SET_MIN) {
    oled.setCursor(0, 0);
    oled.print("Alarm Min");
    oled.setCursor(0, 30);
    oled.printf("%02d", alarmMinute);
  }
  else if (mode == SET_ONOFF) {
    oled.setCursor(0, 0);
     oled.print("Alarm");
    oled.setCursor(30, 15);
    oled.print("Swich");
    oled.setCursor(0, 45);
    oled.print(alarmEnabled ? "ON" : "OFF");
  }
    else if (mode == SET_SOUND_LIST) {
  // 選択位置に応じて startIndex を調整
  if (alarmSound < startIndex) {
    startIndex = alarmSound;
  } else if (alarmSound >= startIndex + VISIBLE_LINES) {
    startIndex = alarmSound - VISIBLE_LINES + 1;
  }
  oled.clearDisplay();
  oled.setTextSize(1);
  oled.setCursor(0, 0);
  oled.print("Select Alarm Sound");
  // 表示するのは startIndex から最大 VISIBLE_LINES 行
  for (int i = 0; i < VISIBLE_LINES; i++) {
    int index = startIndex + i;
    if (index > ALARM_SOUND_COUNT) break;
    oled.setCursor(0, 15 + i * 12);
    if (index == alarmSound) oled.print("> ");
    else oled.print("  ");
    oled.printf("%d: alarm%d.mp3", index, index);
  }
  oled.display();
}
  else if (mode == SET_CHIME_VOL) {
    oled.setTextSize(2);
    oled.setCursor(0, 0);
    oled.print("Chime Vol");
    oled.setCursor(0, 30);
    oled.printf("%.1f", chimeVolume);
  }
  else if (mode == SET_ALARM_VOL) {
    oled.setTextSize(2);
    oled.setCursor(0, 0);
    oled.print("Alarm Vol");
    oled.setCursor(0, 30);
    oled.printf("%.1f", alarmVolume);
  }
  oled.display();
}
// ===== セットアップ =====
void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);
  pinMode(BTN_MODE, INPUT_PULLUP);
  pinMode(BTN_UP, INPUT_PULLUP);
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  oled.clearDisplay();
  oled.display();
  WiFi.begin(ssid, password);
  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED) {
    if (millis() - start > 15000) break;  // ★ WiFiタイムアウト
    delay(200);
  }
  rtc.begin();
  if (WiFi.status() == WL_CONNECTED) {
    syncRTCfromNTP();
  }
  SD.begin(SD_CS);
  out = new AudioOutputI2S();
  out->SetGain(0.4);
}
// ===== メインループ =====
void loop() {
  DateTime now = rtc.now();
  int year = now.year();
  int month = now.month();
  int day = now.day();
  int hour = now.hour();
  int minute = now.minute();
  int second = now.second();
  int wday = now.dayOfTheWeek();
  // ===== 設定モード =====
  if (mode != NORMAL) {
    drawSettingScreen();
    if (btnUpPressed()) {
      if (mode == SET_HOUR) alarmHour = (alarmHour + 1) % 24;
      else if (mode == SET_MIN) alarmMinute = (alarmMinute + 1) % 60;
      else if (mode == SET_ONOFF) alarmEnabled = !alarmEnabled;
      else if (mode == SET_SOUND_LIST) {
        alarmSound++;
        if (alarmSound > ALARM_SOUND_COUNT) alarmSound = 1;
        sw=2;
        out->SetGain(alarmVolume);
        String file = "/alarm/alarm" + String(alarmSound) + ".mp3";
        playMP32(file);
      }
      else if (mode == SET_CHIME_VOL) {
        chimeVolume += 0.1;
        if (chimeVolume > 1.0) chimeVolume = 0.1;
      }
      else if (mode == SET_ALARM_VOL) {
        alarmVolume += 0.1;
        if (alarmVolume > 1.0) alarmVolume = 0.1;
      }
      delay(200);
    }
    if (btnModePressed()) {
      if (mode == SET_HOUR) mode = SET_MIN;
      else if (mode == SET_MIN) mode = SET_ONOFF;
      else if (mode == SET_ONOFF) mode = SET_SOUND_LIST;
      else if (mode == SET_SOUND_LIST) mode = SET_CHIME_VOL;
      else if (mode == SET_CHIME_VOL) mode = SET_ALARM_VOL;
      else if (mode == SET_ALARM_VOL) mode = NORMAL;
      delay(300);
    }
    return;
  }
  // ===== 通常画面 =====
  if (btnModePressed()) {
    mode = SET_HOUR;
    delay(300);
  }
  oled.clearDisplay();
  oled.setTextColor(SSD1306_WHITE);
  oled.setTextSize(2);
  oled.setCursor(0, 0);
  oled.printf("%04d/%02d/%02d     (%s)", year, month, day, weekJP[wday]);
  drawHourMinute(0, 32, hour, minute);
  drawSecondSmall(100, 32, second);
  oled.setTextSize(1);
  oled.setCursor(0, 55);
  oled.printf("Alarm Set:%02d:%02d %s S%d  ",
              alarmHour, alarmMinute,
              alarmEnabled ? "ON" : "OFF",
              alarmSound );
  oled.display();
  // ===== アラーム後5秒は時報を無効化 =====
  bool skipChime = (millis() - lastAlarmTime < 5000);
  // ===== 時報 =====
  if (!skipChime &&
      minute % 10 == 0 &&
      second == 0 &&
      millis() - lastPlayTime > 10000) {
      out->SetGain(chimeVolume);
      lastPlayTime = millis();
      sw=1;
    if (minute == 0) {
      playMP32("/voice/jihou1.mp3");
      playMP32("/voice/hh" + String(hour) + ".mp3");
    } else {
      playMP32("/voice/jihou2.mp3");
      playMP32("/voice/h" + String(hour) + ".mp3");
      playMP32("/voice/m" + String(minute) + ".mp3");
    }
  }
  // ===== アラーム発動 =====
  if (alarmEnabled &&
      hour == alarmHour &&
      minute == alarmMinute &&
      second == 0) {
    out->SetGain(alarmVolume);
    String file = "/alarm/alarm" + String(alarmSound) + ".mp3";
    playMP3(file);
    lastAlarmTime = millis();   // ★ 時報抑制開始
  }
  delay(200);
}

趣味の内容を紹介していきたいと思います。

今回は時計をやっと完成させました。

電源投入後、wifiで時刻を取得し10分毎に時刻をアナウンス

してくれる時計です。

試行錯誤しながら結構な時間を費やしました。

ケースは3Dプリンターで作りました。

細かい内容は、動画を作って紹介したいと思います。

今まで作ってきた内容他もあります。興味あればぜひ

YOUTUBEで見てください。