Wednesday, 15 October 2014

Recent Project...

Arduino Enclosure For Radiation Detector
The latest project is an Arduino enclosure, you can find the STL files on Thingiverse here. The base enclosure is very similar to standard commercial cases. Its principle advantage being it can be readily customized, both in CAD and by hand for fitting additional hardware such as displays, switches, lights and connectors.

Our customised version of the standard case is used to house a radiation detector we built based on an Arduino Uno. We selected the Uno as it has support for programmable PWM's and interrupts, as well as having a few lying around. The intention was to battery power the unit, however as time was limited and the linear voltage regulator circuit is so inefficient we elected to power from the USB if being used in portable mode for the first revision.

The display unit is a Sparkfun Nokia 5110 LCD graphic display. We used an STS-5 Geiger Muller tube, the slightly shorter SB-20 would have been better. An SB-20 can be fitted if required. You can see the tube in what doubles as a handle for the unit. The handle comprises three cylinders housing the tube. With the end two tubes having printed in place integral springs to limit forces on the Geiger Muller tube, this can be seen in the image lower down the page. The circuit was built on an Arduino prototype shield for speedy construction. We constructed the high voltage generator on the bottom left and the detection circuit took up the centre right. The LCD display was connected using a ribbon cable to the PCB pins. A piece of strip board (or Vero board) mounted on the back of the Nokia 5110 housed resistors to adjust the 5v levels to the ~3v levels used by the Nokia 5110 display unit.

The case uses two simple end plates to allow connectors or other hardware to be fitted. The whole case is held together with 4 off 3.5 mm diameter screws on the lower half of the case, which self tap into tapered holes in the upper half of the case. The longer variety required are are commonly found  holding electrical socket and lighting switch face plates into back boxes in the UK and Europe. In the US these would be slightly larger than 1/8 inch. As the holes in the top case are tapered, then slightly longer 1/8" screws should work nicely.

The high voltage generator is based on a single transistor and inductor circuit. The implementation was inspired by the Adafruit Radiation Detector with the added tweak that instead of driving the unit from a 555 timer, the PWM was sourced from the Arduino. Using a library extension to support the code, the frequency and duty factor were optimised to reduce current consumption needed to generate the 400v necessary in order to bias the STS-5 Geiger Muller tube. The most critical components in the circuit are the FJN3303F, 1N4937 and the high voltage capacitors.
Arduino prototype shield used for the generator and detector

Transistor FJN3303FMouser
Diode 1N4937 RS
Inductor 10mH RS
Capacitors - RS
Fuse holder- RS

The pulse from the Geiger Muller tube is fed into pin 2 of the Arduino, as this can be set up to respond as an interrupt. By counting interrupts generated when the Geiger Muller tube goes into avalanche the number of radiation particles can be counted and logged. We used this site for most of the information on calculations used in the code. The best information on the avalanche effect in a tube was described here, as this document is so handy we kept a copy.
HV generator circuit used in Arduino Radiation Detector
As all radiation counters click a 5v piezeo buzzer was connected to output pin assigned as 8.

Our pin assignment on the Arduino were as follows:
2Pulse from Geiger Muller tube
8LED and Buzzer
9High voltage PWM channel

Finally the Nokia 5110 LCD display was connected to the Arduino and the latest version of the driver library installed.
Connections to Nokia 5110 display
3SPI Clk
4SPI Dat
-LCD Vlcd

Libraries used:
  PWM - We found this library has better PWM implementation
  LCD5110_Graph - Nokia 5510 graphics library

The front panel is adhesive backed paper with adhesive backed plastic, burnished over the top to provide a long lasting tough finish.
Arduino Radiation Detector Enclosure
Front Panel Graphic

3D View Of Arduino Enclosure

Aged STS-5 document attached to the rear of the unit

3D printed Arduino case dimensions

Arduino Geiger Counter Internal Circuitry (Arduino Radiation Dosimeter)

The following code will run without hardware, pulses applied to pin 2 will be counted ~1 a second is typical background from the STS-5 tube.

//  Code provided without warranty and as is Rev 0.4

LCD5110 myGLCD(3,4,5,6,7);

extern uint8_t TinyFont[];        // Use the Tiny font to save LCD real estate
extern uint8_t BigNumbers[];      // Use big digital Segment style numbers for read at a distance

#define INTERRUPT_INPUT 2         // pin for interrupt which receives pulse from GM tube
// Nokia 5110 LCD display connections - note resistors connected to modify 5v to ~3v for LCD display
#define PIN_SCE   7  // LCD CS  .... Pin 3
#define PIN_RESET 6  // LCD RST .... Pin 1
#define PIN_DC    5  // LCD Dat/Com. Pin 5
#define PIN_SDIN  4  // LCD SPIDat . Pin 6
#define PIN_SCLK  3  // LCD SPIClk . Pin 4
                     // LCD Gnd .... Pin 2
                     // LCD Vcc .... Pin 8
                     // LCD Vlcd ... Pin 7

#define LCD_C     LOW
#define LCD_D     HIGH

#define LCD_X     84
#define LCD_Y     48
#define LCD_CMD   0

//defines for graphic character positions
#define Y_POS_UNITS             8  // FOR THE BIG NUMBER
#define X_POS_UNITS             58 //  which is usv right now

int led           = 8;            // the pin the LED is attached to
int pwm_channel   = 9;            // the pin the PWM high voltage generator is attached to
int pwm_width     = 45;           // how wide the PWM pulse is
int32_t frequency = 1200;         // frequency of PWM (in Hz)

unsigned long curr_pulse_cnt;     // A copy of the interrupt variable counts of radiation events
unsigned long old_pulse_cnt;      // contains count events not updated in interrupt routine for clicking
unsigned long last_pulse_cnt;     // from the previous 60 seconds
unsigned int counts_per_minute;   // the number of counts in the last 60 seconds
unsigned int counts_per_second;   // the number of counts in the last second
volatile unsigned long pulse_counter = 0;   // count events inside interrupt routine on pin 2
unsigned int cps_array[60];        // update this every second
boolean filled_array = 0;          // Indicates if to fill the array of historical data

  String str;            // used to convert numbers to a string
  char cstr[16];         
  float radiation_value;

unsigned long expire_time;
unsigned long current_time;

void setup()
  myGLCD.InitLCD();                                          // Init graphics library for Nokia 5110 display
  Serial.begin(9600);                                        // Enable serial communications
  // For noise suppression, enable pullup on interrupt pin
  pinMode(INTERRUPT_INPUT, INPUT);                           // Set pin 2 as an input for the radiation pulse
  digitalWrite(INTERRUPT_INPUT, HIGH);                       // set pin 2 high
  attachInterrupt(0, interrupt_handler, FALLING);            // attach to interrupt 0 of the Arduino (pin 2)

  // set up the LCD's number of columns and rows:

  //initialize all timers except for 0, to save time keeping functions
  InitTimersSafe();                                          // Used for PWM function to allow setting of 
                                                             // PWM frequency to better optimise H.V. generation

  // sets the PWM frequency for the specified pin. The PWM frequency was chosen to obtain 400V 
  // for the STS-5 tube bool success = SetPinFrequencySafe(pwm_channel, frequency);
  // if the pin frequency was set successfully, turn a pin on
      pinMode(led, OUTPUT);                                  // program LED channel to show success or failure of PWM
      digitalWrite(led, LOW);                                // PWM good so light off
      pinMode(pwm_channel, HIGH);
      pwmWrite(pwm_channel, pwm_width);                      // write PWM frequency to the port
      digitalWrite(led, HIGH);                               // Error so light on
  if (filled_array == 0)                                     // Pre fill the buffer
      // fill empty buffer array with counts per second
      filled_array = 1;                                      // Don't refill ever again
      Fill_Buffer(cps_array, 60, 1);                         // Change the priming value to counts_per_second
                                                             // if start was unusually high 120 CPM seems 
                                                             // about right


void loop()                                                  // Main program loop
  current_time = millis()/1000;                              // Current time in seconds
  curr_pulse_cnt = pulse_counter;                            // Get a copy of the current pulse count and hold it

  if (curr_pulse_cnt > old_pulse_cnt)                        // click once for multiple pulses but close
                                                             //  enough,         if it's clicking fast run AWAY!!
      old_pulse_cnt = curr_pulse_cnt;
      digitalWrite(led, HIGH);                               // flash the LED and pulse the speaker (if attached)
      digitalWrite(led, LOW);
  // Pulses per minute calc
  if (current_time >= expire_time)                           // Has a second elapsed
      expire_time = current_time + 1;                        // 1 second from now
      counts_per_second = curr_pulse_cnt - last_pulse_cnt;
      last_pulse_cnt = curr_pulse_cnt;                       // overwrite last_pulse_cnt for the next count
      // RollingAverage does two jobs, 
      //  1 adds the latest available value into the end of the array,
      //  2 returns the updated rolling average of the array      counts_per_minute = rollingAverage(cps_array, 60, counts_per_second); 
      radiation_value = counts_per_minute * 0.0057;           // for micro sieverts per hour
      dtostrf(radiation_value, 4, 2, cstr);

      myGLCD.print(cstr, LEFT, 6);

      str = String("uSv/h");                                  // Prep write of pulses since last update
      str.toCharArray(cstr, 6);
      myGLCD.print(cstr, X_POS_UNITS, Y_POS_UNITS);

      str = String(curr_pulse_cnt, DEC);                       // Counts
      myGLCD.print(cstr, 29, 0);

      str = String("Counts:");                                // Prep write of the pulses since last update
      str.toCharArray(cstr, 12);
      myGLCD.print(cstr, 0, 0);

      Draw_Graph(cps_array, 60);                              // Draw the Graph

  str = String(counts_per_second, DEC);                       // Prepare to write out the pulses since last update
  while (str.length() < 3)
      str = "0" + str;
  str = String("S");                    // Prepare to write out the pulses since last update
  str.toCharArray(cstr, 3);

  str = String(counts_per_minute, DEC);               // Counts per minute
  while (str.length() < 3)
      str = "0" + str;

  str = String("M");                    // Prepare to write out the pulses since last update
  str.toCharArray(cstr, 3);


void interrupt_handler()
  pulse_counter++;                // increment pulse counter
  delayMicroseconds(180);         // observed recovery tube time 180uS don't forget code overhead

unsigned int rollingAverage(unsigned int *store, int size, unsigned int entry)
    int j;
    unsigned long total = 0;
    unsigned int result;

    for(j=0; j    {
        store[j] = store[j+1];
        total += store[j];
    store[size-1] = entry;
    total += entry;
    result = total;
    return result;

void Fill_Buffer(unsigned int *store, int size, unsigned int entry)
        // this routine prefills the buffer with a provided value to allow the readings to look good
        int j;
    for(j=0; j    {
        store[j] = entry;
unsigned int max_val_in_buf(unsigned int *store, int size)
        // this routine finds the largest value in the buffer array
        int j;
        unsigned int max_val = 0;
     for(j=0; j          {
            if (store[j] > max_val)
                max_val = store[j];
       return max_val;

int scale_value(int max_val)
        // Choose a scale based the number given
        int selected_scale;
        if (max_val <= 10000) { selected_scale = 1000; }
        if (max_val <=  1000) { selected_scale = 100;  }
        if (max_val <=   100) { selected_scale = 10;   }
        if (max_val <=    50) { selected_scale = 5;    }
        if (max_val <=    20) { selected_scale = 2;    }
        if (max_val <=    10) { selected_scale = 1;    }
        return selected_scale;

int get_scale(unsigned int *store, int size)
         // given an unsigned integer arrary and its length,
         // returns an integer scale value for a graph (or the maximum value for the graph
         return scale_value(max_val_in_buf(store,size));

void Draw_Graph(unsigned int *store, int size)
    int j;
        int k;
        int scale;
        scale = get_scale(store, size);          // The graph scales based on the largest value 
                                                 // in the array, this function choses a scale 
     for(j=0; j          {
              k = 48- (2 * (store[j] / scale));
              myGLCD.drawLine(j, 48, j, k);      // draw the line
              myGLCD.clrLine(j, k, j, 32);       // clears the unused space above the line


No comments:

Post a Comment