Raspberry Pi Picoでアラーム付き時計 ~ ブレッドボードではんだ付けなし工作

2024年3月29日

最近は老眼のため、はんだ付けが辛いです。そこで今回はブレッドボードを使い、実用性を考慮したアラーム時計を作ってみます。

具体的に説明すると、Raspberry Pi PicoとOLED、タクトスイッチをブレッドボードに貼り付けて配線を行い、時計パッケージにしました。ブレッドボードを使うため、作成したパッケージが気に入らなければ各部品を外すだけで再度利用できる利点もあります。

それでは、実際にやってみましょう。

※Wi-Fiが必要なので、Raspberry Pi Pico Wを用意してください。

1.ブレッドボードに部品を配置する

ブレッドボード上に、Raspberry Pi Picoと0.96インチOLED、ボタン3つ、ブザーを配置します。今回は3Vで動くブザーが手元にないので、配置していません(LEDで代用します)。

Raspberry Pi Picoを、上段と下段に跨るように刺します。ブレッドボードは、上段と下段が電気的に分かれており、上段、下段それぞれ縦5つのスロットが電気的に繋がっています※。OLEDとボタンは、OLEDを上段、ボタンを下段に配置しました。

※今回使用したブレッドボードの仕様です。

そして、全ての配線をジャンパーケーブルにすると線だらけで使いにくいので。電源だけは銅線で接続しました。具体的には、ブレッドボードの上段+に5V出力を配線し、下段+には3V出力を配線しました。また、GNDを上段・下段の-に配線しています。

2.ジャンパーケーブルで仮接続する

配線にはジャンパーケーブルを使用します。

ボタン (タクトスイッチ)

ボタンの物理的な配置は、左が時計・アラーム設定切り替え、中央が時設定、右が分設定です。そして、それぞれのボタンの+側を、Raspberry Pi PicoのGPIOに接続します。(ボタンには極性がないので、どちらかの片側で良い)

時計・アラーム設定切り替え:GP04
時:GP02
分:GP03

また、それぞれのボタンのマイナス側を、GNDに接続します。ボタンは、プルアップで扱います(押さない時がHIGHで押すとLOWになる)。

created by Rinker
KKHMF
¥599 (2024/11/18 01:57:46時点 Amazon調べ-詳細)

0.96インチOLED

最近はI2C接続の0.96インチOLEDディスプレイばかり使っています。それは、使用するドライバがSSD1306で、ライブラリが豊富だからです。

SDA:GP16
SCL:GP17
VCC:ブレッドボードの上段+
GND:ブレッドボードの上段-

指定したSDA/SCL用のGPIOは、配線するときにベストと思ったからであり、他の使えるGPIOでも構いません。

ブザー

これは9V動作のブザー

3Vで鳴るブザーを用意してください。そして、GPIO5とブザーの+側を接続し、ブザーのマイナス側をブレッドボードの上段-に接続します。今回は、手持ちのブザーが9V用しかなかったため、LEDを光らせています。

3.プログラム

プログラムは、ChatGPTに作ってもらおうと思いました。作ってもらう設計書は以下とおりです。

Arduino IDEでRaspberry Pi Pico Wで動作する時計プログラムを作成する。
一つだけアラームを設定できるようにする。タクトボタンを2つ用意して、時間と分を指定できるようにする。押し続けると数値が上がり、時間が23時になると次は0に戻るようにする。分も59を超えると0に戻るようにする。
もう一つタクトスイッチを用意して、時計モードとアラーム設定モードを切り替える。アラーム設定モードでアラーム時刻を指定する。0時0分の場合はアラームが設定されていないものとする。
ディスプレイは、SSD1306ドライバを使う0.96インチOLEDディスプレイを使う。表示は、時、分の表示と、アラームモードの場合は「ALM」と小さく表示する。
時間の取得は、インターネットから取得する。SSIDとパスワードは、XXXXXでコメントにしておいてください。

これでひな型を作成してもらったのですが、アラームの設定部分以外は大幅修正することになりました。これはChatGPT側の問題ではなく、私の指示が良くなかったからでしょう。プログラムの指示をもう少し上手に行わないと、的確なプログラムコードは得られないと思いました。

今回、使用するボードは、「Earle F. Philhower」さんの方を使います。また、時間を提供してくれるサーバーの指定にNTPクラスを使いました。これらは、ChatGPTが作ったプログラムややM5Stackの時とは違っています。いろいろ試行錯誤して辿り着きました。

最終的に作成したコードは以下のとおりです。

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_RESET -1

#include <WiFi.h>
#include <time.h>

const char* NTPSRV = "ntp.jst.mfeed.ad.jp";  // NTPサーバーアドレス

const char* ssid = "XXXXXXXXXXXXX"; // Your WiFi SSID
const char* password = "XXXXXXXXXXXX"; // Your WiFi password

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// アラーム関係
const int buttonHourPin = 2;    // Hour button connected to pin 2
const int buttonMinutePin = 3;  // Minute button connected to pin 3
const int buttonModePin = 4;    // Mode button connected to pin 4
const int buzzerPin = 5;        // Buzzer connected to pin 5
bool alarmSetMode = false;
int alarmHour = 0 ;
int alarmMinute = 0 ;
bool alarmTriggered = false;

void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);
  
  pinMode(buttonHourPin, INPUT_PULLUP);   // 時 
  pinMode(buttonMinutePin, INPUT_PULLUP); // 分
  pinMode(buttonModePin, INPUT_PULLUP);   // 時計 / アラーム設定
  pinMode(buzzerPin, OUTPUT) ;    // アラーム出力

  Wire.setSDA(16);
  Wire.setSCL(17);
  Wire.begin();

  while (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    delay(1000);
  } 

  display.setRotation(2) ;  // OLEDの向きを180度回転

  delay(1000);

  setup_wifi() ;  // Wi-Fi設定

  NTP.begin(NTPSRV);  // NTPサーバとの同期
  NTP.waitSet();      // 同期待ち

  delay(1000);

}

void loop() {
  // put your main code here, to run repeatedly:
  char buff[256] ;

  // 時計 / アラーム設定切り替え
  if (digitalRead(buttonModePin) == LOW) {
    alarmSetMode = !alarmSetMode; // Toggle alarm set mode
    delay(300); // Debouncing delay
  }

  if (alarmSetMode) {
    // アラーム設定
    setAlarmTime();
  } else {
    // 時計の表示

    time_t now = time(nullptr);                     // 時刻の取得
    now += (time_t)(60 * 60 * 9) ;                  // 日本時間に

    struct tm timeinfo;                             // tm(時刻)構造体の生成
    
    gmtime_r(&now, &timeinfo);                      // time_tからtmへ変換

    int hours = timeinfo.tm_hour ;
    
    int minutes = timeinfo.tm_min ;
    int seconds = timeinfo.tm_sec ;

    int months = timeinfo.tm_mon + 1 ;
    int days = timeinfo.tm_mday ;
    int years = timeinfo.tm_year + 1900 ; 

    sprintf(buff, "%02d:%02d\n%02d/%02d/%04d", hours, minutes, months, days, years);

    if (alarmHour == hours && alarmMinute == minutes && !alarmTriggered && 
        !(alarmHour == 0 && alarmMinute == 0)) {
      alarmTriggered = true;  // 毎日有効にするならfalseにしてください
      tone(buzzerPin, 1000, 10000); // Sound the buzzer for 10 seconds
    }

    display.clearDisplay();

    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE); 
    display.setCursor(0,0); 
    display.println(buff);

    if (!(alarmHour == 0 && alarmMinute == 0) && !alarmTriggered) {
      display.setTextSize(1);
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(80, 0);
      display.println("ALM");
    }

    display.display();

    delay(500);
  }

}

void print_wifi_state(){
  delay(100);

  Serial.println("Wi-Fi接続完了");
  Serial.println(WiFi.localIP());

  delay(100);
 
}

void setup_wifi(){
 
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
 
  Serial.println("\nWi-Fi 接続開始");

  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
  }
 
  print_wifi_state();

}

void setAlarmTime() {
  static int prevHourButtonState = HIGH;
  static int prevMinuteButtonState = HIGH;
  int hourButtonState = digitalRead(buttonHourPin);
  int minuteButtonState = digitalRead(buttonMinutePin);

  if (hourButtonState == LOW && prevHourButtonState == HIGH) {
    alarmHour = (alarmHour + 1) % 24;
    delay(300); // Debouncing delay
  }
  if (minuteButtonState == LOW && prevMinuteButtonState == HIGH) {
    alarmMinute = (alarmMinute + 10) % 60;
    delay(300); // Debouncing delay
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.print("Set Alarm: ");
  display.print(alarmHour);
  display.print(":");
  display.println(alarmMinute);
  display.display();

  prevHourButtonState = hourButtonState;
  prevMinuteButtonState = minuteButtonState;
}

4.動作結果は?

時刻の表示は、なかなか良いです。Raspberry Pi Picoを電源に接続すると、Wi-Fiに接続が行われ、その後時間が表示されます。Wi-Fi接続に少し時間がかかるときがあります。ここは、経過を画面表示した方が良さそうです。

また、時計モードとアラーム設定モードの切り替えがスムーズでありません。これは、スリープ時間が長くてタイミングが合わないせいです。この調整は必要でしょう。アラーム時刻の設定は、いい感じで設定できます。ただし、0時0分が無効という仕様なので、これが嫌な人は適度に変えてください。

アラームは1回鳴ると終わりにしていますが、alarmTriggeredフラグを立てなければ毎日有効です。

5.実用性は?

Wi-FiのSSIDやパスワードがプログラム内で定義されていますので、一般的なパッケージにするには設定画面を用意するなど、もう一手間が必要でしょう。それでも、個人的にはこれで十分なくらいです。

普段は電源から外しておいて、使いたい時だけ電源に接続すれば使えることが今回のアラーム付き時計の利点だと思います。

6.基板に置き換える?

今回の作成をもとに、基板でしっかりとしたパッケージを作ってみたいですが、冒頭にお話しした通り、老眼で作成にストレスが溜まってしまうため、なかなか乗り気になれません。しかしながら、若い皆さんでしたら、さらに機能アップさせて、使いやすい時計を作成してみてはいかがでしょうか。