The anti-robot: Shannon's Ultimate Machine

Note: this is an earlier version of my design; click here for a second attempt.

The original Ultimate Machine is famously attributed to Claude Elwood Shannon, one of the fathers of modern computing. In "Voice Across the Sea", Arthur C. Clarke describes Shannon's device the following way:

"Nothing could be simpler. It is merely a small wooden casket, the size and shape of a cigar box, with a single switch on one face. When you throw the switch, there is an angry, purposeful buzzing. The lid slowly rises, and from beneath it emerges a hand. The hand reaches down, turns the switch off and retreats into the box. With the finality of a closing coffin, the lid snaps shut, the buzzing ceases and peace reigns once more. The psychological effect, if you do not know what to expect, is devastating. There is something unspeakably sinister about a machine that does nothing - absolutely nothing - except switch itself off."

A video of the original design can be found here.

Through the years, many hobbyists attempted to build their own variants, ranging from the literal to the outright gruesome; it only seemed fitting to also give it a try. In essence, I'm hoping to make the machine seem somewhat ominous and otherworldly, and respond with a motion more complex and more measured than typically attempted elsewhere.

1. Parts inventory

The centerpiece of my design is a cheap but ornate keepsake box (12.5 x 15.5 x 7.5 cm) purchased for $13 on Amazon - exactly the sort of a container to guard a timeless electromechanical secret.

The remaining electronic components are:

Most of these components were selected for their aesthetics or immediate availability alone, and can be substituted freely.

On top of this, I made a couple other mechanical components by machining the geometry on Roland MDX-540 using RenShape 460, making rubber molds with ShinEtsu KE-1310ST, and then casting the final part in IPI IE-3075; if you are curious, click here for a detailed discussion of this process.

2. Circuitry and software

The circuit is based on a very straightforward use of ATmega48P - and rest assured, almost any other microcontroller will do just as well. The MCU has a single input - a two-position toggle switch on the outside of the box - and several outputs: The entire circuit looks as follows:

I opted to power this with a 5V, 2A DC adapter. Strictly speaking, 1A could be sufficient (and costs 50% less): when operated at 5V, each of the motors has a stall current of about 0.6A, the LEDs need around 80 mA, and the microcontroller itself sinks under 5 mA - so as long as the motors are not operated simultaneously, there would be a reasonable reserve maintained.

In any case - moving on to the software part. The basic algorithm for the MCU is about as simple as the circuit itself:

  1. Retract motor 2 (claw actuator) for a predefined time needed to hit the integral limiter,
  2. Retract motor 1 (lid actuator) for a predefined time needed to hit the integral limiter,
  3. Wait for the switch to be toggled,
  4. Turn on LEDs,
  5. Extend motor 1 (lid actuator) for a predefined time needed to lift the lid,
  6. Extend motor 2 (claw actuator) until switch is toggled, or time limit exceeded,
  7. Turn off LEDs,
  8. If previous step successful, go to 1, otherwise stop.
Throw in some PWM magic to make the LEDs flicker subtly, and operate motor 2 at variable speed (which, in retrospect, could be done more easily by building a ghetto resistor ladder DAC connected to the Vref pin on FAN8082, since there are quite a few unused outputs anyway) - and the actual C code ends up looking like this: #define F_CPU 1000000UL #include <avr/io.h> #include <avr/sleep.h> #include <avr/interrupt.h> #include <util/delay.h> /************************** * User-friendly typedefs * **************************/ typedef int8_t s8; typedef uint8_t u8; typedef int16_t s16; typedef uint16_t u16; typedef int32_t s32; typedef uint32_t u32; /****************** * PIN assignment * ******************/ #define B_SWITCH 0 /* External switch input pin */ #define C_CLAW_OUT 0 /* Claw motor: polarity 1 */ #define C_CLAW_IN 1 /* Claw motor: polarity 2 */ #define C_LID_UP 2 /* Lid motor: polarity 1 */ #define C_LID_DOWN 3 /* Lid motor: polarity 2 */ #define D_LED0 0 /* LED 0 enable */ #define D_LED1 1 /* LED 1 enable */ #define D_LED2 2 /* LED 2 enable */ #define D_LED3 3 /* LED 3 enable */ /*************************** * Configuration constants * ***************************/ #define DELAY_START 500 /* Delay before initial response */ #define DELAY_CONT 250 /* Delay in between actions */ #define BRAKE_TIME 10 /* Braking time */ #define CLAW_EXT_SLOW 500 /* Time to extend claw slowly */ #define CLAW_EXT_FAST 500 /* Time to move claw at 100% */ #define CLAW_RET_SLOW 600 /* Time to retract claw slowly */ #define CLAW_RET_FAST 300 /* Time to retract claw at 100% */ #define CLAW_PWM_ON 8 /* Claw PWM on duration */ #define CLAW_PWM_OFF 15 /* Claw PWM off duration */ #define LID_OPEN 650 /* Time needed to open lid */ #define LID_RETRACT 1000 /* Time to retract lid */ /**************** * LED routines * ****************/ u16 led_cnt; u8 led_nth[] = { 1, 1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 7, 8, 8, 8, 8, 7, 7, 6, 5, 4, 3, 2, 2 }; void delay_led(u16 ms) { while (ms--) { u8 div = led_nth[((led_cnt++) / 15) % sizeof(led_nth)]; if (!(led_cnt % div)) PORTD = _BV(D_LED0) | _BV(D_LED1) | _BV(D_LED2) | _BV(D_LED3); else PORTD = 0; _delay_us(380); /* The cost of the remainder of this loop is about 620 us */ } } void led_off(void) { PORTD = 0; } /**************** * Lid routines * ****************/ void close_lid(void) { PORTC = _BV(C_LID_DOWN); delay_led(LID_RETRACT); PORTC = 0; delay_led(BRAKE_TIME); } void open_lid(void) { PORTC = _BV(C_LID_UP); delay_led(LID_OPEN); PORTC = 0; delay_led(BRAKE_TIME); } /*********************** * Claw routines w/PWM * ***********************/ void retract_claw(void) { u16 cnt; for (cnt = 0; cnt < CLAW_RET_SLOW / (CLAW_PWM_ON + CLAW_PWM_OFF); cnt++) { PORTC = _BV(C_CLAW_IN); delay_led(CLAW_PWM_ON); PORTC = 0; delay_led(CLAW_PWM_OFF); } PORTC = _BV(C_CLAW_IN); delay_led(CLAW_RET_FAST); PORTC = 0; delay_led(BRAKE_TIME); } void extend_claw(void) { u16 cnt; /* First move slowly... */ for (cnt = 0; cnt < CLAW_EXT_SLOW / (CLAW_PWM_ON + CLAW_PWM_OFF); cnt++) { PORTC = _BV(C_CLAW_OUT); delay_led(CLAW_PWM_ON); PORTC = 0; delay_led(CLAW_PWM_OFF); } /* Then with full torque to actually toggle the button. */ PORTC = _BV(C_CLAW_OUT); delay_led(CLAW_EXT_FAST); PORTC = 0; delay_led(BRAKE_TIME); /* Failed to toggle? Disable LEDs, interrupts, die. */ if (!(PINB & _BV(B_SWITCH))) { PORTD = 0; PCICR = 0; while (1) sleep_cpu(); } } /*************** * Entry point * ***************/ int main() { CLKPR = _BV(CLKPCE); /* Enable clock prescaler change */ CLKPR = 0b00000011; /* Clock prescaler: 8 (1 MHz) */ DDRB = 0b00000000; /* All B pins are for input */ PORTB = 0b11111111; /* Enable internal pull-up */ DDRC = 0b11111111; /* All C pins are for output */ DDRD = 0b11111111; /* All D pins are for output */ PCMSK0 = _BV(PCINT0); /* Interrupt on pin 14 (PB0) */ PCICR = _BV(PCIE0); /* Enable interrupt 0 */ /* Initialize to a known state. */ retract_claw(); close_lid(); led_off(); /* Go to sleep, waiting for interrupt. */ sei(); set_sleep_mode(SLEEP_MODE_IDLE); sleep_enable(); while (1) sleep_cpu(); } /**************************** * Switch interrupt handler * ****************************/ ISR(PCINT0_vect, ISR_BLOCK) { /* Allow switch bounce... */ _delay_ms(50); /* We only care about falling edge events that lasted at least 50 ms. */ if (PINB & _BV(B_SWITCH)) return; _delay_ms(DELAY_START); open_lid(); delay_led(DELAY_CONT); /* Switch toggled back? Retreat. */ if (PINB & _BV(B_SWITCH)) goto close_lid; extend_claw(); retract_claw(); close_lid: delay_led(DELAY_CONT); close_lid(); led_off(); }

3. Mechanical design

Since the gear motors used in the project deliver around 20-50 RPM (depending on FAN8082 voltage setting) and a torque up to about 3,000 g/cm², the lid is actuated with a very simple assembly - essentially a cam - attached directly to the output shaft.

The claw that extends from the box to toggle the switch is actuated in a similar manner; I originally wanted to use a three-bar linkage for more sophisticated motion, but in the end decided that simple rotation is about as good:

The only two remaining bits of mechanical work involved drilling carefully positioned holes for the toggle switch and the DC power jack in the rear.

4. Final result

The finished box looks like this:

You can see a crappy video of the box here.

5. Questions? Comments?

You can reach me at lcamtuf@coredump.cx.

Your lucky number: 17821933