Geiger counter on an Arduino Uno
A RadiationD-v1.1 Geiger counter board on an Uno: the clicks counted on an interrupt, a running minute of counts, and the dose rate in uSv/h on an I2C LCD. The whole build, an Arduino Uno and 2 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Beginner About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $69. On Mokxi they're free.
What you'll learn
- Counting pulses with an interrupt
- Why radioactive counts are random
- A rolling window built from buckets
- Converting counts per minute to a dose rate
- Showing readings on an I2C LCD
- Difficulty: Beginner
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 4
How it works
Each time a particle sets off the Geiger tube, the board pulls its output on pin 2 low for a moment. An interrupt on the falling edge adds one to a click counter, so no click is missed while the sketch does other things.
Decays are random, so one minute's count differs from the next. The sketch keeps a running minute made of twelve buckets of five seconds each. The counts per minute, CPM, are their sum. In the first minute it scales up what it has so far.
The dose rate is CPM times 0.00812037, shown in uSv/h on the LCD. Treat it as approximate.
Parts list
4 parts, plus the jumper wires. They retail for about $69; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Half breadboard
- 1 × Geiger counter, RadiationD-v1.1
- 1 × Character LCD with I2C backpackIn Claw Machine Kit, Vending Machine Kit, Rhythm Game Kit.
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Geiger counter, RadiationD-v1.1 pin VIN | Arduino Uno R3 pin 2 |
| Geiger counter, RadiationD-v1.1 pin 5V | Arduino Uno R3 pin 5V |
| Character LCD with I2C backpack pin VCC | Arduino Uno R3 pin 5V |
| Geiger counter, RadiationD-v1.1 pin GND | Arduino Uno R3 pin GND |
| Character LCD with I2C backpack pin GND | Arduino Uno R3 pin GND |
| Character LCD with I2C backpack pin SDA | Arduino Uno R3 pin A4 |
| Character LCD with I2C backpack pin SCL | Arduino Uno R3 pin A5 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Geiger counter: the RadiationD-v1.1 (CAJOE) board with its J305 tube, and
// a 16x2 LCD on an I2C backpack.
//
// pin 2 the board's VIN pin, which is really its pulse output
// 5 V, GND the board's 5V and GND pins
// A4, A5 the LCD backpack's SDA and SCL (address 0x27)
//
// Each time a particle sets the tube off, the board pulls its output low for
// a moment (it sits high on a 47 k pull-up of its own the rest of the time),
// so an interrupt on the falling edge counts the clicks.
//
// Decays are random. Even with nothing changing, one minute's count differs
// from the next by about its own square root, so the sketch keeps a running
// minute: twelve five-second buckets, and the counts per minute (CPM) are
// their sum. For the first minute it scales up what it has so far.
//
// The dose rate is CPM times the tube's conversion factor. 0.00812037 uSv/h
// per CPM (123 CPM for 1 uSv/h) is the figure the guides for this board give
// for the J305. Treat it as approximate: it depends on the tube and on the
// kind of radiation. Background is about 0.1 to 0.2 uSv/h.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
const int GEIGER = 2;
const float FACTOR = 0.00812037; // uSv/h per CPM, J305
const int BUCKETS = 12; // twelve buckets of five seconds
const unsigned long BUCKET_MS = 5000;
LiquidCrystal_I2C lcd(0x27, 16, 2);
volatile unsigned long clicks = 0;
unsigned long bucket[BUCKETS];
int filled = 0;
int at = 0;
unsigned long counted = 0;
unsigned long lastBucket = 0;
void onClick() {
clicks++;
}
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: Geiger counter");
pinMode(GEIGER, INPUT);
attachInterrupt(digitalPinToInterrupt(GEIGER), onClick, FALLING);
lcd.init();
lcd.backlight();
lcd.print("Geiger counter");
lcd.setCursor(0, 1);
lcd.print("counting...");
lastBucket = millis();
}
void loop() {
if (millis() - lastBucket < BUCKET_MS) return;
lastBucket += BUCKET_MS;
noInterrupts();
unsigned long all = clicks;
interrupts();
bucket[at] = all - counted;
counted = all;
at = (at + 1) % BUCKETS;
if (filled < BUCKETS) filled++;
unsigned long sum = 0;
for (int i = 0; i < filled; i++) sum += bucket[i];
// Counts a minute: the sum over the buckets there are, scaled to a minute.
unsigned long cpm = sum * BUCKETS / filled;
float usvh = cpm * FACTOR;
lcd.setCursor(0, 0);
lcd.print("CPM ");
lcd.print(cpm);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(usvh, 2);
lcd.print(" uSv/h ");
Serial.print("CPM: ");
Serial.print(cpm);
Serial.print(" uSv/h: ");
Serial.print(usvh, 3);
Serial.print(" total: ");
Serial.println(all);
}
Code walkthrough
The key functions of the sketch, and what each one does.
onClick sketch.ino
The interrupt handler. It does one thing, add one to clicks. Keeping it this short is what makes interrupts safe. The variable is volatile because it changes outside the normal flow of the program.
void onClick() {
clicks++;
}setup sketch.ino
Sets pin 2 as an input and attaches onClick to its falling edge. It starts the LCD, shows a start-up message and records the time for the first five-second bucket.
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: Geiger counter");
pinMode(GEIGER, INPUT);
attachInterrupt(digitalPinToInterrupt(GEIGER), onClick, FALLING);
lcd.init();
lcd.backlight();
lcd.print("Geiger counter");
lcd.setCursor(0, 1);
lcd.print("counting...");
lastBucket = millis();
}loop sketch.ino
Does nothing until five seconds have passed. Then it copies clicks with interrupts briefly off, stores the new clicks in the next bucket and sums the buckets. It scales that sum to a minute for CPM, multiplies by FACTOR for uSv/h and updates the LCD and serial monitor.
void loop() {
if (millis() - lastBucket < BUCKET_MS) return;
lastBucket += BUCKET_MS;
noInterrupts();
unsigned long all = clicks;
interrupts();
bucket[at] = all - counted;
counted = all;
at = (at + 1) % BUCKETS;
if (filled < BUCKETS) filled++;
unsigned long sum = 0;
for (int i = 0; i < filled; i++) sum += bucket[i];
// Counts a minute: the sum over the buckets there are, scaled to a minute.
unsigned long cpm = sum * BUCKETS / filled;
float usvh = cpm * FACTOR;
lcd.setCursor(0, 0);
lcd.print("CPM ");
lcd.print(cpm);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(usvh, 2);
lcd.print(" uSv/h ");
Serial.print("CPM: ");
Serial.print(cpm);
Serial.print(" uSv/h: ");
// ... 4 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- The reading jumps around in the first minute This project
That is expected. The sketch is scaling up a few buckets. Wait a full minute for a steadier number. - The LCD stays blank This project
Check SDA on A4 and SCL on A5, and that the backpack's address is 0x27, as inLiquidCrystal_I2C lcd(0x27, 16, 2). - The count is always zero, or counts at wrong times. Geiger counter
VIN pulses LOW for each count. Use a pin interrupt on the falling edge, and count the number of pulses in a fixed time. Power the board with 5 V and join the grounds. - The counts per minute fluctuate a lot. Geiger counter
Radiation counts are random, so short samples vary. Count for a minute or longer, or use a moving average. About 123 counts per minute is one uSv/h, so scale to match. - The screen stays blank or shows only a row of blocks. Character LCD with I2C backpack
The I2C address is usually 0x27 or 0x3F. Run an I2C scanner to find yours. Then turn the contrast trimmer on the backpack slowly until text appears. - The backlight is on but there is no text, or text is garbled. Character LCD with I2C backpack
Wire SDA and SCL to the right pins (A4 and A5 on an Uno), power it from 5 V, and call lcd.init() and the backlight call in setup. - Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected. - Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.