How I made my programmable sticks

reference materials and general how-to information
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

How I made my programmable sticks

Post by noodalls »

Inspired by KoD of http://www.virtuafighter.com and helped by Slapper Joe of http://www.ozhadou.net, I went about building a programmable stick. The first prototype was with an old Hori Tekken 4 stick, the first round with two Mayflashes, and now my round 2 is with an Astro city panel. The features have been added and added as I went, but I thought it would probably be a good idea to write down how I made my most recent stick, in case anyone wants to follow.

Warning
A note however, from what I understand it is generally a bad idea to have the arduino connected to a console and a PC at the same time. For whatever reason the battery powering the arduino doesn’t seem to supply enough power to keep things going, so I keep mine connected via USB to power it, and I can also upload sketches repeatedly. If you chose to follow the steps in this guide, you take on responsibility for any damage incurred, whether to your computer, your game console or any other electronics or other involved.
I suspect an axis adapter might avoid this problem, if it still communicates wirelessly with the ps3.
My Materials
Astro-city panel with seimitsu parts
Extra buttons (4x24mm, 4x30mm)
Cthulhu MC x 2
Arduino Mega (duemilanove is enough for 1P, Mega for 2P)
LCD screen
LED lights
PS3 eye
Bare minimum
Arduino
Buttons/joystick
Other equipment
25W soldering iron
Flux
Wire
Header pins
Soldering stand


WIRING
The basis of this is straight forward. The arduino has the ability to write or to read through its pins. By connecting the pins to the joystick board, you are able to either read button inputs, or send outputs to the board and then on to the game console. In this way, you can very precisely control what you want your controller to do.



http://img18.imageshack.us/img18/6861/exampleimage.jpg

Here is the basis of it all. This setup involves the arduino, a Cthulhu MC and a button. There are four wires.
Red – connects the arduino to the Axis adapter. This is the connection that lets the arduino read what is being pressed, and also write to the Axis adapter, which will then go on to the console.
Yellow – connects the button to the Axis adapter. When the button is pressed, a signal is sent to the axis adapter and then on to the console.
White – ground wire. Completes the circuit so that when the button is pressed the circuit flows.
Black – ground wire to the arduino. Also needs to be there to let the arduino send its signals.

My way of connecting these was to braid the yellow and red wires together, then tin them, then solder them onto a header pin, and clamp this into the Cthulhu MC. The yellow wire is soldered onto one pin of the button, and the white wire onto the other. The white and black wire can be braided, tinned and soldered onto a header pin in the same fashion as the yellow and red. At the arduino end, I use header pins again, with the red and black wires tinned and soldered on. I try to cut very close to the end so there is little exposed wire not connected to a header pin to cause shorts.
For the rest of the buttons, the ground section can likely be daisy chained to reduce the amount of work needed. For an Axis adapter you might need to do it by groups, for my setup the astro-city came with groundwires already set up so I used those.
For the remainder of the yellow and red buttons it’s just a matter of repeating the braiding, tinning and soldering for each one.

Once you have connected a button or two, it’s probably worth uploading a simple sketch to test that everything is working. Something like when square is pressed also press triangle would test both the read and write functions fairly easily.
Then once everything is connected, you can program your full sketch, upload that and start doing whatever it is you want to do.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

//This is the basic sketch for the programmable stick. This is uploaded to the arduino, and from there will send outputs to the MC Cthulhu and on to the PS3, allowing you to send commands
//to the PS3.
// It also has the ability to read what is being pressed on the controller, through the Cthulhu and report this to the Arduino.
//In its current form, it has two sticks attached (four directions), with eight buttons each, as well home, select and start buttons. All of these are available to be send inputs or outputs.
//There are also eight LEDs attached, on pins 54-61. These currently serve very basic functions, but could be set up to do more advanced features.
//There is also an LCD screen connected. This allows for display of the variables beings used.
//The sketch is frame accurate
//At the moment, the way the sketch works is that when you press and release the SELECT button, a value N will be reset to zero (N=0). Then, every time 16.683msec passes (1 frame at 60hz)
//N will be increased by one. So after 1 second, N should equal 60.
//This can then be used to set up the timing for commands. Lets say you want to do something on frame 20 (we will refer to this as F20 from now on). So, you would need to tell the arduino
// something like "If N is 20, press button X."
//Remember also, that the arduino is just a dumb machine, so you also need to remind it to stop pressing the button (it would be easy to program it to release the button after a frame every
//time you tell it to do something, but this could make it harder for holding buttons for logner.

//It's probably worth mentioning the syntax used in this sketch for sending commands to the arduino. If you input the command "press(20,25,A,B); that will press buttons A+B on F20, and release
//them on F25. It used to be a lot longer, this is I feel the ideal balance between typing too much and not having enough control.

//Remember, when you press and release SELECT, it will reset the N value to zero. Therefore, every time you press and release SELECT, it will wait 20 frames and then press A+B for a duration of
// 5 frames.

//Here are some examples of other common moves. Once you get the hang of them, it's not to hard to work out what to type.
//Fireball - press(1,3,D); press(2,4,R); press(3,4,A);
//Shoryuken - press(1,2,R); press(2,4,D); press(3,4,R,A);
//f,F+2 in Tekken - press(1,2,R); press(3,5,R); press(4,5,B);

//You will have probably already worked out, each of the buttons is referred to by a specific name, which is defined at the start of the sketch. My way of doing this is to use capital
//letters for player 1 and lower case for player 2. This means that I can use the same letters in duplicate, but have it obvious to whom it refers.

// The directions are referred to as U,D,L,R. The buttons are
// A B C P
// X Y Z Q
//Why not A,B,C,D? Well, because D is already used by a direction. HOM, SEL and STA are fairly obvious as well.

//Now is probably the right time to introduce the four variables, I,J,i and j. Let's look at an example to explain why we need variables. Let's say we want to do a simple link with Ryu,
//f+HP,d+HP
//You could try inputting
//press(1,2,R,C); press(3,5,D,C); However, as you would quickly appreciate, the timing would be all wrong. This would do the d+HP motion far too soon,
//and the motion would be complete long before the input window occurs. Now, to work out the timing there are a few things you can do. You could retype the sketch, increasing the timing by one
// or a few frames each time, until you find the correct timing. This is fairly labourious, especially if you're going to be making a combo with multiple parts (and other than James Chen,
//no one has made a good 2HIT combo movie.
//You could record the game (preferably at 60fps) and estimate when the window will occur, based on animations finishing. This works quite well for Tekken, as its hit analysis feature
//changes colour when the recovery is finished, making it quite obvious when to input the next command. There is however an easier way.
//What if we could keep f+HP as a constant, so that every time you press and release SELECT it will do it on the first frame, but then vary the timing on the f+HP portion.
//This is exactly how this sketch is set up to work. We assign d+HP a variable I, and instead of press(3,5,D,C); we just input it as "press(I,I+1,D,C);" This means that on frame I,
// it will do the listed command, and after uploading we are free to alter the value of I. For example, if we run this sketch with I equal to 40 it will still be too early, and if
//we run the sketch with I=80 it will be far too late. I believe I=63 or thereabouts is the correct value to have this combo work.

//Now, if we upload the sketch with a variable I, but then have no way to manipulate it once the sketch is uploaded, it's no better than manually editing the sketch each time.

//The way I have it set up at the moment is to have four different variables (I,J,i,j) and control each one with a button, P,Q,p,q respectively. While holding button P, it will increase the
//value of P depending in which direction you press the stick. R will increase it by 1, U will increase it by 5, L will decrease it by 1, D will decrease it by 5, l will increase it by 10,
// u will increase it by 20 and r will increase it by 50. d will decrease it by 100. There are two additional features, holding sel and pressing c will make all the value 0, and holding sel
//and pressing z will make J one above I, i one above J and j one above i (so that they fire off one frame after the other). You can always set the values to less than zero to make
// the sketch ignore them (as it only starts counting at 0.)

//There are lots of other functions that can be programmed in as well. A control capture is not too hard to implement. The simplest way to do this is to have it read when each button
//is pressed and released, nad output this to the serial minitor. If you do this in the same syntax as that used in the normal sketch to instruct the arduino to do commands,
//then it is a simple matter of cutting and pasting into the sketch once you find what you want.

//You can also have the arduino randomly choose between different options to facilitate defense training or throw break training.

// You can also use it to practice timing, so that it will feed back (LCD or serial)
//when inputs are done at the right time (e.g. Just frames.) You can combine this with throw break training as well.

//A quick note on timing. Initially I had the sketch set up so that the arduino would do a move, then I would use the delay function to wait the required number of msec, and then increase
//N by 1. However, the problem with this is that if you want to do a lot on one frame, potentially it can take a few msec, and so if you take say 3msec to do an action, and then delay the
//normal 16msec, you will end up running slow.

//Instead of this, the way it is set up now is to have an interrupt set up, so that it should stop whatever you are doing when time is up, and advance on to the next frame.
//This seems like the more accurate way of doing it.


//What follows is the main sketch itself, with some notes to the side to explain what is going on.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

#include "TimerOne.h" //Included sketch for timing
#include <LiquidCrystal.h> //Included for LCD screen
LiquidCrystal lcd(12,11,8,5,4,3,2); //sets up the pins for the LCD. Note, cannot use 9 or 10 as these are used by the interrupt timer
int backLight=13;


int F=16683; //Timing for one frame. Change if using PAL instead of NTSC.
int N=0; //Starts the frame counter at 0. When you upload it will start at zero and then begin counting up. After that, releasing SELECT will again reset N to 0.

int ADV=0; //Used as the trigger to move to the next frame once 16666msec have passed
int I=10;int J=20;int i=25; int j=25; //Set some inital values for the variables
int V;

int u=22;int d=24;int l=26;int r=28;int a=30;int b=32;int c=34;int x=36;int y=38;int z=40; int p=42; int q=44; int hom=48; int sta=46; int sel=50;
int U=23; int D=25;int L=27; int R=29; int A=31; int B=33; int C=35; int X=37; int Y=39; int Z=41; int P=43; int Q=45; int HOM=49; int STA=47; int SEL=51;
//Define which pins the buttons are connected to on the arduino



int LED1=54; int LED2=55; int LED3=56; int LED4=57;int LED5=58;int LED6=59;int LED7=60;int LED8=61;

int AState;int BState;int CState;int XState;int YState;int ZState;int UState;int RState;int LState;int DState;int PState;int QState;
int STAState; int SELState; int HOMState;
int aState;int bState;int cState;int xState;int yState;int zState;int uState;int rState;int lState;int dState;int pState; int qState;
int staState; int selState; int homState;

unsigned long time;

void setup(){


Timer1.initialize(F); //This is the timer function from timerone. Every 16666 msec (or whatever F is set to) one frame will tick over.
Timer1.pwm(9,900);
Timer1.attachInterrupt(FrameAdv);

Serial.begin(9600);

pinMode(backLight,OUTPUT); //Turns on the backlight for the LCD
digitalWrite(backLight,HIGH);//Write frame data testing on the LCD
lcd.begin(20, 4);
lcd.clear();


aState=digitalRead(a);// do these have to be done in setup?
bState=digitalRead(b);
cState=digitalRead(c);
xState=digitalRead(x);
yState=digitalRead(y);
zState=digitalRead(z);
uState=digitalRead(u);
dState=digitalRead(d);
lState=digitalRead(l);
rState=digitalRead(r);
pState=digitalRead(p);
qState=digitalRead(q);
AState=digitalRead(A);
BState=digitalRead(B);
CState=digitalRead(C);
XState=digitalRead(X);
YState=digitalRead(Y);
ZState=digitalRead(Z);
UState=digitalRead(U);
DState=digitalRead(D);
LState=digitalRead(L);
RState=digitalRead(R);
PState=digitalRead(P);
QState=digitalRead(Q);

pinMode(a,INPUT); // Sets all the arduinos pins to initially read inputs and not output unless instructed.
pinMode(b,INPUT); //* Can this be done with a <<statement
pinMode(c,INPUT);
pinMode(x,INPUT);
pinMode(y,INPUT);
pinMode(z,INPUT);
pinMode(r,INPUT);
pinMode(l,INPUT);
pinMode(d,INPUT);
pinMode(u,INPUT);
pinMode(p,INPUT);
pinMode(q,INPUT);
pinMode(sel,INPUT);
pinMode(sta,INPUT);
pinMode(hom,INPUT);

pinMode(A,INPUT);
pinMode(B,INPUT);
pinMode(C,INPUT);
pinMode(X,INPUT);
pinMode(Y,INPUT);
pinMode(Z,INPUT);
pinMode(R,INPUT);
pinMode(L,INPUT);
pinMode(D,INPUT);
pinMode(U,INPUT);
pinMode(P,INPUT);
pinMode(Q,INPUT);
pinMode(SEL,INPUT);
pinMode(STA,INPUT);
pinMode(HOM,INPUT);
pinMode(LED1,OUTPUT);
pinMode(LED2,OUTPUT);
pinMode(LED3,OUTPUT);
pinMode(LED4,OUTPUT);
}







void FrameAdv(){ADV=1;}//When the timer ticks over, set advance integer to 1 so that the next frame starts

void press (int start, int finish, int m1, int m2, int m3, int m4) //This is the function that makes it easy to input commands, instead of having to
{if(N==start){pinMode(m1,OUTPUT);digitalWrite(m1,LOW);} //Type everything each time.
if(N==finish){digitalWrite(m1,HIGH);pinMode(m1,INPUT);}//The syntax is press(frame to start on, frame to stop on, button/direction 1,2,3,4)
if(N==start){pinMode(m2,OUTPUT);digitalWrite(m2,LOW);} // If you need more than 4 buttons/directions pressed on a set frame, just set two press functions.
if(N==finish){digitalWrite(m2,HIGH);pinMode(m2,INPUT);}
if(N==start){pinMode(m3,OUTPUT);digitalWrite(m3,LOW);}
if(N==finish){digitalWrite(m3,HIGH);pinMode(m3,INPUT);}
if(N==start){pinMode(m4,OUTPUT);digitalWrite(m4,LOW);}
if(N==finish){digitalWrite(m4,HIGH);pinMode(m4,INPUT);}}
void press (int start, int finish, int m1, int m2, int m3)
{if(N==start){pinMode(m1,OUTPUT);digitalWrite(m1,LOW);}
if(N==finish){digitalWrite(m1,HIGH);pinMode(m1,INPUT);}
if(N==start){pinMode(m2,OUTPUT);digitalWrite(m2,LOW);}
if(N==finish){digitalWrite(m2,HIGH);pinMode(m2,INPUT);}
if(N==start){pinMode(m3,OUTPUT);digitalWrite(m3,LOW);}
if(N==finish){digitalWrite(m3,HIGH);pinMode(m3,INPUT);}}
void press (int start, int finish, int m1, int m2)
{if(N==start){pinMode(m1,OUTPUT);digitalWrite(m1,LOW);}
if(N==finish){digitalWrite(m1,HIGH);pinMode(m1,INPUT);}
if(N==start){pinMode(m2,OUTPUT);digitalWrite(m2,LOW);}
if(N==finish){digitalWrite(m2,HIGH);pinMode(m2,INPUT);}}
void press (int start, int finish, int m1)
{if(N==start){pinMode(m1,OUTPUT);digitalWrite(m1,LOW);}
if(N==finish){digitalWrite(m1,HIGH);pinMode(m1,INPUT);}}

int check(int V1, int V2){
if( digitalRead(V2)==LOW){digitalWrite(V1, HIGH);
if(digitalRead(U)==LOW){if(digitalRead(U)!=UState){V=V+5;}} //
if(digitalRead(L)==LOW){if(digitalRead(L)!=LState){V=V-1;}}
if(digitalRead(D)==LOW){if(digitalRead(D)!=DState){V=V-5;}}
if(digitalRead(R)==LOW){if(digitalRead(R)!=RState){V=V+1;}}
if(digitalRead(u)==LOW){if(digitalRead(u)!=uState){V=V+20;}}
if(digitalRead(l)==LOW){if(digitalRead(l)!=lState){V=V+10;}}
if(digitalRead(d)==LOW){if(digitalRead(d)!=dState){V=V-100;}}
if(digitalRead(r)==LOW){if(digitalRead(r)!=rState){V=V+50;}}
}}


void loop(){ // The start of the working part of the script
if (ADV==1){

//press(1,2,D); press(3,4,A); press(26,27,R);press(I,I+1,A);//press(I,I+1,A);
//press(1,2,R,U,B); press(I,I+1,A);
press(1,2,L); press(1,4,D);press(3,5,R); press(6,8,D,L); press(7,8,B,X);
press(I,63,d);


check(LED1, P); I=I+V; V=0; //pressing L decreases I by 1 and pressing D decreases I by 5.
check(LED2, Q); J=J+V; V=0;
check(LED3, p); i=i+V; V=0;
check(LED4, q); j=j+V; V=0;
if(digitalRead(SEL)==HIGH){if(digitalRead(SEL)!=SELState){N=0;}digitalWrite(LED1,LOW);digitalWrite(LED2,LOW);digitalWrite(LED3,LOW);digitalWrite(LED4,LOW); }


if(digitalRead(sel)==LOW){lcd.clear();
if(digitalRead(a)==LOW){if(digitalRead(a)!=aState){l=28;r=26;L=29;R=27;}}
if(digitalRead(b)==LOW){if(digitalRead(b)!=bState){r=28;l=26;L=27;R=29;}}
if(digitalRead(x)==LOW){if(digitalRead(x)!=xState){u=24;d=22;U=25;D=23;}}
if(digitalRead(y)==LOW){if(digitalRead(y)!=yState){u=22;d=24;U=23;D=25;}}
if(digitalRead(c)==LOW){if(digitalRead(c)!=cState){I=0;i=0;J=0;j=0;}}
if(digitalRead(z)==LOW){if(digitalRead(z)!=cState){J=I+1;i=I+2;j=I+3;}}}
// int r=28;int l=26;int L=27; int R=29;int u=22;int d=24;int U=23; int D=25;




UState=digitalRead(U);DState=digitalRead(D);LState=digitalRead(L);RState=digitalRead(R);
PState=digitalRead(P);QState=digitalRead(Q);AState=digitalRead(A); //This makes the arduino acknowledge the current state of the buttons.
uState=digitalRead(u);dState=digitalRead(d);lState=digitalRead(l);rState=digitalRead(r);
pState=digitalRead(p);qState=digitalRead(q);aState=digitalRead(a);bState=digitalRead(b);cState=digitalRead(c);xState=digitalRead(x);yState=digitalRead(y); zState=digitalRead(z);
SELState=digitalRead(SEL);selState=digitalRead(sel);



lcd.clear();
lcd.setCursor(0,0);lcd.print("I=");lcd.print(I);
lcd.setCursor(0,1);lcd.print("J=");lcd.print(J);
lcd.setCursor(0,2);lcd.print("i=");lcd.print(i);
lcd.setCursor(0,3);lcd.print("j=");lcd.print(j);
lcd.setCursor(10,3);lcd.print("N=");lcd.print(N);

if(l==26){lcd.setCursor(10,0);lcd.print("1P2P");}
if(l==28){lcd.setCursor(10,0);lcd.print("2P1P");}
if(u==22){lcd.setCursor(15,0);lcd.print("Norm");}
if(u==24){lcd.setCursor(15,0);lcd.print("Inve");}
ADV=0;
N=N+1;



}
}
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

Image
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: How I made my programmable sticks

Post by Rufus »

Thank you for posting your work.

Code tags and indentation make stuff easier to read.
People generally use i,j, and k for loop variables - assigning them to something else makes your code a little harder to read.

If you want to comment on a function, you might want to do it more like this:

Code: Select all

//This is a description of the function
//You can read it before you look at the code
//And the text aligns because there's nothing to the left.
int foo(int a) {
...
}
or

Code: Select all


int foo () {      //This text is alinged
   int i,j;       //and not mixed in with the text
   char *string;  //so it's easy to read
If there's a single statement inside the if, I don't think you need braces...

Code: Select all

void press (int start, int finish, int m1, int m2, int m3, int m4) {
   if(N==start)
      pinMode(m1,OUTPUT);digitalWrite(m1,LOW);
   if(N==finish)
      digitalWrite(m1,HIGH);pinMode(m1,INPUT);
   if(N==start)
      pinMode(m2,OUTPUT);digitalWrite(m2,LOW);
   if(N==finish)
      digitalWrite(m2,HIGH);pinMode(m2,INPUT);
   if(N==start)
      pinMode(m3,OUTPUT);digitalWrite(m3,LOW);
   if(N==finish)
      digitalWrite(m3,HIGH);pinMode(m3,INPUT);
   if(N==start)
      pinMode(m4,OUTPUT);digitalWrite(m4,LOW);
   if(N==finish)
      digitalWrite(m4,HIGH);pinMode(m4,INPUT);
}
void press (int start, int finish, int m1, int m2, int m3) {
   if(N==start)
      pinMode(m1,OUTPUT);digitalWrite(m1,LOW);
   if(N==finish)
      digitalWrite(m1,HIGH);pinMode(m1,INPUT);
   if(N==start)
      pinMode(m2,OUTPUT);digitalWrite(m2,LOW);
   if(N==finish)
      digitalWrite(m2,HIGH);pinMode(m2,INPUT);
   if(N==start)
      pinMode(m3,OUTPUT);digitalWrite(m3,LOW);
   if(N==finish)
      digitalWrite(m3,HIGH);pinMode(m3,INPUT);
}
void press (int start, int finish, int m1, int m2)
   if(N==start)
      pinMode(m1,OUTPUT);digitalWrite(m1,LOW);
   if(N==finish)
      digitalWrite(m1,HIGH);pinMode(m1,INPUT);
   if(N==start)
      pinMode(m2,OUTPUT);digitalWrite(m2,LOW);
   if(N==finish)
      digitalWrite(m2,HIGH);pinMode(m2,INPUT);
}
void press (int start, int finish, int m1) {
   if(N==start)
      pinMode(m1,OUTPUT);digitalWrite(m1,LOW);
   if(N==finish)
      digitalWrite(m1,HIGH);pinMode(m1,INPUT);
}
I'm not sure if you want to get into it since there's a much better way to go, but this all could probably be done with a function that handles a variable number of arguments, something like:

Code: Select all

#include<stdarg.h>
...
void press (int argcount,...) {
   int i,tmp,start,finish;
   va_list args;

   va_start(args,argcount);
   if (argc<3)
     return;
   if (finish<=start)
     return;
   start=va_arg(args,int);
   finish=va_arg(args,int);
   if(N==start) {
         pinMode(args[i],OUTPUT);digitalWrite(args[i],LOW);
      }
   }
   if(N==finish) {
      for(i=2;i<;argcount;i++) {
         digitalWrite(args[i],HIGH);pinMode(args[i],INPUT);
      }
   }
}
As you're probably aware, having something like:

Code: Select all

  press(10,20,u);
  press(13,17,u);
May not work in the way you expect.

My inclination is to put stuff like pin assignment and state into arrays. Something more like...

Code: Select all

int pins[]={22,23,24,25,26,...,51};
int state[]={0....0};

int u=0;
int U=1;
...
int sel=28;
int SEL=29;
Then you can do initialization more easily and cleanly:

Code: Select all

void setup () {
   int k;
...
   lcd.clear();

   for(k=0;k<30;k++) {
      state[k]=digitalRead(pins[k]);
      pinMode(pins[k],INPUT);
   }
   pinMode(LED1,OUTPUT);
   pinMode(LED2,OUTPUT);
   pinMode(LED3,OUTPUT);
   pinMode(LED4,OUTPUT);
}
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: How I made my programmable sticks

Post by Rufus »

Regarding the main loop:

What you're doing right now is quite inefficient because you've got the program reading the entire script every time it goes through the loop. In addition, as your script gets larger, you're going to have more and more calls to 'press', each of which has significant overhead.

If you store your script as an array instead of as function calls, then you can simply leave a pointer at the current place in the script, and start reading from there, and then stop when you get past 'now'. As long as you use relative timing information, it should be pretty easy to support your desire for run-time delay modification. (In fact, this approach makes things like controller-based combo editing and support for multiple combos much easier and cleaner if you want to support it in the future.)

For example, let's say that I have a 'language' of 256 commands for example something like:
Commands 0-29 set pins on the stick.
Commands 31-61 unset pins on the stick.
Commands 64-127 wait for 1-64 frames to expire before going on to the next command.

Command 254 puts a message on the LED and waits for user input
Command 255 ends the script.

then I can have:

Code: Select all

   unsigned char script[256];
   int script_pointer;
...
   press(...);
   press(...);
...
   preprocess(script);
   loop() {
      script_pointer=execute(script_pointer,script);
   }
onReload
Posts: 1716
Joined: Wed Apr 22, 2009 7:00 am
Location: NJ, USA

Re: How I made my programmable sticks

Post by onReload »

I know you guys are talking real technical shit, but that giant Japanese-style-colored box looks like a lot of fun.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

Wow, arrays are great, probably something I've wanted for a long time. Implemented it successfully for the first time tonight, am going to go back and have a long look at this. Thanks.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: How I made my programmable sticks

Post by Rufus »

noodalls wrote:Wow, arrays are great, probably something I've wanted for a long time. Implemented it successfully for the first time tonight, am going to go back and have a long look at this.
Be careful... Next I'll have you talking about pointers and linked list next, and that way lies madness. :wink:

Honestly, good on you for taking the plunge. This can be very hard when you're getting started.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

Thanks for that, and on my birthday too no less.

Played with arrays for a good hour tonight, came up with the following sketch. Basically, it displays a row of ones on the LCD screen, and if you press a button or move the stick they change to zeros. It forms the basis of a command capture type ability anyway. Also displays time taken each frame to run the sketch, time per frame, number of dropped frames, and total frames.

#include "TimerOne.h" //Included sketch for timing
#include <LiquidCrystal.h> //Included for LCD screen
LiquidCrystal lcd(12,11,8,5,4,3,2); //sets up the pins for the LCD. Note, cannot use 9 or 10 as these are used by the interrupt timer
int backLight=13;
int time1; int time2; int time3;
int lag=-2; //This works as a counter for dropped frames (where the sketch took longer than one frame. Seems that with uploading it there are two dropped frames
// the start, so will start at -2 and then anything more than zero is a real dropped frame.
//int F=16683; //Timing for one frame. Change if using PAL instead of NTSC.
int F=19153; //as per using micros between frames. Note, this was set to 16683, but on testing with the arduino displaying microseconds passed per frame
//I came to the following value. I suspect the timing code for the timerone function isn't perfect. Still need to test this in real life.

int N=0; //Starts the frame counter at 0. When you upload it will start at zero and then begin counting up. After that, releasing SELECT will again reset N to 0.

int ADV=0; //Used as the trigger to move to the next frame once 16666msec have passed
int V1=10;int V2=20;int V3=25; int V4=25; //Set some inital values for the variables
int V;

//int u=22;int d=24;int l=26;int r=28;int a=30;int b=32;int c=34;int x=36;int y=38;int z=40; int p=42; int q=44; int hom=48; int sta=46; int sel=50;
//int U=23; int D=25;int L=27; int R=29; int A=31; int B=33; int C=35; int X=37; int Y=39; int Z=41; int P=43; int Q=45; int HOM=49; int STA=47; int SEL=51;
//Define which pins the buttons are connected to on the arduino

//int LED1=54; int LED2=55; int LED3=56; int LED4=57;int LED5=58;int LED6=59;int LED7=60;int LED8=61;

int pins[]={22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51};


int u=0;int d=1;int l=2; int r=3; int a=4;int b=5;int c=6;int x=7;int y=8;int z=9;int p=10;int q=11;int sta=12;int hom=13;int sel=14;
int U=15;int D=16;int L=17; int R=18; int A=19;int B=20;int C=21; int X=22; int Y=23; int Z=24;int P=25;int Q=26;int STA=27;int HOM=28;int SEL=29;
//These are here so that I can refer to directions/buttons by pins instead of needing to remember where they're plugged in.
int state[]={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int k;
unsigned long time;
int leds[]={54,55,56,57,58,59,60,61};

void setup(){


Timer1.initialize(F); //This is the timer function from timerone. Every 16666 msec (or whatever F is set to) one frame will tick over.
Timer1.pwm(9,900);
Timer1.attachInterrupt(FrameAdv);

Serial.begin(9600);

pinMode(backLight,OUTPUT); //Turns on the backlight for the LCD
digitalWrite(backLight,HIGH); //Write frame data testing on the LCD
lcd.begin(20, 4);
lcd.clear();

for(k=0;k<30;k++) {state[k]=digitalRead(pins[k]);pinMode(pins[k],INPUT);}

for(k=0;k<8;k++){pinMode(leds[k],OUTPUT);}
}

void FrameAdv(){ADV=1;} //When the timer ticks over, set advance integer to 1 so that the next frame starts

void press (int start, int finish, int m1)
{if(N==start){pinMode(m1,OUTPUT);digitalWrite(m1,LOW);}
if(N==finish){digitalWrite(m1,HIGH);pinMode(m1,INPUT);}}

void loop(){ // The start of the working part of the script
if (ADV==1){
lcd.clear();
time1=micros();
lcd.setCursor(0,2);lcd.print(time1-time3); //This is here to tell me how long a frame is taking.
if((time1-time3)>17000){lag++;}

time3=micros();
for(k=0;k<30;k++) {state[k]=digitalRead(pins[k]);pinMode(pins[k],INPUT);}

for(k=0;k<10;k++){lcd.setCursor(k,0);lcd.print(state[k]);} //This displays on the LCD screen which buttons are being pressed. Sadly, to do 1P and 2P takes about
//18000msec, which is more than a frame. 1P only takes 10msec, which is ok.
if(digitalRead(pins[A])==0){N=0;} //Reset the frame counter to N=0 when A is pressed.
time2=micros();
lcd.setCursor(0,1);lcd.print(time2-time1); //And this is here to tell me how long the running of each frame takes.
lcd.setCursor(0,3);lcd.print(lag); lcd.setCursor(10,3);lcd.print(N);
ADV=0;
N=N+1;

}
}
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: How I made my programmable sticks

Post by Rufus »

noodalls wrote:Thanks for that, and on my birthday too no less.

Played with arrays for a good hour tonight, came up with the following sketch. Basically, it displays a row of ones on the LCD screen, and if you press a button or move the stick they change to zeros. It forms the basis of a command capture type ability anyway. Also displays time taken each frame to run the sketch, time per frame, number of dropped frames, and total frames.
How about using some kind of indenting?

Code: Select all

#include "TimerOne.h"                   //Included sketch for timing
#include <LiquidCrystal.h>              //Included for LCD screen

LiquidCrystal lcd(12,11,8,5,4,3,2)
//sets up the pins for the LCD. Note, cannot use 9 or 10 as these are used by the interrupt timer;

int backLight=13;
int time1,time2,time3;
int lag=-2;
//This works as a counter for dropped frames (where the sketch took longer
//than one frame. Seems that with uploading it there are two dropped frames
// the start, so will start at -2 and then anything more than zero is a real
//dropped frame.

//Timing for one frame. Change if using PAL instead of NTSC.
//int F=16683;
int F=19153;
//as per using micros between frames. Note, this was set to 16683, but
//on testing with the arduino displaying microseconds passed per frame
//I came to the following value. I suspect the timing code for the timerone
//function isn't perfect. Still need to test this in real life.

int N=0;
//Starts the frame counter at 0. When you upload it will start at zero and
//then begin counting up. After that, releasing SELECT will again reset N to 0.

int ADV=0;
//Used as the trigger to move to the next frame once 16666msec have passed

int V1=10;
int V2=20;
int V3=25;
int V4=25;
//Set some inital values for the variablesnt V;

//int u=22;int d=24;int l=26;int r=28;int a=30;int b=32;int c=34;int x=36;int y=38;int z=40; int p=42; int q=44;
//int hom=48; int sta=46; int sel=50;
//int U=23; int D=25;int L=27; int R=29; int A=31; int B=33; int C=35; int X=37; int Y=39; int Z=41; int P=43; int
//Q=45; int HOM=49; int STA=47; int SEL=51;
//Define which pins the buttons are connected to on the arduino

//int LED1=54; int LED2=55; int LED3=56; int LED4=57;int LED5=58;int LED6=59;int LED7=60;int LED8=61;

int  pins[]={22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51};

int u=0;int d=1;int l=2; int r=3; int a=4;int b=5;int c=6;int x=7;int y=8;int z=9;int p=10;int q=11;int sta=12;int hom=13;int sel=14;
int U=15;int D=16;int L=17; int R=18; int A=19;int B=20;int C=21; int X=22; int Y=23; int Z=24;int P=25;int Q=26;int STA=27;
int HOM=28;int SEL=29;
//These are here so that I can refer to directions/buttons by pins[U] instead of needing to remember where they're plugged in.

int state[]={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

int k;
unsigned long time;

int leds[]={54,55,56,57,58,59,60,61};

void setup() {

   Timer1.initialize(F);
//This is the timer function from timerone. Every 16666 msec (or whatever F is
//set to) one frame will tick over.
   Timer1.pwm(9,900);
   Timer1.attachInterrupt(FrameAdv);

   Serial.begin(9600);

   pinMode(backLight,OUTPUT);             //Turns on the backlight for the LCD
   digitalWrite(backLight,HIGH);          //Write frame data testing on the LCD
   lcd.begin(20, 4);
   lcd.clear();

   for(k=0;k<30;k++) {
      state[k]=digitalRead(pins[k]);
      pinMode(pins[k],INPUT);
   }

   for(k=0;k<8;k++) {
      pinMode(leds[k],OUTPUT);
   }
}

//When the timer ticks over, set advance integer to 1 so that the next frame starts
void FrameAdv() {
   ADV=1;
}

void press (int start, int finish, int m1) {
   if(N==start) {
      pinMode(m1,OUTPUT);
      digitalWrite(m1,LOW);
   }

   if(N==finish) {
      digitalWrite(m1,HIGH);
      pinMode(m1,INPUT);
   }
}

// The start of the working part of the script
void loop() {
   if (ADV==1) {
      lcd.clear();
      time1=micros();
      lcd.setCursor(0,2);lcd.print(time1-time3);
//This is here to tell me how long a frame is taking.
      if((time1-time3)>17000) {
         lag++;
      }
      time3=micros();
      for(k=0;k<30;k++) {
         state[k]=digitalRead(pins[k]);
         pinMode(pins[k],INPUT);
      }
      for(k=0;k<10;k++) {
         lcd.setCursor(k,0);
         lcd.print(state[k]);
      }
//This displays on the LCD screen which buttons are being pressed.
//Sadly, to do 1P and 2P takes about 18000msec, which is more than a
//frame. 1P only takes 10msec, which is ok.
      if(digitalRead(pins[A])==0) {
         N=0;
      }
//Reset the frame counter to N=0 when A is pressed.
      time2=micros();
      lcd.setCursor(0,1);
      lcd.print(time2-time1);
//And this is here to tell me how long the running of each frame takes.
      lcd.setCursor(0,3);lcd.print(lag); lcd.setCursor(10,3);lcd.print(N);
      ADV=0;
      N=N+1;
   }
}
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: How I made my programmable sticks

Post by Rufus »

int lag=-2;
//This works as a counter for dropped frames (where the sketch took longer
//than one frame. Seems that with uploading it there are two dropped frames
//the start, so will start at -2 and then anything more than zero is a real
//dropped frame.
This seems strange. The only place that should be modifying that is part of your main loop - where does any loading happen?
int N=0;
//Starts the frame counter at 0. When you upload it will start at zero and
//then begin counting up. After that, releasing SELECT will again reset N to 0.
You know, you could call this "frame_count" and your code might be easier to read.
int V1=10;
int V2=20;
int V3=25;
int V4=25;
//Set some inital values for the variables
int V;
Do you use these anywhere?
int u=0;int d=1;int l=2; int r=3; int a=4;int b=5;int c=6;int x=7;int y=8;int z=9;int p=10;int q=11;int sta=12;int hom=13;int sel=14;
int U=15;int D=16;int L=17; int R=18; int A=19;int B=20;int C=21; int X=22; int Y=23; int Z=24;int P=25;int Q=26;int STA=27;
int HOM=28;int SEL=29;
//These are here so that I can refer to directions/buttons by pins instead of needing to remember where they're plugged in.

People usually do something like 'btn_U' instead of just 'U', but it probably doesn't matter for something this small.

//This displays on the LCD screen which buttons are being pressed.
//Sadly, to do 1P and 2P takes about 18000msec, which is more than a
//frame. 1P only takes 10msec, which is ok.

Twice as much work, and 2000 times as long seems very strange to me.
You may be able to shave some overhead by doing this -
Initialize to:

Code: Select all

...
char state[]={' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',0};
...

Note that the 0 is in addition to the values you're using.
and then

Code: Select all

      for(k=0;k<30;k++) {
         if (digitalRead(pins[k])) {
             state[k]='1';
         } else {
             state[k]='0';
         }
      }
      lcd.setCursor(0,0);
      lcd.print(state);
Note that you shouldn't have to reset the I/O state of the pin in every iteration of your loop. Not sure how much time this will save you.
The lcd.print function is probably pretty sophisticated. By making 'state' a null-terminated character array, you can use it as a string, and it's likely
to save you some overhead.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

Whoops. 18msec versus 10msec. Just under twice as long.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

Spent a long time working on va_arg, probably had it working about 5 hours ago, but I think because the main script was a bit bloated it wasn't quite working right. Seems ok now.

The code I ended up using is below for interests sake. I've kept press1 just for single presses, as it seems a bit more resilient.

Code: Select all

void press1 (int start, int finish, int m1)
        {if(frame_count==start){pinMode(m1,OUTPUT);digitalWrite(m1,LOW);}
         if(frame_count==finish){digitalWrite(m1,HIGH);pinMode(m1,INPUT);}}

void press (int start, int finish, ...) {
   int i,tmp;
   va_list args;
   va_start(args,finish);
   for(i=0;i<finish;i++){tmp=va_arg(args,int);
                         if(frame_count==start){pinMode(tmp,OUTPUT);digitalWrite(tmp,LOW);}
                         if(frame_count==finish){digitalWrite(tmp,HIGH);pinMode(tmp,INPUT);}}
                         if(frame_count==finish+1){return;} 
  va_end(args);}
                                                                                  //http://www.cplusplus.com/reference/clibrary/cstdarg/va_arg/

void loop(){                                                                        // The start of the working part of the script
if (ADV==1){time1=micros();
  lcd.clear(); 
  lcd.setCursor(0,2);lcd.print(time1-time2);                                          //This is here to tell me how long a frame is taking. 
time2=time1;
press(1,2,A,B); 
if(digitalRead(SEL)==0){frame_count=0;}                                                    
lcd.setCursor(0,3);lcd.print(lag); lcd.setCursor(10,3);lcd.print(frame_count);
time3=micros();
lcd.setCursor(10,2);lcd.print(time3-time1);  //And this is here to tell me how long the running of each frame takes. 
if(time3-time1>17000){lag++;}
frame_count=frame_count+1; 
ADV=0;


}
Guess it's time to work on script pointers.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

Massive achievement this afternoon. I had a session of programming, and have been able to write a sketch that records moves, and can then play them back.

The below is the code (without comments, and not well formatted - need to sleep first) which I did it with. Basically, you can record up to five button presses, and then send them to a PS360 (tested with Windows 7 USB game controller settings.)

Timing should be accurate as it's reporting it only takes 55microseconds to do the sketch. :D

Code: Select all

#include "TimerOne.h"
#include <stdarg.h>
//deviates a little from v5 in trying to get record function working. can go back to v5 if this fails. 

  int button =   22;
  int record =   23;
  int play =     24;
  int button_out = 25;
  int playstate; 
  int recordstate;
  int buttonstate;
  
  int print_value; 
    int j;
    int i;
    int k;
    int time1;
    int time2;
    int F=16683;
    int frame_count=0;
    int frame_advance=0;
    unsigned long time;
    int record_state [10][2];
    int current_record_slot=0;

void setup() {

  
for (i=0;i<11;i++)
{record_state[i][0]=0;record_state[i][1]=1;}

  
  
Timer1.initialize(F);                     
Timer1.pwm(9,900);
Timer1.attachInterrupt(FrameAdv);
     
   Serial.begin(9600);
    pinMode(button,INPUT);
    pinMode(record,INPUT);
    pinMode(play,INPUT);
    pinMode(button_out,OUTPUT); 
    digitalWrite(button,HIGH);
    digitalWrite(play,HIGH);
    digitalWrite(record,HIGH); 
   
    
}
void FrameAdv(){frame_advance=1;}  



void loop(){
    
  if (frame_advance==1){

     time1=micros(); 
     
if(digitalRead(record)==LOW){    
    if (digitalRead(record)!=recordstate)
    {frame_count=0;current_record_slot=0;Serial.println("reset");
    for (i=0;i<11;i++)
    {record_state[i][0]=0;record_state[i][1]=1;}}}


if (digitalRead(button)!=buttonstate)
    {digitalWrite(button_out,digitalRead(button)); 
    if(current_record_slot<11)
    {record_state[current_record_slot][0]=frame_count; 
    record_state[current_record_slot][1]=digitalRead(button);
    current_record_slot++;}}
    
   
   if (digitalRead(play)==LOW){if (digitalRead(play)!=playstate)
       {frame_count=0;  Serial.print("play @ "); Serial.println(time2-time1+16683);   
       for (k=0;k<10;k++)
       {print_value = record_state[k][0]; Serial.print(print_value);Serial.print(" @ ");Serial.print(record_state[k][1]);Serial.println("");}}}
     
     
    for (j=0;j<10;j++)
     if(frame_count==record_state[j][0]){digitalWrite(button_out,record_state[j][1]); }


playstate=digitalRead(play);
recordstate=digitalRead(record);
buttonstate=digitalRead(button);

frame_count++;
time2=micros();
frame_advance=0;
}}

noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

http://www.youtube.com/watch?v=bYm7umpMagw
http://www.youtube.com/watch?v=57zPuY6qsPE

Two quick videos showing my achievements (no gamer points though.)
Don Vecta
Posts: 635
Joined: Mon Mar 10, 2008 6:15 am

Re: How I made my programmable sticks

Post by Don Vecta »

noodalls wrote:http://www.youtube.com/watch?v=bYm7umpMagw
http://www.youtube.com/watch?v=57zPuY6qsPE

Two quick videos showing my achievements (no gamer points though.)
Looking good! :D
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

Have now set up so that it will work with two buttons. Will be easy to expand to however many buttons with just a few code changes.

Code: Select all

#include "TimerOne.h"
#include <stdarg.h>
//deviates a little from v5 in trying to get record function working. can go back to v5 if this fails. 
  
  int button =   22;
  int button2 = 23;             //three inputs and one output. Button is the button to be recorded.
  int buttons_in[] = {button,button2};
  
  
  
  int record =   24;         //Record clears the memory and starts recording.
  int play =     25;         //Play commences playback
  
  int button_out = 26;       //This is the output to the PS360
  int button_out2 = 27;
  int buttons_out[] = {button_out,button_out2};
  
  
  int playstate;             //States are used so that I can compare the state of the button last frame to this frame
  int recordstate;
  int buttons_state[]={1,1};
  
  
    int j;int k; int i;      //Three variables used for various functions in the sktech. Do I need all three? Maybe not. 
   
    
    
    int time1;              //I use these to time how long my sketches takes. 
    int time2;
    int F=16683;            //The duration of one frame
    int frame_count=0;      //Used to set timing by frame
    int frame_advance=0;    //Trigger to advance to the next frame.
    unsigned long time;
    int record_state [10][3];       //Where the recorded move is stored.
    int current_record_slot=0;      //The current slot for data recording. 

void setup() {

  
for (i=0;i<11;i++){record_state[i][0]=0;
    record_state[i][1]=1;record_state[i][2]=1;}       //Clears the record memory. 

  
Timer1.initialize(F);                                                //Sets up the frame counter                     
Timer1.pwm(9,900);
Timer1.attachInterrupt(FrameAdv);
     
   Serial.begin(9600);     //Allows information to be sent back to the arduino monitor
                            //Sets up the buttons
   for (i=0;i<2;i++){pinMode(buttons_in[i],INPUT);
      digitalWrite(buttons_in[i],HIGH);}
   for (i=0;i<2;i++){pinMode(buttons_out[i],OUTPUT);
      digitalWrite(buttons_out[i],HIGH);}
    pinMode(record,INPUT);
    pinMode(play,INPUT);
 
   
    digitalWrite(play,HIGH);
    digitalWrite(record,HIGH); 
for (i=0;i<2;i++)
    {buttons_state[i]=digitalRead(buttons_in[i]);}
    
}
void FrameAdv(){frame_advance=1;}  



void loop(){
    
if (frame_advance==1){

     time1=micros(); 
     
if(digitalRead(record)==LOW && digitalRead(record)!=recordstate)
    {frame_count=0;current_record_slot=0;Serial.print("reset @ ");
    Serial.println(time2-time1+16683);
    for (k=0;k<11;k++){for(i=1;i<3;i++)
      {record_state[k][0]=0;record_state[k][i]=1;}}}


for (k=0;k<2;k++){if (digitalRead(buttons_in[k])!=buttons_state[k])
  {digitalWrite(buttons_out[k],digitalRead(buttons_in[k])); 
  
   if(current_record_slot<11)
    {record_state[current_record_slot][0]=frame_count; 
    
    for(i=0;i<2;i++){
      record_state[current_record_slot][i+1]=digitalRead(buttons_in[i]);}
      current_record_slot++;}}}
    
   
if (digitalRead(play)==LOW && digitalRead(play)!=playstate)
       {frame_count=0;  Serial.print("play @ "); 
       Serial.println(time2-time1+16683);   
       
       for (k=0;k<10;k++){for(i=0;i<2;i++)
         {Serial.print(record_state[k][i+1]);}
         Serial.print(" @ ");
         Serial.print(record_state[k][0]);Serial.println("");}}
     
     
for (k=0;k<10;k++)
   {if(frame_count==record_state[k][0]){for(i=0;i<2;i++)
   {digitalWrite(buttons_out[i],record_state[k][i+1]);}}}



playstate=digitalRead(play);
recordstate=digitalRead(record);

for(k=0;k<2;k++){buttons_state[k]=digitalRead(buttons_in[k]);}


frame_count++;
time2=micros();
frame_advance=0;
}}
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

Code is now simplified. 255 slots for recording. Still only two buttons (enough for now.) Tempted to hook up my stick totally, will do so when some QDs arrive. For now, a new video, showing input vs playback.

http://www.youtube.com/watch?v=nPwIPijGmoI

255 slots is not the maximum, still only sitting at about 600microseconds (must be less than 16683 microseconds).
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

http://www.youtube.com/watch?v=ag_H4xHu1jg

Code: Select all

#include "TimerOne.h"
#include <stdarg.h>

#include <LiquidCrystal.h>
  LiquidCrystal lcd(2, 3, 4, 10, 11, 12,13);
  
  int A=22; int B=23; int C=24; int X=25; int Y=26; int Z=27;
  int P=28; int Q=29; int U=30; int D=31; int R=32; int L=33;
  int STA=34; int SEL=35; int TUR=36; int HOM=37;
  int a=38; int b=39; int c=40; int x=41; int y=42; int z=43; 
  int p=44; int q=45; int u=46; int d=47; int r=48; int l=49;
  int sta=50; int sel=51; int tub=52; int hom=53;
  
  int a_in=54; int b_in=55; int c_in=56; int x_in=57; int y_in=58; int z_in=59;
  int p_in=60; int q_in=61; int u_in=62; int d_in=63; int r_in=64; int l_in=65;
  int sta_in=66; int sel_in=67; int tur_in=68; int hom_in=69;
  
  int buttons_in[]=     {54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69};
  int buttons_out[] =   {22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37};
  int buttons_outP2[] = {38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53};
  int buttons_state []= { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
 
  int record =   60;         //Record clears the memory and starts recording.
  int play =     61;         //Play commences playback
  int playstate;             //States are used so that I can compare the state of the button last frame to this frame
  int recordstate;
  int button_changed_state=0;
  int P1; int P2;
  int variables[]={0,5,10};
  int v1=variables[0];int v2=variables[1];int v3=variables[2];
  
  
   int j;int k; int i;int m;      //Three variables used for various functions in the sktech. Do I need all three? Maybe not. 
   
   //I use these to time how long my sketches takes.
   int longest_frame;int lag;int lag_count=0; 
   int time1;int time2;
    int F=16683;int frame_count=0;int frame_advance=0;    
    unsigned long time;
    
    int record_slot_max = 124;// int button_number_max = 12;
    int record_state [125][12];
   int record_stateP2 [125][12]; 
    int record_state_time[255];    //Where the recorded move is stored.
    int current_record_slot=0;      //The current slot for data recording. 
//////////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
  lcd.begin(16,2);analogWrite(5,60);
  
  for (i=0;i<record_slot_max;i++){record_state_time[i]=0;
  for (k=0;k<12;k++){record_state[i][k]=1;record_stateP2[i][k]=1;
}}       //Clears the record memory. * may need to change to button number

  
  Timer1.initialize(F);                                                //Sets up the frame counter                     
  Timer1.pwm(9,900);
  Timer1.attachInterrupt(FrameAdv);
     
   Serial.begin(9600);     //Allows information to be sent back to the arduino monitor
                            //Sets up the buttons
   for (i=0;i<16;i++){pinMode(buttons_in[i],INPUT);
      digitalWrite(buttons_in[i],HIGH);}
   for (i=0;i<16;i++){pinMode(buttons_out[i],OUTPUT);pinMode(buttons_outP2[i],OUTPUT);
      digitalWrite(buttons_out[i],HIGH);digitalWrite(buttons_outP2[i],HIGH);}
    pinMode(record,INPUT);
    pinMode(play,INPUT);
   pinMode(7,INPUT);
   pinMode(8,INPUT);
   pinMode(9,INPUT);

digitalWrite(7,HIGH);
   digitalWrite(8,HIGH);
   //digitalWrite(9,HIGH);
    digitalWrite(play,HIGH);
    digitalWrite(record,HIGH); 
    //for (i=0;i<12;i++)
  //  {buttons_state[i]=digitalRead(buttons_in[i]);}
    
}
  void FrameAdv(){frame_advance=1;}  

  void press (int start, int finish, ...) {
     int m,tmp;
     va_list args;
     va_start(args,finish);
     for(m=0;m<finish;m++){tmp=va_arg(args,int);
         if(frame_count>=start && frame_count<finish){digitalWrite(tmp,LOW);}
         if(frame_count==finish){digitalWrite(tmp,HIGH);}}
         if(frame_count==finish+1){return;}
    va_end(args);}


  int check(int button, int var){
    if(digitalRead(button)==LOW){
      if(digitalRead(u_in)==LOW){if(digitalRead(u_in)!=buttons_state[8])
    {variables[var]=variables[var]+5;}}
      if(digitalRead(d_in)==LOW){if(digitalRead(d_in)!=buttons_state[9])
    {variables[var]=variables[var]-5;}}
      if(digitalRead(r_in)==LOW){if(digitalRead(r_in)!=buttons_state[10])
    {variables[var]=variables[var]+1;}}
      if(digitalRead(l_in)==LOW){if(digitalRead(l_in)!=buttons_state[11])
    {variables[var]=variables[var]-1;}}
}} 

//////////////////////////////////////////////////////////////////////////////////////////////////////
void loop(){
    //digitalWrite(51,LOW);digitalWrite(35,LOW);
    //digitalWrite(37,LOW);//
   //digitalWrite(53,LOW); //use these to swap 360 and ps3
  digitalWrite(53,HIGH);
  digitalWrite(37,HIGH); 
  digitalWrite(51,HIGH);
  digitalWrite(35,HIGH);
  

 
  if (frame_advance==1){
   time1=micros(); 
     press(1,2);
  
  check(p_in,0);
  check(q_in,1);
    v1=variables[0];v2=variables[1];v3=variables[2];
press(v1,v1+1,R,C);press(v2,v2+1,D,C);
 

    for (k=0;k<16;k++){if (digitalRead(buttons_in[k])!=buttons_state[k]){button_changed_state=1;}}
    
    if(button_changed_state==1){
  //  if(current_record_slot<record_slot_max)
    {record_state_time[current_record_slot]=frame_count;}
      
    for(k=0;k<14;k++){
      if(P1==1){digitalWrite(buttons_out[k],digitalRead(buttons_in[k]));
      buttons_state[k]=digitalRead(buttons_in[k]);  
      record_state[current_record_slot][k]=digitalRead(buttons_in[k]); }
          
      if(P2==1){digitalWrite(buttons_outP2[k],digitalRead(buttons_in[k]));
      buttons_state[k]=digitalRead(buttons_in[k]);  
      record_stateP2[current_record_slot][k]=digitalRead(buttons_in[k]); }
      


}
  
current_record_slot++;
}
      
      
 


   
   if(digitalRead(record)==LOW && digitalRead(record)!=recordstate)
    {frame_count=0;current_record_slot=0;Serial.print("reset @ ");
    Serial.println(time2-time1+16683);
    for (k=0;k<record_slot_max;k++){for(i=0;i<12;i++)
      {record_state_time[k]=0;record_state[k][i]=1;record_stateP2[k][i]=1;}}}



    
   
    if (digitalRead(play)==LOW && digitalRead(play)!=playstate)
       {frame_count=0;  Serial.print("play @ "); 
    
    
      Serial.println(time2-time1+16683);  
      Serial.println("ABCXYZPQUDRLabcxyzpqudrl");//A,B,C,X,Y,Z,P,Q,U,D,R,L 
       
       for (k=0;k<20;k++){for(i=0;i<12;i++)//record_slot_max - show fewer commands to get play to work earlier
         {Serial.print(record_state[k][i]);}
         for(i=0;i<12;i++)//record_slot_max - show fewer commands to get play to work earlier
         {Serial.print(record_stateP2[k][i]);}
         Serial.print(" @ ");
         Serial.print(record_state_time[k]);Serial.println("");}
       
      
   
 
 }
     
     
for (k=0;k<record_slot_max;k++)
   {if(frame_count==record_state_time[k]){for(i=0;i<12;i++)
   {digitalWrite(buttons_out[i],record_state[k][i]);
   digitalWrite(buttons_outP2[i],record_stateP2[k][i]);
    }}}

playstate=digitalRead(play);
recordstate=digitalRead(record);

frame_count++;
button_changed_state=0;
//if (frame_count>1){int lag = (time2-time1);
//lcd.clear();
//lcd.setCursor(5,1);lcd.print(lag);}
lcd.clear(); lcd.setCursor(0,0); lcd.print(variables[0]);lcd.setCursor(4,0); lcd.print(variables[1]);
lcd.setCursor(8,0); lcd.print(variables[2]);
if(digitalRead(7)==LOW){P1=1;lcd.setCursor(8,1);lcd.print(P1);}
if(digitalRead(7)==HIGH){P1=0;lcd.setCursor(8,1);lcd.print(P1);}
if(digitalRead(8)==LOW){P2=1;lcd.setCursor(11,1);lcd.print(P2);}
if(digitalRead(8)==HIGH){P2=0;lcd.setCursor(11,1);lcd.print(P2);}
//if(digitalRead(7) == LOW && digitalRead(8)==LOW){lcd.setCursor(10,1);lcd.print("&");}

//if(digitalRead(9)==LOW){lcd.setCursor(10,1);lcd.print("1");}
//if(digitalRead(9)==HIGH){lcd.setCursor(10,1);lcd.print("0");}
time2=micros();
lcd.setCursor(0,1);lcd.print(time2-time1);
frame_advance=0;
}}
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: How I made my programmable sticks

Post by Rufus »

So, I tried my hand at this, and didn't realize that the Arduino is so memory constrained. Maybe some of the things you were doing make more sense in light of that.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

My next plan is to take one of these

http://www.ebay.com.au/itm/LCD-Keypad-S ... 3eff016eee

on top of a arduino and create an easy to use program. No further input of code in the arduino program, unless you want to. Everything else done via lcd.

This way, people can have access to the arduino without having to (a) spend much money or (b) do much wiring.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: How I made my programmable sticks

Post by Rufus »

noodalls wrote:My next plan is to take one of these ....
I think I'm about halfway to getting an Arduino-controlled stick working on the serial port. Then the stick just gets a USB cable that you put into your computer to control it.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

http://diy-machine.blogspot.com/

I'm assuming you've seen this webpage, where they have a similar goal. Looks like you need a teensy for that one though.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: How I made my programmable sticks

Post by Rufus »

Well, here's the first generation code for the Arduino driven controller. It's a macrolua interpreter and the interface is USB/Serial port. This only runs 1 stick. Still need to make sure the timing works properly. etc.

Code: Select all

/*
   Copyright 2011 Nathan Hellweg all rights reserved
   Macrolua interepreter for Arduino UNO
   Macrolua by Dammit
*/


#include <avr/pgmspace.h>
#define MAX_MACRO 1024

char macro[MAX_MACRO]; /* the macro */
char keys[128]={
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,

  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
};


/* Macro Execution Globals */
int cur_pos=0; //Macro Execution Pointer
//double usecs_per_frame=16683; //Defaulting to 59.94 Hz.
double usecs_per_frame=16708.333; //59.843333 Hz for SSF4 on Composite
boolean incomment=false;
boolean firstplayer=true;
boolean var_waiting=false; //Whether there is a wait loop
int variable_wait=0; //Wait loop value
int wait=0;

int loop_level=-1; //Repeat stack pointer
int loop_start[8];//Repeat block start list
int loop_count[8];//Repeat count
unsigned int held=0;

void setpins(unsigned int pins) {
   int i,j;
   
//   Serial.print(pins,BIN);
//   Serial.println(" setpins");

   for(i=0;i<16;i++) {
      j=pins&(1<<i);
      if(0==j) {
        digitalWrite(i+2,HIGH);
      } else {
        digitalWrite(i+2,LOW);
      }
   }
}

void loadmacro() {
  int pointer=0;
  int last=-1;
  int paren=0;
  int comment=0;

  Serial.println("nEnter the Macro Ending in '!':");
  while(pointer<MAX_MACRO) {
     last=Serial.read();
     if(-1 != last) {
        macro[pointer]=last;
        Serial.write(last);
        pointer++;
        if(comment) {
           if(10==last || 13==last) { /* Carrige Return / Line Feed */
              comment=0;
           }
        } else {
           if ('(' == last) {
              paren++;
           } else if (')' == last) {
              paren--;
              if(paren < 0) {
                 Serial.println("Unmatched ()s");
              }
           } else if ('#' == last ) {
              comment=1;
           }
           if ('!' == last) {
              if(paren) {
                 Serial.println("Unmatched ()s");
              }
              break;
           }
        }
        if (0 == last) {
           if(paren) {
              Serial.println("Unmatched ()s");
           }
           break;
        }
     }
  }
  if(pointer > MAX_MACRO) {
     Serial.println("Macro Overrun");
     while (1)
        ;
  } else {
     while(pointer < MAX_MACRO && macro[pointer]) {
        macro[pointer]=0;
        pointer++;
     }
  }
  Serial.write("n");
}
void printmacro() {
   Serial.println(macro);
}

void grabcomment() { //Moves the execution pointer to the end of the comment.
   while(1) {
      if(cur_pos<MAX_MACRO && ( 0==macro[cur_pos] || 10==macro[cur_pos] || 13==macro[cur_pos])) {
         return;
      }
      cur_pos++;
   }
}

int grabnumber() { //Grabs a number.  Checks for 'variable wait' and returns it
   int i=0;
   char buffer[]={0,0,0,0,0,0};

//   Serial.println("grabnumber");

   while(i<5 && macro[cur_pos]>47 && macro[cur_pos]<58) {
      buffer[i]=macro[cur_pos];
      cur_pos++;
      i++;
   }
   if(i>=5) {
     Serial.println("No support for oversize numbers");
   }
   if(0==i) {
      buffer[0]='0';
      buffer[1]=0;
   }
   i=atoi(buffer);
   if('?'==macro[cur_pos]) {
      if(!var_waiting) {
         variable_wait=i;
         var_waiting=true;
      } else {
         i=variable_wait;
      }
      cur_pos++;
   }
   return(i);
}

void hold () {
//   Serial.println("hold");
//   Serial.print(held,BIN);
//   Serial.write("n");
   if(keys[macro[cur_pos]]) {
      held=held|(1<<(keys[macro[cur_pos]]-1));
   } else {
      Serial.println("Ignoring Null Hold");
   }
//   Serial.print(held,BIN);
//   Serial.write("n");
   cur_pos++;
}

void release () {
//   Serial.println("release");
   if(keys[macro[cur_pos]]) {
      held=held&(~1<<(keys[macro[cur_pos]]-1));
   } else {
      Serial.println("Ignoring Null Release");
   }
   cur_pos++;
}

void startloop() {
//   Serial.println("startloop");
   loop_level++;
   loop_start[loop_level]=cur_pos;
}

void endloop() {
//   Serial.println("endloop");
   if(loop_level<0) {
      Serial.println("Loop underflow");
      return;
   }
   if(0==loop_count[loop_level]) {//First Iteration 
      loop_count[loop_level]=grabnumber();
   } else {
      loop_count[loop_level]--;
   }
   if(loop_count[loop_level]>0) {
      cur_pos=loop_start[loop_level];
   } else {
      loop_count[loop_level]=0; //This should be unnecessary;
      loop_level--;
   }
}

boolean stepmacro() {
   int input=-1;
   char curchar;
   int number;
   unsigned int curcmd=0;
   boolean done=false;
   
//   Serial.println("stepmacro");
   
   if(Serial.available()) { //Break on user input. -- Maybe sync support later
      return false;
   } else if(wait>0) { //Multiframe wait.
      wait--;
   } else  {
      while(false==done) {
         curchar=macro[cur_pos];
         cur_pos++;
         if (false==firstplayer) {
            if('+'==curchar || '>'==curchar || '<'== curchar)  { // '+','>' or '<' reset to P1
               firstplayer=true;
            }
         } else {
            switch (curchar) {
               case '!': //End of Marco
               case 0:
                  if(var_waiting) {
                     variable_wait++;
                     Serial.print("Incrementing Variable: ");
                     Serial.print(variable_wait,DEC);
                     Serial.write("n");
                     held=0;
                     cur_pos=0;
                  } else {
                     return(0);
                     break;
                  }
               case 'W':
               case 'w':
                  wait=grabnumber()-1;
                  done=true;
                  break;
               case '.':
                  done=true;
                  break;
               case '<': //Multi-character controls
               case '+': //These indicate P1 which should be ignorable.
                  break;
               case '/': //Multi-character controls
               case '-': //These indicate non-P1 status, so stop reading
                  firstplayer=false; 
                  Serial.println("WARNING: No multiplayer synchronization");
                  break;
               case '>': //Ends < / / > block.  Should be ignorable.
                  break;
   /*I suppose I should really have separate states for the two different
     mult-character command systems and/or the ability to run scripts as
     P2-Pn.  The </> block timing support stuff is a little annoying. */
               case '$': //Save and restore commands.  Not supported.
               case '&': //But it's relatively easy to handle these better...
                  Serial.println("Save and Restore are not supported");
                  grabnumber(); //Skipping the state number.
                  break;
               case '?': //Not sure what ??? does.  The variable wait should
                         //Should be handled by grabnumber();
                  Serial.println("??? command is not supported.");
                  break;
               case '#':
                  grabcomment();
                  break;
               case '_':
                  hold();
                  break;
               case '^':
                  release();
                  break;
               case '*':
                  held=0;
                  break;
               case '(':
                  startloop();
                  break;
               case ')':
                  endloop();
                  break;
               default :
                  if(curchar<128 and keys[curchar]) {
                     curcmd=curcmd|int(1<<(keys[curchar]-1));
                  }
            }
         }
      }
      setpins(held|curcmd);
   }
   return true;
}

void runmacro() {
   int i;
   unsigned long LastFrame;
   unsigned long NextFrame;
   boolean wrap=false;
  
//   Serial.println("runmacro");
   
   cur_pos=0;
   var_waiting=false;
   variable_wait=0;

   loop_level=0;
   held=0;
   for(i=0;i<8;i++) {
       loop_start[i]=0;
       loop_count[i]=0;
   }
/* Set Pins to output */
   for(i=0;i<16;i++) {
      pinMode(i+2,OUTPUT); /* Pins 2-13, A0-A3 */
      digitalWrite(i+2,HIGH);
   }
   NextFrame=micros()+usecs_per_frame;
   do {
     LastFrame=NextFrame;
     NextFrame=LastFrame+usecs_per_frame;
     if(NextFrame<usecs_per_frame) {
        wrap=true;
     }
//     Serial.println(NextFrame);
     while(wrap && micros()>usecs_per_frame)
        ;
     while(micros()<NextFrame)
        ;
   } while (stepmacro());
/* Set pins to input */
   for(i=0;i<16;i++) {
      pinMode(i+2,INPUT); /* Pins 2-13, A0-A3 */
   }
}

void setup () {
   int i;
   Serial.begin(115200);
   for(i=0;i<MAX_MACRO;i++) {
      macro[i]=0;
   }
   /* Key (Pin=Button) */
   /* 16=r, 15=l, 14=u, 13=d */
   /* 12=back, 11=start, 10=RT, 9=LT */
   /* 8=RB, 7=LB, 6=Y, 5=X */
   /* 4=B, 3=A */
   
   keys['r']=16;
   keys['R']=16;
   keys['l']=15;
   keys['L']=15;
   keys['u']=14;
   keys['U']=14;
   keys['D']=13;
   keys['d']=13;
   keys['1']=5;
   keys['2']=6;
   keys['3']=8;
   keys['4']=7;
   keys['5']=3;
   keys['6']=4;
   keys['7']=10;
   keys['8']=9;
   keys['s']=11;
   keys['S']=11;
   keys['b']=12;
   keys['B']=12;  
}

void loop () {
   int cmd;
   Serial.print("Ticks per frame: ");
   Serial.println(usecs_per_frame);
   Serial.println("(l)oad,(p)rint,(r)un");
   while(0==Serial.available())
      ;
   if(Serial.available()) {
      cmd=Serial.read();
      switch (cmd) {
         case 'l':
         case 'L':
           loadmacro();
           break;
         case 'p':
         case 'P':
           printmacro();
           break;
         case 'r':
         case 'R':
           runmacro();
           break;
         default :
           Serial.println("Unknown Command");
      }
   }
}
Raine
Posts: 476
Joined: Sun Sep 27, 2009 2:16 am
Location: Wollongong, Australia
Contact:

Re: How I made my programmable sticks

Post by Raine »

So you just wrote something that allows Macrolua scripts to be run through the programmable stick setup? This is exactly what I've been waiting for, so I think I'll start researching how to get it all setup together. Thanks a lot!
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: How I made my programmable sticks

Post by Rufus »

Remxi wrote:So you just wrote something that allows Macrolua scripts to be run through the programmable stick setup? This is exactly what I've been waiting for, so I think I'll start researching how to get it all setup together. Thanks a lot!
FYI This is a different setup than the one on my web page. This one consists of:
Arduino UNO
Madcatz SE stick.
Solder, wire, and some connectors.

Anyhow, the script will want a bunch of cleaning and improvement.
Dammit
Posts: 359
Joined: Thu Nov 05, 2009 1:50 am
Contact:

Re: How I made my programmable sticks

Post by Dammit »

Rufus, if you are interested in putting code and binaries on the macrolua page, I can give you access.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: How I made my programmable sticks

Post by noodalls »

Rufus wrote:
Remxi wrote:So you just wrote something that allows Macrolua scripts to be run through the programmable stick setup? This is exactly what I've been waiting for, so I think I'll start researching how to get it all setup together. Thanks a lot!
FYI This is a different setup than the one on my web page. This one consists of:
Arduino UNO
Madcatz SE stick.
Solder, wire, and some connectors.

Anyhow, the script will want a bunch of cleaning and improvement.
This looks very awesome.

Some quick questions - how do you connect from the arduino to the madcatz? Just straight wiring? Would it work the same way to a cthulhu etc? How much more work to get two players?

Will definitely have a play with this when I get a stick to put it on. Cheers.

N
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: How I made my programmable sticks

Post by Rufus »

noodalls wrote: Some quick questions - how do you connect from the arduino to the madcatz? Just straight wiring? Would it work the same way to a cthulhu etc? How much more work to get two players?
It's straight from pins 5-13 and A0-A3 to the lines on the MadCatz. In principle it should work with any common ground controller, but it wouldn't be the worst idea to have 470 Ohm resistors in there to limit current, and, if the controller PCB requires more current per line PNP transistors might also be necessary.

I'll try to post a picture after work today.

On a Mega, which has more than 28 IO pins, it should be straightforward to support 2 users.

FWIW, this is basically a first working version. There's a bunch of stuff that really wants to be improved like:
Better timing and frame synchronization.
Ability to modify frame rate on the fly.
Multiple macro support.
Multi-player flow control & Better handling of control structure interactions.
Slaving Arduinos for multi-controller support.
Input recording.
General cleanliness of the code.
Post Reply