Friday, 13 February 2015

Winter Project ...

Thing 582366 by Huxley #710
With the onset of winter thoughts turned to heating bills and trying to keep them down. Most time recently has been devoted to writing code on a Raspberry Pi for my Honeywell Evohome central heating system.

I decided to print a case I had seen on Thingiverse which had some interesting design features I was looking forward to trying out for my self.

Later Edit - Natko decided to take this design down from Thingiverse. This in turn seems to have destroyed the record of my make. A support request has been posted with Makerbot, I will post on this later. Fortunate I keep my own copies of images I post and the feedback left for the make. These are below, I also have all the STL files as well.

Printed on Huxley #710 using 0.25mm layer height. Slicing by Slic3r Rev 1.20 (edgy). I have a better retraction in Slic3r setting now so blobs greatly improved. Faberdashery filament, white and black.

The ribs inside on the lower half printed wider than the allowance / clearance on the upper mating half. So 1.50mm on the design came out as ~1.65mm due to bead thickness. The result is the lip on the bottom half will not immediately engage with the upper mating half as the post are nice and stiff. As there is a friction grip on the posts with spring action. I might have been tempted to create more clearance for the lower lip, to allow for manufacturing tolerance. Currently the design has it at around ~0.10mm clearance. This is quite a tight tolerance, for any (plastic) part and along a length of ~60mm, exciting.
Thing 582366 by Huxley #710

Some relieving of the posts with a craft knife and it snaps together satisfyingly snugly.
I wasn't sure that Huxley #710 would cope with the lower half overhang design, it seems to have worked well.

The break off supports worked a treat, that's why I included a picture of them, as I wanted to better understand the performance of this type of support. The feet recesses faired less well and without destroying the case these are on for good. I suspect more vertical height is required on the break off although a cooling fan might have helped as well.

I do think the case is ~7mm longer than absolutely necessary as there is a lot of clearance around the SD card to the edge of the case. Could be the design accommodates some feature I don't know about such as an expansion board or longer SD card.

Love the LED pipe / shield.

Nice design, better than the commercial case which felt flimsy at £4.50, for my Evohome temperature logger.

Meanwhile, as the winter progressed it was noticeable that quite a few windows in the house were not correctly seated in the frames. Wind in the autumn was howling through! There didn't seem to be the adjustment expected to cause the plastic frame to be pulled into position correctly. Inspection in the window frame jamb revealed a number of wedges, intended to push the window against the elastomer frame seal. It seemed clear these were not at the correct height. I could have printed a flat spacer and popped it under the original. However this would have meant the screws would not have the correct engagement. Additionally the whole item would be stood off more into the jamb and potentially fouled. I suspect the fitting kit for the window had different size wedges available. However we were never supplied any with the house. So I measured an original part and increased the wedge face height by 1.0 mm. It worked beautifully and having printed 12 of them (2 per frame) the offending windows now seal beautifully against the elastomer seal. All snug and warm for winter!
Window wedge to align and close plastic window into frame to seal

Evohome PHP software running on Raspberry PI

Thing 582366 by Huxley #710

Thing 582366 by Huxley #710

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