Wiimote Crane

Andrew Courtney(apc27)

Wan Ling Yih(wy47)


Table of Contents

I. Introduction

II. High level design

III. Program/Hardware Design

IV. Results

V. Conclusion

VI. Appendixes

          A. ProgramCode

          B. Wii code

C. References

D. Schematics

E. Budget


I. Introduction

We used the Wiimote’s IR tracking capability and Bluetooth to wirelessly control a robotic crane arm.  The Wiimote is a powerful gadget and we wanted to build a new hack with it.  Our crane is composed of three servo motors, one of which is connected to a gripper.  The servos are controlled by PWM signals sent by the Mega32 MCU, and these signals are generated from parsing the received IR data from a serial connection with a Macbook wirelessly connected through Bluetooth to the Wiimote.


II. High level design


After watching some of Johnny Chung Lee’s videos on Wiimote hacks(http://www.cs.cmu.edu/~johnny/projects/wii/), especially the infrared tracking video, we were inspired to create our own Wiimote application.  Our first idea was to track points using two Wiimotes in 3-d, but the difficulty of getting just one Wiimote to work scratched that idea.  We decided to use the remote to track infrared points and use this data to control a crane composed of three servos with a gripper. 

Background Math

There wasn’t any extremely challenging math involved in our project.  We needed to understand how the Wii’s IR camera worked and also needed to be good at PWM signals for the servo motors.  We also needed some basic circuit design skills for building the LED array.  The explanations for these concepts come up in the later sections of this report.

Logical Structure 

Figure 1: Project Diagram


Hardware/Software tradeoffs

We needed to generate three PWM signals to control the three servo motors.  Only two timers on the Mega32 have PWM capabilities (Timer0/Timer2), so we had to manually generate a PWM signal, which sacrificed some accuracy on our timing.  Fortunately the servo doesn’t have to be in a precise position for the crane to work, it only has to be roughly controllable.

Relation to Standards and Intellectual Property

Our project uses the Bluetooth wireless protocol, which is a built-in feature of the Macbook as well as the Wiimote.   The Wiimote was designed by Nintendo, though I haven’t found a patent for the Wiimote itself.  We do not intend to commercialize our design so this should not be an issue.


III. Program/hardware design


Timers and PWM

We use three timers to set up the PWM signals used in this project.  For timer0 and timer2 we run them on full PWM mode with the prescalar set so they run at clk/1024 = 15.625 KHz.  Since they are 8-bit timers they overflow with a period of roughly 16ms.  We use this period as one PWM cycle and set OCR0 (or OCR2) to vary the width of the pulse.  Timer 1 is set to clear on match mode running at 5KHz.  In the timer1 compare match interrupt we set up a custom PWM signal to control the gripper servo.  The hardest part of setting up the PWM signals was configuring the servos to be in the correct position and varying the pulses to the right degree to move the servo arm where we wanted it.

Parsing/Porting Wiimote Data

The Wiimote has two types of sensors: accelerometers and an infrared camera.  With these two devices, the players communicate with the main console via Bluetooth. In our project we use the infrared camera and its ability to track an infrared source’s x-y coordinates within the Wiimote’s plane of view.  A Wiimote view is 41° in x direction and 31° in y direction.

Communicating between the Wiimote and the MCU is established through the USB serial RS232 input using the serial UART receive/transmit feature on the Mega32. Here we only use the receive portion on the microcontroller to receive the Wiimote’s IR data from a MacBook.  We use the MacBook to port the Wiimote’s IR data since MacBook has Bluetooth functionality built in, and by downloading a Wiimote driver on the MacBook  (open source driver DarwiinRemote), we extract the IR data from the Wiimote and send it through the USB-to-serial connection

Within the DarwiinRemote application, the package includes  a WiiRemote.framework, which is the binary for the WiiRemote library on the Macintosh. After downloading the application and the WiiRemoteFramework library source code from Source forge, with the XCode IDE we are able to locate where IR data and button status data are in the source code, open a serial port by calling the open serial port C code acquired from the Apple Developer website, and write data to the USB to serial connection located on the dev port of the MacBook hardware.  Within the DarwiinRemote application, the x-coordinate IR data ranges from 0 to 1023 and y-coordinate IR data ranges from 0 to 767. If the WiiRemote sees no IR source, then the x and y IR data values are both 1023. These values are important for servo calibration control. The serial port code serialPort.c includes various functions, but the main functions we used were serialPort (int x, int y), OpenSerial (bsdPath) (internally called by serialPort), and InitializeModem (int x, int y, int pressed). When opening a serial port, the user can specify if the port is open for transmit or receive, blocking or nonblocking, and set for the existence of a control terminal. Here we set up the port as write only (since we are only sending data to MCU), nonblocking, and no controlling terminal.  The serial port C code lets the user set the baud rate, data packet size, number of parity bits, and number of stop bits.  We configure this to our regular hyper terminal settings at 9600 baud, 8 data bits, no parity bits, 1 stop bit, and no flow control.

For porting the x, y, and button data to the serial port, we first convert the x and y data from integer to string using sprintf  and store them in buffers. For the A button, there is a defined array called buttonState, along with a define macro WiiRemoteAButton, where WiiRemoteAButton acts as a index into the buttonState array and will be set to predefined macro value YES if button is pressed. We define a local variable integer pressed to be set high whenever buttonState[WiiRemoteAButton] equals to YES within the WiiRemoteFramework source code. We then write this value to a string buffer and send the button data along with the x and y IR data every time we communicate with the serial port.  Finally to terminate every set of data sent, we send a carriage return (“\r”) to the serial port so the MCU will know when a set of data completes transmission.

On the MCU side, we initialize the serial UART RX/TX capability in the initialize() function by setting UBRRL and UCSRB to the appropriate baud rate and enable serial USB receive. On the hardware side, we have to connect jumper RX/TX to pin D.0 and pin D.1 to enable receive. The receive interrupt will then be triggered after every incoming string character. The UDR_RXC interrupt will be called, and within the interrupt we place the data in its respective buffer array (x, y, or button data).

Using the IR data

An extremely challenging part of our project was figuring out how to translate the XY coordinates from the Wiimote to pulse widths for the servo motors.  For the x-coordinates we set up a system where every 50 pixel range of points corresponded to one pulse width and we limited the motion of the servo to a change of one “pulse width” or one value of OCR0 per call of the compare_x function.  For the y-coordinates we segmented the points into three positions, one for upwards movement, one to stay stationary, and one for downwards movement.  The gripper servo has a specific sequence of pulses that is triggered each time the A button on the Wiimote is pressed.  The gripper pulses slowly open and then close the gripper.


Custom Board

We built the custom Mega32 board exactly as described on the ECE 476 website (http://www.nbb.cornell.edu/neurobio/land/PROJECTS/Protoboard476/index.html). 

Figure 2 Custom Mega32 Board

LED Arrays

The hardware used for this project wasn’t extremely complicated, but required several parts that have to work together.  To provide infrared light that the Wiimote could track we built several IR led arrays.  In the appendix there is a schematic for an LED array.  The array has a 12V source connected in series with a 75 Ohm resistor and 8 LEDs.  Each LED is rated for a forward voltage drop of 1.2V so 8 LEDs is a total drop of 9.6V.  With a 12V source there should be a 2.4V drop over the 75 Ohm resistor, drawing 32mA of current.  The LEDs are rated for 20mA with a maximum of 50mA so this array configuration should be fine.

Figure 3 LED Array and Wiimote

Optoisolator Circuit

Since we were using noisy motors we had to make sure the ground paths for the MCU and servo were not shared.  Similar to Lab 3 we used 4N35 optoisolators to separate the two sides of the circuit.  On the MCU side of the circuit we have a 330 Ohm resistor connected to the diode on the 4N35.  On the motor side the base of the transistor is connected to ground through a 1MOhm resistor, the emitter is connected to the control line of the servo as well as to ground through a 10KOhm resistor.  The collector is connected to 5V Vcc (separate from MCU).

Mechanical Design

To build the crane we mounted the arm servo to a piece of wood for a base and nailed a another piece of wood to the horn of the servo for the rotating arm.  At the end of the arm is the continuous rotation servo that raises and lowers the gripper.  We tied the gripper hardware to the horn of this servo with some string.  The gripper as well as the gripper control servo hangs from the bottom of the string.

Figure 4 Crane Hardware


Figure 5 Gripper with Servo


Servo PWM control

A servo motor is controlled by a pulse-width modulated signal (PWM) that tells its arm which position to be in.  Generating these signals is fairly straightforward using the timers on the Mega32, though we had to ensure all three of our PWM signals were accurately timed.  Below is a basic diagram of a PWM signal.  There is a shorter pulse of voltage Vcc at the start of the pulse while the total pulse has a period of 20ms (though this has some tolerance).  For two of our servos we used a 16 ms pulse, for the other we used a 20ms pulse.  The difference was only due to the timers we used to set up the pulses.


Figure 6: PWM Signal

Modifying a servo for continuous rotation

To raise and lower the gripper we needed a servo that could rotate continuously.  The standard servos we ordered could only rotate about 270 degrees, so we had to take one apart and do some modifications.  This required learning a little bit more about how servo motors work and getting our hands dirty.  The first step was opening the servo case to expose the gear train.  One of the gears had a notch at the bottom which prevented the servo from rotating 360 degrees, so first we cut out the notch. 

The servo has a potentiometer that is part of a feedback loop with the motor to control the servo arm’s position.  The position of the potentiometer is compared to the pulse width arriving on the control line and the motor rotates the potentiometer until its resistance matches with a value derived based on the pulse width.  We had to disengage the potentiometer from the motor to break the feedback loop.  There was a washer in the base of the gear sitting over the potentiometer that we removed which allowed the gear to spin without moving the potentiometer.  Next we calibrated the servo to stop spinning with a constant width PWM by adjusting the potentiometer position.  Now that we had a reference pulse width to stop the servo, making the pulse slightly wider would make the servo spin continuously in one direction, and making the pulse narrower spins it the other way.  The potential problems with this modification are:

A)    If you break the servo somehow – you just voided any warranty you had by modifying it.

B)     You no longer know where the servo is based on the pulse width, the servo no longer has feedback to stay in one constant position.

C)    If the potentiometer slips or you want to use a different default pulse width, you need to open up the servo again and recalibrate it with a new pulse.

However for our application we needed the servo to do this and didn’t want to order a new servo so we accepted these tradeoffs.

Stuff that didn’t work

Our final code uses the fast PWM mode of timer0 and timer2 and uses timer1 to set up a manual pulse inside its compare match interrupt.  In our first version we tried to manually create pulses for all three servos.  We called functions from main that set output ports to 1 or 0 based on some software counters.  This plan worked at first until we generated all three PWM signals, at which point the code wasn’t fast enough to accurately create all the signals which caused the servos to jitter.  First we tried optimizing the code to make it faster, but finally we switched to hardware PWM control which worked much better.

We tried a number of software schemes for controlling the servos based on the coordinates being received.  The trickiest servo to control was the continuous rotation servo, because its internal potentiometer was disengaged the servo’s feedback loop doesn’t function and the servo position has to be controlled in software.  Instead of spinning the servo for different amounts of time depending on the IR data’s y-coordinates, we created three regions of y-data.  If the y-data was near the top of the IR camera’s range, the servo pulls the rope up - in the middle and the servo stops – near the bottom and the rope lowers.  This gave much better control than the other methods we tried.


IV. Results of the design

We managed to build a Wiimote-controllable crane that can rotate from side to side, raise and lower a gripper, and pick up small objects.  The Wiimote control works, though it can be really sensitive so using it requires some practice.  The continuous rotation servo turns smoothly and the gripper servo works well.  The rotating arm servo’s movement is still somewhat choppy however.  Overall the servo crane works alright but is a little bit too sensitive to small movements by the Wiimote.


The user interface for our project is the Wiimote, which is a common device and extremely safe(unless you throw it at your television).  The crane arm could potentially swing fast enough to pinch a finger and the gripper servo can grab pretty hard, but these problems can be mitigated by moving the crane assembly away from the user or encasing it behind glass(like the candy cranes at a mall).

Interference with other designs

Our project is unlikely to interfere with nearby projects.  The Wiimote uses Bluetooth to communicate with the Macbook.  Bluetooth is unlikely to interfere with other devices as it quickly hops between frequencies around 2.4 GHz rather than broadcasting at a single frequency.

V. Conclusion

Results versus expectations

Our first idea for a project was to track 3d points with two Wiimotes, and we eventually changed this to one Wiimote after having trouble getting the remote to work correctly.  Once we got the remote working and sending data to the MCU we had to come up with an application of the IR tracking.  Building a crane seemed like a good combination of software and hardware as well as a device that would be fun to play around with.  The crane itself works well, though with some more time we could build one with a stronger frame and more aesthetics.

We would like to thank Professor Land for teaching us all about microcontrollers and helping in the lab.  Morgan Jones, Adrian Wong, and the other TAs and consultants were helpful in offering advice and troubleshooting.  A number of other students were very helpful in giving us ideas, working on hardware, and teaching Andrew how to solder.

IP Considerations

Our project uses Nintendo’s Wiimote and is inspired by Johnny Chung Lee’s Wiimote hacks.  The basic IR tracking of our project is similar to Lee’s “Tracking Your Fingers With The Wiimote” project, though we use the Wiimote in a slightly different fashion by moving the remote itself rather than reflecting IR light off our fingers as in his video.

Relationship to Patents/Standards 

This project has been designed in close adherence to the IEEE Code of Ethics.  Safety considerations have been a primary concern and we believe that we have disclosed any risks inherent to our design as well as producing a project unlikely to cause harm.  The project uses low voltages for its electrical components and as long as the crane is positioned correctly our user interface is perfectly safe.  We believe that we have adequately credited the contributions of those who motivated and helped with our project.  Intellectual property issues have been disclosed and are not of primary concern as we have intent to patent or commercialize our design.

Legal Considerations

Since we are not intending on profiting from this project intellectual property issues should not be much of a concern.  Bluetooth is not a high power wireless protocol and not a problem for interference with other devices. 


VI. Appendixes

Program Code

MCU Code

#include <Mega32.h>

#include <delay.h>

#include <math.h>

#include <stdio.h>

#include <values.h>

#include <stdlib.h>

#include <string.h>       


#define t1  2

#define FILTERSIZE 5


unsigned char r_index, r_buffer_x[5], r_buffer_y[5], r_buffer_button[1], r_ready, r_char; 

unsigned int time1;

unsigned char read_buffer[16]; 

unsigned int x_val, y_val, button_a, last_x_val, last_y_val, badValueCount; 

unsigned char turn_right, turn_left, grab_count;

int ropePWMctrl, gripPWMctrl, armPWMctrl, PWMadd;   

unsigned int yPosArray[5], xPosArray[5];

int yAve, xAve, last_y_ave, last_x_ave;  

unsigned char PWM_cycle, num_grab_cycles, grab_pulse;


//function prototypes

void processIRData(void);

void PWMctrler(void);

void puts_int(void);

void gets_int(void);

void initialize(void);    

void compare_x (void);

void compare_y (void);


//timer2 overflow interrupt, decrements time2 and resets timer2 for PWM control

//this interrupt is called roughly once every 16ms

interrupt [TIM2_OVF] void timer2_overflow(void)


  //if (time2>0) time2--;



//timer1 cmp-match is called at 5KHz   

interrupt [TIM1_COMPA] void timer1_cmpmatch(void)


    //this timer controls the grabber servo when button A is pressed on the Wii remote

    if (grab_count >0)


        //manual PWM signal 

        if (PWM_cycle == 2) PORTB.2 = 1;

        if (PWM_cycle == grab_pulse) PORTB.2 = 0;   

        //each cycle is 20ms so 100 calls of this function at 5KHz

        if (PWM_cycle == 100)



              PWM_cycle = 0;    //reset PWM pulse



        if (num_grab_cycles ==10) //send 10 pulses at one length before changing the width


             grab_pulse--;        //controls pulse width

             num_grab_cycles = 0;






//timer 1 is running at 16MHz/1024 or 15625 Hz and timer0 is 8bits so this is called roughly once every 16ms 

interrupt [TIM0_OVF] void timer0_overflow(void)


  if (time1 >0) time1--;        



//USART receive complete interrupt

interrupt [USART_RXC] void REC(void)


     r_char = UDR;

     if (r_char != '\r')


          //parse x and y data


            r_buffer_x[r_index++] = r_char;

          else if (r_index >=4 && r_index < 8)              


            PORTB.4 = ~PORTB.4;

            r_buffer_y[r_index-4] = r_char; 





            r_buffer_button[0] = r_char;



     else  //end of line


          putchar('\n');  //won't block

          r_ready = 1;   //ready to process string   

          UCSRB.7 = 0;   //disable RX complete interrupt


end //end USART.RXC intr


//processIRData takes the incoming IR data, filters it, and uses it to set servo positions

void processIRData(void)


  int i;

  int k;

  time1 = t1;     


  if (r_ready)


      last_x_val = x_val;

      if (y_val != 1023) last_y_val = y_val;

      last_y_ave = yAve;

      if (y_val == 1023 || x_val == 1023) badValueCount = 1;



           if (badValueCount>0) badValueCount--;   


           //update x and y position arrays

           for (k = 0; k<FILTERSIZE-1; k++)


               yPosArray[k+1] = yPosArray[k];

               xPosArray[k+1] = xPosArray[k];  


           yPosArray[0] = y_val;

           xPosArray[0] = x_val; 


      //calculate position averages of the last 5 readings

      for (k = 0; k<FILTERSIZE; k++)


          yAve += yPosArray[k];

          xAve += xPosArray[k];


      yAve = yAve/FILTERSIZE;

      xAve = xAve/FILTERSIZE;


      //get new x, y, and button readings

      x_val = atoi(r_buffer_x); 

      y_val = atoi(r_buffer_y); 

      button_a = atoi(r_buffer_button);        

        //if the user presses button A on the remote start contracting the grabber

      if (button_a)


          grab_count = 100;

          grab_pulse = 12; 


        else if (badValueCount == 0)     









void PWMctrler(void)


       //rotating servo controls, high being 30 won't turn it at all,

       if (turn_left)


          OCR2 = 28;


       else if (turn_right)


          OCR2 = 32;




          OCR2 = 30;


       //manual control of pulse width via STK buttons                              

       if (PINA.2==0)


          OCR0 += PWMadd;

          OCR2 += PWMadd;

          PWMadd = 0;


       if (PINA.1==0)


          PWMadd = 1;


       if (PINA.0==0)


          PWMadd = -1;




void compare_x (void)


//this function uses the average of the last 5 x points to set a servo position

//x coordinates run between 0-1023, with 1023 representing no reading

//each range of 50 pixels is set to a certain servo position by changing a pulse width

//to minimize jerk in the servo the pulse width is only changed by 1 each time

//this function is called until the servo has the proper orientation

if (xAve > 0 && xAve < 50 && OCR0 > 10) OCR0--;

else if (xAve >= 50 && xAve < 100)


    if (OCR0 > 11) OCR0--;

    else if (OCR0 < 11) OCR0++;


else if (xAve >= 100 && xAve < 150)


    if (OCR0 > 12) OCR0--;

    else if (OCR0 < 12) OCR0++;



else if (xAve >= 150 && xAve < 200)   


    if (OCR0 > 13) OCR0--;

    else if (OCR0 < 13) OCR0++;



else if (xAve >= 200 && xAve < 250)  


    if (OCR0 > 14) OCR0--;

    else if (OCR0 <14)OCR0++;



else if (xAve >= 250 && xAve < 300) 


    if (OCR0 > 15) OCR0--;

    else if (OCR0 <15)OCR0++;



else if (xAve >= 300 && xAve < 350)


    if (OCR0 > 16) OCR0--;

    else if (OCR0 <16)OCR0++;



else if (xAve >= 350 && xAve < 400)


    if (OCR0 > 17) OCR0--;

    else if (OCR0 <17)OCR0++;



else if (xAve >= 400 && xAve < 450)


    if (OCR0 > 18) OCR0--;

    else if (OCR0 <18)OCR0++;



else if (xAve >= 450 && xAve < 500)


    if (OCR0 > 19) OCR0--;

    else if (OCR0 <19)OCR0++;



else if (xAve >= 500 && xAve < 550)


    if (OCR0 > 20) OCR0--;

    else if (OCR0 <20)OCR0++;



else if (xAve >= 550 && xAve < 600)


    if (OCR0 > 21) OCR0--;

    else if (OCR0 <21)OCR0++;



else if (xAve >= 600 && xAve < 650)


    if (OCR0 > 22) OCR0--;

    else if (OCR0 <22)OCR0++;



else if (xAve >= 650 && xAve < 700) 


    if (OCR0 > 23) OCR0--;

    else if (OCR0 <23)OCR0++;



else if (xAve >= 700 && xAve < 750)   


    if (OCR0 > 24) OCR0--;

    else if (OCR0 <24)OCR0++;



else if (xAve >= 750 && xAve < 800)   


    if (OCR0 > 25) OCR0--;

    else if (OCR0 <25)OCR0++;



else if (xAve >= 800 && xAve < 850)   


    if (OCR0 > 26) OCR0--;

    else if (OCR0 <26)OCR0++;



else if (xAve >= 850 && xAve < 900)   


    if (OCR0 > 27) OCR0--;

    else if (OCR0 <27)OCR0++;



else if (xAve >= 900 && xAve < 950)  


    if (OCR0 > 28) OCR0--;

    else if (OCR0 <28)OCR0++;



else if (xAve >= 950 && xAve < 1000)   


    if (OCR0 > 29) OCR0--;

    else if (OCR0 <29)OCR0++;



else if (xAve >=1000 && xAve < 1024)  


    if (OCR0 > 30) OCR0--;

    else if (OCR0 <30)OCR0++;




void compare_y(void)


        //the y-values for the IR camera are between 0-787, with 1023 meaning no reading

        //The y-servo has been modified to run continuously and therefore has only three commands: stop turning, turn CW, and turn CCW

        //if y is in the middle of the range, we stop the servo

        //if y is near the top of the range, we raise the rope

        //if y is near the bottom of the range, we lower the rope

        if (y_val == 1023 )


                turn_right = 0;

                turn_left = 0;




             if (y_val > 475)


                  turn_right = 1;

                  turn_left = 0;


             else if (y_val < 225)


                  turn_left = 1;

                  turn_right = 0;




                  turn_left = 0;

                  turn_right = 0;





void gets_int(void)


  r_ready = 0;

  r_index = 0;

  UCSRB.7 = 1;      //RX complete interrupt enable on



void main(void)



  //endless loop



      if (time1 == 0)





  end //end while

end  //end main


void initialize(void)


  int k = 0;

  //set I/O direction for port pins

  DDRB = 0xff; 

  DDRD.7 = 1;   

  DDRC.7 = 1; 

  DDRC.0 = 1; 

  DDRC.2 = 1;


  PORTB = 0xf0;

  PORTC.7 = 0b1;


  UCSRB = 0x90;  //enables serial receive UCSRB = 0b10010000

  UBRRL = 103;   //set baud rate 9600


  //initialize starting PWM widths

  OCR0 = 20; //PINB.3                     


  TIMSK = 0b01010001;          //enables timer 2 overflow intr, timer 1 cmp-match, timer 0 overflow interrupt


  TCCR2 = 0b01101111;          //bits 3 and 6 enable fast-pwm mode, bits 4 and 5 set OC2 at top and clear at OCR2, prescalar 111 is CLK/1024

  ASSR = ASSR & 0b11110111;

  OCR2 = 30; //PIND.7


  TCCR1A = 0x00;

  TCCR1B = 0b00001011; //sets up timer1 for clear on match counting, with presclar clk/64=250KHz

  OCR1A = 50;          //helps to set up 1/5 ms time base


  TCCR0 = 0b01101101;  //turn on fast PWM mode, set OC0 bit at TOP and clear at match 


  PWMadd = 0;


  time1 = t1;

  x_val = 1023;


  y_val = 1023;

  badValueCount = 0;

  last_x_val = 1023;

  last_y_val = 1023;

  last_y_ave = 1023;    

  last_x_ave = 1023;


  turn_right = 0;

  turn_left = 0;

  xAve = 1023;

  yAve = 1023;


  grab_count = 0;

  PWM_cycle = 0;

  num_grab_cycles = 0;

  grab_pulse = 14;                  


  for (k=0; k<FILTERSIZE; k++)  


    //initialize x/y arrays to "no reading" value of 1023

    yPosArray[k] =  1023;

    xPosArray[k] =  1023;



  //turn on interrupts


  r_ready = 1;




Snippets of Code modified for Wii connection

- (void) handleIRData:(unsigned char *) dp length:(size_t) dataLength


//    NSLog(@"Handling IR Data for 0x%00x", dp[1]);  

      int i = 0;

      //int* test = malloc(sizeof int *);

      char pressed = 0;

      //int blah =0;

      if (dp[1] == 0x33) { // 12 IR bytes

            int startByte = 0;

            for(i=0 ; i < 4 ; i++) {

                  startByte = 7 + 3 * i;

                  irData[i].x = (dp[startByte +0] | ((dp[startByte +2] & 0x30) << 4)) & 0x3FF;

                  irData[i].y = (dp[startByte +1] | ((dp[startByte +2] & 0xC0) << 2)) & 0x3FF;

                  irData[i].s =  dp[startByte +2] & 0x0F;


      } else { // 10 IR bytes

            int shift = (dp[1] == 0x36) ? 4 : 7;

            int startByte = 0;

            int i;

            for (i=0; i < 2; i++) {

                  startByte = shift + 5 * i;

                  irData[2*i].x = (dp[startByte +0] | ((dp[startByte +2] & 0x30) << 4)) & 0x3FF;

                  irData[2*i].y = (dp[startByte +1] | ((dp[startByte +2] & 0xC0) << 2)) & 0x3FF;

                  irData[2*i].s = ((irData[2*i].x == irData[2*i].y) && (irData[2*i].x == 0x3FF)) ? 0x0F : 0x05;  // No size is given in 10 byte report.

                  irData[(2*i)+1].x = (dp[startByte +3] | ((dp[startByte +2] & 0x03) << 8)) & 0x3FF;

                  irData[(2*i)+1].y = (dp[startByte +4] | ((dp[startByte +2] & 0x0C) << 6)) & 0x3FF;

                  irData[(2*i)+1].s = ((irData[(2*i)+1].x == irData[(2*i)+1].y) && (irData[(2*i)+1].x == 0x3FF)) ? 0x0F : 0x05;  // No size is given in 10 byte report.



      NSLogDebug (@"IR Data (%i, %i, %i) (%i, %i, %i) (%i, %i, %i) (%i, %i, %i)",

              irData[0].x, irData[0].y, irData[0].s,

              irData[1].x, irData[1].y, irData[1].s,

              irData[2].x, irData[2].y, irData[2].s,

              irData[3].x, irData[3].y, irData[3].s);

      int p1 = -1;

      int p2 = -1;

      // we should modify this loop to take the points with the lowest s (the brightest ones)

      for (i=0 ; i<4 ; i++) {

            if (p1 == -1) {

                  if (irData [i].s < 0x0F)

                        p1 = i;

            } else {

                  if (irData [i].s < 0x0F) {

                        p2 = i;





//    NSLogDebug (@"p1=%i ; p2=%i", p1, p2);

      double ox, oy;

      if ((p1 > -1) && (p2 > -1)) {

            int l = leftPoint;

            if (leftPoint == -1) {

                  switch (orientation) {

                        case 0: l = (irData[p1].x < irData[p2].x) ? 0 : 1; break;

                        case 1: l = (irData[p1].y > irData[p2].y) ? 0 : 1; break;

                        case 2: l = (irData[p1].x > irData[p2].x) ? 0 : 1; break;

                        case 3: l = (irData[p1].y < irData[p2].y) ? 0 : 1; break;


                  leftPoint = l;


            int r = 1-l;

            double dx = irData[r].x - irData[l].x;

            double dy = irData[r].y - irData[l].y;

            double d = hypot (dx, dy);

            dx /= d;

            dy /= d;

            double cx = (irData[l].x + irData[r].x)/kWiiIRPixelsWidth - 1;

            double cy = (irData[l].y + irData[r].y)/kWiiIRPixelsHeight - 1;

            ox = -dy*cy-dx*cx;

            oy = -dx*cy+dy*cx;

            // cam:

            // Compensate for distance. There must be fewer than 0.75*768 pixels between the spots for this to work.

            // In other words, you have to be far enough away from the sensor bar for the two spots to have enough

            // space on the image sensor to travel without one of the points going off the image.

            // note: it is working very well ...

            double gain = 4;

            if (d < (0.75 * kWiiIRPixelsHeight))

                  gain = 1 / (1 - d/kWiiIRPixelsHeight);   

            ox *= gain;

            oy *= gain;      

//          NSLog(@"x:%5.2f;  y: %5.2f;  angle: %5.1f\n", ox, oy, angle*180/M_PI);

      } else {

            ox = oy = -100;

            if (leftPoint != -1) {

                  //    printf("Not tracking.\n");

                  leftPoint = -1;



      if ([_delegate respondsToSelector:@selector (irPointMovedX:Y:)])

            [_delegate irPointMovedX:ox Y:oy];

      if ([_delegate respondsToSelector:@selector (rawIRData:)])

            [_delegate rawIRData:irData];




      //send data through serial if A button is pressed    

      if (buttonState[WiiRemoteAButton] == YES) {

            pressed = 1;


      if (j ==0) //open serial port at first data set


            test = serialPort(irData[0].x, irData[0].y);

            j++; //increment j, a flag to indicate that serial port is already opened


      else if (j==11) { //only send data at every 10 cycles,pervents serial  port being too busy and crashes application

            InitializeModem(test, irData[0].x, irData[0].y, pressed); //send irData and button a pressed status

            j =1; //reset to 1 to prevent serial port being opened multiple times


       else j++;

      //NSLog(@"test : %d\n", test);

} // handleIRData


Bluetooth Standard Specifications

Johnny Chung Lee’s Wiimote Project Page

Wii hacking website

Good article on Servos



Infrared LEDs

Standard Servos  

4N35 Optoisolator

Servo Gripper





Figure 7: LED Array


Figure 8: Mega32 and Servo connections


Distribution of Tasks Between Group Members

Though often we worked together on tasks like getting the motors configured and testing the design, below is a breakdown of individual contributions.

Wan Ling Yih

            -Wiimote-Macbook-Mega32 software interface

            -Construction of Mechanical Design

            -Parts ordering

Andrew Courtney:

            -PWM Servo controller code

            -Soldering and hardware design

            -Device Calibration




Price per unit


Total Price

6 inch solder board




Small solder board




Power supply




Custom PC Board








RS232 Connector








2 pin flat jumper cables




DIP socket




Standard Servo (R276-S03N)




LED – LTE-4208




Wood + Wood glue





Total = $70.56


Free Stuff

Wii remote – Andrew has a Wii

One standard servo – Wan Ling’s friend had one lying around