知的好奇心 for IoT

IoT関連の知的好奇心を探求するブログです

ガイガーカウンターにOLED(有機ELディスプレイ)を追加してスタンドアローンでも動くようにした

少し前に小型で安いOLEDグラフィックディスプレイを見つけて、色々と使ってみようと思っていたので、まずはガイガーカウンターから手を付けてみました。

モニタリング用ならディスプレイは要らないと思うのですが、計りたい場所に持って行ってる場合はやっぱりスマホだけではなく、機器で直接見えた方がいいですよね。

で、こんな感じになりました。

f:id:IntellectualCuriosity:20180718084614j:plain

真ん中に鎮座しているのが128x64ドットのOLEDです。秋月電子で¥580のやつを買いました。

ちなみに、以前の記事はこれですよ。

 

ビデオ

 

OLED

こういったディスプレイの場合、闇雲にデータを表示しても見やすくはならないので、Excelを持っていないぼくはGoogleスプレッドシートでこんな感じでデザインしました。

f:id:IntellectualCuriosity:20180718090242p:plain

このOLEDはSSD1306というコントローラーを使っていて、今回もAdafruitのライブラリを使わせてもらっています。

その場合、一番小さな文字は6x8のフォントサイズになっているので、横21文字、縦8文字まで表示できます。フォントサイズは整数倍の大きさが指定できるので、1分間のカウント数と1時間平均値は3倍の大きさで表示して、他は一番小さな文字にしました。

上から3文字目と4文字目のグレイの部分は、1秒間のカウント数を昔のオーディオ機器のようにグラフィックで左から21段階で表示するようにしました。

接続方法

このOLEDは、I2C用のSCLとSDAの他は電源用のVCCとGNDしか信号線がないので接続は簡単です。

SCLをGPIO5にSDAをGPIO4に接続すると、Wire.beginをsetupで行わなくてもESP8266ではI2Cで使えるみたいです。

 

スタンドアローン

画面デザインの右上に表示されている「N」は「NETWORK」モードを表していて、「S」だと今回新たに用意した「STANDALONE」モードで動作していることがわかります。

モードの切り替え方法はスゴク単純にGPIO13が「LOW」だったら「NETWROK」モードで動作するようにしていて、GPIO13とグランドを繋いでいるジャンパー線をエイッと引っこ抜いて電源を入れるとスタンドアローンで動きます。ジャンパー線を外してもスタンドアローンで動かないときは、GPIO13とVCCをジャンパー線で繋いでくださいね。

 

BLYNK

スマホのBLYNKの画面はあまり変わっておらず、OLEDのON/OFF用ボタンを追加しただけです。

f:id:IntellectualCuriosity:20180718104126p:plain

OLEDはとても明るいので、真っ暗な部屋だと結構目立ちます。最近買ったAtermもランプを全部消せるようになっていたので、OLEDをオン・オフできるようにしてみました。その機能を実現させるためにプログラムをちょっと工夫しています。

 

スケッチ

※2020年1月19日追加 ボードマネージャーでインストールするESP8266のバージョンを2.5.xにすると割込みが動かないみたいです。バージョンを2.4.2にすると正常に動作するようになります。

※2020年7月20日追加

割込み処理用の関数宣言を次のようにして

ICACHE_RAM_ATTR void interrupted()

割込み処理の定義を次のようにすると、最新のESP8266のボードバージョン(2.7.2)でも動くようになりました。

attachInterrupt(digitalPinToInterrupt(SIGNAL_PIN), interrupted, FALLING);

 

/*
 * Geiger Counter OLED
 * October 31, 2018
 * By Hiroyuki ITO
 * http://intellectualcuriosity.hatenablog.com/ 
 * MIT Licensed.
*/
#define BLYNK_PRINT Serial

#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define BLYNK_AUTH      "YourAuthToken"
#define WIFI_SSID       "YourNetworkName"
#define WIFI_PASS       "YourPassword"
#define NOTIFY_LIMIT    10 // 10 spm
#define NOTIFY_TEXT     "WARNINIG! The device detected more than 10 spm."
#define SIGNAL_PIN      12
#define MODE_PIN        13
#define PWM_PIN         14
#define PWM_FREQ        750
#define NETWORK_MODE    1
#define STANALONE_MODE  2

// for OLED
#define OLED_RESET -1
Adafruit_SSD1306 display(OLED_RESET);
#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif
#define CNT_SEC         1
#define CNT_MIN         2
#define CNT_HOUR        3
#define PULSE_X1        0
#define PULSE_Y1        16
#define PULSE_X2        128
#define PULSE_Y2        15

/*
 * Global Variable
 */
volatile int sec_count =  0;
volatile int min_count =  0;
volatile int hour_count = 0;
int mcount = -1;
int hcount = -1;
int times = 0;
int runmode;
int notification_switch = 0;
int oled_switch = 1;          // OLED ON
BlynkTimer timer;

/*
 * Interrupt Function
 */
void interrupted() {
  sec_count++;
  min_count++;
  hour_count++;
}

/*
 * Display Count
 */
void displayCount(int cnt, int mode) {
  switch (mode) {
    case CNT_SEC:
      display.fillRect(108, 8, 18, 8, BLACK);
      display.setCursor(108, 8);
      display.setTextSize(1);
      break;
    case CNT_MIN:
      display.fillRect(6, 40, 54, 24, BLACK);
      display.setCursor(6, 40);
      display.setTextSize(3);
      break;
    case CNT_HOUR:
      display.fillRect(72, 40, 54, 24, BLACK);
      display.setCursor(72, 40);
      display.setTextSize(3);
      break;
  }
  if (cnt < 10) {
    display.print("  ");
  } else if (cnt < 100) {
    display.print(" ");
  }
  if (cnt < 0) {
    display.print("-");
  } else {
    display.print(cnt);
  }
  if (oled_switch) {
    display.display();
  }
}

/*
 * Show Pulse for Second
 */
void showPulse(int cnt) {
  display.fillRect(PULSE_X1, PULSE_Y1, PULSE_X2, PULSE_Y2, BLACK);
  display.fillRect(PULSE_X1, PULSE_Y1, PULSE_X1+cnt*6, PULSE_Y2, WHITE); 
  if (oled_switch) {
    display.display();
  }
}

/*
 * Show Display Frame
 */
void showFrame() {
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print("   GEIGER COUNTER");
  if (runmode == NETWORK_MODE) {
    display.println("   N");
  } else {
    display.println("   S");
  }
  display.setCursor(0, 32);
  display.println("       CPM   HOUR AVG");
  if (oled_switch) {
    display.display();
  }
}

/*
 * Send Minute Data to Blynk
 */
void sendMinData() {
  mcount = min_count;
  Blynk.virtualWrite(V5, mcount);
  BLYNK_LOG("Minutes: %04d", mcount);
  displayCount(mcount, CNT_MIN);
  min_count = 0;
  times++;

  // 1 hour
  if (times > 60-1) {
    hcount = hour_count / 60;
    Blynk.virtualWrite(V6, hcount);
    BLYNK_LOG("Hours: %04d", hcount);
    displayCount(hcount, CNT_HOUR);
    hour_count = 0;
    times = 0;
  }
}

/*
 * Send Second Data to Blynk
 */
void sendSecData() {
  int cnt = sec_count;
  Blynk.virtualWrite(V4, cnt);
  BLYNK_LOG("sec count: %04d", cnt);

  // Push Notification
  if (notification_switch == 1 && cnt >= NOTIFY_LIMIT) {
    Blynk.notify(NOTIFY_TEXT);
  }

  displayCount(cnt, CNT_SEC);
  showPulse(cnt);
  sec_count = 0;
}

/*
 * Receive Data from Cloud at connected
 */
BLYNK_CONNECTED() {
  Blynk.syncAll();
}

/*
 * OLED ON/OFF
 */
BLYNK_WRITE(V1)
{
  oled_switch = param.asInt();
  if (oled_switch) {
    showFrame();
    displayCount(0, CNT_SEC);
    displayCount(mcount, CNT_MIN);
    displayCount(hcount, CNT_HOUR);
  } else {
    display.clearDisplay();
    display.display();
  }
}

/*
 * Notification ON/OFF
 */
BLYNK_WRITE(V3)
{
  notification_switch = param.asInt();
}

void setup() {
  delay(10);
  Serial.begin(74880);
  BLYNK_LOG2("Reset Reason: ", ESP.getResetReason());
  pinMode(SIGNAL_PIN, INPUT);
  pinMode(MODE_PIN, INPUT);

  // Make high voltage
  pinMode(PWM_PIN, OUTPUT);
  analogWriteFreq(PWM_FREQ);
  analogWriteRange(100);
  analogWrite(PWM_PIN, 80);

  // Netowrk or Standalone
  if (digitalRead(MODE_PIN) == LOW) {
    runmode = NETWORK_MODE;
    Blynk.begin(BLYNK_AUTH, WIFI_SSID, WIFI_PASS);
  } else {
    runmode = STANALONE_MODE;
  }

  // Setup OLED and Show Frame
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  showFrame();
  display.display();
  
  // Setup a function to be called every second or minutes
  timer.setInterval(60000L, sendMinData); // 1 min
  timer.setInterval( 1000L, sendSecData); // 1 sec
  attachInterrupt(SIGNAL_PIN, interrupted, FALLING);
}

void loop() {
  if (runmode == NETWORK_MODE) {
    Blynk.run();
  }
  timer.run();
}

ちらっと書いた通り、OLEDにはAdafruitのライブラリを使わせてもらっています。

display.begin()後にdisplay.clearDisplay() をせずにdisplay.display()を実行すると、綺麗なAdafruitのロゴが表示されるので、興味のある方は試してみてください。

最初わからなかったのが、塗りつぶし矩形表示のdisplay.fillrect()ですね。第1パラメーターと第2パラメーターで始点のXとYを指定するのですが、次の第3パラメーターと第4パラメーターは終点のXとYではなく、始点からの相対距離を指定するようになっているんです。

あと、Adafruitのライブラリを使うときにはヘッダーファイルの「Adafruit_SSD1306.h」を修正する必要があります。

73行目の#define SSD1306_128_64のコメントを外して次の行をコメントアウトします。I2C用のアドレスは0x3Cのままで動きます。

 

おしまい