Simple command line interpreter.

I would like for a user to be able to plug into the usb port of the arduino and interrogate it. Help cr should bring up a short list of commands that can be issued.

Has anyone done anything similar please? Its not laziness , im just trying to save time.

1 Like

Yep, I have done exactly that:

There’s no example at the moment, but this the code of a project I use it in (it won’t work for you as it’s written for the PIC32, but you can pull out the CLI portions of it):

#pragma parameter extra.flags=-DCDCACM_MAN="Majenko-Technologies" -DCDCACM_PROD="Manlab" -DCDCACM_SER="World-Domination-Edition" -DCDCACM_PID=0xA663 -DCDCACM_VID=0x1781

#include <DebouncedInput.h>
#include <Output.h>
#include <PIC32.h>
#include <CLI.h>

/*
 * IO0	FPIO0 (OC, SPI)
 * IO1	FPIO1 (OC, UART, I2C, SPI)
 * IO2	FPIO2 (OC, UART, I2C, SPI)
 * IO3	FPIO3 (OC)
 * IO4	SS 3
 * IO5	SS 2
 * IO6	+3.3V Relay
 * IO7	+12V Relay
 * IO8	+5V Relay
 * IO9	-12V Relay
 * IO10	Amplifier Relay
 * IO11	Left Pedal
 * IO12	Right Pedal
 * IO13	Amplifier Input
 * IO14 SS 1
 * IO15 SPI Bus
 * IO16 SPI Bus
 * IO17 SPI Bus
 * 
 * SPI Bus:
 * 3 SS pins connected to 3-to-8 decoder - outputs are:
 * 0: Deselected
 * 1: IO expanders
 * 2: DAC
 * 3: 
 * 4: 
 * 5: 
 * 6: 
 * 7: 
 */

DebouncedInput btnMinus12(S0, 20, true);
DebouncedInput btnPlus12(S1, 20, true);
DebouncedInput btnPlus5(S2, 20, true);
DebouncedInput btnPlus33(S3, 20, true);
DebouncedInput btnAmp(IO13, 20, true);

// Clever trick this - two buttons on the same input
// but with very different debounce times.
DebouncedInput progButton(S4, 3000, true);
DebouncedInput killSwitch(S4, 20, true);

Output rlyMinus12(IO9, LOW);
Output rlyPlus12(IO7, LOW);
Output rlyPlus5(IO8, LOW);
Output rlyPlus33(IO6, LOW);
Output rlyAmp(IO10, LOW);

CLI_COMMAND(help);
CLI_COMMAND(relay);
CLI_COMMAND(status);
CLI_COMMAND(bootload);

void rlyMinus12Report(uint8_t state) {
		CLI.println(state ? "\r\n%RLY:-12:ON" : "\r\n%RLY:-12:OFF");
}

void rlyPlus12Report(uint8_t state) {
		CLI.println(state ? "\r\n%RLY:+12:ON" : "\r\n%RLY:+12:OFF");
}

void rlyPlus5Report(uint8_t state) {
		CLI.println(state ? "\r\n%RLY:+5:ON" : "\r\n%RLY:+5:OFF");
}

void rlyPlus33Report(uint8_t state) {
		CLI.println(state ? "\r\n%RLY:+33:ON" : "\r\n%RLY:+33:OFF");
}

void rlyAmpReport(uint8_t state) {
		CLI.println(state ? "\r\n%RLY:AMP:ON" : "\r\n%RLY:AMP:OFF");
}

void uc(char *s) {
	char *p;
	for (p = s; *p; p++) {
		*p = (*p >= 'a' && *p <= 'z') ? *p - 32 : *p;
	}
}

int leftPedal = 0;
int rightPedal = 0;

void setup() {
	progButton.begin();
	btnMinus12.begin();
	btnPlus12.begin();
	btnPlus5.begin();
	btnPlus33.begin();
	btnAmp.begin();

	rlyMinus12.begin();
	rlyPlus12.begin();
	rlyPlus5.begin();
	rlyPlus33.begin();
	rlyAmp.begin();

	rlyMinus12.onChange(rlyMinus12Report);
	rlyPlus12.onChange(rlyPlus12Report);
	rlyPlus5.onChange(rlyPlus5Report);
	rlyPlus33.onChange(rlyPlus33Report);
	rlyAmp.onChange(rlyAmpReport);

	Serial.begin(460800);
	CLI.addClient(&Serial);
	CLI.addCommand("help", &help);
	CLI.addCommand("relay", &relay);
	CLI.addCommand("status", &status);
	CLI.addCommand("bootloader", &bootload);
	leftPedal = analogRead(IO11) >> 2;
	rightPedal = analogRead(IO12) >> 2;
}

void loop() {
	uint8_t v;
	char input[1024];
	char *argv[20];
	int argc;
	char *w;
	int av;
	static unsigned long avtick = millis();

	CLI.process();

	if (millis() - avtick > 50) {
		avtick = millis();
	
		av = analogRead(IO11) >> 2;
		if (av < 25) av = 0;
		if (av != leftPedal) {
			leftPedal = av;
			CLI.print("%PDL:LFT:");
			CLI.println(leftPedal);
		}
	
		av = analogRead(IO12) >> 2;
		if (av < 25) av = 0;
		if (av != rightPedal) {
			rightPedal = av;
			CLI.print("%PDL:RGT:");
			CLI.println(rightPedal);
		}
	}
	
	if (progButton.read() == LOW) {
		bootloader();
	}

	if (killSwitch.changedTo(LOW)) {
		rlyMinus12.low();
		rlyPlus12.low();
		rlyPlus5.low();
		rlyPlus33.low();
	}

	if (btnMinus12.changedTo(LOW)) {
		rlyMinus12.toggle();
	}

	if (btnPlus12.changedTo(LOW)) {
		rlyPlus12.toggle();
	}

	if (btnPlus5.changedTo(LOW)) {
		rlyPlus5.toggle();
	}

	if (btnPlus33.changedTo(LOW)) {
		rlyPlus33.toggle();
	}

	if (btnAmp.changedTo(LOW)) {
		rlyAmp.toggle();
	}
}

CLI_COMMAND(help) {
	dev->println("No, I won't help you!");
	return 0;
}

CLI_COMMAND(relay) {
	if (argc < 2 || argc > 3) {
		dev->println("Usage: relay <name> [<state>]");
		return 10;
	}

	uc(argv[1]);

	if (argc == 2) {
		if (strcmp(argv[1], "-12") == 0) {
			dev->println(rlyMinus12.getState() ? "%RLY:-12:ON" : "%RLY:-12:OFF");
			return 0;
		}
		if (strcmp(argv[1], "+12") == 0) {
			dev->println(rlyPlus12.getState() ? "%RLY:+12:ON" : "%RLY:+12:OFF");
			return 0;
		}
		if (strcmp(argv[1], "+5") == 0) {
			dev->println(rlyPlus5.getState() ? "%RLY:+5:ON" : "%RLY:+5:OFF");
			return 0;
		}
		if (strcmp(argv[1], "+33") == 0) {
			dev->println(rlyPlus33.getState() ? "%RLY:+33:ON" : "%RLY:+33:OFF");
			return 0;
		}
		if (strcmp(argv[1], "AMP") == 0) {
			dev->println(rlyAmp.getState() ? "%RLY:AMP:ON" : "%RLY:AMP:OFF");
			return 0;
		}
		dev->println("Name must be -12, +12, +5, +33 or amp");
		return 10;
	}

	uint8_t state = 255;

	if (strcmp(argv[2], "on") == 0) {
		state = 1;
	}

	if (strcmp(argv[2], "off") == 0) {
		state = 0;
	}

	if (state == 255) {
		CLI.println("State must be on or off");
		return 10;
	}

	if (strcmp(argv[1], "-12") == 0) {
		rlyMinus12.set(state);
		return 0;
	}
	if (strcmp(argv[1], "+12") == 0) {
		rlyPlus12.set(state);
		return 0;
	}
	if (strcmp(argv[1], "+5") == 0) {
		rlyPlus5.set(state);
		return 0;
	}
	if (strcmp(argv[1], "+33") == 0) {
		rlyPlus33.set(state);
		return 0;
	}
	if (strcmp(argv[1], "AMP") == 0) {
		rlyAmp.set(state);
		return 0;
	}
	dev->println("Name must be -12, +12, +5, +33 or amp");
	return 10;

}

CLI_COMMAND(status) {
	dev->println(rlyMinus12.getState() ? "%RLY:-12:ON" : "%RLY:-12:OFF");
	dev->println(rlyPlus12.getState() ? "%RLY:+12:ON" : "%RLY:+12:OFF");
	dev->println(rlyPlus5.getState() ? "%RLY:+5:ON" : "%RLY:+5:OFF");
	dev->println(rlyPlus33.getState() ? "%RLY:+33:ON" : "%RLY:+33:OFF");
	dev->println(rlyAmp.getState() ? "%RLY:AMP:ON" : "%RLY:AMP:OFF");
	dev->print("%PDL:LFT:"); dev->println(leftPedal);
	dev->print("%PDL:RGT:"); dev->println(rightPedal);
	return 0;
}

CLI_COMMAND(bootload) {
	bootloader();
}

Here is an example of code that runs as a monitor program on an Arduino. It handles some things like backspace etc entered by the user on the PC then parses the command line and executes one of the many functions.

This is a very large program, I have cut out some of the parts, hopefully enough to give you an idea.

typedef struct command {
	char	cmd[10];
	void	(*func)	(void);
};

command commands[] = {
	{"rd",		Cmd_RD},
	{"rdw",		Cmd_RDW},
	{"wr",		Cmd_WR},
	{"l",		Cmd_L},
	{"p",		Cmd_P},
	{"regs",	Cmd_REGS},
	{"io",		Cmd_IO},
	{"f",		Cmd_F},
	{"sp",		Cmd_SP},
	{"per",		Cmd_PER},
	{"watch",	Cmd_WATCH},
	{"rst",		Cmd_RST},
	{"ipc",		Cmd_IPC},
	{"set",		Cmd_SET},
	{"dig",		Cmd_DIG},
	{"an",		Cmd_AN},
	{"pin",		Cmd_PIN},
	{"pc",		Cmd_PC},
	{"info",	Cmd_INFO},
	{"/",		Cmd_HELP}
};
...
void	Cmd_WR () {  // example of one of the commands
	/////////////////////////////////////////////////////////
	// write a value to target RAM
	int addr = GetAddrParm(0);
	byte val = GetValParm(1, HEX);
	if (SetTargetByte (addr, val) != val)
		Serial << "Write did not verify" << endl;	
}
...
void	loop() {
	char c;

	if (Serial.available() > 0) {
		c = Serial.read();
		if (ESC == c) {
			while (Serial.available() < 2) {};
			c = Serial.read();
			c = Serial.read();	
			switch (c) {
				case 'A':  // up arrow
					// copy the last command into the command buffer
					// then echo it to the terminal and set the 
					// the buffer's index pointer to the end 
					memcpy(cmd_buffer, last_cmd, sizeof(last_cmd));
					cmd_buffer_index = strlen (cmd_buffer);
					Serial << cmd_buffer;
					break;
			}
		} else {
			c = tolower(c);
			switch (c) {
				
				case TAB:   // retrieve and execute last command
					memcpy(cmd_buffer, last_cmd, sizeof(cmd_buffer));
					ProcessCommand ();
					break;

				case BACKSPACE:  // delete last char
					if (cmd_buffer_index > 0) {
						cmd_buffer[--cmd_buffer_index] = NULLCHAR;
						Serial << _BYTE(BACKSPACE) << SPACE << _BYTE(BACKSPACE);
					}
					break;

				case LF:
					ProcessCommand ();
					Serial.read();		// remove any following CR
					break;
				
				case CR:
					ProcessCommand ();
					Serial.read();		// remove any following LF
					break;

				default:
					cmd_buffer[cmd_buffer_index++] = c;
					cmd_buffer[cmd_buffer_index] = NULLCHAR;
					Serial.print (c);
			}
		}
	}
}

plug into the usb port of the arduino and interrogate it.

Exactly what the program does, although it gets the data from a second Arduino that only runs a small handler. But you can read/write memory, registers, read/write pins etc.

I can post the entire thing if it helps, it’s 2700 lines though :slight_smile:


Rob

I forgot the func that processes the user’s command

void	ProcessCommand () {
	int cmd, per;
	peripheral p;
	
	/////////////////////////////////////////////////////
	// any command will kill the watching feature
	watching = false;

	Serial.println("");

	/////////////////////////////////////////////////////
	// trap just a CRLF
	if (cmd_buffer[0] == NULLCHAR) {
		Serial.print("AVR> ");
		return;
	}
	
	/////////////////////////////////////////////////////
	// save this command for later use with TAB or UP arrow
	memcpy(last_cmd, cmd_buffer, sizeof(last_cmd));
	
	/////////////////////////////////////////////////////
	// Chop the command line into substrings by
	// replacing ' ' with '\0' 
	// Also adds pointers to the substrings 
	SplitCommand(); 
	
	/////////////////////////////////////////////////////
	// Scan the command table looking for a match
	for (cmd = 0; cmd < N_COMMANDS; cmd++) {
		if (strcmp (commands[cmd].cmd, (char *)cmd_buffer) == 0) {
			commands[cmd].func();
			goto done;
		}
	}

	/////////////////////////////////////////////////////
	// If we didn't find a valid command scan the peripherals 
	// table looking for a match with the entered command
	for (per = 0; per < N_PERIPHERALS; per++) {
		memcpy_P(&p, &peripherals[per], sizeof (p));
		if (strcmp (UCASE_STR(cmd_buffer), p.name) == 0) {
			// we found a match, point the first parm pointer 
			// to the command and call Cmd_PER to do the work
			// Cmd_PER will use parms[0] to find the peripheral
			// to dump
			parms[0] = cmd_buffer;
			n_parms = 1;
			Cmd_PER();
			goto done;
		}
	}
	
	/////////////////////////////////////////////////////
	// if we get here no valid command was found
	Serial << "wtf?" << endl;

	done:
	cmd_buffer_index = 0;
	cmd_buffer[0] = NULLCHAR;
	Serial << "AVR> ";
}

Note that if the command is “invalid” (not in the command table) it scans various lists of registers, so for example the user can type

TCCR0A

to read one of the timer registers.


Rob

Has anyone done anything similar please?

Essentially, the serial scientific calculator does about the same thing... You would need to change the "block nature" of the commands (all 3 char wide) but that no big deal.

http://forum.arduino.cc/index.php/topic,147550.0.html