Node oriented communication interface for the simplefoc

Hi guys,

@JorgeMaker has recently published hes work on a great and very useful user interface for the simplefoc:
https://community.simplefoc.com/t/simplefoc-gui-configuration-tool/460/20

With this in mind I would like to discuss the communication interface for the simplefoc library and what should it be in future. Not so much in terms of technology as in terms of architecture.

At the moment the motors are configurable using motor commands, and all the code is hard-coded into the FOCMotor.cpp class. For one of the next releases the whole communication will be separated from monitoring and moved to the new class completely. Which will be more flexible and make swaping in between communication types simpler.
I know there is many of you interested in CAN at this point, but I am still interested into the serial and taking the full potential out of it.

That being said there are few problems with motor commands:

  • Supports only one motor at a time
  • Uses the same serial as for monitoring
  • Cannot simply be extended or modified

So what I would like to have is something like this. I would like to have a class communicator which will have a list of nodes that it needs to/can communicate with their ids. Those nodes could be motors, scalars or PID and LPF classes and similar. Something like:

BLDCMotor motor = BLDCMotor(...);
communicator.addNode(&motor, 'M1');

Or PID and LPF classes:

PIDController some_pid = PIDController{...};
communicator.addNode(&some_pid , 'P1');
LowPassFilter some_lpf = LowPassFilter{...};
communicator.addNode(&some_pid , 'F1');

Or just a scalar value:

float variable = 10.00;
communicator.addNode(&variable , 'V1');

And that the user is able by sending the command to the mcu, for example ?, to get the list of nodes addached:

?
Attached nodes:
M1: motor
M2: motor
P1: pid
F1: lpf
V1: scalar

And once you have it the user can send/request something similar to the motor commands for each single one of those nodes, by first specifying the id and then the command and value. Something like

M1#P1.00                      
PID velocity | P: 1.00   
M2:I                           
PID velocity | I:10   

But you could also set and configure one PID and LPF which is not in the motor directly. This would make it easier to make some mechatronics projects such as balancing robots and similar.
Also you could add a down-sampling rate as one of your nodes something like:

int configurable_cnt;
int downsample_cnt = 100;
void setup(){
 .....
 communicator.addNode( &downsample_cnt, 'DSC');
 .....
}
void loop(){
    motor.loopFOC();
    
    if(downsample_cnt++ > configurable_cnt){
      motor.move();
    }
   
   // communication stuff  
   communicator.communicate()
}

And then you can configure it using serial:

DSC:
value: 100
DSC:200
value: 200

I am really interested to hear what you guys think. I am actually pretty happy with this concept. A generic communication interface in the shape of nodes. It would add a lot of flexibility to the part of fine-tuning of the library.

Also, I think the awesome interface @JorgeMaker has made could be extended to support these kinds of communication protocols for easier visualization. :smiley:

One of the current limitations is that they can only query the V1, V2, V3 values and when you start the control application you have no way of knowing what configuration the motor has at that moment … the proposed approach I think will solve it :slight_smile:

It would be nice to allow the sensor to be zeroed and add some command to disable the motor.

At the moment for every command you send you can request it as well. Just send the empty commands.
Like if you are not sure what is the control loop, send just C and the motor will tell you. The same goes with any other configuration parameter.
V1-V3 are only getters because their values are set in the algorithm.

Zeroing the senor is also in the list :smiley:
Definitely a quick and important update, but I am not sure if this parameter is interesting to be communicated.
The disabling can be easily implemented as well, I agree.

So you are not too optimistic about this upgrade, you think it is not a good approach? :slight_smile:

1 Like

Some great ideas.

I’m wondering how this fits in/replaces existing monitor()?

When communicator.communicate() is called, it’ll log telemetry.
Will it return something like:

M1    <voltage>    <velocity_sp>    <velocity>
M2    <voltage>    <velocity_sp>    <velocity>

Can we influence what is logged e.g by providing a monitor_bitmask?
e.g.

communicator.addNode(&motor, 'M1', VOLTAGE | VELOCITY_SP | VELOCITY | ANGLE_SP | ANGLE);

I’m also thinking that it would be cool if the GUI Tool could have a bit of runtime control of things like downsampling and monitor_bitmask.

Finally I think the responses to sets and gets should be tool readable and stateless. e.g. the tool parser knows exactly what the response is for just by reading the response. This may make the response slightly less human readable.

> M1#P1.00                      
M1#P:=1.00   
> M2#I                           
M2#I=10

The tool parser should also be able to tell push data (i.e. monitor()) from polled data (i.e. where we ask for a value).

Looking at that concept of monitor_bitmask above

#define VOLTAGE         b00000001
#define VELOCITY_SP b00000010
#define VELOCITY        b00000100
...

So b00000011 would be asking for first two terms.

Honestly, I would like to avoid using the monitoring. I would like to keep it as a debugging tool only. It is very time consuming and it is almost never the best approach for reading data.

I would advocate either an async requesting ( so that we can decide the sampling rate from outside) or I really like your idea of monitoring as an on/off feature of the communicator. Defining the parameters that you want to dump.

For example what could be cool is to have a monitor request (example letter M) and that motor responds with a bunch of states that we are interested in (we define using commands).

M
1.2:10.3:120.4  # 1.2 rad/s, 10.3 Volt, 120 rad

I agree, I was thinking of a verbose flag for example. Or completely avoiding the text

What you be asking for M or M1? :wink:

Actually this is a serious question. So in code I might configure the M1 node. I can perhaps change it’s config from tool. And I can ask ā€˜M1’ to respond with its configured print variables.

Sure, I was thinking more in terms of the functionality. I guess you would do something like:

M1#M
1.2:10.3:120.4  # 1.2 rad/s, 10.3 Volt, 120 rad

But what do you gust thing about this node approach. That we can add the pids, lpfs and scalars to the communication?
All these things we are talking about here are motor specific and can be dealt with just a bit better written motor commands.

What do you think about having a very flexible communication.

My idea is that regardless what I do if it includes controlling some hardware, I end up writing some simple tuning communication protocol to tune the values.
For example in the balancing robot that I’ve built I’ve had several PIDs and lpfs and down-sampling variables as well as 2 motors. Finally the motors did not have too much tunning involved but the pids were a bit tricky.

So I ended up with as code like this:

void tuneController() {
  // a string to hold incoming data
  static String inputString; 
  while (Serial.available()) {
     // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline
    // end of input
    if (inChar == '\n') {
      if(inputString.charAt(0) == 'A'){
        pid_stb.P = inputString.substring(1).toFloat();
      }else if(inputString.charAt(0) == 'B'){
        pid_stb.D = inputString.substring(1).toFloat();
      }else if(inputString.charAt(0) == 'C'){
        pid_stb.I= inputString.substring(1).toFloat();
      }else if(inputString.charAt(0) == 'D'){
        pid_vel.P = inputString.substring(1).toFloat();
      }else if(inputString.charAt(0) == 'E'){
        pid_vel.D=inputString.substring(1).toFloat(); 
      }else if(inputString.charAt(0) == 'F'){
        pid_vel.I =inputString.substring(1).toFloat(); 
      }else if(inputString.charAt(0) == 'G'){
        lpf_pitch.Tf =inputString.substring(1).toFloat(); 
      }else{
        vel=inputString.toFloat();
      }
      inputString = "";
    }
  }
}

This is basically a motor command interface but for few pids and lpfs.

It would be cool to have a way to specify the communicator that you have pids+lpfs+some other variables you want to communicate to the user as well as the motors:

com.addNode(&motor1,'M1');
com.addNode(&motor2,'M2');
com.addNode(&pid_stb,'c1');
com.addNode(&pid_vel,'C2');
com.addNode(&lpf_pitch,'F1);

And then combine it with a tool such as the GUI that @JorgeMaker has made or even just the python code.

1 Like

I did not know that was possible to query each configuration command by not providing any value … this makes possible to grab the current configuation when you start a new connection :slight_smile: I will take advantage of this.

Currently each monitoring response has to be interpreted positionally depending on the control mode you set the motor, … I guess that with this new approach that you are proposing each reported value will come tagged what makes everything easier and gives a lot of flexibility :slight_smile:

It would be also great that from an application you could query for the list of variables that are monitored and make possible to add and remove monitored variables.

Some similar projects have slick REPL-like shell interface. This is a lot easier when you are only catering for a single hardware type and when you are not constrained by lowest denominator hardware - I’m looking at you atmega328p :wink:

I agree. But still, I’m confident that we can integrate all of this into the atmega328 :grinning_face_with_smiling_eyes:

We will extend the motor commands that is for sure. I am just not sure if people are interested into this node vision of mine :smiley:
I’m not sure if the memory trade-off for convenience is too high :upside_down_face:

In any case for the next release I’m confident we will have:

  • support for multiple motors
  • more motor commands
  • disable motor
  • verbose mode
  • Maybe separated comm class from the motor class ( I’m afraid the memory requirements a bit)
2 Likes

I can see myself using it. E.g Dual motors and external PID.

I’m guessing you’ll be considering removal of useMonitoring(Serial). Apart from a few messages in initFOC it’ll be redundant.

Hey @Antun_Skuric,

I think it is a good idea to move it into a separate class.

I don’t know if you got a chance to look at my latest pull request, Second, more comprehensive OSC example by runger1101001 Ā· Pull Request #48 Ā· simplefoc/Arduino-FOC Ā· GitHub for the OSC example, but in this example I handle controlling 2 motors from a pD GUI, and I use the parameter names from your serial control in the OSC interface. In OSC, that becomes something like /M1/P 0.2 to set the P-gain. But OSC has a concept of addresses and routing and so it was conceptually easy to take these OSC requests and route them to 2 BLDCMotor objects.

In plain serial comms, you have to invent the concepts behind message addressing and framing yourself, and I think there are basically 2 approaches:

  1. use a command to select the motor, like ā€œM1ā€ → chooses Motor 1
    Then all following commands affect this motor until you select another one
  2. include the motor in each command, like ā€œM1#P0.2ā€

Note that you could actually support both at the same time, especially if there is an equivalence between '#' and '\n'… Then

M1#P0.2#M1#I20#M1#D0.001#M2#P0.2#M2#I20#M2#D0.001#

is actually the same thing as

M1
P0.2
I20
D0.001
M2
P0.2
I20
D0.001

Some other thoughts:

  1. I would keep the PID, LowPass and Motor kind of all together… why attach them all separately to the communicator? Is there a scenario where you might not want to control the PID? Does it hurt having it attached? Ah, I see, its for controlling other parameters of a larger system…
  2. I would keep it simple on the whole, with a focus on what is needed for development and debugging. Because I fear you will almost always ā€œmissā€ the needs of the individual applications if you try to address them - its simpler to have a clean API and some good example code so people can roll their own control interfaces depending on the application.
  3. In terms of the need to control other stuff, like the PIDs related to balancing, I am also working to solve exactly this problem, for my projects. I want to control and tune SimpleFOC motors, but also other things like sensors, drive algorithms and LEDs (don’t forget the LEDs!! :wink: ).
    My approach is a library I call ā€œSmallRobotā€ - at the moment I’m using OSC at its core to send, receive and route messages. It should essentially be a kind of wrapper library that glues a robot together in terms of communications. So my approach is to solve this outside of SimpleFOC, in a surrounding software layer. I’ll share the GitHub with you, maybe you have some comments or maybe you’re interested in collaborating on that… it’s not working yet as I only started on it a few days ago…

Oh, and another thought: sometimes it is not enough to just set a value, sometimes you need to check limits, or do some calculation - so it would be cool to have a addNode(void (*callback)(float), const char*) function that takes a function-pointer as its argument…

Hello!
I’d really like the commander to accept \cr\lf line breaks (or make it configurable), since quite a few serial console use that as default (even screen on linux)
I just spent an hour trying to figure out why commander wasn’t accepting my commands…

I could create a MR if you’d prefer…

int ch = serial.read();
received_chars[rec_cnt++] = (char)ch;
// end of user input
if (ch == '\n' || ch == '\r') {
  // execute the user command
  run(received_chars); }

or ( I don’t like this 2nd option since another dude like me will loose another hour fining out about EOLChar)

int ch = serial.read();
received_chars[rec_cnt++] = (char)ch;
// end of user input
if (ch ==EOLChar) {
  // execute the user command
  run(received_chars);

It is very weird, I had exactly the same issue this weekend - it’s hard to send newline characters to serial port from PureData.

It would be good to make this configurable.

1 Like

Awesome, thanks for the quick reaction. Well include this in the nexr release. :smiley: