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");
}
}
}