Optimizing SimpleMotion V2 performance

From Granite Devices Knowledge Wiki
Revision as of 11:54, 10 June 2015 by Tero K (Talk | contribs) (Writing and reading multiple values at one cycle)


Jump to: navigation, search

This article lists the ways to optimize throughput and reduce latency of SimpleMotion V2.

Factors affecting to SMV2 speed

The speed of SMV2 is limited by:

  • Latency of RS485 communication device on the host side (i.e. SimpleMotion V2 USB adapter). This is the time delay of the communication device has during one write-read cycle, typically in range of milliseconds.
  • Command execution time of SMV2 slave device (servo drive). This is the delay how soon slave sends the return data after it has received the command from the SMV2 host. On Argon this is typically around 1-2 ms for a single command and 0.4 ms on each additional command in the queue (see later sections for info about queued commands).
  • Baud rate of RS485 bus. By default this is 460800 bits per second which is not typically the bottle neck. It is possible to change baud rate by one SMV2 command if necessary (TODO: how).
  • Usage of SM library commands. Using higher level commands usually produce more bus traffic and lower performance compared to lower level code.

Improving performance

RS485 adapter latency

One major source of latency is the RS485 adapter. Two ways to improve this:

  • If using SimpleMotion V2 USB adapter on Windows, go to Control panel->Device manager and find the USB Serial Port of the adapter. In the driver settings you will find the latency timer setting which defaults to 16 ms. Reduce this to 1 ms and click Ok. See detailed instructions at SMV2USB adapter latency settings.
  • Another option is to use a PCI/PCI-E based RS485 adapter which may have near zero latency and also better real-time capabilities than USB devices.

Optimizing code

The easiest way to use SMV2 library is to use smSetParameter and smRead1Parameter commands. However these commands are not optimal as they set and read only one parameter at the time and all latencies are added to this single call.

Queued calls

The more optimal way is to use queued SM commands. Queued command is a list of commands that are transferred to a slave in single data transfer, executed as a batch in the slave and finally all return data is returned as list. In this case bus latencies occur only once per multiple commands.

Instead of doing this...

smSetParameter( busHandle, deviceAddress, paramAddr1, paramVal1 );
smSetParameter( busHandle, deviceAddress, paramAddr2, paramVal1 );
smSetParameter( busHandle, deviceAddress, paramAddr3, paramVal1 );
smSetParameter( busHandle, deviceAddress, paramAddr4, paramVal1 );

...one may do this:

smint32 nul;
//append commands to queue
smAppendSetParamCommandToQueue( busHandle, paramAddr1, paramVal1 );
smAppendSetParamCommandToQueue( busHandle, paramAddr2, paramVal2 );
smAppendSetParamCommandToQueue( busHandle, paramAddr3, paramVal3 );
smAppendSetParamCommandToQueue( busHandle, paramAddr4, paramVal4 );
 
//execute commands over SM bus
smExecuteCommandQueue( handle,deviceAddress );
 
/*read return packet values. This is dummy as we don't need to read any parameters 
in this example now. However it's always required to read all return data from buffer 
before appending new commands. So each smAppendSetParamCommandToQueue will need 
one smGetQueuedSetParamReturnValue after smExecuteCommandQueue.*/
smGetQueuedSetParamReturnValue(  busHandle, &nul );
smGetQueuedSetParamReturnValue(  busHandle, &nul );
smGetQueuedSetParamReturnValue(  busHandle, &nul );
smGetQueuedSetParamReturnValue(  busHandle, &nul );
 
//TODO add some SM error checking in appropriate places

In the above example, the smExecuteCommandQueue is the only line where RS485 is being used thus generating only one write-read latency instead. smAppendSetParamCommandToQueue and smGetQueuedSetParamReturnValue transfer data only inside SMV2 library buffers and are fast calls. The optimized code does only one bus transfer cycle instead of four.

Queued size limit

One SMV2 command or return data packet can hold up to 120 bytes of payload data. This means that one should not add too many commands to the queue to avoid exceeding the 120 byte limit in outbound or inbound direction.

Single smAppendSetParamCommandToQueue commands consumes maximum of 6 bytes in outbound data and smGetQueuedSetParamReturnValue maximum of 4 bytes. So one can safely queue 20 set parameter commands in the one transfer.

Low level queued commands

This section shall describe how to further optimize communication by using lower level queued commands instead of smAppendSetParamCommandToQueue and smGetQueuedSetParamReturnValue which do some dummy assumptions (such as all parameters are written as 30 bit values and every time parameter address is set even if it's not changed from the previous set parameter command). Also reading variables while writing one is possible without separate command which reduces the amount of SM commands needed.

Writing and reading multiple values at one cycle

Following example will:

  • Initialize read-out parameter size to 24 bits. All consequent commands will have return data length of 24 bits until changed next time.
  • Use variable parameter bit lengths to reduce amount of data transferred. For example sending 24 bit commands is good for values such as torque command or parameter values and 32 bits for position commands when travel length is large.

Initialization code where we specify readout variable length.

/*setting return data lenght to 24 bits (22 bits maximum returned integer value 
as 2 bits are used by SM protocol). 16 bit data will fit in return value without clipping. */
smSetParameter( busHandle, deviceAddress, SMP_RETURN_PARAM_LEN, SMPRET_24B);

This is our optimized code that goes into high frequency loop. It writes 3 different parameters and reads 3 parameters with single bus transfer.

    /* Variables:
    -paramValue1-3 are the parameter values written to parameter numbers defined in paramAddr1-3
    -readOutParamAddr1-3 will define the parameter numbers to read out into readout1-3
    */
 
    //VALUES DEFINED
    int paramAddr1=SMP_DEBUGPARAM4;
    int paramAddr2=SMP_DEBUGPARAM5;
    int paramAddr3=SMP_DEBUGPARAM6;
    int paramValue1=100;
    int paramValue2=200;
    int paramValue3=300;
    int readOutParamAddr1=SMP_ACTUAL_TORQUE;
    int readOutParamAddr2=SMP_ACTUAL_POSITION_FB;
    int readOutParamAddr3=SMP_BUS_SPEED;
    smint32 nul, readout1, readout2, readout3;
 
    //WRITING PARAMETERS
    //set an parameter number where next values are written. This line consumes 2 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, paramAddr1 );
 
    //write the parameter value to previously set parameter address. SMPCMD_24B consumes 3 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, paramValue1);
 
    //set an new parameter number where next values are written. This line consumes 2 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, paramAddr2 );
 
    //write the parameter value to previously set parameter address. SMPCMD_24B consumes 3 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, paramValue2);
 
    //set an new parameter number where next values are written. This line consumes 2 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, paramAddr3 );
 
    //write the parameter value to previously set parameter address. SMPCMD_32B consumes 4 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_32B, paramValue3);
 
    //READING PARAMETERS
    //set parameter parameter number which will be returned from each command executed after this
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, SMP_RETURN_PARAM_ADDR ); //consumes 2 bytes
    /*write new values to parameter SMP_RETURN_PARAM_ADDR. after execution these commands return the parameter values
    defined by readOutParamAddrN*/
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, readOutParamAddr1 ); //consumes 3 bytes
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, readOutParamAddr2 ); //consumes 3 bytes
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, readOutParamAddr3 ); //consumes 3 bytes
 
    //execute commands over SM bus
    smExecuteCommandQueue( busHandle, deviceAddress );
 
    /*read value commands (one per each append command). Size of inbound payload depends on 
    what was set to SMP_RETURN_PARAM_LEN at initialization */
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
 
 
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
 
    /*the next readouts reflect to the last three commands appended before smExecuteCommandQueue
    and here we get the readout values*/
 
    smGetQueuedSMCommandReturnValue( busHandle,&readout1 );
    smGetQueuedSMCommandReturnValue( busHandle,&readout2 );
    smGetQueuedSMCommandReturnValue( busHandle,&readout3 );
 
    //TODO add some SM error checking in appropriate places

Below an alternative code that writes one variable (absolute setpoint) and read three variables (torque, position and velocity feedbacks). This code is present in live demo in SMV2Commander example.

    //VALUES DEFINED
    int paramAddr1=SMP_ABSOLUTE_SETPOINT;
    int paramValue1=0;//your setpoint value
    int readOutParamAddr1=SMP_ACTUAL_TORQUE;
    int readOutParamAddr2=SMP_ACTUAL_POSITION_FB;
    int readOutParamAddr3=SMP_ACTUAL_VELOCITY_FB;
    smint32 nul, readout1, readout2, readout3;
 
    //WRITING PARAMETERS
    //set an parameter number where next values are written. This line consumes 2 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, paramAddr1 );
 
    //write the parameter value to previously set parameter address. SMPCMD_24B consumes 3 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, paramValue1);
 
    //READING PARAMETERS
    //set parameter parameter number which will be returned from each command executed after this
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, SMP_RETURN_PARAM_ADDR ); //consumes 2 bytes
    /*write new values to parameter SMP_RETURN_PARAM_ADDR. after execution these commands return the parameter values
    defined by readOutParamAddrN*/
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, readOutParamAddr1 ); //consumes 3 bytes
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, readOutParamAddr2 ); //consumes 3 bytes
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, readOutParamAddr3 ); //consumes 3 bytes
 
    //execute commands over SM bus
    smExecuteCommandQueue( busHandle, deviceAddress );
 
    /*read value commands (one per each append command). Size of inbound payload depends on
    what was set to SMP_RETURN_PARAM_LEN at initialization */
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
 
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
 
    /*the next readouts reflect to the last three commands appended before smExecuteCommandQueue
    and here we get the readout values*/
 
    smGetQueuedSMCommandReturnValue( busHandle,&readout1 );
    smGetQueuedSMCommandReturnValue( busHandle,&readout2 );
    smGetQueuedSMCommandReturnValue( busHandle,&readout3 );
 
====Writing multiple and reading single value at one cycle====
If we need to read only one variable, we don't need to change readout parameter address during cycle and further improve performance. In this case we set it at initialization and then all consequent commands will return the desired return value.
 
Initialization:
<syntaxhighlight lang="c">
    /* Variables:
    -readOutParamAddr1 will define the parameter number to be read out into readout1
    */
    smint32 nul, readOutParamAddr1=SMP_ACTUAL_POSITION_FB;
 
    /*setting return data lenght to 24 bits (22 bits maximum returned integer value
    as 2 bits are used by SM protocol). 16 bit data will fit in return value without clipping. 
 
    Note: we do it the low level way in this case because SMPCMD_SETPARAMADD is 
    not available on higher level SM functions*/
 
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, SMP_RETURN_PARAM_LEN ); //2b
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, SMPRET_24B );//3b
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, SMP_RETURN_PARAM_ADDR );//2b
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, readOutParamAddr2 );//3b
 
    //execute commands over SM bus
    smExecuteCommandQueue( busHandle, deviceAddress );
 
    //must get all inserted commands from buffer
    smGetQueuedSMCommandReturnValue( busHandle, &nul );
    smGetQueuedSMCommandReturnValue( busHandle, &nul );
    smGetQueuedSMCommandReturnValue( busHandle, &nul );
    smGetQueuedSMCommandReturnValue( busHandle, &nul );
 
    //TODO add some SM error checking in appropriate places

Frequently called function with writing 3 variables and reading one in one bus transfer:

    /* Variables:
    -paramValue1-3 are the parameter values written to parameter numbers defined in paramAddr1-3
    -readOutParamAddr1-3 will define the parameter numbers to read out into readout1-3
    */
    int paramAddr1=SMP_DEBUGPARAM4;
    int paramAddr2=SMP_DEBUGPARAM5;
    int paramAddr3=SMP_DEBUGPARAM6;
    int paramValue1=100;
    int paramValue2=200;
    int paramValue3=300;
    smint32 nul, readout1;
 
    //WRITING PARAMETERS
    //set an parameter number where next values are written. This line consumes 2 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, paramAddr1 );
 
    //write the parameter value to previously set parameter address. SMPCMD_24B consumes 3 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, paramValue1);
 
    //set an new parameter number where next values are written. This line consumes 2 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, paramAddr2 );
 
    //write the parameter value to previously set parameter address. SMPCMD_24B consumes 3 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_24B, paramValue2);
 
    //set an new parameter number where next values are written. This line consumes 2 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_SETPARAMADDR, paramAddr3 );
 
    //write the parameter value to previously set parameter address. SMPCMD_32B consumes 4 bytes of outbound payload
    smAppendSMCommandToQueue( busHandle, SMPCMD_32B, paramValue3);
 
    //notice that we don't need to change readout parameter addresses here as it we have only one variable to read out
 
    //execute commands over SM bus
    smExecuteCommandQueue( busHandle, deviceAddress );
 
    /*read value commands (one per each append command before execute). Size of inbound payload depends on 
    what was set to SMP_RETURN_PARAM_LEN at initialization. In this case 3 bytes per return value (total 6x3). */
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&nul );
    smGetQueuedSMCommandReturnValue( busHandle,&readout1 ); //we could get the readout1 from any of the above lines as well
 
    //TODO add some SM error checking in appropriate places

In no event the Product Information or parts hereof shall be regarded as guarantee of conditions or characteristics. The Product Information or any part thereof may also not be regarded as a warranty of any kind. No liability of any kind shall be assumed by Author with respect to Product Information or any use made by you thereof, nor shall Author indemnify you against or be liable for any third party claims with respect to such information or any use thereof.

As content of this Wiki may be edited by user community, Granite Devices Oy or it's affiliates do not take any responsibility of the contents of this Wiki. Use information at your own risk. However, Granite Devices staff attempts to review all changes made to this Wiki and keep information trustworthy.

Without written consent, Granite Devices' Products or Intellectual Property shall not be used in situations or installations where living beings, material property, or immaterial property could be harmed by the operation, features or failures of Product. Products may only be used in a way where hazards like moving parts, electric shock, laser radiation, or fire can't be realized even if the content of this Wiki would suggest otherwise.