zplt.net / ブログ

ESP32で4:6メソッドのコーヒー計算機を作った話

( 更新)

こんにちは、zpltです。

Seeed Studio XIAO ESP32C3を使って、コーヒーの4:6メソッド用の計算機を作ってみた話です。お湯の量を選ぶと、豆の量と、何回・何gずつ・いつ注げばいいかを計算して、45秒ごとにブザーで教えてくれるといったものです。最初はArduino Mega 2560 R3で試作して、そのあとESP32に載せ替えました。

やりたかったこと

前にV60レシピ(1人分)の記事を書きましたが、4:6メソッドで淹れるのも好きです。

4:6メソッドは、粕谷哲さんが考案した淹れ方です。お湯を「前半の40%」と「後半の60%」に分けて、前半の注ぎ方で味を、後半の注ぎ方で濃さを決めます。考え方はシンプルなのですが、お湯の量を変えるたびに「1回目は何gで、スケールが何gになるまで注ぐんだっけ」と計算するのが少し面倒でした。

そこで、計算とタイマーを小さなマイコンにまかせることにしました。

4:6メソッドについて

作る前に、4:6メソッドが本当に合っているかを調べ直しました。基本はこんな感じです。

項目 内容
豆とお湯の比率 1:15(豆20gならお湯300g)
前半40% 2回に分けて注ぐ。1回目を少なくすると甘く、多くすると酸味が出て明るい味になる
後半60% 1〜3回に分けて注ぐ。回数が多いほど濃くなる
注ぐ間隔 45秒ごと
終わり 3:30でドリッパーを外す

基本のレシピは「60gずつ5回」で、前半の120gを2回、後半の180gを3回に分けて注ぎます。1投目は蒸らしも兼ねています。

解説のサイトによっては、味や濃さの向きが逆に書かれているものもありました。今回は、粕谷さんの説明と多くの解説に合わせています。

Megaで試作して、ESP32に載せ替えた

最初は、手元にあったArduino Mega 2560 R3とELEGOOのスターターキットの部品で作りました。画面はLCD1602(16文字×2行の液晶)。

ひととおり動いたのですが、キッチンで電池で使うことを考えると、Megaは基板が大きく、USBの変換チップなど、プログラムを止めても電気を使い続ける部品も載っています。そこで、XIAO ESP32C3に載せ替えることにしました。

Mega版(試作) ESP32版
マイコン Arduino Mega 2560 R3 Seeed Studio XIAO ESP32C3
画面 LCD1602 OLED 0.96インチ
音 アクティブブザー パッシブブザー
節電 バックライトを消す ディープスリープで眠らせる
Wi-Fi なし 時計合わせと天気に使う

XIAO ESP32C3は親指くらいの大きさで、リポバッテリーの充電回路も付いています。

使ったもの

項目 内容
マイコン Seeed Studio XIAO ESP32C3
画面 OLED 0.96インチ(SSD1306、128×64ドット、I2C)
操作 ロータリーエンコーダ(KY-040)
音 パッシブブザー
その他 10kΩの抵抗、ブレッドボード、ジャンパーワイヤ
電源 モバイルバッテリー

操作

操作は、ロータリーエンコーダ1つだけです。つまみを回すと「カチカチ」と1クリックずつ値が変わって、押し込むとボタンにもなる部品です。

操作 動き
回す お湯の量・比率・味・濃さを変える
押す 決定、タイマーのスタート
長押し 1つ前の画面に戻る
抽出中に回す 早送り・巻き戻し(1クリック5秒)
抽出中に押す 中止
3秒押し続ける 電源オフ(もう一度押すと電源オン)

配線

配線は以下の通りです。

部品 つなぎ方
OLED VCC→3V3、GND→GND、SDA→D4、SCL→D5
ロータリーエンコーダ CLK→D1、DT→D2、SW→D3、+→3V3、GND→GND
10kΩの抵抗 SW(D3)と3V3の間
パッシブブザー +→D10、−→GND

ESP32は3.3Vで動くので、エンコーダの「+」もOLEDも3V3につなぎます。

10kΩの抵抗は、眠っているときにつまみを押して、確実に起きるようにするためのものです。ESP32C3で眠りから起こす合図に使えるのはGPIO0〜5だけなので、押すボタン(SW)はD3(GPIO5)にしています。

画面

OLEDで使用したのは、U8g2というライブラリの日本語フォントで、漢字も表示できます。

湯量     ☀ 18℃ 8:15
       300g
     豆 20.0g

抽出中は、真ん中にスケールの目標を大きく出して、次の注湯までの残り時間をバーで出しています。

2/5回目        00:52
       120g
+60g  [■■■■■■■□□□□]

できあがると、湯気がゆらゆら上がるコーヒーカップのアニメーションが出ます。

プログラム

プログラムはArduino IDEで書きました。やっていることは、こんな流れになっています。

  1. お湯の量(150〜900g)を選ぶと、豆の量を計算して表示する
  2. 比率(1:13〜1:17)、味、濃さを選ぶ
  3. 何回目に何gずつ注ぐかを計算して、計画を表示する
  4. スタートしたら、45秒ごとにブザーで知らせて、注ぐ量とスケールの目標を表示する
  5. 3:30になったら、ドリッパーを外すように知らせる
  6. しばらくさわらなければ、眠る

注ぐ量を計算する

前半の40%を味の設定で2回に分け、後半の60%を濃さの設定で1〜3回に分けます。スケールを見ながら注ぐので、1回ごとの量だけでなく「スケールが何gになるまで注ぐか」(累計)も出しています。

// 前半40%のうち1投目が占める割合(甘め 5:7 / 標準 6:6 / 酸味 7:5)
const float FIRST_POUR_RATIO[] = {5.0 / 12.0, 0.5, 7.0 / 12.0};

void calcPlan() {
  float first40 = water * 0.4;
  float p[MAX_POURS];
  p[0] = first40 * FIRST_POUR_RATIO[taste];
  p[1] = first40 - p[0];

  byte laterPours = strength + 1;  // 後半は1〜3回
  float each = water * 0.6 / laterPours;
  pourCount = 2 + laterPours;
  for (byte i = 2; i < pourCount; i++) p[i] = each;

  // 累計値を丸めてから差分を取り、合計がずれないようにする
  float cum = 0;
  int prev = 0;
  for (byte i = 0; i < pourCount; i++) {
    cum += p[i];
    pourTotal[i] = (int)(cum + 0.5);
    pourAmount[i] = pourTotal[i] - prev;
    prev = pourTotal[i];
  }
}

エンコーダを読む

エンコーダは、CLKとDTの2本の信号が、回す向きによって少しずれて変化します。2本の「前の状態」と「今の状態」の組み合わせを表にしておいて、そこから右回りか左回りかを判断しています。

DRAM_ATTR static const int8_t ENC_TABLE[16] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};

void IRAM_ATTR encoderISR() {
  portENTER_CRITICAL_ISR(&encoderMux);
  encoderState = ((encoderState << 2) | (digitalRead(ENC_CLK_PIN) << 1) | digitalRead(ENC_DT_PIN)) & 0x0F;
  encoderSteps += ENC_TABLE[encoderState];
  portEXIT_CRITICAL_ISR(&encoderMux);
}

ESP32では、割り込みの関数にIRAM_ATTRを付けて、表もRAMに置く(DRAM_ATTR)ようにしています。また、手元のエンコーダは1クリックで信号が2回変わるタイプだったので、2回分たまったら「1クリック回した」と数えるようにしています。

音

パッシブブザーは、PWMで鳴らす周波数が音の高さ、ONの時間の割合(デューティ比)が音量になります。音の高さと長さを並べておいて、場面ごとにメロディを鳴らすようにしました。

const Note SND_START[] = {{N_C6, 90}, {N_E6, 90}, {N_G6, 90}, {N_C7, 220}};  // 抽出スタート(ドミソド)
const Note SND_POUR[] = {{N_G6, 90}, {REST, 30}, {N_C7, 220}};               // 2投目から(ソ・ド)

void toneOn(uint16_t freq) {
  ledcChangeFrequency(BUZZER_PIN, freq, BUZZER_RESOLUTION);
  ledcWrite(BUZZER_PIN, (1 << (BUZZER_RESOLUTION - 1)) * VOLUME / 100);
}

次の注湯の3秒前からは、だんだん高くなる「ド・レ・ミ」でカウントダウンします。つまみを回したときだけは、「ピッ」にしています。

ディープスリープ

抽出中以外で3分間さわらないと、画面を消してディープスリープに入ります。つまみを押すと起きます。

void goToSleep() {
  u8g2.setPowerSave(1);  // 画面を消す
  esp_deep_sleep_enable_gpio_wakeup(1ULL << WAKE_GPIO, ESP_GPIO_WAKEUP_GPIO_LOW);  // つまみを押すと起きる
  esp_deep_sleep_start();
}

ディープスリープから起きると、プログラムは最初から始まります。選んでいたお湯の量などは、RTC_DATA_ATTRを付けた変数に入れておくと、眠っている間も残っていました。

最初は1分で眠るようにしていたのですが、豆を挽いている間に消えてしまったので、3分にしました。

時計と天気

Wi-Fiは、時計合わせと天気にだけ使っています。電源を入れたときに家のWi-Fiにつないで時計を合わせ、Open-Meteoという無料のサービスから天気と気温を取って、すぐに切ります。眠っている間も時計は進み続けるので、合わせるのは電源を入れたときだけです。天気は、起きたときに前回から1時間以上たっていれば取り直します。

天気はアイコンで出しています。晴れ・くもり・雨・雪・雷・霧の6種類を、12×12ドットで描いています。

試作品

以下は試作品です。

この記事を共有

X でポスト