Wednesday, November 24, 2010

Persistence of Vision

I descided to make a new puzzle cache based around POV. The basic prinicple being that in the human eye an 'after image' persists for approximately one twenty-fifth of a second on the retina. The small device I outline here will display the final coordinates of a puzzle cache when it is swipped through the air at speed.
As usual the design is prototyped using an Arduino and then built from scratch using the raw components.

Basic POV with 7 LED array
An Atmega chip, 7 Leds and a 16Mhz crystal. I didnt bother with resisters to protect the Leds as at the 3.5V they should be fine, and I even excluded the recommended capacetors for the VIN and the crystal. Here is the minimal layout;
The device was powered by 3xAAA bateries, again, to keep the unit as small and simple as possible.
Here is a partial image from a swipe, displaying coordinates;

Lights in the sky

Wednesday, September 29, 2010

Gemini Puzzle Caches

I came up with an idea for a pair of identical puzzle caches. At the published coordinates there will be a box with a single button and an LCD display. While the button is pressed the LCD will display the location of the cache. Sound easy? Like that yes, but there is a catch. Both boxes must have their buttons pressed at exacly the same time. Only then will the final location be shown. If only one box is activated then an error message is shown. So to solve these caches different people must go to the given coordinates at the same time and work together.

Castor and Pollux. The names of my two boxes

I was planning on basing the initial design around the arduino and an XBee module. In a cost cutting exercise I decided to start out smaller and use a Transceiver nRF24L01+ Module instead.

So the main components are;
Arduino Main Board
Transceiver nRF24L01+ Module with Chip Antenna
Basic 16x2 Character LCD - Red on Black 3.3V
Trimpot 10K with Knob
Wires + Headers
Custom Perspex box.

Nearly forgot to mention, 2 of everything, 2 boxes for this geocache game.

Wiring of the LCD and the tranceiver was very simple. All ports mappings are specified in the script.

I added a arcade machine momentary push button so that the unit would only be powered when the button was pressed. Mainly to prevent it being accidentally left turned on, but also because I thought the buttons looked great.
And finally I descided to rip the Atmega out of the Arduino and use that directly. Another cost saving, but also allows for a smaller box size and better power usage.

Basic layout
Providing connections for Reset, Tx and Rx allow for connecting the Atmega to its chipless Arduino board. This allows the Atmega to be reprogrammed while its in the final device.

Final box showing greeting message
Error message shown when other box not activated

Both perspex boxes cost 10 euros to have made in a shop downtown Athens. To do this I made a prototype box from card to make sure everything would fit and then took that to the shop and they simply copied it.
Protoype case
The main downside of using the nRF24L01+ is range. At clear line of sight a maximum distance of about 80m was possible. This wasnt that much of a problem in that there are plenty of places I can place the devices on my local mountain. Locations where it may be 80m direct between the two points but to walk it required over 1km to avoid a river/cliff/etc. The main problem is that 80m is half the minimum seperation required for publishing new caches. So doing this as 2 puzzle caches would never get published on geocaching.com. To get arround this I simple rebranded it as a single puzzle cache with multiple stages that had to be solved simultaniously.

Here is the script. This script was uploaded to both devices, with only changing the value of 'deviceNumber' from 1 to 2.

 #include <Spi.h>  
 #include <mirf.h>  
 #include <LiquidCrystal.h>  
 #include <string.h>  
 // Pins  
 #define pinUnused 0  
 // LCD  
 #define pinD4 14 // analog 0  
 #define pinD5 15 // analog 1  
 #define pinD6 16 // analog 2  
 #define pinD7 17 // analog 3  
 #define pinEnable 18 // analog 4  
 #define pinRS 19 // analog 5  
 // nRF24L01+  
 #define pinCSN 9  
 #define pinCE 10  
 #define pinMOSI 11  
 #define pinMISO 12  
 #define pinSCK 13  
 LiquidCrystal lcd( pinRS, pinEnable, pinD4, pinD5, pinD6, pinD7 );  
 #define deviceNumber 1  
 #define device1Name  "geminiOne"  
 #define device1Final  " N 38 xx.xxx' "  
 #define device1Alone  "Awaiting Castor."  
 #define device2Name  "geminiTwo"  
 #define device2Final  " E 23 xx.xxx' "  
 #define device2Alone  "Awaiting Pollux."  
 #define RF_DR_LOW   5  
 #define RF_PWR_LOW  1  
 #define RF_PWR_HIGH  2  
 #define cacheMessage   "Gemini Geocache "  
 #define copyrightMessage "  (C)R10N   "  
 #define successMessage  "Final Position: "  
 void setup( void )  
 {  
  Serial.begin(38400);  
  lcd.begin( 16, 2 );  
  lcd.clear();  
  lcd.noAutoscroll();  
  Mirf.csnPin = pinCSN;  
  Mirf.cePin = pinCE;  
  Mirf.init();  
  Mirf.setRADDR( ( deviceNumber == 1 ) ? (byte*)device1Name : (byte*)device2Name );  
  Mirf.setTADDR( ( deviceNumber == 1 ) ? (byte*)device2Name : (byte*)device1Name );  
  Mirf.payload = sizeof( unsigned long );  
  uint8_t settings = ( 1 << RF_DR_LOW ) | ( 1 << RF_PWR_LOW ) | ( 1 << RF_PWR_HIGH );   
  Mirf.configRegister( RF_SETUP, settings );  
  Mirf.config();   
  randomSeed( analogRead( pinUnused ) );  
  displayMessage( cacheMessage, copyrightMessage );  
  delay( 2000 );  
 }  
 void displayMessage( const char* line1, const char* line2 )  
 {  
  lcd.setCursor( 0, 0 );  
  lcd.print( line1 );  
  lcd.setCursor( 0, 1 );  
  lcd.print( line2 );  
 }  
 void sendData( void )  
 {   
  unsigned long now = millis();  
  Mirf.send( (byte*)&now );  
  while( Mirf.isSending() )  
   delay( random( 10 ) );  
 }  
 bool readData( void )  
 {  
  bool dataFound = false;  
  while( Mirf.dataReady() )  
  {  
   byte data[Mirf.payload];  
   Mirf.getData(data);  
   dataFound = true;  
  }  
  return dataFound;  
 }  
 void loop( void )  
 {  
  sendData();  
  if( readData() == true )  
  {  
   displayMessage( successMessage, ( deviceNumber == 1 ) ? device1Final : device2Final );  
   unsigned long now = millis();  
   while( millis() <= (now + 1000) )  
    sendData();  
  }  
  else  
  {  
   displayMessage( cacheMessage, ( deviceNumber == 1 ) ? device1Alone : device2Alone );  
  }  
 }  

As I mentioned before, XBee was my original thought for this. Depending on the XBee module a range of 1.6km is possible. Much more for the high end versions, but the price is large and a hefty antenna is needed. 1.6km would allow for the initial plan of it being 2 seperate puzzle caches.
For my next project, instead of using XBee, I've decided to take this further and go international. Using the internet as the communication channel. More to come on that later.

Friday, August 27, 2010

GPS Puzzle Box

Last year the Reverse Geocache Puzzle was created by Mikal Hart. The general concept is that of a box that is locked and will only open when taken to a specific location. All of the implementations I have seen to date are based around using an LCD to show the game player how far they are from the final location. I thought I would try and come up with something a little more cryptic.

I've always been a fan of the 'Myst' series of games. There you are dropped in an environment where you have no idea of whats to be done and how things work. So with this in mind I decided to make a locking puzzle box with a similar amount of instruction for the player. None.

So instead of a LCD displaying distance to the final location I went for an analog needle without a scale;

Analog Output
(showing about 120 degrees)
Turning the box on resets the needle to zero and once a GPS signal has been established the needle is positioned. The analog nature of this made it more of a challenge to solve. For an initial version I made it relatively simple by setting the scale to be 1 degree equal to 1km. Setting the maximum range to 180km.

There are plenty of resources online about how to construct such a device. Mine was a Arduino Duemilanove, GPS shield and EM-406 GPS receiver, 2 servos (1 for the lock and 1 for the needle) and a simple switch. All powered by a 9V battery.

So I gave the box, obviously locked, to my wife Myrto with no instruction on how it worked. All she was told was that it was a GPS puzzle and that I have a love of Geocaching. I also told her not to leave it turned on when it wasn't in use to save battery. (I decided against a polulu switch as I wanted the industrial feel of a toggle switch)

A few hours driving around Athens and she came home with a set of readings from different locations;

Armed with this info and Google Earth it took her a good few hours to finally put it all together and work out that the readings were distance. (Setting the scale of 1 degree to 1km definitely made it easier). Using a KML circle generator (there are a few online, cant remember which one she used though) she loaded the data into Google Earth;


And zooming into the intersection;



Given the analog nature of the needle and the distance from the final location it wasn't possible to get an exact destination. Even without any errors due to parallax reading the needle the level of accuracy was only to within 1km. But even so, the possible band of destinations contained the mountain Parnasos and Delphi. Not enough to be sure, but enough to give it a go on a spare day as it was about 180km drive.

So off we went, I was designated driver, and was keeping shtum about the final destination. Myrto took a couple of reading as we went along to confirm that her theory about the game was correct and as the needle moved less and less all was well. For the final 5 km we just left the device turned on and watched as the needle slowly dropped down to zero about 500m before we entered Delphi. The servo whined and the box opened releasing the gift within. But the main gift was the challenge and the visit to the Temple of Apollo which followed;


Since then I've made a few more puzzles like this, which I'll post soon, but this was by far the most fun to build, plan and solve.

Woodpecker Puzzle Cache

I've just published my Pendeli Woodpecker puzzle cache on geocaching.com. The main idea of this cache is that you go to a specific location and at a predesignated time a device makes the sound of a woodpecker drumming on a log. The cache is located at the source of the drumming. Far enough away from the initial coordinates that simply going to the puzzle coordinates and having a manual hunt is unlikely to find the cache.
The idea for this puzzle came from the fantastic wind-up birds project by HC Gilje.

The basic components required for this project are;
  1. Atmega 328
  2. 100N capacitor
  3. 16MHz crystal
  4. Solenoid
  5. 1K resistor * 2
  6. TIP120 transistor * 2
  7. 1N4004 diode
  8. 5V Voltage Regulator (LM78S05)
  9. Real Time Clock Module
  10. Water Proof Box
The Woodpecker
The general principle is that the Atmega polls the real time clock until 10am either Saturday or Sunday. Once reached, for a 2 minute period the solenoid is powered to drum against a piece of wood.


The Woodpecker (Naked)
The solenoid is powered by 2x9V batteries and the Atmega is powered from 3 * 4.5V batteries. The RTC is powered from the same source as the solenoid but with a voltage regulator, LM78S05, bringing that down to the required 5V. This is my first project trying to use a 5V module controlled directly from an Atmega chip. Its been an interesting experience. getting everything working together and able to run uninterupted for a long time. To preserve battery power I've used full power saving features of the Atmega so everything is switched into a low power sleep state for most of the time, only checking the time once every 9 seconds.

The main circuit layout is something like (use at own risk);


I used an Arduino to program the Atmega and then extracted the chip. Not the most elegant solution but the simplest with the hardware I had to hand. Very easy to break off connections doing this though so beware. Here is the script. I'm not big on comments, sorry. The code to calculate Summer time is for Athens Greece, switch over points are the last Sundays in March and October.

 // RP  © 2010
 #include <avr/sleep.h>
 #include <avr/wdt.h>
 #include <Wire.h>
 #ifndef cbi
 #define cbi( sfr, bit ) ( _SFR_BYTE( sfr ) &= ~_BV( bit ) )
 #endif
 #ifndef sbi
 #define sbi( sfr, bit ) ( _SFR_BYTE( sfr ) |= _BV( bit ) )
 #endif
 #define DS1307_I2C_ADDRESS 0x68
 int solenoidPin = 7; // digital 7
 int SDAPin = 14 + 4; // analog 4
 int SCLPin = 14 + 5; // analog 5
 int RTCVCCPin = 14 + 3; // analog 3
 byte decToBcd( byte val )
 {
  return ( ( val / 10 * 16 ) + ( val % 10 ) );
 }
 byte bcdToDec( byte val )
 {
  return ( ( val / 16 * 10 ) + ( val % 16 ) );
 }
 volatile boolean watchdogFired = true;
 void setup()
 {
  cbi( SMCR,SE );
  cbi( SMCR,SM0 );
  sbi( SMCR,SM1 );
  cbi( SMCR,SM2 ); 
  randomSeed( analogRead( 0 ) );
  Serial.begin( 38400 );
  Wire.begin();
  beatOutHour();
 }
 boolean summerTime( byte month, byte dayOfWeek, byte dayOfMonth )
 {
  if (month < 3 || month > 10)
   return false;
  if (month == 3)
  {
   int lastSunday = (dayOfWeek == 7) ? dayOfMonth : (dayOfMonth - dayOfWeek);
   return ((31 - lastSunday) < 7);
  }
  else if (month == 10)
  {
   int lastSunday = (dayOfWeek == 7) ? dayOfMonth : (dayOfMonth - dayOfWeek);
   return ((31 - lastSunday) >= 7);
  }
  else
   return true;
 }
 void now( byte& second, byte& minute, byte& hour, byte& dayOfWeek )
 {
  pinMode( RTCVCCPin, OUTPUT );
  digitalWrite( RTCVCCPin, HIGH );
  Wire.beginTransmission( DS1307_I2C_ADDRESS );
  Wire.send( 0x00 );
  Wire.endTransmission();
  Wire.requestFrom( DS1307_I2C_ADDRESS, 7 );
  second = bcdToDec( Wire.receive() & 0x7f );
  minute = bcdToDec( Wire.receive() );
  hour = bcdToDec( Wire.receive() & 0x3f );
  dayOfWeek = bcdToDec( Wire.receive() );
  byte dayOfMonth = bcdToDec( Wire.receive() );
  byte month = bcdToDec( Wire.receive() );
  byte year = bcdToDec( Wire.receive() );
  digitalWrite( RTCVCCPin, LOW );
  pinMode( RTCVCCPin, INPUT );
  if( summerTime( month, dayOfWeek, dayOfMonth ) )
   hour++;
  Serial.print( "time: " );
  Serial.print( hour, DEC );
  Serial.print( ":" );
  Serial.print( minute, DEC );
  Serial.print( ":" );
  Serial.print( second, DEC );
  Serial.print( ", day of week:" );
  Serial.println( dayOfWeek, DEC );
  delay( 2 );
 }
 void beatOutHour()
 { 
  byte second, minute, hour, dayOfWeek;
  now( second, minute, hour, dayOfWeek );
  pinMode( solenoidPin, OUTPUT );
  for( int beat = 0; beat < hour; beat++ )
  {
   digitalWrite(solenoidPin, HIGH);
   delay( 100 );
   digitalWrite(solenoidPin, LOW);
   delay( 200 );
  }
  pinMode( solenoidPin, INPUT );
 }
 void loop()
 {
  if( watchdogFired == true )
  {
   watchdogFired = false;
   Serial.println( "loop" );
   delay( 2 );
   byte second, minute, hour, dayOfWeek;
   now( second, minute, hour, dayOfWeek );
   if( hour == 10 && minute < 2 && (dayOfWeek == 6 || dayOfWeek == 7) )
   {
    Drum();
    system_sleep( random(4) + 6 );
   }
   else
   {
    delay( 2 );
    for( int seconds = ( 60 - second ); seconds > 0; seconds -= 8 )
     system_sleep(9);
   }
  }
 }
 void Drum()
 {
  int beats = 7 + random( 9 );
  int cadence = 12 + random( 7 );
  int beatSpace = ( 1000 / cadence );
  Serial.print( "beats: " ); 
  Serial.print( beats, DEC );
  Serial.print( ", cadence: " ); 
  Serial.println( cadence, DEC );
  delay( 2 );
  pinMode( solenoidPin, OUTPUT );
  for( int beat = 0; beat < beats; beat++ )
  {
   digitalWrite(solenoidPin, HIGH);
   delay( beatSpace / 2 );
   digitalWrite(solenoidPin, LOW);
   delay( beatSpace / 2 );
  }
  pinMode( solenoidPin, INPUT );
 }
 void system_sleep(int ii)
 {
  Serial.print( "sleep: " ); 
  Serial.println( ii, DEC );
  delay( 2 );
  setup_watchdog(ii);
  cbi( ADCSRA, ADEN );
  set_sleep_mode( SLEEP_MODE_PWR_DOWN );
  sleep_enable();
  sleep_mode();
  sleep_disable();
  sbi( ADCSRA, ADEN );
 }
 // 6 = 1 sec, 7 = 2 sec, 8 = 4 sec, 9 = 8 sec
 void setup_watchdog(int ii)
 {
  byte bb;
  int ww;
  if( ii > 9 )
   ii = 9;
  bb = ii & 7;
  if( ii > 7 )
   bb |= ( 1 << 5 );
  bb |= ( 1 << WDCE );
  ww = bb;
  MCUSR &= ~( 1 << WDRF );
  WDTCSR |= ( 1 << WDCE ) | ( 1 << WDE );
  WDTCSR = bb;
  WDTCSR |= _BV( WDIE );
 }
 ISR( WDT_vect )
 {
  watchdogFired = true;
 }  



A fun and simple way to add a new dimension to your puzzle caches.

Ive two more variants on this currently in the pipeline.
1) Instead of powering a solenoid a stepper motor turns a hand cranked music box mechanism. I don't know, the idea of walking in the mountains around Athens and suddenly hearing a music box playing in the middle of nowhere seems magical to me.
2) Using a audio player to have it appear that someone is shouting "hey, its over here"

Let me know what you think. I would love to hear of anyone else taking puzzle caches to a new level.