USBChuck: A smaller iTapStick

Want to show off your own project? Want to keep a build log of it? Post it here!
Post Reply
User avatar
Merlin04
Posts: 32
Joined: Wed Jun 22, 2016 1:36 pm
Has thanked: 22 times
Been thanked: 6 times

USBChuck: A smaller iTapStick

Post by Merlin04 » Thu Sep 01, 2016 6:19 pm

I recently got the idea to make a smaller iTapStick. The iTapStick is a Wii Nunchuck that is a mouse using USB: https://learn.adafruit.com/nunchuket-ku ... y?view=all
The way I would make it smaller is put the Pro Trinket inside the Wii Nunchuck, not as a separate board the Nunchuck plugs into. I call it the USBChuck.
usbchuck.png
usbchuck.png (17.81 KiB) Viewed 5069 times
The Nunchuck is suprisingly empty, and has just enough space for a Trinket...
IMG_1755.JPG
IMG_1755.JPG (1.42 MiB) Viewed 5069 times
However, there is lots of support structure that gets in the way of the Trinket. I will have time to cut the support structure in a few days.

I checked the printed labels on the board for the wires, and despite the confusing wire colors (red for ground? What were they thinking?) they are correct. My wirings are as follows:
Red --> A2
Green --> A3
Yellow --> A4
White --> A5
Black --> NC

I have no idea what the black wire does.


I will update this when I make more progress.
I haven't been active for a while. I periodically check the forum and blog for interesting projects, but don't post very much.

User avatar
Merlin04
Posts: 32
Joined: Wed Jun 22, 2016 1:36 pm
Has thanked: 22 times
Been thanked: 6 times

Re: USBChuck: A smaller iTapStick

Post by Merlin04 » Mon Sep 05, 2016 10:09 am

Merlin04 wrote: I will update this when I make more progress.
Yay, I made more progress!

I made some simple cuts of the support structure, and soldered the wires from the Nunchuck to the Pro Trinket. I also had to cut some of the casing off of my USB cable. Then, I added Sugru to hold things together and to protect the wires. I also put electrical tape on the end of the mystery black wire and left it in the Nunchuck.
IMG_1760.JPG
IMG_1760.JPG (1.14 MiB) Viewed 5017 times
Now time for the code!
I have been modifing the iTapStick code from Adafruit. So far I have made the mouse easier to control, and added click-and-drag. Soon, I will add basic acceleration. Here is my modified code:
[spoiler="Code"]itapstick.ino

Code: Select all

/*************************************************************************
    iTapStick Project:
    Wii Nunchuk-to-Mouse USB Controller Stick
    Copyright (c) 2015 iTapArcade, LLC
    
    Date Created: 9 March 2015
    Date Modified: 10 March 2015
    Version: 1.0
    Visit us at iTapArcade.com
    Follow us on Twitter @iTapArcade
    
    Function: Wired and Wireless mouse emulation (mouse cursor, left click, and right click) 
    from Wii Nunchuk to support Tap and Mouse control based Games for PC, Mac, and Android based games
    
    Support our open source projects by getting an iTapStick Kit Available:
    https://www.tindie.com/products/itaparcade/itapstick-wii-nunchuck-to-mouse-usb-stick/?pt=ac_prod_search
    
    Microcontroller: Pro Trinket (3 or 5 volt) from Adafruit.com
    Note: Need 5 volt Pro Trinket for Wireless Wii Nunchuks!
 
    Wii Nunchuck Library Code from: 
    Author: Tim Teatro
    Date  : Feb 2012
     http://www.timteatro.net/2012/02/10/a-library-for-using-the-wii-nunchuk-in-arduino-sketches/
 ************************/

 /* Modified by Merlin04 */
 
#include <Wire.h> 
#include <EEPROM.h>
#include "wiinunchuk.h"  // Wii Nunchuk Library
#include <ProTrinketMouse.h>    // Pro Trinket V-USB mouse emulator
 
int led = 13;    // Use LED on Pro Trinket for Status and Button Press Indicator
int loop_cnt = 0;   
int mode = 0;     // Defines mode of the iTapStick: left mouse click is C or Z button
 
// parameters for reading the joystick:
int range = 20;                 // output range of X or Y movement
int threshold = range/10;      // resting threshold
int center = range/2;         // resting position value
int pressed = 1;

void setup() {
  pinMode(led, OUTPUT); 
 
  nunchuk_setpowerpins();    // set power pins for Wii Nunchuk
  nunchuk_init();            // initilization for the Wii Nunchuk
  delay(250);
  
  while(!nunchuk_get_data()){ // loop until Wii Nunchuk is connected to Nunchucky
  nunchuk_init();
  delay(250);
  nunchuk_get_data();
  digitalWrite(led, HIGH); 
  delay(250);
  digitalWrite(led, LOW); 
  delay(500);
  }
  
  digitalWrite(led, HIGH);  // let user know they can select mode within (5 seconds)
  delay(5000);
 
 // check if user is holding any of buttons to change mode
 
  nunchuk_init(); 
  delay(250);           // Once connected lets get additional data to determine if C or Z
  nunchuk_get_data();  // button is being held down for programming mode
  delay(10);           
  nunchuk_get_data();
  delay(10);           
  nunchuk_get_data();
  
  int leftState = nunchuk_zbutton();
  int rightState = nunchuk_cbutton();
 
   
    if(leftState){  // save mode for future time if Z is defined as left mouse click
       mode = 0;    
     EEPROM.write(0, 0); 
     digitalWrite(led, LOW);   
     delay(250);
     digitalWrite(led, HIGH);   
     delay(250);
     digitalWrite(led, LOW);   
     delay(250);
     digitalWrite(led, HIGH);   
     delay(250);
     digitalWrite(led, LOW);   
     delay(250);
     digitalWrite(led, HIGH);   
     delay(3000); 
    }else if(rightState){ // save mode for future time if C is defined as left mouse click 
     mode = 1;
     EEPROM.write(0, 1); 
     digitalWrite(led, LOW);   
     delay(100);
     digitalWrite(led, HIGH);   
     delay(100);
     digitalWrite(led, LOW);   
     delay(100);
     digitalWrite(led, HIGH);   
     delay(100);
     digitalWrite(led, LOW);   
     delay(100);
     digitalWrite(led, HIGH); 
     delay(100);
     digitalWrite(led, LOW);   
     delay(100);
     digitalWrite(led, HIGH);   
     delay(100);
     digitalWrite(led, LOW);   
     delay(100);
     digitalWrite(led, HIGH);   
     delay(100);
     digitalWrite(led, LOW);   
     delay(100);
     digitalWrite(led, HIGH);    
     delay(3000);
    }else
    {  
    mode = EEPROM.read(0);   // no mode selection load in saved mode
    }
   
  digitalWrite(led, LOW);  // get LED indicator ready to show when buttons are being pressed    
 
 // Start Pro Trinket into Mouse mode
  TrinketMouse.begin();               // Initialize mouse library
}
 
void loop() {
  if( loop_cnt > 10 ) { // every 10 msecs get new data
    loop_cnt = 0;
    
   
    if(nunchuk_get_data())   // only check for data if data is available from Wii Nunchuk
    {
    
    // right and left click control
    int leftState = nunchuk_zbutton();
    int rightState = nunchuk_cbutton();
    
    if (leftState) // if button is pressed update
    {
      if(mode==0){
      TrinketMouse.move(0,0,0,MOUSEBTN_LEFT_MASK);
      pressed = MOUSEBTN_LEFT_MASK;
      }else
      {
      TrinketMouse.move(0,0,0,MOUSEBTN_RIGHT_MASK);
      pressed = MOUSEBTN_RIGHT_MASK;
      }
      digitalWrite(led, HIGH); // show button is pressed by LED
     }else if (rightState) // if button is pressed update
    {
      if(mode==0){
      TrinketMouse.move(0,0,0,MOUSEBTN_RIGHT_MASK);
      pressed = MOUSEBTN_RIGHT_MASK;
      }else
      {
      TrinketMouse.move(0,0,0,MOUSEBTN_LEFT_MASK);  
      pressed = MOUSEBTN_LEFT_MASK;
      }
       digitalWrite(led, HIGH);// show button is pressed by LED  
     }
     else
    {
    TrinketMouse.move(0,0,0,0); // if no button is pressed update
    pressed = 0;
     digitalWrite(led, LOW); // show no button is pressed by LED
    }
    
    int xReading = nunchuk_joy_x();    // read the x axis
    xReading = map(xReading, 38, 232, 0, range); // map accordingly
    int xDistance = xReading - center;
    if (abs(xDistance) < threshold) {
      xDistance = 0;
    } 
 
    
    int yReading = nunchuk_joy_y();   // read the y axis
    yReading = map(yReading, 38, 232, 0, range); // map accordingly
    int yDistance = yReading - center;
    if (abs(yDistance) < threshold) {
      yDistance = 0;
    } 
 
    if ((xDistance != 0) || (yDistance != 0)) { // move the mouse based on x and y
      
    TrinketMouse.move(xDistance, -yDistance, 0, pressed); 
    } 
    }
  }
  loop_cnt++;
  delay(1);
}
wiinunchuk.h (unmodified)

Code: Select all

/*
 * File  : wiinunchuk.h V0.9
 * Author: Tim Teatro
 * Date  : Feb 2012
 *
 * Description:
 *
 *   Library to set up and poll a Wii nunchuk with Arduino. There are
 * many libraries available to do this, none of which I really liked.
 * I was fond of Tod Kurt's, but his was incomplete as it did not work
 * with knockoff nunchuks, it did not consider the least significant
 * bits of accelerometer data and didn't have any advanced functions
 * for processing the data such as calculating pitch and roll angles.
 *
 *
 * Provides functions:
 *  void nunchuk_setpowerpins()
 *  void nunchuk_init()
 *  int nunchuk_get_data()
 *  void nunchuk_calibrate_joy()
 *  inline unsigned int nunchuk_zbutton()
 *  inline unsigned int nunchuk_cbutton()
 *  inline int nunchuk_joy_x()
 *  inline int nunchuk_cjoy_x()
 *  inline int nunchuk_cjoy_y()
 *  inline uint16_t nunchuk_accelx()
 *  inline uint16_t nunchuk_accely()
 *  inline uint16_t nunchuk_accelz()
 *  inline int nunchuk_caccelx()
 *  inline int nunchuk_caccely()
 *  inline int nunchuk_caccelz()
 *  inline int nunchuk_joyangle()
 *  inline int nunchuk_rollangle()
 *  inline int nunchuk_pitchangle()
 *  void nunchuk_calibrate_accelxy()
 *  void nunchuk_calibrate_accelz()
 *
 * This library is inspired by the work of Tod E. Kurt,
 *  (http://todbot.com/blog/bionicarduino/)
 *
 * (c) 2012 by Tim Teatro
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#endif

//
// These are suitable defaults for most nunchuks, including knockoffs.
// If you intend to use the same nunchuk all the time and demand accu-
// racy, it is worth your time to measure these on your own.
//     If you may want to use various nunchuks, you may want to
// calibrate using functions
//   nunchuk_calibrate_joy()
//   nunchuk_calibrate_accelxy()
//   nunchuk_calibrate_accelz()
//
#define DEFAULT_CENTRE_JOY_X 124
#define DEFAULT_CENTRE_JOY_Y 132
#define ACCEL_ZEROX 490
#define ACCEL_ZEROY 500
#define ACCEL_ZEROZ 525

//
// Global vars are kept to a minimum.
//
uint8_t ctrlr_type[6];   // Used externally?
uint8_t nunchuk_buf[6];  // Keeps data payload from nunchuk
// Accelerometer values and callibration centres:
uint16_t accel_zerox, accel_zeroy, accel_zeroz;
// Joystick values and calibration centres:
int joy_x, joy_y, joy_zerox, joy_zeroy;

//
//
// Uses port C (analog in) pins as power & ground for nunchuk
//
void nunchuk_setpowerpins()
  {
  #define pwrpin PORTC3
  #define gndpin PORTC2
  DDRC |= _BV(pwrpin) | _BV(gndpin);
  PORTC &=~ _BV(gndpin);
  PORTC |=  _BV(pwrpin);
  delay(100); // wait for things to stabilize
  }

//
//
// Initialize and join the I2C bus, and tell the nunchuk we're
// talking to it. This function will work both with Nintendo
// nunchuks, or knockoffs.
//
// See http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1264805255
//
void nunchuk_init()
  {
  Wire.begin();
  delay(1);
  Wire.beginTransmission(0x52);  // device address
  #if (ARDUINO >= 100)
    Wire.write((uint8_t)0xF0);  // 1st initialisation register
    Wire.write((uint8_t)0x55);  // 1st initialisation value
    Wire.endTransmission();
    delay(1);
    Wire.beginTransmission(0x52);
    Wire.write((uint8_t)0xFB);  // 2nd initialisation register
    Wire.write((uint8_t)0x00);  // 2nd initialisation value
  #else
    Wire.send((uint8_t)0xF0);   // 1st initialisation register
    Wire.send((uint8_t)0x55);   // 1st initialisation value
    Wire.endTransmission();
    delay(1);
    Wire.beginTransmission(0x52);
    Wire.send((uint8_t)0xFB);   // 2nd initialisation register
    Wire.send((uint8_t)0x00);   // 2nd initialisation value
  #endif
  Wire.endTransmission();
  delay(1);
  //
  // Set default calibration centres:
  //
  joy_zerox = DEFAULT_CENTRE_JOY_X;
  joy_zeroy = DEFAULT_CENTRE_JOY_Y;
  accel_zerox = ACCEL_ZEROX;
  accel_zeroy = ACCEL_ZEROY;
  accel_zeroz = ACCEL_ZEROZ;
  }

//
//
// T.T.
// Standard nunchuks use a byte-wise encryption using bit-wise XOR
// with 0x17. This function decodes a byte.
//
// This function is not needed since the way we initialize the nunchuk
// does not XOR encrypt the bits.
//
//static inline char nunchuk_decode_byte (char x)
//  {
//  x = (x ^ 0x17) + 0x17;
//  return x;
//  }

static void nunchuk_send_request()
{
  Wire.beginTransmission(0x52);// transmit to device 0x52
  #if (ARDUINO >= 100)
    Wire.write((uint8_t)0x00);// sends one byte
  #else
    Wire.send((uint8_t)0x00);// sends one byte
  #endif
  Wire.endTransmission();// stop transmitting
}

//
//
// Gets data from the nunchuk and packs it into the nunchuk_buff byte
// aray. That array will be processed by other functions to extract
// the data from the sensors and analyse.
//
int nunchuk_get_data()
  {
  int cnt=0;
    // Request six bytes from the chuck.
  Wire.requestFrom (0x52, 6);
  while (Wire.available ())
    {
    // receive byte as an integer
    #if (ARDUINO >= 100)
      nunchuk_buf[cnt] = Wire.read();
    #else
      nunchuk_buf[cnt] = Wire.receive();
    #endif
    cnt++;
    }

  Wire.beginTransmission(0x52);// transmit to device 0x52
  #if (ARDUINO >= 100)
    Wire.write((uint8_t)0x00);// sends one byte
  #else
    Wire.send((uint8_t)0x00);// sends one byte
  #endif
  Wire.endTransmission();// stop transmitting

  if (cnt >= 5)
    {
    return 1;   // success
    }
  return 0; // failure
  }

//
//
//
// Calibrate joystick so that we read the centre position as (0,0).
// Otherwise, we use the default values from the header.
//
void nunchuk_calibrate_joy()
  {
  joy_zerox = joy_x;
  joy_zeroy = joy_y;
  }

// Returns c and z button states: 1=pressed, 0=not
// The state is in the two least significant bits of the 6th byte.
// In the data, a 1 is unpressed and 0 is pressed, so this will be
// reversed. These functions use a bitwise AND to determine the value
// and then the () ? true : false; conditional structure to pass out
// the appropriate state.
//
static inline unsigned int nunchuk_zbutton()
  {
  return ((nunchuk_buf[5] >> 0) & 1) ? 0 : 1;
  }

static inline unsigned int nunchuk_cbutton()
  {
  return ((nunchuk_buf[5] >> 1) & 1) ? 0 : 1;
  }

//
//
// Returns the raw x and y values of the the joystick, cast as ints.
//
static inline int nunchuk_joy_x()
  {
  return (int) nunchuk_buf[0];
  }

static inline int nunchuk_joy_y()
  {
  return (int) nunchuk_buf[1];
  }

//
//
// Return calibrated x and y values of the joystick.
//
static inline int nunchuk_cjoy_x()
  {
  return (int)nunchuk_buf[0] - joy_zerox;
  }

static inline int nunchuk_cjoy_y()
  {
  return (int)nunchuk_buf[1] - joy_zeroy;
  }

//
//
// Returns the raw 10-bit values from the 3-axis accelerometer sensor.
// Of the six bytes recieved in a data payload from the nunchuk, bytes
// 2, 3 and 4 are the most significant 8 bits of each 10-bit reading.
// The final two bits are stored in the 6th bit along with the states
// of the c and z button. These functions take the most significant
// 8-bits and stacks it into a 16 bit unsigned integer, and then tacks
// on the least significant bits from the 6th byte of the data
// payload.
// 
// Load the most sig digits into a blank 16-bit unsigned int leaving
// two bits in the bottom ( via a 2-bit shift, << 2) for the least sig
// bits:
//  0x0000 | nunchuk_buff[*] << 2
// Add to the above, the least sig bits. The code for x:
//  nunchuk_buf[5] & B00001100
// for example selects the 3rd and 4th bits from the 6th byte of the
// payload to be concatinated with nunchuk_buff[2] to complete the 10-
// bit datum for a given axis.
//
static inline uint16_t nunchuk_accelx()
  {
  return (  0x0000 | ( nunchuk_buf[2] << 2 ) +
    ( ( nunchuk_buf[5] & B00001100 ) >> 2 )  );
  }

static inline uint16_t nunchuk_accely()
  {
  return (  0x0000 ^ ( nunchuk_buf[3] << 2 ) +
    ( ( nunchuk_buf[5] & B00110000 ) >> 4 )  );
  }

static inline uint16_t nunchuk_accelz()
  {
  return (  0x0000 ^ ( nunchuk_buf[4] << 2 ) +
    ( ( nunchuk_buf[5] & B11000000 ) >> 6 )  );
  }

//
//
// Returns the x,y and z accelerometer values with calibration values
// subtracted.
//
static inline int nunchuk_caccelx()
  {
    return (int)(nunchuk_accelx() - accel_zerox);
  }

static inline int nunchuk_caccely()
  {
    return (int)(nunchuk_accely() - accel_zeroy);
  }

static inline int nunchuk_caccelz()
  {
    return (int)(nunchuk_accelz() - accel_zeroz);
  }

//
//
// Returns joystick angle in degrees. It uses the ratio of calibrated
// x and y potentiometer readings to find the angle, zero being direct
// right (positive x) and measured counter-clockwise from there.
//
// If the atan2 function returns a negative angle, it is rotated back
// into a positive angle. For those unfamiliar, the atan2 function
// is a more inteligent atan function which quadrant the vector <x,y>
// is in, and returns the appropriate angle.
//
static inline int nunchuk_joyangle()
  {
  double theta;
  theta = atan2( nunchuk_cjoy_y(), nunchuk_cjoy_x() );
  while (theta < 0) theta += 2*M_PI;
  return (int)(theta * 180/M_PI);
  }

//
//
// Returns roll angle in degrees. Under the assumption that the
// only acceleration detected by the accelerometer is acceleration due
// to gravity, this function uses the ratio of the x and z
// accelerometer readings to gauge pitch. This only works while the
// nunchuk is being held still or at constant velocity with zero ext-
// ernal force.
//
static inline int nunchuk_rollangle()
  {
  return (int) (  atan2( (double) nunchuk_caccelx(),
    (double) nunchuk_caccelz() ) * 180 / M_PI  );
  }

//
//
// Returns pitch angle in degrees. Under the assumption that the
// only acceleration detected by the accelerometer is acceleration due
// to gravity, this function uses the ratio of the y and z
// accelerometer readings to gauge pitch.  This only works while the
// nunchuk is being held still or at constant velocity with zero ext-
// ernal force.
//
static inline int nunchuk_pitchangle()
  {
  return (int) (  atan2( (double) nunchuk_caccely(),
    (double)nunchuk_caccelz() ) * 180 / M_PI  );
  }

//
//
// Because gravity pulls down on the z-accelerometer while the nunchuk
// is upright, we need to calibrate {x,y} and {z} separately. Execute
// this function while the nunchuk is known to be upright and then 
// execute nunchuk_calibrate_accelz() when the nunchuk is on its side.
//
void nunchuk_calibrate_accelxy()
  {
  accel_zerox = nunchuk_accelx();
  accel_zeroy = nunchuk_accely();
  }

//
//
// See documentation for nunchuk_calibrate_xy()
//
void nunchuk_calibrate_accelz()
  {
  accel_zeroz = nunchuk_accelz();
  }
//
//
// EOF
[/spoiler]

This project is almost finished. I will add acceleration to the code sometime soon, and then it will be done.
Last edited by Merlin04 on Fri Sep 16, 2016 7:50 pm, edited 1 time in total.
I haven't been active for a while. I periodically check the forum and blog for interesting projects, but don't post very much.

Racerboy
Posts: 129
Joined: Thu Jun 02, 2016 4:49 pm
Has thanked: 25 times
Been thanked: 36 times

Re: USBChuck: A smaller iTapStick

Post by Racerboy » Mon Sep 05, 2016 12:09 pm

Cool project! Any way to make it wireless?

Also, it's spoiler="text" :D

User avatar
Merlin04
Posts: 32
Joined: Wed Jun 22, 2016 1:36 pm
Has thanked: 22 times
Been thanked: 6 times

Re: USBChuck: A smaller iTapStick

Post by Merlin04 » Fri Sep 16, 2016 8:21 pm

Racerboy wrote:Cool project! Any way to make it wireless?

Also, it's spoiler="text" :D
Thanks! I think that you could probably use Bluefruit EZ-Key combined with a pro trinket. A small battery could fit in the Nunchuk, and the pro trinket battery backpack could manage power. It would end up looking like this:
Capture4Billion.PNG
Capture4Billion.PNG (168.24 KiB) Viewed 4911 times
Please note that some of the wiring might be wrong.
I haven't been active for a while. I periodically check the forum and blog for interesting projects, but don't post very much.

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest