Jump to content
43oh

Compact command parser/dispatcher example


Recommended Posts

A command parser/dispatcher example from my CO2 laser engraver codebase, using a struct array containing the commands and associated  pointer to functions. A lot cleaner (and easier to maintain) than switch/case statements or if/else constructs...

Functions get called with a pointer to the command tail for local parameter parsing.

The struct array data are all placed in flash.

typedef struct {
	char const *const command;
	bool (*const handler)(char *);
	const bool report;
} command;

bool query (char* params);
bool start (char* params);
bool moveXrel (char* params);
bool moveYrel (char* params);
bool moveZrel (char* params);
bool XHome (char* params);
bool YHome (char* params);
bool ZHome (char* params);
bool XYHome (char* params);
bool zeroAllAxes (char* params);
bool laser (char* params);
bool setLaserPower (char* params);
bool setImageDPI (char* params);
bool setPulseDutyCycle (char* params);
bool enableCoolant (char* params);
bool enableAirAssist (char* params);
bool setMode (char* params);
bool getPosition (char* params);
bool setPPI (char* params);
bool setPulseWidth (char* params);
bool enableExhaustFan (char* params);
bool setEngravingSpeed (char* params);
bool getStatus (char* params);
bool setEchoMode (char* params);
bool setAMode (char* params);
bool setPWROffset (char* params);
bool loadProfile (char* params);
bool setXBcomp (char* params);

void exeCommand (char *cmdline) {

	static const command commands[] = {
		"?",			&query, true,
		"Power:",		&setLaserPower, true,
		"DutyCycle:",	&setPulseDutyCycle, true,
		"PulseWidth:",	&setPulseWidth, true,
		"DPI:",			&setImageDPI, true,
		"Start:",		&start, true,
		"X:",			&moveXrel, true,
		"Y:",			&moveYrel, true,
		"Z:",			&moveZrel, true,
		"HomeXY",		&XYHome, true,
		"HomeX",		&XHome, true,
		"HomeY",		&YHome, true,
		"HomeZ",		&ZHome, true,
		"ZeroAll",		&zeroAllAxes, true,
		"Laser:",		&laser, true,
		"Coolant:",		&enableCoolant, true,
		"Air:",			&enableAirAssist, true,
		"Mach3:",		&setMode, true,
		"Pos",			&getPosition, false,
		"PPI:",			&setPPI, true,
		"Exhaust:",		&enableExhaustFan, true,
		"Speed:",		&setEngravingSpeed, true,
		"Status",		&getStatus, false,
		"ASelect:",		&setAMode, true,
		"PWROffset:",	&setPWROffset, true,
		"LoadProfile:",	&loadProfile, true,
		"XBComp:",      &setXBcomp, true,
		"Echo:",		&setEchoMode, false
	};

	bool ok = false;
	uint32_t i = 0, numcmds = sizeof(commands) / sizeof(command), cmdlen;

	while(!ok && i < numcmds) {

		cmdlen = strlen(commands[i].command);

		if(!(ok = !strncmp(commands[i].command, cmdline, cmdlen)))
			i++;

	}

	if(ok) {
		ok = commands[i].handler(cmdline + cmdlen);
		if(commands[i].report)
			serialWriteLn(ok ? "OK" : "FAILED"));
	} else
		serialWriteLn("Bad command");

}

For further reading see http://www.barrgroup.com/Embedded-Systems/How-To/C-Function-Pointers

 

 

 

Link to post
Share on other sites
  • Replies 31
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Popular Posts

A command parser/dispatcher example from my CO2 laser engraver codebase, using a struct array containing the commands and associated  pointer to functions. A lot cleaner (and easier to maintain) than

Thank you for sharing! Also as an FYI... Tivaware also has a similar parser, under utils/cmdline.c

@@zeke - ok I'll try, here is the setEchoMode function as I have implemented it: bool setEchoMode (char* params) { echo = parseInt(params) != 0; return true; } echo is a boolean variable - comma

Thanks for the link to the Nigel Jones article!

 

Even though I can compile your example, I confess that I do not know how to make good use of it yet.

 

I will have to fiddle around with it to see how I can convert my command switch() code into something more elegant and orderly using your methods @@terjeio

Link to post
Share on other sites

Thank you for sharing!

Also as an FYI... Tivaware also has a similar parser, under utils/cmdline.c

 

I found the tivaware cmdline.c file but I'm still trying to wrap my mind around this methodology. 

 

Can you point me in the direction of a good tutorial or example?

Link to post
Share on other sites

http://www.cprogramming.com/tutorial/function-pointers.html

 

Cant find the link I was looking for, but this guy usually gets the point across. But the term I was looking for above in my last post was "PID controller", and the link I had was from a guy who illustrated the concept in code using 4 functions pointers( perfectly ). Maybe I'll be able to find it again ? Then more importantly the code was simple, so not a hard read to figure out.

 

EDIT:

Here it is: https://kjarvel.wordpress.com/2011/10/26/table-driven-state-machine-using-function-pointers-in-c/ but not exactly what I remember it to be. But one goo reason to put information up on this forum http://forum.43oh.com/topic/324-best-information-for-those-new-to-the-msp430/?p=27295 Because you get to run into it again if you need it and otherwise impossible to find on the web.

 

Anyhow the guy illustrates a state machine using function ptr's, but not a PID like I was remembering.

Link to post
Share on other sites

@@zeke

 

Function pointers in C. I'd have to try and dig up the link I had a long time ago, but it was used for . . .well state machines. Typically state machines in C use if/else, or switch statements. But as @@terjeio has shown here, you can use function pointers as well.

 

One thing I've done in the past for state machines and command processors was to make the commands or the state variable the value of the function pointer itself, so there is no lookup table wasting space. Making the commands "human readable" in the code was simply creating readable #defines for each value. This wouldn't work great for a human to input commands, but two devices communicating with one another don't care that the "Reset" command is some random 32 bit integer.

Link to post
Share on other sites

@@terjeio

 

Could you teach/tutor me how to make use of your code?

 

Could you show me how to create a command to toggle a Boolean on or off?

 

@@zeke - ok I'll try, here is the setEchoMode function as I have implemented it:

bool setEchoMode (char* params) {
	echo = parseInt(params) != 0;
	return true;
}

echo is a boolean variable - command will be either "Echo:0" (off) or "Echo:1" (on - in fact any parameter value diferent from 0 will set echo to true).

this version relies on my parseInt function, code can be simplified to:

bool setEchoMode (char* params) {
    echo = *params == '1';
    return true;
}

with improved parameter checking and proper status return:

bool setEchoMode (char* params) {
	if(*params == '1') {
		echo = true;
		return true;
	} else if(*params == '0') {
		echo = false;
		return true;
	} else
		return false;
}

I am using ":" as command/parameter separator, I have added this to the command in the command list so easy to change.

Link to post
Share on other sites

@@zeke - converting code from switch/case statements is just creating a function of/for each case: ... break; block with signature as defined for the handler in the command struct. The handler signature can of course be changed to suit your needs.

 

Another use of pointer to functions is for implementing a HAL (Hardware Abstraction Layer), I am going to use that approach in my port of Grbl to Tiva C. I find using #defines in the main code (as it stands now) makes it messy and hard to read and modify - using function pointers instead clearly defines the signatures of the functions to be implemented and there is (in theory) no need to change the main code for a new HAL/processor implementation.

 

This is my current HAL structure for Grbl:

typedef struct {
	void (*initMPU)(struct HAL *hal);
	void (*releaseMPU)(void);
	void (*serial_write)(uint8_t data);
	uint8_t (*serial_read)(void);
	void (*limits_disable)(void);
	uint8_t (*limits_get_state)(void);
	void (*coolant_stop)(void);
	void (*coolant_set_state)(uint8_t mode);
	void (*delay_ms)(uint16_t ms);
	void (*delay_us)(uint32_t us);
	uint8_t (*probe_get_state)(uint8_t probe_invert_mask);
	void (*spindle_stop)(void);
	void (*spindle_set_state)(uint8_t state, float rpm);
	uint8_t (*system_check_safety_door_ajar)(void);
	void (*stepper_wake_up)(uint8_t pulse_time);
	void (*stepper_go_idle)(void);
	void (*stepper_disable)(uint8_t state);
	void (*stepper_set_outputs)(uint8_t step_outbits);
	void (*stepper_set_directions)(uint8_t dir_outbits);
	void (*stepper_cycles_per_tick)(uint16_t cycles_per_tick);
	void (*stepper_pulse_time)(uint8_t cycles_per_tick);
	uint8_t (*uart_receive_data)(void);
	void (*uart_send_data)(uint8_t data);
	void (*uart_int_enable)(bool on);
	// callbacks - set up by library before MCU init
	uint8_t (*stepper_driver_interrupt_callback)(void);
	uint8_t (*stepper_reset_interrupt_callback)(void);
	uint8_t (*stepper_delay_interrupt_callback)(void);
	void (*limit_interrupt_callback)(void);
	void (*control_interrupt_callback)(uint8_t pin);
	settings_t *settings;
} HAL;

IIRC TIs graphics library has implemented the driver HAL in the same/similar way.

Link to post
Share on other sites

One thing I've done in the past for state machines and command processors was to make the commands or the state variable the value of the function pointer itself, so there is no lookup table wasting space. Making the commands "human readable" in the code was simply creating readable #defines for each value. This wouldn't work great for a human to input commands, but two devices communicating with one another don't care that the "Reset" command is some random 32 bit integer.

I guess that's an MCU's poor mans COM/OLE!

Link to post
Share on other sites

The problem I see with the approach above is the overhead of having pointer to functions instead of direct functions. Using C++ templates you can get compile time polymorphism with zero overhead.  I noticed you are exposing the delay_us() function as a HAL method. I'm guessing you aren't expecting to actually get a 1 microsecond delay.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...