Slingshot

Apparently, Angry Birds is played for 300 million minutes a day. With that amount of dedication, isn't it about time it became a real sport?!

USB Slingshot

To make Angry Birds a real sport, we need real equipment. So the idea for this hack was to build a real slingshot as a USB peripheral to play Angry Birds.

It was inspired as a demo to show off how easy it is becoming to build your own USB devices, thanks to the emergence of ultra-low-cost 32-bit microcontrollers that include USB. There is more about this industry trend and our new mbed NXP LPC11U24 board that we designed to open up these opportunities and enable very productive prototyping of USB devices:

The resulting hack means fusing electronics, microcontrollers, software and, umm..., carpentry!

The Technology

The slingshot emulates a USB mouse, so it really is a plug 'n' play. It translates the physical use of the slingshot in to appropriate mouse controls.

USB Slingshot

With a real slingshot, you tilt the slingshot and stretch sling. The idea to measure these was using:

  • An accelerometer - this can measure the tilt by tracking the gravity vector (which way is down!)
  • A rubber stretch sensor - this can be used as the sling, and measure how much it is stretched

That is the input, but to play Angry Birds, you actually click on the bird, then drag to the launch angle and strength you want, then release. This means we have to translate the real slingshot movements in to a series of different mouse movements.

For example, this means translating starting to stretch the slingshot to a mouse to click and hold, whilst the vector of the stretch determined by the angle of sling and how far it is stretched translates to mouse movements relative to where we started. More on the maths later...

Ingredients to build a USB Slingshot

The main brain is the new mbed NXP LPC11U24, as that is designed for prototyping USB devices:

mbed NXP LPC11U24

It is packaged in a DIP prototyping form-factor, uses the NXP LPC11U24 MCU based on a 32-bit ARM Cortex-M0 core, and includes I2C, SPI, UART and ADC interfaces. It is supported by the mbed online development tools and developer website, mbed C/C++ SDK, and a full set of USB libraries; USB Mouse, Keyboard, HID, Serial, MIDI, MSC and Audio classes, that make reliably creating a new USB device a matter of a few lines of code!

This is great as it makes emulating the mouse nice and easy!

The rest of the ingredients are:

3-axis AccelerometerStretch SensorUSB B connectorHandcrafted Slingshot
3-axis AccelerometerStretch SensorUSB type B connectorHandcrafted Slingshot

Prototype

In order to test if the project was possible, a first prototype was made on breadboard.

Hardware

The hardware is based on the following connections to the mbed NXP LPC11U24:

  • The accelerometer is connected over SPI to the mbed
  • The stretch sensor is a resistor as part of a voltage-divider circuit, read on p15 (AnalogIn)
  • The USB Type B connector is connected to the mbed D+/D- pins, and also provides the power supply to the mbed
SchematicsFirst Prototype
SchematicsFirst prototype

Here is the pinout table:

ADXL345 Signal Namembed pin
VccVout
GndGnd
SDAp5
SDOp6
SCLp7
CSp8
Stretch sensormbed pin
One extremityVout
The otherp15
USB connectormbed pin
VccVin
GndGnd
D+D+
D-D-

Software

The software to test the feasibility is kept simple; the objective is to observe all different elements of the project working together.

#include "mbed.h"
#include "USBMouse.h"
#include "ADXL345.h"
 
USBMouse mouse;
ADXL345 acc(p5, p6, p7, p8);
AnalogIn strength(p15);
 
int main() {
    //Initialize accelerometer
    acc.setPowerControl(0x00);
    acc.setDataFormatControl(0x0B);
    acc.setDataRate(ADXL345_3200HZ);
    acc.setPowerControl(0x08);
 
    while (1) {
        int readings[3];
        acc.getOutput(readings);        // test accelerometer
        printf("acc: %i, %i, %i\r\n", (int16_t)readings[0], (int16_t)readings[1], (int16_t)readings[2]);
 
        uint16_t str = strength.read_u16();        // test stretch sensor
        printf("strength: %d\r\n", str);
       
        mouse.move(10, 10);             // test USB relative mouse

        wait(0.1);
    }
}

Import programAngryBirdsFeasibility

Test Slingshot

The experiments to see that it works:

  • move the breadboard
  • pull the stretch sensor
  • move and pull the stretch sensor at the same time
  • observe that the mouse is moving on the screen

So we have proven control! Next step is to make it in to a real slinghot!

The Real Slingshot!

Hardware

The slingshot was crafted by Chris Jarratt, from a branch found in Epping Forest, London! This is the structure in to which we embedded all the electronics.

The wiring is exactly the same as the first prototype, just that the mbed will be embedded in to the real slingshot this time.

DiagramA handcrafted slingshotWires embedded in the slingshot
diagramPiece of wood handcraftedWires inside
Final slingshotInside the slingshot with the accelerometerMbed USB slingshot
Final slingshotInside the slingshot with the accelerometerMbed USB slingshot

Finally, combining all different parts we get our mbed USB Slingshot!

Software

How does it all work ?

The first question concerns the USBMouse. The options are an absolute or relative mouse. The answer is quite simple because the mbed doesn't know the absolute position of the bird on the screen, so the natural solution is a relative mouse (like a normal mouse) - position the cursor over the mouse, then the slingshot takes over and moves relative to the starting point based on interpreting the manipulations of the slingshot.

Slingshot Angle

The angle of the slingshot is the main thing we need to calculate the direction of the vector to apply to the mouse position.

We simply use the fact that we know gravity is causing a 1G force on the accelerometer, and use that to calculate the angle of the slingshot with some simple trigonometry.

/media/uploads/simon/slingshot-angle.jpg

Mouse Movement

The mouse position is then calculated based on the vector offset calculated using the angle of the slingshot, and the stretch sensor reading.

Because we send relative movements, we actually calculate the desired position, then work out the difference from where we know we are and send that.

/media/uploads/simon/slinghot-mouse.jpg

Algorithm

The general idea for how it works for a complete firing comes in a few steps:

WAITING

  • We start by WAITING, with the cursor over the bird - regardless of how we tilt the slingshot, nothing happens
  • When we see a strong enough stretch, we consider that the start of AIMING, and click and hold the left mouse button

AIMING

  • We then continuously calculate a vector based on the angle of the slingshot, and the stretch of the sling
  • This is translated in to relative mouse movements with some more trigonometry, and the mouse is moved as appropriate
  • As we are positioning based on a vector but sending relative mouse positions, we keep a note of the accumulated movements so we can send the difference each time

FIRING

  • We enter FIRING when we see a fast reduction in the sling stretch
  • At this point we release the mouse button, then return the mouse back to the starting position, ready for the next throw!

Code

The whole code is fairly concise, so here it is in full:

Import program

00001 /* mbed USB Slingshot, 
00002  *
00003  * Copyright (c) 2010-2011 mbed.org, MIT License
00004  * 
00005  * smokrani, sford
00006  *
00007  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
00008  * and associated documentation files (the "Software"), to deal in the Software without
00009  * restriction, including without limitation the rights to use, copy, modify, merge, publish,
00010  * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
00011  * Software is furnished to do so, subject to the following conditions:
00012  * 
00013  *  The above copyright notice and this permission notice shall be included in all copies or
00014  * substantial portions of the Software.
00015  *
00016  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
00017  * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
00018  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
00019  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00020  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00021  */
00022 
00023 #include "mbed.h"
00024 #include "USBMouse.h"
00025 #include "ADXL345.h"
00026 
00027 // Physical interfaces
00028 USBMouse mouse;
00029 ADXL345 accelerometer(p5, p6, p7, p8);
00030 AnalogIn stretch_sensor(p15);
00031 BusOut leds(LED1, LED2, LED3, LED4);
00032 
00033 // Return slingshot angle in radians, up > 0 > down
00034 float get_angle() {
00035     int readings[3];
00036     accelerometer.getOutput(readings);
00037     float x = (int16_t)readings[0];
00038     float z = (int16_t)readings[2];
00039     return atan(z / x);    
00040 }
00041 
00042 // Return normalised stretch value based on bounds of all readings seen
00043 float get_stretch() {
00044     static float min_strength = 0.7;
00045     static float max_strength = 0.7;
00046     float current_strength = stretch_sensor.read();
00047     if(current_strength > max_strength) { max_strength = current_strength; }
00048     if(current_strength < min_strength) { min_strength = current_strength; }
00049     float stretch = (current_strength - min_strength) / (max_strength - min_strength);
00050     return 1.0 - stretch;
00051 }
00052 
00053 // move mouse to a location relative to the start point, stepping as needed
00054 void move_mouse(int x, int y) {
00055     const int STEP = 10;
00056     static int current_x = 0;
00057     static int current_y = 0;
00058     
00059     int move_x = x - current_x;
00060     int move_y = y - current_y; 
00061 
00062     // Move the mouse, in steps of max step size to ensure it is picked up by OS
00063     while(move_x > STEP) { mouse.move(STEP, 0); move_x -= STEP; }
00064     while(move_x < -STEP) { mouse.move(-STEP, 0); move_x += STEP; }
00065     while(move_y > STEP) { mouse.move(0, STEP); move_y -= STEP; }
00066     while(move_y < -STEP) { mouse.move(0, -STEP); move_y += STEP; }
00067     mouse.move(move_x, move_y);
00068     
00069     current_x = x;
00070     current_y = y;
00071 }
00072 
00073 template <class T>
00074 T filter(T* array, int len, T value) {
00075     T mean = 0.0;
00076     for(int i = 0; i<len - 1; i++) {
00077         mean += array[i + 1];
00078         array[i] = array[i + 1];
00079     }
00080     mean += value;
00081     array[len - 1] = value;
00082     return mean / (T)len;
00083 }
00084 
00085 typedef enum {
00086     WAITING = 2,
00087     AIMING = 4,
00088     FIRING = 8
00089 } state_t;
00090 
00091 int main() {
00092     leds = 1;
00093 
00094     // setup accelerometer
00095     accelerometer.setPowerControl(0x00);
00096     accelerometer.setDataFormatControl(0x0B);
00097     accelerometer.setDataRate(ADXL345_3200HZ);
00098     accelerometer.setPowerControl(0x08);
00099 
00100     state_t state = WAITING;    
00101     Timer timer;
00102 
00103     float angles[8] = {0};
00104     float stretches[8] = {0};
00105     
00106     while(1) {        
00107 
00108         // get the slingshot parameters
00109         float this_stretch = get_stretch();
00110         float this_angle = get_angle();
00111 
00112         // apply some filtering
00113         float stretch = filter(stretches, 8, this_stretch);
00114         float angle = filter(angles, 8, this_angle);
00115             
00116         leds = state;
00117                 
00118         // act based on the current state
00119         switch (state) {
00120             case WAITING:
00121                 if(stretch > 0.5) {             // significant stretch, considered starting 
00122                     mouse.press(MOUSE_LEFT);
00123                     state = AIMING;
00124                 }
00125                 break;
00126 
00127             case AIMING:
00128                 if(stretch - this_stretch > 0.1) { // rapid de-stretch, considered a fire
00129                     mouse.release(MOUSE_LEFT);
00130                     move_mouse(0, 0);
00131                     timer.start();
00132                     state = FIRING;
00133                 } else {
00134                     int x = 0.0 - cos(angle) * stretch * 200;
00135                     int y = sin(angle) * stretch * 200;
00136                     move_mouse(x, y);
00137                 }
00138                 break;
00139 
00140             case FIRING:
00141                 if(timer > 3.0) {
00142                     timer.stop();
00143                     timer.reset();
00144                     state = WAITING;
00145                 }        
00146                 break;
00147         };
00148         
00149         wait(0.01);
00150     }
00151 }

Mouse Setup

To use it, simply position the mouse cursor over the bird, and then start using the slingshot!

The cursor is not coming back at the initial position

On Windows (and probably on Linux or Mac OS), the cursor is not coming back exactly at the same initial position after a firing. To solve this issue, I modified this:

  • go into the Control Panel
  • Hardware and Sound
  • Mouse in the devices and printers section
  • in the pointer options tab, deselect Enhance pointer precision
  • You can also reduce the cursor speed

In Action

Turns out it works! Here it is in action!

USB Slingshot

So now you can kill pigs with a real USB Slingshot, by combining the two totally different worlds of carpentry and embedded systems!

Hopefully this gives you all the instructions you need to build your own, or any other USB devices you need to design!

Thanks

Thanks to everyone who helped us to make this possible!

Making the Video

Here is a little behind the scenes on how we put together the video one Wednesday afternoon. Not really something we'd done before, so a lot of making it up as we went along :)

Prototype your own USB devices with the new mbed NXP LPC11U24!

The LPC11U24 is one of the new ultra-low-cost 32-bit ARM microcontrollers entering the market that open up all sorts of opportunities for building USB devices. The times of requiring in-depth knowledge of the USB protocol, fiddling with complex software stacks and fighting 8 or 16-bit MCUs is over. Or being constrained by USB bridge chips. These chips only cost $1-2 in volume!

Using the mbed tools and the set of USB libraries, prototyping these devices should be really fast too!

For a look at all the USB Device types that work out-of-the-box, take a look at:

  • /handbook/USBMouse - Emulate a USB Mouse with absolute or relative positioning
  • /handbook/USBKeyboard - Emulate a USB Keyboard, sending normal and media control keys
  • /handbook/USBMouseKeyboard - Emulate a USB Keyboard and a USB mouse with absolute or relative positionning
  • /handbook/USBHID - Communicate over a raw USBHID interface, great for driverless communication with a custom PC program
  • /handbook/USBMIDI - Send and recieve MIDI messages to control and be controlled by PC music sequencers etc
  • /handbook/USBSerial - Create a virtual serial port over the USB port. Great to easily communicate with a computer.
  • /handbook/USBAudio - Create a USBAudio device able to receive audio stream from a computer over USB.
  • /handbook/USBMSD - Generic class which implements the Mass Storage Device protocol in order to access all kinds of block storage chips

These pages should show how easy it is to prototype low-cost USB devices, so you can concentrate on inventing the applications. Looking forward to seeing what devices you develop!

See also:

The demo was inspired by the emergence of ultra-low-cost 32-bit microcontrollers that include USB, and we wanted to show off how easy it can be to prototype them to take advantage of this trend. For more about this and why we built the new mbed NXP LPC11U24 board, take a look at:


All wikipages