I blog therefore I am…
Long time now that I hack around here but I never really took the time to publish or document my work. Let’s change that!
It’s been a while that I was thinking about it, but as I was starting another arduino (for blog cred.?) project, I decided to take the time to implement a small command line interface for arduino.
For now this is just a test bench for stepper motor (more on this project in a future post) :Â I wanted to be able to start and stop the motor, change the delay between steps and the step mode (full, half, …) easily while focusing on some mechanical tests (I didn’t wanted to change values in code and upload at each parameters change). For this purpose, a set of commands with parameters usable from the arduino’s IDE serial monitor would be a convenient way to trigger motor action (start, stop, do 100 steps, …) and settings (direction, stepping mode, delay between steps, …). As the complexity of correctly driving the motor is abstracted by the use of a Alegro A3977 IC(on that board to be exact), there is plenty of cpu time to do some strings parsing.
Basic idea : Each byte is pushed in a buffer until a newline character is received and the parsing triggered. The command is then compared to an array of known commands. When a command match, a function pointer stored in another array is called with the command string as parameter (so additional parameters can also be parsed).
const char* cmd_array[] = {"help", "delay", "step", "dir", "start", "stop", 0}; typedef void(*cmd_func_ptr)(char*); cmd_func_ptr cmd_array_func_ptr[] = {&display_help, &set_delay, &set_step_mode, &set_dir, &do_start, &do_stop, 0}; void parse_command(char* cmd) { byte i = 0; do if (strncmp(cmd_array[i], cmd, strlen(cmd_array[i])) == 0) return (*cmd_array_func_ptr[i])(cmd); while(cmd_array[i++]); Serial.print("unkown command: "); Serial.println(cmd); }
With this tricks, adding a command don’t mean editing lots of nested if-else. Just add the new command function and edit the two arrays. In example, the “start” command can be called without parameters and the motor will spin endlessly, or if it is called with a numeric parameter, it will spin the specified numbers of steps.
void do_start(char* cmd) { unsigned long temp; if (!motor_started) { if (sscanf(cmd,"%*s %lu", &temp) == 1) { step_to_do = temp; motor_started = 2; digitalWrite(ENABLE_pin, LOW); Serial.print("Start motor to do "); Serial.print(step_to_do); Serial.println(" steps"); } else { motor_started = 1; digitalWrite(ENABLE_pin, LOW); Serial.println("Start motor"); } } else { Serial.println("Motor is already started"); } }
The use of sscanf is maybe not recommended in a low resources environment as arduino, but as I don’t need a lot of processing power or flash space in this project this is acceptable. Others lighter solution could involve atoi().
Usage example :
Note that you have to change the line ending to send new line characters to trigger command parsing. Newline + carriage return will also work because carriage return are filtered out (as every non-printable characters others than new line).
The inputs are not all checked for sanity, and there is a lot of improvement that can be made to motor driving (better use of micro-stepping, acceleration curve, …) but to illustrate the use of a command line interface, this is already a good start 🙂
source code download (rename from .c to .pde):
arduino-CLI-stepper-motor (See below)
[edit]
A friend of mine pointed some improvements and corrections to my first implementation:
- Use of another simpler syntax for the function pointer call
- Use of a struct allow to use a unique array for much clearer command/function binding
- There was a lot of const missing! Not so dangerous , but const correctness is the way to happiness
- Using of a define macro to get rid of my hack to detect the end of the array (hey! it was working! 🙂 )
Thanks a lot Silex0r !
typedef void(*cmd_func_ptr)(const char*); typedef struct { const char* str; cmd_func_ptr func; } command; command commands[] = { { "help", &display_help }, { "delay", &set_delay }, { "step", &set_step_mode }, { "dir", &set_dir }, { "start", &do_start }, { "stop", &do_stop } }; #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) void parse_command(const char* cmd) { byte i = 0; for(i = 0; i != ARRAY_SIZE(commands); ++i) { if (strncmp(commands[i].str, cmd, strlen(commands[i].str)) == 0) return commands[i].func(cmd); } Serial.print("unkown command: "); Serial.println(cmd); }
Download of the 0.2 version: