SPI -> L6470 (smartlynx) stepper motor driver

I just got my stepper motor, and Phi robotics Smartlynx (L6470 based) stepper motor driver (Documentation is here http://www.robotshop.com/media/files/zip/documentation-h-elc-mdr-stp-2.zip)

It give some pseudocode, which for the most part is C, so it was no trouble figuring that out, but they missed giving me the function “smartlynxDataTransfer”… there’s a function called “smartlynxWriteData”, with a similar signature, but since this is my first SPI based project I’m a little lost as to what ought to be in that missing function… the L6470 datasheet doesn’t help me much either.

Here is my code.

#include <SPI.h>

const int SPI_CS = 7;    //SPI cable select pin
const int STEP_PIN = 50; //pin for single stepping

// smartlynx register addresses
const byte NOP = 0b00000000;

const int SMARTLYNX_ABS_POS = 0x01;
const int SMARTLYNX_SPEED = 0x04;
const int SMARTLYNX_ACC = 0x05;
const int SMARTLYNX_DEC = 0x06;
const int SMARTLYNX_MAX_SPEED = 0x07;
const int SMARTLYNX_MIN_SPEED = 0x08;
const int SMARTLYNX_FS_SPD = 0x15;
const int SMARTLYNX_STEP_MODE = 0x16;
const int SMARTLYNX_STATUS = 0x19;
//smartlynx commands
const int SMARTLYNX_SET_PARAM = 0x00;
const int SMARTLYNX_GET_PARAM = 0x20;
const int SMARTLYNX_RUN = 0x50;
const int SMARTLYNX_MOVE = 0x40;
const int SMARTLYNX_SOFT_STOP = 0xB0;
const int SMARTLYNX_HARD_STOP = 0xB8;
const int SMARTLYNX_GET_STATUS = 0xD0;
// step size selection
const int SMARTLYNX_STEP_FULL = 1;
const int SMARTLYNX_STEP_HALF = 2;
const int SMARTLYNX_STEP_QUARTER = 3;
const int SMARTLYNX_STEP_ONE_EIGHT = 4;
const int SMARTLYNX_STEP_ONE_16TH = 5;
const int SMARTLYNX_STEP_ONE_32ND = 6;
const int SMARTLYNX_STEP_ONE_64TH = 7;
const int SMARTLYNX_STEP_ONE_128TH = 8;

void setup() {
  Serial.begin(9600);
  // start the SPI library:
  SPI.begin();
  pinMode(STEP_PIN, OUTPUT);
  smartlynxSetMaxSpeed(100);

}
void loop() {
  smartlynxRun(1, 100);

}

//I made these two functions, though I'll probably just get rid of them in the future...
void gpioClear(int CS_Pin) {
  digitalWrite(CS_Pin, LOW);
}
void gpioSet(int CS_Pin) {
  digitalWrite(CS_Pin, HIGH);
}

void spiWriteByte(byte data) {
  gpioClear(SPI_CS);
  SPI.transfer(data);
  gpioSet(SPI_CS);
}

void smartlynxDataTransfer(uint32_t Register, uint32_t data) {
  //this is the function I'm not given pseudocode for
}

void smartlynxWriteData(uint32_t data, uint8_t byteCount)
{
  for (int index = 3; index >= 1; index--) {
    // right shift data according to loop index
    gpioClear(SPI_CS);
    SPI.transfer(data >> (8 * (index - 1)));
    gpioSet(SPI_CS);
  }
}

void smartlynxSetMaxSpeed(uint32_t maxSpeed)
{
  uint32_t data;
  // data conversion for smart lynx
  data = 0.065536 * maxSpeed;
  gpioClear(SPI_CS); // assert chip select
  // set maximum speed command
  spiWriteByte(SMARTLYNX_SET_PARAM | SMARTLYNX_MAX_SPEED);
  gpioSet(SPI_CS); // de-assert chip deselect
  // set maximum speed of rotation
  smartlynxDataTransfer(data, 2);
}

void smartlynxSetMinSpeed(uint32_t minSpeed)
{
  uint32_t data;
  // data conversion for smart lynx
  data = 4.1943 * minSpeed; // minimum speed can be 0
  gpioClear(SPI_CS);
  // set minimum speed command
  spiWriteByte(SMARTLYNX_SET_PARAM | SMARTLYNX_MIN_SPEED);
  gpioSet(SPI_CS);
  //set minimum speed of rotation
  smartlynxDataTransfer(data, 2);
}

void smartlynxSetAcceleration(uint32_t acc)
{
  uint32_t data;
  // data conversion for smart lynx
  data = 0.0687 * acc;
  gpioClear(SPI_CS);
  // set acceleration command
  spiWriteByte(SMARTLYNX_SET_PARAM | SMARTLYNX_ACC);
  gpioSet(SPI_CS);
  // set acceleration value
  smartlynxDataTransfer(data, 2);
}

void smartlynxSetStepSize(uint8_t stepSize)
{
  gpioClear(SPI_CS);
  // set step size command
  spiWriteByte(SMARTLYNX_SET_PARAM | SMARTLYNX_STEP_MODE);
  gpioSet(SPI_CS);
  gpioClear(SPI_CS);
  // set step size
  spiWriteByte(stepSize);
  gpioSet(SPI_CS);
}

void smartlynxSetSpeed(uint32_t runspeed)
{
  gpioClear(SPI_CS);
  // set speed command
  spiWriteByte(SMARTLYNX_SET_PARAM | SMARTLYNX_FS_SPD);
  gpioSet(SPI_CS);
  // set speed
  smartlynxDataTransfer(runspeed, 2);
}

// this function will put motor in continuous running mode
void smartlynxRun(uint8_t dir, uint32_t runspeed)
{
  uint32_t data;
  // data conversion for smart lynx
  data = 67.1 * runspeed;
  gpioClear(SPI_CS);
  // run command
  spiWriteByte(SMARTLYNX_RUN | dir);
  gpioSet(SPI_CS);
  // set speed
  smartlynxDataTransfer(data, 3);
}

// this function will move motor by specified number of steps (stepCount)
void smartlynxMove(uint8_t dir, uint32_t stepCount)
{
  gpioClear(SPI_CS);
  // move command
  spiWriteByte(SMARTLYNX_MOVE | dir);
  gpioSet(SPI_CS);
  // set number of steps to move
  smartlynxDataTransfer(stepCount, 3);
}

// single step driving for manual control
void smartlynxSingleStep(void)
{
  // pulse to STEP pin
  gpioClear(STEP_PIN);
  delayMicroseconds(1);
  gpioSet(STEP_PIN);
}

// this function will stop motor by decelerating it
// to minimum speed set
void smartlynxSoftStop(void)
{
  gpioClear(SPI_CS);
  // soft stop command
  spiWriteByte(SMARTLYNX_SOFT_STOP);
  gpioSet(SPI_CS);
}


// this function will stop the motor immediately
void smartlynxHardStop(void)
{
  gpioClear(SPI_CS);
  // hard stop command
  spiWriteByte(SMARTLYNX_HARD_STOP);
  gpioSet(SPI_CS);
}

I think I’ll figure the rest out once I know what should be in that function
thanks

Nothing any of us can do to help, contact the company Their Website I noticed it is a v1.0 so I guess this is just a possible mistake.

thanks.. I figured if not this particular board, at least this controller would have been used elsewhere, but I searched and found nothing.. I was hoping someone was better at reading the datasheet than I am

I tried passing the data from the data transfer directly to write data, no results from that (I don't even really know the expected data types of the data transfer function).. I put a run and stop commands in the loop with a 1 second delay, and as a test I put a 100ms delay in the gpio clear and set functions, and my LED on the CS line is working, so I removed those delays.

I've just got a stack of three L6474s working on top of a Uno. The chip you have seems to be dumbed down version of the same thing.

I found the following lib on github very helpful in understanding how the SPI daisy-chain works for multiple cards.

https://github.com/MotorDriver/L6474

It's pretty inefficient but a good illustration of the basics. It may be sufficient out of the box for what you are doing. If not, there are some comments about how to improve it.

I tried to look at their site but it's so slow and stuffed with browser unfriendly content that I gave up.

ST Micro do a nice stackable, Arduino pinout, demo board for the L6474, for about 10 euro-bucks, X-NUCLEO-IHM01A1

I have three of these running under GRBL ( also on github ) to run three stepper motors, for a full blown CNC application.

Ps991: Nothing any of us can do to help, contact the company Their Website I noticed it is a v1.0 so I guess this is just a possible mistake.

Who voted you as a rep. to speak for "us" , or to declare our abilities or lack thereof ?

Declare your own lack of ability to help, if you wish. If you can't help, it may be best to join the thousands of others that can't help and remain silent. ;)

I don't have anything tidy enough to publish yet, but I can get the three status words back from the boards in about 18 us on a Uno. that's not far from the max possible with the clock speed.

I think the basic SPI communication would be the same for the two chips.

ardnut: Who voted you as a rep. to speak for "us" , or to declare our abilities or lack thereof ?

Declare your own lack of ability to help, if you wish. If you can't help, it may be best to join the thousands of others that can't help and remain silent. ;)

Part of me wants to hate you, but the reasonable side says you're right.

The simplest way is to ask the creator...however the device uses the chip L6470 and on the datasheet on section 9.2 it lists how you should send commands...

I'm glad you have a reasonable side ;)

The datasheet for the L6474 is not complete and does not explain the detail of the SPI communication. That is documented elsewhere but this is not even linked in the main doc. I assume that ST take the same approach with the L6470. The author of the lib does provide a link in comments in one of the github "issues".

Also having working code examples makes it a lot quicker to get some clear understanding.

Thanks.. I've pored over that L6474 sketch, and it's far beyond my comprehension... At this point if I can get simple code I'm not too concerned about peak transfer speeds.. I'm only going to be writing to it about every 10ms or so. I tried adapting that code but I can't find where he defines the SPI_CS pin.. it took me a bit to just get it to find the libraries but I wasn't getting anything to move.. I do have a ticket open with Phi Robotics asking them to provide a demo sketch.. the form factor of this board isn't for Arduino, but I got it wired onto a soldered breadboard with jumpers

Judging by the datasheets they are really similar, the 6474 doesn't have as good microstepping, but might have some other things.. As far as the programming, except one lacking some registers of the other, they look the same.

You're right, they don't say much about the details of it.. I just understand that the CS pin has to be pulled high between each byte transfered,.. even though I don't need to (yet) I haven't quite understood howyou read from a register... It sounds like you pull the CS low, write the register you want to read, pull the CS high, and somehow you automagically get data back?

At this point nothing is happening, I'll let you know what Phi Robotics help desk says

Apparently Phi Robotics is going to make a better example.. one that isn't missing snippets. I'll see what they come up with and post it when I get it

OK, I got something from Phi Robotics, it’s exclusively for Atmel Studio… I wouldn’t mind working with it, but it refuses to upload, it stays in “Simulator” and I’m blue in the face from trying to get it to work…

Anyhow, here’s what I got from them, it’s a little over my pay grade to figure out and I still need a bit of a hand with it… it’s attached

Example_SmartLynx.zip (52.2 KB)

Try this out,

Change this in the header file to the correct values and ports
Careful though, those arent the actual pins, those are the bits on the port, so

// SPI GPIO pins 
#define SPI_PORT		PORTB //this is correct
#define MOSI_PIN		3	//I corrected this
#define SCK_PIN			5	//I corrected this
#define MISO_PIN		4	//I corrected this
#define CS_PIN			0   <-- change this, must be on port B

// SmartLynx Reset pin
#define RESET_PORT		PORTJ <--change this, wtf
#define RESET_PIN		4	<-- change this

And try using this for the code file

#include <avr/io.h>
#include <util/delay.h>
#include "smartLynx.h"


// The function initializes GPIO pins and SPI port
static void boardInit(void);

// The function transmits given byte over SPI bus and return the received byte
static unsigned char spiExchangeByte(unsigned char data);

// function to transfer and receive values of registers of different byte length
static unsigned long smartLynxDataTransfer(unsigned long value, unsigned char bit_len);


int main(void)
{
	unsigned char dir;
	float speed;
	
	boardInit();
	smartLynxOpen();
	smartLynxSetAccelerationProfile(300,300);
	smartLynxSetMinimumSpeed(100);
	
	while(1)
	{
		dir = DIR_CLOCKWISE;
	    speed = 200;	// 200 steps per second		
		smartLynxRun(dir, speed);
	    delay(5000);
	    smartLynxSoftStop();
	    delay(5);
		dir = DIR_ANTICLOCKWISE;
	    speed = 100;			// 100 steps per second
	    smartLynxRun(dir, speed);
	    delay(5000);
		smartLynxSoftStop();
		delay(5);
	}
	return 0;	        
}

static void boardInit(void)
{
	// GPIO pins initialization
	DDRB = 0;
	DDRB = (1 << MOSI_PIN) | (1 << SCK_PIN) | (1 << CS_PIN); // set output direction for MOSI and SCK pin
	DDRB &= ~(1 << MISO_PIN);  // set input direction for MISO
	PORTB |= (1 << MISO_PIN);  // pull up MISO
	
	digitalWrite(RESET_PIN, LOW);
        digitalWrite(RESET_PIN, HIGH);
	PORTB |= (1 << CS_PIN);  // pull up 
	
	// configure SPI module
	SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0) | (1<<CPHA) | (1<<CPOL); // SPI enable, Master mode, SPI Mode 3, F_SCK = F_CPU/16
	
	// reset SmartLynx
	gpioClear(RESET_PORT, RESET_PIN);
	delayMicroseconds(250);
	gpioSet(RESET_PORT, RESET_PIN);
	delayMicroseconds(250);
}

static unsigned char spiExchangeByte(unsigned char data)
{
	unsigned char rdData = 0;
	gpioClear(SPI_PORT, CS_PIN);	// chip select low					
	SPDR = data;					// start transmission 
	while(!(SPSR & (1<<SPIF)));		// wait till transmission completes 
	rdData = SPDR;					// read data byte
	gpioSet(SPI_PORT, CS_PIN);		// chip select high
	return rdData;
}

void smartLynxOpen(void)
{
	volatile unsigned long  dummy=0;
	volatile unsigned long status=0;
	
	// check device status
	smartLynxDataTransfer((SMARTLYNX_GET_PARAM | SMARTLYNX_CONFIG), 1);
	status = smartLynxDataTransfer(0, 2);

	// set voltage for deceleration
	smartLynxDataTransfer((SMARTLYNX_SET_PARAM | SMARTLYNX_KVAL_DEC), 1);
	smartLynxDataTransfer((SMARTLYNX_GET_PARAM | SMARTLYNX_KVAL_DEC), 1);
	status = smartLynxDataTransfer(0, 1);
		
	// set voltage for acceleration
	smartLynxDataTransfer((SMARTLYNX_SET_PARAM | SMARTLYNX_KVAL_ACC), 1);
	smartLynxDataTransfer((SMARTLYNX_GET_PARAM | SMARTLYNX_KVAL_ACC), 1);
	status = smartLynxDataTransfer(0, 1);
		
	// set voltage for constant motor run
	smartLynxDataTransfer((SMARTLYNX_SET_PARAM | SMARTLYNX_KVAL_RUN), 1);
	smartLynxDataTransfer((SMARTLYNX_GET_PARAM | SMARTLYNX_KVAL_RUN), 1);
	status = smartLynxDataTransfer(0, 1);

	
	// set voltage for motor hold
	smartLynxDataTransfer((SMARTLYNX_SET_PARAM | SMARTLYNX_KVAL_HOLD), 1);
	smartLynxDataTransfer((SMARTLYNX_GET_PARAM | SMARTLYNX_KVAL_HOLD), 1);
	status = smartLynxDataTransfer(0, 1);
	
	// set step size
	smartLynxDataTransfer( (SMARTLYNX_SET_PARAM | SMARTLYNX_STEP_MODE), 1);
	smartLynxDataTransfer( SMARTLYNX_STEP_FULL, 1);
	smartLynxDataTransfer((SMARTLYNX_GET_PARAM |  SMARTLYNX_STEP_MODE), 1);
	status = smartLynxDataTransfer(0, 1);
	
	// read status register is must to set ULV0(under-voltage lockout) flag
	smartLynxDataTransfer(SMARTLYNX_GET_STATUS, 1);
	status = smartLynxDataTransfer(dummy, 2);
}

void smartLynxRun(unsigned char dir, float speed)
{
	volatile long regVal = 0;
	
	regVal = (unsigned long)(67.1*speed);				// convert speed value as per speed register in SmartLynx	
	
	smartLynxDataTransfer((SMARTLYNX_RUN | dir), 1);	// issue run command to SmartLynx
	smartLynxDataTransfer( regVal, 3);					// transfer 3 bytes length speed register value
}

void smartLynxMove(unsigned char dir, unsigned long steps)
{	
	smartLynxDataTransfer((SMARTLYNX_MOVE | dir), 1);	// issue move command to  SmartLynx
	smartLynxDataTransfer(steps, 3);					// transfer 3 bytes length step counts register value
}

void smartLynxSetMinimumSpeed(float speed)
{
	volatile long regVal = 0;
	
	regVal = (unsigned long)(4.1943*speed); // convert speed value as per minimum speed register in SmartLynx	
	
	smartLynxDataTransfer((SMARTLYNX_SET_PARAM | SMARTLYNX_MIN_SPEED), 1);  // issue minimum speed to SmartLynx
	smartLynxDataTransfer( regVal, 2); // transfer 2 bytes length minimum speed register value
}

void smartLynxResetDevice(void)
{
	smartLynxDataTransfer(SMARTLYNX_RESET_DEVICE, 1);  // reset SmartLynx driver
}

void smartLynxSoftStop()
{
	smartLynxDataTransfer(SMARTLYNX_SOFT_STOP, 1); //stop stepper with deceleration
}

void smartLynxHardStop()
{
	smartLynxDataTransfer(SMARTLYNX_HARD_STOP, 1); // stop stepper immediately
}

void smartLynxSetAccelerationProfile(float acceleration, float deceleration)
{
	volatile unsigned long regVal=0;

	// set acceleration
	regVal=(unsigned long)(0.0687*acceleration); // convert acceleration value as per acceleration register in SmartLynx
		
	smartLynxDataTransfer((SMARTLYNX_SET_PARAM | SMARTLYNX_ACC), 1);  // issue set acceleration command
	smartLynxDataTransfer(regVal, 2);  // transfer 2 bytes length acceleration register value

	
// set deceleration
	regVal=(unsigned long)(0.0687*deceleration); // convert deceleration value as per deceleration register in SmartLynx

	smartLynxDataTransfer((SMARTLYNX_SET_PARAM | SMARTLYNX_DEC), 1);   // issue set deceleration command
	smartLynxDataTransfer(regVal, 2);   // transfer 2 bytes length deceleration register value
}

static unsigned long smartLynxDataTransfer(unsigned long value, unsigned char len)
{
	volatile unsigned char data=0;
	volatile unsigned long rdData=0;
	
	switch(len)
	{
		case 3:
			//transfer 3rd data byte
			data = value>>16;
			rdData = spiExchangeByte(data);
			rdData = rdData << 8;
			
		case 2:
			//transfer 2nd  data byte
			data = value>>8;
			rdData |= spiExchangeByte(data);
			rdData = rdData << 8;
			
		case 1:
			//transfer 1st data byte
			data = value;
			rdData |= spiExchangeByte(data);
			
	}
	return rdData;
}

OK, I've made the changes, but still can't figure out how to make it upload it.. when I worked in Atmel Studio and used it with arduino sketches it was fine, but it just stays in simulator mode... I looked into "Device programming" and tried switching to the only other option of STK500, but that doesn't have the ATmega2560

been a long day.. need some shut eye,.. thanks for looking at this!

try using the arduino IDE?

I will… should it copy and paste pretty straightforward into the arduino IDE?

I don't quite understand your changes to the SPI port pins.... When I look at the Mega board layout, it gives me the following pinouts

Port B:0 = SS but not connected Port B:1 = SCK Port B:2 = MOSI Port B:3 = MISO

And the CS pin needs to be on port B? I don't need to worry about it too much for basic testing, but I kinda need my interrupt enabled pins.. For now I won't bother with it and see what I can make it do. At some point I will want the FLAG pin on an interrupt enabled pin to handle changes without polling.

So perhaps putting CS on PortB:6? (Arduino pin 11?) for now?.. I won't have much time to fiddle with this until this evening, but I'll see if things 'just work' by some miracle right now :P

OK, I set up a serial log, and it seems to hang waiting for a reply…

I tried with the pin setup you had, and looked up the pinouts on the Mega board and tried those, neither works, I’m going to doublecheck my connections.

I also tried disabling the “while (!(SPSR & (1 << SPIF)));” in the spiExchangeByte and replace it with a set delay (tried from 1us to 1000us), it made the program continue, but didn’t make things work.

I put the reset pin and CS pin all on port B as well, I’ve attached what I got so far.

Smartlynx_v1.0-150724a.zip (2.61 KB)

Im sorry, I did not know you were using a Mega, now PORTJ makes sense and the original values were correct. So change back to the PORTB0,1,2,3,...for SPI

CS doesn't need to be on port be, I think that was just a choice and that port B was what they chose

I was wondering where I find the code behind SPI.h?.. seems like a lot of this is probably covered there

static unsigned char spiExchangeByte(unsigned char data)
{
  unsigned char rdData = 0;
  Serial.print("   Sending Data 0x");
  Serial.println(data, HEX);
GetFlag();

  gpioClear(SPI_PORT, CS_PIN);	// chip select low
  SPDR = data;				// start transmission
  while (!(SPSR & (1 << SPIF)));	// wait till transmission completes   <----IT HANGS HERE
  rdData = SPDR;			        // read data byte
  gpioSet(SPI_PORT, CS_PIN);	        // chip select high

  Serial.print("       Data returned is 0x");
  Serial.println(rdData, HEX);
  GetFlag();
  return rdData;
}

Here’s the Serial output

Starting Setup
Initializing board
Port b status =            0b11000
PortB direction register = 0b110110
Resetting board...
Board init finished... 
Opening Smartlynx
   Getting Status... 
   Sending Data 0x38
       Data returned is 0x0
   Sending Data 0x0