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:
PinFunction
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
PinFunction
3SPI Clk
4SPI Dat
5Dat/Com
6RST
7CS
-
-Gnd
-LCD VCC
-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
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
//----------------------------------------------------------------------------//

#include
#include
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_COUNTS_PER_MINUTE 16
#define X_POS_COUNTS_PER_MINUTE 68
#define Y_POS_COUNTS_PER_SECOND 24
#define X_POS_COUNTS_PER_SECOND 68
#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 60 seconds
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];         // used to
  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
  if(success)
    {
      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
    }
  else
    {
      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)
      delay(2);
      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.setFont(BigNumbers);
      myGLCD.print(cstr, LEFT, 6);

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


      str = String(curr_pulse_cnt, DEC);                       // Counts
      str.toCharArray(cstr,10);
      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.toCharArray(cstr,8);
  myGLCD.setFont(TinyFont);
  myGLCD.print(cstr, X_POS_COUNTS_PER_MINUTE, Y_POS_COUNTS_PER_MINUTE);
 
  str = String("S");                    // Prepare to write out the pulses since last update
  str.toCharArray(cstr, 3);
  myGLCD.print(cstr, X_POS_COUNTS_PER_MINUTE + 13, Y_POS_COUNTS_PER_MINUTE);

  str = String(counts_per_minute, DEC);               // Counts per minute
  while (str.length() < 3)
    {
      str = "0" + str;
    }
  str.toCharArray(cstr,8);
  myGLCD.setFont(TinyFont);
  myGLCD.print(cstr, X_POS_COUNTS_PER_SECOND, Y_POS_COUNTS_PER_SECOND);

  str = String("M");                    // Prepare to write out the pulses since last update
  str.toCharArray(cstr, 3);
  myGLCD.print(cstr, X_POS_COUNTS_PER_SECOND + 13 , Y_POS_COUNTS_PER_SECOND);

  myGLCD.update();
   
}

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
          }
  }




Saturday, 28 June 2014

Huxley #710 Coupling Service...

One of the issues seen on Huxley #710 early on was Z axis coupling failure.

Alternate coupling fitted to Huxley #710
Any failure on a 3D printer tend to produce a mess but the nature of coupling failure experienced tended to be be when prints had been running a while and the Z axis has advanced. Probably as the stress on the coupling increases as the axis moves away from the bed and the heat from the table and motor tend to warm up the plastic parts. This of course is really annoying as there would be quite a bit of plastic wasted by the time failure occurred.

Failure seemed to happen either due to cracks in the printed coupling or movement of the clear plastic tube. Additionally fitting the coupling was tricky as the thread engagement on the design is difficult to achieve, particularly when on the last screw. The access is limited so this typically meant Huxley #710 had to be disconnected and moved to allow access to both sides of the printer. Then of course a bed leveling operation is implicit with coupling failure. Being prior to the glass bed and discovering that Huxley #710's Aluminium table is distorted so can not be levelled. This was a particularly trick task.

RepRap Pro produced an alternate coupling
which can be seen below with some pictures taken during fitting of the coupling.

Huxley #710 Z Axis Original Coupling Installation
Fitting of the new coupling will require a Z axis alignment validation. As its likely the bridge carrying the X axis will move during fitting. In fact it did and this was one of the reasons I put off changing the couplings out. But having been reminded of the issue today and having finished a number of prints on the current project.  I elected to make the change.


RepRap Pro Alternate Z Axis Coupling

Huxley #710 Original Z Axis Coupling Removed

Adjusted for the new coupling - Wish there was a little more tube
Fitting required a clean out of the 3mm holes, this is usual with RepRap Pro designs as they prefer to clean out with a drill. The nut should be pushed home into the bracket prior to placing the coupling on the shaft as the thread will not be long enough to engage when the coupling is placed over the clear plastic tube on the shaft. Fitting and sliding the clamp over the tube required the clamp was spread to clear the tube. The clamp was then carefully aligned and clearance checked. It does clear nicely!

Finally the print head height was checked and adjusted by rotating the motor to bring the X axis back into parallel to the table.


Huxley #710 with updated Z Axis coupling fitted

On the left in the picture you can see some nut covers I printed very early on to protect from the sharp edges on the ends of the threaded bar used to hold Huxley #710 together. I did this even though I spent considerable time dressing cut ends during the build. It finishes the build a little more cleanly and offers some protection from scratching.