Commander interface
Commander is a simple and flexible interface monitoring, supervision, configuration and control using the G-code like communication protocol. The communication is based on ASCII
character command ids which makes simple and efficient to parse on any MCU. After the command id has been received the function attached to this command is called and provided the remaining string of characters received which follows the command character.
This g-code like interface provides callback to configure and tune any:
- PID controllers
- Low pass filters
- Scalar variables
- Motion control NEW📢
- Setting target values and limits at once (ex. angle velocity torque)
- Changing the motion and torque control mode
- Enable/Disable the motor
- Fully integrated configuration for BLDC or Stepper motors
- PID controllers
- Low pass filters
- Motion control
- monitoring
- limits
- enable/disable
- sensor offsets
- phase resistance
- …
Furthermore commander enables you to easily create your own commands and extend this interface in any way you might need for your particular application. Here is the link to the docs about how to make your custom commands.
What happens when user sends a command?
When the commander received the string:
It first checks the command id, identifies its M
and sends the remaining string to the motor handling callback. Then the motor callback checks what is the coommand id, finds V
and sends the remaining string to the PID velocity callbacK. Then the PID velocity callback scans the command id and finds it is the D
, so derivative gain and sets the value.
Commander | Motor callback (cmd id M ) | PID callback (cmd id V ) |
---|---|---|
![]() | ![]() | ![]() |
The other example is if the commander receives:
First id that it finds is O
, which is for example motor. It calls the callback that is assigned to this command (which is in this case motor callback) with the string remaining string. Then the motor callback finds the command E
and knows its the status (enabled/disabled) either getting or getting. It checks the value and sees that the value is empty, which means the user has sent a get request.
Commander | Motor callback (cmd id O ) |
---|---|
![]() | ![]() |
Using the commander interface
Command interface is implemented in the Commander
class.
// Commander interface constructor
// - serial - optionally receives HardwareSerial/Stream instance
// - eol - optionally receives eol character - by default it is the newline: "\n"
// - echo - option echo last typed character (for command line feedback) - defualt false
Commander commander = Commander(Serial, "\n", false);
The end of line (eol) character is an optional input of the Commander
class which represents end of command character. User can define its own end of command characters here, but by default the character used is newline character \n
. For example i
BEWARE: EOL characters
Different operating systems have different EOL characters by default. Newline character is probably the most common one but there is also the carriage return '\r' for linux users. Be sure to provide it to the constructor of the Commander class if you wish to use it with your setup!
The echo flag can be used as a debugging feature but it is not recommended to be used for real time motor control and configuration!
Next step would be to add the commander function that reads the Serial
instance that you provided into the Arduino loop()
:
void loop(){
...
commander.run(); // reads Serial instance form constructor
}
If you did not provide the Serial
instance to the Commander
constructor you can provide it to the run()
function.
void loop(){
...
commander.run(Serial); // reads Serial instance form run
}
Or, if you wish to use the commander without Serial
and using just string variables then you can provide and char*
variables to the run()
function:
char* my_string = "user command";
commander.run(my_string); // reads the string
Serial output
TheCommander
class will always try to print the output to the serial instance provided in the constructor. If it did not receive one in the constructor, then it will use the one provided in therun()
function. If it does not any of the two, it will not output anywhere, but the user can still use it.
Adding commands
In order to add the callback for a given command character to the Commander
you will need to call the function add()
that receives the command character, the function pointer and the commands label:
// creating the command A in the commander
// - command id - character
// - callback - function pointer - void callback(char* cmd)
// - label - label of the command (optional)
commander.add('A',doSomething,"do something");
The only real requirement for the type of the function you can use as the callback is that hey need to return void
and they have to receive char*
string:
void doSomething(char* cmd){ ... }
With this simple interface you can create your own commands very simply and subscribe them to the Commander
using just one line of code.
In addition to this flexible interface for adding generic callbacks the Commander
class additionally implements standardized callbacks for:
- BLDC (
BLDCMotor
) or Stepper (StepperMotor
) motor -commander.motor(&motor, cmd)
- see more - PID controller (
PIDController
) -commander.pid(&pid, cmd)
- see more - Low pass filter (
LowPassFilter
) -commander.lpf(&lpf, cmd)
- see more - Any numeric variable (
float
) -commander.scalar(&variable, cmd)
- see more - Target setting control (
BLDCMotor
orStepperMotor
) -commander.target(&motor, cmd)
- see more - Full motion control (
BLDCMotor
orStepperMotor
) -commander.motion(&motor, cmd)
- see more
For example if you are interested in full configuration of one motor
your code could look something like this:
BLDCMotor motor = .....
Commander commander = ....
// defined wrapper for generic callback
void onMotor(char* cmd){commander.motor(&motor, cmd);}
void setup(){
...
commander.add('m',onMotor,"my motor");
...
}
void loop(){
...
commander.run();
}
Or maybe you wish to tune the velocity PID and you and change the target value of the motor and you wish to remove unnecessary memory overhead due to the other functionalities you do nto necessarily need, then your code could look something like:
BLDCMotor motor = .....
Commander commander = ....
// defined wrappers for generic callbacks
void onPid(char* cmd){commander.pid(&motor.PID_velocity, cmd);}
void onLpf(char* cmd){commander.lpf(&motor.LPF_velocity, cmd);}
void onTarget(char* cmd){commander.target(&motor, cmd);}
void setup(){
...
commander.add('C',onPid,"PID vel");
commander.add('L',onLpf,"LPF vel");
commander.add('T',onTarget,"target vel (+ torque limit)");
...
}
void loop(){
...
commander.run();
}
This simple interface provides the user a simple way to make communicate and configure multiple motors, PID controllers, low pass filters, scalar variables and custom commands in the same time if necessary. It also makes the tuning of the custom control loops much easier since you can close the loop with a pid controller PIDController
very easily and just add it to the commander to tune it in real time.
You can find more examples in library examples examples/utils/communication_test/commander
folder.
Commander built-in commands
When using the Commander
in your program the user will have three built-in default commands he can use:
?
- list all the commands available#
- get/set decimal point number- Examples:
- get decimal places
#
- set 5 decimal places:
#5
- get decimal places
- Examples:
@
- get/set verbose output mode- Examples:
- get mode:
@
- set user friendly mode :
@2
- set noting mode :
@0
- set on request mode :
@1
- get mode:
- Examples:
The list command ?
will display all the commands that were added to the Commander
and their labels. For example if we have the added commands like these ones:
void setup(){
...
commander.add('M',doSomeMotor,"some motor");
commander.add('P',doSomePID,"some pid");
commander.add('R',doSomeOtherMotor,"some other motor");
...
}
Here is the example of the output of the list ?
command in user-friendly mode:
$ ?
M: some motor
P: some pid
R: some other motor
Configuration commands
Commander class has two configuration parameters:
verbose
- Serial output modedecimal_places
- Number of decimal places for floating point numbers
Number of decimal places for floating point numbers can be changed easily by setting the parameter decimal_places
:
commander.decimal_places = 4; // default 3
Serial output mode can be easily changed by setting the parameter verbose
// VerboseMode::nothing - display nothing - good for monitoring
// VerboseMode::on_request - display only on user request
// VerboseMode::user_friendly - display textual messages to the user (default)
// VerboseMode::machine_readable - display machine readable messages
commander.verbose = VerboseMode::user_friendly;
There are four types of output modes:
VerboseMode::nothing
- this mode does not output anything to the serial terminal - it is very useful whenCommander
is used in combination with monitoring to avoid unknown values in the Arduino’s Serial Plotter for example$ MLU1.2 # set voltage limit to 1.2V for the motor with the command id 'M' $ # no response
VerboseMode::on_request
- this mode outputs only the results of get and set commands and will not output any additional unnecessary (human readable) text.$ MLU1.2 # set voltage limit to 1.2V for the motor with the command id 'M' $ 1.2 # set value is 1.2
VerboseMode::user_friendly
- this mode is the default mode and is intended for the cases when it is the user who sends the commands using the serial monitor. This mode will in addition to all the necessary get and set values output additional text for easier comprehension for human user.$ MLU1.2 # set voltage limit to 1.2V for the motor with the command id 'M' $ Limits| volt: 1.2000 # human readable return - value is 1.2
VerboseMode::machine_readable
- this mode is there for easier parsing returned values by software. This mode is essentially the same as theVerboseMode::on_request
, however it will repeat the command characters before returning values.$ MLU1.2 # set voltage limit to 1.2V for the motor with the command id 'M' $ MLU1.2 # machine readable format, command repeated + set value is 1.2
List of available commands
All built-in commands and subcommands are defined in the library source, in file src/communication/commands.h
. If you wish to change the character id of a certain command that is the place to do it. 😄
In general we can separate the commands into:
- Commander commands - commands specific for the
Commander
class - PID commands - commands specific for the
PIDController
class - Low pass filter commands - commands specific for the
LowPassFilter
class - Motor commands - commands specific for the
FOCMotor
classes
When adding the scalar
variable to the commander or the motion control target
the only command letter used is the one provided to the commander.add
.
- Scaler variable - adding the scalar
float
variable - Motion control and target setting - setting the target for the
FOCMotor
classes
Commander provides a very simple way to extend the command list and implement new ones
- Custom commands - create your own callbacks
Commander with Serial Monitor in Arudino IDE
Once the commander interface is added to the code you will be able to communicate to it using the Serial Monitor of the Arduino IDE
Commander paramters in the serial monitor are the same as for every other Arduino code working with the Serial
. Make sure to:
- Set the baudrate number the same as in the
ino
file : for examle if in theino
file you haveSerial.begin(115200)
, the baud rate should be115200
- Make sure to set the termination character to
newline
SimpleFOCStudio by @JorgeMaker
SimpleFOCStudio is an awesome application built by @JorgeMaker which we will try to keep up to date with out library. It is a python application that uses commander interface for tunning and configuring the motor.
For more info how to install and use this application visit the studio docs .