Tag Archives: arduino

Building a distributed cosmic ray detector in a weekend at CERN Webfest

This weekend I made a Android/Arduino based web enabled Muon (Cosmic ray) detector. I have to give a tonne of thanks to the team who did it with me (Ramviyas, Olof, Brad, Justin and Hugo) who deserve full credit for making it happen. I should also thank the ERGO Telescope team for thinking up the whole scheme of a distributed cosmic ray observatory in the first place and sending us the missing elements for our project.

Our Muon Source

What?

One ERGO pixel unit (which I hacked)

One Geiger-Muller tube and amp/PSU board (which we didn’t use in the end)

One Arduino MEGA ADK

Arduino Mega ADK

One Android Phone (with GPS, running 2.3.4 or better) – we used a Samsung Galaxy Mini GT-S5570

CNY17 optocoupler

Some Arduino Code, some Java and some web scripting

Muon Detector (ERGO Pixel)

How:

1) We hooked up the GM tube (in the end I had to hack the LED on the front of the ERGO box) to the Arduino MEGA ADK via a CNY-17 Opto-isolator. The signals out of the Geiger-Muller tube PCB were a bit too noisy to use straight up, and I didn’t manage to build myself a suitable high impedance device/amp to read them directly. We did the whole project in a weekend, so there wasn’t much time for anything!

Writing code…

2)We wrote some Arduino code that will hook up to an android phone and log events with an accuracy of 0.000212s. This is effectively a polling loop which checks a single input pin repeatedly and sends a signal to the Android when it sees a logical high on the input. The code will take any input on pin2 and output a packet with the relative timestamp to the Android. With this method we can resolve to approx 4700 loop cycles within the Arduino, using a counter (declared as a simple int that we add to). The counter is also re-set each 1s by a signal from the Android GPS clock. Counter values at reset are logged to use for a rolling timing calibration, but we didn’t get chance to implement this in the weekend.
3)We wrote an android app that reads NMEA sentences to get the raw time out of the GPS chipset (tested on Samsung phones running Android 2.3.4 and cyanogenmod) and sends a reset to the Arduino every second based on this value. The app also receives signals from the Arduino with the local timestamp (the loop counter in the Arduino) and adds this to the Navstar time (being the correct name for GPS time) We also correct the Arduino timestamp by multiplication with the constant of 0.000212 (our measured resolvable time interval) which bring the Arduino timestamp into seconds. We couldn’t get the date out of the GPS, so the leapsecond (Navstar time is 13 seconds behind UTC at the moment) correction will be necessary to datestamp it correctly – unless we figure out how to get the date from GPS directly.
4) For the moment we’re pushing it via HTTP post to  http://posttestserver.com/data/2012/08/05/populous/  Eventually we will send it to the ERGO database, once we have done some more precise measurements of the timing accuracy.

The Android App in action

We have a few things that would also be nice to add:
The Arduino code could do with some optimisation – we can probably increase the timing accuracy significantly (but this will obviously take more time than we had over the weekend) to go beyond the 212 microsecond resolution. It’s also running as a polling loop without any de-bounce, so duplicate readings are a distinct possibility. Using interrupts caused problems with the Arduino crashing, I’ll put it on my list to figure out when I have time.

Our system timing and architecture drawing

Timing calibration has been worked out on the back of an envelope, with some work the Android could do a rolling calibration on the Arduino (so that the 4700 loop cycles are adjusted based on observed performance).

As for the GM tube interface (which I struggled with), SEEED studio make a geiger shield that at first glance it looks like it would plug straight in fine – the design is open source as well. It can be found here: http://www.seeedstudio.com/depot/grove-geiger-counter-p-867.html?cPath=190

A great team!

That’s everything for the moment –  here are some links for the resources/websites we used/made:
Source code (arduino and android): https://github.com/jussy/cern-webfest
Pirate pads (where we did the working out..) http://piratepad.net/ep/pad/view/ro.-NYTlmpQZIl/latest
Advertisements
Tagged , , , , , , , ,

Arduino + Radiation = ?

In this post I’m going to talk about some preliminary radiation tests I’ve been performing on a couple of Arduino Uno modules and present the story so far…

This shows one of the two test plates, with the Orange LED luminaire, 10 230V direct LEDs, an arduino+ prototype shield and a set of terminals for connection to the main test chassis.

A while ago I attended the awesome Lift’12 conference in Geneva. I met some inspirational people and got inspired to test some arduinos in a radioactive environment. I haven’t found anyone else doing this kind of thing, which isn’t surprising as general access to radioactive environments is fortunately quite limited. However, I’m lucky enough to work at CERN and we have plenty of interesting places to test things!

This is very much a first tentative step into testing the arduino’s performance under the influence of radiation – only two devices were tested, there is no control group and I’m only writing down what I did after the event. I’ve been working on an LED lighting test for about a year now with some colleagues and last time we had access to the test facility I persuaded them to let me add two arduinos (which I named Archie and Bob) to the test bench as cheap signal conditioners to monitor the current flowing in our LED samples. I did also check with my boss (a highly recommended stage of the process for anyone else feeling similarly inspired into testing things at work…).

Beyond the glare of the LED's you can just make out the black ABS enclosure with arduino & shield.

Each arduino (UNO, R2) was mounted in an ABS enclosure with a prototype shield mounted on the top. The shields comprise:

  • Homebrew radiation hard power supply (230V to 16ishV)
  • Screw terminals for the connection of terminals to the outside world
  • Miniature CT + burden resistor to measure current in the LED samples

This shot shows the prototype shield with the power supply and the CT measuring the current in the test LEDs.

The ABS boxes are mounted on steel plates approximately 40cms by 60cms, which also contain a set of terminal blocks (for connection to the main test chassis) and a couple of LED samples under test. I should stress at this point that the arduino isn’t an integral part of the test bench, just a cheap way for us to extract additional information.

The ‘homebrew’ radiation hard power supply is a very basic affaire, composed of a cheap PCB mounted 230V-12V transformer and a GBU8k glass passivated full bridge rectifier with an 800V breakdown voltage. The GBU8K has previously been tested at CERN and found to be a reasonably solid performer when subjected to radiation. This PSU is obviously extremely basic, providing only a wobbly DC voltage at about 14-16V with no load. This set of tests relies on the linear regulator onboard the arduino to step down the voltages to a smooth 3.3/5V level.

The CT is hooked up across a burden resistor to the Ain pins, as this is a test bench and I’m using some components which are sized to a full scale deployment (72 led’s instead of 2) the observed voltage is very low – which is where the signal conditioning of the Arduino comes in. The onboard ADC and DAC are used to sample the voltage across the CT several times, take the maximum measured value (in an effort to measure the peak current for the LEDs running on a 230V 50Hz supply), multiply it by a factor of 10 and then send an analogue output (0-5V) reflecting the multiplied value. The analogue output is sent for 5 minutes, followed by a 0V value for 5 minutes before the whole cycle is repeated and the current is re-sampled. Potentially this helps us check that the arudino can still output a 0 volt signal, that the internal clock is reasonably accurate and from the periodic change in output voltage we can rapidly conclude if it’s broken…  the signal also looks rather like a heartbeat, which is intrinsically pleasing (for me as an Engineer anyway!) to see.

This shows the daily heartbeat of one of the test benches, with operation for 1 hour per day in alternating periods of 5 minutes 'sample' and 5 minutes at 0V.

The justification for this is that it would be very hard to measure the raw CT signal (which is just a few mV, for a current of only a few mA in the LED’s) via the experimental infrastructure we have in place (over a kilometer of copper cables), without adding some sensitive and expensive amplifiers – which more likely than not would be rapidly destroyed by the radiation in the test environment. So in this case the Arduino gives us a cheap (<20 euro) alternative, indicating as a minimum that there is still current flowing to the LEDs, and as an added bonus we can monitor and evaluate the performance of both Archie and Bob as they soak up the rays!

So far (over a month into the ‘test beam’) both test benches are still functional, although some distinctive wobbles can be observed on the outputs of the DAC’s from both Archie and Bob. I was personally quite surprised that they lasted more than 1 day, as a previous test had destroyed a number of SMPS (switch mode power supply) within mere hours once the radiation started.

This is a close up of 1 hour of operation, showing the shape of the DAC output in periods of discrete 5 minute operation, interspersed with periods of a 0V output signal.

So far the conclusion is that the two arduino’s tested are still functional, after a 1 hour per day duty cycle within the radiation test area. As time goes on I’ll update this blog post with some more details about the specific type and levels of radiation experienced, plus a more scientific analysis of the outputs taken from the ADC’s. I feel obliged to point out that this is a very long way short of a formal ‘radiation qualification’ , which would require amongst other things components with a fully controlled provenance, a statistically significant sample pool, source code which tests the operation of (ideally) all the silicon within the arduino and peripherals, and of course a detailed scientific analysis of the results. But every journey starts with the first step and so far Archie and Bob are still marching down their radioactive road…

The source code (sorry it’s rather messy, but it works) running on the arduino’s is below:

/*
arduino radiation test code version 1
current monitoring for led sources within CNGS tunnel, CERN. 8/3/12
100:1 CT with four passes of wire (i.e. 4x amplification on current)

written very quickly and based on a bunch of examples by
David Cuartielles
& Tom Igoe

*/

int sensorPin = A0;    // select the input pin for the potentiometer
int ledPin = 3;      // select the pin for the LED
int sensorValue = 0;  // variable to store the value coming from the sensor
int interimValue = 0;

void setup() {
// declare the ledPin as an OUTPUT:
//pinMode(ledPin, OUTPUT);
Serial.begin(9600);
}

void loop() {
// read the value from the sensor:

//init and first value read
sensorValue = 0;
interimValue = analogRead(sensorPin);
sensorValue = interimValue;
//start the acquisition process proper
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(123);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(137);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}  interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(123);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(137);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}  interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(113);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(123);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}
delay(137);
interimValue = analogRead(sensorPin);
if (interimValue > sensorValue)
{
sensorValue = interimValue;
}

//Multiply by 10. Typical measured values pre-start were 15-17 raw (150-170 with multiplication)
//then send this value out over the serial and the pwm ports
sensorValue = sensorValue * 10;
analogWrite(ledPin, sensorValue);
Serial.println(“I am alive and my name is Archie”);
Serial.println(sensorValue, DEC);
//now wait another 5 mins
delay(300000);

//send a zero for 5mins to allow measurement calib.
analogWrite(ledPin, 0);
delay(300000);

}

Tagged , , ,

Robot Drawing Arm

Printer ink is the most expensive commodity in the world (by weight, or volume.. apparently). I’m always running out of it just when I need to print out a boarding pass, or some other essential printed document. So wouldn’t it be great if you could print things out using a biro?

Here is my version 0 attempt at such a device in action at Lift’12. It actually has a lot of similarities to this drawing automaton, although I only realised that after I’d finished and the video blogger Nicolas Charbonnier pointed it out to me! Nicolas did a video interview with me at Lift’12 where I had the chance to show off my creation a little. You can watch the video (which includes a fair bit of discussion about my day job at CERN) here.

Basic idea: A robot arm with two drive servos and one more to move a pen (or pencil) up and down onto standard A5 paper. Plugs into a computer for the power and the drawing data. Draws following the mouse or via a crude black & white filter on a JPEG image.

Ingredients:

Arduino (I used my trusty old duemilanove), USB lead to connect it to a computer, three Futaba S3003 servos (or equivalent), a few bits of wire, and some stationary from Muji (one A5 file box, two aluminium rulers, one wooden ruler and one acryllic one to be precise). A biro (one with four colours if you are feeling fancy!). Software: Processing and some jpg’s (+a bit of patience)!

Building it was fairly straight foreward, with the most complicated part making a cam to lift the pen up/down. I did this by drilling 1/2 of an acryllic ruler so that the pen can pass through it, with the pen attached using a single thin screw to an extremity of a servo horn. Fixing the acrylic ruler to the up/down servo didn’t work very well, hence the blue ‘LIFT’ tape in the photos.

Once assembled it should look like this – obviously you can modify the hardware build as you wish.Image

You can see the coins I added (wrapped in masking tape) as a counterweight on the left. It helped to resolve some issues with the lack of co-planar motion in the two arm segments.

After trying to write my own code I gave up and used the excellent Firmata library for the arduino/processing link (my own code was doing strange things…). The computer communicates serially to the plotter, sending positions for each of the three servos.

When I was writing the initial processing sketch, I controlled the arm directly for the first few passes. This was disasterous as I managed to snap teeth off two out of my three servos (originally they were super-micro lightweight ones… after the damage I decided to upgrade to the S3003’s). This lead me to develop my own ’emulator’ for the arm, so that I could solve the mathematical and physical constraints without breaking anything else. You can see an early demo of the arm here.

Here is the processing source code:

import processing.serial.*;
import cc.arduino.*;
Arduino arduino;
int countstart = 0;
int servo1Pin = 9; // Control pin for servo motor
int servo2Pin = 10; // Control pin for servo motor
int servo3Pin = 11; // Control pin for servo motor
int armposn;// = 0;
int digitposn;// = 0;
//int penposn;//= 0;
int penstate = 0;

int xcent = 300;
int ycent = 300;
//int targetX= 400;
//int targetY= 200;
int line1 = 130;
int line2 = 130;
int minimumlength = 40;
color black = color(0);
PImage img;
PImage edgeImg ;

void setup(){
float[][] kernel = { { -1, -1, -1 },
{ -1, 9, -1 },
{ -1, -1, -1 } };

size (600, 600);
background(255);

img = loadImage(“ben.jpg”); // Load the original image

img.loadPixels();
edgeImg = createImage(img.width, img.height, RGB);

// Loop through every pixel in the image.
for (int y = 1; y < img.height-1; y++) { // Skip top and bottom edges
for (int x = 1; x < img.width-1; x++) { // Skip left and right edges
float sum = 0; // Kernel sum for this pixel
for (int ky = -1; ky <= 1; ky++) {
for (int kx = -1; kx <= 1; kx++) {
// Calculate the adjacent pixel for this kernel point
int pos = (y + ky)*img.width + (x + kx);
// Image is grayscale, red/green/blue are identical
float val = red(img.pixels[pos]);
// Multiply adjacent pixels based on the kernel values
sum += kernel[ky+1][kx+1] * val;
}
}
// For this pixel in the new image, set the gray value
// based on the sum from the kernel
edgeImg.pixels[y*img.width + x] = color(sum);
}
}
// State that there are changes to edgeImg.pixels[]
edgeImg.updatePixels();

arduino = new Arduino(this, Arduino.list()[1]);
arduino.pinMode(servo1Pin, Arduino.OUTPUT);
arduino.pinMode(servo2Pin, Arduino.OUTPUT);
arduino.pinMode(servo3Pin, Arduino.OUTPUT);

arduino.analogWrite(servo1Pin, 70);
arduino.analogWrite(servo2Pin, 170);
arduino.analogWrite(servo3Pin, 50); // the servo moves to the horizontal location of the mouse

// note – we are setting a digital pin to output
background(255);

}

void draw()
{
if (countstart == 1){
makepic();
arduino.analogWrite(servo1Pin, 70);
arduino.analogWrite(servo2Pin, 170);
arduino.analogWrite(servo3Pin, 50);
}
countstart = countstart +1;
}

//void draw(){
void makepic(){
background(255);
//fill(50);
stroke(0);
//rect(350+30,320-10,130,130);
image(img, 0, 0, 130, 130); // Displays the image from point (0,0)

image(img, 130, 0, 130, 130); // Draw the new image
filter(POSTERIZE,4);
filter(THRESHOLD);
colorMode(HSB);

for (int xcycle = 130; xcycle < 259; xcycle = xcycle+2)
{
for (int ycycle = 0; ycycle < 129; ycycle++)
{
if (get(xcycle,ycycle)<-2)
{
set(xcycle+350-130+30,ycycle+320-10,black);
markpaper(xcycle+350-130+30,ycycle+320-10,110);//changed pen variable 110
delay(20);
markpaper(xcycle+350-130+30,ycycle+320-10,170);//comment this line and the one further below out to draw lines not dots
//delay(20);
}
else
{
set(xcycle,ycycle,0);
markpaper(xcycle+350-130+30,ycycle+320-10,170);
//delay(50);
}
}
for (int ycycle = 129; ycycle > 0; ycycle–)
{
if (get(xcycle+1,ycycle)<-2)
{
set(xcycle+350-130+1+30,ycycle+320-10,black);
markpaper(xcycle+350-130+1+30,ycycle+320-10,110);
delay(20);
markpaper(xcycle+350-130+1+30,ycycle+320-10,170);//likewise comment this line out for lines not dots on your paper as well as the one further up

}
else
{
set(xcycle+1,ycycle,0);
markpaper(xcycle+350-130+1+30,ycycle+320-10,170);
//delay(50);
}
}
}

}

void markpaper(int targetX, int targetY, int penposn){
//print(“xposn” + targetX);
//println(” yposn” + targetY);
float angle1 = atan2((targetX – xcent), (targetY – ycent));
float sectorlength = sqrt(sq(targetX – xcent)+sq(targetY – ycent));

//if it’s out of reach, shorten the length in the same direction
if (sectorlength > (line1+line2))
{
sectorlength = line1+line2;
targetX = int(xcent + sin(angle1)*sectorlength);
targetY = int(ycent + cos(angle1)*sectorlength);
}

if (sectorlength < minimumlength)
{
sectorlength = minimumlength;
targetX = int(xcent + sin(angle1)*sectorlength);
targetY = int(ycent + cos(angle1)*sectorlength);
}

float internangle = acos((sq(sectorlength)-sq(line2)-sq(line1))/(2*line1*line2));

float sendangle1st = (angle1+(internangle/2));
if (degrees(sendangle1st) < 0 )
{
sendangle1st = radians(0);
}
if (degrees(sendangle1st) > 180 )
{
sendangle1st = radians(180);
}

int line1X = int(xcent+ sin(sendangle1st)*line1);
int line1Y = int(ycent+ cos(sendangle1st)*line1);
int line2X = line1X + int(sin(((angle1+(internangle/2) – internangle)))*line2);
int line2Y = line1Y + int(cos(((angle1+(internangle/2) – internangle)))*line2);
stroke(0,9);
line(xcent, ycent, line1X, line1Y);
line(line1X, line1Y, line2X-1, line2Y-1);
// if (penposn == 150)
// {
// set(line2X,line2Y,black);
// println(“TEXT”);
// }
// set(mouseX,mouseY,black);

//if it’s too close set the minimum threshold in the same direction

int sendangle1 = round(degrees(sendangle1st));
int sendangle2 = round(degrees((radians(180) – internangle)-radians(35)));
//println(“Send1>”+ sendangle1 + ” Send2 >” + sendangle2);
digitposn = sendangle2;
armposn = sendangle1;

arduino.analogWrite(servo1Pin, digitposn);
arduino.analogWrite(servo2Pin, penposn);
arduino.analogWrite(servo3Pin, armposn);
if (penstate != penposn)
{
delay(300);
}
else
{
delay(1);
}// the servo moves to the horizontal location of the mouse
penstate = penposn;
}

//end of source\\

I should add that this code is very rough, and contains hard-coded boundary limits for the drawing area which are specific to the physical configuration of my hardware (and painstakingly obtained by moving the arm and looking at where the pen is). This code is also set to draw dots rather than lines, as people generally indicated a preference for this kind of output.

Performance is ‘interesting’. I’ve set it to draw an approx 160×160 matrix, one pixel at a time. Depending upon the number of dark pixels this can take up to 1.5 hours per image. If you want to draw lines that go between all the connected pixels the time per image comes down to <20 minutes. This is mostly a function of delays added in the code to cope with mechanical oscillations in the two arms and the pen.

Things that could be improved:

  • The code is very ropey! Like my flat, it would benefit from a tidy and the hoovering up of any stray variables.
  • The drawing area is still a bit sub-optimal. The arm can cover almost 100% of the space of an A5 sheet, however I’m only using about 65% for drawing at the moment. The boundary conditions for the angles would need to be carefully adjusted for this.
  • Worst of all the image processing is very crude (simple cut for black and white). This is mostly because I’ve been lazy (it WORKS, right?) and wish to avoid re-inventing the wheel when there are so many excellent image processing suites out there. If I have time it would be great to code something to vectorise JPG’s properly rather than my current ‘quick and dirty’ approach.
  • Colour! My pen has Red, Green, Blue and Black ink.. mostly I use black or blue for clarity, but it would be very interesting to come up with a multi-pass colour image reproduction system.
  • Good projects are never quite finished 😉

Image

A picture of Ben Bashford drawn from a JPG photo, live on the stage of Lift’12.

I hope you liked my creation – please feel free to comment, suggest and contribute. I’d like to thank Amy Shen for being my first drawing subject and for helping me to figure out the maths of converting an XY co-ordinate space into a two polar variables.

Tagged , , , , , , , , , , , , , , , ,

Arduinopower Source Code

So the other day I got a comment about my Ardunipower project. This encouraged me to dig out the source code and post it here. It’s a long way short of finished (and I’ve stopped working on the project.. since early 2010!), but hopefully this will provide at least some kind of insight into how I had commands going back and forth to the ADE7753 power measurement IC.

This code initialises all the various addresses within the ADE7753 from the data sheet. I then wrote (hardest part) a couple of routines to do multi-byte send/receive operations over SPI (Maybe it’s I2C.. I can’t remember anymore!) between the Arduino and the ADE7753. This particular version of the code is a demonstrator that polls some values and sends them back to a PC over serial (great if you have a bluetooth link going on!), slightly less good if you’re on a cable as my PCB design isn’t opto-isolated.

The next stage (that I didn’t do!) was calibration – actually turning the echoed values into something meaningful, and in particular adjusting to the measurement skew introduced between the voltage measures and the current transformer.

 

//registers on ADE7753  
#define WAVEFORM 0x01
#define AENERGY 0x02
#define RAENERGY 0x03
#define LAENERGY 0x04
#define VAENERGY 0x05
#define LVAENERGY 0x06
#define LVARENERGY 0x07
#define MODE 0x09
#define IRQEN 0x0A
#define STATUS 0x0B
#define RSTSTATUS 0x0C
#define CH1OS 0x0D
#define CH2OS 0x0E
#define GAIN 0x0F
#define PHCAL 0x10
#define APOS 0x11
#define WGAIN 0x12
#define WDIV 0x12
#define CFNUM 0x14
#define CFDEN 0x15
#define IRMS 0x16
#define VRMS 0x17
#define IRMSOS 0x18
#define VRMSOS 0x19
#define VAGAIN 0x1A
#define VADIV 0x1B
#define LINECYC 0x1C
#define ZXTOUT 0x1D
#define SAGCYC 0x1E
#define SAGLVL 0x1F
#define IPKLVL 0x20
#define VPKLVL 0x21
#define IPEAK 0x22
#define RSTIPEAK 0x23
#define VPEAK 0x24
#define RSTVPEAK 0x25
#define TEMP 0x26
#define PERIOD 0x27
#define TMODE 0x3D
#define CHKSUM 0x3E
#define DIEREV 0x3F

#define DATAOUT 11//MOSI
#define DATAIN  12//MISO 
#define SPICLOCK  13//sck
#define SLAVESELECT 10//ss

//opcodes
#define WREN  6
#define WRDI  4
#define RDSR  5
#define WRSR  1
#define READ  3
#define WRITE 2

//SPCR = (1<<SPE)|(1<<MSTR)|(1<<CPOL)|(1<<CPHA)|(1<<SPR1)|(1<<SPR0); //set clock rate to 1/16th system

byte eeprom_output_data;
byte multi_byte_data[3];
byte eeprom_input_data=0;
long long_eeprom_data = 0;
byte clr;
int address=0;
//data buffer
char buffer [128];

void fill_buffer()
{
  for (int I=0;I<128;I++)
  {
    buffer[I]=I;
  }
}

char spi_transfer(volatile char data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  {
  };
  return SPDR;                    // return the received byte
}

void setup()
{
  Serial.begin(115200);

  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT,OUTPUT);
  digitalWrite(SLAVESELECT,HIGH); //disable device
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<CPHA)|(1<<SPR1)|(1<<SPR0);
  SPSR = (0<<SPI2X);
  clr=SPSR;
  clr=SPDR;
  delay(10);
  Serial.println("init complete");
  delay(1000);

  //testrun starts here

  //utils
  //read_eeprom(address value, how many bytes)
  //write_to_eeprom(target, values, bytes to write)

  //read what is there right now
  //address = LINECYC;
//  Serial.print(address,HEX);
 // eeprom_output_data = read_eeprom(STATUS,2);

  //long TestWrite;
  //TestWrite = 0xABCD;
  //write_to_eeprom(address, TestWrite, 2);
// Serial.println(eeprom_output_data, BIN);
  //eeprom_output_data = read_eeprom(address, 2);
  //Serial.println("Completed basic read write test");

}

void write_to_eeprom(int EEPROM_address, long write_buffer, int bytes_to_write)
{
  //Serial.print("Multiwrite ops to addr>");
  //Serial.println(EEPROM_address, HEX);
  //set write mode
  byte make_write_cmd = B10000000;
  byte this_write = B00000000;
  EEPROM_address = EEPROM_address|make_write_cmd;
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer((char)(EEPROM_address));      //send address

  //here there should be a t7 delay, however long that is
  for (int i = 0; i<bytes_to_write; i++){
  //Serial.println(i);
  this_write = byte(write_buffer>>(8*((bytes_to_write-1)-i)));
  //Serial.println(this_write, HEX);
  spi_transfer((char)(this_write));      //send data byte
  }
  digitalWrite(SLAVESELECT,HIGH); //release chip, signal end transfer
}

long read_eeprom(int EEPROM_address, int bytes_to_read)
{
  //Serial.print("Multi-read to addr>");
  //Serial.print(EEPROM_address, HEX);
  Serial.println(" Data starts:");
  long data = 0;
  byte reader_buf = 0;
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer((char)(EEPROM_address));      //send LSByte address
  for (int i = 1; i <= bytes_to_read; i++){
    reader_buf = spi_transfer(0xFF); //get data byte
    Serial.println(i);
    Serial.println(reader_buf, BIN);

    data = data|reader_buf;
    if (i< bytes_to_read) {
      data = data<<8;
    }
    }
  Serial.print("completed. data was>");
  Serial.println(data, BIN);
  digitalWrite(SLAVESELECT,HIGH); //release chip, signal end transfer
  return data;
}

void loop()
{

    eeprom_output_data = read_eeprom(STATUS,2);
   Serial.println("STATUS CHECK");
   Serial.println(eeprom_output_data, BIN);
   Serial.println(eeprom_output_data, HEX);
   delay(1000);
   eeprom_output_data = read_eeprom(LINECYC,2);
   Serial.println("LINECYC CHECK");
   Serial.println(eeprom_output_data, BIN);
   Serial.println(eeprom_output_data, HEX);   

}
Tagged , , , , , , , , , , , , ,

DIY Wedding Photobooth

As if there aren’t enough accounts of how to build your own photobooth on the internet already, here is one more!

Brief: An easy to assemble, relatively drunken person (it is a wedding after all…) proof photobooth that is robust and easy to use by all ages. Basic photo taking capability that produces the authentic Photo-booth experience and encourages silly poses. Cheap-ish to construct with a maximum of re-usable parts afterwards as the remains are going to the bride and groom’s place as shelving units for their shed.

Physical Hardware (mostly from Ikea):

Two sets of GORM shelves, one slim (for the computer enclosure) and one deep for the seating area.

Four height-extenders used to brace the gap between the two shelf units (so 2 packs of 2 bits of wood each)

A curtain rail to cover the door when the booth is in use, hung with an Irma fleece rug that has holes cut into it to go on the rail

One additional large shelf to connect the two shelves together on the side

Four plastic shelf wall mounting brackets (a bargain at about 40p each!)

Covers – 10 x Irma covers (super cheap at 1 pound each) and two of the more expensive Polarvide covers – one black one for the front to cover the computer/electronics and one for the back to make the rear ‘curtain’

Electrical and Electronics:

Again from IKEA 4x Lagra spotlights and compact fluorescent 7W lamps to give it that ‘showbiz’ feel inside. I had problems screwing the bulbs in due to the metal reflectors, so had to cut a couple of them out. After removing the first two, I found that the other two could be persuaded to screw in with a fair bit of force.

One IKEA ‘non’ 11W fluorescent strip light, to go in the top of the booth for ‘house lights’ when the spotlights are off.

A four way power strip from home, plus a two way switch from my dad’s box of household electrical stuff. We also brought along an extension lead to ensure that we could power it all at the venue.

The main component of the electronics was my old laptop – A Highgrade Notino circa 2003, with a 180 degree hinge, so I could slot it in to a space between a shelf mounted vertically with a height extender batten screwed to the back of it. The laptop simply slots in and sits in the gap – excellent for checking the saving of files and making last minute modifications. If you don’t have a laptop that goes flat this will be more complicated! I hooked it up to an old MSI Star Cam Clip (as I bought a couple some time ago) to take the photos, which provided a resolution of 640×480. Not exactly HD, but fine for a photobooth.

Some more sparkle was added by including my girlfriends desk-fan, wired through the second switch on the switch plate, to give that ‘blowing hair’ look.

The booth action was controlled by a single button, mounted in between the slats of the vertical front facing shelf. It was connected to an Arduino running some very simple code which sent a letter over the serial port to the laptop when pressed. Originally I had two buttons, but actually only having one worked fine! The electronics weren’t that well screened, so switching on the fan also had the effect of triggering the booth countdown, which wasn’t all bad for those who couldn’t find the real trigger button.

Software:

The booth software was written in processing, running on Windows XP. To make it work with the webcam I needed to install quicktime and a legacy version of WinVDIG which I found on the web. In the traditional ‘photobooth’ format, I set it up to take 4 consecutive pictures, saving each as a JPEG with a unique time/date/incremented counter filename. On the completion of the fourth picture, they were all re-loaded and put into a 2×2 square format like a print out, displayed back to the user and saved as a composite JPEG. We didn’t attempt to print anything out as the booth was intended (and succeeded in being) able to run ‘stand alone’ without too much human intervention for the evening.

[After all the testing, I actually put my processing source on a corrupt USB key so had to re-write it from the previous version the night before the wedding! Oops.. ]

Source Code:

For the Arduino –

int stbuttonPin = 2;  // change to whatever you want
int tkbuttonPin = 3;
int ledPin = 13; // just using for example
boolean tkoldval = HIGH;
boolean stoldval = HIGH;

void setup()
{
     pinMode(ledPin, OUTPUT);     // LED as output
     pinMode(stbuttonPin, INPUT);    // button as input
     digitalWrite(stbuttonPin, HIGH); // turns on pull-up resistor after input
     pinMode(tkbuttonPin, INPUT);    // button as input
     digitalWrite(tkbuttonPin, HIGH); // turns on pull-up resistor after input
     Serial.begin(9600);
}

void loop()
{
tkoldval = digitalRead(tkbuttonPin);
stoldval = digitalRead(stbuttonPin);
     if( (tkoldval == HIGH) &(digitalRead(tkbuttonPin) == LOW ))   // when pin goes LOW
	   {
	    Serial.println('t');
            digitalWrite(ledPin, HIGH);	     // turn on LED
            delay(10);
	    }
     if( (stoldval == HIGH) & (digitalRead(stbuttonPin) == LOW ))   // when pin goes LOW
	   {
	    Serial.println('s');
            digitalWrite(ledPin, HIGH);	     // turn on LED
            delay(10);
	    }

    digitalWrite(ledPin, LOW);	  // well, turns led off!
    //delay(1000); //one second delay

}

And the code for Processing:

 
  public static void main( String args[] ) {
   PApplet.main( new String[] { "--present", "superbooth8511" } );
 }
//import fullscreen.*;
//import japplemenubar.*;
import processing.serial.*;

/* (made by Tjerk in 10 minutes  ) */

import processing.video.*;
Serial myPort;  // Create object from Serial class
char val;      // Data received from the serial port

Capture myCapture;
int a = 1024; // width of window
int b = 768;  // height of window
int x = 100;  // x- position text
int y = 700; // y- position text
int capnum = 0;
int countdowntimer = 10;
int globalframecount = 0;
PImage aj;
PImage bj;
PImage cj;
PImage dj;
//FullScreen fs;

void setup(){

    //print(Serial.list()[0]);
  String portName = Serial.list()[0];
  myPort = new Serial(this, portName, 9600);
  val = 'o';
  frameRate(25);
 size(a,b);
 // fs = new FullScreen(this);

// setFullScreen(true);
 background(0);

 //PFont fontB = loadFont("Ziggurat-HTF-Black-32.vlw");
 //textFont(fontB, 100);
myCapture = new Capture(this, a, b, 30);
}
void captureEvent(Capture myCapture) {
 myCapture.read();
 //fs.enter();
}
void draw(){
 PFont fontA = loadFont("Ziggurat-HTF-Black-32.vlw");
 textFont(fontA);
  //println("start");
if ( myPort.available() > 0) {  // If data is available,
    val = (myPort.readChar());
   //println('1');
//println(val);    // read it and store it in val
 }
 if (mousePressed== true){
   globalframecount = 1;
 }

   //background(255);
   fill(0);

switch(val) {

  case 's':
  globalframecount = 1;
//  background(0);

 break;

  case 't':
//null
    break;
  default:
    //println("Zulu");   // Prints "Zulu"
    break;
}

 if (globalframecount == 25) {
 countdowntimer = 9;
 }
 if (globalframecount == 50) {
 countdowntimer = 8;
 }
 if (globalframecount == 75) {
 countdowntimer = 7;
 }
 if (globalframecount == 100) {
 countdowntimer = 6;
 }
 if (globalframecount == 125) {
 countdowntimer = 5;
 }
 if (globalframecount == 150) {
 countdowntimer = 4;
 }
 if (globalframecount == 175) {
 countdowntimer = 3;
 }
 if (globalframecount == 200) {
 countdowntimer = 2;
 }
 if (globalframecount == 225) {
 countdowntimer = 1;
 } 

 if ((globalframecount < 250) & (globalframecount > 0)) {
       image (myCapture, 0,0);
       textFont(fontA, 30);
       fill(0);
       text ("Preview! Get ready for your photo in "+str(countdowntimer), x+2, y);
       text ("Preview! Get ready for your photo in "+str(countdowntimer), x, y+2);
       text ("Preview! Get ready for your photo in "+str(countdowntimer), x-2, y);
       text ("Preview! Get ready for your photo in "+str(countdowntimer), x, y-2);
       textFont(fontA, 30);
       fill(255);
       text ("Preview! Get ready for your photo in "+str(countdowntimer), x, y);
       //text (countdowntimer, width-250, y);
       globalframecount++;
 }

 if ((globalframecount >= 250) & (globalframecount < 500)) {
      image (myCapture, 0,0);

 if ((globalframecount > 250) & (globalframecount < 311))
 {
 textFont(fontA, 100);
 fill(0);
 text (str(countdowntimer), width/2+2, height/2);

 text (str(countdowntimer), width/2, height/2+2);

 text (str(countdowntimer), width/2-2, height/2);

 text (str(countdowntimer), width/2, height/2-2);

 fill(255);
 text (str(countdowntimer), width/2, height/2-2);
  }
if (globalframecount == 250)
{
  countdowntimer = 3;
}

if (globalframecount == 270)
{
  countdowntimer = 2;
}

if (globalframecount == 290)
{
  countdowntimer = 1;
}

if (globalframecount == 312) {
  background(255);
}
   if (globalframecount == 313){
  saveFrame(capnum+".jpeg");
  aj = loadImage(capnum+".jpeg");
  capnum++;}

  if ((globalframecount > 314) & (globalframecount < 373))
 {
 textFont(fontA, 100);
 fill(0);
 text (str(countdowntimer), width/2+2, height/2);

 text (str(countdowntimer), width/2, height/2+2);

 text (str(countdowntimer), width/2-2, height/2);

 text (str(countdowntimer), width/2, height/2-2);

 fill(255);
 text (str(countdowntimer), width/2, height/2-2);
  }
if (globalframecount == 314)
{
  countdowntimer = 3;
}

if (globalframecount == 335)
{
  countdowntimer = 2;
}

if (globalframecount == 355)
{
  countdowntimer = 1;
}

  if (globalframecount == 374) {
  background(255);
}

  if (globalframecount == 375){
  saveFrame(capnum+".jpeg");
  bj = loadImage(capnum+".jpeg");
  capnum++;
    }

  if ((globalframecount > 375) & (globalframecount < 436))
 {
 textFont(fontA, 100);
 fill(0);
 text (str(countdowntimer), width/2+2, height/2);

 text (str(countdowntimer), width/2, height/2+2);

 text (str(countdowntimer), width/2-2, height/2);

 text (str(countdowntimer), width/2, height/2-2);

 fill(255);
 text (str(countdowntimer), width/2, height/2-2);
  }
if (globalframecount == 376)
{
  countdowntimer = 3;
}

if (globalframecount == 401)
{
  countdowntimer = 2;
}

if (globalframecount == 420)
{
  countdowntimer = 1;
}

  if (globalframecount == 438) {
  background(255);
}

    if (globalframecount == 439){
  saveFrame(capnum+".jpeg");
  cj = loadImage(capnum+".jpeg");
  capnum++;
    }

  if ((globalframecount > 440) & (globalframecount < 497))
 {
 textFont(fontA, 100);
 fill(0);
 text (str(countdowntimer), width/2+2, height/2);

 text (str(countdowntimer), width/2, height/2+2);

 text (str(countdowntimer), width/2-2, height/2);

 text (str(countdowntimer), width/2, height/2-2);

 fill(255);
 text (str(countdowntimer), width/2, height/2-2);
  }
if (globalframecount == 440)
{
  countdowntimer = 3;
}

if (globalframecount == 460)
{
  countdowntimer = 2;
}

if (globalframecount == 480)
{
  countdowntimer = 1;
}

   if (globalframecount == 497) {
  background(255);
}    

     if (globalframecount == 498){
  saveFrame(capnum+".jpeg");
  dj = loadImage(capnum+".jpeg");
  capnum++;
     }

  if (globalframecount == 499) {
 fill(255);
 background(0);
 rect(width/11, height/10, (width-240), (height-100));
 image(aj, width/8, height/8, width/3, height/3);
 image(bj, 4*(width/8), height/8, width/3, height/3);
 image(cj, width/8, 4*(height/8), width/3, height/3);
 image(dj, 4*(width/8), 4*(height/8), width/3, height/3);
 fill(0);
 text ("Press button to take some more!", x, y);
 saveFrame("multipage"+capnum+".jpeg");
 globalframecount = -1;
     }

 //delay(50);
 globalframecount++;}

}

The photobooth output!

The assembly took a few hours with my trusty battery drill and a socket bit to drive the GORM bolts into place. I also used plenty of woodscrews for my ‘non standard’ fixings into the gorm framework, and a couple of different sizes of wood drill bits to pre-drill to prevent splitting in the new mount locations.

Relative to the picture above, the only modifications were the omission of the top and bottom shelves in the middle section – these were replaced with two height extender bars screwed into the tops of each of the other modules, which proved sufficiently rigid to hold it all together. I also added another height extender bar at the back of the computer cabinet, to mount the webcam on. With the installation of the 4 shelf brackets to support the seat, there wasn’t room to install a full depth shelf underneath as a ‘back board’ for peoples feet, so I put a narrow one there instead, and didn’t put any horizontal element in the middle of the computer cabinet section.

Finally, I wrapped the whole thing in the IRMA cloth, using screws as attachment – the original plan called for a staple gun but we couldn’t find one! The curtain rail was mounted above the entrance, screwed to each corner of the booth. The final height extender was screwed to the top using the 90 degree ‘non topple’ brackets, and served as a sign. I had to cut the black polarvide cloth that covered the laptop (apart from the camera and screen of course!), so that I could fit the screen up in the middle of it. Otherwise damage to the cloth was minimal.

A last minute enhancement came from the IKEA bargain bin, where I picked up a large seat cushion cover for £3 and secured this to the seat with some screws underneath. Extra padding would have been nice, but the cover was sufficient to make sure that fancy clothes didn’t get caught on any rough bits of wood.

On the night, the ‘non’ fluorescent tube was useful as I didn’t wire it through the switch, so it remained on all the time – showing people that the booth was ready to use, even if the spotlights inside had been turned off. The booth broke down a couple of times when users accidentally pressed the ‘take picture’ button on the webcam whilst adjusting it, but otherwise it was trouble free.

Disassembly took less than an hour, again using the power drill with a socket to remove the GORM bolts. I was also able to recover 90% of the screws I used, which I can recycle for future projects! I was very happy with how it all worked out, so was my girlfriend. Fingers crossed the bride and groom liked it too.. and will like their new shelves once they get back from honeymoon!

The finished product

Evaluation:

I thought it worked well, building was fast, the construction was solid enough for up to 2 adults and 2 small children to fit inside. The processing sketch performed perfectly, although the resolution wasn’t amazing – if I had a better webcam maybe next time, although there is also a constraint from the size of the laptop screen, as processing actually takes a screendump in my code, rather than a true photo (which would let you go up to 1.3Mp, if I knew how to do it). Overall I went a little over budget (the target was £100, it actually cost about £125) mostly buying extra lights, but everything except a couple of the cloths can be re-used – either somewhere in the house or re-building the photo booth for another happy occasion! I hope this is useful for anyone trying to build a booth.

Original Bill of Materials (we actually bought only some of this and some extra things not listed) –

NON
Countertop lighting,
fluorescent
£15.31
Length: 65.0 cm
Cord length: 1.5 m
Article no:: 001.436.45

IRMA
Throw
£1.01
color: light blue
Length: 170 cm
Width: 130 cm
Article no:: 000.704.89

POLARVIDE
Throw
£2.85
color: green
Length: 170 cm
Width: 130 cm
Article no:: 401.229.43

GORM
Post
£3.06
Height: 174 cm
Package quantity: 2 pack
Article no:: 000.585.24

GORM
Height extension post
£1.53
Height: 59 cm
Package quantity: 2 pack
Article no:: 700.585.06

GORM
Shelving unit
£24.50
Width: 78 cm
Depth: 55 cm
Height: 174 cm
Article no:: 000.585.19
Tagged , , , , , , , , , , , , , , , ,