A digital Geiger Counter display with Frequency Counter Kit

The attached code is some heavy modifications that I made to the Frequency Counter Kit (sku: KIT-10140) Arduino

Sketch to make use of it on the Geiger Counter (sku: SEN-09848). This could be used with any Geiger counter which

has a pulse output however. This code attempts to display count per minute and approximate energy. It uses the three

unused digital inputs to select units and mode of operation. Note that, while it works well, it could use cleanup and

reorganization… Enjoy it and I hope some one else can get some benefit from it…

/*
 Geiger Counter Arduino Sketch
 by: Barry Harding
 Feb 12 2011
 This started from the following project:
 
 Frequency Counter Arduino Sketch
 by: Jim Lindblom
 SparkFun Electronics
 License: Beerware
 
 This is basically a modified (heavily so)version of the SparkFun
 Electronics "Frequency Counter Arduino Sketch". This has been
 used on the "Frequency Counter Kit sku: KIT-10140" connected to
 the "Geiger Counter sku: SEN-09848". But any Geiger counter
 circuit with digital out (+5v TTL) should work. 

 It has two modes of operation, "Survey" and "Background average"
 mode. It can also display measured energy in standard or metric
 units. For "Background average" mode there are three sub-modes
 1 minute, 5 minute, or continous. There are three unused digital
 inputs that can easily be used for all these choise user inputs
 (others could be retasked but three is enough). To select the
 mode/submode two inputs are used as shown:

          Reset button
                      |  o \                       Survey
              /       |--o  \                      5min Average
   GRND ------ .------|  o   \o---------- D12      1min Average
                  |   |--o                         Continuous
                  |   
                  |   |  o  \
                  |   |  o   \
                  |---|--o    \o--------- D11
                      |--o
 Note that this uses a 2P4T switch and that you may want to add a 10k
 ohm resister as pull up on D11 and D12 (but should not be needed since
 328p has them). Also note that there is a trick to get the reset
 functionality for the three average modes. When the mode switches the
 counters are reset to zero. By pressing the reset button the mode is
 tempararily switched to Survey more. For standard/metric mode a 
 simple 1P1T switch goes between ground and D13.

 The circuit: Powered at 5V (5V LCD), Arduino running at 16MHz
 D2 - RS (LCD)
 D3 - R/W (LCD)
 D4 - E (LCD)
 D5 - Frequency input
 D6 - DB4 (LCD)
 D7 - DB5 (LCD)
 D8 - DB6 (LCD)
 D9 - DB7 (LCD)
 D10 - Gate of NPN transistor (Collector tied to 5V, emitter tied to LCD backlight pin)
         (could be D10 - RESET COUNT input)
 D11 - CPM 5 minute input 11 CPS, 01 CPM1, 10 CPM5, 00 count
 D12 - CPM 1 minute input  "        "        "        "
 D13 - metric input  1 = standard, 0 = metric
For the LiquidCrystal library, much thanks to:
   David A. Mellis
   Limor Fried (http://www.ladyada.net)
   Tom Igoe
*/

#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(2, 3, 4, 6, 7, 8, 9);
unsigned int tovf1 = 0;
unsigned long frequency = 0;
unsigned long temp = 0;
unsigned long prevmilli = 0;
unsigned long subprevmilli = 0;
unsigned long secs = 0;
unsigned long interval = 1000;
boolean ncpm5 = true;
boolean ncpm1 = true;
boolean nmetric = true;
boolean nmicro = false;  // In micro instead of milli scale for auto scaling
int cpmcps = 0;          //                 "
unsigned long whole;  // Used when simulating decimals in with integer math
unsigned long frac;   //             "
unsigned long fact;

// Timer 1 is our counter
// 16-bit counter overflows after 65536 counts
// tovfl will keep track of how many times we overflow
ISR(TIMER1_OVF_vect)
{
	tovf1++;
}

void setup() {
	pinMode(5, INPUT);   // This is the frequency input
	pinMode(10, OUTPUT);  // Reset count WAS Backlight control pin
	pinMode(11, INPUT);  // CPM 5 minute
	pinMode(12, INPUT);  // CPM 1 minute
	pinMode(13, INPUT);  // standard/metric switch
	digitalWrite(10, HIGH);  // Turn backlight on

	// Timer 1 will be setup as a counter
	// Maximum frequency is Fclk_io/2
	// (recommended to be < Fclk_io/2.5)
	// Fclk_io is 16MHz
	TCCR1A = 0;
	// External clock source on D5, trigger on rising edge:
	TCCR1B = (1<<CS12) | (1<<CS11) | (1<<CS10);	
	// Enable overflow interrupt
	// Will jump into ISR(TIMER1_OVF_vect) when overflowed:
	TIMSK1 = (1<<TOIE1);

	// Reset all counter variables to start at zero
	TCNT1H = 0;
	TCNT1L = 0;

	// set up the LCD's number of rows and columns: 
	lcd.begin(16, 2);
  
	// Print a splash screen to the LCD.
	lcd.print("GeigerCounter   ");
	lcd.setCursor(0, 1);
	lcd.print("      v1.3      ");
	delay(2000);
}

void loop() {
	boolean  tmpncpm1;
	boolean  tmpncpm5;

	unsigned long curmilli = millis();

	nmetric = digitalRead(13); // Get current energy display mode

	// If we are in Survey mode do it
	if(ncpm1 == true && ncpm5 == true) {
		if(curmilli - prevmilli > interval) { // Time for an update (every second)?
			frequency = (TCNT1H<<8)|TCNT1L;
			frequency = (TCNT1H<<8)|TCNT1L;

			// Correct weird counter bug
			// A small range of frequencies (~30k-50k) are getting
			// 42949 appended to the front of them
			// Will look into this but this works for now
			if (frequency > 40000000)
				frequency -= 4294900000;
  
			// 65536 (2^16) is maximum of counter
			// We'll multiply that by how many times we overflowed
			temp = 65536*(unsigned long)tovf1;
			frequency += temp;
  
			lcd.clear();
			lcd.print("Count/Sec ");
			lcd.print(frequency);
			lcd.setCursor(0,1);

			// We avoid using floating point here since it is too slow
			// and big for a smaller imbedded processor. Instead we adjust
			// all math by shifting decimal place to the left.
			temp = frequency * 5;
			if(nmetric) {
				fact = 10000;      // Four decimal places
				whole = temp/fact;
				frac = temp % fact;

				lcd.print("mSv/hr ");
				lcd.print(whole);
				lcd.print(".");
				if(frac < 10)
					lcd.print("000");
				else if(frac < 100)
					lcd.print("00");
				else if(frac < 1000)
					lcd.print("0");
				lcd.print(frac);
			}else{
				fact = 100;        // Two decomal places
				whole = temp/fact;
				frac = temp % fact;

				lcd.print("mR/hr  ");
				lcd.print(whole);
				lcd.print(".");
				if(frac < 10)
					lcd.print("0");
				lcd.print(frac);
			}

			// Reset all counter variables and start over for every sample
			TCNT1H = 0;
			TCNT1L = 0;
			tovf1 = 0;
			prevmilli = millis();
		}
	// Else we are in background average mode
	}else{
		// We only reset the collected data at the end of average period
		if(interval && curmilli - prevmilli > interval) {
			// Reset all counter variables and start over
			TCNT1H = 0;
			TCNT1L = 0;
			tovf1 = 0;
			frequency = 0;
			secs = 0;
			prevmilli = millis();
		}
		// But we update the display and collected count every second
		if(curmilli - subprevmilli > 1000) {
			temp = (TCNT1H<<8)|TCNT1L;
			temp = (TCNT1H<<8)|TCNT1L;

			// Correct weird counter bug
			// A small range of frequencies (~30k-50k) are getting
			// 42949 appended to the front of them
			// Will look into this but this works for now
			if (temp > 40000000)
				temp -= 4294900000;
 			frequency += temp; 
			// 65536 (2^16) is maximum of counter
			// We'll multiply that by how many times we overflowed
			temp = 65536*(unsigned long)tovf1;
			frequency += temp;

			secs++; // Total number of seconds for this collection cycle

			lcd.clear();
			// If in contous mode we switch between displaying raw totals
			// and Count per minute (or second) every three seconds.
			if(ncpm1 == false && ncpm5 == false && cpmcps > 2) {
				// we are doing count per,  mode

				// Adjust number of seconds in counter per mode
				if(cpmcps == 5)
					cpmcps = 0;
				else
					cpmcps++;

				// Display the data
				temp = frequency/secs;
				if(temp){
					lcd.print("Count/Sec ");
					lcd.print(temp);
				}else{
					if(secs > 60)
						temp = secs/60;
					else
						temp = 1;
					temp = frequency/temp;
					lcd.print("Count/Min ");
					lcd.print(temp);
				}
			}else{
				// We are doing totals mode

				// Adjust number of seconds in raw mode
				if(ncpm1 == false && ncpm5 == false)
					cpmcps++;
				else
					cpmcps = 0;

				// Display the data
				if(secs < 1000)            // Hack to make large numbers fit
					lcd.print("Sec ");
				else if(secs < 10000)
					lcd.print("Sc ");
				else if(secs < 100000)
					lcd.print("S ");
				else
					lcd.print("s");
				lcd.print(secs);
				if(frequency < 10000)      // Hack to make large numbers fit
					lcd.print(" Cnt ");
				else if(frequency < 100000)
					lcd.print(" Ct ");
				else if(frequency < 1000000)
					lcd.print(" C ");
				else if(frequency < 10000000)
					lcd.print(" c");
				else
					lcd.print(" ");
				lcd.print(frequency);
			}
			lcd.setCursor(0,1);
			temp = (frequency/secs) * 5;
			if(temp == 0)                    // frequency too small sa adjust units to micro
				nmicro = true;
			else
				nmicro = false;
			if(nmetric) {
				if(nmicro) {
					// uSv mode  xxx.x
					temp = (frequency * 50000)/secs;
					fact = 100;
					whole = temp/fact;
					frac = temp % fact;

					lcd.print("uSv/hr ");
					lcd.print(whole);
					lcd.print(".");
					if(frac < 10)
						lcd.print("0");
					lcd.print(frac);
				}else{
					// mSv mode xx.xxx
					fact = 10000;
					whole = temp/fact;
					frac = temp % fact;

					lcd.print("mSv/hr ");
					lcd.print(whole);
					lcd.print(".");
					if(frac < 10)
						lcd.print("000");
					else if(frac < 100)
						lcd.print("00");
					else if(frac < 1000)
						lcd.print("0");
					lcd.print(frac);
				}
			}else{
				if(nmicro) {
					// uR mode   xxxx.
					whole = (frequency * 50000)/secs;
					lcd.print("uR/hr ");
					lcd.print(whole);
				}else{
					// mR mode xxx.x
					fact = 100;
					whole = temp/fact;
					frac = temp % fact;

					lcd.print("mR/hr ");
					lcd.print(whole);
					lcd.print(".");
					if(frac < 10)
						lcd.print("0");
					lcd.print(frac);
				}
			}

			// Reset all counter variables and start over since we collected
			// data from the hardware.
			TCNT1H = 0;
			TCNT1L = 0;
			tovf1 = 0;
  
			subprevmilli = millis();
		}
	}

	// Now that we displayed anything needed update mode switch settings state.
	tmpncpm1 = digitalRead(11);
	tmpncpm5 = digitalRead(12);

	// Any mode changes from last time here?
	if(tmpncpm1 != ncpm1 || tmpncpm5 != ncpm5) {
		// YES

		// How long before we reset data collection
		if(tmpncpm1 == true && tmpncpm5 == true)       // Survey mode every second
			interval = 1000  ;
		else if(tmpncpm1 == false && tmpncpm5 == true) // 1 minute averge 60 seconds
			interval = 60000;
		else if(tmpncpm1 == true && tmpncpm5 == false) // 5 minute averge 300 seconds
			interval = 300000;
		else{
			interval = 0;                          // continous - no auto data resets
		}
		// Reset all counter variables and start over.
		// We need to do this since units may have changed.
		// Also this helps with trick to make reset button not need
		// its own input pin.
		TCNT1H = 0;
		TCNT1L = 0;
		tovf1 = 0;
		frequency = 0;
		secs = 0;
		ncpm1 = tmpncpm1;
		ncpm5 = tmpncpm5;
		prevmilli = millis();
		subprevmilli = prevmilli;
	  }
}

Barry -

On my breadboard experiment, I get large numbers of counts for every pulse on the “OUT” pin of the counter. On a 'scope, I’m not seeing that level of activity, nor am I on the geiger terminal output with hyperterminal. I’ve also noticed that the arduino input seems very sensitive to noise: when it’s left floating with a lead attached, it gets random input numbers, which are always fairly large.

Did you have to deal with this?

Suggestions welcomed…

Thanks -

Kevin

Well, I may well have answered my own question: a 10K resistor between the counter out and the arduino in seems to resolve my issue.

Next question: how was the relationship between counts and exposure determined? We don’t know the energy of the particles we’re counting, which Q factor is most useful to the user, etc. I see widely varied ratios used for equipment sold on the net - and most of them provide no justification or basis for those numbers. An old surplus counter I have (non-working) has shutters of various material that shield against specific particles, so the user can make qualified assumptions about exposure…

Side note: I’ve made some corrections, and modified the code to work with serial LCDs, if anyone is interested. A work in progress…

All input welcomed!

Thanks -

Kevin

Here is Barry’s code revisited. I’ve adapted it to a sparkfun serial LCD, changed some assumptions based on the data sheet for the Geiger tube, changed the displays, and dropped the continuous background update in favor of a one hour mode (in addition to the one and five minute modes).

Enjoy! Comments and hacks welcomed… Next, a “tweet-a-rad” version. :slight_smile:

Kevin

/*
Version 1.4
 Modified March, 2011 by Kevin Quinn
 * fixed a few bugs
 * changed to support a Sparkfun serial LCD module using SoftwareSerial. Someone needs to write
 a modified LiquidCrystal library to support serial LCDs... :-)
 * note: I had a lot of trouble with noise on the Arduino input pin that reads the digital out from the geiger counter. 
 I found adding a 10K resistor between the two resolved my issues.
 * changed the use of the constants 'true' and 'false' for the digital I/O pins to 'HIGH' and 'LOW'. 'HIGH' means 'selected' or
 set to Vcc.
 * NOTES: 'Survey' mode means counting second to second; no history is kept. 'Background' modes "average" the counts 
 *        over the time period selected. In this version 
 *        there's one, five, and sixty minute background averageing modes. 
 *        In these modes, we display the running Counts per Second, Counts per Minute, and on the next line
 *        u or m Sieverts or Roentgens per hour as selected by the "metric" switch. Please note that even though we may be registering an average
 *        CPM>0, the running average CPS has to be > 0 to compute R/hr or Sv/hr: inteter math!
 *
 *        Switch Settings:
 *          11 HIGH, 12 LOW: 5 minute background averaging mode
 *          11 LOW, 12 HIGH: 1 minute background averaging mode
 *          11 LOW, 12 LOW: 60 minute background averaging mode
 *          11 HIGH, 12 HIGH: 'Survey' one second reading mode
 *
 * For the ND-712 tube used in the Sparkfun geiger counter, we know the following dry air dose rates for Co60 gamma response:
 * 10^0 CPS = .000055 R/hr (Roentgen)
 * 10^1 CPS = .00055 R/hr
 * 10^3 CPS = .055 R/hr
 * The next step ramps up sharply. Time to run...
 * 10^4 CPS = 4 R/hr
 *
 * Using the following conventions, derived as best I can from various internet sources:
 * - assuming dry air exposure
 * - assuming gamma
 * - using that assumption, Roentgen converts to the following absorbed dose equivalents:
 * - 1 R = 0.877 rem
 * -     = 0.00877 Sievert
 * -     = 0.877 rad
 * - Some simply round to 1 R = 1 rad = 1 rem. I've used the .877 factor... change as you see fit!
 
 
 
 Geiger Counter Arduino Sketch
 by: Barry Harding
 Feb 12 2011
 This started from the following project:
 
 Frequency Counter Arduino Sketch
 by: Jim Lindblom
 SparkFun Electronics
 License: Beerware
 
 This is basically a modified (heavily so)version of the SparkFun
 Electronics "Frequency Counter Arduino Sketch". This has been
 used on the "Frequency Counter Kit sku: KIT-10140" connected to
 the "Geiger Counter sku: SEN-09848". But any Geiger counter
 circuit with digital out (+5v TTL) should work.
 
 It has two modes of operation, "Survey" and "Background average"
 mode. It can also display measured energy in standard or metric
 units. For "Background average" mode there are three sub-modes
 1 minute, 5 minute, or continous. There are three unused digital
 inputs that can easily be used for all these choise user inputs
 (others could be retasked but three is enough). To select the
 mode/submode two inputs are used as shown:
 
 Reset button
 |  o \                       Survey
 /       |--o  \                      5min Average
 GRND ------ .------|  o   \o---------- D12      1min Average
 |   |--o                         Continuous
 |   
 |   |  o  \
 |   |  o   \
 |---|--o    \o--------- D11
 |--o
 Note that this uses a 2P4T switch and that you may want to add a 10k
 ohm resister as pull up on D11 and D12 (but should not be needed since
 328p has them). Also note that there is a trick to get the reset
 functionality for the three average modes. When the mode switches the
 counters are reset to zero. By pressing the reset button the mode is
 tempararily switched to Survey more. For standard/metric mode a
 simple 1P1T switch goes between ground and D13.
 
 The circuit: Powered at 5V (5V LCD), Arduino running at 16MHz
 D2 - RS (LCD)
 D3 - R/W (LCD)
 D4 - E (LCD)
 D5 - Frequency input
 D6 - DB4 (LCD)
 D7 - DB5 (LCD)
 D8 - DB6 (LCD)
 D9 - DB7 (LCD)
 D10 - Gate of NPN transistor (Collector tied to 5V, emitter tied to LCD backlight pin)
 (could be D10 - RESET COUNT input)
 D11 - CPM 5 minute input 11 CPS, 01 CPM1, 10 CPM5, 00 count
 D12 - CPM 1 minute input  "        "        "        "
 D13 - metric input  1 = standard, 0 = metric
 For the LiquidCrystal library, much thanks to:
 David A. Mellis
 Limor Fried (http://www.ladyada.net)
 Tom Igoe
 */

//#include <LiquidCrystal.h>

#include <SoftwareSerial.h>
// Used to support a serial LCD (Sparkfun LCD-10097)
// These definitions will be used by SoftwareSerial. Only the txPin is used. Pick ones that make sense for you.
#define rxPin 3
#define txPin 2

// initialize the library with the numbers of the interface pins
//LiquidCrystal lcd(2, 3, 4, 6, 7, 8, 9);
unsigned int tovf1 = 0;
unsigned long counts = 0;
unsigned long temp = 0;
unsigned long curmilli = 0;
unsigned long prevmilli = 0;
unsigned long back_prevmilli = 0;
unsigned long secs = 0;
unsigned long interval = 1000;
boolean ncpm5 = HIGH;
boolean ncpm1 = HIGH;
boolean  tmpncpm1 = ncpm1;
boolean  tmpncpm5 = ncpm5;

boolean nmetric = LOW;

int cpmcps = 0;          //                 "
unsigned long whole;  // Used when simulating decimals in with integer math
unsigned long frac;   //             "
unsigned long fact;

// Build our serial object to write to
SoftwareSerial lcd = SoftwareSerial(rxPin, txPin);

// Timer 1 is our counter
// 16-bit counter overflows after 65536 counts
// tovfl will keep track of how many times we overflow
ISR(TIMER1_OVF_vect)
{
  tovf1++;
}

void setup() {
  pinMode(rxPin, INPUT);  // sofware serial in/out pins
  pinMode(txPin, OUTPUT);
  pinMode(5, INPUT);   // This is the counts input
  pinMode(10, OUTPUT);  // Reset count WAS Backlight control pin
  pinMode(11, INPUT);  // CPM 5 minute
  pinMode(12, INPUT);  // CPM 1 minute
  pinMode(13, INPUT);  // Roentgen/Sievert select ("metric")

  // Timer 1 will be setup as a counter
  // Maximum frequency is Fclk_io/2
  // (recommended to be < Fclk_io/2.5)
  // Fclk_io is 16MHz
  TCCR1A = 0;
  // External clock source on D5, trigger on rising edge:
  // NOTE: the Sparkfun Geiger would probably be best read on a falling edge...
  // It's normally high, going low for each count.
  TCCR1B = (1<<CS12) | (1<<CS11) | (1<<CS10);   
  // Enable overflow interrupt
  // Will jump into ISR(TIMER1_OVF_vect) when overflowed:
  // NOTE: if the Geiger puts us in overflow, you're probably dead...
  TIMSK1 = (1<<TOIE1);

  // Reset all counter variables to start at zero
  TCNT1H = 0;
  TCNT1L = 0;

  // set up the LCD's number of rows and columns:
  //lcd.begin(16, 2);
  // set up the LCD pins to run at 9600
  lcd.begin(9600);
  //DEBUG
  Serial.begin(9600);
  // Position the cursor to top left
  clearLCD();
  SetCur(0);
  // Print a splash screen to the LCD.
  lcd.print("GeigerCounter   ");
  //lcd.setCursor(0, 1);
  SetCur(16);
  lcd.print("      v1.4      ");
  delay(2000);
  clearLCD();

  ncpm5 = digitalRead(11);  
  ncpm1 = digitalRead(12);

  if(ncpm1 == HIGH && ncpm5 == HIGH)       // Survey mode every second
    interval = 1000  ;
  else if(ncpm5 == LOW && ncpm1 == HIGH) // 1 minute averge 60 seconds
    interval = 60000;
  else if(ncpm5 == HIGH && ncpm1 == LOW) // 5 minute averge 300 seconds
    interval = 300000;
  else interval = 3600000;                 // Both LOW: 60 minute average 3600 seconds.
  
  prevmilli = millis();
  back_prevmilli = prevmilli;
  curmilli = prevmilli;

}

void loop() {
  
  unsigned long cycle_counts = 0;

  curmilli = millis();

  // Both selects HIGH: Survey mode
  // Pin 11 HIGH, Pin 12 LOW: 5 minute mode
  // Pin 12 HIGH, Pin 11 LOW: 1 minute mode
  // Both LOW: 60 minute mode

  // Survey mode:::
  if(interval == 1000) {    // Update every second. No rolling averages, no history. 
    if(curmilli - prevmilli > interval) { // Every second we update the display.
      nmetric = digitalRead(13); // Get current energy display mode: 'metric' or 'standard'.

      // Compute and display.
      counts = (TCNT1H<<8)|TCNT1L;

      //lcd.clear();
      // Clear and position to 0
      clearLCD();
      SetCur(0);

      lcd.print("Count/Sec ");
      lcd.print(counts);

      //lcd.setCursor(0,1);
      SetCur(16);

      // Computing radiation exposure here, from the spec sheet for
      // the Sparkfun tube ND712. While it's hard to read the chart exactly,
      // it looks like the following:
      // 1 CPS = .000055 R/hr  (Roentgens). This ratio appears to be fairly constant
      // until we hit around 1150 CPS. There, it begins to slope rapidly into "glow" territory:
      // 10000 CPS = 4 R/hr
      // For now, I'm not going to correct for high counts: I'll be running too fast to care.

      // We avoid using floating point here since it is too slow
      // and big for a smaller imbedded processor. Instead we adjust
      // all math by shifting decimal place to the left.

      // Common math
      temp = counts * 100000;
      temp = temp * 55;  // conversion to R for the Sparkfun

      if(nmetric) { 
        // Display Sieverts
        temp = temp / 1000000; 
        temp = temp * 877;
        temp = temp / 10000;  // the answer in micro Sieverts
        if (temp < 1000){
          // Print the uSv
          lcd.print("uSv/hr ");
          lcd.print(temp);
        } 
        else {
          fact = 1000;      // three decimal places to the left and we have milli Sieverts
          whole = temp/fact;
          frac = temp % fact;
          lcd.print("mSv/hr ");
          lcd.print(whole);
          lcd.print(".");
          lcd.print(frac);
        }
      }
      else{ 
        // Translate to micro Roentgen
        temp = temp / 100000;
        if (temp < 1000){
          // Just print the uR.
          lcd.print("uR/hr  ");
          lcd.print(temp);      
        } 
        else {  
          // We have mR. Break it into the whole and decimal portion.
          fact = 1000;
          whole = temp/fact;
          frac = temp % fact;
          lcd.print("mR/hr  ");
          lcd.print(whole);
          lcd.print(".");
          lcd.print(frac);
        } 
      }

      // Reset all counter variables and start over for every sample
      TCNT1H = 0;
      TCNT1L = 0;
      tovf1 = 0;
      prevmilli = millis();
      back_prevmilli = prevmilli;
      // Read mode switch settings - won't do anything until after this loop, but only want to read oncce a second.
      tmpncpm1 = ncpm1;
      tmpncpm5 = ncpm5;
      ncpm5 = digitalRead(11);  
      ncpm1 = digitalRead(12);

    }  
  }  // end of survey processing
  else { 
    //
    // Background Average mode
    //
    // We only reset the collected data at the end of average period
    if((curmilli - back_prevmilli) > interval) { 
      
      // Reset all counter variables
      TCNT1H = 0;
      TCNT1L = 0;
      tovf1 = 0;
      counts = 0;
      secs = 0;
      prevmilli = millis();
      back_prevmilli = prevmilli;
      cycle_counts = 0;
    }  

    // We update the display and collected count every second
    if((curmilli - prevmilli) > 1000) { 
      nmetric = digitalRead(13); // Get current energy display mode: 'metric' or 'standard'.

      // We're updating the display.
      counts = (TCNT1H<<8)|TCNT1L;

      secs++; // Total number of seconds for this collection period

      clearLCD();
      SetCur(0);

      // Display the CPS and CPM 
      // This is the RUNNING AVERAGE! Not the last second...
      lcd.print("CPS ");
      temp = counts/secs;
      if (temp < 1) 
        lcd.print("<0"); 
      else 
        lcd.print(temp);
        
      lcd.print(" CPM ");
      if (secs > 60) {
        temp = (counts/(secs/60));
      }
      else temp = counts;
      if (temp > 9999) {
        lcd.print(">10k");
      } else {
        lcd.print(temp);
      }
      if (temp > 100) {
        // High counts. Light an LED on LadyAda's Arduino protoshield. Do as you see fit...
        digitalWrite(10, HIGH);  // Turn led on
      } else {
        digitalWrite(10, LOW);  // Turn led off
      }

      //lcd.setCursor(0,1);
      SetCur(16);

      // Common math
      temp = counts/secs * 100000;
      temp = temp * 55;  // conversion to R for the Sparkfun

      if(nmetric) { 
        // Display Sieverts
        temp = temp / 1000000; 
        temp = temp * 877;
        temp = temp / 10000;  // the answer in micro Sieverts
        if (temp < 1000){
          // Print the uSv
          lcd.print("uSv/hr ");
          lcd.print(temp);
        } 
        else {
          fact = 1000;      // three decimal places to the left and we have milli Sieverts
          whole = temp/fact;
          frac = temp % fact;
          lcd.print("mSv/hr ");
          lcd.print(whole);
          lcd.print(".");
          lcd.print(frac);
        }
      }
      else{ 
        // Translate to micro Roentgen
        temp = temp / 100000;

        if (temp < 1000){
          // Just print the uR.
          lcd.print("uR/hr ");
          lcd.print(temp);        
        } 
        else {  
          // We have mR. Break it into the whole and decimal portion.
          fact = 1000;
          whole = temp/fact;
          frac = temp % fact;
          lcd.print("mR/hr  ");
          lcd.print(whole);
          lcd.print(".");
          lcd.print(frac);
        }  
      }

      prevmilli = curmilli;
      // Print averaging period
      SetCur(29);
      if (interval == 60000) lcd.print("P01");
      if (interval == 300000) lcd.print("P05");
      if (interval == 3600000) lcd.print("P60");
      // Read mode switch settings - won't do anything until after this loop, but only want to read oncce a second.
      tmpncpm1 = ncpm1;
      tmpncpm5 = ncpm5;
      ncpm5 = digitalRead(11);  
      ncpm1 = digitalRead(12);

    }  // end of update background display

  }  // end of background processing

  // Any mode changes from last time here?

  if((tmpncpm1 != ncpm1) || (tmpncpm5 != ncpm5)) {
    // YES

    // The new collection time

    if(ncpm1 == HIGH && ncpm5 == HIGH)       // Survey mode every second
      interval = 1000  ;
    else if(ncpm1 == HIGH && ncpm5 == LOW) // 1 minute averge 60 seconds
      interval = 60000;
    else if(ncpm1 == LOW && ncpm5 == HIGH) // 5 minute averge 300 seconds
      interval = 300000;
    else interval = 3600000;                 // Both LOW: 60 minute average 3600 seconds.

    // Reset all counter variables and start over.
    // We need to do this since units may have changed, and the time period has.

    TCNT1H = 0;
    TCNT1L = 0;
    tovf1 = 0;
    counts = 0;
    secs = 0;
    tmpncpm1 = ncpm1;
    tmpncpm5 = ncpm5;
    prevmilli = curmilli = back_prevmilli = millis();
    clearLCD();
  }

}

void SetCur(int position) { //position = line1: 0-15, line 2: 16-31. 31+ defaults back to 0.
  if (position>31) {
    SetCur(0); // default for too large value
  } 
  else {
    lcd.print(0xFE, BYTE); // command flag
    lcd.print(0x80, BYTE); // position command
    lcd.print(position, BYTE); // new cursor position
  }
}

void clearLCD(){
  lcd.print(0xFE, BYTE);  // command flag
  lcd.print(0x01, BYTE);  // clear command
}

Hi guys,

Is this code to be loaded in the ATMEGA of the geiger counter itself or should it be uploaded to an additional arduino?

Thank you so much for any info concerning this,

zws

This code is running on an external board. I’ve not experimented with running it on the counter itself, but as this code also deals with interrupts without notable issues I suspect it might work on the board itself. There would, of course, have to be some mods, and I would want to tighten the code up.

Let us know if you try and what you find!

Kevin

Thank you, Kevin.

For now I intend follow the usual steps. I wanted to upload the new firmware (v13) as my geiger counter came with v12. I am stuck. Do you maybe know how I can achieve this?

Afterwards I would like to try your methods where it would work besides an external board before thinking about running it on the counter itself (where it would mean I’d have to learn how to burn the arduino bootloader to the counter chip, right?).

Any help with my problem is highly appreciated.

zws

I forgot to mention that I am really interested in your posts. And in due time I will tell you what I managed to achieve or will come with additional questions, of course! :stuck_out_tongue:

Hello all,

I have been running the kit for a couple of months now. The first few weeks had a steady radiation level and seemed reasonable. In the last month and a half, the level has been rising at a constant rate. I haven’t touched the sensor since its been turned on and its located indoors. I have rebooted the Arduino and sensor board several times but it continues its uphill climb. See my data here: https://pachube.com/feeds/28570 . Anyone else seeing this behavior?

Thanks!

I made a new version running on the target atmega328 of the G-M module. A 16*2 LCD module is connected through a shift register. This way it is a standalone unit.

George Fenyvesi

    /*
   version 1.5
     Modified April, 2012 by gyorgy.fenyvesi@gmail.com
     * The program is running on the target atmega368 on the G-M module after burning an Arduino bootloader onto it. It is a luxury to use another one for doing almost nothing.
     * The results are displayed on a 16*2 LCD connected through a HEF4094/CD4094 latching shift-register; PIN 11,12,13 (Enable, Data, Clock).
     * The library used for LCD shift register: LiquidCrystal_SR_LCD3.
     * Because of the fact that there is no more free PINs (except of serial PINs) to connect a switch, the mode is fixed at 1 min moving average mode rewritten. The max of CPM is shown.
       The Survey mode is in the program, if somebody wants to use it, but it is not active.
     * If the average CPM is 20 and the upper limit of the actual G-M module is ~3600, it is worth to count the overflow(!) of a 16 bit counter to measure radiation only 
         in the case if the rats would check it after everybody died. So I simplified. Practically only the calculation of radiation remained from v1.4.
     
    Version 1.4
     Modified March, 2011 by Kevin Quinn
     * fixed a few bugs
     * changed to support a Sparkfun serial LCD module using SoftwareSerial. Someone needs to write
     a modified LiquidCrystal library to support serial LCDs... :-)
     * note: I had a lot of trouble with noise on the Arduino input pin that reads the digital out from the geiger counter.
     I found adding a 10K resistor between the two resolved my issues.
     * changed the use of the constants 'true' and 'false' for the digital I/O pins to 'HIGH' and 'LOW'. 'HIGH' means 'selected' or
     set to Vcc.
     * NOTES: 'Survey' mode means counting second to second; no history is kept. 'Background' modes "average" the counts
     *        over the time period selected. In this version
     *        there's one, five, and sixty minute background averageing modes.
     *        In these modes, we display the running Counts per Second, Counts per Minute, and on the next line
     *        u or m Sieverts or Roentgens per hour as selected by the "metric" switch. Please note that even though we may be registering an average
     *        CPM>0, the running average CPS has to be > 0 to compute R/hr or Sv/hr: inteter math!
     *
     *        Switch Settings:
     *          11 HIGH, 12 LOW: 5 minute background averaging mode
     *          11 LOW, 12 HIGH: 1 minute background averaging mode
     *          11 LOW, 12 LOW: 60 minute background averaging mode
     *          11 HIGH, 12 HIGH: 'Survey' one second reading mode
     *
     * For the ND-712 tube used in the Sparkfun geiger counter, we know the following dry air dose rates for Co60 gamma response:
     * 10^0 CPS = .000055 R/hr (Roentgen)
     * 10^1 CPS = .00055 R/hr
     * 10^3 CPS = .055 R/hr
     * The next step ramps up sharply. Time to run...
     * 10^4 CPS = 4 R/hr
     *
     * Using the following conventions, derived as best I can from various internet sources:
     * - assuming dry air exposure
     * - assuming gamma
     * - using that assumption, Roentgen converts to the following absorbed dose equivalents:
     * - 1 R = 0.877 rem
     * -     = 0.00877 Sievert
     * -     = 0.877 rad
     * - Some simply round to 1 R = 1 rad = 1 rem. I've used the .877 factor... change as you see fit!
       
     Geiger Counter Arduino Sketch
     by: Barry Harding
     Feb 12 2011
     This started from the following project:
     
     Frequency Counter Arduino Sketch
     by: Jim Lindblom
     SparkFun Electronics
     License: Beerware
     
     This is basically a modified (heavily so)version of the SparkFun
     Electronics "Frequency Counter Arduino Sketch". This has been
     used on the "Frequency Counter Kit sku: KIT-10140" connected to
     the "Geiger Counter sku: SEN-09848". But any Geiger counter
     circuit with digital out (+5v TTL) should work.
     
     It has two modes of operation, "Survey" and "Background average"
     mode. It can also display measured energy in standard or metric
     units. For "Background average" mode there are three sub-modes
     1 minute, 5 minute, or continous. There are three unused digital
     inputs that can easily be used for all these choise user inputs
     (others could be retasked but three is enough). To select the
     mode/submode two inputs are used as shown:
     
     Reset button
     |  o \                       Survey
     /       |--o  \                      5min Average
     GRND ------ .------|  o   \o---------- D12      1min Average
     |   |--o                         Continuous
     |   
     |   |  o  \
     |   |  o   \
     |---|--o    \o--------- D11
     |--o
     Note that this uses a 2P4T switch and that you may want to add a 10k
     ohm resister as pull up on D11 and D12 (but should not be needed since
     328p has them). Also note that there is a trick to get the reset
     functionality for the three average modes. When the mode switches the
     counters are reset to zero. By pressing the reset button the mode is
     tempararily switched to Survey more. For standard/metric mode a
     simple 1P1T switch goes between ground and D13.
     
     The circuit: Powered at 5V (5V LCD), Arduino running at 16MHz
     D2 - RS (LCD)
     D3 - R/W (LCD)
     D4 - E (LCD)
     D5 - Frequency input
     D6 - DB4 (LCD)
     D7 - DB5 (LCD)
     D8 - DB6 (LCD)
     D9 - DB7 (LCD)
     D10 - Gate of NPN transistor (Collector tied to 5V, emitter tied to LCD backlight pin)
     (could be D10 - RESET COUNT input)
     D11 - CPM 5 minute input 11 CPS, 01 CPM1, 10 CPM5, 00 count
     D12 - CPM 1 minute input  "        "        "        "
     D13 - metric input  1 = standard, 0 = metric
     For the LiquidCrystal library, much thanks to:
     David A. Mellis
     Limor Fried (http://www.ladyada.net)
     Tom Igoe
     */
     
#include <LiquidCrystal_SR_LCD3.h>

// the following PINs (from prgaramming interface) are connected to the shift register.
const int PIN_LCD_STROBE         =  11;  // Out: LCD IC4094 shift-register strobe 
const int PIN_LCD_DATA           =  12;  // Out: LCD IC4094 shift-register data 
const int PIN_LCD_CLOCK          =  13;  // Out: LCD IC4094 shift-register clock 

// srdata / srclock / strobe
LiquidCrystal_SR_LCD3 lcd(PIN_LCD_DATA, PIN_LCD_CLOCK, PIN_LCD_STROBE);

    volatile unsigned long counts = 0;
    unsigned long temp = 0;
    unsigned long curmilli = 0;
    unsigned long prevmilli = 0;
    unsigned long back_prevmilli = 0;
    unsigned long secs = 0;
    unsigned long interval = 1000;
    boolean       survey = false;
    boolean nmetric = HIGH;

    unsigned long whole;                        // Used when simulating decimals in with integer math
    unsigned long frac;   //             "
    unsigned long fact;

    byte sec_counts[60];                        // to store the CPS-s, the minute average is calculated from this array
    unsigned int t;
    unsigned int cpm_max;                      // the highest CPM
    
    void setup() {

      pinMode(A5, OUTPUT);                      // redefine Analog 5 to digital, because LED is connected to A5
      digitalWrite(A5, HIGH);                  // Turn led off

      pinMode(2, INPUT);                       // This is the G-M counts input, interrupt
      attachInterrupt(0, sensing, FALLING);    //int0, PD2

      lcd.begin(16, 2);                       // initialize the lcd 
      lcd.home ();                            // go home
      lcd.setCursor (0, 0);
      // Print a splash screen to the LCD.
      lcd.print(F("GeigerCounter"));
      lcd.setCursor(0, 1);
      lcd.print(F("      v1.5"));
      delay(2000);
      lcd.clear();

      if (survey)
        interval = 1000  ;
      else // 1 minute averge 60 seconds
        interval = 60000;
     
      prevmilli = millis();
      back_prevmilli = prevmilli;
      curmilli = prevmilli;
    }

void loop() 
{
      curmilli = millis();

      if(survey)     // Survey mode, Update every second. No rolling averages, no history.
          update_count_per_second();
      else // Background Average mode
         Background_Average();
}
   
void sensing()    // interrupt routine, initiated by the G-M
    {
      digitalWrite(A5, LOW);  // LED is on PC5, Analog input 5, Turn led on
      counts++;
      digitalWrite(A5, HIGH);  // Turn led off
    }
 
 void    update_count_per_second()        
  {
        if(curmilli - prevmilli > interval)  // Every second we update the display.
        {
          lcd.clear();
          lcd.setCursor(0,0);
          lcd.print("Count/Sec ");
          lcd.print(counts);
          lcd.setCursor(0,1);

          // Computing radiation exposure here, from the spec sheet for the Sparkfun tube ND712. While it's hard to read the chart exactly, it looks like the following:
          // 1 CPS = .000055 R/hr  (Roentgens). This ratio appears to be fairly constant until we hit around 1150 CPS. There, it begins to slope rapidly into "glow" territory:
          // 10000 CPS = 4 R/hr
          // For now, I'm not going to correct for high counts: I'll be running too fast to care.

          // We avoid using floating point here since it is too slow and big for a smaller imbedded processor. Instead we adjust all math by shifting decimal place to the left.
          // Common math
          temp = counts * 100000;
          temp = temp * 55;  // conversion to R for the Sparkfun

          if(nmetric) // Display Sieverts
          {  
            temp = temp / 1000000;
            temp = temp * 877;
            temp = temp / 10000;  // the answer in micro Sieverts
            if (temp < 1000)      // Print the uSv
            {
              lcd.print("uSv/h ");
              lcd.print(temp);
            }
            else {
              fact = 1000;      // three decimal places to the left and we have milli Sieverts
              whole = temp/fact;
              frac = temp % fact;
              lcd.print("mSv/h ");
              lcd.print(whole);
              lcd.print(".");
              lcd.print(frac);
            }
          }
          else // Translate to micro Roentgen
          {
            temp = temp / 100000;
            if (temp < 1000)              // Just print the uR.
            {
              lcd.print("uR/h  ");
              lcd.print(temp);     
            }
            else               // We have mR. Break it into the whole and decimal portion.
            {
              fact = 1000;
              whole = temp/fact;
              frac = temp % fact;
              lcd.print("mR/h  ");
              lcd.print(whole);
              lcd.print(".");
              lcd.print(frac);
            }
          }

          // Reset all counter variables and start over for every sample
          counts = 0;
          prevmilli = millis();
          back_prevmilli = prevmilli;
          // Read mode switch settings - won't do anything until after this loop, but only want to read oncce a second.
        } 
   }  // end of survey processing

void Background_Average()
{

byte i;        
     if((curmilli - back_prevmilli) > interval) // We only reset the collected data at the minute  Reset all counter variables
        {           
          counts = 0;
          secs = 0;
          prevmilli = millis();
          back_prevmilli = prevmilli;
        } 
        if((curmilli - prevmilli) > 1000)  // We update the display and collected count every second
        {
          sec_counts[secs] = counts;        // store the CPS for calculating average

          lcd.clear();
          lcd.setCursor(0,0);
          lcd.print("CPS:");
          lcd.print(counts);

          secs++;                           // Total number of seconds in the minute
          counts = 0;                        // the count of the second  is already stored        

          lcd.print(" CPM:");
          t = 0;                // calculate moving average of the minute CPM
          for (i=0;i<60;i++)
            t += sec_counts[i];  // moving average CPM value
          if (t > cpm_max)        // store the max
            cpm_max = t;
            
          if (t > 9999) 
            lcd.print(">10k");
          else 
            lcd.print(t);
//          if (t > 100) // High counts. Light an LED on LadyAda's Arduino protoshield. Do as you see fit...
//            digitalWrite(10, HIGH);  // Turn led on
//          else 
//            digitalWrite(10, LOW);  // Turn led off

          lcd.setCursor(0,1);

          // Common math
//          temp = counts/secs * 100000;
//          temp = t/60 * 100000;
          temp = (t * 100000) /60;
          temp = temp * 55;  // conversion to R for the Sparkfun

          if(nmetric) // Display Sieverts
           {          
            temp = temp / 1000000;
            temp = temp * 877;
            temp = temp / 10000;  // the answer in micro Sieverts
            if (temp < 1000)  // Print the uSv
            {        
              lcd.print("uSv/h:");
              lcd.print(temp);
            }
            else 
            {
              fact = 1000;      // three decimal places to the left and we have milli Sieverts
              whole = temp/fact;
              frac = temp % fact;
              lcd.print("mSv/h:");
              lcd.print(whole);
              lcd.print(".");
              lcd.print(frac);
            }
          }
          else  // Translate to micro Roentgen
          {            
            temp = temp / 100000;
            if (temp < 1000) // Just print the uR.
            {             
              lcd.print("uR/hr ");
              lcd.print(temp);       
            }
            else // We have mR. Break it into the whole and decimal portion.
            {               
              fact = 1000;
              whole = temp/fact;
              frac = temp % fact;
              lcd.print("mR/h  ");
              lcd.print(whole);
              lcd.print(".");
              lcd.print(frac);
            } 
          }
          prevmilli = curmilli;
          
          lcd.print(" M:"); // Print max CPM
          lcd.print(cpm_max);
        }  // end of update background display
}  // end of background processing