/* The system below already seems useful and friendly. The user can connect with a serial connection and find out the capabilities of the little arduino machine. We can make this even better by using a bluetooth HC05 module in serial mode to connect wirelessly. Then organise a battery power source, and the little machine becomes and independant talking sensor. The dictionary is stored in flash. using the F() macro to put print strings into flash and thus save SRAM memory. Also, we can put the whole dictionary into flash. See the link above for how to code it. FORTH QUIRKS This system was inspired by forth ideas, so sometimes we use the word "word" instead of "function" or "command". This is not currently a compiling forth system. TIPS use PORT = 0b0010000 (eg) to set a pin high if speed in required. See gunter spanner code. HOW TO COMPILE AND UPLOAD So, this info is based on a linux computer, with all actions undertaken in a bash shell. We dont want to sully ourselves with the arduino java interface thankyou very much. * how to compile and upload ----- Only one .ino file can be in the app/ folder at the moment Go to the books/arduino/app folder. Open MakeFile change tags depending on arduino board and usb port. Type "make". Type ard.up. Type ard.se to get a serial connection to stackmash. ,,, ROADMAP The flash code memory will fill up, quite quickly. Especially because of the help strings. This code seems promising. A data format for EEPROM can be created. promCreate() erase all data and set 1st bytes to 3xyz or similar (this is a signature). Also set the eeprom data pointer to byte after 3xyz. Each data item in eeprom has a 'type' (byte) which indicates the type of data which follows 0 end of data 1 char (byte) 2 unsigned byte (0-255) 3 signed byte 4 unsigned 2 bytes (word) 5 signed 2 bytes counted string 6 hash type (set of pairs) 7 array 8 'pair' ... Also, code 'types' which can be regarded as opcodes 15 relative short jump (byte) JMP 16 relative long jump (word) LJMP 17 following word is dictionary index. 18 next word is colon definition ( TODO - make a 'do last command' key or phrase. eg enter or 'last' - functions: promAppend(char * text) {} - promShow() - signature string, byte, int, etc. - promEdit(editpoint, char * string) { edit point should be a valid string type byte. error if not. check capacity (next byte is string count/capacity). strings are padding with spaces. Check each byte if different before writing (update). } - promNextObject() jumps the current object (string, int, hash etc) and points to object or end of data. - sort out the parseWord/nextWord function. Maybe just parse to machine parse buffer and update the input pointer. - make the bash helper functions better, maybe with an argument such as "due" or "uno", for different boards. IDEAS Use gunter spanners code to create a sin wave for a piezo or speaker and compare to a square wave tone, or sawtooth tone. Write "for next" so we can loop and play tones. A bit tricky because we have no compiling system yet Remove "wrapper" functions to save space. Remove blink duplicate. TIPS Use "unsigned int" or "byte" when looping with small positive integers. Use progmem to keep data in flash memory (see below). BUGS HISTORY 1 july 2023 Will compile one word to ram/eeprom and execute it for testing. Then compile all words in a line and execute. 29 june 2023 Starting to write compile code. wrote "s/ " to search for term in help system. This word reads ahead in the input buffer with parseWord() and updates the machine.inPoint variable. Added input/output words for pins. Tested this on a arduino pro mini mounted on a breadboard, connected to the computer usb port with an FTDI connector. It worked! but need to press reset button on the pro mini after uploading, there is no automatic reset. Investigating HC-05 bluetooth to serial device (with button). 27 june 2023 More work. hc-sr04 distance sensor. write byte to eeprom. machine type with input buffer done. unique word abbreviations done. 23 june 2023 Continuing work on this. Starting to use servos with this code. A machine structure seems a good idea. Servos work better with 5V not 3V3 22 june 2023 Having another look. Wish to create "programmable devices" in stable useful packages for teaching programming. Managed to program the duemilenove board using the bash helper functions. included required parameters in command structure. and check for required params when executing command. - when a bad word is typed, list words starting with/ containing/ or help containing the typed word. Realised that commands need access to input for reading ahead. eg searching for word with s/ . Also, commands are either native or symbolic. native commands are a function pointer to compiled c code. symbolic commands are just a list of pointers (indexes?) to other functions and constants. 9 june 2022 Revisiting this. The system seems simple and good. Would like to read a temperature sensor like LM35. Also, would like to create some semi permanent hardware setup, to allow for developing the code. Set up the hardware so that it is battery powered and using an hc05 bluetooth, so that the unit becomes remote. 28 May 2021 Removed function prototypes. Added capacity in showStack. 27 May 2021 I separated this into its own file "stackmash.ino" from the book books/arduino/arduino-book.txt . In the booklet I was using this as an example of a interactive stack based system with a dictionary (dont mention the word 'forth'). This system already seems useful and user-friendly. Unlike classic forth systems, the dictionary is not a linked list, it is just a c array. Also, wordlists are just a reference to an 'enum' type. Added a wordlist structure so that I can display info about vocabs, and created a tone function. */ #define MAXSTACKLENGTH 20 #define MAXDICTLENGTH 2 #define INBUFFERSIZE 80 // user typed input buffer #define PHRASESIZE 80 // phrase buffer size #define DICTSIZE 20 // some note frequency definitions for the piezo #define NOTE_A1 55 #define NOTE_AS1 58 #define NOTE_B1 62 #define NOTE_C2 65 #define NOTE_CS2 69 #define NOTE_D2 73 #define NOTE_DS2 78 #define NOTE_E2 82 #define NOTE_F2 87 #define NOTE_FS2 93 #define NOTE_G2 98 #define NOTE_GS2 104 #define NOTE_A2 110 #define NOTE_AS2 117 #define NOTE_B2 123 #define NOTE_C3 131 #define NOTE_CS3 139 #define NOTE_D3 147 #define NOTE_DS3 156 #define NOTE_E3 165 #define NOTE_F3 175 #define NOTE_FS3 185 #define NOTE_G3 196 #define NOTE_GS3 208 #define NOTE_A3 220 #define NOTE_AS3 233 #define NOTE_B3 247 #define NOTE_C4 262 #define NOTE_CS4 277 #define NOTE_D4 294 #define NOTE_DS4 311 #define NOTE_E4 330 #define NOTE_F4 349 #define NOTE_FS4 370 #define NOTE_G4 392 #define NOTE_GS4 415 #define NOTE_A4 440 #define NOTE_AS4 466 #define NOTE_B4 494 #define NOTE_C5 523 #define NOTE_CS5 554 #define NOTE_D5 587 #define NOTE_DS5 622 #define NOTE_E5 659 #define NOTE_F5 698 #define NOTE_FS5 740 #define NOTE_G5 784 #define NOTE_GS5 831 #define NOTE_A5 880 #define NOTE_AS5 932 #define NOTE_B5 988 #define REST 0 #include #include // maybe make the dictionary into an object just like the stack? /* global variables, should be in a machine structure a servo motor on pin9. Also could/should have an array of servo objects and corresponding servo pins */ int servoPin = 9; // not constants Servo servoA; // servo object uses 8 bytes of program memory int index = 0; // index into dictionary long value = 0; // signed long integer to push on stack // values for the HC-SR04 hypersonic sensor int sonicTriggerPin = 9; int sonicEchoPin = 10; // Make a simple stack implementation here. // convert code below to use the stack structure. typedef struct stacktype { long data[MAXSTACKLENGTH]; int top; } Stack; /* A definition of an arduino forth stack machine, with 'periferals' included, such as servos, beepers, sensors etc. Each periferal definition takes up data space, so will adjust for each actual device I have made both mm->ip and mm->compPoint integers because I want to be able to compile and execute both sram and eeprom memory (and eventually compile to flash as well). */ typedef struct machinetype { // Servo, and pins // Hypersonic and pins // Beeper // Temp sensor char in[80]; // user input buffer char phrase[40]; // each word or phrase as parsed char anon[80]; // where input is compiled first Stack stack; // all data int ip; // instruction pointer int ipMemType; // what sort of memory is ip pointing to. char * inPoint; // start of next word in machine.in int compPoint; // where next compilation will go. int compMemType; // what sort of memory to compile to.ram/flash/prom int testLedPin; // many arduinos have an on-board LED here int piezoPin; // a piezo buzzer int compile; // if true, then typed commands are // compiled but not executed. // int last; // last executed command, useful? // servos here. //int promData; // pointer to eeprom data (editing) //int storage; // where c! ! and @ fetch should operate } Machine; enum MemoryType { RAM=0, FLASH, EPROM}; enum Wordlist { HELP=0, STACK, IO, TIME, SERVO, TEST, FEEDBACK, SOUND}; typedef struct Vocab { Wordlist type; char name[32]; // display name of the wordlist char help[50]; } Vocablist; // the wordlists are in the same order in this array as // the enum type constants, which allows easy access. // with eg vocabs[HELP] const struct Vocab vocabs[] PROGMEM = { {HELP, "help", "provide help to the user"}, {STACK, "stack", "manipulate the data stack"}, {IO, "i/o", "input/output"}, {TIME, "time", "time/timing related words"}, {SERVO, "servo", "servo motors"}, {TEST, "test", "test the system"}, {FEEDBACK, "feedback", "user feedback (display etc)"}, {SOUND, "sound", "sound, beeps and music etc"} }; typedef struct command { char name[32]; char help[100]; Wordlist vocab; // function pointer signature. void (*action)(const struct command [], Machine *); byte params; // number of required parameters for command // byte immediate; immediate words like char, var, : compile // themselves but not immediate words do not. } Command; //Stack datastack = {{0,0},0}; Machine machine; struct command cc; int size(Stack * ss) { return ss->top; } /* reads the machine input buffer, skips white space and copies the next word into the 'phrase' buffer (if any found). Updates the machine 'input Pointer' to the first character after the next word (or end of inbuffer). This function is called 'word' in traditional forths, but I find that too confusing (since word is also a command or function). A related function is 'parse' which reads the in buffer until a given character (which allows us to implement the forth string printer eg ." print this") Returns a char * pointer NULL=no word found, or phrase buffer */ char * parseWord(Machine * mm) { /* might be better to hand write this, and walk the in buffer char by char rather than using sscanf. eg char * last = ptr+INBUFFERSIZE; // skip leading whitespace while (((*ptr == ' ')||(*ptr =='\t')) && (ptr <= last)) { ptr++; } while ((*ptr != ' ') && (*ptr !='\t') && (ptr <= last)) { // copy char to phraseBuffer ptr++; } machine->inPoint = ptr++; */ char buf[32]; char * ptr = mm->inPoint; if ((sscanf(ptr, "%s", buf)) == 1) { //Serial.print("current word:"); Serial.println(buf); strcpy(mm->phrase, buf); ptr = strstr(ptr, buf); // Find where the current word starts. ptr += strlen(buf); // Skip past the current word. mm->inPoint = ptr; // Serial.print("remaining["); // Serial.print(mm->inPoint); Serial.println("]"); return mm->phrase; } // if no more words, return nothing return NULL; } // shows the name and description for one word void showWordInfo (const struct command * cc) { Serial.print(" ["); Serial.print(cc->name); Serial.print("] "); Serial.println(cc->help); } /* shows help for all words in dictionary containing the given text in their name, or description or else all words if text is empty 'nop' is guaranteed to be the last word in htis dictionary so is used as an end marker. */ void showAllWordInfo (const Command dd[], char * text) { struct command cc; // sizeof(dd) does not give a good result, I think because // dd is in flash memory at this point, not ram. // Serial.print("dict size:"); Serial.println(sizeof(dd)); int ii = 0; strcpy(cc.name, ""); while (strcmp(cc.name, "nop") != 0) { memcpy_P( &cc, &dd[ii], sizeof(cc)); if (strlen(text) == 0) { showWordInfo(&cc); } else if ((strstr(cc.name, text) != NULL) || (strstr(cc.help, text) != NULL)) { showWordInfo(&cc); } ii++; } } // shows names of words in dictionary containing the given // text in their name, or else all words if text is empty void showAllWords (const Command dd[], char * text) { struct command cc; int ii = 0; strcpy(cc.name, ""); while (strcmp(cc.name, "nop") != 0) { memcpy_P( &cc, &dd[ii], sizeof(cc)); if (strlen(text) == 0) { showWordInfo(&cc); } else if (strstr(cc.name, text) != NULL) { showWordInfo(&cc); } ii++; } } // the idea is that these simple functions will save space void sayOk() { Serial.print(F("[ok] ")); } void sayError() { Serial.print(F("[whoops] ")); } // check if the stack contains enough parameters to // execute the command. int checkParams(struct command * c, Machine * mm) { if (c->params > size(&mm->stack)) { sayError(); Serial.print(F("Stack underflow: '")); Serial.print(c->name); Serial.print(F("' needs ")); Serial.print(c->params); Serial.println(F(" parameter(s), ")); Serial.print(F("and stack has ")); Serial.print(size(&mm->stack)); Serial.println(F(" parameter(s). ")); showWordInfo(c); // return false return 0; } // return parameters as a true value return 1; } int isEmpty(Stack * ss) { return size(ss) <= 0; } int isFull(Stack * ss) { return size(ss) >= MAXSTACKLENGTH-1; } void push(Stack * ss, long ll) { if (isFull(ss)) { Serial.println(F("Stack Full!")); return; } ss->data[ss->top] = ll; ss->top++; } long pop(Stack * ss) { if (isEmpty(ss)) { Serial.println(F("Stack Empty!")); return 0; } ss->top--; return ss->data[ss->top]; } void clearStack(const Command dd[], Machine * mm) { if (isEmpty(&mm->stack)) { return; } mm->stack.top = 0; } void drop(const Command dd[], Machine * mm) { if (isEmpty(&mm->stack)) { return; } mm->stack.top--; } void dup(const Command dd[], Machine * mm) { if (isEmpty(&mm->stack)) { return; } if (isFull(&mm->stack)) { sayError(); Serial.println(F(" Stack Full!")); return; } mm->stack.data[mm->stack.top] = mm->stack.data[mm->stack.top - 1]; mm->stack.top++; } void swap(const Command dd[], Machine * mm) { if (size(&mm->stack) < 2) { return; } long ii = pop(&mm->stack); long jj = pop(&mm->stack); push(&mm->stack, ii); push(&mm->stack, jj); } void plus(const Command dd[], Machine * mm) { long ii = pop(&mm->stack); long jj = pop(&mm->stack); push(&mm->stack, ii+jj); } void minus(const Command dd[], Machine * mm) { long ii = pop(&mm->stack); long jj = pop(&mm->stack); push(&mm->stack, ii-jj); } void times(const Command dd[], Machine * mm) { long ii = pop(&mm->stack); long jj = pop(&mm->stack); push(&mm->stack, ii*jj); } /* 7 3 > returns true (-1) in forth, */ void greaterThan(const Command dd[], Machine * mm) { long ii = pop(&mm->stack); long jj = pop(&mm->stack); if (jj > ii) { push(&mm->stack, -1); } else { push(&mm->stack, 0); } } /* Functions relating to eeprom data. I will use a format that begins with a 'signature' of 4bytes 3,'x','y','z' and followed by zero to indicated end of data I may also store compiled forth code in EEPROM which will have the same data format as other data. Arduino Duemilanove has 1K of eeprom */ enum dataTypes { END=0, // a zero indicates the end of data CHAR, // unsigned byte representing an ascii character UBYTE, // unsigned byte integer (0-255) SBYTE, // signed byte integer (-127-128) UWORD, // unsigned word integer WORD, // signed word integer STRING, // forth style counted string HASH, // array of pairs (key:value) ARRAY, // array of any type, and mixed types // type constants related to code JMP, // relative short jump (signed byte integer) LJMP, // relative long jump (signed word integer) DFN, // next 2 bytes is dictionary index NOP // no operation }; /* (c a -- ) store one byte c in EEPROM at address a */ void promByteStore(const Command dd[], Machine * mm) { int address = pop(&mm->stack); byte c = pop(&mm->stack); EEPROM.write(address, c); } /* (a -- c) get one byte c from EEPROM at address a */ void promByteFetch(const Command dd[], Machine * mm) { int address = pop(&mm->stack); byte c = EEPROM.read(address); push(&mm->stack, c); } /* (n -- ) make pin n output */ void setOutput(const Command dd[], Machine * mm) { pinMode(pop(&mm->stack), OUTPUT); } /* (n -- ) make pin n input */ void setInput(const Command dd[], Machine * mm) { pinMode(pop(&mm->stack), INPUT); } /* (n -- ) pull pin n high */ void setHigh(const Command dd[], Machine * mm) { digitalWrite(pop(&mm->stack), HIGH); } /* creates the signature 3xyz at the start of the eeprom sets the next byte to 0 and set promdata pointer to 4th byte of eeprom (?) */ void promCreate(const Command dd[], Machine * mm) { } /* calculated how much free space is available in the eeprom by reading eeprom from beginning until zero byte, or maintain an 'appendPointer' in prom */ void promFreeSpace(const Command dd[], Machine * mm) { } /* reads through a signatured eeprom and displays the data in a usable form */ void promShowData(const Command dd[], Machine * mm) { } /* compiles 1 byte into ram/flash/prom memory and advances the compPoint pointer. But at the moment, I dont know how to write to flash */ void compileByte(byte b, Machine * mm) { } /* compiling dictionary definitions. A 'def' is a word/command that is defined in arduino c++ code. It is called using the index in the dictionary in flash memory. */ void compileDef(int def, Machine * mm) { *(char *)mm->compPoint = DFN; //mm->compPoint[1] = def; // 1 byte, the dictionary index //mm->compPoint += 2; } /* execute one compiled command at the instruction pointer The command could be in RAM or flash or eeprom. Also, the command could be a dictionary reference in any memory a JMP etc instruction or a forth word. Maybe the machine ip instruction pointer, should indicate what sort of memory it is referencing... ram, flash or eeprom.? */ void execCommand(Machine * mm) { // find out where the word is, // increment the instruction pointer. // return. This function only execute one compiled command // not multiple. byte command = 0; if (mm->ipMemType == RAM) { // convert the integer to a character pointer, and then // dereference it. To get the command. command = *(char *)mm->ip; if (command == DFN) { // read the next byte, which is a flash dict index // and execute its function pointer } } } /* display internal state of the stack machine */ void showMachine(Machine * mm) { // ip, in, phrase, inPoint, compPoint, stack } /* using the HC-SR04 hypersonic sensor for distance Also see gunther code with PORTB etc and delay_us code which may be more accurate. 40KHz sound wave, max: 4m, min 2cm, Accuracy 3mm, current: 15mA connections: a arduino 5V to vcc, gnd to gnd, trig to pin9, echo to pin10 Errors are produced if the object is at a big angle to perpendicular. */ /* get echo duration for sr04 */ long sonicEcho() { long duration; // clear trigger pin digitalWrite(sonicTriggerPin, LOW); delayMicroseconds(2); // fire trigger digitalWrite(sonicTriggerPin, HIGH); delayMicroseconds(10); digitalWrite(sonicTriggerPin, LOW); // read echo pin, sound wave travel time in microseconds duration = pulseIn(sonicEchoPin, HIGH); return duration; } /* duration in microseconds of sonic echo */ void sonicDuration(const Command dd[], Machine * mm) { long duration = sonicEcho(); push(&mm->stack, duration); } /* distance in cm to object */ void sonicDistance(const Command dd[], Machine * mm) { long duration; long distance; duration = sonicEcho(); distance = duration * 0.034 / 2; push(&mm->stack, distance); //Serial.print("distance (cm): "); Serial.println(distance); } void watchSonicDistance(const Command dd[], Machine * mm) { long duration; long distance; char key = 0; Serial.println(F("distance to object (q=quit) ")); while (key != 'q') { //while (!Serial.available()) {} key = Serial.read(); duration = sonicEcho(); distance = duration * 0.034 / 2; Serial.print(F("distance: ")); Serial.print(distance); Serial.println(" cm"); delay(500); } } int findWord (const Command dd[], char * aword); // not used void (*fp)(const Command [], Machine * mm); // we dont need function prototypes because the dictionary // is at the end // do we really need this? A flat array seems to be sufficient typedef struct dictionary { struct command contents[MAXDICTLENGTH]; int size; } Dict; // All commands are already checked for sufficient parameters // on the stack by the checkParams function. // returns -1 if word not found, otherwise the index. int findWord (const Command dd[], char * aword) { struct command cc; //int dictsize = sizeof(dd)/sizeof(struct command); int ii = 0; strcpy(cc.name, ""); while (strcmp(cc.name, "nop") != 0) { //memcpy_P( &arraySRAM, &arrayPROGMEM[i], sizeof(acommand)); memcpy_P( &cc, &dd[ii], sizeof(cc)); if (strcmp(cc.name, aword) == 0 ) { return ii; } ii++; } return -1; } /* searchs for all words containing the text in their name or help text. The search term follows the s/ word eg: s/ he will search for all words containing 'he' in their name and help string */ void search (const Command dict[], Machine * mm) { char * text = parseWord(mm); if (text == NULL) { Serial.println("search with s/ "); return; } showAllWordInfo(dict, text); } // add a "containing" parameter so only words containing // text in name or description will be shown void showHelp (const Command dd[], Machine * mm) { Serial.println(F("*Stackmash* commands:")); //showAllWordInfo(dd, ""); showAllWordInfo(dd, ""); } void showStack (const Command dd[], Machine * mm) { Serial.print(F("Stack (")); Serial.print(size(&mm->stack)); Serial.print("/"); Serial.print(MAXSTACKLENGTH); Serial.print(F("): ")); for (int ii = 0; ii < size(&mm->stack); ii++) { Serial.print(mm->stack.data[ii]); Serial.print(" "); } Serial.println(F(" ")); } // this implement the forth dot '.' command which prints // the top stack item and pops it off the stack void printItem (const Command dd[], Machine * mm) { Serial.println(pop(&mm->stack)); } void emit (const Command dd[], Machine * mm) { Serial.println((char)pop(&mm->stack)); } /* this word will set a flag that means that the interpreter will read each word and compile it to ram or eeprom. This should allow us to test the compiling system. This is like the 'state' flag in traditional forths, but I dont think that I will have a state variable in this forth. */ void comp (const Command dd[], Machine * mm) { mm->compile = 1; Serial.println("Compiling commands"); // hit enter on empty line to execute again. } void twodup(const Command dd[], Machine * mm) { if (isEmpty(&mm->stack)) { return; } if (isFull(&mm->stack)) { Serial.println(F("Stack Full!")); return; } mm->stack.data[mm->stack.top] = mm->stack.data[mm->stack.top - 2]; mm->stack.top++; if (isFull(&mm->stack)) { Serial.println(F("Stack Full!")); return; } mm->stack.data[mm->stack.top] = mm->stack.data[mm->stack.top - 2]; mm->stack.top++; } /* testing a servo motor with stackmash. circuit: 5v: red wire, gnd: black/brown wire, other wire to pin 9 (for example), no resistor is required. on the Duemilanove pins 3,5,6,9,10,11 are pulse width modulation (pwm). Which is how servos and ESC motors are controlled. */ void sweep(const Command dd[], Machine * mm) { Serial.print(F("Servo on digital (PWM) pin ")); Serial.print(servoPin); Serial.print(F(", forwards...")); for (int pos = 0; pos <= 180; pos += 1) { servoA.write(pos); delay(15); } Serial.println(F(" and back...")); for (int pos = 180; pos >= 0; pos -= 1) { servoA.write(pos); delay(15); } } /* set servo digital (pwm) pin to top of stack value */ void setServoPin(const Command dd[], Machine * mm) { // servoPin is global value, or could be made part // of machine structure //int pin servoPin = pop(&mm->stack); if ((servoPin < 0) || (servoPin > 60)) { return; } servoA.attach(servoPin); sayOk(); Serial.print(F("Servo attached to pin ")); Serial.println(servoPin); } /* set servo motor to position (0 <= pos <= 180) */ void aimServo(const Command dd[], Machine * mm) { int position = pop(&mm->stack); if ((position < 0) || (position > 180)) { sayError(); Serial.println(F("bad angle")); return; } servoA.write(position); sayOk(); Serial.print(F("Servo on [pwm] pin")); Serial.print(servoPin); Serial.print(F(" set to ")); Serial.println(position); } /* sound and music related things */ // the numbers in the array are durations. negative durations // are dotted notes. int melody[] = { NOTE_E4,-4, REST,8, NOTE_FS4,8, NOTE_G4,-4, REST,8, NOTE_DS4,8, NOTE_E4,-8, NOTE_FS4,8, NOTE_G4,-8, NOTE_C5,8, NOTE_B4,-8, NOTE_G4,8, NOTE_B4,-8, NOTE_E5,8, NOTE_DS5,1, NOTE_D5,2, REST,4, REST,8, NOTE_DS4,8, NOTE_E4,-4, REST,8, NOTE_FS4,8, NOTE_G4,-4, REST,8, NOTE_DS4,8, NOTE_E4,-8, NOTE_FS4,8, NOTE_G4,-8, NOTE_C5,8, NOTE_B4,-8, NOTE_E4,8, NOTE_G4,-8, NOTE_B4,8, NOTE_AS4,2, NOTE_A4,-16, NOTE_G4,-16, NOTE_E4,-16, NOTE_D4,-16, NOTE_E4,-4, REST,4, REST,4, NOTE_E5,-8, NOTE_D5,8, NOTE_B4,-8, NOTE_A4,8, NOTE_G4,-8, NOTE_E4,-8, NOTE_AS4,16, NOTE_A4,-8, NOTE_AS4,16, NOTE_A4,-8, NOTE_AS4,16, NOTE_A4,-8, NOTE_AS4,16, NOTE_A4,-8, NOTE_G4,-16, NOTE_E4,-16, NOTE_D4,-16, NOTE_E4,16, NOTE_E4,16, NOTE_E4,2, }; void playSong(const Command dd[], Machine * mm) { int buzzer = mm->piezoPin; int tempo = 180; int notes = sizeof(melody) / sizeof(melody[0]) / 2; // this calculates the duration of a whole note in ms int wholenote = (60000 * 4) / tempo; int divider = 0, noteDuration = 0; for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) { // calculates the duration of each note divider = melody[thisNote + 1]; if (divider > 0) { // regular note, just proceed noteDuration = (wholenote) / divider; } else if (divider < 0) { // dotted notes are represented with negative durations!! noteDuration = (wholenote) / abs(divider); noteDuration *= 1.5; // increases the duration in half for dotted notes } // we only play the note for 90% of the duration, // leaving 10% as a pause tone(buzzer, melody[thisNote], noteDuration * 0.9); // Wait for the specief duration before playing the next note. delay(noteDuration); // stop the waveform generation before the next note. noTone(buzzer); } } void octaves(const Command dd[], Machine * mm) { // startfreq n -- // play nn octaves starting as int nn = pop(&mm->stack); int freq = pop(&mm->stack); int pin = mm->piezoPin; int dur = 700; while (nn > 0) { tone(pin, freq, dur*0.8); delay(dur); noTone(pin); freq = freq * 2; nn--; } } void tone(const Command dd[], Machine * mm) { // pin freq dur -- long dur = pop(&mm->stack); long freq = pop(&mm->stack); long pin = mm->piezoPin; tone(pin, freq, dur); } /* a440 beep for testing */ void beep(const Command dd[], Machine * mm) { // pin freq dur -- long dur = 1000; long freq = 440; tone(mm->piezoPin, freq, dur); } /* a slide tone */ void slide(const Command dd[], Machine * mm) { // start.freq end.freq dur increment int incr = pop(&mm->stack); int dur = pop(&mm->stack); int endfreq = pop(&mm->stack); int startfreq = pop(&mm->stack); long pin = mm->piezoPin; for (int ff = startfreq; ff < endfreq; ff += incr) { tone(pin, ff, dur); delay(dur); noTone(pin); } } void millis(const Command dd[], Machine * mm) { push(&mm->stack, millis()); } /* does nothing for n milliseconds */ void sleep(const Command dd[], Machine * mm) { delay(pop(&mm->stack)); } /* prints a particular character repeated n times */ void printChars (char c, int repeat) { for (int ii = 0; ii < repeat; ii++) { Serial.print(c); } } /* implements the 'star' word */ void star (const Command dd[], Machine * mm) { int value = pop(&mm->stack); printChars('*', value); Serial.println(); } /* prints a table of ascii chars to serial but serial connection cannot display ascii ? */ void asciiTable (const Command dd[], Machine * mm) { for (int ii = 0; ii < 256; ii++) { if (ii % 12 == 0) { Serial.println(); Serial.print(" "); } Serial.print((char)ii); } } /* create an interesting looking banner for some text */ void banner (char * text, byte indent) { int width = strlen(text); // top box border Serial.println(); printChars(' ', indent); Serial.print(" "); printChars('*', width+2); Serial.print(" "); Serial.println(); // box sides and text printChars(' ', indent); Serial.print("{ "); Serial.print(text); Serial.println(" }"); // bottom box border printChars(' ', indent); Serial.print(" "); printChars('*', width+2); Serial.print(" "); Serial.println(); Serial.println(); } void greeting (const Command dd[], Machine * mm) { banner("Stackmash!", 8); Serial.println(F(" on Arduino Duemilanove")); Serial.println(F(" Atmega328p 32K flash, 8K ram")); Serial.println(F(" Try 'help'")); } void blink(Machine * mm, long duration, long times) { for (int ii = 0; ii < times; ii++) { digitalWrite(mm->testLedPin, HIGH); delay(duration); digitalWrite(mm->testLedPin, LOW); delay(duration); } } // change to blink any led from stack void blink(const Command dd[], Machine * mm) { int times = pop(&mm->stack); int duration = pop(&mm->stack); sayOk(); Serial.print(F("Blinking led (pin ")); Serial.print(mm->testLedPin); Serial.print(F(") ")); Serial.print(times); Serial.print(F(" times, duration ")); Serial.print(duration); Serial.println(F(" ms.")); blink(mm, duration, times); } void printUpTime(const Command dd[], Machine * mm) { unsigned long currentMillis = millis(); unsigned long seconds = currentMillis / 1000; unsigned long minutes = seconds / 60; unsigned long hours = minutes / 60; //unsigned long days = hours / 24; currentMillis %= 1000; seconds %= 60; minutes %= 60; hours %= 24; Serial.print(F("Run-time: ")); if (hours < 10) { Serial.print(F("0")); } Serial.print(hours); Serial.print(F(":")); if (minutes < 10) { Serial.print(F("0")); } Serial.print(minutes); Serial.print(F(":")); if (seconds < 10) { Serial.print(F("0")); } Serial.print(seconds); Serial.println(F(" (h:m:s)")); } void keycode(const Command dd[], Machine * mm) { char key = 0; Serial.println(F("Print serial keycodes (q=quit) ")); while (key != 'q') { Serial.print(F("Press key:")); while (!Serial.available()) {} key = Serial.read(); Serial.print(F(" Code:")); Serial.println((uint8_t) key); } Serial.println(F("Bye.")); } /* just count how many words in dict start with this prefix This is used to execute abbreviations if they are not ambiguous */ int countPrefixWords (const Command dd[], char * prefix) { struct command cc; int ii = 0; int count = 0; strcpy(cc.name, ""); while (strcmp(cc.name, "nop") != 0) { memcpy_P( &cc, &dd[ii], sizeof(cc)); if (strncmp(prefix, cc.name, strlen(prefix)) == 0) { count++; } ii++; } return count; } /* returns the index of first word in the dictionary starting with the given prefix or NULL */ int firstAbbrev (const Command dd[], char * prefix) { struct command cc; int ii = 0; strcpy(cc.name, ""); while (strcmp(cc.name, "nop") != 0) { memcpy_P( &cc, &dd[ii], sizeof(cc)); if (strncmp(prefix, cc.name, strlen(prefix)) == 0) { return ii; } ii++; } return -1; } // list the names of each word/command // add a "containing" parameter so only words containing // text will be shown void listWords (const Command dd[], Machine * mm) { /* dd is actually pointer to first struct */ struct command cc; int ii = 0; strcpy(cc.name, ""); Serial.print(F("All commands:")); while (strcmp(cc.name, "nop") != 0) { memcpy_P( &cc, &dd[ii], sizeof(cc)); if (ii % 12 == 0) { Serial.println(); Serial.print(" "); } Serial.print(cc.name); Serial.print(" "); ii++; } Serial.println(" "); } void nop (const Command dd[], Machine * mm) { return; } const Command dict[] PROGMEM = { {"hi", "gives a greeting", HELP, greeting, 0}, {"help", "show all words and help", HELP, showHelp, 0}, {"words", "just show words", HELP, listWords, 0}, {"s/", "search for words, eg s/ ", HELP, search, 0}, {".s", "show the stack", STACK, showStack, 0}, {".", "print and drop top of stack", STACK, printItem, 1}, {"emit", "print top item as char", STACK, emit, 1}, // todo, this allows testing the compiling system. {"comp", "compile dont execute input", STACK, comp, 0}, {"Ec!", "(c a -- ) store byte c to EEPROM at addr a", STACK, promByteStore, 2}, {"Ec@", "(a -- c) get byte c from EEPROM at addr a", STACK, promByteFetch, 1}, /* input/output functions */ {"output", "set pin n for output", IO, setOutput, 1}, {"input", "set pin n for input", IO, setInput, 1}, {"on", "set pin n high", IO, setHigh, 1}, /* data stack manipulation */ {"clear", "clear the stack", STACK, clearStack, 0}, {"drop", "(n -- ) drop top stack item", STACK, drop, 1}, {"dup", "(n -- n n ) duplicate top stack item", STACK, dup, 1}, {"2dup", "duplicate top 2 stack items", STACK, twodup, 2}, {"swap", "swap top 2 stack items", STACK, swap, 2}, /* arithmetic on the data stack */ {"+", "(x y -- x+y) add top of stack ", STACK, plus, 2}, {"-", "(x y -- x-y) minus top of stack", STACK, minus, 2}, {"*", "(x y -- x*y) multiply", STACK, times, 2}, {">", "(x y -- if x>y, -1)", STACK, greaterThan, 2}, /* piezo sound and music words */ {"tone", "(freq dur -- ) square wave note", SOUND, tone, 2}, {"beep", "a440 beep", SOUND, beep, 0}, {"slide", "(start end dur inc -- ) sliding tones", SOUND, slide, 4}, {"octaves", "(start n-- ) play n octaves", SOUND, octaves, 2}, {"play/song", "a piezo song", SOUND, playSong, 0}, /* time and timing words */ {"sleep", "do nothing for milliseconds", TIME, sleep, 1}, {"ms", "push milliseconds run-time", TIME, millis, 0}, {".time", "print current run-time in minutes+seconds", TIME, printUpTime, 0}, /* drive a servo motor */ {"sweep", "sweep servo on pwm pin", SERVO, sweep, 0}, {"aim", "set servo to position x", SERVO, aimServo, 1}, {"servo/pin", "set servo (pwm) pin", SERVO, setServoPin, 1}, /* commands for hypersonic sensor hc-sr04 */ {"dist", "centimeter distance to object", TEST, sonicDistance, 0}, {"watch/dist", "watch distance change", TEST, watchSonicDistance, 0}, /* use bluetooth to serial device hc-05 or hc-06 */ // todo /* general testing of the system. */ {"keycode", "get and print keycodes", TEST, keycode, 0}, {"star", "print n stars", TEST, star, 1}, {"ascii", "print table of ascii chars", TEST, asciiTable, 0}, {"blink", "(dur repeat --) blinks led on pin 13 'n' times duration d ms", FEEDBACK, blink, 2}, {"nop", "do nothing!", FEEDBACK, nop, 0} }; void initMachine(Machine * mm) { // erase input buffer and phrase buffer mm->in[0] = 0; mm->phrase[0] = 0; // set parse pointer to start of input buffer mm->inPoint = mm->in; mm->compPoint = (int) mm->anon; mm->ip = (int) mm->anon; mm->ipMemType = RAM; } // holds pointer to next parsed word or NULL if none char * oneword = NULL; void setup() { initMachine(&machine); machine.piezoPin = 8; pinMode(machine.piezoPin, OUTPUT); // for hc-sr04 sensor pinMode(sonicTriggerPin, OUTPUT); pinMode(sonicEchoPin, INPUT); machine.testLedPin = 13; pinMode(machine.testLedPin, OUTPUT); Serial.setTimeout(60000); Serial.begin(9600); // make servos part of the machine. servoA.attach(servoPin); while (!Serial); // wait for serial port. Needed for native USB //greeting(dict, &datastack); greeting(dict, &machine); } void loop() { char * end; showStack(dict, &machine); // print the command prompt Serial.print(">"); while (Serial.available() == 0) {} // but it depends on serial monitor settings what is your // line ending. It could be \n etc // this blocks until \r received or timeout. Serial.readStringUntil('\r').toCharArray(machine.in, 80); // remove \r char at end of string if (machine.in[strlen(machine.in)-1] == '\r') { machine.in[strlen(machine.in)-1] = 0; } //Serial.print("Got: ["); Serial.print(machine.in); Serial.println("]"); //help(dict, &datastack); // Read each word. But reading the next word should be a // function so that we can 'read ahead' in the input. Eg // to implement a search function like s/ oneword = parseWord(&machine); while (oneword != NULL) { // Serial.print("word["); Serial.print(oneword); Serial.println("]"); index = findWord(dict, oneword); if (index == -1) { // 'atol' cant distinguish between 0 and error // 10 is the base for the conversion. // convert word to long integer value = strtol(oneword, &end, 10); // still pushing zero on empty input? if (!*end) { // word is an integer, so push it. push(&machine.stack, value); } else { // If the input is a unique abbreviation of a valid // word then execute the word. This saves a lot of // typing... at the serial prompt if (countPrefixWords(dict, oneword) == 1) { index = firstAbbrev(dict, oneword); memcpy_P(&cc, &dict[index], sizeof(cc)); Serial.print("'"); Serial.print(oneword); Serial.print("'"); Serial.print(F(" abbreviates: ")); Serial.println(cc.name); // only execute the command if sufficient items are on the // data stack. if (checkParams(&cc, &machine) == 1) { (*cc.action)(dict, &machine); } } else { // check if it is the name of a vocab, if so // show the vocab description and list words // in that vocab. Serial.print(F("Not sure what {")); Serial.print(oneword); Serial.print(F("} means.")); Serial.println(" "); // here print words starting with/ containing/ or // help containing the typed word. Or just list all // words. Serial.println(F("Did you mean...?")); showAllWords(dict, oneword); } } } else { // not a number, so execute the command code. // the function pointer is in flash (in a command struct) // so has to be copied over to sram. memcpy_P(&cc, &dict[index], sizeof(cc)); // only execute the command if sufficient items are on the // data stack. if (checkParams(&cc, &machine) == 1) { // execute word as a function pointer // some words like s/ need access to the input buffer // so that they can read ahead. This means that the input // buffer is either a global variable or a parameter. (*cc.action)(dict, &machine); // strategy! instead executing immediately, compile to // sram and execute, one by one. Then compile all words // to anon and execute all. Then deal with immediate // words. for/next if/fi/then begin/end } } oneword = parseWord(&machine); // Serial.print("nextword["); // Serial.print(oneword); Serial.println("]"); } // while more words // erase input buffer and reset parse pointer. machine.in[0] = 0; machine.inPoint = machine.in; } // loop