2012 m. rugpjūčio 31 d., penktadienis
2012 m. rugpjūčio 27 d., pirmadienis
Software sketch for the Geiger counter with updated shield :DiY-GDC1.2.1
to upload the below sketch you need to download and place these libraries in your Arduino IDE installation's libraries folder
the sketch as a file can be downloaded here
/* GeigerKit_Default sketch (v.8.0) bHogan 6/22/12
* Adjusted for LCD shield P2-GCS1-040512 impexeris on ebay.com on 08/21/12
* Adjustments for P2-GCS1-040512 only concern Alarm and button BUTTON_PIN change from 10 to 11 and ALARM_PIN change from 15 to 10
* Tactile button should be used on the P2-GCS1-040512 PCB instead of the top-right alarm LED and corresponding current limiting resistor should be removed)
* This sketch was written for the DIYGeigerCounter Kit.
* It requires the Arduino IDE rel. 1.0.0 or above to compile.
* FEATURES:
* CPM & uSv/hr output to LCD display and serial port. Bar graph on LCD. Low voltage warning.
* Counts are displayed every 5 sec using a moving average which is reset every 30 sec.
* An alternate display shows a running average for the previous 1 and 10 minute periods.
* One of 2 CPM to uSv/h conversion ratios can be selected via jumper. ACCURACY NOT VERIFIED
* This version adds a configurable alarm, new alt screen, and a 'realtime' bargraph.
* It also supports the SensorGraph app on the Android.
* More info is available at: http://sites.google.com/site/diygeigercounter/home
* SETUP: (Any 2x16 Hitachi HD44780 compatible LCD)
* +5V LCD Vdd pin 2 >>> Gnd LCD Vss pin 1, and R/W pin 5
* LCD RS pin 4 to D3 >>> LCD Enable pin 6 to D4
* LCD D4 pin 11 to D5 >>> LCD D5 pin 12 to D6
* LCD D6 pin 13 to D7 >>> LCD D7 pin 14 to D8
* LCD LEDA pin 15 to ~1K to +5 >>> LCD LEDK pin 16 to GND
* 10K pot: - ends to +5V and Gnd, wiper to LCD VO pin (pin 3)
* *INT from Geiger circuit is connected to PIN 2 and triggered as FALLING.
* PIN 9 - jumper to GND if secondary conversion ratio is desired
* PIN 10 - use button for alt display and to set alarm. (see site)
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* Do not remove information from this header.
*
* THIS PROGRAM AND IT'S MEASUREMENTS IS NOT INTENDED TO GUIDE ACTIONS TO TAKE, OR NOT
* TO TAKE, REGARDING EXPOSURE TO RADIATION. THE GEIGER KIT AND IT'S SOFTWARE ARE FOR
* EDUCATIONAL PURPOSES ONLY. DO NOT RELY ON THEM IN HAZARDOUS SITUATIONS!
*/
#include <MeetAndroid.h> // connects Geiger to SensorGraph on Android
#include <LiquidCrystal.h> // HD44780 compatible LCDs work with this lib
#include <EEPROM.h> // alarm setting is stored in EEPROM
#define DEBUG false // if true, shows available memory
#define TUBE_SEL 9 // jumper to select alt conversion to uSv
#define BUTTON_PIN 11 // button to toggle alternate display and set alarm (Pin number changed from 10 to 11 for the LCD shield P2-GCS1-040512)
#define LED_PIN 13 // for debug only - flashes 5X at startup
#define ALARM_PIN 10 // Outputs HIGH when Alarm triggered (Pin number changed from 15 to 10 for the LCD shield P2-GCS1-040512)
#define ALARM_SET_ADDR 8 // address of alarm setting in EEPROM
#define MAX_ALARM 600 // max the alarm can be set for
// HOW MANY CPM =1 uSV? The commonly used ratios for the SBM-20 & LND712 are defined:
#define PRI_RATIO 175.43 // no TUBE_SEL jumper - SET FOR SBM-20
#define SEC_RATIO 100.00 // TUBE_SEL jumper to GND - SET FOR LND712
// mS between writes to serial - counts also accumulate seperately for this period
#define LOGGING_PEROID 60000 // 1 min (60000 mS) best - unless testing
// mS between writes to display - counts/period are averaged to CPM
#define DISP_PERIOD 5000.0 // (5 sec) mS sample & display
#define FULL_SCALE 1000 // max CPM for all 7 bars
#define LOW_VCC 4200 //mV // if Vcc < LOW_VCC give low voltage warning
#define ONE_MIN_MAX 12 // elements in the oneMinute accumulater array
#define TEN_MIN_MAX 120 // elements in the tenMinute accumulater array
#define DEBOUNCE_MS 50 // buttom debounce period in mS
boolean lowVcc = false; // true when Vcc < LOW_VCC
boolean altDispUsed = false; // used to start counts from zero
boolean altDispOn = false; // true when SW_1 on, and in continous count mode
boolean dispOneMin = false; // true if enough time passed to begin displaying counts
boolean dispTenMin = false;
boolean AlarmOn = false; // CPM > set alarm
byte sampleCnt; // the number of samples making up the average
byte altDispCnt = 0; // controls when alt display is on
int oneMinute[ONE_MIN_MAX]; // used to accumulate 1 & 10 minute counts
int oneMinuteIndex = 0;
int tenMinute[TEN_MIN_MAX];
int tenMinuteIndex = 0;
int AlarmPoint; // CPM alarm is set for
int Vcc_mV; // mV of Vcc from last check
int androidReturn = 0; // reads the slider on Android SensorGraph.
volatile unsigned long dispCnt, logCnt; // inc by ISR
volatile unsigned long runCnt, barCnt;
long runCountStart;
unsigned long tempSum;
unsigned long dispPeriodStart, dispCPM; // counters for the display period . . .
unsigned long logPeriodStart, logCPM;
unsigned long checkVccTime; // counter for how often to check voltage
unsigned long altDispStart; // counter for alt display refresh period
unsigned long lastButtonTime; // counter for pressing the button to quickly
unsigned long barGraphStart; // counter for bargraph refresh period
float uSv = 0.0; // display CPM converted to "unofficial" uSv
float uSvLogged = 0.0; // logging CPM converted to "unofficial" uSv
float uSvRate; // holds the rate selected by jumper
float avgCnt = 0.0; // holds the previous average count
float temp_uSv = 0.0;
//Custom characters used for bar graph
byte bar_0[8] = {
0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00}; //blank
byte bar_1[8] = {
0x10, 0x10, 0x18, 0x18, 0x18, 0x10, 0x10, 0x00}; //1 bar
byte bar_2[8] = {
0x18, 0x18, 0x1c, 0x1c, 0x1c, 0x18, 0x18, 0x00}; //2 bars
byte bar_3[8] = {
0x1C, 0x1C, 0x1e, 0x1e, 0x1e, 0x1C, 0x1C, 0x00}; //3 bars
byte bar_4[8] = {
0x1E, 0x1E, 0x1f, 0x1f, 0x1f, 0x1E, 0x1E, 0x00}; //4 bars
byte bar_5[8] = {
0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x00}; //5 bars
byte bar_6[8] = {
0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x00}; //6 bars (same)
// instantiate the library and pass pins for (RS, Enable, D4, D5, D6, D7)
LiquidCrystal lcd(3, 4, 5, 6, 7, 8); // default layout for the Geiger board
MeetAndroid Android;
void setup(){
Serial.begin(9600); // comspec 96,N,8,1
attachInterrupt(0,GetEvent,FALLING); // Geiger event on pin 2 triggers interrupt
pinMode(LED_PIN,OUTPUT); // setup LED pin
pinMode(TUBE_SEL,INPUT); // setup tube select jumper pin
pinMode(BUTTON_PIN,INPUT); // setup menu button
pinMode(ALARM_PIN, OUTPUT); // setup Alarm pin
digitalWrite(TUBE_SEL, HIGH); // set 20K pullup on jumper pins(low active)
digitalWrite(BUTTON_PIN, HIGH);
Blink(LED_PIN,4); // show it's alive
lcd.begin(16,2); // cols, rows of display (8x2, 16x2, etc.)
lcd.createChar(0, bar_0); // load 7 custom characters in the LCD
lcd.createChar(1, bar_1);
lcd.createChar(2, bar_2);
lcd.createChar(3, bar_3);
lcd.createChar(4, bar_4);
lcd.createChar(5, bar_5);
lcd.createChar(6, bar_6);
lcd.setCursor(0,0);
Android.registerFunction(androidInput,'o'); // calls androidInput() when new input frpm droid
clearDisp(); // clear the screen
lcd.print(" Ver. 8.0"); // display a simple banner
lcd.setCursor(0,1); // set cursor on line 2
lcd.print(" LCD Shield rev."); // display the version
delay (1500); // leave the banner up for a bit
clearDisp(); // clear the screen
if(digitalRead(TUBE_SEL)){ // read jumper to select conversion ratio
uSvRate = PRI_RATIO; // use the primary ratio defined
}
else{ // jumper is set to GND . . .
uSvRate = SEC_RATIO; // use the secondary ratio defined
}
lcd.print(uSvRate,0); // display conversion ratio in use
lcd.print(" CPM to uSv");
lcd.setCursor(0,1); // set cursor on line 2
Vcc_mV = readVcc(); // read Vcc voltage
lcd.print("Running at "); // display it
lcd.print(Vcc_mV/1000. ,2); // display as volts with 2 dec. places
lcd.print("V");
delay (2000); // leave info up for a bit
#if (DEBUG) // show available SRAM if DEBUG true
clearDisp();
lcd.print("RAM Avail: ");
lcd.print(AvailRam());
delay (2000);
#endif
// this section tests if button pressed to set alarm threshold and calls a function to change it
AlarmPoint = EEPROM.read(ALARM_SET_ADDR); // get last alarm value from EEPROM
if (AlarmPoint == 255) AlarmPoint = 0; // deal with virgin EEPROM
AlarmPoint = AlarmPoint * 10; // values stored are 1/10th
clearDisp(); // put up new alarm set screen
lcd.print("Set Alarm?");
lcd.setCursor(0, 1);
if (AlarmPoint >0){
lcd.print("Now ");
lcd.print(AlarmPoint);
lcd.print(" CPM");
}
else lcd.print("Now Off");
long timeIn = millis(); // you have 3 sec to push button or move on
while (millis() < timeIn + 3000) {
if (readButton(BUTTON_PIN)== LOW) setAlarm(); // alarm is to be set
} // if no button press continue
clearDisp(); // clear the screen
lcd.print("CPM? "); // display beginning "CPM"
Serial.println("CPM \t uSv \t Vcc"); // print header for log
dispPeriodStart = millis(); // start timing display CPM
logPeriodStart = dispPeriodStart; // start logging timer
checkVccTime = dispPeriodStart; // start Vcc timer
runCountStart = dispPeriodStart; // start alternate timer
barGraphStart = dispPeriodStart; // start bargraph timer
dispCnt = 0; // start with fresh totals
logCnt= 0;
runCnt = 0;
barCnt = 0;
}
void loop(){
//uncomment 5 lines below for self check - you should see very close to 360 CPM
//dispCnt++;
//logCnt++;
//runCnt++;
//barCnt++;
//delay(167); // 167 mS = 6 Hz `= X 60 = 360 CPM
if (millis() >= lastButtonTime + 500){ // wait a bit between button pushes
lastButtonTime = millis(); // reset the period time
if (readButton(BUTTON_PIN)== LOW){ // start alt display mode if button pin is low
clearDisp(); // clear the screen when switching displays
altDispOn = !altDispOn; // toggle altDispOn state
if (altDispUsed == false){
altDispUsed = true; // false until first used
runCnt = 0; // clear counts until used
runCountStart = millis();
}
if (altDispOn) DispRunCounts(); // start alt display immediately - or
else DispCounts(dispCnt); // start main display immediately
}
}
if (millis() >= barGraphStart + 1000){ // refresh bargraph, alarm and Vcc if in main display
if (!altDispOn)fastDisplay(); // display quick response data
barCnt = 0; // reset counts
barGraphStart = millis(); // reset the period time
}
if (millis() >= altDispStart + 5000){ // refresh alt display if it's on
if (altDispOn) DispRunCounts(); // display running counts
altDispStart = millis(); // reset the period time
}
if (millis() >= runCountStart + 5000){ // Collect running counts every 10 sec.
RunningCounts(runCnt); // add counts
runCnt = 0; // reset counts
runCountStart = millis(); // reset the period time
}
if (millis() >= dispPeriodStart + DISP_PERIOD){ // DISPLAY PERIOD
if (!altDispOn) DispCounts(dispCnt);// period is over - display counts
dispCnt = 0; // reset counter
dispPeriodStart = millis(); // reset the period time
}
if (millis() >= logPeriodStart + LOGGING_PEROID){ // LOGGING PERIOD
logCount(logCnt); // pass in the counts to be logged
logCnt = 0; // reset log event counter
dispCnt = 0; // reset display event counter too
dispPeriodStart = millis(); // reset display time too
logPeriodStart = millis(); // reset log time
}
if (millis() >= checkVccTime + 6000){ // timer for check battery (every 6 sec.)
checkVccTime = millis(); // reset timer
Vcc_mV = readVcc();
if (Vcc_mV <= LOW_VCC) lowVcc = true; // check if Vcc is low
else lowVcc = false;
}
}
void DispCounts(long dcnt){ // calc and display predicted CPM & uSv/h
byte maxSamples = (60000 / DISP_PERIOD) / 2; // number of sample periods in 30 seconds
sampleCnt++; // inc sample count - must be at least 1
avgCnt += (dcnt - avgCnt) / sampleCnt; // CALCULATE AVERAGE COUNT - moving average
dispCPM = (avgCnt * 60000.0) / DISP_PERIOD; // convert to CPM
//handle reset of sample count - sample is for 1/2 min and reset. Options for reset value are:
// "0" - throw away last average, "1" - keeps last average, "maxSamples -1" - keeps running avg.
if (sampleCnt >= maxSamples) sampleCnt = 0; // start a fresh average every 30 sec.
// the following line gives a faster response when counts increase or decrease rapidly
if ((dcnt - avgCnt) > 9 || (avgCnt - dcnt) > 9) sampleCnt = 0;
uSv = dispCPM / uSvRate; // make uSV conversion
//Blink(LED_PIN,1); // uncomment to blink each didplay
Android.receive(); // looks for new input from Android
// don't send data via BT if not using app (no input) else it screws up serial output
if (androidReturn >0)Android.send((dispCPM * androidReturn) / 100);
clearArea (0,0,9); // clear count area
lcd.print("CPM "); // display static "CPM"
clearArea (0,1,16); // clear line 2
lcd.print("uSv/hr "); // display static "uSv/hr"
lcd.print(uSv,2); // display uSv/hr on line 1
lcd.setCursor(4,0); // CPM LAST TO DISPLAY - NEVER PARTIALLY OVERWRITTEN
lcd.print(dispCPM); // display CPM on line 1
lcd.print(" ");
if (dispCPM > AlarmPoint && AlarmPoint > 0) { // Alarm takes priority over low Vcc
AlarmOn = true;
digitalWrite(ALARM_PIN, HIGH); // turn on alarm (set alarm pin to Vcc)
}
else {
digitalWrite(ALARM_PIN, LOW); // turn off alarm (set alarm pin to Gnd)
AlarmOn = false;
}
}
void fastDisplay(){ // quick response display on 2nd half of line 1
barCnt = barCnt * 60; // scale CPS to CPM
if (barCnt <= FULL_SCALE && !AlarmOn){
clearArea (9,0,7); // move cursor to 9th col, 1st line for lcd bar
lcdBar(barCnt); // display bargraph on line 1
}
if (lowVcc) { // overwrite display with battery voltage if low
clearArea (11,0,5);
lcd.print(Vcc_mV/1000.,2); // display as volts with 2 dec. place
lcd.print("V");
}
if (AlarmOn) { // overwrite display with alarm if on
clearArea (10,0,6);
lcd.setCursor(11,0);
lcd.print("ALARM");
}
}
void DispRunCounts(){ // create the screen that shows the running counts
clearDisp();
lcd.print(" 1M "); // display 1 & 10 min lits
lcd.setCursor(0,1);
lcd.print("10M ");
// 1 MINUTE DISPLAY LINE . . .
tempSum = 0;
for (int i = 0; i <= ONE_MIN_MAX-1; i++){ // sum up 1 minute counts
tempSum = tempSum + oneMinute[i];
}
temp_uSv = tempSum / uSvRate; // calc uSv/h
if (dispOneMin){ // disp first so it can be overwritten by high counts
lcd.setCursor(9, 0);
lcd.print("/");
lcd.setCursor(13 - getLength(temp_uSv), 0); // right justify the uSv!
lcd.print(temp_uSv,2); // display 1 minute uSv
}
else { // for running counts
lcd.setCursor(10, 0);
lcd.print(" COUNT");
}
lcd.setCursor(4, 0);
lcd.print(tempSum,DEC); // display 1 minute CPM or running count
// 10 MINUTE DISPLAY LINE . . .
tempSum = 0;
for (int i = 0; i <= TEN_MIN_MAX-1; i++){ // sum up 10 minute counts
tempSum = tempSum + tenMinute[i];
}
if (dispTenMin) tempSum = tempSum / 10; // sum over 10 minutes so divide by that when CPM is displayed
temp_uSv = tempSum / uSvRate;
if (dispTenMin){ // disp first so it can be overwritten by high counts
lcd.setCursor(9, 1);
lcd.print("/");
lcd.setCursor(13 - getLength(temp_uSv), 1); // right justify the uSv!
lcd.print(temp_uSv,2); // display 1 minute uSv
}
else {
lcd.setCursor(13, 1); // right justify
lcd.print(5*tenMinuteIndex,DEC); // show seconds count
}
lcd.setCursor(4, 1);
lcd.print(tempSum,DEC); // display 10 minute CPM
// give the display a little more time for fast display periods
if (DISP_PERIOD < 5000) delay(1500); // a delay is OK here - interrupts are still handled
}
void setAlarm(){ // RECURSIVE FUNCTION to change alarm set point when button repeatidly pushed
long timeIn = millis(); // capture the time you got here
while (millis() < timeIn + 2000) { // you got 2 sec. to push button again - else done
if (readButton(BUTTON_PIN)== LOW){ // button pushed
if (AlarmPoint < 100) AlarmPoint = AlarmPoint +10; // inc by 10 up to 100 CPM
else AlarmPoint = AlarmPoint +50; // inc by 50 over 100 CPM
if (AlarmPoint > MAX_ALARM) AlarmPoint = 0; // start over if max point reached - zero is off
EEPROM.write(ALARM_SET_ADDR, AlarmPoint /10); // store new setting in EEPROM
lcd.setCursor(0, 1); // display what's going on
clearArea (0,1,16);
if (AlarmPoint >0){
lcd.print(AlarmPoint);
lcd.print(" CPM");
}
else lcd.print("Alarm Off");
delay(500);
setAlarm(); // call this function recursively if button was pushed
}
} // button not pushed - done use last setting for alarm point
}
void lcdBar(int counts){ // displays CPM as bargraph on 2nd line
// Adapted from DeFex http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1264215873/0
unsigned int scaler = FULL_SCALE / 41; // 7 char=41 "bars", scaler = counts/bar
unsigned int cntPerBar = (counts / scaler); // amount of bars needed to display the count
unsigned int fullBlock = (cntPerBar / 6); // divide for full "blocks" of 6 bars
unsigned int prtlBlock = (cntPerBar % 6 ); // calc the remainder of bars
if (fullBlock >7){ // safty to prevent writing >7 blocks
fullBlock = 7;
prtlBlock = 0;
}
for (int i=0; i<fullBlock; i++){
lcd.write(5); // print full blocks
}
lcd.write(prtlBlock); // print remaining bars with custom char
for (int i=(fullBlock + 1); i<8; i++){
lcd.print(" "); // blank spaces to clean up leftover
}
}
void logCount(unsigned long lcnt){ // unlike logging sketch, just outputs to serial
if (millis() < logPeriodStart + LOGGING_PEROID) return; // period not over
logCPM = float(lcnt) / (float(LOGGING_PEROID) / 60000);
uSvLogged = logCPM / uSvRate; // make uSV conversion
// Print to serial in a format that might be used by Excel
Serial.print(" ");
Serial.print(logCPM,DEC);
Serial.print(",\t");
Serial.print(uSvLogged,4);
Serial.print(",\t"); // comma delimited
Serial.print(Vcc_mV/1000. ,2); // print as volts with 2 dec. places
//Serial.print("V"); // removed for better graphing
Serial.println(",");
Blink(LED_PIN,2); // show it logged
}
void RunningCounts(unsigned long dcnt){ // Add CPM of period to 1M and 10M array
if (altDispUsed == false)return; // keep the totals clean until first run
oneMinute[oneMinuteIndex] = dcnt;
if(oneMinuteIndex >= ONE_MIN_MAX-1) {
oneMinuteIndex = 0;
dispOneMin = true; // indicate that average is available
}
else oneMinuteIndex++;
tenMinute[tenMinuteIndex] = dcnt;
if(tenMinuteIndex >= TEN_MIN_MAX-1) {
tenMinuteIndex = 0;
dispTenMin = true; // indicate that average is available
}
else tenMinuteIndex++;
dcnt = 0;
}
long readVcc() { // SecretVoltmeter from TinkerIt
long result;
// Read 1.1V reference against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = 1126400L / result; // Back-calculate AVcc in mV
return result;
}
///////////////////////////////// UTILITIES ///////////////////////////////////
void clearArea (byte col, byte line, byte nspaces){
// starting at col & line, prints n spaces then resets the cursor to the start
lcd.setCursor(col,line);
for (byte i=0; i<nspaces; i++){
lcd.print(" ");
}
lcd.setCursor(col,line);
}
void clearDisp (){
// The OLED display does not always reset the cursor after a clear(), so it's done here
lcd.clear(); // clear the screen
lcd.setCursor(0,0); // reset the cursor for the poor OLED
lcd.setCursor(0,0); // do it again for the OLED
}
void Blink(byte led, byte times){ // just to flash the LED
for (byte i=0; i< times; i++){
digitalWrite(led,HIGH);
delay (150);
digitalWrite(led,LOW);
delay (100);
}
}
int AvailRam(){
int memSize = 2048; // if ATMega328
byte *buf;
while ((buf = (byte *) malloc(--memSize)) == NULL);
free(buf);
return memSize;
}
byte getLength(unsigned long number){
byte length = 0;
for (byte i = 1; i < 10; i++){
if (number > pow(10,i)) length = i;
else return length +1;
}
}
byte readButton(int buttonPin) { // reads LOW ACTIVE push buttom and debounces
if (digitalRead(buttonPin)) return HIGH; // still high, nothing happened, get out
else { // it's LOW - switch pushed
delay(DEBOUNCE_MS); // wait for debounce period
if (digitalRead(buttonPin)) return HIGH; // no longer pressed
else return LOW; // 'twas pressed
}
}
void androidInput(byte flag, byte numOfValues){ // automatically called when input from Android
androidReturn = Android.getInt(); // set global with value from slider
}
///////////////////////////////// ISR ///////////////////////////////////
void GetEvent(){ // ISR triggered for each new event (count)
dispCnt++;
logCnt++;
runCnt++;
barCnt++;
}
the sketch as a file can be downloaded here
/* GeigerKit_Default sketch (v.8.0) bHogan 6/22/12
* Adjusted for LCD shield P2-GCS1-040512 impexeris on ebay.com on 08/21/12
* Adjustments for P2-GCS1-040512 only concern Alarm and button BUTTON_PIN change from 10 to 11 and ALARM_PIN change from 15 to 10
* Tactile button should be used on the P2-GCS1-040512 PCB instead of the top-right alarm LED and corresponding current limiting resistor should be removed)
* This sketch was written for the DIYGeigerCounter Kit.
* It requires the Arduino IDE rel. 1.0.0 or above to compile.
* FEATURES:
* CPM & uSv/hr output to LCD display and serial port. Bar graph on LCD. Low voltage warning.
* Counts are displayed every 5 sec using a moving average which is reset every 30 sec.
* An alternate display shows a running average for the previous 1 and 10 minute periods.
* One of 2 CPM to uSv/h conversion ratios can be selected via jumper. ACCURACY NOT VERIFIED
* This version adds a configurable alarm, new alt screen, and a 'realtime' bargraph.
* It also supports the SensorGraph app on the Android.
* More info is available at: http://sites.google.com/site/diygeigercounter/home
* SETUP: (Any 2x16 Hitachi HD44780 compatible LCD)
* +5V LCD Vdd pin 2 >>> Gnd LCD Vss pin 1, and R/W pin 5
* LCD RS pin 4 to D3 >>> LCD Enable pin 6 to D4
* LCD D4 pin 11 to D5 >>> LCD D5 pin 12 to D6
* LCD D6 pin 13 to D7 >>> LCD D7 pin 14 to D8
* LCD LEDA pin 15 to ~1K to +5 >>> LCD LEDK pin 16 to GND
* 10K pot: - ends to +5V and Gnd, wiper to LCD VO pin (pin 3)
* *INT from Geiger circuit is connected to PIN 2 and triggered as FALLING.
* PIN 9 - jumper to GND if secondary conversion ratio is desired
* PIN 10 - use button for alt display and to set alarm. (see site)
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* Do not remove information from this header.
*
* THIS PROGRAM AND IT'S MEASUREMENTS IS NOT INTENDED TO GUIDE ACTIONS TO TAKE, OR NOT
* TO TAKE, REGARDING EXPOSURE TO RADIATION. THE GEIGER KIT AND IT'S SOFTWARE ARE FOR
* EDUCATIONAL PURPOSES ONLY. DO NOT RELY ON THEM IN HAZARDOUS SITUATIONS!
*/
#include <MeetAndroid.h> // connects Geiger to SensorGraph on Android
#include <LiquidCrystal.h> // HD44780 compatible LCDs work with this lib
#include <EEPROM.h> // alarm setting is stored in EEPROM
#define DEBUG false // if true, shows available memory
#define TUBE_SEL 9 // jumper to select alt conversion to uSv
#define BUTTON_PIN 11 // button to toggle alternate display and set alarm (Pin number changed from 10 to 11 for the LCD shield P2-GCS1-040512)
#define LED_PIN 13 // for debug only - flashes 5X at startup
#define ALARM_PIN 10 // Outputs HIGH when Alarm triggered (Pin number changed from 15 to 10 for the LCD shield P2-GCS1-040512)
#define ALARM_SET_ADDR 8 // address of alarm setting in EEPROM
#define MAX_ALARM 600 // max the alarm can be set for
// HOW MANY CPM =1 uSV? The commonly used ratios for the SBM-20 & LND712 are defined:
#define PRI_RATIO 175.43 // no TUBE_SEL jumper - SET FOR SBM-20
#define SEC_RATIO 100.00 // TUBE_SEL jumper to GND - SET FOR LND712
// mS between writes to serial - counts also accumulate seperately for this period
#define LOGGING_PEROID 60000 // 1 min (60000 mS) best - unless testing
// mS between writes to display - counts/period are averaged to CPM
#define DISP_PERIOD 5000.0 // (5 sec) mS sample & display
#define FULL_SCALE 1000 // max CPM for all 7 bars
#define LOW_VCC 4200 //mV // if Vcc < LOW_VCC give low voltage warning
#define ONE_MIN_MAX 12 // elements in the oneMinute accumulater array
#define TEN_MIN_MAX 120 // elements in the tenMinute accumulater array
#define DEBOUNCE_MS 50 // buttom debounce period in mS
boolean lowVcc = false; // true when Vcc < LOW_VCC
boolean altDispUsed = false; // used to start counts from zero
boolean altDispOn = false; // true when SW_1 on, and in continous count mode
boolean dispOneMin = false; // true if enough time passed to begin displaying counts
boolean dispTenMin = false;
boolean AlarmOn = false; // CPM > set alarm
byte sampleCnt; // the number of samples making up the average
byte altDispCnt = 0; // controls when alt display is on
int oneMinute[ONE_MIN_MAX]; // used to accumulate 1 & 10 minute counts
int oneMinuteIndex = 0;
int tenMinute[TEN_MIN_MAX];
int tenMinuteIndex = 0;
int AlarmPoint; // CPM alarm is set for
int Vcc_mV; // mV of Vcc from last check
int androidReturn = 0; // reads the slider on Android SensorGraph.
volatile unsigned long dispCnt, logCnt; // inc by ISR
volatile unsigned long runCnt, barCnt;
long runCountStart;
unsigned long tempSum;
unsigned long dispPeriodStart, dispCPM; // counters for the display period . . .
unsigned long logPeriodStart, logCPM;
unsigned long checkVccTime; // counter for how often to check voltage
unsigned long altDispStart; // counter for alt display refresh period
unsigned long lastButtonTime; // counter for pressing the button to quickly
unsigned long barGraphStart; // counter for bargraph refresh period
float uSv = 0.0; // display CPM converted to "unofficial" uSv
float uSvLogged = 0.0; // logging CPM converted to "unofficial" uSv
float uSvRate; // holds the rate selected by jumper
float avgCnt = 0.0; // holds the previous average count
float temp_uSv = 0.0;
//Custom characters used for bar graph
byte bar_0[8] = {
0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00}; //blank
byte bar_1[8] = {
0x10, 0x10, 0x18, 0x18, 0x18, 0x10, 0x10, 0x00}; //1 bar
byte bar_2[8] = {
0x18, 0x18, 0x1c, 0x1c, 0x1c, 0x18, 0x18, 0x00}; //2 bars
byte bar_3[8] = {
0x1C, 0x1C, 0x1e, 0x1e, 0x1e, 0x1C, 0x1C, 0x00}; //3 bars
byte bar_4[8] = {
0x1E, 0x1E, 0x1f, 0x1f, 0x1f, 0x1E, 0x1E, 0x00}; //4 bars
byte bar_5[8] = {
0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x00}; //5 bars
byte bar_6[8] = {
0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x00}; //6 bars (same)
// instantiate the library and pass pins for (RS, Enable, D4, D5, D6, D7)
LiquidCrystal lcd(3, 4, 5, 6, 7, 8); // default layout for the Geiger board
MeetAndroid Android;
void setup(){
Serial.begin(9600); // comspec 96,N,8,1
attachInterrupt(0,GetEvent,FALLING); // Geiger event on pin 2 triggers interrupt
pinMode(LED_PIN,OUTPUT); // setup LED pin
pinMode(TUBE_SEL,INPUT); // setup tube select jumper pin
pinMode(BUTTON_PIN,INPUT); // setup menu button
pinMode(ALARM_PIN, OUTPUT); // setup Alarm pin
digitalWrite(TUBE_SEL, HIGH); // set 20K pullup on jumper pins(low active)
digitalWrite(BUTTON_PIN, HIGH);
Blink(LED_PIN,4); // show it's alive
lcd.begin(16,2); // cols, rows of display (8x2, 16x2, etc.)
lcd.createChar(0, bar_0); // load 7 custom characters in the LCD
lcd.createChar(1, bar_1);
lcd.createChar(2, bar_2);
lcd.createChar(3, bar_3);
lcd.createChar(4, bar_4);
lcd.createChar(5, bar_5);
lcd.createChar(6, bar_6);
lcd.setCursor(0,0);
Android.registerFunction(androidInput,'o'); // calls androidInput() when new input frpm droid
clearDisp(); // clear the screen
lcd.print(" Ver. 8.0"); // display a simple banner
lcd.setCursor(0,1); // set cursor on line 2
lcd.print(" LCD Shield rev."); // display the version
delay (1500); // leave the banner up for a bit
clearDisp(); // clear the screen
if(digitalRead(TUBE_SEL)){ // read jumper to select conversion ratio
uSvRate = PRI_RATIO; // use the primary ratio defined
}
else{ // jumper is set to GND . . .
uSvRate = SEC_RATIO; // use the secondary ratio defined
}
lcd.print(uSvRate,0); // display conversion ratio in use
lcd.print(" CPM to uSv");
lcd.setCursor(0,1); // set cursor on line 2
Vcc_mV = readVcc(); // read Vcc voltage
lcd.print("Running at "); // display it
lcd.print(Vcc_mV/1000. ,2); // display as volts with 2 dec. places
lcd.print("V");
delay (2000); // leave info up for a bit
#if (DEBUG) // show available SRAM if DEBUG true
clearDisp();
lcd.print("RAM Avail: ");
lcd.print(AvailRam());
delay (2000);
#endif
// this section tests if button pressed to set alarm threshold and calls a function to change it
AlarmPoint = EEPROM.read(ALARM_SET_ADDR); // get last alarm value from EEPROM
if (AlarmPoint == 255) AlarmPoint = 0; // deal with virgin EEPROM
AlarmPoint = AlarmPoint * 10; // values stored are 1/10th
clearDisp(); // put up new alarm set screen
lcd.print("Set Alarm?");
lcd.setCursor(0, 1);
if (AlarmPoint >0){
lcd.print("Now ");
lcd.print(AlarmPoint);
lcd.print(" CPM");
}
else lcd.print("Now Off");
long timeIn = millis(); // you have 3 sec to push button or move on
while (millis() < timeIn + 3000) {
if (readButton(BUTTON_PIN)== LOW) setAlarm(); // alarm is to be set
} // if no button press continue
clearDisp(); // clear the screen
lcd.print("CPM? "); // display beginning "CPM"
Serial.println("CPM \t uSv \t Vcc"); // print header for log
dispPeriodStart = millis(); // start timing display CPM
logPeriodStart = dispPeriodStart; // start logging timer
checkVccTime = dispPeriodStart; // start Vcc timer
runCountStart = dispPeriodStart; // start alternate timer
barGraphStart = dispPeriodStart; // start bargraph timer
dispCnt = 0; // start with fresh totals
logCnt= 0;
runCnt = 0;
barCnt = 0;
}
void loop(){
//uncomment 5 lines below for self check - you should see very close to 360 CPM
//dispCnt++;
//logCnt++;
//runCnt++;
//barCnt++;
//delay(167); // 167 mS = 6 Hz `= X 60 = 360 CPM
if (millis() >= lastButtonTime + 500){ // wait a bit between button pushes
lastButtonTime = millis(); // reset the period time
if (readButton(BUTTON_PIN)== LOW){ // start alt display mode if button pin is low
clearDisp(); // clear the screen when switching displays
altDispOn = !altDispOn; // toggle altDispOn state
if (altDispUsed == false){
altDispUsed = true; // false until first used
runCnt = 0; // clear counts until used
runCountStart = millis();
}
if (altDispOn) DispRunCounts(); // start alt display immediately - or
else DispCounts(dispCnt); // start main display immediately
}
}
if (millis() >= barGraphStart + 1000){ // refresh bargraph, alarm and Vcc if in main display
if (!altDispOn)fastDisplay(); // display quick response data
barCnt = 0; // reset counts
barGraphStart = millis(); // reset the period time
}
if (millis() >= altDispStart + 5000){ // refresh alt display if it's on
if (altDispOn) DispRunCounts(); // display running counts
altDispStart = millis(); // reset the period time
}
if (millis() >= runCountStart + 5000){ // Collect running counts every 10 sec.
RunningCounts(runCnt); // add counts
runCnt = 0; // reset counts
runCountStart = millis(); // reset the period time
}
if (millis() >= dispPeriodStart + DISP_PERIOD){ // DISPLAY PERIOD
if (!altDispOn) DispCounts(dispCnt);// period is over - display counts
dispCnt = 0; // reset counter
dispPeriodStart = millis(); // reset the period time
}
if (millis() >= logPeriodStart + LOGGING_PEROID){ // LOGGING PERIOD
logCount(logCnt); // pass in the counts to be logged
logCnt = 0; // reset log event counter
dispCnt = 0; // reset display event counter too
dispPeriodStart = millis(); // reset display time too
logPeriodStart = millis(); // reset log time
}
if (millis() >= checkVccTime + 6000){ // timer for check battery (every 6 sec.)
checkVccTime = millis(); // reset timer
Vcc_mV = readVcc();
if (Vcc_mV <= LOW_VCC) lowVcc = true; // check if Vcc is low
else lowVcc = false;
}
}
void DispCounts(long dcnt){ // calc and display predicted CPM & uSv/h
byte maxSamples = (60000 / DISP_PERIOD) / 2; // number of sample periods in 30 seconds
sampleCnt++; // inc sample count - must be at least 1
avgCnt += (dcnt - avgCnt) / sampleCnt; // CALCULATE AVERAGE COUNT - moving average
dispCPM = (avgCnt * 60000.0) / DISP_PERIOD; // convert to CPM
//handle reset of sample count - sample is for 1/2 min and reset. Options for reset value are:
// "0" - throw away last average, "1" - keeps last average, "maxSamples -1" - keeps running avg.
if (sampleCnt >= maxSamples) sampleCnt = 0; // start a fresh average every 30 sec.
// the following line gives a faster response when counts increase or decrease rapidly
if ((dcnt - avgCnt) > 9 || (avgCnt - dcnt) > 9) sampleCnt = 0;
uSv = dispCPM / uSvRate; // make uSV conversion
//Blink(LED_PIN,1); // uncomment to blink each didplay
Android.receive(); // looks for new input from Android
// don't send data via BT if not using app (no input) else it screws up serial output
if (androidReturn >0)Android.send((dispCPM * androidReturn) / 100);
clearArea (0,0,9); // clear count area
lcd.print("CPM "); // display static "CPM"
clearArea (0,1,16); // clear line 2
lcd.print("uSv/hr "); // display static "uSv/hr"
lcd.print(uSv,2); // display uSv/hr on line 1
lcd.setCursor(4,0); // CPM LAST TO DISPLAY - NEVER PARTIALLY OVERWRITTEN
lcd.print(dispCPM); // display CPM on line 1
lcd.print(" ");
if (dispCPM > AlarmPoint && AlarmPoint > 0) { // Alarm takes priority over low Vcc
AlarmOn = true;
digitalWrite(ALARM_PIN, HIGH); // turn on alarm (set alarm pin to Vcc)
}
else {
digitalWrite(ALARM_PIN, LOW); // turn off alarm (set alarm pin to Gnd)
AlarmOn = false;
}
}
void fastDisplay(){ // quick response display on 2nd half of line 1
barCnt = barCnt * 60; // scale CPS to CPM
if (barCnt <= FULL_SCALE && !AlarmOn){
clearArea (9,0,7); // move cursor to 9th col, 1st line for lcd bar
lcdBar(barCnt); // display bargraph on line 1
}
if (lowVcc) { // overwrite display with battery voltage if low
clearArea (11,0,5);
lcd.print(Vcc_mV/1000.,2); // display as volts with 2 dec. place
lcd.print("V");
}
if (AlarmOn) { // overwrite display with alarm if on
clearArea (10,0,6);
lcd.setCursor(11,0);
lcd.print("ALARM");
}
}
void DispRunCounts(){ // create the screen that shows the running counts
clearDisp();
lcd.print(" 1M "); // display 1 & 10 min lits
lcd.setCursor(0,1);
lcd.print("10M ");
// 1 MINUTE DISPLAY LINE . . .
tempSum = 0;
for (int i = 0; i <= ONE_MIN_MAX-1; i++){ // sum up 1 minute counts
tempSum = tempSum + oneMinute[i];
}
temp_uSv = tempSum / uSvRate; // calc uSv/h
if (dispOneMin){ // disp first so it can be overwritten by high counts
lcd.setCursor(9, 0);
lcd.print("/");
lcd.setCursor(13 - getLength(temp_uSv), 0); // right justify the uSv!
lcd.print(temp_uSv,2); // display 1 minute uSv
}
else { // for running counts
lcd.setCursor(10, 0);
lcd.print(" COUNT");
}
lcd.setCursor(4, 0);
lcd.print(tempSum,DEC); // display 1 minute CPM or running count
// 10 MINUTE DISPLAY LINE . . .
tempSum = 0;
for (int i = 0; i <= TEN_MIN_MAX-1; i++){ // sum up 10 minute counts
tempSum = tempSum + tenMinute[i];
}
if (dispTenMin) tempSum = tempSum / 10; // sum over 10 minutes so divide by that when CPM is displayed
temp_uSv = tempSum / uSvRate;
if (dispTenMin){ // disp first so it can be overwritten by high counts
lcd.setCursor(9, 1);
lcd.print("/");
lcd.setCursor(13 - getLength(temp_uSv), 1); // right justify the uSv!
lcd.print(temp_uSv,2); // display 1 minute uSv
}
else {
lcd.setCursor(13, 1); // right justify
lcd.print(5*tenMinuteIndex,DEC); // show seconds count
}
lcd.setCursor(4, 1);
lcd.print(tempSum,DEC); // display 10 minute CPM
// give the display a little more time for fast display periods
if (DISP_PERIOD < 5000) delay(1500); // a delay is OK here - interrupts are still handled
}
void setAlarm(){ // RECURSIVE FUNCTION to change alarm set point when button repeatidly pushed
long timeIn = millis(); // capture the time you got here
while (millis() < timeIn + 2000) { // you got 2 sec. to push button again - else done
if (readButton(BUTTON_PIN)== LOW){ // button pushed
if (AlarmPoint < 100) AlarmPoint = AlarmPoint +10; // inc by 10 up to 100 CPM
else AlarmPoint = AlarmPoint +50; // inc by 50 over 100 CPM
if (AlarmPoint > MAX_ALARM) AlarmPoint = 0; // start over if max point reached - zero is off
EEPROM.write(ALARM_SET_ADDR, AlarmPoint /10); // store new setting in EEPROM
lcd.setCursor(0, 1); // display what's going on
clearArea (0,1,16);
if (AlarmPoint >0){
lcd.print(AlarmPoint);
lcd.print(" CPM");
}
else lcd.print("Alarm Off");
delay(500);
setAlarm(); // call this function recursively if button was pushed
}
} // button not pushed - done use last setting for alarm point
}
void lcdBar(int counts){ // displays CPM as bargraph on 2nd line
// Adapted from DeFex http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1264215873/0
unsigned int scaler = FULL_SCALE / 41; // 7 char=41 "bars", scaler = counts/bar
unsigned int cntPerBar = (counts / scaler); // amount of bars needed to display the count
unsigned int fullBlock = (cntPerBar / 6); // divide for full "blocks" of 6 bars
unsigned int prtlBlock = (cntPerBar % 6 ); // calc the remainder of bars
if (fullBlock >7){ // safty to prevent writing >7 blocks
fullBlock = 7;
prtlBlock = 0;
}
for (int i=0; i<fullBlock; i++){
lcd.write(5); // print full blocks
}
lcd.write(prtlBlock); // print remaining bars with custom char
for (int i=(fullBlock + 1); i<8; i++){
lcd.print(" "); // blank spaces to clean up leftover
}
}
void logCount(unsigned long lcnt){ // unlike logging sketch, just outputs to serial
if (millis() < logPeriodStart + LOGGING_PEROID) return; // period not over
logCPM = float(lcnt) / (float(LOGGING_PEROID) / 60000);
uSvLogged = logCPM / uSvRate; // make uSV conversion
// Print to serial in a format that might be used by Excel
Serial.print(" ");
Serial.print(logCPM,DEC);
Serial.print(",\t");
Serial.print(uSvLogged,4);
Serial.print(",\t"); // comma delimited
Serial.print(Vcc_mV/1000. ,2); // print as volts with 2 dec. places
//Serial.print("V"); // removed for better graphing
Serial.println(",");
Blink(LED_PIN,2); // show it logged
}
void RunningCounts(unsigned long dcnt){ // Add CPM of period to 1M and 10M array
if (altDispUsed == false)return; // keep the totals clean until first run
oneMinute[oneMinuteIndex] = dcnt;
if(oneMinuteIndex >= ONE_MIN_MAX-1) {
oneMinuteIndex = 0;
dispOneMin = true; // indicate that average is available
}
else oneMinuteIndex++;
tenMinute[tenMinuteIndex] = dcnt;
if(tenMinuteIndex >= TEN_MIN_MAX-1) {
tenMinuteIndex = 0;
dispTenMin = true; // indicate that average is available
}
else tenMinuteIndex++;
dcnt = 0;
}
long readVcc() { // SecretVoltmeter from TinkerIt
long result;
// Read 1.1V reference against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = 1126400L / result; // Back-calculate AVcc in mV
return result;
}
///////////////////////////////// UTILITIES ///////////////////////////////////
void clearArea (byte col, byte line, byte nspaces){
// starting at col & line, prints n spaces then resets the cursor to the start
lcd.setCursor(col,line);
for (byte i=0; i<nspaces; i++){
lcd.print(" ");
}
lcd.setCursor(col,line);
}
void clearDisp (){
// The OLED display does not always reset the cursor after a clear(), so it's done here
lcd.clear(); // clear the screen
lcd.setCursor(0,0); // reset the cursor for the poor OLED
lcd.setCursor(0,0); // do it again for the OLED
}
void Blink(byte led, byte times){ // just to flash the LED
for (byte i=0; i< times; i++){
digitalWrite(led,HIGH);
delay (150);
digitalWrite(led,LOW);
delay (100);
}
}
int AvailRam(){
int memSize = 2048; // if ATMega328
byte *buf;
while ((buf = (byte *) malloc(--memSize)) == NULL);
free(buf);
return memSize;
}
byte getLength(unsigned long number){
byte length = 0;
for (byte i = 1; i < 10; i++){
if (number > pow(10,i)) length = i;
else return length +1;
}
}
byte readButton(int buttonPin) { // reads LOW ACTIVE push buttom and debounces
if (digitalRead(buttonPin)) return HIGH; // still high, nothing happened, get out
else { // it's LOW - switch pushed
delay(DEBOUNCE_MS); // wait for debounce period
if (digitalRead(buttonPin)) return HIGH; // no longer pressed
else return LOW; // 'twas pressed
}
}
void androidInput(byte flag, byte numOfValues){ // automatically called when input from Android
androidReturn = Android.getInt(); // set global with value from slider
}
///////////////////////////////// ISR ///////////////////////////////////
void GetEvent(){ // ISR triggered for each new event (count)
dispCnt++;
logCnt++;
runCnt++;
barCnt++;
}
2012 m. rugpjūčio 11 d., šeštadienis
Ways to connect SBT-9 tube
I have tried three different ways to connect the SBT-9 tube
#1
Strip two wires so that one part is long and other is short
bend one wire as shown this is to keep the wire in place when inserting into thermal tube
Cut a piece of a thermal tube (the one which shrinks when heat is applied)
Hold the tube and insert it into the a piece of a thermal tube
!Very carefully heat the thermal tube (do not overheat the SBT9)
I do it in several steps - heat a little so that thermal tube starts shrinking and then wait for the whole piece to cool. Then again heat it a little and so on. I have not ruined any tubes this way myself, however I assume it is easily possible if you overheat.
Same thing with Anode of the tube ...
then cut the remainder of the Cathode wire
#2
Similar but no heating - use a good isolation tape
#3
Using clips
2012 m. rugpjūčio 10 d., penktadienis
Užsisakykite:
Pranešimai (Atom)