Sunday, February 5, 2012

TouchOSC with Processing, iPhone and Arduino.


I've seen alot of projects that use iPhones or similair devices to interface with processing, and sometimes then control the arduino using the format sketch. This protocol is called OSC; a popular iOS/android app that implements this is called TouchOSC. It turned out that I couldn't fnd much documentation on how to use TouchOSC with processing, so it took a little fidling to successfuly read the values from the iPod Touch.

TouchOSC has a computer based layout creator which lays out the position, address, and value range of the objects in the layout. Some of the objects are push and toggle buttons, rotarys, encoders, XY pads and a few others. The TouchOSC program on the iPod then sends the OSC messages to a certain IP address(a computer with processing running). The Processing code then reads the OSC messages and can do whatever depending on what the value is.Processing needs the oscP5 library and the arduino library if you want to send commands to a Arduino from processing. MyFullExOSC.touchosc is the layout file for TouchOSC.

The following Processing program reads from device:
-toggle buttons
-push buttons
-XY pad
-Faders
-Rotaries
-Encoders
-multipush button matrixes
-multitoggle button matrixes
-multiXY pad (up to 5 touch points)
-multifader
-accelerometer
Also, the program sends back the highest and lowest accelerometer XYZ values that it has received from the iPod and displays them in labels. All of these readings have some sort of graphical representation in processing to test for:)





import oscP5.*;        //  Load OSC P5 library
import netP5.*;        //  Load net P5 library
import processing.serial.*;    // Load serial library
import cc.arduino.*;  //load arduino interface library

OscP5 oscP5;            //  Set oscP5 as OSC connection
NetAddress iPod; //holds address for sending data to iPod
Arduino arduino; // create new arudino device

PFont f; //initailize a font

//determine tab pos
boolean [] TabStat = new boolean [5];

//tab1
float [] ToggleStat = new float [3]; //Toggle: btn1, btn2, btn3
float [] PushStat = new float [3]; //Push: btn1, btn2, btn3
float [] XYpad = new float [2]; //x, y
//tab2
float [] TabII = new float [5]; //fader, rotary, encoder, Enc Previous value, Enc Pos(software based increase/decrease based on encoder)
//tab3
float [] MultiPush = new float [9]; //3x3 multi push button matrix
float [] MultiToggle = new float [9]; //3x3 multi toggle button matrix
//tab4
float [] MultiXYpad = new float [10]; //up to 5 xy coordinate pairs
float [] MultiFader = new float [5]; //multi fader with 5 faders
//sine wave
float WaveWidth = 500+16;              // Width of entire wave
float theta = 0.0;  // Start angle at 0
float dx;  // Value for incrementing X, a function of period and xspacing
float[] yvalues = new float[1];  // Using an array to store height values for the wave
//tab5
float [] Accel = new float [13]; //x, y, z, Center press button, x center, y center, z center, final x/y/z, prev final x/y/z
float [] AccelMM = new float [6]; //stores min/max values for xyz

void setup()
{
  //println(Serial.list()); //shows list of avaliable serial ports, put number for you port in Arduino.list()[X]

  size(500, 500);        // Processing screen size
  frameRate(60);
  smooth();

  f = loadFont("ComicSansMS-48.vlw"); //Load Font
  textFont(f, 25);                 //Specify font type

  oscP5 = new OscP5(this, 8000);  // Start oscP5, listening for incoming messages at port 8000
  iPod = new NetAddress("192.168.1.4", 9000); //address for sending messages to iPod(also the iPod sending messages to this computer), not needed if iPod is only sending
  arduino = new Arduino(this, Arduino.list()[0], 115200); //arduino connected, use the standard firmata example sketch
}

void draw()
{
  if (TabStat[0]) //if we are on the first tab
  {
    background(50); //color of background

    fill(0);
    text("Toggle:", 218, 25);
    text("Push:", 45, 25);

    for (int x = 0; x<3; x++)
    {
      fill(0); //fill font with black
      text(x+1, 110, 75+(x * 50)); //label buttons
      text(x+1, 310, 75+(x * 50));

      //draw buttons
      if (ToggleStat[x] == 1)  //if x button is pressed
      {
        fill(0, 255, 0); //R,G,B/0,255,0
        ellipse(146, 65+(x * 50), 30, 30); //x,y,width.height
        arduino.digitalWrite(x+2, 1); //turns on(HIGH) digital pin x+2(2->5) on the arduino
      }
      else
      {
        fill(255, 0, 0);
        ellipse(146, 65+(x * 50), 30, 30);
        arduino.digitalWrite(x+2, 0); //turns off(LOW) digital pin x+2(2->5) on the arduino
      }

      if (PushStat[x] == 1)
      {
        fill(0, 255, 0);
        ellipse(342, 65+(x * 50), 30, 30);
      }
      else
      {
        fill(255, 0, 0);
        ellipse(342, 65+(x * 50), 30, 30);
      }
    }

    //draw XY Pad box
    fill(0); //fill with black
    stroke(255, 0, 0); //outline box with red
    rectMode(CORNER); //measure x,y from top left corner
    rect(150, 250, 200, 200);  //x,y,width,height

    //level bar
    fill(0, 255, 0);
    stroke(0); //black outline
    ellipse((150+XYpad[0]), (450-XYpad[1]), 5, 5);  //150(from top of program) + how far from top left corner my finger is on the xy pad, dido for y, dot size
  }

  else if (TabStat[1]) //if we are on the second tab
  {
    rectMode(CORNER);
    background(50);

    //fader box
    fill(0);
    text("Fader", 30, 65); //this text, x ,y
    stroke(255, 0, 0);
    rect(50, 75, 25, 350);    // 50 pixels from left, 75 from top, width 25, height 350
    //fader fill
    fill(255, 0, 0);
    rect(50, 75+350, 25, -TabII[0]); //75 is how far the bars are from the top of the window, then 350 for length to the bottom of bar


    //rotary, ~270deg
    fill(0);
    text("Rotary", 125, 175);
    fill(0, 0, 255);
    stroke(0, 0, 255);
    ellipse(300, 175, TabII[1], TabII[1]); //set size of ellipse based on rotary


    //encoder, free turning, alternates between two values, ex 01010101011111111111,LRLRLRLRLRLRLRRRRRRRRRRR
    //have fun with this below :D
    fill(0);
    text("Encoder", 125, 300);

    if (mouseX >= 200 && mouseX <= 400 && mouseY >= 200 && mouseY <= 400)  //if mouse over encoder circle, stop
    {
      TabII[4]=TabII[3]; //keep circle the same size
    }
    else //continue changing
    {
      if (TabII[2] == 1) //if this way
      {
        TabII[4]+=1; //add one
      }
      else if (TabII[2] == 0)
      {
        TabII[4]-=1;
      }
    }

    fill(200, 0, 100);
    stroke(200, 0, 150);
    ellipse(300, 300, TabII[4], TabII[4]); //draw ellipse based on encoder

    TabII[3] = TabII[4];
  }

  else if (TabStat[2]) //if we are on the third tab
  {
    background(50);
    stroke(0);
    fill(0);
    text("Toggle:", 218, 25);
    text("Push:", 45, 25);

    for (int x = 0; x<9; x++) //test all the buttons for on/off and set them green/red accordingly
    {
      fill(0);
      text(x+1, 107, 55+(x * 50)); //print button numbers
      text(x+1, 305, 55+(x * 50));

      if (MultiPush[x] == 1)  //draw buttons
      {
        fill(0, 255, 0);
        ellipse(146, 45+(x * 50), 30, 30);
      }
      else
      {
        fill(255, 0, 0);
        ellipse(146, 45+(x * 50), 30, 30);
      }

      if (MultiToggle[x] == 1)
      {
        fill(0, 255, 0);
        ellipse(342, 45+(x * 50), 30, 30);
      }
      else
      {
        fill(255, 0, 0);
        ellipse(342, 45+(x * 50), 30, 30);
      }
    }
  }

  else if (TabStat[3]) //if we are on the fourth tab
  {
    background(50);

    //draw multi XY Pad box
    fill(0);
    stroke(255, 0, 0);
    rectMode(CORNER);
    rect(150, 275, 200, 200);

    fill(0, 255, 0);
    stroke(0);
    ellipse((150+MultiXYpad[1]), (475-MultiXYpad[0]), 5, 5); //draw touch 1, 150 is the box x coordinate then + the x coordinate of touch, 475 is box y coordinate plus y length of box - the y value of touch
    ellipse((150+MultiXYpad[3]), (475-MultiXYpad[2]), 5, 5); //touch 2
    ellipse((150+MultiXYpad[5]), (475-MultiXYpad[4]), 5, 5); //...
    ellipse((150+MultiXYpad[7]), (475-MultiXYpad[6]), 5, 5);
    ellipse((150+MultiXYpad[9]), (475-MultiXYpad[8]), 5, 5);


    //sine wave maker based on fader
    theta += MultiFader[1]; //angular velocity
    // For every x value, calculate a y value with sine function
    float x = theta;
    for (int i = 0; i < yvalues.length; i++)
    {
      yvalues[i] = sin(x)* MultiFader[2];
      x+=dx;
    }

    for (int b = 0; b < yvalues.length; b++)
    {
      noStroke();
      fill(255, 0, 0);  //
      rectMode(CENTER);
      rect(b*MultiFader[0], 450/3+yvalues[b], (MultiFader[4]/15), (MultiFader[4]/15)); //x,y, width, height
    }
  }

  else if (TabStat[4]) //if we are on the fith tab
  {
    background(50);
    if (Accel[3]==1) //when zeroing the accel, make font green
    {
      fill(0, 255, 0);
      text("3-Axis Accelerometer", 130, 75);
    }
    else
    {
      fill(255, 0, 0);
      text("3-Axis Accelerometer", 130, 75);
    }

    Accel[7] = (Accel[0] - Accel[4]); //x final val = current val - center val
    Accel[8] = (Accel[1] - Accel[5]); //y
    Accel[9] = (Accel[2] - Accel[6]); //z

    fill(255, 0, 0);
    text("X Axis: ", 175, 125); //label axis in program
    text(Accel[7], 265, 125); //then write values next to label

    text("Y Axis: ", 175, 175);
    text(Accel[8], 265, 175);

    text("Z Axis: ", 175, 225);
    text(Accel[9], 265, 225);

    //all of this is for sending the highest and lowest value so far back to the ipod!
 
    //x
    if (Accel[10] < Accel[7] && (Accel[7] - Accel[10]) > AccelMM[0])  //if prev final val is less than current final val && current max is higher than prev max
    {
      //from here
      AccelMM[0] = Accel[7]; //set new max
      OscMessage Xmax = new OscMessage("/5/Xmax"); //new message called Xmax going to /5/Xmax label
      Xmax.add(Accel[7]); //add value to Xmax message that is going to be sent
      oscP5.send(Xmax, iPod); //oscP5.send(Xmax, iPod); Xmax is name of message, iPod in this line is the address, see top of code
      //to here is how you send info to device
    }
    else if (Accel[10] > Accel[7] && (Accel[10] - Accel[7]) < AccelMM[1]) //if prev final val is greater than current final val && current min is less than prev min
    {
      AccelMM[1] = Accel[10]; //set new min
      OscMessage Xmin = new OscMessage("/5/Xmin");
      Xmin.add(-Accel[10]);
      oscP5.send(Xmin, iPod);
    }

    //y
    if (Accel[11] < Accel[8] && (Accel[8] - Accel[11]) > AccelMM[2])  //if prev final val is less than current final val && current max is higher than prev max
    {
      AccelMM[2] = Accel[8]; //set new max
      OscMessage Ymax = new OscMessage("/5/Ymax");
      Ymax.add(Accel[8]);
      oscP5.send(Ymax, iPod);
    }
    else if (Accel[11] > Accel[8] && (Accel[11] - Accel[8]) < AccelMM[3]) //if prev final val is greater than current final val && current min is less than prev min
    {
      AccelMM[3] = Accel[11]; //set new min
      OscMessage Ymin = new OscMessage("/5/Ymin");
      Ymin.add(-Accel[11]);
      oscP5.send(Ymin, iPod);
    }

    //z
    if (Accel[12] < Accel[9] && (Accel[9] - Accel[12]) > AccelMM[4])  //if prev final val is less than current final val && current max is higher than prev max
    {
      AccelMM[4] = Accel[8]; //set new max
      OscMessage Zmax = new OscMessage("/5/Zmax");
      Zmax.add(Accel[9]);
      oscP5.send(Zmax, iPod);
    }
    else if (Accel[12] > Accel[9] && (Accel[12] - Accel[9]) < AccelMM[5]) //if prev final val is greater than current final val && current min is less than prev min
    {
      AccelMM[5] = Accel[12]; //set new max
      OscMessage Zmin = new OscMessage("/5/Zmin");
      Zmin.add(-Accel[12]);
      oscP5.send(Zmin, iPod);
    }
 
    /*this is also how you turn on a virtual LED on the iPod:
 
      OscMessage LEDmsg = new OscMessage("/1/LED1"); //new message going to the "/1/LED1" led
      LEDmsg.add(0); //add value to message that is going to be sent , 0 for LED off, 1 for LED on
      oscP5.send(LEDmsg, iPod); //send the message
    */

    Accel[10] = Accel[7]; //to be previous val = current final val
    Accel[11] = Accel[8];
    Accel[12] = Accel[9];
  }
}

void oscEvent(OscMessage theOscMessage) //  This runs whenever there is a new OSC message
{
  /*
  OSC message looks like this: /1/toggle1 or /1/multitoggle/2/1.
   /tab/"object name"   /tab/"object name"/row pos/column pos
 
   multitoggle 3x3 example:
       1  2  3   Y
       __ __ __
X   3  |      |
    2  |      |
    1  |      |
       __ __ __
   /1/multitoggle1/X/Y
 
   */

  String addr = theOscMessage.addrPattern();  //  Creates a string out of the OSC message
  if (addr.equals("/1")) //search string, if we are on the first tab
  {
    TabStat[0] = true; //so we know what part of the draw function to run
    TabStat[1] = false; //and not to run
    TabStat[2] = false; //...
    TabStat[3] = false;
    TabStat[4] = false;
  }
  else if (addr.equals("/2")) //tab 2
  {
    TabStat[0] = false;
    TabStat[1] = true;
    TabStat[2] = false;
    TabStat[3] = false;
    TabStat[4] = false;
  }
  else if (addr.equals("/3")) //tab 3
  {
    TabStat[0] = false;
    TabStat[1] = false;
    TabStat[2] = true;
    TabStat[3] = false;
    TabStat[4] = false;
  }
  else if (addr.equals("/4")) //tab 4
  {
    TabStat[0] = false;
    TabStat[1] = false;
    TabStat[2] = false;
    TabStat[3] = true;
    TabStat[4] = false;
  }
  else if (addr.equals("/5")) //tab 5
  {
    TabStat[0] = false;
    TabStat[1] = false;
    TabStat[2] = false;
    TabStat[3] = false;
    TabStat[4] = true;
  }

  float val = theOscMessage.get(0).floatValue();  //get the value

  if (TabStat[0]) //if we are on the first tab
  {
    if (addr.equals("/1/toggle1")) //if we find this address
    {
      ToggleStat[0] = val; //write the value to it, 0 or 1
    }
    if (addr.equals("/1/toggle2"))
    {
      ToggleStat[1] = val;
    }
    if (addr.equals("/1/toggle3"))
    {
      ToggleStat[2] = val;
    }

    if (addr.equals("/1/push1"))
    {
      PushStat[0] = val;
    }
    if (addr.equals("/1/push2"))
    {
      PushStat[1] = val;
    }  
    if (addr.equals("/1/push3"))
    {
      PushStat[2] = val;
    }

    if (addr.equals("/1/xyPad"))
    {  //xy pad writes to values to one address
      XYpad[1] = val;  //y
      XYpad[0] = theOscMessage.get(1).floatValue(); //buttons dont work if i put this(get(1)) into a float at begining of function...???
    }
  }

  else if (TabStat[1]) //if we are on the second tab
  {
    if (addr.equals("/2/fader"))
    {
      // print("fader recv");  //just checking
      // println(val);
      TabII[0] = val;
    }
    if (addr.equals("/2/rotary"))
    {
      TabII[1] = val;
    }
    if (addr.equals("/2/encoder"))
    {
      TabII[2] = val;
    }
  }

  else if (TabStat[2]) //if we are on the third tab
  {
    //MULTIPUSH-------------------------
    //Row3
    if (addr.equals("/3/multipush/1/1"))  //remember weird row numbering
    {
      MultiPush[6] = val;
    }
    if (addr.equals("/3/multipush/1/2"))
    {
      MultiPush[7] = val;
    }
    if (addr.equals("/3/multipush/1/3"))
    {
      MultiPush[8] = val;
    }
    //Row2
    if (addr.equals("/3/multipush/2/1"))
    {
      MultiPush[3] = val;
    }
    if (addr.equals("/3/multipush/2/2"))
    {
      MultiPush[4] = val;
    }
    if (addr.equals("/3/multipush/2/3"))
    {
      MultiPush[5] = val;
    }
    //Row1
    if (addr.equals("/3/multipush/3/1"))
    {
      MultiPush[0] = val;
    }
    if (addr.equals("/3/multipush/3/2"))
    {
      MultiPush[1] = val;
    }
    if (addr.equals("/3/multipush/3/3"))
    {
      MultiPush[2] = val;
    }


    //MULTITOGGLE-------------------------
    //Row1
    if (addr.equals("/3/multitoggle/1/1"))
    {
      MultiToggle[6] = val;
    }
    if (addr.equals("/3/multitoggle/1/2"))
    {
      MultiToggle[7] = val;
    }
    if (addr.equals("/3/multitoggle/1/3"))
    {
      MultiToggle[8] = val;
    }
    //Row2
    if (addr.equals("/3/multitoggle/2/1"))
    {
      MultiToggle[3] = val;
    }
    if (addr.equals("/3/multitoggle/2/2"))
    {
      MultiToggle[4] = val;
    }
    if (addr.equals("/3/multitoggle/2/3"))
    {
      MultiToggle[5] = val;
    }
    //Row3
    if (addr.equals("/3/multitoggle/3/1"))
    {
      MultiToggle[0] = val;
    }
    if (addr.equals("/3/multitoggle/3/2"))
    {
      MultiToggle[1] = val;
    }
    if (addr.equals("/3/multitoggle/3/3"))
    {
      MultiToggle[2] = val;
    }
  }

  else if (TabStat[3]) //if we are on the fourth tab
  {  
    //Multi touch XY, up to 5 x/y points
    if (addr.equals("/4/multixy/1")) //point 1 x and y values
    {
      MultiXYpad[0] = val; //y
      MultiXYpad[1] = theOscMessage.get(1).floatValue();  //x
    }

    if (addr.equals("/4/multixy/2")) //point 2 x and y values
    {
      MultiXYpad[2] = val;  //y
      MultiXYpad[3] = theOscMessage.get(1).floatValue();  //x
    }

    if (addr.equals("/4/multixy/3")) //point 3 x and y values
    {
      MultiXYpad[4] = val;
      MultiXYpad[5] = theOscMessage.get(1).floatValue();
    }

    if (addr.equals("/4/multixy/4")) //point 4 x and y values
    {
      MultiXYpad[6] = val;
      MultiXYpad[7] = theOscMessage.get(1).floatValue();
    }

    if (addr.equals("/4/multixy/5")) //point 5 x and y values
    {
      MultiXYpad[8] = val;
      MultiXYpad[9] = theOscMessage.get(1).floatValue();
    }


/* what multi faders look like
--------------- fader 4
--------------- 3
--------------- 2
--------------- 1


*/
    if (addr.equals("/4/multifader/1"))  //if multi fader 1 has been moved
    {
      // divide by 40 to get smaller value that fits this application
      MultiFader[0] = (val/40.0)+1; //How far apart should each horizontal location be spaced,
      yvalues = new float[int(WaveWidth/MultiFader[0])]; //height values for the wave
    }
    if (addr.equals("/4/multifader/2"))
    {
      MultiFader[1] = (val/400.0); //angular velocity, theta
    }
    if (addr.equals("/4/multifader/3"))
    {
      MultiFader[2] = val/2.5;  //amplitude -- Height of wave
    }
    if (addr.equals("/4/multifader/4"))
    {
      //MultiFader[3] = val = period -- How many pixels before the wave repeats
      dx = (TWO_PI / val) * MultiFader[0];  //dx Value for incrementing X, a function of period and xspacing
    }
    if (addr.equals("/4/multifader/5"))
    {
      MultiFader[4] = val;   //line thickness
    }
  }
  else if (TabStat[4]) //if we are on the fith tab
  {
    //read Accelerometer
    if (addr.equals("/accxyz"))
    {
      Accel[0] = val;  //x
      Accel[1] = theOscMessage.get(1).floatValue();  //y
      Accel[2] = theOscMessage.get(2).floatValue();  //z
    }

    //Accel Center
    if (addr.equals("/5/AccCenter"))
    {
      Accel[3] = val;
      for (int x = 0; x<3; x++)
      {
        Accel[x+4] = Accel[x]; //save current x, y, z values as center values
      }
    }
  }
}







Thursday, January 26, 2012

DIY Arduino Racing Wheel! (with Direct Port Manipulation)

The title says it all: I'm building a racing wheel for my computer. It will use the HDD encoders from a few posts back to measure how far the pedals have been pressed and the angle of the steering wheel, six buttons for a six speed shifter(with another button for toggling speed shift vs real shift, how this works depends on the game being played), and a few auxiliary buttons.

Since that i am now using multiple encoders and interrupts, i am now using direct port manipulation to read the pin states as it is many times faster than regular digitalReads.

I found a way better way to write the Arduino values to the virtual joystick; PPJoyCom. It is a add-on type program for the PPJoy virtual joystick driver. PPJoyCom waits for a initialization byte over the serial port from the Arduino(print a byte value 240+number of channels). then the encoder values range from 0 to 255(max value of a byte) and are sent to PPJoyCom and written to the joystick. way easier than a C# app. :) basic sample PPJoyCom arduino sketch below, along with encoder code.

basic flow:
1. arduino reads HDD encoders for position, button states etc. sends the data to the PPJoyCom program.
3. PPJoyCom program "writes" the values from the Arduino to the virtual joystick.
3. racing game reads the virtual joystick; X axis is turning, Y is throttle, Z is brake, buttons for shifting, menu keys...



#define Xpin  2
#define Ypin 3
#define Btn1Pin 11
#define Btn2Pin 12

byte Xaxis;
byte Yaxis;
byte Btn1 = 0;
byte Btn2 = 0;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  Btn1 = digitalRead(Btn1Pin);
  Btn2 = digitalRead(Btn2Pin);

  Xaxis = analogRead(Xpin) / 4;
  Yaxis = analogRead(Ypin) / 4;

  Serial.print(244,BYTE); // 240 + # of channels, 2 Buttons and 2 axis
  Serial.write(0);
  Serial.print(Btn1,BYTE);
  Serial.print(Btn2,BYTE); 
  Serial.print(x,BYTE);
  Serial.print(y,BYTE);
  delay(25);
}


#include <PinChangeInt.h>
#include <PinChangeIntConfig.h>

#define WheelPinA 2
#define WheelPinB 3

#define GasPinA 4
#define GasPinB 5

#define BrakePinA 6
#define BrakePinB 7

#define ClutchPinA 8
#define ClutchPinB 9

#define Gear1pin 10
#define Gear2pin 11
#define Gear3pin 12
#define Gear4pin 13
#define Gear5pin 14 //A0
#define Gear6pin 15 //A1

#define ShiftModepin 16 //A2, real shifting or speed shift
#define Aux1pin 17 //A3
#define Aux2pin 18 //A4
#define Aux3pin 19 //A5

volatile unsigned int Encoders[3] = {0}; //wheel, gas, brake, clutch

volatile boolean ButtonStates[3] = {0}; //current gear, shift mode, aux: 1, 2, or 3

void setup()
{
  //         76543210
  //(PIND & B00001000) check only the pin '1' (in this case digital 3) in this line for low or high, & means dont check any others
  //would be the same as: PIND & (1 << 3);

  //1111111 checking for true would be 128+64+32+16+8+4+2+1
  //(PIND & B00001000) checking for true would be 8

  //goes from left to right
  //       76543210
  DDRD =  B00000000; //sets 2 through 7 as inputs
  PORTD &= B11111100; //enable pullups on all but tx/rx

  //       ..13...8
  DDRB &= B11000000; //sets  through 13 as inputs excpet for crytsl pins
  PORTB &= B00111111; //enable pullups on all but crystal pins

   //     ..543210
  DDRC = B00000000; //sets analog 0 through 5 as inputs
  PORTC &= B00111111; //enable pullups on all but analog 6 an 7 (6 and 7 arent avaliable on atmega 328)

  PCattachInterrupt(WheelPinA, WheelTrigA, CHANGE);
  PCattachInterrupt(WheelPinB, WheelTrigB, CHANGE);

  PCattachInterrupt(GasPinA, GasTrigA, CHANGE);
  PCattachInterrupt(GasPinB, GasTrigB, CHANGE);

  PCattachInterrupt(BrakePinA, BrakeTrigA, CHANGE);
  PCattachInterrupt(BrakePinB, BrakeTrigB, CHANGE);

  PCattachInterrupt(ClutchPinA, ClutchTrigA, CHANGE);
  PCattachInterrupt(ClutchPinB, ClutchTrigB, CHANGE);

  PCattachInterrupt(Gear1pin, GearItrig, CHANGE);
  PCattachInterrupt(Gear2pin, GearIItrig, CHANGE);
  PCattachInterrupt(Gear3pin, GearIIItrig, CHANGE);
  PCattachInterrupt(Gear4pin, GearIVtrig, CHANGE);
  PCattachInterrupt(Gear5pin, GearVtrig, CHANGE);
  PCattachInterrupt(Gear6pin, GearVItrig, CHANGE);
  PCattachInterrupt(Aux1pin, AuxItrig, CHANGE);
  PCattachInterrupt(Aux2pin, AuxIItrig, CHANGE);
  PCattachInterrupt(Aux3pin, AuxIIItrig, CHANGE);
  PCattachInterrupt(ShiftModepin, ShiftTrig, CHANGE);

  Serial.begin (115200);
}

void loop()
{
  for(int x=0; x<4; x++)
  {
    Serial.print(Encoders[x]);
    Serial.print(","); //delimiter
  }
  for(int x=0; x<4; x++)
  {
    Serial.print(ButtonStates[x]);
    Serial.print(","); //delimiter
  }
  Serial.println();
}

void GearItrig()
{
  ButtonStates[0]=1;
}

void GearIItrig()
{
  ButtonStates[0]=2;
}

void GearIIItrig()
{
  ButtonStates[0]=3;
}

void GearIVtrig()
{
  ButtonStates[0]=4;
}

void GearVtrig()
{
  ButtonStates[0]=5;
}

void GearVItrig()
{
  ButtonStates[0]=6;
}

void ShiftTrig()
{
  ButtonStates[1]=~ButtonStates[1];
}

void AuxItrig()
{
  ButtonStates[2]=1;
}

void AuxIItrig()
{
  ButtonStates[2]=2;
}

void AuxIIItrig()
{
  ButtonStates[3]=3;
}

void WheelTrigA()
{
  // look for a low-to-high on pin A
  if ((PIND & B00000100) == 4)
  {
    // check pin B to see which way encoder is turning
    if ((PIND & B00001000) == 0)
    {
      Encoders[0]  -= 1;         // CW
    }
    else
    {
      Encoders[0]  += 1;         // CCW
    }
  }
  else   // must be a high-to-low edge on pin A                                    
  {
    // check pin B to see which way encoder is turning
    if ((PIND & B00001000) == 8)
    {
      Encoders[0]  -= 1;          // CW
    }
    else
    {
      Encoders[0]  += 1;          // CCW
    }
  }
  //Serial.println (Encoders[0], DEC);        
  // use for debugging - remember to comment out
}
void WheelTrigB()
{
  // look for a low-to-high on pin B
  if ((PIND & B00001000) == 8)
  {
    // check pin A to see which way encoder is turning
    if ((PIND & B00000100) == 4)
    {
      Encoders[0]  -= 1;         // CW
    }
    else
    {
      Encoders[0]  += 1;         // CCW
    }
  }
  // Look for a high-to-low on pin B
  else
  {
    // check pin B to see which way encoder is turning
    if ((PIND & B00000100) == 0)
    {
      Encoders[0]  -= 1;          // CW
    }
    else
    {
      Encoders[0]  += 1;          // CCW
    }
  }
}

void GasTrigA()
{
  // look for a low-to-high on pin A
  if ((PIND & B00010000) == 16)
  {
    // check pin B to see which way encoder is turning
    if ((PIND & B00100000) == 0)
    {
      Encoders[1]  -= 1;         // CW
    }
    else
    {
      Encoders[1]  += 1;         // CCW
    }
  }
  else   // must be a high-to-low edge on pin A                                    
  {
    // check pin B to see which way encoder is turning
    if ((PIND & B00100000) == 32)
    {
      Encoders[1]  -= 1;          // CW
    }
    else
    {
      Encoders[1]  += 1;          // CCW
    }
  }
  //Serial.println (Encoders[1], DEC);        
  // use for debugging - remember to comment out
}
void GasTrigB()
{
  // look for a low-to-high on pin B
  if ((PIND & B00100000) == 32)
  {
    // check pin A to see which way encoder is turning
    if ((PIND & B00010000) == 16)
    {
      Encoders[1]  -= 1;         // CW
    }
    else
    {
      Encoders[1]  += 1;         // CCW
    }
  }
  // Look for a high-to-low on pin B
  else
  {
    // check pin B to see which way encoder is turning
    if ((PIND & B00010000) == 0)
    {
      Encoders[1]  -= 1;          // CW
    }
    else
    {
      Encoders[1]  += 1;          // CCW
    }
  }
}

void BrakeTrigA()
{
  // look for a low-to-high on pin A
  if ((PIND & B01000000) == 64)
  {
    // check pin B to see which way encoder is turning
    if ((PIND & B10000000) == 0)
    {
      Encoders[2]  -= 1;         // CW
    }
    else
    {
      Encoders[2]  += 1;         // CCW
    }
  }
  else   // must be a high-to-low edge on pin A                                    
  {
    // check pin B to see which way encoder is turning
    if ((PIND & B10000000) == 128)
    {
      Encoders[2]  -= 1;          // CW
    }
    else
    {
      Encoders[2]  += 1;          // CCW
    }
  }
  //Serial.println (Encoders[2], DEC);        
  // use for debugging - remember to comment out
}

void BrakeTrigB()
{
  // look for a low-to-high on pin B
  if ((PIND & B10000000) == 128)
  {
    // check pin A to see which way encoder is turning
    if ((PIND & B01000000) == 64)
    {
      Encoders[2]  -= 1;         // CW
    }
    else
    {
      Encoders[2]  += 1;         // CCW
    }
  }
  // Look for a high-to-low on pin B
  else
  {
    // check pin B to see which way encoder is turning
    if ((PIND & B01000000) == 0)
    {
      Encoders[2]  -= 1;          // CW
    }
    else
    {
      Encoders[2]  += 1;          // CCW
    }
  }
}

void ClutchTrigA()
{
  // look for a low-to-high on pin A
  if ((PINB & B00000001) == 1)
  {
    // check pin B to see which way encoder is turning
    if ((PINB & B00000010) == 0)
    {
      Encoders[3]  -= 1;         // CW
    }
    else
    {
      Encoders[3]  += 1;         // CCW
    }
  }
  else   // must be a high-to-low edge on pin A                                    
  {
    // check pin B to see which way encoder is turning
    if ((PINB & B00000010) == 2)
    {
      Encoders[3]  -= 1;          // CW
    }
    else
    {
      Encoders[3]  += 1;          // CCW
    }
  }
  //Serial.println (Encoders[3], DEC);        
  // use for debugging - remember to comment out
}
void ClutchTrigB()
{
  // look for a low-to-high on pin B
  if ((PINB & B00000010) == 2)
  {
    // check pin A to see which way encoder is turning
    if ((PINB & B00000001) == 1)
    {
      Encoders[3]  -= 1;         // CW
    }
    else
    {
      Encoders[3]  += 1;         // CCW
    }
  }
  // Look for a high-to-low on pin B
  else
  {
    // check pin B to see which way encoder is turning
    if ((PINB & B00000001) == 0)
    {
      Encoders[3]  -= 1;          // CW
    }
    else
    {
      Encoders[3]  += 1;          // CCW
    }
  }
}

Monday, January 9, 2012

2-Axis Joystick sample from parallax!

It arrived to day, so i though i'd attempt to control my little TV IR remote controlled robot with it. Compared to the old DB15 joystick i was using before its precision way better and easier to code for!

I have one Arduino on the robot, and one connected to the joystick. I didn't have to change the robot code; just needed to program the Joystick Arduino to send the same IR codes as the remote but, this time using a joystick as input. I still prefer using the remote for two reasons: the Arduino IR LED has horrible range, and i still cant get each servo to have better speed control than stop, forward and reverse(so a joystick is kinda pointless :)). I have a little board with three IR LEDs from one of those cheap IR helicopters, so im gonna hook that up to a NPN or PNP(allows more current than the Arduino can deliver) and see if i can get better range.

Lastly, i though he way parallax laid out the pins on the joystick as slightly annoying. It has a GND(linked to other the other GND and pot), two separate output pins (one for each pot) and 5v for one pot on each side of the board. But the 5v was labeled and traced as two different lines, so i had to have two 5v wires(one for each pot). They could have just had one 5v on each side(like they do now), but link them together(or just one 5v pin...)! But, hey, I got it for free, why complain, right?!

Sunday, January 8, 2012

Sonar auto/self balancing Robot

Trying to find something to do with the IMU and control board, I thought it'd be fun to balance a robot not two wheels. Since i have no clue how to use PIDs or calibrate the sensors, so I just used the AHRS sketch provided by Pololu for balancing the bot. It outputs pitch, roll, yaw directions to serial; I thought i could use the pitch output to balance the robot. I planned to calibrate the sensors when it is level and then tip the bot vertical. Then, the pitch output would determine the movement of the motors(see the italicized below). But strangely, every time the I would move the bot into the upright position it would freeze and reset. its caused by the servo code changing the servo direction; still havent figured out why. (any help here on this issue?)

So, i used a SR04 sonar module instead with a really simple approach. i held the robot in its upright posisition on a flat surfaced and found the distance to the table. The motor control loops throught the following for every sonar read(every 30-50ms):
If the sonar value is too low(close to the table) turn the servos to compensate
If too far from table, then turn servos the other way.
If sonar value balanced, lock motors.  

I used my old Boe-bot frame so it wasn't very balanced in the first place(made out of steel), then i added the battery pack and that totally messed it up in terms of weight distribution(messy as heck too :)). I will soon make a custom acrylic or light wood frame for it so that it will be more balanced. i think if i want to drive it around(maybe with that TV remote :)) im going to have to make this control setup a little more complex.

Pics:

Sonar Module




Code:

//-----Robot Stuff------------------------------------------------//
int LmtrSpd=0;
int RmtrSpd=0;
#define LServoMtrPin 2
#define RServoMtrPin 3
#include <Servo.h>
Servo LServoMtr;
Servo RServoMtr;
//----------------------------------------------------------------//


//Sonar------------------------------------------------------------
float dist=0;
int trig=6;
int echo=7;
float SonarVal=0;

#define BlncPnt 850 //find this !!!
//----------------------------------------------------------------//


void setup()
{
  Serial.begin(9600);

  pinMode(echo, INPUT);
  pinMode(trig, OUTPUT);

  LServoMtr.attach(LServoMtrPin);
  LServoMtr.write(90);
  RServoMtr.attach(RServoMtrPin);
  RServoMtr.write(90);
}

void loop()
{
  //Sonar------------------------------------------------------------
  digitalWrite(trig, HIGH);
  delayMicroseconds(10);
  digitalWrite(trig, LOW);
  SonarVal= pulseIn(echo, HIGH);
  Serial.println(SonarVal);

  //---Balance-Test---------------------------------------------
  if(SonarVal > BlncPnt+25)
  {
    LServoMtr.write(0);
    RServoMtr.write(180);
  }
  else if(SonarVal < BlncPnt-25)
  {
    LServoMtr.write(180);
    RServoMtr.write(0);
  }
  else
  {
    LServoMtr.write(90);
    RServoMtr.write(90);
  }
  delay(45);
}

IR TV Remote Controlled Arduino Robot!

I put that TV remote to good use again! This time used it to control a two continuous rotation servo robot. I had previously recorded the IR codes for all the remote buttons, so I made the robot check the received IR code with one in a array and then moves the servos accordingly. I used the keypad so that i could use keys 2, 4, 6, and 8 for forward, left, right, and backwards. then 1, 3, 7, 9 for forward/left, forward/right, back/left, back/right.

But for some reason i cant get the servo to vary their speed(I can stop, reverse, and forward though). The servos take values 0 to 180; 0 being reverse, 90 stop, 180 forward. If i were to set a servo to 135 for say, the servo still goes like its set to 180. same if i set it to 91, which is only barely above stop...

Pics:
Remote I used.


This is the balance bot, but it was the same basic frame and controller...


Code:

#include <IRremote.h>
long int Buttons[49] = {
  10037463,  //0Play  //
  10059903,  //1Repeat
  10088463,  //2Stop  //
  10057863,  //3PrevTrack  //
  10082853,  //4NextTrack  //
  10029303,  //5Eject //
  10055823,  //6F_REV
  10049703,  //7F_FWD
  10078263,  //8Pause
  10084893,  //9Vol_Neg  //
  10031853,  //10Vol_Pos  //
  10051743,  //11Mute //
  10054293,  //12Subtitle
  10042053,  //13Up
  10058373,  //14Left
  10091013,  //15Right
  10074693,  //16Down
  10063983,  //17Num1
  10074183,  //18Num2
  10090503,  //19Num3
  10072143,  //20Num4
  10066023,  //21Num5
  10082343,  //22Num6
  10061943,  //23Num7
  10070103,  //24Num8
  10086423,  //25Num9
  10045623,  //26Num0
  10053783,  //27Plus_10
  10056333,  //28Info
  10044093,  //29Dimmer
  10037973,  //30Angle
  10027263,  //31PlayMode
  10080303,  //32Index
  10068063,  //33TimeSearch
  10043583,  //34Resume
  10076223,  //35Remain
  10070613,  //36Zoom
  10086933,  //37SlowFwd
  10072653,  //38Setup
  10084383,  //39Guide
  10039503,  //40Menu
  10047663,  //41GoBack
  10027773,  //42Clear
  10035423,  //43A_B
  10066533,  //44Pitch_b
  10033893,  //45Pitch_S
  10033383,  //46Sound
  10078773,  //47Audio
  4294967295};  //48BTNHOLD

//-----Robot Stuff-------------------------------------------------//
int LmtrSpd=0;
int RmtrSpd=0;
#define LServoMtrPin 2
#define RServoMtrPin 3
#include <Servo.h>
Servo LServoMtr;
Servo RServoMtr;
//----------------------------------------------------------------//

//-----IR-Stuff---------------------------------------------------//

int RECV_PIN = 11;
IRrecv irrecv(RECV_PIN);
decode_results results;
int BtnPressed = 0;
//----------------------------------------------------------------//
int Ttime = 0;
int Ftime = 0;
void setup()
{
  Serial.begin(9600);
  LServoMtr.attach(LServoMtrPin);
  LServoMtr.write(90);
  RServoMtr.attach(RServoMtrPin);
  RServoMtr.write(90);

  irrecv.enableIRIn(); // Start the receiver
}

void dump(decode_results *results)
{
  if (results->decode_type == NEC)
  {
    unsigned long store = (results->value);
    //Serial.println(store);
    for(int x=0; x<49; ++x)
    {
      if(store == Buttons[x])
      {
        BtnPressed = x;
       // Serial.println(x);
      }
    }
  }
}

void loop()
{
  Ttime = millis();
  if (irrecv.decode(&results))
  {
    dump(&results);
    if(BtnPressed == 18) //Fwd
    {
      Serial.println("FWD");
      LmtrSpd=180;
      RmtrSpd=0;
    }
    if(BtnPressed == 19) //Fwd Right
    {
      Serial.println("FWD+RIGHT");
      LmtrSpd=180;
      RmtrSpd=23;
    }
    if(BtnPressed == 17) //Fwd left
    {
      Serial.println("FWD+LEFT");
      LmtrSpd=113;
      RmtrSpd=0;
    }
    else if(BtnPressed == 24) //Back
    {
      Serial.println("BACK");
      LmtrSpd=0;
      RmtrSpd=180;
    }
    else if(BtnPressed == 25) //back right
    {
      Serial.println("BACK+RIGHT");
      LmtrSpd=0;
      RmtrSpd=113;
    }
    else if(BtnPressed == 23) //Back left
    {
      Serial.println("BACK+LEFT");
      LmtrSpd=23;
      RmtrSpd=180;
    }
    else if(BtnPressed == 20) //Left
    {
      Serial.println("LEFT");
      LmtrSpd=0;
      RmtrSpd=0;
    }
    else if(BtnPressed == 22) //Right
    {
      Serial.println("RIGHT");
      LmtrSpd=180;
      RmtrSpd=180;
    }
 
    else if(BtnPressed == 21) //stop
    {
      Serial.println("STOP");
      LmtrSpd=90;
      RmtrSpd=90;
    }

    LServoMtr.write(LmtrSpd);
    RServoMtr.write(RmtrSpd);

    irrecv.resume(); // Receive the next value
  }
  int Ftime = millis();
  delay(25-(Ftime-Ttime));
}

Thursday, January 5, 2012

Arduinio IR VLC Media Remote!

I figured the next step after controlling my TV with a Arduino, and sonar, would be to control VLC(a media player) on my computer with a old VCR remote.

The Arduino receive a IR signal from the remote, and if the decoded(to DEC) IR code matches one on the remote, it sends a number(assigned only to that button) over serial to the C# app. I haven't run into any misreading issues with the IR codes, so that's good. See my previous posts on IR remotes for how to get codes etc.

Then depending on what button is pressed, the C# app selects the right program and injects the key combo equivalent. Since I'm really new to C#, I used a window switching trick. Instead of attempting to directly interface with an application running, for example VLC media player, it switches to that window(similar to ALT+TAB) really fast, injects the keys and jumps back to the original window before the button was pressed; all of this with no apparent on screen artifacts.

C# code written in Visual Studio so it is more of a group Arduino files rather than just one source file. Download Arduino and C# code together HERE as a zip*.

*note: the file is .zip but I renamed it to '.zipp' to get around Google's file protection. Download it, then remove a 'p' from the end of the file name and extract! Below in comments, let me know if you have any issues.

PS See the moppy(musical floppy) post again for a quick start guide that should have everything you need to get started :)

HDD rotary encoder with interrupts!

I ripped apart a few of  the many old IDE HDD siting around for the BLDC motors that they contain to create a rotary encoder device.

something like:

The circuit building turned out pretty well. The HDD had a BLDC with 3 pins(whye windings) so i used the following scheme, but only the first two Op-Amps. One of the windings(pin) is a reference to the other two windings and used to determine direction. Signal 1 and 2 go to digital ports 2 and 3 on the Arduino for a 3 pin motor.
VCC(5v) and GND also need to be connected.
The coding side was, of course, an adventure; I first attempted it all on my own with interrupts(i understand them finally :)) and it worked OK, but could be improved a little. These are the waveforms of the two pins that the Arduino relies on to determine the direction the motor is turning.

Code that someone posted that is more efficient, but i don't understand: Reading Encoder Gray code 2 bit

Pics:
Encoder and needed LM324N IC. Also the joystick breakout cable is there.



My Code:

#define EncPinA 2
#define EncPinB 3
volatile unsigned int EncPos = 0;
void setup()
{
  pinMode(EncPinA, INPUT);
  digitalWrite(EncPinA, HIGH);

  pinMode(EncPinB, INPUT);
  digitalWrite(EncPinB, HIGH);

  attachInterrupt(0, EncA, CHANGE);  // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(1, EncB, CHANGE);  // encoder pin on interrupt 1 (pin 3)
  Serial.begin (115200);
}
void loop()
{
  //FREE for a MIDI interface(turntables?) or something!
}
void EncA(){
  // look for a low-to-high on pin A
  if (digitalRead(EncPinA) == HIGH) {
    // check pin B to see which way encoder is turning
    if (digitalRead(EncPinB) == LOW) {
      EncPos = EncPos - 1;         // CW
    }
    else {
      EncPos = EncPos + 1;         // CCW
    }
  }
  else   // must be a high-to-low edge on pin A                                    
  {
    // check pin B to see which way encoder is turning
    if (digitalRead(EncPinB) == HIGH) {
      EncPos = EncPos - 1;          // CW
    }
    else {
      EncPos = EncPos + 1;          // CCW
    }
  }
  Serial.println (EncPos, DEC);        
  // use for debugging - remember to comment out
}
void EncB(){
  // look for a low-to-high on pin B
  if (digitalRead(EncPinB) == HIGH) {
    // check pin A to see which way encoder is turning
    if (digitalRead(EncPinA) == HIGH) {
      EncPos = EncPos - 1;         // CW
    }
    else {
      EncPos = EncPos + 1;         // CCW
    }
  }
  // Look for a high-to-low on pin B
  else {
    // check pin B to see which way encoder is turning
    if (digitalRead(EncPinA) == LOW) {
      EncPos = EncPos - 1;          // CW
    }
    else {
      EncPos = EncPos + 1;          // CCW
    }
  }
}