/* Pingoo Geiger Special Sketch (v0.1 BETA) YVERGER 12/06/12 * * New modif : Pingoo geiger : Special edition V0.1 * New modification of Brohogan's soft for more accurate and fast reliable measure : * - Display dose permanent. * - Display of becquerel added. * - Dose is calculated on moving average of 4 cycles of 30 sec (can be modified to shorten time measure) * - New dose start with each new cycle of measure. Calculated dose is displayed every 30 sec. * - Bargraph LCD related to immediate estimated CPM (displayed on line 2 now) * - Bargraph LCD and Bq measurement refreshed every 1 sec. * - Ratio modification optional * - Bargraph fullscale depending of geiger tube (max scale calculated to get about 10 µSv/h) * - Calculated dose is guaranted until 11 µSv/h.(due to dead time of geiger tube) * - no modif of Brohogan sodft > Counts events/min. and outputs CPM & ~uSv/hr to LCD display and also to serial port. -* - no modif of Brohogan sodft > check for voltage at the uC to indicate a low battery voltage * - no modif of Brohogan sodft > adds seperate counter for serial - set at 1 min to give CPM without rounding * * 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 - jumper to GND to turn on dose mode (provides 1 & 10 min running counts) * * 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. * * 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 THIS PROGRAM IN HAZARDOUS SITUATIONS! */ #include // HD44780 compatible LCDs work with this lib #define TUBE_SEL 9 // jumper to select alt conversion to uSv #define DOSE_MODE_SEL 10 // jumper to to turn on dose mode - see header #define LED_PIN 13 // for debug only - flashes 5X at startup // HOW MANY CPM =1 uSV? Two commonly used sets of ratios for the SBM-20 & LND712 are defined: // libelium now uses: 175.43 for SBM-20 and 100.00 for LND712 // www.utsunomia.com/y.utsunomia/Kansan.html use: 150.51 for SBM-20 and 123.14 for LND712 // #define PRI_RATIO 175.43 // no TUBE_SEL jumper - SET FOR SBM-20 #define PRI_RATIO 225.00 // no TUBE_SEL jumper - (SB29G) // SBM20 = 150-190, SB29G = 200-240, LND712 = 90-110 // SBT-9 = 130-160 (alpa,beta,gamma) // define SEC_RATIO 100 // mS between writes to serial - counts also accumulate seperately for this period #define LOGGING_PEROID 60000 // set for at least 1 min (60000 mS) unless testing // mS between writes to display - counts/period are averaged to CPM // LOGGING_PERIOD shuld be a multiple of this period #define DISP_PERIOD 1000.0 // (1sec) mS sample & display #define FULL_SCALE 2000 // max CPM for 8 bars & overflow warning #define LOW_VCC 4200 //mV // if Vcc < LOW_VCC give low voltage warning boolean lowVcc = false; // true when Vcc < LOW_VCC boolean doseMode = false; // true when SW_1 on, and in continous count mode volatile unsigned long dispCnt, logCnt; unsigned long dispPeriodStart, dispCPM; // counters for the display period unsigned long logPeriodStart, logCPM; // counters for the logging period unsigned long checkVccTime; float uSv = 0.0; // display CPM converted to absolute uSv float uSvLogged = 0.0; // logging CPM converted to absolute uSv float uSvRate; // holds the rate selected by jumper float avgCnt = 0.0; // holds the previous average count int Vcc_mV; // mV of Vcc from last check // NEW STUFF #define PERIOD_MAX 2 // overflow for PERIOD_MAX * 10 sec (Choose time between each measure) #define ONE_MIN_MAX 5 // overflow for oneMinute #define NBRE_CYCLE_MAX 3 // overflow for MAX CYCLE. NBRE_CYCLE_MAX * PERIOD_MAX (nbre = nombre = number) #define MAX_DISP_RANGE 10 // overflow for max range bar boolean dispstart = true; // flag first start boolean dispPeriod = false; // flag display period not/available boolean dispOneMin = false; // flag display one minute measurement not/available boolean dispnbrecycle = false; // flag display cycle not/avaible int rangeCPM = 0; // Calculated max range of calculated CPM int dispuSvrange = 0; // Calculated display max µSv range int Period[5]; // Array of max 5 field int PeriodIndex = 0; int oneMinute[6]; // Array of 6 field of 10 sec of measurement int oneMinuteIndex = 0; // int nbrecycle[4]; // Array of max 4 field int nbrecycleIndex = 0; volatile unsigned long runCnt; long runCountStart; unsigned long tempSum; float temp_uSv = 0.0; unsigned char altDispCnt = 0; //Custom characters used for bar graph unsigned char bar_0[8] = {0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00}; //blank unsigned char bar_1[8] = {0x10, 0x10, 0x18, 0x18, 0x18, 0x10, 0x10, 0x00}; //1 bar unsigned char bar_2[8] = {0x18, 0x18, 0x1c, 0x1c, 0x1c, 0x18, 0x18, 0x00}; //2 bars unsigned char bar_3[8] = {0x1C, 0x1C, 0x1e, 0x1e, 0x1e, 0x1C, 0x1C, 0x00}; //3 bars unsigned char bar_4[8] = {0x1E, 0x1E, 0x1f, 0x1f, 0x1f, 0x1E, 0x1E, 0x00}; //4 bars unsigned char bar_5[8] = {0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x00}; //5 bars unsigned char 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 // Hardware and software init 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(DOSE_MODE_SEL,INPUT); // setup dose mode display select jumper pin digitalWrite(TUBE_SEL, HIGH); // set 20K pullup on jumper pin(low active) digitalWrite(DOSE_MODE_SEL, HIGH); Blink(LED_PIN,5); // 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); clearDisp(); // clear the screen lcd.print(" PINGOO GEIGER"); // display name lcd.setCursor(0,1); // set cursor on line 2 lcd.print(" DOSIMETRE V8"); // display job delay (2000); // leave the banner up 2 sec. clearDisp(); // clear the screen // My kit don't use this jumper. Uncomment this line if you need it. // 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 (4000); // leave info up for 4 sec. clearDisp(); // clear the screen lcd.print(" ATTENDEZ SVP"); // ATTENDEZ SVP = PLEASE WAIT 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 dispCnt = 0; // start with fresh totals logCnt= 0; runCnt = 0; } // Main proc void loop(){ //uncomment 4 lines below for self check (1/166ms X 60 = 360 CPM // dispCnt++; // logCnt++; // runCnt++; // delay(167); // 167 mS ~= 6 Hz if (millis() >= runCountStart + 10000){ // Collect 1 & 4 min. 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 every one second 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 + 2000){ // timer for check battery checkVccTime = millis(); // reset timer Vcc_mV = readVcc(); if (Vcc_mV <= LOW_VCC) lowVcc = true; // check if Vcc is low else lowVcc = false; } } // ************* DEFINED PROC **************** // Proc : Calcul DOSE and CPM. void RunningCounts(unsigned long dcnt){ // add to running counts and reset counters as needed Period[PeriodIndex] = dcnt; // Add count in Period's Array. if(PeriodIndex >= PERIOD_MAX){ // If Counting period is over then Display is possible. dispPeriod=true; PeriodIndex = 0; } else PeriodIndex++; temp_uSv = 0; if (dispstart){ // Fast display of wide calculated first dose measured dispstart = false; temp_uSv = dcnt * 6 / uSvRate; // Calcul of immmediate µSv/h absolute. clearArea (0,0,16); // clear line 1 lcd.setCursor(7 - getLength(temp_uSv), 0); // right justify the uSv! lcd.print(temp_uSv,2); // display X minute uSv lcd.print(" uSv/h"); } dcnt = 0; } // Proc : Display calculated result. void DispCounts(long dcnt){ // calc and display predicted CPM & uSv/h dispCPM = (dcnt * 60000) /(DISP_PERIOD); // older simple calc method - now a moving average // New method not used in this software. // unsigned char 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 // the following line gives a faster response when counts increase or decrease rapidly // if ((dcnt - avgCnt) > 9 || (avgCnt - dcnt) > 9) sampleCnt = 0; // Display dose when flag dispPeriod is true if (dispPeriod){ // don't disply unless there is something to display DispRunCounts(); return; // skip the normal count screen } // Display bargraph LCD every DISP_PERIOD. clearArea (0,1,16); // clear line 2 lcd.setCursor(0,1); // move cursor to 1th col, 2nd line for lcd bar temp_uSv = 0; tempSum = 0; // Display range of estimated dose if > 1µSv/h every 5 secondes in place of bargraph LCD. if (dispCPM >= (uSvRate) && dispuSvrange >= MAX_DISP_RANGE) { temp_uSv = rangeCPM / MAX_DISP_RANGE / uSvRate; lcd.print(" > "); lcd.print(int(temp_uSv),DEC); lcd.print(" uSv"); rangeCPM = 0; dispuSvrange = 0; } else { dispuSvrange = dispuSvrange +1; rangeCPM = rangeCPM + dispCPM; if (dispCPM <= FULL_SCALE) lcdBar(dispCPM); // display bargraph on line 2 else { // or put warning on line 2 if > full scale lcd.print(">"); // show what full scale on bars is set at temp_uSv = FULL_SCALE / uSvRate; lcd.print(temp_uSv,2); lcd.print(" MAX"); } } // Display bq lcd.setCursor(13 - getLength(dcnt),1); // move cursor to 9th ol, 2nd line for lcd bar lcd.print(dcnt,DEC); lcd.setCursor(13,1); // move cursor to 14th line, 2nd line for lcd bar lcd.print(" Bq"); // display static unit Bq // Display warning message if low voltage if (lowVcc) { // overwrite display with battery voltage if low clearArea (9,1,6); lcd.print("Vcc="); lcd.print(Vcc_mV/1000. ,2); // display as volts with 2 dec. places } } // Proc : Data logged on serial port 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"); Serial.println(","); Blink(LED_PIN,2); // show it logged } // Calcul and send battery voltage 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; } void DispRunCounts(){ // create the screen that shows the running counts clearArea (0,0,16); // clear line 1 dispPeriod = false; // Reset le flag(Mesure period ok) temp_uSv = 0; tempSum = 0; for (int i = 0; i <= PERIOD_MAX; i++){ // sum up Period counts tempSum = tempSum + (Period[i]); // sum array } lcd.setCursor(0,0); for (int i = 0; i <= nbrecycleIndex; i++){ // Display present cycle lcd.print("*"); } tempSum = tempSum * 6 / (PERIOD_MAX + 1); // First sted of Calculated and conversion of absolute dose nbrecycle[nbrecycleIndex]= tempSum; // Save calculated CPM in array's cycle tempSum = 0; for (int i = 0; i <= nbrecycleIndex; i++){ tempSum = tempSum + nbrecycle[i]; } temp_uSv = tempSum / (nbrecycleIndex+1) / uSvRate; // Convert in absolute µSv/h lcd.setCursor(7 - getLength(temp_uSv), 0); // right justify the uSv! lcd.print(temp_uSv,2); // display X minute uSv lcd.print(" uSv/h"); lcd.setCursor(4, 0); if (nbrecycleIndex >= NBRE_CYCLE_MAX) { nbrecycleIndex = 0; // Cycle over then reset index } else nbrecycleIndex = nbrecycleIndex + 1; // increase index's value // 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 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 int scaler = FULL_SCALE / 47; // 8 char=47 "bars", scaler = counts/bar int cntPerBar = (counts / scaler); // amount of bars needed to display the count int fullBlock = (cntPerBar / 6); // divide for full "blocks" of 6 bars int prtlBlock = (cntPerBar % 6 ); // calc the remainder of bars for (int i=0; i pow(10,i)) length = i; else return length +1; } } ///////////////////////////////// ISR /////////////////////////////////// void GetEvent(){ // ISR triggered for each new event (count) dispCnt++; logCnt++; runCnt++; }