キャンプといえば!という料理は、その方の個性があって多種多様ですが、それでも多くの方が楽しまれるのが、炊飯(炊爨)ではないでしょうか?
美味しくお米を炊くためには、経過時間に応じた火力調節が大事ですよね。
キャンプの場合、ベストな火力調整は難しくて、感覚に頼るか、なり行き任せてやってみることになりがちだと思います。
私は、沸騰状況やフタの動きを目安に、感覚で火力を調節していますが、自分好みの炊き上がりにできるか毎回不安になります。
もしも、自動で火力調整できれば、成功事例を忠実に再現できて安心できる上に、炊飯中の時間は他の作業にも専念できる!と思い、サーボモーターとArduinoを使って、キャンプ用自動炊飯器を自作してみました。

飯盒炊飯の火力管理
飯盒炊飯の火力管理については、好みの炊き上がりに個人差があるため、いろんな意見があります。
お焦げは最少でいい私は、熱伝導率が良すぎて火力調節が難しいアルミの丸型飯盒を使って、
として、自動化に挑戦してみます。
お米の量がいつもと違う場合は、時間管理は変えずに、火力をお米の量に比例して調整します。
自動炊飯器を自作します
自動炊飯器といっても、家で使うような釜一体の大掛かりなものは使いませんし、作りません!
キャンプ用の飯盒とCB缶バーナーを使いつつ、バーナーのガスコックに接続する自動火力調節器を自作して、トータルシステムとしては自動炊飯できるようにします。
部品の準備
駆動部は、サーボモーターを使います。
充分なトルクがある、下の写真左側のラジコンカー用のもので動かします。

炊飯を開始する前に、可変抵抗(ボリューム)を操作してサーボモーターを動かし、バーナー火力の初期設定をします。
20分の炊飯時間は、炊飯開始ボタンを押した時からカウントダウンを開始します。
さらに、経過時間や火力調節状態が一目でわかるように、16x2文字のLCDモニターにリアルタイムで情報を表示します。

主な使用部品は
- Arduino
- サーボモーター
- スイッチ
- ボリューム
- LCDモニター
です。
Arduinoでサーボモーターを動かす制御仕様
ArduinoとLCDモニターの電力は、モバイルバッテリーから供給します。
それとは別に、サーボモーター用の電源として6.4Vバッテリーを準備します。
スイッチは、電源のオンオフではなく、初期入力の切り替えと炊飯時間の管理のために使います。
使い方の流れとしては、
- 電源コードをモバイルバッテリーにつなぐ。サーボモーターの電源はラジコン用6.4Vバッテリーにつなぐ。

- CB缶残量や気圧などに応じた初期設定のために、ボリュームで火力を調節して強火にする

- 初期入力値切り替えボタンを押す
- ボリュームで火力を調節して弱火にする

- 炊飯スタートボタンを押す
の順番に作業すると炊飯タイマーがカウントダウンを始めて、後は炊き上がりまで自動で火力調節します。
自動炊飯中に火力を微調整したい場合は、その都度ボリュームで火力調節すれば、その最新の値を初期設定の値に入れ替えて、その後も自動炊飯し続けます。
回路図
Fritzingで回路図をつくりました。

回路図左上のサーボモーターは、その下のボリュームに連動して動かします。
初期設定切り替えボタン(S3)は、強火と弱火の初期設定を切り替えるために使用します。
炊飯スタートボタン(S2)を押したら炊飯タイマーがスタートして、自動で中火→弱火→強火の火力調節を始めます。
回路図右のLCDには、タイマー残り時間や火力を表示します。
はんだ付けで回路を仕上げて作動確認しました。
(下の写真のサーボモーターは、確認時の電源電力の事情により、小型のものを使っています)

完成!
木の風合いのボックスで仕上げたかったので、Arduinoと回路やモーターなどは、100円ショップの木の箱を加工して取り付けていきます。
バーナーの火力調節コックとサーボモーターとの連結は、事務用クリップを使います。
LCDモニターとボリュームなども取り付けて、完成しました。
キャンプ用自動炊飯器を試してみます
自動で上手に炊けるか、さっそく試してみました。
炊飯の準備
電力は2系統です。
サーボーモーターはラジコン用の6.4V、その他のArduinoなどにはジャンプスターターにも使えるモバイルバッテリーから供給します。
バーナーは愛用のSOTO Gストーブを使い、丸形飯盒で炊飯します。

白米ではなく、私がキャンプでよく楽しんでいる炊き込みご飯を炊飯してみます。
ちょうどムカゴを入手できたので、一緒に飯盒へ入れておきます。

自動炊飯してみます
Gストーブに火をつけて、強火と弱火の初期設定をした後に、炊飯スタートボタンを押します。
自動炊飯器にお任せにする火力調節は、思惑どおりにうまくできています。
youtu.be
今までの手動の炊飯では、途中で不安になって細かく火力調節していましたが、今回は意図して...というよりも我慢して、手を出さないようにします。
20分後、タイマーが終了して、サーボモーターがガスコックを閉じました。
そのまま、さらに20分ほど我慢して、蒸らし終わったタイミングで、フタを開けてみます。
...私の好みの炊き加減にできています!
アウトドアで使うことを想定しているので、気温や風速などによっては、火力や時間を少し変える必要があるかもしれません。
とはいえ、季節が変わるまでの間は、何回でも同じ炊き上がりを望めそうです。
一年を通して使ってみて、外気温と火力や炊飯時間との関係性を見出せたら、温度センサーを追加して、自動化を進化させてもいいですね。
参考;サーボモーターを実用するスケッチ(プログラム)
サーボモーターを簡単に制御できるGitHubのVarSpeedServoライブラリを使ってスケッチを作りました。
参考で紹介いたします。
#include <VarSpeedServo.h>
#include <LiquidCrystal.h>
VarSpeedServo myservo1;
LiquidCrystal lcd( 12, 11, 5, 4, 3, 2 );
int volumePin = 3;
int switchPin = 9;
int operationswPin = 10;
int totalTime = 20 ;
int firstStep = 2 ;
int lastStep = 1 ;
int lowFlame = 180 ;
int midFlame = 90 ;
int highFlame = 0 ;
unsigned long startTime = 0;
unsigned long nowTime = 0;
int m = 0;
int s = 0;
int valAng = 0 ;
int valTemp = 0 ;
int valAngprint = 0 ;
boolean initialSet = true;
boolean highSet = true;
void setup() {
Serial.begin(9600);
pinMode(switchPin, INPUT) ;
lcd.begin(16, 2);
lcd.setCursor(0, 0);
lcd.print("Auto rice cooker");
lcd.setCursor(0, 1);
lcd.print(" with arduino ");
delay(3000);
lcd.clear();
myservo1.attach(7);
}
void loop() {
Serial.print (nowTime) ;
Serial.print (" ") ;
Serial.print (m) ;
Serial.print (" ") ;
Serial.print (s) ;
Serial.print (" ") ;
Serial.print (valAng) ;
Serial.print (" ") ;
Serial.println (valTemp) ;
if (digitalRead(switchPin) == HIGH) {
initialSet = !initialSet ;
delay(50);
while (digitalRead(switchPin) == HIGH) {}
} else {
if (initialSet == true) {
myservo1.attach(7);
startTime = millis();
if (digitalRead(operationswPin) == HIGH) {
highSet = !highSet ;
delay(50);
while (digitalRead(operationswPin) == HIGH) {}
} else {
lcd.setCursor(0, 0);
lcd.print("Adjust the flame");
valAng = 180 - analogRead(volumePin) * 3 / 17 ;
valAngprint = 180 - valAng;
if (valAngprint < 10) {
lcd.setCursor(12, 1);
lcd.print(" ");
lcd.setCursor(15, 1);
lcd.print(valAngprint);
} else if (valAngprint < 100) {
lcd.setCursor(12, 1);
lcd.print(" ");
lcd.setCursor(14, 1);
lcd.print(valAngprint);
} else {
lcd.setCursor(12, 1);
lcd.print(" ");
lcd.setCursor(13, 1);
lcd.print(valAngprint);
}
if (highSet == false) {
lcd.setCursor(0, 1);
lcd.print("LOW setting ");
lowFlame = valAng ;
valTemp = lowFlame ;
myservo1.write(valTemp, 30, true);
} else {
lcd.setCursor(0, 1);
lcd.print("High setting");
highFlame = valAng ;
valTemp = highFlame ;
myservo1.write(valTemp, 30, true);
}
}
} else {
nowTime = totalTime * 60000 + startTime - millis();
m = nowTime / 60000;
s = (nowTime % 60000) / 1000;
if (highSet == false) {
lowFlame = 180 - analogRead(volumePin) * 3 / 17 ;
} else {
highFlame = 180 - analogRead(volumePin) * 3 / 17 ;
}
midFlame = (highFlame + lowFlame) / 2 ;
valAngprint = 180 - valTemp;
lcd.setCursor(15, 1);
lcd.print(" ");
if (valAngprint < 10) {
lcd.setCursor(12, 1);
lcd.print(" ");
lcd.setCursor(15, 1);
lcd.print(valAngprint);
} else if (valAngprint < 100) {
lcd.setCursor(12, 1);
lcd.print(" ");
lcd.setCursor(14, 1);
lcd.print(valAngprint);
} else {
lcd.setCursor(12, 1);
lcd.print(" ");
lcd.setCursor(13, 1);
lcd.print(valAngprint);
}
if (m < 10) {
lcd.setCursor(0, 0);
lcd.print(" ");
lcd.setCursor(1, 0);
lcd.print(m);
} else {
lcd.setCursor(0, 0);
lcd.print(m);
}
lcd.setCursor(2, 0);
lcd.print(":");
if (s < 10) {
lcd.setCursor(3, 0);
lcd.print("0");
lcd.setCursor(4, 0);
lcd.print(s);
} else {
lcd.setCursor(3, 0);
lcd.print(s);
}
if (m > 18) {
lcd.setCursor(5, 0);
lcd.print(" ");
} else if (m > 16) {
lcd.setCursor(6, 0);
lcd.write(">");
} else if (m > 14) {
lcd.setCursor(7, 0);
lcd.write(">");
} else if (m > 12) {
lcd.setCursor(8, 0);
lcd.write(">");
} else if (m > 10) {
lcd.setCursor(9, 0);
lcd.write(">");
} else if (m > 8) {
lcd.setCursor(10, 0);
lcd.write(">");
} else if (m > 6) {
lcd.setCursor(11, 0);
lcd.write(">");
} else if (m > 4) {
lcd.setCursor(12, 0);
lcd.write(">");
} else if (m > 2) {
lcd.setCursor(13, 0);
lcd.write(">");
} else if (m > 0) {
lcd.setCursor(14, 0);
lcd.write(">");
} else {
lcd.setCursor(15, 0);
lcd.write(">");
}
if ( nowTime > totalTime * 60000) {
myservo1.write(180, 30, true);
myservo1.detach();
lcd.setCursor(0, 0);
lcd.print(" Cook finished. ");
lcd.setCursor(0, 1);
lcd.print(" Valve closed ");
delay(500) ;
} else if (m >= totalTime - firstStep) {
if (abs(valTemp - midFlame) < 1) {
myservo1.stop();
} else {
valTemp = midFlame ;
myservo1.write(valTemp, 30, true);
}
lcd.setCursor(0, 1);
lcd.print("1. Mid level");
} else if (nowTime >= lastStep * 60000) {
if (abs(valTemp - lowFlame) < 1) {
myservo1.stop();
} else {
valTemp = lowFlame ;
myservo1.write(valTemp, 30, true);
}
lcd.setCursor(0, 1);
lcd.print("2. Low level");
} else if (nowTime > 0) {
if (abs(valTemp - highFlame) < 1) {
myservo1.stop();
} else {
valTemp = highFlame ;
myservo1.write(valTemp, 30, true);
}
lcd.setCursor(0, 1);
lcd.print("3. Final HI ");
} else {
myservo1.write(180, 30, true);
myservo1.detach();
lcd.setCursor(0, 0);
lcd.print(" Cook finished. ");
lcd.setCursor(0, 1);
lcd.print(" Valve closed ");
delay(500) ;
}
}
}
}