Arduino Based Programmable Stick Project

reference materials and general how-to information
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Arduino Based Programmable Stick Project

Post by Rufus »

Since I'm passing a stick on to Wolverine-Master - and maybe later to others, I figured I should document how to use the thing.
Here's a version of the code. I'll probably be updating it, but it's clear that I shouldn't be hijacking noodal's thread.

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 256

char macro[MAX_MACRO]; /* Macro Storage */
int macro_start[10]={-1,-1,-1,-1,-1, -1,-1,-1,-1,-1}; /* Start Locations */
int next_start=0; /* Pointer to free storage */

/*
   Character map for interpretation
   Hardcoding this to progmem could save space,
   but makes things less readable.
*/

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 */
#define MAX_THREADS 3
int start[0]; //Macro start pointer
int cur_pos[3]; //Macro Execution Pointer 0 default, 1&2 are for threads

/*Frame time.  Defaulting to nominal 59.94 Hz. */
unsigned long frame_ticks=16683;
unsigned long frame_ticks_num=22938;
unsigned long frame_ticks_den=65535;

boolean var_waiting=false; //Whether there is a wait loop
int variable_wait=0; //Wait loop value
int wait=0;

unsigned long NextFrame;
unsigned long FractionalTicks;

#define MAX_LOOPS 4
int loop_level[3]; //Repeat stack pointer
int loop_start[3][4];         //Repeat block start list
int loop_count[3][4];         //Repeat count

unsigned long held=0; //This may neet per-thread...

//Pins 0&1 are reserved for Serial Comm, so I can use 0 as null.
char pins[]={ 6, 7, 9, 8,  4, 5,11,10, 12,13,14,15, 16,17, 0, 0,
              0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0};
// On the controller this works out to
// X, Y,RB,LB, A, B,RT,LT, St,Ba, D, U,  L, R

//For Noodals' setup
//// Y, Z, A, B,  X, C, Q, P, ST,SL, D, U, L, R,
//char pins[]={39,41,31,33, 37,35,45,43, 47,51,25,23, 27,29, 0, 0,
//             38,40,30,32, 36,34,44,42, 46,50,24,22, 26,28, 0, 0};
//Noodals pin assignments
//U=23;D=25;L=27;R=29;A=31;B=33;C=35;X=37;Y=39;Z=41;P=43;Q=45;HOM=49;STA=47;SEL=51;
//u=22;d=24;l=26;r=28;a=30;b=32;c=34;x=36;y=38;z=40;p=42;q=44;hom=48;sta=46;sel=50;


void loadmacro(int macro_id) {
   int pointer=0;
   int last=-1;
   int paren=0;
   int comment=0;
   
   if(macro_id<0 or macro_id>9) {
      Serial.println("Macro id must be 0-9.  Load failed.");
      return;
   }
   if(macro_start[macro_id]>=0) {
      Serial.println("You must erase first.  Load failed.");
      return;
   }
   if(next_start>=MAX_MACRO) {
      Serial.println("Out of Storage.  Load failed.");
      return;
   }
   Serial.print("You have ");
   Serial.print(MAX_MACRO-next_start);
   Serial.print(" of ");
   Serial.print(MAX_MACRO);
   Serial.print(" chararcters remaining");

   macro_start[macro_id]=next_start;
   pointer=next_start;

   Serial.println("nEnter the Macro Ending in '!':");
   while(pointer<MAX_MACRO) {
      last=Serial.read();
      if(-1 != last) {
         macro[pointer]=last;
         Serial.write(last);
         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");
               }
               macro[pointer]=0; //Use null instead of '!' to mark end of macro
               break;
            }
         }
         if (0 == last) {
            if(paren) {
               Serial.println("Unmatched ()s");
            }
            break;
         }
         pointer++;
      }
   }
   Serial.write("n");
   pointer++;
   next_start=pointer;
   if(pointer > MAX_MACRO) {
      Serial.println("Macro clipped at end of memory.");
   }
}

void printmacro(int macro_id) {
   int pointer;

   if(macro_start[macro_id]<0) {
      Serial.println("Macro not found");
      return;
   }
   pointer=macro_start[macro_id];
   while(macro[pointer]) {
   Serial.write(macro[pointer]);
      pointer++;
   }
   Serial.println("!");
}

void erasemacro(int macro_id) {
   int i,pointer,delta;

   if(macro_start[macro_id]<0) {
      Serial.println("Macro not found");
      return;
   }
   pointer=macro_start[macro_id];
   for(pointer=macro_start[macro_id];pointer<MAX_MACRO && macro[pointer];pointer++) {
      macro[pointer]=0; //This should be unnecessary...
   }
   for(delta=pointer-macro_start[macro_id];pointer<MAX_MACRO;pointer++) {
      macro[pointer-delta]=macro[pointer];
   }
   for(pointer-=delta;pointer<MAX_MACRO;pointer++) {
      macro[pointer]=0;
   }
   for(i=0;i<10;i++) {
      if(macro_start[i]>macro_start[macro_id])
         macro_start[i]-=delta;
   }
   next_start-=delta;
   macro_start[macro_id]=-1;
   Serial.print("Macro ");
   Serial.print(macro_id);
   Serial.println(" erased.");
}

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

//   Serial.println("grabnumber");

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

void setpins(unsigned long downpins) {
   int i;

   for(i=0;i<32;i++) {
      if(pins[i]) {
         if(downpins&(1<<i)) {
           digitalWrite(pins[i],LOW);
         } else {
           digitalWrite(pins[i],HIGH);
         }
      }
   }
//   Serial.println(downpins|1073741824,BIN);
}

//macro_pos is the execution pointer.
//thread is the thread ID (for use with loops)
//start is the block start position.
//next is a bitfield pointer.

boolean stepmacro(int *macro_pos, unsigned long *next, int start, int thread_id) {
   char curchar;
   boolean done=false;
//   int i;

/*
   Serial.println("Stepmacro");
   Serial.print("Macro Position ");
   Serial.println(*macro_pos);
   Serial.print("Start ");
   Serial.println(start);
   Serial.print("Thread ID");
   Serial.println(thread_id);
*/
   
   if(Serial.available()) { //Break on user input. -- Maybe sync support later
      Serial.println("Broke on user input");
      return false;
   } else if(wait>0) { //Multiframe wait.
      wait--;
   } else  {
      while(false==done) {
         curchar=macro[*macro_pos];
//         Serial.print("*macro_pos=");
//         Serial.println(*macro_pos);
         (*macro_pos)++;
//         Serial.print("curchar=");
//         Serial.println(curchar);
         switch (curchar) {
            case '!': //End of Marco - this should no longer happen.
            case 0:
               Serial.println("End of Macro");
               return(0);
               break;
            case 'W':
            case 'w':
               wait=grabnumber(macro_pos)-1;
               done=true;
               break;
            case '.':
               done=true;
               break;
            case '+': //These P1/Pn setings do not require threading
            case '-': //so they are ignorable.  I'm not going to bother
                      //with B/F support.
               break;
            case '<': //Multi-character controls
               /* FIXME: This needs to be implemented */
               /* startthreads(*macro_pos) */
               /* break; */
               Serial.println("WARNING: No threading support.  Unexpected behavior ahead.");
            case '/': //Multi-character controls
            case '>': //Ends < / / > block.  Should be ignorable.
               /* FIXME: These are end-of-thread when threading works*/
               /* if(endthread(thread_id)) { */
               /*    done=true;  */
               /* } */
               Serial.println("WARNING: No threading support.  Unexpected behavior behind?");
               break;
            case '$': //Save and restore commands.  Not supported.
            case '&':
               Serial.println("Save and Restore are not supported");
               grabnumber(macro_pos); //Skipping the state number.
               break;
            case '?': //Not sure what ??? does.  The variable wait ?
                      //Should be handled by grabnumber and never show up
               Serial.print(*macro_pos,DEC);
               Serial.println("??? command is not supported.");
               break;
            case '#': //Start Comment
               while(*macro_pos<MAX_MACRO && 0!=macro[*macro_pos] && 10!=macro[*macro_pos] && 13!=macro[*macro_pos]) {
                     (*macro_pos)++;
               }
               break;
            case '_': //Hold
               if(keys[macro[*macro_pos]]) {
                  held=held|(1<<(keys[macro[*macro_pos]]-1));
               } else {
                  Serial.println(" Ignoring Null Hold");
               }
               (*macro_pos)++;
               break;
            case '^': //Release
               if(keys[macro[*macro_pos]]) {
                  held=held&(~1<<(keys[macro[*macro_pos]]-1));
               } else {
                  Serial.println("Ignoring Null Release");
               }
               (*macro_pos)++;
               break;
            case '*': //Release All
               held=0;
               break;
            case '(': //Start Loop
               loop_level[thread_id]++;
               if(loop_level[thread_id]<MAX_LOOPS) {
                  loop_start[thread_id][loop_level[thread_id]]=*macro_pos;
               } else {
                  loop_level[thread_id]--;
                  Serial.println("Loop overflow -- undeflow ahead");
               }
               break;
            case ')': //End Loop
               if(loop_level[thread_id]<0) {
                  Serial.println("Loop underflow");
               } else {
                  if(0==loop_count[thread_id][loop_level[thread_id]]) {//First Iteration
                     loop_count[thread_id][loop_level[thread_id]]=grabnumber(macro_pos);
                  } else {
                     loop_count[thread_id][loop_level[thread_id]]--;
                  }
                  if(loop_count[thread_id][loop_level[thread_id]]>0) {
                     *macro_pos=loop_start[thread_id][loop_level[thread_id]];
                  } else {
                     loop_count[thread_id][loop_level[thread_id]]=0; //This should be unnecessary;
                     grabnumber(macro_pos); //ignore loop count.
                     loop_level[thread_id]--;
                  }
               }
               break;
            default : //Single Frame Input
               if(curchar<128 and keys[curchar]) {
//                  Serial.println(*next,BIN);
                  *next=*next|(1<<(keys[curchar]-1));
//                  Serial.println(keys[curchar],DEC);
//                  Serial.println(pins[keys[curchar]-1],DEC);
//                  Serial.println(*next,BIN);
               }
         }
      }
   }
   return true;
}

//Stub function for now.  Deals with setting up 'threaded' macro execution
void startthreads() {
}

//Stub function for now.  Deals with ending the current thread.
void endthread() {
}

void runmacro(int macro_id) {
   int i,j;
   unsigned long FractionalTicks;
   unsigned long next=0;

//   Serial.println("runmacro");

   if(macro_start[macro_id]<0) {
      Serial.println("Macro ");
      Serial.println(macro_id);
      Serial.println(" not found");
      return;
   }
   
//   Serial.println("runmacro1");
//Reset execution globals.
   cur_pos[0]=macro_start[macro_id];
   start[0]=macro_start[macro_id];
   for(i=1;i<MAX_THREADS;i++) {
      cur_pos[i]=-1;
      start[i]=-1;
   }
   for(i=0;i<MAX_THREADS;i++) {
      loop_level[i]=-1;
      for(j=0;j<4;j++) { //Probably unnecessary
         loop_start[i][j]=0;
         loop_count[i][j]=0;
      }
   }
   var_waiting=false;
   variable_wait=0;
//   held=0;
//   Serial.println("runmacro2");

/* Set Pins to output */
   for(i=0;i<32;i++) {
      if(pins[i]) {
         pinMode(pins[i],OUTPUT);
         digitalWrite(pins[i],HIGH);
      }
   }
   setpins(next|held);
   NextFrame=micros();
   do {
      NextFrame+=frame_ticks;
      FractionalTicks+=frame_ticks_num;
      if(FractionalTicks>frame_ticks_den) {
         NextFrame+=int(FractionalTicks/frame_ticks_den);
         FractionalTicks%=frame_ticks_den;
      }
//      Serial.print("Next Frame:");
//      Serial.println(NextFrame);
//      Serial.print("Fractional Ticks:");
//      Serial.println(FractionalTicks);
      if(NextFrame<=frame_ticks) {
         while(micros()>=4294967295-frame_ticks)
            ;
      }
      while(micros()<NextFrame)
         ;
      setpins(next|held);
      next=0;
   } while (stepmacro(cur_pos,&next,start[0],0));
//Two more frames - one for the last input, one for last stepmacro, and
//one to clear.  There's bound to be an elegant way to handle this, but at
//least this is semi-readable.
   for(i=0;i<2;i++) {
      NextFrame+=frame_ticks;
      FractionalTicks+=frame_ticks_num;
      if(FractionalTicks>frame_ticks_den) {
         NextFrame+=int(FractionalTicks/frame_ticks_den);
         FractionalTicks%=frame_ticks_den;
      }
      if(NextFrame<=frame_ticks) {
         while(micros()>=4294967295-frame_ticks)
            ;
      }
      while(micros()<NextFrame)
         ;
      setpins(next|held);
      next=0;
   }

/* Set pins to input */
   for(i=0;i<32;i++) {
      if(pins[i]) {
         pinMode(pins[i],INPUT);
      }
   }
}

long serialreadnum() {
  char buffer[]={0,0,0,0,0,0,0,0,0,0};
  int i=0;
  long input;

  do {
    input=Serial.peek();
    if(47 < input && 58 > input && i<10) {
       buffer[i]=input;
       i++;
       Serial.read();
       Serial.write(input);
    }
  } while (-1==input || (47<input && 58 > input && i<10));

  if(0<i && i<10) {
    Serial.read();
    return (atol(buffer));
  }
  Serial.println("Problem Reading Serial Input Number");
  return -1;
}

void setup () {
   int i;
   Serial.begin(115200);
   for(i=0;i<MAX_MACRO;i++) {
      macro[i]=0;
   }

// My buttons...
// A, B, X, Y, LB,RB,LT,RT, ST,BA, D, U,  L, R
   keys['1']=1;
   keys['2']=2;
   keys['3']=3;
   keys['4']=4;
   keys['5']=5;
   keys['6']=6;
   keys['7']=7;
   keys['8']=8;
   keys['s']=9;
   keys['S']=9;
   keys['b']=10;
   keys['B']=10;
   keys['d']=11;
   keys['D']=11;
   keys['u']=12;
   keys['U']=12;
   keys['l']=13;
   keys['L']=13;
   keys['r']=14;
   keys['R']=14;

// Noodals' buttons
// Y, Z, A, B,  X, C, Q, P, ST,SL, D, U, L, R,
/*
   keys['Y']=1;
   keys['Z']=2;
   keys['A']=3;
   keys['B']=4;
   keys['X']=5;
   keys['C']=6;
   keys['Q']=7;
   keys['P']=8;
   keys['S']=9; //S for start
   keys['E']=10; //E for select
   keys['D']=11;
   keys['U']=12;
   keys['L']=13;
   keys['R']=14;
   keys['y']=17;
   keys['z']=18;
   keys['a']=19;
   keys['b']=20;
   keys['x']=21;
   keys['c']=22;
   keys['q']=23;
   keys['p']=24;
   keys['s']=25; //s for start
   keys['e']=26; //e for select
   keys['d']=27;
   keys['u']=28;
   keys['l']=29;
   keys['r']=30;
*/

   NextFrame=micros()+frame_ticks;
   FractionalTicks=0;
}

void loop () {
   int cmd;
   long number;

   Serial.println("");
   Serial.print("Ticks per frame: ");
   Serial.print(frame_ticks);
   Serial.print(" and ");
   Serial.print(frame_ticks_num);
   Serial.print("/");
   Serial.println(frame_ticks_den);
   Serial.println("(l)oad,(p)rint,(e)rase,(0-9)run macro,(f)rame ticks,(n)umerator,(d)enominator, (t/T) tripple/third frame time");
   while(0==Serial.available()) {
      if(NextFrame<=frame_ticks) {
         while(micros()>4294967295-frame_ticks)
            Serial.println(micros());
      }
      while(micros()>NextFrame) {
         NextFrame+=frame_ticks;
         FractionalTicks+=frame_ticks_num;
         if(FractionalTicks>frame_ticks_den) {
            NextFrame++;
            FractionalTicks-=frame_ticks_den;
         }
      }
   }
   if(Serial.available()) {
      cmd=Serial.read();
      switch (cmd) {
         case 'l':
         case 'L':
           Serial.println("Enter macro id to load:");
           number=serialreadnum();
           if(0<=number && number<10) {
              loadmacro(number);
           } else {
              Serial.println("Invalid macro id");
           }
           break;
         case 'p':
         case 'P':
           Serial.println("Enter macro id to print:");
           number=serialreadnum();
           if(0<=number && number<10) {
              printmacro(number);
           } else {
              Serial.println("Invalid macro id");
           }
           break;
         case '0':
         case '1':
         case '2':
         case '3':
         case '4':
         case '5':
         case '6':
         case '7':
         case '8':
         case '9':
           runmacro(int(cmd-48));
           break;
         case 'e':
         case 'E':
           Serial.println("Enter macro id to erase:");
           number=serialreadnum();
           if(0<=number && number<10) {
              erasemacro(number);
           } else {
              Serial.println("Invalid macro id");
           }
           break;
         case 'f':
         case 'F':
           Serial.println("Enter frame tick count:");
           number=serialreadnum();
           if(number) {
              frame_ticks=number;
           }
           Serial.read();
           break;
         case 'n':
         case 'N':
           Serial.println("Enter frame tick count fractional numerator");
           number=serialreadnum();
           if(number and number<frame_ticks_den) {
              frame_ticks_num=number;
           }
           Serial.read();
           break;
         case 'd':
         case 'D':
           Serial.println("Enter frame tick count fractional denominator");
           number=serialreadnum();
           if(number and number>frame_ticks_num) {
              frame_ticks_den=number;
              FractionalTicks=0;
           }
           Serial.read();
           break;
//FIXME: This gets calibration.  There really should also be
//synchronizaition support.
         case 't'://One third frame time.
           frame_ticks*=3;
           frame_ticks_num*=3;
           frame_ticks+=int(frame_ticks_num/frame_ticks_den);
           frame_ticks_num%=frame_ticks_den;
           Serial.println("Frame Time Trippled");
           break;
         case 'T'://Three times frame time
           frame_ticks_num+=frame_ticks_den*(frame_ticks%3);
           frame_ticks_num/=3;
           frame_ticks/=3;
           Serial.println("Frame Time Thirded");
           break;
         default :
           Serial.println("Unknown Command");
      }
   }
}
Last edited by Rufus on Fri Jul 22, 2011 4:29 am, edited 1 time in total.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Getting Started

Post by Rufus »

In order to use this stick you'll need the arduino drivers, and you'll probably want to download and install the Arduino development environment. Basically,, go through Steps 1-5 of http://www.arduino.cc/en/Guide/Windows (the procedure will vary for a mac or linux). If you're assembling your own programmable stick, you should probably go through the blink example to make sure the Arduino is mostly working before you start doing assembly with it. (In the stick that I passed to Wolverine-Master, the Arduino is hidden inside, so 'blink' might not be that useful as a test.)

If the Arduino is already programmed to be a pstick, you should be able to just hit the 'Serial Monitor' button in the IDE to pull up a window with the stick's prompt:
Image
(If the Arduino is not programmed, you can copy & paste the code above into the code editor window, and the go through the steps 'compile','upload',"serial monitor".)

The window should show text something like:
Frame time 16677 ticks and 357/65535
(l)oad,(p)rint,(e)rase,(0-9)run macro,(f)rame ticks,(n)umerator,(d)enominator, (t/T) tripple/third frame time
This indicates that the arduino is connected, and, most likely, running some version of the programmable stick code.

You may now want to test the stick has with the console. So, plug the stick into the console, and run a game with input display support of some kind - like SSFIV - and set things so that the input from the stick will be displayed. Enter the following text into the line at the top of the serial monitor window:
"l0 1.1...2.2...3.3...4.4...5.5...6.6...7.7..8.8...u.u...d.d...l.l...r.r...!0"
and hit enter. With the default button settings, the stick should be sending the top row of buttons (left to right) then the bottom row (also left to right) and then up,down,left, and right. If all the buttons and directions show, then the pstick is working.
Last edited by Rufus on Tue Jul 26, 2011 6:21 pm, edited 4 times in total.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Calibrating the Frame Rate

Post by Rufus »

Between the inconsistencies of the Arduino's clock, and the variable frame rates of games on the Xbox360 (or other systems of this generation), a good first step is to calibrate the frame rate of the pstick to match that of the game. This example is going to be based on my current (as of the time of writing) button mapping and SSFIV, though it shouldn't be too hard to extend to other games and button mappings.

Calibration is a bit easier if the game has a training mode with input display, though it is possible to do without.

Games still run at around 59.94 FPS, so you might as well start by setting the frame time to 16683 give or take a few. The board should boot that way, but...
you can set the frame time in 'ticks' by sending the command "f#####" where ###### are the digits of a number. I haven't had call to go above 17000, but you should be able set it a bit higher without issues.

The board should be sending you back something that looks like:

Code: Select all

Ticks per frame 16684 and 2938/65535
(l)oad,(p)rint,(0-9)run macro,(f)rame ticks,(n)umerator,(d)enominator, (t/T) tripple/third frame time
If you like, you can test things with the following:

Code: Select all

e0 l0 (l.r.)9999!0
This makes the controller to hit left, and then right, for a frame each 9999 times. Depending on the game, you'll probably see your character jiggling back and forth (MvC3 and SF2), or 'frozen' in place (SF4). If you do not, there are more fundamental problems than calibration.

In the games where the character jiggles, you may notice that the wiggling isn't entirely constant. That's because the frame rate of the stick and the frame rate of the game don't match. This calibration will help bring the two closer together. Send the command 'T' to the stick. This will have it cut it's frame time in a third. You should see something like:

Code: Select all

Ticks per frame 16684 and 2938/65535
(l)oad,(p)rint,(0-9)run macro,(f)rame ticks,(n)umerator,(d)enominator, (t/T) tripple/third frame time
T
Ticks per frame 5561 and 22824/65535
(l)oad,(p)rint,(0-9)run macro,(f)rame ticks,(n)umerator,(d)enominator, (t/T) tripple/third frame time
This cuts the ticks per frame on the stick in a third, and it will now send roughly 180 inputs per second to the console.
To make things a little easier later, set the numerator of the fractional part to 1 with the command:

Code: Select all

n1 
(There's a space after the 1)
You should see something like this at the console:

Code: Select all

Ticks per frame 5561 and 22824/65535
(l)oad,(p)rint,(0-9)run macro,(f)rame ticks,(n)umerator,(d)enominator, (t/T) tripple/third frame time
n1 
Ticks per frame 5561 and 1/65535
(l)oad,(p)rint,(0-9)run macro,(f)rame ticks,(n)umerator,(d)enominator, (t/T) tripple/third frame time
Also, you should set up the test macro with:

Code: Select all

e0 l0 ((d.l.r.)9999)9999!
When you execute macro 0, the game will send a long series of very short down, left, and right commands to the stick. Because the games only check the input once per frame, most of these will be ignored. If you have input display on in the game, it will probably show something like

Code: Select all

lrlrlrlrlrlrlrlrlrlrlrdrdrdrdrdrdrdrdrdrdrdrdldldldldldldldl
(This indicates that the frame time you have on the stick is too short.)

Code: Select all

lrlrlrlrlrlrlrlrlrlrlrlddldldldldldldldldldldldrdrdrdrdrdrdrdrdr
(This indicates that the frame time you have on the stick is too long.)

For a variety of reasons, the game will typically not check the input at the same time every frame, so things 'wiggle' a bit, even if the stick is very close to calibrated. As you get closer to the correct ticks per frame, the number of repetitions of 'lr' should get longer, and the input display might pause on 'r', 'l', or 'd'.

If the frame time that you have on the stick is too short (as indicated above) use the 'n' command to set the ticks per frame to a larger number. If the frame time that you have is too long, use the 'f' command to set the ticks per frame to a larger number. Then run macro 0 again, and repeat the adjustment.

Eventually, you should get to a point where you find that some number, say 5559 ticks per frame is too short, and the next number - 5560 - is too long. This means that it's time to set the fractional ticks.

Set the number of ticks per frame to the smaller of the two numbers (in our case 5559). Then use the use the 'n' command to set the numerator of the fractional ticks higher and lower the same way. Start by setting it to 32768, and then raise it if the frame time is too short, or lower it if the frame time is too long.

At this point, you may notice that it's taking longer and longer for the inputs to shift from 'lr' to 'dr' or 'dl'. When the time it takes from the inputs to go from 'lrlrlrlrlrlrlrlr' to 'drdrdrdrdrdrdrdr' is longer than your combo, then the stick is probably calibrated well enough.

To finish calibration, send the 't' command to the stick. This will have it multiply the ticks per frame, and fractional ticks per frame, by 3 so that the frame rate is now back to around 60 frames per second. (I tend to forget this step myself, and then have things not work at all.)
Last edited by Rufus on Tue Aug 23, 2011 3:23 pm, edited 1 time in total.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: Arduino Based Programmable Stick Project

Post by noodalls »

Not that I minded the two being in the same thread, but for clarity as to what refers to what I think it's reasonable to split them up. Cheers Rufus.
wolverine-master
Posts: 579
Joined: Thu Nov 27, 2008 2:10 pm
Location: brooklyn NY

Re: Arduino Based Programmable Stick Project

Post by wolverine-master »

So yea, I tried this thing out.....ima really need your help dude.
I got it working, I think. Whatever character is on screen just taunts....3 seconds later, taunt again.
What do i do from here?
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

It shouldn't be doing anything by default... I'll try to contact you Tue.
wolverine-master
Posts: 579
Joined: Thu Nov 27, 2008 2:10 pm
Location: brooklyn NY

Re: Arduino Based Programmable Stick Project

Post by wolverine-master »

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

Re: Arduino Based Programmable Stick Project

Post by Rufus »

According the USPS, my new Arduino arrived on Friday...but I don't have it my hands yet. :(
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: Arduino Based Programmable Stick Project

Post by noodalls »

Did you get a Mega this time?
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

noodalls wrote:Did you get a Mega this time?
Well, I certainly ordered one.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

noodalls wrote:Did you get a Mega this time?
A shiny new Mega 2560 arrived on Friday.... Time for more refinement.

Todo:
Better frame rate calibration assistance.
User input recording and playback.
More efficient internal format.
Editing?
Better Documentation.
Last edited by Rufus on Wed Aug 24, 2011 2:08 pm, edited 1 time in total.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: Arduino Based Programmable Stick Project

Post by noodalls »

Not sure that it will help, but I put up the code from my latest build in the other thread.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

noodalls wrote:Not sure that it will help, but I put up the code from my latest build in the other thread.
Thanks. But it looks like I'll have to get the thing working right on my new laptop first. :/
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: Arduino Based Programmable Stick Project

Post by noodalls »

One thought that I've been having.

I remember seeing a video on youtube where someone had the yellow video cable attached to the TV, and also to the arduino. This allowed them to sync the arduino to the video output. I thought this might provide an effective way to sync arduino outputs to the refresh rate.

Unfortunately arduino can do TV-out, and this seems to dominate the search terms.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

noodalls wrote: I remember seeing a video on youtube where someone had the yellow video cable attached to the TV, and also to the arduino. This allowed them to sync the arduino to the video output. I thought this might provide an effective way to sync arduino outputs to the refresh rate.
The refresh rate of the video signal and the frame rate of the game can be different.
http://www.youtube.com/watch?v=uA6BjMTcQ7g

The light/dark bands move at the game's frame rate. If it were the same as the refresh, it would be stationary on the video.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project - Button Remapp

Post by Rufus »

To remap the letters/numbers that correspond to the buttons, you can change the code in this section:

Code: Select all

// My buttons...
// A, B, X, Y, LB,RB,LT,RT, ST,BA, D, U,  L, R
   keys['1']=1;
   keys['2']=2;
   keys['3']=3;
   keys['4']=4;
   keys['5']=5;
   keys['6']=6;
   keys['7']=7;
   keys['8']=8;
   keys['s']=9;
   keys['S']=9;
   keys['b']=10;
   keys['B']=10;
   keys['d']=11;
   keys['D']=11;
   keys['u']=12;
   keys['U']=12;
   keys['l']=13;
   keys['L']=13;
   keys['r']=14;
   keys['R']=14;
Directly below it is an example that remaps functions for Noodal's set-up which is commented out.
wolverine-master
Posts: 579
Joined: Thu Nov 27, 2008 2:10 pm
Location: brooklyn NY

Re: Arduino Based Programmable Stick Project

Post by wolverine-master »

I guess I have to respond here.....
In fba-rr, I remapped my macro-lua config to the following: Up-U, Down-D, Left-L, Right-R, 1-Q, 2-W, 3-E, 4-Z, 5-X, 6-C.
What I was asking is can I do that with this project?
I'm going to focus more on arduino and try to push its limits.

With the inconsistencies that I was enduring before, I found out that if something goes wrong at the end of a long sequence it usually causes that inconsistent behavior.
For example, if something works better with 11 frames instead of 10, if I proceed to go ahead with the 10 frames, earlier parts of the combo wouldn't work right as supposed to using the 11 frames which skips from time to time, but runs smoother.
Its like the program is asking me to go back and adjust certain parts.

Is there a way to adjust a sequence and test it without having to erase it every time?
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

This version of the code should have the buttons you want.

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 256

    char macro[MAX_MACRO]; /* Macro Storage */
    int macro_start[10]={-1,-1,-1,-1,-1, -1,-1,-1,-1,-1}; /* Start Locations */
    int next_start=0; /* Pointer to free storage */

    /*
       Character map for interpretation
       Hardcoding this to progmem could save space,
       but makes things less readable.
    */

    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 */
    #define MAX_THREADS 3
    int start[0]; //Macro start pointer
    int cur_pos[3]; //Macro Execution Pointer 0 default, 1&2 are for threads

    /*Frame time.  Defaulting to nominal 59.94 Hz. */
    unsigned long frame_ticks=16683;
    unsigned long frame_ticks_num=22938;
    unsigned long frame_ticks_den=65535;

    boolean var_waiting=false; //Whether there is a wait loop
    int variable_wait=0; //Wait loop value
    int wait=0;

    unsigned long NextFrame;
    unsigned long FractionalTicks;

    #define MAX_LOOPS 4
    int loop_level[3]; //Repeat stack pointer
    int loop_start[3][4];         //Repeat block start list
    int loop_count[3][4];         //Repeat count

    unsigned long held=0; //This may neet per-thread...

    //Pins 0&1 are reserved for Serial Comm, so I can use 0 as null.
    char pins[]={ 6, 7, 9, 8,  4, 5,11,10, 12,13,14,15, 16,17, 0, 0,
                  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0};
    // On the controller this works out to
    // X, Y,RB,LB, A, B,RT,LT, St,Ba, D, U,  L, R

    //For Noodals' setup
    //// Y, Z, A, B,  X, C, Q, P, ST,SL, D, U, L, R,
    //char pins[]={39,41,31,33, 37,35,45,43, 47,51,25,23, 27,29, 0, 0,
    //             38,40,30,32, 36,34,44,42, 46,50,24,22, 26,28, 0, 0};
    //Noodals pin assignments
    //U=23;D=25;L=27;R=29;A=31;B=33;C=35;X=37;Y=39;Z=41;P=43;Q=45;HOM=49;STA=47;SEL=51;
    //u=22;d=24;l=26;r=28;a=30;b=32;c=34;x=36;y=38;z=40;p=42;q=44;hom=48;sta=46;sel=50;


    void loadmacro(int macro_id) {
       int pointer=0;
       int last=-1;
       int paren=0;
       int comment=0;
       
       if(macro_id<0 or macro_id>9) {
          Serial.println("Macro id must be 0-9.  Load failed.");
          return;
       }
       if(macro_start[macro_id]>=0) {
          Serial.println("You must erase first.  Load failed.");
          return;
       }
       if(next_start>=MAX_MACRO) {
          Serial.println("Out of Storage.  Load failed.");
          return;
       }
       Serial.print("You have ");
       Serial.print(MAX_MACRO-next_start);
       Serial.print(" of ");
       Serial.print(MAX_MACRO);
       Serial.print(" chararcters remaining");

       macro_start[macro_id]=next_start;
       pointer=next_start;

       Serial.println("nEnter the Macro Ending in '!':");
       while(pointer<MAX_MACRO) {
          last=Serial.read();
          if(-1 != last) {
             macro[pointer]=last;
             Serial.write(last);
             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");
                   }
                   macro[pointer]=0; //Use null instead of '!' to mark end of macro
                   break;
                }
             }
             if (0 == last) {
                if(paren) {
                   Serial.println("Unmatched ()s");
                }
                break;
             }
             pointer++;
          }
       }
       Serial.write("n");
       pointer++;
       next_start=pointer;
       if(pointer > MAX_MACRO) {
          Serial.println("Macro clipped at end of memory.");
       }
    }

    void printmacro(int macro_id) {
       int pointer;

       if(macro_start[macro_id]<0) {
          Serial.println("Macro not found");
          return;
       }
       pointer=macro_start[macro_id];
       while(macro[pointer]) {
       Serial.write(macro[pointer]);
          pointer++;
       }
       Serial.println("!");
    }

    void erasemacro(int macro_id) {
       int i,pointer,delta;

       if(macro_start[macro_id]<0) {
          Serial.println("Macro not found");
          return;
       }
       pointer=macro_start[macro_id];
       for(pointer=macro_start[macro_id];pointer<MAX_MACRO && macro[pointer];pointer++) {
          macro[pointer]=0; //This should be unnecessary...
       }
       for(delta=pointer-macro_start[macro_id];pointer<MAX_MACRO;pointer++) {
          macro[pointer-delta]=macro[pointer];
       }
       for(pointer-=delta;pointer<MAX_MACRO;pointer++) {
          macro[pointer]=0;
       }
       for(i=0;i<10;i++) {
          if(macro_start[i]>macro_start[macro_id])
             macro_start[i]-=delta;
       }
       next_start-=delta;
       macro_start[macro_id]=-1;
       Serial.print("Macro ");
       Serial.print(macro_id);
       Serial.println(" erased.");
    }

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

    //   Serial.println("grabnumber");

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

    void setpins(unsigned long downpins) {
       int i;

       for(i=0;i<32;i++) {
          if(pins[i]) {
             if(downpins&(1<<i)) {
               digitalWrite(pins[i],LOW);
             } else {
               digitalWrite(pins[i],HIGH);
             }
          }
       }
    //   Serial.println(downpins|1073741824,BIN);
    }

    //macro_pos is the execution pointer.
    //thread is the thread ID (for use with loops)
    //start is the block start position.
    //next is a bitfield pointer.

    boolean stepmacro(int *macro_pos, unsigned long *next, int start, int thread_id) {
       char curchar;
       boolean done=false;
    //   int i;

    /*
       Serial.println("Stepmacro");
       Serial.print("Macro Position ");
       Serial.println(*macro_pos);
       Serial.print("Start ");
       Serial.println(start);
       Serial.print("Thread ID");
       Serial.println(thread_id);
    */
       
       if(Serial.available()) { //Break on user input. -- Maybe sync support later
          Serial.println("Broke on user input");
          return false;
       } else if(wait>0) { //Multiframe wait.
          wait--;
       } else  {
          while(false==done) {
             curchar=macro[*macro_pos];
    //         Serial.print("*macro_pos=");
    //         Serial.println(*macro_pos);
             (*macro_pos)++;
    //         Serial.print("curchar=");
    //         Serial.println(curchar);
             switch (curchar) {
                case '!': //End of Marco - this should no longer happen.
                case 0:
                   Serial.println("End of Macro");
                   return(0);
                   break;
                case 'W':
                case 'w':
                   wait=grabnumber(macro_pos)-1;
                   done=true;
                   break;
                case '.':
                   done=true;
                   break;
                case '+': //These P1/Pn setings do not require threading
                case '-': //so they are ignorable.  I'm not going to bother
                          //with B/F support.
                   break;
                case '<': //Multi-character controls
                   /* FIXME: This needs to be implemented */
                   /* startthreads(*macro_pos) */
                   /* break; */
                   Serial.println("WARNING: No threading support.  Unexpected behavior ahead.");
                case '/': //Multi-character controls
                case '>': //Ends < / / > block.  Should be ignorable.
                   /* FIXME: These are end-of-thread when threading works*/
                   /* if(endthread(thread_id)) { */
                   /*    done=true;  */
                   /* } */
                   Serial.println("WARNING: No threading support.  Unexpected behavior behind?");
                   break;
                case '$': //Save and restore commands.  Not supported.
                case '&':
                   Serial.println("Save and Restore are not supported");
                   grabnumber(macro_pos); //Skipping the state number.
                   break;
                case '?': //Not sure what ??? does.  The variable wait ?
                          //Should be handled by grabnumber and never show up
                   Serial.print(*macro_pos,DEC);
                   Serial.println("??? command is not supported.");
                   break;
                case '#': //Start Comment
                   while(*macro_pos<MAX_MACRO && 0!=macro[*macro_pos] && 10!=macro[*macro_pos] && 13!=macro[*macro_pos]) {
                         (*macro_pos)++;
                   }
                   break;
                case '_': //Hold
                   if(keys[macro[*macro_pos]]) {
                      held=held|(1<<(keys[macro[*macro_pos]]-1));
                   } else {
                      Serial.println(" Ignoring Null Hold");
                   }
                   (*macro_pos)++;
                   break;
                case '^': //Release
                   if(keys[macro[*macro_pos]]) {
                      held=held&(~1<<(keys[macro[*macro_pos]]-1));
                   } else {
                      Serial.println("Ignoring Null Release");
                   }
                   (*macro_pos)++;
                   break;
                case '*': //Release All
                   held=0;
                   break;
                case '(': //Start Loop
                   loop_level[thread_id]++;
                   if(loop_level[thread_id]<MAX_LOOPS) {
                      loop_start[thread_id][loop_level[thread_id]]=*macro_pos;
                   } else {
                      loop_level[thread_id]--;
                      Serial.println("Loop overflow -- undeflow ahead");
                   }
                   break;
                case ')': //End Loop
                   if(loop_level[thread_id]<0) {
                      Serial.println("Loop underflow");
                   } else {
                      if(0==loop_count[thread_id][loop_level[thread_id]]) {//First Iteration
                         loop_count[thread_id][loop_level[thread_id]]=grabnumber(macro_pos);
                      } else {
                         loop_count[thread_id][loop_level[thread_id]]--;
                      }
                      if(loop_count[thread_id][loop_level[thread_id]]>0) {
                         *macro_pos=loop_start[thread_id][loop_level[thread_id]];
                      } else {
                         loop_count[thread_id][loop_level[thread_id]]=0; //This should be unnecessary;
                         grabnumber(macro_pos); //ignore loop count.
                         loop_level[thread_id]--;
                      }
                   }
                   break;
                default : //Single Frame Input
                   if(curchar<128 and keys[curchar]) {
    //                  Serial.println(*next,BIN);
                      *next=*next|(1<<(keys[curchar]-1));
    //                  Serial.println(keys[curchar],DEC);
    //                  Serial.println(pins[keys[curchar]-1],DEC);
    //                  Serial.println(*next,BIN);
                   }
             }
          }
       }
       return true;
    }

    //Stub function for now.  Deals with setting up 'threaded' macro execution
    void startthreads() {
    }

    //Stub function for now.  Deals with ending the current thread.
    void endthread() {
    }

    void runmacro(int macro_id) {
       int i,j;
       unsigned long FractionalTicks;
       unsigned long next=0;

    //   Serial.println("runmacro");

       if(macro_start[macro_id]<0) {
          Serial.println("Macro ");
          Serial.println(macro_id);
          Serial.println(" not found");
          return;
       }
       
    //   Serial.println("runmacro1");
    //Reset execution globals.
       cur_pos[0]=macro_start[macro_id];
       start[0]=macro_start[macro_id];
       for(i=1;i<MAX_THREADS;i++) {
          cur_pos[i]=-1;
          start[i]=-1;
       }
       for(i=0;i<MAX_THREADS;i++) {
          loop_level[i]=-1;
          for(j=0;j<4;j++) { //Probably unnecessary
             loop_start[i][j]=0;
             loop_count[i][j]=0;
          }
       }
       var_waiting=false;
       variable_wait=0;
    //   held=0;
    //   Serial.println("runmacro2");

    /* Set Pins to output */
       for(i=0;i<32;i++) {
          if(pins[i]) {
             pinMode(pins[i],OUTPUT);
             digitalWrite(pins[i],HIGH);
          }
       }
       setpins(next|held);
       NextFrame=micros();
       do {
          NextFrame+=frame_ticks;
          FractionalTicks+=frame_ticks_num;
          if(FractionalTicks>frame_ticks_den) {
             NextFrame+=int(FractionalTicks/frame_ticks_den);
             FractionalTicks%=frame_ticks_den;
          }
    //      Serial.print("Next Frame:");
    //      Serial.println(NextFrame);
    //      Serial.print("Fractional Ticks:");
    //      Serial.println(FractionalTicks);
          if(NextFrame<=frame_ticks) {
             while(micros()>=4294967295-frame_ticks)
                ;
          }
          while(micros()<NextFrame)
             ;
          setpins(next|held);
          next=0;
       } while (stepmacro(cur_pos,&next,start[0],0));
    //Two more frames - one for the last input, one for last stepmacro, and
    //one to clear.  There's bound to be an elegant way to handle this, but at
    //least this is semi-readable.
       for(i=0;i<2;i++) {
          NextFrame+=frame_ticks;
          FractionalTicks+=frame_ticks_num;
          if(FractionalTicks>frame_ticks_den) {
             NextFrame+=int(FractionalTicks/frame_ticks_den);
             FractionalTicks%=frame_ticks_den;
          }
          if(NextFrame<=frame_ticks) {
             while(micros()>=4294967295-frame_ticks)
                ;
          }
          while(micros()<NextFrame)
             ;
          setpins(next|held);
          next=0;
       }

    /* Set pins to input */
       for(i=0;i<32;i++) {
          if(pins[i]) {
             pinMode(pins[i],INPUT);
          }
       }
    }

    long serialreadnum() {
      char buffer[]={0,0,0,0,0,0,0,0,0,0};
      int i=0;
      long input;

      do {
        input=Serial.peek();
        if(47 < input && 58 > input && i<10) {
           buffer[i]=input;
           i++;
           Serial.read();
           Serial.write(input);
        }
      } while (-1==input || (47<input && 58 > input && i<10));

      if(0<i && i<10) {
        Serial.read();
        return (atol(buffer));
      }
      Serial.println("Problem Reading Serial Input Number");
      return -1;
    }

    void setup () {
       int i;
       Serial.begin(115200);
       for(i=0;i<MAX_MACRO;i++) {
          macro[i]=0;
       }

    // My buttons...
    // A, B, X, Y, LB,RB,LT,RT, ST,BA, D, U,  L, R
       keys['Q']=1;
       keys['W']=2;
       keys['E']=3;
       keys['4']=4;
       keys['Z']=5;
       keys['X']=6;
       keys['C']=7;
       keys['8']=8;
       keys['s']=9;
       keys['S']=9;
       keys['b']=10;
       keys['B']=10;
       keys['d']=11;
       keys['D']=11;
       keys['u']=12;
       keys['U']=12;
       keys['l']=13;
       keys['L']=13;
       keys['r']=14;
       keys['R']=14;

       NextFrame=micros()+frame_ticks;
       FractionalTicks=0;
    }

    void loop () {
       int cmd;
       long number;

       Serial.println("");
       Serial.print("Ticks per frame: ");
       Serial.print(frame_ticks);
       Serial.print(" and ");
       Serial.print(frame_ticks_num);
       Serial.print("/");
       Serial.println(frame_ticks_den);
       Serial.println("(l)oad,(p)rint,(e)rase,(0-9)run macro,(f)rame ticks,(n)umerator,(d)enominator, (t/T) tripple/third frame time");
       while(0==Serial.available()) {
          if(NextFrame<=frame_ticks) {
             while(micros()>4294967295-frame_ticks)
                Serial.println(micros());
          }
          while(micros()>NextFrame) {
             NextFrame+=frame_ticks;
             FractionalTicks+=frame_ticks_num;
             if(FractionalTicks>frame_ticks_den) {
                NextFrame++;
                FractionalTicks-=frame_ticks_den;
             }
          }
       }
       if(Serial.available()) {
          cmd=Serial.read();
          switch (cmd) {
             case 'l':
             case 'L':
               Serial.println("Enter macro id to load:");
               number=serialreadnum();
               if(0<=number && number<10) {
                  loadmacro(number);
               } else {
                  Serial.println("Invalid macro id");
               }
               break;
             case 'p':
             case 'P':
               Serial.println("Enter macro id to print:");
               number=serialreadnum();
               if(0<=number && number<10) {
                  printmacro(number);
               } else {
                  Serial.println("Invalid macro id");
               }
               break;
             case '0':
             case '1':
             case '2':
             case '3':
             case '4':
             case '5':
             case '6':
             case '7':
             case '8':
             case '9':
               runmacro(int(cmd-48));
               break;
             case 'e':
             case 'E':
               Serial.println("Enter macro id to erase:");
               number=serialreadnum();
               if(0<=number && number<10) {
                  erasemacro(number);
               } else {
                  Serial.println("Invalid macro id");
               }
               break;
             case 'f':
             case 'F':
               Serial.println("Enter frame tick count:");
               number=serialreadnum();
               if(number) {
                  frame_ticks=number;
               }
               Serial.read();
               break;
             case 'n':
             case 'N':
               Serial.println("Enter frame tick count fractional numerator");
               number=serialreadnum();
               if(number and number<frame_ticks_den) {
                  frame_ticks_num=number;
               }
               Serial.read();
               break;
             case 'd':
             case 'D':
               Serial.println("Enter frame tick count fractional denominator");
               number=serialreadnum();
               if(number and number>frame_ticks_num) {
                  frame_ticks_den=number;
                  FractionalTicks=0;
               }
               Serial.read();
               break;
    //FIXME: This gets calibration.  There really should also be
    //synchronizaition support.
             case 't'://One third frame time.
               frame_ticks*=3;
               frame_ticks_num*=3;
               frame_ticks+=int(frame_ticks_num/frame_ticks_den);
               frame_ticks_num%=frame_ticks_den;
               Serial.println("Frame Time Trippled");
               break;
             case 'T'://Three times frame time
               frame_ticks_num+=frame_ticks_den*(frame_ticks%3);
               frame_ticks_num/=3;
               frame_ticks/=3;
               Serial.println("Frame Time Thirded");
               break;
             default :
               Serial.println("Unknown Command");
          }
       }
    }
There's currently no inline editor on the board.
If you open something like notepad, you can edit there.
That is to say:
Open note pad and start a line with
"e0 l0...r0"
where you replace the "..." with the script. Then you can edit there and cut and paste it into the line on the serial monitor.
...
I could try to set up an editor, but the serial monitor on the arduino IDE is not suitable for having it on the Arduino.
...In English: Changing the script in-line is something I have to think about more.

...
It occurs to me that 'W' is already in use by the scripting language.
noodalls
Posts: 75
Joined: Sun Jul 18, 2010 11:33 am

Re: Arduino Based Programmable Stick Project

Post by noodalls »

Rufus, some people on line are looking to start making programmable sticks, do you mind if I post a copy of your code for them to go through? They wanted something that they could edit on the PC and send commands to the stick, which your program does nicely.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

noodalls wrote:Rufus, some people on line are looking to start making programmable sticks, do you mind if I post a copy of your code for them to go through? They wanted something that they could edit on the PC and send commands to the stick, which your program does nicely.
Go ahead... There's also the C-stamp based stuff:
http://www.pedantic.org/~nate/controller/
CPS2
Posts: 842
Joined: Mon Nov 09, 2009 11:53 pm

Re: Arduino Based Programmable Stick Project

Post by CPS2 »

I'm really confused, I just got one of these http://www.freetronics.com/products/usbdroid and it seems like it should work as a programmable controller, but I haven't even got the thing wired up properly yet. The PCB I'm using is this one http://in2amusements.com.au/images/prod ... 0pcb-1.jpg

The wiring doesn't look complicated, I have 10 wires in output sockets from the arduino controller, each one going a socket on the PS360 PCB, for up/down/left/right and 6 attack buttons. I'm not meant to connect them to the other end of each wire am I? Like directly to the quick disconnect? Anyway I also have the buttons wired into the PS360 PCB as well, they were working fine before I connected the arduino, but now it's acting really strange.

When I have a USB cable plugged into the arduino, it has LP on autofire, and if I hold the LP button on the arcade stick, it stops activating. All the other buttons on the arcade stick seem to work fine. When I unplug the USB cable from the arduino, ANY button I push on the arcade stick, activates EVERY attack button. That's not meant to happen right?

I should probably get it wired correctly before having a go at the arduino program, but I did the blink test and it worked properly, however I haven't got as far as running Rufus's program. I had a go at it, and after pasting the code into the window, then pressing verify, upload and serial monitor, the next window that opened didn't look right...

Anyway, anyone have any ideas how I'm supposed to wire this thing?
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

CPS2 wrote:I'm really confused, I just got one of these http://www.freetronics.com/products/usbdroid and it seems like it should work as a programmable controller, but I haven't even got the thing wired up properly yet. The PCB I'm using is this one http://in2amusements.com.au/images/prod ... 0pcb-1.jpg
It should work just fine as a programmable controller. (You don't need the USB host part.)
The wiring doesn't look complicated, I have 10 wires in output sockets from the arduino controller, each one going a socket on the PS360 PCB, for up/down/left/right and 6 attack buttons. I'm not meant to connect them to the other end of each wire am I? Like directly to the quick disconnect? Anyway I also have the buttons wired into the PS360 PCB as well, they were working fine before I connected the arduino, but now it's acting really strange.
I'm not sure what you mean by 'directly to the quick disconnect'. Electrically speaking, the Arduino should be parallel to the buttons. Wiring directly from the PS360 to the arduino should work. (FWIW directly wiring components into circuits isn't always safe, but I didn't have any problems with my PS360/arduino stuff..)
When I have a USB cable plugged into the arduino, it has LP on autofire, and if I hold the LP button on the arcade stick, it stops activating. All the other buttons on the arcade stick seem to work fine. When I unplug the USB cable from the arduino, ANY button I push on the arcade stick, activates EVERY attack button. That's not meant to happen right?
The autofire/LP thing shouldn't be too much of a surprise. I'd expect that you're getting the LP button held down - not off. You can test this with Cody since holding LP will charge his Zonk Knuckle.
The arduino may well be hooking the wires together while power is off. Just like with a dual mod, you want all of the PCBs to be tied to the same ground and powered up. (You should be able to pull +5 from the PS360 to power the Arduino.)
I should probably get it wired correctly before having a go at the arduino program, but I did the blink test and it worked properly, however I haven't got as far as running Rufus's program. I had a go at it, and after pasting the code into the window, then pressing verify, upload and serial monitor, the next window that opened didn't look right...
Did it produce anything intelligible?
CPS2
Posts: 842
Joined: Mon Nov 09, 2009 11:53 pm

Re: Arduino Based Programmable Stick Project

Post by CPS2 »

The arduino may well be hooking the wires together while power is off. Just like with a dual mod, you want all of the PCBs to be tied to the same ground and powered up. (You should be able to pull +5 from the PS360 to power the Arduino.)
This seems most likely.

So am I supposed to have a wire from the ground terminal on the PS360 pcb connected to the ground terminal on the arduino? That seems like the most likely thing to cause this. I've tried all of the ground terminals on the PS360 pcb and am having the same problem.
Did it produce anything intelligible?
I just had a quick looks and didn't even change the code for my button mappings, but it brought up a window with an input area, and it had an area where it printed messages. I tried typing 1, 2, etc in the input area, but the printed messages actually looked kinda scrambled. I'll try all that again once I get the wiring sorted out tho...

EDIT: actually Rufus, if you're using pretty much the same PCBs that I have, can I see a picture or diagram of how you've wired it? I've never done a dual mod or anything before and don't really get how the grounding or voltage stuff works =/
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

CPS2 wrote: So am I supposed to have a wire from the ground terminal on the PS360 pcb connected to the ground terminal on the arduino? That seems like the most likely thing to cause this. I've tried all of the ground terminals on the PS360 pcb and am having the same problem.
Any of the ground terminals on the PS360 should work.
CPS2 wrote:
Did it produce anything intelligible?
I just had a quick looks and didn't even change the code for my button mappings, but it brought up a window with an input area, and it had an area where it printed messages. I tried typing 1, 2, etc in the input area, but the printed messages actually looked kinda scrambled. I'll try all that again once I get the wiring sorted out tho...
Check the baud rate. It's probably not 115200 by default.

I'm at work right now, but I'll see about getting you a diagram later today. (Probably the small hours your time.)
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

OK, so here's the wiring on my Arduino:
http://www.pedantic.org/~nate/acontroll ... rduino.jpg
http://www.pedantic.org/~nate/acontroll ... SE_PCB.jpg

Note that I'm pulling +5 and Ground from the stick with the black and white wires. The colored wires go to the directions and buttons. Everything is just running straight through.
CPS2
Posts: 842
Joined: Mon Nov 09, 2009 11:53 pm

Re: Arduino Based Programmable Stick Project

Post by CPS2 »

Cool I think I know what I have to do now, the voltage socket on the PS360 appears to be labelled as Vcc, so I'm assuming I connect that to the 5v socket on the arduino. I've already got a ground wire connected to ground sockets on each board. I'm off to that SFxTK event thing, but I'll try this when I get back, hopefully nothing gets fried :)
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

CPS2 wrote:Cool I think I know what I have to do now, the voltage socket on the PS360 appears to be labelled as Vcc, so I'm assuming I connect that to the 5v socket on the arduino. I've already got a ground wire connected to ground sockets on each board. I'm off to that SFxTK event thing, but I'll try this when I get back, hopefully nothing gets fried :)
Yeah, Vcc is the + line.
CPS2
Posts: 842
Joined: Mon Nov 09, 2009 11:53 pm

Re: Arduino Based Programmable Stick Project

Post by CPS2 »

Done :D

It's really late so I'll test the software tomorrow, but I'm pretty sure it's wired correctly now. The arduino powers up without the USB cable plugged in, I've got 10 outputs going to the PS360 from the arduino, and I can use the stick as a normal stick without any repeated or held inputs. Thanks for all your help so far Rufus :)
CPS2
Posts: 842
Joined: Mon Nov 09, 2009 11:53 pm

Re: Arduino Based Programmable Stick Project

Post by CPS2 »

Ok so using the software, I set the baud rate to 115200 and now it looks like it's making sense. Ran into a problem tho, I can type any number into the serial monitor, and it only activates whatever wire I have plugged into the #1 socket. So 1,2,3,4,5,6 etc, all activate the #1 socket, and there's supposedly 13 I/O digital sockets which should be enough, but I have no idea how to get them to work. At least I'm getting some kind of frequent response from the software on the PC and the game now, feels a bit like progress.
Rufus
Posts: 647
Joined: Thu Apr 01, 2010 3:12 am

Re: Arduino Based Programmable Stick Project

Post by Rufus »

CPS2 wrote:Ok so using the software, I set the baud rate to 115200 and now it looks like it's making sense. Ran into a problem tho, I can type any number into the serial monitor, and it only activates whatever wire I have plugged into the #1 socket. So 1,2,3,4,5,6 etc, all activate the #1 socket, and there's supposedly 13 I/O digital sockets which should be enough, but I have no idea how to get them to work. At least I'm getting some kind of frequent response from the software on the PC and the game now, feels a bit like progress.
Pins 1 and 0 are used for communication between the computer and the arduino, but you can use the analog I/O pins to digital I/O. I should probably redo things with a more human-friendly interface...
Post Reply