Arduino Nanoで作る携帯型ガイガーカウンター
必要な部品と材料
- Arduino Nano R3 × 1
- 完成済みガイガーカウンターキット × 1
- 0.96インチ OLEDカラーディスプレイ (96×64) × 1
- マイクロUSB充電ボード × 1
- 3.7V 4000mAh 保護回路付き充電式18650リチウムイオン電池 × 1
- トランジスタ BC547 × 1
- ブザー 12mm × 1
- 抵抗 1kΩ × 1
必要な工具と機器
- 3Dプリンター (汎用)
アプリとオンラインサービス
- Autodesk Fusion 360
- Arduino IDE
- CURA
- ClickCharts
- EasyEDA
- Fritzing
プロジェクト概要
このプロジェクトは、Banggoodで完成済みガイガーカウンターキットを購入したことから始まりました。

このキットを3Dプリントしたケースに収め、完全に動作する携帯型ガイガーカウンターセットを作るのがアイデアでした。最終結果は以下の通りです:

ステップ1: システム設計
携帯型ガイガーカウンターの設計は以下の図の通りです:

ガイガーカウンターには0.96インチカラーOLEDディスプレイを搭載し、測定されたCPM(1分間あたりの電離イベント検出率)と、使用しているガイガー・ミュラー(GM)管のタイプに対して文献値として知られる係数151を用いて計算された線量当量 (µSv/hr) を表示します。
参考: Wikipedia - Counts per minute
実際、表示されるCPMは、1秒あたりのカウント数(CPS)を測定し、過去10秒間の測定値を配列に蓄積し、その合計を6倍してCPM値を求めています。
過去1秒間のカウント数は、OLEDディスプレイ上のバーグラフとして瞬時の測定値を表示するために使用します。これはカウントレートが高い場合や、携帯カウンターを放射線源の上で動かした際に急激な変化が起きた場合に有用です。
ガイガーカウンターは18650型リチウムイオン電池で駆動し、マイクロUSB経由で充電可能です。Arduino NanoのUSBポートもソフトウェア更新用にアクセス可能です。追加のブザーをガイガーカウンターボードに接続し、GM管での電離音を増幅しています。
すべての電子部品は3Dプリントしたケース内に収納されています:

OLEDディスプレイは、ガイガーカウンター上部の別ボックスに収納されています:

完全組み立て版:

ステップ2: ガイガーカウンターアセンブリの製作
使用材料:
- Arduino NANO 1 - 店舗リンク
- ガイガーカウンターキット 1 - Banggoodリンク
- 0.96インチ OLEDカラーディスプレイ 96×64 1 - Banggoodリンク
- マイクロUSB充電ボード 18650電池用 1 - Banggoodリンク
- 3.7V 4000mAh 保護付き18650リチウムイオン電池 1 - Banggoodリンク
- トランジスタ BC547 1
- ブザー 12mm 1
- 抵抗 1kΩ 1
電子回路設計
ガイガーカウンターキットの電子回路図は以下の通りです:

完全なガイガーカウンターセットアップの回路図は以下の通りです:

5V電源は、マイクロUSB充電ボードに搭載された充電式リチウムイオン電池から供給されます。OLEDディスプレイ用の3.3Vもこのボードから取り出しています。
Arduino IDEでソフトウェアを構築・テストするために使用したブレッドボード配線は以下の通りです:


機械的・電気的組み立て
すべての機械的・電子的パーツの組み立ては以下の写真の通りです:

この携帯型ガイガーカウンターにはケーブル接続が一切ありません。
3.7Vリチウムイオン電池の充電用に、ケースにマイクロUSBプラグを一時的に接続するための専用開口部を設けています。
Arduino Nanoのソフトウェア更新用に、追加のミニUSB接続も利用可能です。
ステップ3: ソフトウェア設計
以下のフローチャートは、ガイガーカウンターの一般的なソフトウェア設計を示しています:

0.96インチOLEDディスプレイの表示内容は以下の通りです:

完全なArduinoスケッチは以下に記載します:
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1331.h>
#include <SPI.h>
// OLEDディスプレイ接続
#define sclk 13 // SCL (青線)
#define mosi 11 // SDA (白線)
#define cs 10 // CS (灰色線)
#define rst 9 // RES (緑線)
#define dc 8 // DC (黄色線)
#define LOGtime 1000 // ログ記録時間 (ミリ秒、1秒)
#define Minute 60000 // CPM計算用1分間
#define show endWrite
#define clear() fillScreen(0)
// 色定義
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
Adafruit_SSD1331 display = Adafruit_SSD1331(cs, dc, rst);
int Counts = 0; // LOGtime内のGM管イベント数
unsigned long previousMillis = 0; // 前回時刻保存用
int AVGCPM = 0; // 固定移動ウィンドウ期間の浮動平均カウント数
int TenSecCPM = 0;
int units = 0;
int tens = 0;
int hundreds = 0;
int thousands = 0;
float Sievert = 0;
int COUNTS[10]; // 連続10秒間の各1秒間のパルス数を格納する配列
int t = 0;
////////////////////// セットアップコード: 「電源オン」またはリセット後に1回実行 /////////////////////
void setup() {
Serial.begin(115200);
display.begin();
display.fillScreen(BLACK);
float Battery = analogRead(A3); // (橙色線)
float BattPerc = 100 * (Battery / 770);
//Serial.print("battery value = "); Serial.println (Battery);
//Serial.print("battery percentage = "); Serial.println (BattPerc);
display.setCursor(4,4);
display.setTextSize(2);
display.setTextColor(MAGENTA);
display.println("Battery");
display.setCursor(4,24);
display.print(int(BattPerc)); display.print("."); display.print(int((10*BattPerc)-(10*int(BattPerc)))); display.print(" %");
delay(3000);
display.fillScreen(BLACK);
for(int x = 0; x < 10; x++) { // COUNTS配列の全データを0で初期化 (配列位置0-9、10秒間分)
COUNTS[x] = 0;
}
attachInterrupt(0, IMPULSE, FALLING); // 外部割り込みピン D2/INT0 に割り込みルーチン IMPULSE を設定 (緑線)
display.drawRect(0,0,96,64,WHITE);
display.setCursor(4,4);
display.setTextColor(RED);
display.setTextSize(2);
display.print("CPM");
display.setCursor(50,4);
display.setTextSize(1);
display.print("10 sec");
display.setCursor(50,12);
display.print("window");
display.setCursor(4,38);
display.setTextSize(1);
display.setTextColor(GREEN);
display.print("uSv/hr");
display.drawRect(0,48, 96, 16, YELLOW);
}
////////////////////////// ループコード: 「電源オフ」またはリセットまで繰り返し実行 ///////////
void loop() {
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > LOGtime) {
previousMillis = currentMillis;
COUNTS[t] = Counts;
for (int y = 0; y < 10; y++) { // COUNTS配列の全データを加算
TenSecCPM = TenSecCPM + COUNTS[y]; // 過去10秒間のローリング平均CPMを計算
}
AVGCPM = 6 * TenSecCPM;
TenSecCPM = 0;
t++;
if (t > 9) { t = 0; }
//Serial.print("COUNTS "); Serial.print(t); Serial.print(" = "); Serial.println(COUNTS[t]);
display.fillRect(4,20,90,17,BLACK); // ディスプレイ上のCPM値フィールドをクリア
display.setCursor(4,20);
display.setTextColor(RED);
display.setTextSize(2);
display.println(AVGCPM);
//Serial.print("AVGCPM = "); Serial.print(AVGCPM); //Serial.print(" CPM = "); Serial.println(CPM);
display.fillRect(45,38,50,10,BLACK); // ディスプレイ上のµSv/hr値フィールドをクリア
display.setCursor(45,38);
display.setTextColor(GREEN);
display.setTextSize(1);
Sievert = (AVGCPM / 151.0); //Serial.print(" Sievert = "); Serial.println(Sievert);
units = int(Sievert); //Serial.print("units = "); Serial.println(units);
tens = int((10*Sievert) - (10*units)); //Serial.print("tens = "); Serial.println(tens);
hundreds = int((100*Sievert) - (100*units) - (10*tens)); //Serial.print("hundreds = "); Serial.println(hundreds);
thousands = int((1000*Sievert) - (1000*units) - (100*tens) - (10*hundreds)); //Serial.print("thousands = "); Serial.println(thousands);
display.print(units); display.print("."); display.print(tens); display.print(hundreds); display.println(thousands);
display.fillRect(1,49,94,14,BLACK); // CPMインジケータフィールドをクリア
display.fillRect(1,49,Counts,14,RED); // CPMインジケータフィールドをバー表示
Counts = 0;
}
}
////////////////// ループ終了 ////////////////////////////////////
///////////////////////////////// 以下、ガイガーカウンターキットからのパルス数カウント関数 ////////////////////////////////
void IMPULSE() {
Counts++;
}
このスケッチで最も重要な部分は、GM管でインパルスが測定され、ガイガーカウンターのINT出力がトリガーされる(短期間LOWになる)と呼び出される割り込み関数です。INT信号はArduino NanoのピンD2(外部割り込みピンINT0)に接続されています:
attachInterrupt(0, IMPULSE, FALLING);
INT信号により割り込みルーチン IMPULSE() が開始され、Countsを1増やします:
void IMPULSE() { Counts++; }
1000ミリ秒経過後:
- 整数 Counts を 0 にリセット
- 配列 COUNTS[] に過去1000ミリ秒間の測定カウント数を格納
- 配列 COUNTS[] の全数値を加算し、過去10秒間の総カウント数を求め、6倍してCPM値をディスプレイに表示
- 線量当量 (µSv/hr) を CPM値を151で割って計算(文献値)
- カラーOLEDディスプレイに、過去1秒間のCounts値に基づく赤いバーを表示(実質的にCPS = Counts Per Secondを表示)
ガイガーカウンター用の完全なArduinoスケッチは約150行です。完全なリストはこのチュートリアルに含まれており、Arduinoコード(第9章参照)には十分な量のコメントが付いています。
コード
- Geiger_Counter.ino
Geiger_Counter.ino (Arduino)
/********* This project has been developed and produced by Pierre Pennings (December 2019), This Project is about making a handheld Geiger Counter using a ready made Geiger Counter kit, in a 3D printed handheld radiation detector, De kit is connected to an Arduino Nano that counts the number of impulses from the Geiger/Muller tube in a period of 10 seconds and then displays the average CPM (Counts Per Minute) on a 96*64 OLED color display. The measured CPM is also displayed as micro Sievert per Hour (with a simple conversion factor of 151) The OLED display also displays the momentary measured amount of impulses (per second) and displays the measurements as a moving red bar on the display. The 3D printed housing contains also the 18650-Ion Lithium battery (rechargeble) which provides the 5V power to the Arduino Nano as well as the Geiger Counter kit and 3,3V for the OLED display. A power-on swith and an external beeper complete the detector. Upon power-on, the charging level of the 18650 battery is shown on the color display. This code is licensed under GPL3+ license. *********/
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1331.h>
#include <SPI.h>
// OLEDディスプレイ接続
#define sclk 13 // SCL (青線)
#define mosi 11 // SDA (白線)
#define cs 10 // CS (灰色線)
#define rst 9 // RES (緑線)
#define dc 8 // DC (黄色線)
#define LOGtime 1000 // ログ記録時間 (ミリ秒、1秒)
#define Minute 60000 // CPM計算用1分間
#define show endWrite
#define clear() fillScreen(0)
// 色定義
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
Adafruit_SSD1331 display = Adafruit_SSD1331(cs, dc, rst);
int Counts = 0; // LOGtime内のGM管イベント数
unsigned long previousMillis = 0; // 前回時刻保存用
int AVGCPM = 0; // 固定移動ウィンドウ期間の浮動平均カウント数
int TenSecCPM = 0;
int units = 0;
int tens = 0;
int hundreds = 0;
int thousands = 0;
float Sievert = 0;
int COUNTS[10]; // 連続10秒間の各1秒間のパルス数格納配列
int t = 0;
////////////////////// セットアップコード: 「電源オン」またはリセット後に1回実行 /////////////////////
void setup() {
Serial.begin(115200);
display.begin();
display.fillScreen(BLACK);
float Battery = analogRead(A3); // (橙色線)
float BattPerc = 100 * (Battery / 770);
//Serial.print("battery value = "); Serial.println(Battery);
//Serial.print("battery percentage = "); Serial.println(BattPerc);
display.setCursor(4,4);
display.setTextSize(2);
display.setTextColor(MAGENTA);
display.println("Battery");
display.setCursor(4,24);
display.print(int(BattPerc)); display.print("."); display.print(int((10*BattPerc)-(10*int(BattPerc)))); display.print(" %");
delay(3000);
display.fillScreen(BLACK);
for (int x = 0; x < 10; x++) {
COUNTS[x] = 0; // 10秒間分
}
attachInterrupt(0, IMPULSE, FALLING); // 外部割り込みピン D2/INT0 設定 (緑線)
display.drawRect(0,0,96,64,WHITE);
display.setCursor(4,4);
display.setTextColor(RED);
display.setTextSize(2);
display.print("CPM");
display.setCursor(50,4);
display.setTextSize(1);
display.print("10 sec");
display.setCursor(50,12);
display.print("window");
display.setCursor(4,38);
display.setTextSize(1);
display.setTextColor(GREEN);
display.print("uSv/hr");
display.drawRect(0,48, 96, 16, YELLOW);
}
////////////////////////// ループコード: 「電源オフ」またはリセットまで繰り返し実行 ///////////
void loop() {
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > LOGtime) {
previousMillis = currentMillis;
COUNTS[t] = Counts;
for (int y = 0; y < 10; y++) {
TenSecCPM = TenSecCPM + COUNTS[y];
}
AVGCPM = 6 * TenSecCPM;
TenSecCPM = 0;
t++;
if (t > 9) { t = 0; }
//Serial.print("COUNTS "); Serial.print(t); Serial.print(" = "); Serial.println(COUNTS[t]);
display.fillRect(4,20,90,17,BLACK);
display.setCursor(4,20);
display.setTextColor(RED);
display.setTextSize(2);
display.println(AVGCPM);
//Serial.print("AVGCPM = "); Serial.print(AVGCPM); //Serial.print(" CPM = "); Serial.println(CPM);
display.fillRect(45,38,50,10,BLACK);
display.setCursor(45,38);
display.setTextColor(GREEN);
display.setTextSize(1);
Sievert = (AVGCPM / 151.0);
//Serial.print("Sievert = "); Serial.println(Sievert);
units = int(Sievert);
//Serial.print("units = "); Serial.println(units);
tens = int((10*Sievert) - (10*units));
//Serial.print("tens = "); Serial.println(tens);
hundreds = int((100*Sievert) - (100*units) - (10*tens));
//Serial.print("hundreds = "); Serial.println(hundreds);
thousands = int((1000*Sievert) - (1000*units) - (100*tens) - (10*hundreds));
//Serial.print("thousands = "); Serial.println(thousands);
display.print(units); display.print("."); display.print(tens); display.print(hundreds); display.println(thousands);
display.fillRect(1,49,94,14,BLACK);
display.fillRect(1,49,Counts,14,RED);
Counts = 0;
}
}
////////////////// ループ終了 ////////////////////////////////////
///////////////////////////////// 以下、ガイガーカウンターキットからのパルス数カウント関数 ////////////////////////////////
void IMPULSE() {
Counts++;
}
カスタムパーツとケース
- 0.96インチOLEDディスプレイ用3Dプリントケース
- Fusion 360で作成したハウジング(上部と下部パーツで構成)
geiger_counter_housing_top__bottom_rev04_v1_cvCIwkO13j.obj(3D STLファイル)
回路図
電子部品のセットアップを示す図です。
製造プロセス
- Arduinoで作るレトロNumitron管時計:シンプル、信頼性高く、省エネ
- Arduino NanoとMPU-6050で作るリアルタイムジャイロスコープゲーム
- Arduinoデジタルダイス: LCD表示の乱数ジェネレーター自作
- DIY Arduino USBゲームコントローラー - 高性能ゲームパッドを自作
- スマート貯金箱を作ろう:Arduino Nanoでコインアクセプターを制御
- 自作16×8 LEDマトリックスでテトリスゲーム:Arduino Nano実装
- ArduinoでBluetooth経由でLEDを制御 – シンプルDIYガイド
- Arduino Nanoとジョイスティックで2軸ステッピングモーターを制御:Visuinoで簡単プログラミング
- Arduino、高圧ブースト、20x4 LCDで自作ガイガー・ミュラー計数管を作る
- Pixel Chaser: Arduino Nanoで作るワンタップLEDゲーム
- Arduino Nanoとロータリーエンコーダーで作る高精度測定ホイール