a simple command interpreter

Go To Last Post
23 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi All! :-)
I have written a very simple command interpreter that takes commands from the USART. It is working as I expect, but it would be really nice to get your inputs about making it better/cleaner/extendable.
Here's how it works:
For a command, I type # followed by the command name at the hyperterminal. All strings terminate with a / char. e.g. #on/ is a command to turn on an LED, #off/ turns it off.
The # char is to distinguish a command from a normal string. e.g. myname/ is just a string.
(more later on why I chose to terminate with a / instead of a newline or return. Not too relevant, for now).

The code is as follows:

/*cmd.c. implementing a simple command interpreter*/
#include 
#include 
#include 
#include 

int main(){
DDRC= 0xff; //LEDs on PORTC for debugging
sei();
bash_init();
char *tempstr= 0;

	for(;;){
		if(temp==1){//a '/' terminated string received. time to respond!
			temp=0;
			PORTC = 0x01;//the outputs are active HIGH. turns on LED 1.
			if(str[0]=='#'){//first char is a #, so this is a command.
			PORTC |= 0x02;//turns on LED 2
			tempstr= str; //copy the command entered 
			tempstr++;;   //minus the # char
			}
			if(!strcmp(tempstr,"on")){
			PORTC |= 0x04;//turns on LED 3
			tempstr= 0;
			}
			else if(!strcmp(tempstr,"off")){
			PORTC = 0x00; //turns off all LEDs
			tempstr= 0;
			}
		}
	}
}

/*bashcom.h: contains declarations of UART functions and macros*/
#define F_CPU 16000000
#define BAUD_RATE 9600
#define BAUD_PRESCALE ((F_CPU/(BAUD_RATE * 16UL)) - 1)

volatile char str[15];
volatile int temp;

void bash_init();
void putc_to_bash(char char_to_display);
void puts_to_bash(char* string_to_display);

/*bashcom.c. contains USART funtions*/
#include 
#include 
#include 

/*int main(void) //the main function. just in case I need to test it later...
{

bash_init();

sei();
	for(;;){
	if(temp == 1){
	puts_to_bash(str);
	temp= 0;
	}
	}
}*/

void bash_init(){//init the USART
UCSRB |= (1<<RXEN) | (1<<TXEN);
UCSRC= 0x00;
UCSRC |= (1<<URSEL) | (1<<UCSZ0) | (1<<UCSZ1);//8 bit mode.
UBRRH |= (BAUD_PRESCALE >> 8);
UBRRL |= BAUD_PRESCALE; //prescaler set for 9600 baud
UCSRB |= (1<<RXCIE);
temp= 0;
}

ISR(USART_RXC_vect){
static int count;//number of chars received

char RxByte;
RxByte = UDR;
	if(!(RxByte == '/')){
	str[count]= RxByte;//storing characters in str array.
	count++;
	}
	else if((RxByte == '/') || (count==13)){
	str[count]= '\0';
	count= 0;
	temp= 1;//got a '/' terminated string. tell anyone who cares to know
	}
}

void puts_to_bash(char* string){//writes an entire string to bash
	while(!((*string) == '\0')){
	putc_to_bash(*string);
	string++;
	}
	temp= 0;
}

void putc_to_bash(char dispchar){//writes single chars to bash
	while(!(UCSRA & (1<<UDRE)));
UDR= dispchar;
}

Bit 3 of PORTC turns on when I type #on/ and all bits turn off with #off/. So far so good!:-)

Could you please suggest how can I add more commands and still keep it very organized? One option is to simply use a lot of if else statements, but I think it just may not be as elegant.
Also, if there are any serious mistakes in the code, including BAD code practices, please comment.
All suggestions are most welcome.

Thank you for your time! :-)

Cheers,
Aashish

P.S. The code is for atmega16 running off a 16MHz xtal and a 5V supply.

-Aashish.
If you don't see it coming, you'll never know what hit you...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's pretty late at night here, so I'm not going to look at your code now, but I will give you a quick rundown of how I would probably write it from scratch. There are as many ways to do this as there are programmers, and there's obviously no "best" way.

I'll assume that the only thing coming in on the serial line are the commands, although a given command may consume additional input characters.

I'd create a structure that holds a string (the command) and a function pointer. Then I'd declare an array of these, in flash, and populate it with the initial values. This would be the command name ("command1") and a pointer to a function that will be called when that command is received. There would also be a declared constant that would define how many of these structures were in the array. I'd put the array in flash so it didn't take any RAM.

Then I'd write an input routine that accepted serial characters and stuck them in a circular buffer. The buffer would be long enough to hold the longest command, plus a little, and would overwrite the oldest character. This input routine could be UART interrupt driven, or could be timer driven, or could be called regularly in the main loop to keep it going. I'd probably use UART interrupts.

When the terminating character comes in, the input routine would then check the buffer against all the commands in the command array. If it finds a match, it calls the routine associated with that command. If you are using interrupts to get the input, then you don't want to call the command service function within in the ISP. Instead, you'd set a flag indicating that a certain command has been received, then the mainline would call the associated function. Also, once the input routine has recognized a command, it clears the input buffer.

There are lots of details I've left out, but hopefully you get the main idea. It's easy to expand, doesn't require much on the part of the mainline once it's set up, and doesn't take any RAM except the buffer. The way I've outlined it assumes a fixed set of commands, all of which are always active. Making your structure a little more complex would allow you to have tiers or hierarchies of commands with them being sometimes active and sometimes not.

Chuck Baird

"I wish I were dumber so I could be more certain about my opinions. It looks fun." -- Scott Adams

http://www.cbaird.org

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For text command strings that can have a varying length I pretty much have to agree with Chuck how it should be done.

For very simple stuff I may use one-letter commands, so just pressing 'A' would turn led on and 'a' would turn led off, this is easily put into switch-case logic, to either run a function or just setting the IO pin there. Usually letters available on a keyboard is sufficiently large for debugging a lot of stuff at once, and if it is not, then remove the things that already work.

It brings in a whole new level of complexity if commands can have a parameter or several parameters, like "led 1" or "led 0", although they are complete strings too like "led on" or "led off".

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@chuck: Thanks for all that description! I will try and implement the structure and an array of such structures as you mention. However, could you please elaborate on the circular buffer for receiving the command part? I have read about the idea elsewhere too but I couldn't really understand the advantage over a simple character array. Some explanation of it would be a great help, if it's not too much trouble. :-)
I will restructure my code and post it again later.

@Jepael: I agree that commands with 'parameters' would be too complex. I don't wish to go that far either.
I just wrote this as an exercise (not a school/lab project :-) ) to myself. Learnt a few things on the way, such as why is it important to keep shared variables 'volatile'. It's fun! :-)

Thanks for the prompt response, people!

-Aashish.
If you don't see it coming, you'll never know what hit you...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

However, could you please elaborate on the circular buffer for receiving the command part? I have read about the idea elsewhere too but I couldn't really understand the advantage over a simple character array.

Ring buffers are used with interrupt receive routines. They guarantee that received characters are not missed whatever "long job" the main code may be doing (with the proviso that if more characters arrive than the length of the buffer the oldest will be lost). The idea is to have an array (often the size is a power of 2 - maybe [16] or [32]?). You also maintain two "pointers" to the buffer. These can either be true pointers (machine addresses within the array) or they could just be the number of array elements. Each time a character is received you put the new character at the next position in the buffer given by the write pointer/index and increment it. If it goes beyond the end of the array it is set back to the start (this is where the name "ring buffer" or "circular buffer2 comes from).

The code in main() that will use the received characters uses a routine that that takes the next character from the read pointer/index position in the buffer. That index is then incremented and if it goes beyond the end of the buffer it, too, is set back to 0.

the test for whether there are any received characters in the buffer that have not been used yet is whether the read and write points are at the same location. If they are the same there's no character available.

By using indices rather than pointers and picking a buffer length that is a multiple of 2 you can use the C and operator to quickly handle the wrapping of the pointer. That is:

write_index = (write_index + 1) & (BUFFER_LENGTH - 1);

That both increments the index and puts it back to 0 if it goes beyond the buffer length.

Last Edited: Thu. May 19, 2011 - 09:47 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@Clawson: Thanks for the great explanation! :-)
I'll include this too and post the updated code later.

-Aashish.
If you don't see it coming, you'll never know what hit you...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

One way is to have that command table in flash, in this case each command is terminated with '0x00'.

Have a second table of made up of the labels of the action addresses for each command, in the same order.

.cseg
_AND:
;this is the code for the AND command
ret

_DIV:
;this is the code for the AND command
ret

_EOR:
;this is the code for the AND command
ret


.dseg
TOKENS:
.db	"AND",0,"DIV",0,"EOR",
ACTION_ADDRESS:
.dw	_AND, _DIV, _EOR

The hard bit I leave to you is that you take each command received and check it against each entry in the table in turn, counting how many entries along it is. in the example "DIV" is number 1 (not 2 - AND is command 0).

You then load a double register with TOKENS
Add the number of the command (2<<1)
The register now points at the location containing the value of the label "_DIV"

Load this value in to the double register Z

Perform an ijmp which will enter the routine at _DIV.

May sound a little complex, but once you get your head around it, it's a hugely powerful technique for structured control of program flow instead of endless compares.

Cheers,

Joey

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Joey! Will do some homework on this and get back.

-Aashish.
If you don't see it coming, you'll never know what hit you...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Joey's code in C is roughly:

typedef void (*fptr_t)(void);

typedef struct {
  fptr_t fptr;
  char * str;
} cmd_t;

void and(void) {
  // do and function
}

void divide(void) {
  // do div function
}

void eor(void) {
  // do eor function
}

cmd_t cmd_table[] = {
  { and, "AND" },
  { divide, "DIV" },
  { eor, "EOR" }
};

(I changed "div" to "divide" to avoid name pollution). The generated Asm for the table is:

.global	cmd_table
	.data
.LC0:
	.string	"AND"
.LC1:
	.string	"DIV"
.LC2:
	.string	"EOR"
	.type	cmd_table, @object
	.size	cmd_table, 12
cmd_table:
 ;  fptr:
	.word	gs(and)
 ;  str:
	.word	.LC0
 ;  fptr:
	.word	gs(divide)
 ;  str:
	.word	.LC1
 ;  fptr:
	.word	gs(eor)
 ;  str:
	.word	.LC2

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I must post more assembler - I could learn a lot this way:)

Cheers,

Joey

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi One more interesting way to use many commands in hyper terminal without using some special characters like '#' or '/' is hashing. This technique works based on finding some unique value for every command and matching the value using switch case statement (as switch-case is much better than else if ). The value that u map with every command can be a 32 bit one and it can be obtained from carrying out some operation on the characters of the command.
Example: Let the command be "Lights on". when you receive each and every character you can update a value called key like " key = (character + (key<<4) + (key << 16) - key)" where key is initially 'zero'. Once you type the full command you can type enter. This 'enter ' is received as '\r' which could act as end of the command and inturn the end for updating the key value. Now this key value can just pass through the switch case and do the desired action. One overhead is that you should write a program to find the key value for every command you need to add. This key value should be used in the case statements.

This technique works for quite a good number of commands.

-Krishna Balan S

-------------------------------------------------------------------------

"Heroes are ordinary people with extraordinary commitment"

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@clawson: If I understand correctly, isn't this the same approach that Chuck mentioned?

Quote:
I'd create a structure that holds a string (the command) and a function pointer. Then I'd declare an array of these, in flash, and populate it with the initial values. This would be the command name ("command1") and a pointer to a function that will be called when that command is received. There would also be a declared constant that would define how many of these structures were in the array. I'd put the array in flash so it didn't take any RAM.

I am writing it right now. Need to sort out a few compilation errors before I get it going. :-)

-Aashish.
If you don't see it coming, you'll never know what hit you...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I'd create a structure that holds a string (the command) and a function pointer. Then I'd declare an array of these, in flash, and populate it with the initial values.

Yes, that's exactly what the C code does, I just tried to make Joey's Asm more "C friendly" but took the opportunity to do the obvious thing (which is trickier in Asm) and group pairs of items into an array of struct's. I guess it's kind of inevitable that this just coincidentally is what Chuck described because that's the obvious way to do it in C.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I got it to work! :-) will post the cleaned up code in a while. Thanks for all the great advice, everyone!! :-)
I now recall using this approach back in college for a little project, so I should've thought of it earlier... Just a bit dusty with my programming, maybe.
Hmm..now I need to build upon this to make something useful. :-)

-Aashish.
If you don't see it coming, you'll never know what hit you...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, the idea of predefined commands suits well for humans armed with some kind of serial terminal interface.

I once thought it is quite time-consuming to write a separate led-blinking hello-world type program for every different board with same AVR chip, so I wrote a generic program that did nothing except initialize the UART to receive only two commands: read register xx and write register xx with data yy. Armed with those two commands, you can control every peripheral in the AVR, albeit slowly through serial port for starters. Turning some LEDs on was just two commands, setting the DDR register and then setting the PORT register of course, just like in C language.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You'r right that I could combine the two tables into one, but the reason I didn't is that the original command table was a point of the BBC Micro one (I took the token and the flag bytes out of the example to keep it simple).

As some of my entries are only used for decoding tokens and don't have action addresses, the split is slightly more space efficient. Also, the calculation I use for the ijmp address is actually:

(Jump table base address-token offset)+token value

Quote:

Armed with those two commands, you can control every peripheral in the AVR, albeit slowly

Sounds a useful idea.

Cheers,

Joey

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Also, the calculation I use for the ijmp address is actually:

(Jump table base address-token offset)+token value


The joy of C:

for (i=0; i

There's an ICALL in there somewhere but I don't really care how the compiler calculated it - I can reaarange the table in whatever way I like - maybe add elements to the struct for "number of parameters" or something - and the C compiler still ICALLs the right thing ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Armed with those two commands, you can control every peripheral in the AVR, albeit slowly

Hmm...how do we do that?
I tried to make something like that. Got as far as separating PORTC (or any other PORT or register name) and 02 (hex value) strings from a command such as PORTC.02 . An example would be really nice here, please. :-)

-Aashish.
If you don't see it coming, you'll never know what hit you...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's a sample of some command handling code.

/////////////////////////////////////////////////
// Base for a command line handler
// To be used with a terminal program of some kind
//
// Echoes all characters to the serial port
// Handles backspace for editing
// TAB will run the last command
// UP ARROW will load the last command into the command buffer and echo it to the serial port
// Can handle up to 5 parametere in HEX or DEC

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

#define MAX_PARMS 5
char *parms[MAX_PARMS];
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}
};
const int N_COMMANDS = sizeof (commands)/ sizeof (command);

static 	int		n_parms; // number of parameters entered after the command
static	int		last_address = 0;
static	int		cmd_buffer_index = 0;
static	char	cmd_buffer[50];
static	char	last_cmd[50];

////////////////////////////////////////////////////
// example command, reads the first parm (address) and the second parm (value)
// and calls a function to do something with the values
void	Cmd_WR () {
	/////////////////////////////////////////////////////////
	// write a value to target RAM
	int addr = GetParm(0, HEX);
	byte val = GetParm(1, HEX);
	do_something_with (addr, val);
	
}

unsigned int htoi (const char *ptr) {
	unsigned int value = 0;
	char ch = *ptr;
	
    while (ch == ' ' || ch == '\t')
        ch = *(++ptr);

    for (;;) {

        if (ch >= '0' && ch <= '9')
            value = (value << 4) + (ch - '0');
        else if (ch >= 'A' && ch <= 'F')
            value = (value << 4) + (ch - 'A' + 10);
        else if (ch >= 'a' && ch <= 'f')
            value = (value << 4) + (ch - 'a' + 10);
        else
            return value;
        ch = *(++ptr);
    }
}

int		GetParm (byte parm, byte base) {
	switch (base) {
		case HEX:
		return ( htoi(parms[parm]));

		case DEC:
		return ( atoi(parms[parm]));
	}
}

void	SplitCommand() {
	/////////////////////////////////////////////////////
	// split the command line by replacing all ' ' with 
	// '\0' and saving pointers to all the parm strings
	int i, p;

	/////////////////////////////////////////////////////
	// clear the pointer array
	n_parms = 0;
	for (i = 0; i < MAX_PARMS; i++)
		parms[i] = NULL;

	/////////////////////////////////////////////////////
	// scan the command line, dork any spaces with null chars
	// and save the location of the first char after the null
	for (i = 0, p = 0; cmd_buffer[i] != NULLCHAR; i++) {
		if (cmd_buffer[i] == SPACE) {
			cmd_buffer[i] = NULLCHAR;
			parms[p++] = &cmd_buffer[i] + 1;
			n_parms++;
		}
	}
}
void	ProcessCommand () {
	int cmd;
	
	Serial.println("");

	/////////////////////////////////////////////////////
	// trap just a CRLF
	if (cmd_buffer[0] == NULLCHAR) {
		Serial.print("QM> ");
		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();  // command found, run its function
			goto done;
		}
	}

	/////////////////////////////////////////////////////
	// if we get here no valid command was found
	Serial << "wtf?" << endl;

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

void	command_handling_loop() {
	char c;
	
	if (Serial.peek() > 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:  // TAB runs the last command
					memcpy(cmd_buffer, last_cmd, sizeof(cmd_buffer));
					ProcessCommand ();
					break;

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

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

				default:  // just put the char in the buffer
					cmd_buffer[cmd_buffer_index++] = c;
					cmd_buffer[cmd_buffer_index] = NULLCHAR;
					Serial.print (c);
			}
		}
	}
}

NOTES:
Does not use a circular buffer so I can easily memcpy() command lines.

The serial Rx and TX are Arduino, you'll have to replace with serial funcs of your own.

It's a cut and paste job from a much larger program (an AVR monitor program), then cleaned up to remove stuff specific to that prog. Lord knows what I missed, so there's no chance it will run or even compile as is.

Hopefully this will give you a start.

______
Rob

Scattered showers my arse -- Noah, 2348BC.
Rob Gray, old fart, nature photographer, embedded hardware/software designer, and serial motorhome builder, www.robgray.com

Last Edited: Sun. May 22, 2011 - 05:52 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
   // if we get here no valid command was found
   Serial << "wtf?" << endl;

Aahhh ... the old "what's this for" handler :lol:

Ross McKenzie ValuSoft Melbourne Australia

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My non politically-correct error message :)

______
Rob

Scattered showers my arse -- Noah, 2348BC.
Rob Gray, old fart, nature photographer, embedded hardware/software designer, and serial motorhome builder, www.robgray.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have always written my command interpreters to interface with an application rather than a terminal, but the principle are pretty much the same. As was said before, use a circular buffer, with the USART in interrupt mode. The packet structure I was using consisted of first a length byte, the actual message (which contains the command byte; lower 4 bits are command, upper 4 bits are option flags) and a checksum byte (just XORing all bytes together). The USART RX interrupt assembles complete packets by counting length and calculating checksums from the circular buffer, and put the result in a separate command buffer before raising a "command ready" flag. Idletop waits for that flag, and when detected proceeds to parsing the command buffer, executing the command, then resetting the command ready flag to let the RX routine it's ready for more.

The parser is just a long Select Case statement in the form of a jump table, and each command branch is responsible for fetching its parameters within the command message buffer.

The same form is completely scalable, on ARM over a USB link for example I would scale up the length field to 2 bytes, use a full byte for command and a separate byte for options bitflag, and no checksum (since USB takes care of all the error handling)...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Here's a sample of some command handling code.

For 'remote control' of AVR registers I had imagined something rather simpler. Send a three byte command : Rn,X,Y where Rn is the data space address of the register.

Read Register Rn, AND it with X, EOR it with Y, then write it back and echo the result back as well.

Set X to 0 to write Y into the register, set Y to 0 and X to 1 to read the register without changing it.

Virtual prize for anyone who can spot where I got the idea from.

Cheers,

Joey