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
    }
  }
}