Arduino Uno R4 WiFiで防湿庫・楽器ケース・ワインセラー向け温湿度計測システムを作ってみた
カメラの防湿庫、アコースティックギターなどの楽器ケース、ワインセラーのように、「温度と湿度の状態を見ながら保管したいもの」は意外と身近にあります。見た目では分かりにくくても、湿度が高すぎればカビや劣化の原因になり、逆に乾燥しすぎてもコンディションに悪影響が出ることがあります。
今回も「エレクトロニクスを簡単に」をテーマとしてArduino Uno R4 WiFi と DHT11 温湿度センサー、そして OLEDディスプレイ を使って、収納環境の温湿度をローカル表示できる簡易計測ソリューションを作ってみました。ボタン操作で用途を切り替えられる構成にしており、防湿庫・楽器ケース・ワインセラーのような用途を、その場で切り替えて確認できるようにしました。
注意
今回は PoC として、DHT11 による簡易な温湿度計測を行う構成です。 DHT11 は低コストで扱いやすい一方、精度や応答速度は控えめです。厳密な品質管理や長期保存の本格運用では、より高精度なセンサーも検討したほうが安心です。DHT11 は 20〜80%RH を 5%精度、0〜50℃ を ±2℃精度で扱う基本的なセンサーで、サンプリングも 1Hz までです。
目次
はじめに:保管環境を“見える化”したい
カメラやレンズは湿度が高いとカビやコンディション低下が気になりますし、木材を使う楽器は乾燥しすぎても湿りすぎても状態が崩れやすくなります。ワインもまた、温度や湿度の安定性が保管品質に影響します。ただ、収納庫やケースの中は普段閉じているため、「今どんな状態なのか」が見えにくいのが実際のところです。
そこで今回は、DHT11 で温度・湿度を取得し、OLED Screen に現在値と簡易判定を表示する小さなPoCを作りました。これにより、防湿庫・楽器ケース・ワインセラーのような収納環境について、“なんとなく大丈夫”ではなく、数値で確認するところから始められます。
ローカル表示の基本構成
今回の構成はシンプルです。
・DHT11 が温度と湿度を計測
・OLEDディスプレイ が現在値を表示
・Button を押すと用途モードを切り替え
・用途に応じた簡易評価を表示
・マイコンは Arduino Uno R4 WiFi
・開発は VS Code + PlatformIO
使用した主な部品
・Arduino Uno R4 WiFi
・DHT11 温湿度センサー
・0.96インチ OLEDディスプレイ(SSD1306系 / I2C想定)
・タクトスイッチ(Button)
・抵抗(必要に応じて 10kΩ)
・ブレッドボード
・ジャンパーピン
Arduino Uno R4 WiFi の特徴
Arduino Uno R4 WiFi は、Renesas RA4M1(48MHz Arm Cortex-M4、256KB Flash、32KB SRAM) を搭載し、さらに ESP32-S3 により Wi‑Fi / Bluetooth に対応しています。5V動作で、RTC や DAC なども備えており、ローカル表示のPoCから将来のネットワーク連携まで広げやすいボードです。
今回の温湿度計測ではまず OLED に表示するだけですが、Uno R4 WiFi を使うことで、将来的には クラウド連携、MQTT送信、履歴保存、異常通知 などへ発展させやすいのが魅力です。
用途別の目安
このPoCを「何のために使うか」を考えるために、代表的な保管対象ごとの目安を整理しておきます。
カメラ防湿庫
Sonyの案内では、交換レンズ式カメラの保管では 40〜50%程度の湿度 が望ましいとされています。乾燥剤だけでなく、湿度計を入れて管理することも推奨されています。
楽器ケース
Taylor Guitarsの案内では、ギター用の二方向加湿システムで 45〜50%RH を維持する考え方が紹介されています。木材系の楽器では、乾燥や過湿を避けて相対湿度を安定させることが重要です。
ワインセラー
Stag’s Leap Wine Cellarsでは、ワインの保管温度は 13〜16℃程度 が理想で、湿度は 70%が理想、50〜80%が安全域 とされています。ワインは温度変動にも弱いため、単純に数字だけでなく「安定していること」も重要です。
開発環境と実装のポイント
開発環境には Visual Studio Code と PlaformIO IDE を使いました。PlatformIO では Arduino Uno R4 WiFi のボードIDとして uno_r4_wifi を指定し、プラットフォームには renesas-ra を使えます。
配線情報
各モジュールを以下のように配線します。
モジュール | 信号 | 接続先 | 補足 |
DHT11 | VCC | Arduino Uno R4 WiFiの | DHT系は5V給電の方が安定しやすい場合があります。 |
DHT11 | GND | Arduinoの | 共通GNDにします。 |
DHT11 | DATA | Arduinoの | データ取得用です。 |
DHT11 | プルアップ抵抗 | VCCとDATA間に | bareセンサーでは必要。モジュールによっては内臓済みです。 |
OLED(1306系) | SDA | Arduino Uno R4 WiFiの | I2C通信で使用します。 |
OLED(1306系) | SCL | Arduino Uno R4 WiFiの | I2C通信で使用します。 |
OLED(1306系) | VCC |
| モジュール仕様を確認して接続します。 |
OLED(1306系) | GND | Arduinoの | 共通GNDにします。 |
Button | 片側 | Arduinoの |
|
Button | もう片側 | Arduinoの | 押すと |
実際のプログラム
今回のPoCでは、2秒ごとに DHT11 を読み取り、OLED に温度・湿度・状態を表示します。さらに、Button を押すとモードが切り替わり、用途に応じて評価基準も変わるようにしています。
main.cpp
#include <Arduino.h>
#include <Wire.h>
#include <DHT.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define DHTPIN 2
#define DHTTYPE DHT11
#define BUTTON_PIN 3
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDR 0x3C
DHT dht(DHTPIN, DHTTYPE);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
enum StorageMode {
CAMERA_DRYBOX,
GUITAR_CASE,
WINE_CELLAR
};
StorageMode storageMode = CAMERA_DRYBOX;
unsigned long lastReadTime = 0;
const unsigned long readIntervalMs = 2000; // DHT11は余裕をもって2秒周期
unsigned long lastButtonTime = 0;
const unsigned long debounceMs = 200;
int lastButtonState = HIGH;
struct StatusResult {
String shortLabel;
String targetLabel;
};
const char* getModeName() {
switch (storageMode) {
case CAMERA_DRYBOX: return "CAMERA";
case GUITAR_CASE: return "GUITAR";
case WINE_CELLAR: return "WINE";
default: return "UNKNOWN";
}
}
StatusResult evaluateStatus(float temperature, float humidity) {
StatusResult result;
switch (storageMode) {
case CAMERA_DRYBOX:
if (humidity < 40.0) {
result.shortLabel = "DRY";
result.targetLabel = "40-50%RH";
} else if (humidity > 50.0) {
result.shortLabel = "HUMID";
result.targetLabel = "40-50%RH";
} else {
result.shortLabel = "OK";
result.targetLabel = "40-50%RH";
}
break;
case GUITAR_CASE:
if (humidity < 45.0) {
result.shortLabel = "DRY";
result.targetLabel = "45-50%RH";
} else if (humidity > 50.0) {
result.shortLabel = "HUMID";
result.targetLabel = "45-50%RH";
} else {
result.shortLabel = "OK";
result.targetLabel = "45-50%RH";
}
break;
case WINE_CELLAR:
if (temperature < 13.0 || temperature > 16.0) {
result.shortLabel = "TEMP";
result.targetLabel = "13-16C";
} else if (humidity < 50.0) {
result.shortLabel = "DRY";
result.targetLabel = "50-80%RH";
} else if (humidity > 80.0) {
result.shortLabel = "HUMID";
result.targetLabel = "50-80%RH";
} else {
result.shortLabel = "OK";
result.targetLabel = "13-16C/50-80";
}
break;
}
return result;
}
void drawDisplay(float temperature, float humidity, const StatusResult& status) {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// 1行目: モード
display.setTextSize(1);
display.setCursor(0, 0);
display.print("MODE: ");
display.println(getModeName());
// 2行目: 温度(大きめ)
display.setTextSize(2);
display.setCursor(0, 14);
display.print(temperature, 1);
display.print("C");
// 3行目: 湿度(大きめ)
display.setCursor(0, 34);
display.print(humidity, 0);
display.print("%");
// 4行目: ステータスを横一列で表示
display.setTextSize(1);
display.setCursor(0, 56);
display.print("ST:");
display.print(status.shortLabel);
display.print(" TG:");
display.print(status.targetLabel);
display.display();
}
void showModeChanged() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("Mode Changed");
display.setTextSize(2);
display.setCursor(0, 20);
display.println(getModeName());
display.display();
delay(600);
}
void showError(const char* message) {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("Sensor Error");
display.setCursor(0, 16);
display.println(message);
display.setCursor(0, 32);
display.println("Check wiring");
display.display();
}
void handleButton() {
int currentState = digitalRead(BUTTON_PIN);
unsigned long now = millis();
// HIGH -> LOW の変化で押下判定
if (lastButtonState == HIGH && currentState == LOW) {
if (now - lastButtonTime > debounceMs) {
storageMode = static_cast<StorageMode>((storageMode + 1) % 3);
lastButtonTime = now;
Serial.print("Mode changed to: ");
Serial.println(getModeName());
showModeChanged();
}
}
lastButtonState = currentState;
}
void setup() {
Serial.begin(115200);
delay(200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
dht.begin();
Wire.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
while (true) {
// OLED初期化失敗時は停止
}
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("Temp/Humidity");
display.setCursor(0, 16);
display.println("Initializing...");
display.display();
delay(1000);
}
void loop() {
handleButton();
unsigned long now = millis();
if (now - lastReadTime < readIntervalMs) {
return;
}
lastReadTime = now;
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
if (isnan(humidity) || isnan(temperature)) {
Serial.println("Failed to read from DHT11");
showError("DHT11 read failed");
return;
}
StatusResult status = evaluateStatus(temperature, humidity);
Serial.print("Mode: ");
Serial.print(getModeName());
Serial.print(" / Temp: ");
Serial.print(temperature, 1);
Serial.print(" C / Humidity: ");
Serial.print(humidity, 0);
Serial.print(" % / Status: ");
Serial.println(status.shortLabel);
drawDisplay(temperature, humidity, status);
}
platformio.ini
[env:uno_r4_wifi]
platform = renesas-ra
board = uno_r4_wifi
framework = arduino
monitor_speed = 115200
lib_deps =
adafruit/DHT sensor library
adafruit/Adafruit Unified Sensor
adafruit/Adafruit SSD1306
adafruit/Adafruit GFX LibraryOLED表示の補足
項目 | 表示例 | 意味 |
MODE | CAMERA/GUITAR/WINE | 現在の用途モード |
温度 | 24.5C | 現在温度 |
湿度 | 48% | 現在湿度 |
ST | OK/DRY/HUMID/TEMP | 現在状態の簡易判定 |
TG | 40-50%RHなど | そのモードの目安条件 |
ST / TG 一覧
モード | TG | ST | 判定条件 |
CAMERA | 40-50%RH | DRY | 湿度40%未満 |
CAMERA | 40-50%RH | OK | 湿度40~50% |
CAMERA | 40-50%RH | HUMID | 湿度50%超 |
GUITAR | 45-50%RH | DRY | 湿度45%未満 |
GUITAR | 45-50%RH | OK | 湿度45~50% |
GUITAR | 45-50%RH | HUMID | 湿度50%超 |
WINE | 13-16C/50-80%RH | TEMP | 温度13~16℃の範囲外 |
WINE | 13-16C/50-80%RH | DRY | 温度範囲内かつ湿度50%未満 |
WINE | 13-16C/50-80%RH | OK | 温度範囲内かつ湿度50~80% |
WINE | 13-16C/50-80%RH | HUMID | 温度範囲内かつ湿度80%超 |
Button切り替えの動作イメージ
今回の用途切り替えは、以下のような流れです。
1. 起動直後は CAMERA モード
2. Button を1回押す
3. GUITAR モードへ切り替え
4. さらに Button を押す
5. WINE モードへ切り替え
6. さらに Button を押す
7. CAMERA に戻る
この方式にしておくと、防湿庫の確認から楽器ケースの確認へ、さらにワインセラーの確認へと、1台で複数用途を試せるのが便利です。再書き込み不要で切り替えられるため、PoCとしても扱いやすくなります。
実際にワインセラーに入れてみる
ワインセラーの内部にArduinoごと温湿度センサーを入れてみました。
外にOLEDディスプレイを出しています。
しばらく経ちましたが、ワインセラーの温度計と比較して温度に大きな乖離があります。
実用化を想定して開発する場合は、より高精度な温度センサーが必要となりそうです。
今後の展望
今回のPoCはとてもシンプルですが、実運用を考えると発展の余地があります。
・モード記憶機能
電源を入れ直しても前回モードを保持する
・Wi‑Fiログ送信
Uno R4 WiFi の無線機能を使い、定期的に温湿度を送信して履歴を残す
・しきい値アラート
一定時間以上、湿度が高すぎる・低すぎる状態が続いたら通知する
・複数センサー対応
ケース内の場所によるムラを把握するため、複数ポイントを測る
・高精度センサー化
DHT11 から DHT22 / SHT31 などへ置き換えて精度を上げる
・OLEDメニュー化
Button を複数に増やして、設定変更や履歴表示を行う
特に Uno R4 WiFi は RA4M1 + ESP32-S3 を備えているため、ローカル表示だけで終わらず、将来的な IoT 化との相性が良いのが魅力です。
・Source
無線という観点では、Renesasは近年 Wi-Fi 6 / Bluetooth LE 対応MCU(RA6シリーズ)やコンボモジュール(RRQ61051)も展開しています。 今回の作例でそこまで踏み込んでいるわけではありませんが、IoT機器が「つながること」を前提に設計される流れの中で、より低消費電力・高機能な常時接続デバイスへ広げていく方向性を考える参考になります。
・Wifi/BLE-MCU(RA6シリーズ)
・Wifi/BLEモジュール(RRQ61051)
まとめ
今回は、Arduino Uno R4 WiFiと DHT11、OLED Screen、そして Button を使って、防湿庫・楽器ケース・ワインセラー向けの温湿度計測ソリューションを作ってみました。
構成自体はとてもシンプルですが、
・温湿度を計測する
・OLED に現在値を表示する
・Button で用途モードを切り替える
・用途ごとの目安で簡易判定する
・将来的にログ保存や通知へ広げる
という流れを通して、保管環境を“見える化”する最初の一歩としては十分に面白いPoCになります。
特に今回の Button 切り替え対応によって、コードを書き換えなくてもその場で用途変更できるようになり、PoCとしてかなり扱いやすくなりました。まずは DHT11 でざっくり状態を把握し、必要に応じてセンサー精度やネットワーク機能を拡張していく、という進め方が現実的だと感じました。
参考リンク
・Arduino UNO R4 WiFi 公式ドキュメント
・PlatformIO: Arduino Uno R4 WiFi ボード情報
・Adafruit: Using a DHTxx Sensor with Arduino
・Adafruit: DHT11 / DHT22 センサー解説PDF
・Adafruit: Monochrome OLED Breakouts / Arduino Library & Examples
・Sony: 交換レンズ式カメラの保管方法
・Taylor Guitars: Guitar Humidifier の使い方
・Stag’s Leap Wine Cellars: Proper Cellaring Conditio




