/* terminal.pde - Don Cross http://cosinekitty.com Serial terminal that allows generic reading/writing of hardware pins. */ //------------------------------------------------------------------------------------------------- #define BINARY(a,b,c,d,e,f,g,h) ((a)<<7|(b)<<6|(c)<<5|(d)<<4|(e)<<3|(f)<<2|(g)<<1|(h)) #define BAUDRATE 115200 typedef void (* COMMANDFUNC) (const char *args); const int MAX_COMMAND_LENGTH = 15; const int MAX_COMMAND_SIZE = 1 + MAX_COMMAND_LENGTH; // allows space for '\0' at end of string #define LED_ON() (PORTB |= (1<<5)) #define LED_OFF() (PORTB &= ~(1<<5)) // The number of times timer 0 has overflowed since the program started. // Must be volatile or gcc will optimize away some uses of it. extern volatile unsigned long timer0_overflow_count; // defined in lib/targets/ardino/wiring.c //------------------------------------------------------------------------------------------------- class SerialCommandProcessor { public: SerialCommandProcessor() { reset(); } void feed (char c); protected: void reset() { index = 0; first_space_index = 0; line[0] = '\0'; } void append (char c); void execute(); private: char line [MAX_COMMAND_SIZE]; char index; // number of characters received for current command char first_space_index; // 0 if no spaces; otherwise index of first space (makes verb parsing easier) }; //------------------------------------------------------------------------------------------------- SerialCommandProcessor processor; struct tCommandInfo { const char *verb; COMMANDFUNC func; }; //------------------------------------------------------------------------------------------------- void setup() { Serial.begin (BAUDRATE); DDRB = BINARY(0,0,1,0,0,0,0,0); // -,-,13,12,11,10,9,8 LED_ON(); delay(1000); LED_OFF(); } void loop() { int c = Serial.read(); // check serial port for incoming character if (c != -1) { // did we receive a character? //Serial.println (c, HEX); processor.feed (char(c)); // yes we did... feed the character to the command processor } } //------------------------------------------------------------------------------------------------- bool ScanDigitalPinNumber (char &pin, char x) // digital pins must be in the range 2..d (hex) { pin = 0xff; // init output parameter with invalid value if (x>='a' && x<='d') { pin = x - 'a' + 10; } else if (x>='2' && x<='9') { pin = x - '0'; } else { Serial.println ("? Invalid digital pin number (must be 2..d)"); return false; } return true; } bool ScanAnalogPinNumber (char &pin, char x) { pin = 0xff; // init output parameter with invalid value if (x>='0' && x<='5') { pin = x - '0'; return true; } else { Serial.println ("? Invalid analog pin number (must be 0..5)"); return false; } } bool ScanPwmPinNumber (char &pin, char x) { pin = 0xff; // init output parameter with invalid value if (x>='a' && x<='b') { pin = x - 'a' + 10; } else if (x == '9') { pin = x - '0'; } else { Serial.println ("? Invalid PWM pin number (must be 9..b)"); return false; } return true; } bool ScanHexDigit (int &value, char digit) { if (digit>='0' && digit<='9') { value = digit - '0'; return true; } else if (digit>='a' && digit<='f') { value = digit - 'a' + 10; return true; } else { Serial.println ("? Invalid hex digit"); return false; } } bool ScanHexByte (int &value, const char *string) { int x, y; value = -1; // init output value with invalid value if (ScanHexDigit(x,string[0]) && ScanHexDigit(y,string[1])) { value = (x<<4) | y; return true; } else { return false; } } //------------------------------------------------------------------------------------------------- void Command_id (const char *args) { Serial.println ("Terminal program by Don Cross - http://cosinekitty.com"); } void Command_pin (const char *args) { char pin; if (ScanDigitalPinNumber(pin,args[0])) { if (args[1] == 'i') { pinMode (pin, INPUT); } else if (args[1] == 'o') { pinMode (pin, OUTPUT); } else { Serial.println ("? Invalid digital pin mode (must be 'i' or 'o')"); } } } void RightJustify (long value, char field) { char digits; long temp = value; if (temp == 0) { digits = 1; // special case: zero has 1 digit! } else { digits = 0; while (temp > 0) { temp /= 10; ++digits; } } while (digits++ < field) { Serial.print (" "); } Serial.print (value, DEC); } void Command_ar (const char *args) // analog read { char pin; int value; int temp; if (args[0]) { if (ScanAnalogPinNumber(pin,args[0])) { value = analogRead (pin); Serial.println (value, DEC); } } else { for (pin=0; pin<=5; ++pin) { value = analogRead (pin); RightJustify (value, 5); } Serial.println(); } } void Command_dr (const char *args) // digital read { char pin, value; if (args[0]) { if (ScanDigitalPinNumber(pin,args[0])) { value = digitalRead (pin); Serial.println (value, DEC); } } else { for (pin=2; pin<=13; ++pin) { value = digitalRead (pin); Serial.print (value, DEC); } Serial.println(); } } void Command_dt (const char *args) // digital toggle { char pin; if (ScanDigitalPinNumber(pin,args[0])) { digitalWrite (pin, !digitalRead(pin)); } } void Command_dw (const char *args) { char pin; if (ScanDigitalPinNumber(pin,args[0])) { if (args[1]=='0' || args[1]=='1') { digitalWrite (pin, args[1]-'0'); } else { Serial.println ("? Written value must be 0 or 1"); } } } void Command_pwm (const char *args) { char pin; int value; if (ScanPwmPinNumber(pin,args[0])) { if (ScanHexByte(value,&args[1])) { analogWrite (pin, value); } } } void Command_ms (const char *args) { Serial.println (millis(), DEC); } void Command_t0 (const char *args) { cli(); // turn off interrupts so that timer0_overflow_count will not be modified while we copy its value unsigned long x = timer0_overflow_count; sei(); // re-enable interrupts Serial.println (x, DEC); } void Command_help (const char *args); void Command_loop (const char *args); //------------------------------------------------------------------------------------------------- const tCommandInfo CommandTable[] = { { "ar", Command_ar }, { "dr", Command_dr }, { "dt", Command_dt }, { "dw", Command_dw }, { "help", Command_help }, { "id", Command_id }, { "loop", Command_loop }, { "ms", Command_ms }, { "pin", Command_pin }, { "pwm", Command_pwm }, { "t0", Command_t0 } }; const int COMMAND_TABLE_SIZE = sizeof(CommandTable) / sizeof(CommandTable[0]); void Command_help (const char *args) { Serial.println ("The following commands are available:"); for (int i=0; i < COMMAND_TABLE_SIZE; ++i) { if (i > 0) { Serial.print (", "); } Serial.print ((char *)CommandTable[i].verb); } Serial.println(); } const tCommandInfo *LookupCommand (const char *verb) { for (int i=0; i < COMMAND_TABLE_SIZE; ++i) { if (0 == strcmp(verb, CommandTable[i].verb)) { return &CommandTable[i]; } } return NULL; // could not find the command! } void Command_loop (const char *args) { char i; const int MAX_VERB_LENGTH = 5; char verb [1+MAX_VERB_LENGTH]; if (args[0]) { for (i=0; args[i] && args[i]!=' '; ++i) { if (i == MAX_VERB_LENGTH) { Serial.println ("? verb after 'loop' is too long"); return; } verb[i] = args[i]; } verb[i] = '\0'; const tCommandInfo *command = LookupCommand (verb); if (command) { if (args[i] == ' ') { ++i; } while (Serial.read() != 'q') { (*command->func) (&args[i]); delay(999); // everything else takes about 1 millisecond } Serial.println (); Serial.println ("Looping canceled."); } else { Serial.println ("? unknown command after 'loop'"); } } else { Serial.println ("? missing command after 'loop'"); } } //------------------------------------------------------------------------------------------------- void SerialCommandProcessor::feed (char c) { switch (c) { case '\r': case '\n': case ';': // Arduino serial monitor does not send carriage returns! if (index > 0) { // ignore empty command lines execute(); } break; case 8: // backspace if (index > 0) { line[--index] = '\0'; if (first_space_index == index) { first_space_index = 0; // cancel the space character } } break; case '\0': // ignore these characters break; case '\t': case ' ': if (index > 0) { // ignore leading spaces if (line[index-1] != ' ') { // do not append more than one space in a row if (first_space_index == 0) { first_space_index = index; // remember where first space was, so we can easily patch for verb terminator } append(' '); } } break; default: append(c); break; } } void SerialCommandProcessor::append (char c) { if (index < MAX_COMMAND_LENGTH) { line[index++] = c; line[index] = '\0'; // always keep null terminated } else { Serial.println ("? command too long"); reset(); } } void SerialCommandProcessor::execute() { const char *args; if (first_space_index) { line[first_space_index] = '\0'; args = &line[1 + first_space_index]; } else { args = &line[index]; // point to '\0', thus passing "" as args string } const tCommandInfo *command = LookupCommand (line); if (command) { (*command->func) (args); } else { Serial.println ("? unknown command"); } reset(); } //------------------------------------------------------------------------------------------------- /* $Log: terminal.pde,v $ Revision 1.13 2007/02/16 14:33:06 dcross cleaned up for publication in Arduino forum Revision 1.12 2006/12/25 23:37:04 dcross In ar command, display each analog pin's value right-justified in a 5-character field, so that when we use the loop command on ar, the numbers all line up. Also, this could make it easier for some programs to scan the integers, because now they will all be in the same location on each line. Print an extra line before printing "Looping canceled.", so we don't see "qLooping canceled.". On each iteration, delay 999 ms instead of 1000 ms, because printing output takes about 1 ms anyway. Program size: 5958 bytes. Revision 1.11 2006/12/25 03:10:23 dcross Added command dt, for toggling the current state of any digital pin. Revision 1.10 2006/12/25 03:04:15 dcross Added loop command, but then I ran into problems with running out of SRAM (crashes and weird behavior). So I turned off the morse code stuff to get back more SRAM and it works now. Program size: 5716 bytes. Revision 1.9 2006/12/24 23:33:29 dcross Added ability to send Morse Code to pin 13. Code can be included/excluded via #define SUPPORT_MORSE. Increased maximum command length to 63 characters. Commented out the spew command. Added t0 command to directly display the timer0_overflow_counter. 6120 bytes (with SUPPORT_MORSE defined to 1). 5448 bytes (with SUPPORT_MORSE defined to 0). Revision 1.8 2006/12/24 03:02:51 dcross Added spew command, which transmits characters until the character 'q' is received. Added support for backspace key, though there is a bug: if repeated consecutive spaces are entered by the user, the backspace is confusing. Program size: 5414 bytes. Revision 1.7 2006/12/24 00:56:30 dcross Added ms command, which displays the current real-time clock in milliseconds. Program size: 5284 bytes. Revision 1.6 2006/12/24 00:50:25 dcross Added skimpy help command... all it does is list the verbs. Print an error message when an invalid hex digit is found, instead of just quietly ignoring the bad command. Program size: 5258 bytes. Revision 1.5 2006/12/24 00:39:49 dcross Added pwm command for setting Pulse Width Modulation on pins 9..11. Program size: 5102 bytes. Revision 1.4 2006/12/24 00:18:28 dcross Allow either '\r' or '\n' to end a command. Ignore empty commands, which also handles getting both '\r' and '\n' at the end of a command. Program size: 4712 bytes. Revision 1.3 2006/12/24 00:02:56 dcross Added ability to read all digital or analog pins by not passing parameters to dr or ar commands. Program size: 4706 bytes. Revision 1.2 2006/12/23 23:54:04 dcross Now we have commands ar, dr, dw, id, pin. Program size: 4570 bytes. Revision 1.1 2006/12/23 22:37:25 dcross First version of program: the only command is 'id'. */