terjeio 134 Posted July 8, 2016 Share Posted July 8, 2016 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 yosh, agaelema, bluehash and 8 others 11 Quote Link to post Share on other sites
greeeg 460 Posted July 8, 2016 Share Posted July 8, 2016 Nice! Thanks for sharing. The thing I like about your approach is that you could easily alias different commands for a "human readable form" and a binary/automated form. (If needed) Eg "Status" and "s" ... "Status", &getStatus, false, "s", &getStatus, false, ... Quote Link to post Share on other sites
bluehash 1,581 Posted July 8, 2016 Share Posted July 8, 2016 Thank you for sharing! Also as an FYI... Tivaware also has a similar parser, under utils/cmdline.c terjeio, pine and zeke 3 Quote Link to post Share on other sites
yyrkoon 250 Posted July 8, 2016 Share Posted July 8, 2016 Thank you for sharing! Also as an FYI... Tivaware also has a similar parser, under utils/cmdline.c And . . . if you look at my reflow code from 3-4 years ago. I do something similar. Although not quite the same way. Quote Link to post Share on other sites
zeke 693 Posted July 8, 2016 Share Posted July 8, 2016 I was just thinking about this command line stuff as I work on my marquee clock code. My way is so cumbersome and tedious if I want to add in more commands. You way is pretty straight forward. <YOINK!> Thanks! spirilis 1 Quote Link to post Share on other sites
zeke 693 Posted July 8, 2016 Share Posted July 8, 2016 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 Quote Link to post Share on other sites
zeke 693 Posted July 8, 2016 Share Posted July 8, 2016 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? Quote Link to post Share on other sites
yyrkoon 250 Posted July 9, 2016 Share Posted July 9, 2016 @@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. zeke 1 Quote Link to post Share on other sites
yyrkoon 250 Posted July 9, 2016 Share Posted July 9, 2016 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. zeke 1 Quote Link to post Share on other sites
USWaterRockets 57 Posted July 9, 2016 Share Posted July 9, 2016 @@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. Quote Link to post Share on other sites
zeke 693 Posted July 9, 2016 Share Posted July 9, 2016 @@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? Quote Link to post Share on other sites
terjeio 134 Posted July 9, 2016 Author Share Posted July 9, 2016 @@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. spirilis and zeke 2 Quote Link to post Share on other sites
terjeio 134 Posted July 9, 2016 Author Share Posted July 9, 2016 @@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. zeke 1 Quote Link to post Share on other sites
spirilis 1,265 Posted July 9, 2016 Share Posted July 9, 2016 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! Quote Link to post Share on other sites
Rickta59 589 Posted July 9, 2016 Share Posted July 9, 2016 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. Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.