How to use SimpleMotion bus to download Drive Current and Following Error from IONI?

I have a question regarding using the SimpleMotion V2 bus to download various drive performance data from an IONI Pro drive.

I want to write automated control software that instructs a movement to the IONI via SimpleMotion, reads back performance data (such as Drive Current) during the movement and adjusts a process based on the data that is read back.

I would like to be able to access the same data that is used to draw the graphs in the Testing section of Granity.

I have reviewed the SimpleMotion V2 docs an the wiki, the host code ( and examples ( but can’t see any hints of how the performance data are read.

Since Granity is able to do it then it must be possible, but it appears to be undocumented. I’m sorry if I’m missing something, and if so please would you point me to the relevant document(s) or code?

If not, then please may I have some information on how that part of the protocol works?

Ideally, a document if you’ve got it.

Otherwise, I’m in a bit of a hurry, and I’m willing to use a serial sniffer and reverse engineer from the messages Granity sends, but I’d really appreciate it if you would give me some hints?

I’m an experienced software developer, so a little help would go a long way. For example, the #defines of the relevant parameter names, addresses, and bit pattern meanings would speed things up a lot.

Many thanks for the great servo drives and the open source approach,


Leo, is that you? :slight_smile:

Hi Phillip

I’m certainly a Leo, but I don’t know whether I’m the Leo you’re thinking of. :slight_smile:

I’m Leo Dearden, from the UK. I’ve been in touch with Granite a few times, and have bought VSD-E, Argon, and IONI Pro drives.

All the best,


Hi Leo! Glad to hear from you :slight_smile: I seem still to have some your emails back from 2012!

Scope capture example is coming to github, but for immediate needs, here is a simple command line C example for using the drive capture feature.

I hope this helps!

#include <stdio.h>
#include <unistd.h> //for usleep
#include "simplemotion.h"

smbus handle;
int nodeAddress;

bool setupScope()
	SM_STATUS status = 0;

	status |= smSetParameter(handle, nodeAddress, SMP_CAPTURE_BUF_LENGHT, 2048);
	status |= smSetParameter(handle, nodeAddress, SMP_CAPTURE_BEFORE_TRIGGER_PERCENTS, 0); //note, requires on IONI FW 1.6.0 or later
	status |= smSetParameter(handle, nodeAddress, SMP_CAPTURE_SAMPLERATE, 7); //with IONI sample rate is 20000/(SMP_CAPTURE_SAMPLERATE+1) Hz, so here it is 2500 Hz
	status |= smSetParameter(handle, nodeAddress, SMP_CAPTURE_TRIGGER, TRIG_INSTANT);
	status |= smSetParameter(handle, nodeAddress, SMP_CAPTURE_STATE, 1); //start capture

		return false;
	return true;

//parameters: nsamples=number of samples to read 1-2048, samples=pointer to buffer with at least nsamples length
bool downloadScope( int nsamples, int *samples )
	smint32 retval;
	int samplesread=0, storepos=0;
	SM_STATUS smStat=0;

	//setup for scope reading
	smStat|=smAppendSMCommandToQueue( handle, SMPCMD_SET_PARAM_ADDR, SMP_RETURN_PARAM_LEN );
	smStat|=smAppendSMCommandToQueue( handle, SMPCMD_24B, SMPRET_32B );//read 32 bit (values capped to 30 bits) samples
	smStat|=smAppendSMCommandToQueue( handle, SMPCMD_SET_PARAM_ADDR, SMP_RETURN_PARAM_ADDR );
	smStat|=smAppendSMCommandToQueue( handle, SMPCMD_24B, SMP_CAPTURE_BUFFER_GET_VALUE ); //read at get_value param
	smStat|=smAppendSMCommandToQueue( handle, SMPCMD_SET_PARAM_ADDR, SMP_CAPTURE_BUFFER_GET_ADDR ); //set store address to get_addr
	smStat|=smGetQueuedSMCommandReturnValue(  handle, &retval );
	smStat|=smGetQueuedSMCommandReturnValue(  handle, &retval );
	smStat|=smGetQueuedSMCommandReturnValue(  handle, &retval );
	smStat|=smGetQueuedSMCommandReturnValue(  handle, &retval );
	smStat|=smGetQueuedSMCommandReturnValue(  handle, &retval );

	//loop to read samples
		int i;
		int samplestofetch=nsamples-samplesread;
		if(samplestofetch>30)samplestofetch=30;//maximum per one SM cycle (4*30=120 bytes=max payload)

		//add read param commands to queue
			smStat|=smAppendSMCommandToQueue( handle, SMPCMD_24B, samplesread );

		//transmit & redeive over SM bus

		//read values from return data queue
			smStat|=smGetQueuedSMCommandReturnValue(  handle, &retval );

	//read one dummy variable just to cause SMP_RETURN_PARAM_ADDR to change to non-SMP_CAPTURE_BUFFER_GET_VALUE so next time we read data, we get it all from beginning
	smStat |= smRead1Parameter(handle, nodeAddress, SMP_NULL, &retval );

		return false;

	return true;

SM_STATUS scopeWait()
	smint32 state;

	//loop until SMP_CAPTURE_STATE is 0 (scope idle)
	while (true)
		SM_STATUS status = smRead1Parameter(handle, nodeAddress, SMP_CAPTURE_STATE, &state);

		if (status != SM_OK)
			return status;

		fprintf(stderr, "scope state: %d\n", state);

		if (state == 0)


	return SM_OK;

int main( void )
	const char *portName="COM3"; //bus device name
	nodeAddress = 4; //SM device address
	int samples[2048];

	fprintf(stderr, "opening bus %s\n", portName);
	handle = smOpenBus(portName);

	if (handle < 0)
		fprintf(stderr, "could not open bus: %s\n", portName);
		return -4;

		return -1;

		return -2;

	if(downloadScope( 2048, samples )==false)
		return -3;

	//sample list will be repeating list of samples defined to SMP_CAPTURE_SOURCE.
	//I.e if we capture 3 channels A, B and C, then samples will contain A0,B0,C0,A1,B1,C1,A2,B3,C3 etc
	for( int i=0; i<2048; i++)
		fprintf(stderr, "samples[%d]=%d\n", i, samples[i]);

	fprintf(stderr, "All done\n");
	return 0;


That looks like exactly what I was looking for.

Thank you so much.

Ha, I was thinking another Leo :slight_smile:

Welcome back though!