Daisy Chaining multiple AutoDrivers

In a previous post I tested running SPI over a distance of roughly 100 feet using RS-485 over Cat5 with only a single SPI slave device, in this post I will cover the how to have multiple SPI slaves (AutoDriver with RS-485 shield) daisy chained together. The goal here is to communicate with multiple AutoDrivers over a long distance with only a single chip select signal.

The way daisy chaining works with multiple devices on a single SPI bus is similar to shift registers, data from the SPI master is shifted out MSB first and shifted into the SPI slave on the falling edge of the SPI clock. At the same time the data in the slave is shifted out to the next device, when there is only a single slave device this would go back to the MISO pin on the master or with multiple slave devices the MOSI pin on the next slave. The number of devices that can be daisy chained together is only limited by length of the SPI bus and delay caused by each of the SPI devices on the SPI bus (see my SPI over long distances post for more details).

When there are multiple devices on an SPI chain the way data is sent must be altered to correctly send and receive data. Even if you are sending or receiving only from one SPI slave the number of bytes transmitted must equal the number of SPI slaves on the bus. In my case there are going to be three AutoDrivers on the SPI bus so three bytes must be sent each time, one for each device. If you want to send to and receive a byte from the first device (AutoDriver #0) two byte of 0x00 (NOP) must be sent first and then the byte destined for the first device. The first two bytes sent shifts the data in the shift register of the first device (AutoDriver #0) to the third device (AutoDriver #2) and the two bytes of 0x00 into the first and second devices (AutoDriver #0 and #1). The third byte sent will shift the data from the first device (currently in AutoDriver #2) into the Arduino and the byte for the first device into the first device.

AutoDriver daisy chaining data flow

When communicating with only a single device at a time as mentioned above the Arduino must save and return only the byte that was returned from the SPI slave in question and ignore the bytes from the other devices. In the case of the above example, since the third byte transmitted is intended for the first device the third byte received is the data from the first device and the byte to be returned for processing. In general to send and receive a byte from a specific SPI slave device in a chain the position of the data should be number_of devices_in_chain – position_of_device_in_chain, where the position starts from 0. For a better understanding of how daisy chaining multiple device on a SPI bus works I would suggest looking over this Daisy-Chaining SPI Devices app note from Maxim Integrated.

To make life easy I modified the Arduino AutoDriver library to add index and count variable so that each instance of the AutoDriver library is assigned a unique index number and the count represents the total number of instances.

class AutoDriver
{
  public:
    ...
    
    // SPI chain index
    int SPIindex;
    
  private:
    ...
    
    static int _SPIcount;
};
void AutoDriver::SPIConfig()
{
  ...
  
  // Config current location in the SPI chain.
  SPIindex = _SPIcount++;
}

I also modified the SPIXfer function to use this information and send and receive data from the correct device in the chain.

byte AutoDriver::SPIXfer(byte data)
{
  byte rxTemp;
  byte rxData;
  byte txData;
  
  // The built in Arduino digitalWrite function is to slow to use for the SPI  
  // bus and the AutoDriver will not respond. So the pins are being accessed
  // directly to speed up the bus write speed.
  
  // Get bit mask for SPI Master signals
  uint8_t cs_mask = digitalPinToBitMask(_CSPin);
  uint8_t clk_mask = digitalPinToBitMask(_CLKPin);
  uint8_t mosi_mask = digitalPinToBitMask(_MOSIPin);
  
  // Get ports for SPI Master signals
  uint8_t cs_port = digitalPinToPort(_CSPin);
  uint8_t clk_port = digitalPinToPort(_CLKPin);
  uint8_t mosi_port = digitalPinToPort(_MOSIPin);
  
  // Get output registers for SPI Master signals
  volatile uint8_t *cs_reg = portOutputRegister(cs_port);
  volatile uint8_t *clk_reg = portOutputRegister(clk_port);
  volatile uint8_t *mosi_reg = portOutputRegister(mosi_port);
  
  // Set chip select low
  *cs_reg &= ~cs_mask;
  
  // Delay to meet chip select setup time
  asm("NOP");
  
  // Loop though for each device in the SPI chain
  for (int x = 1; x <= _SPIcount; x++) {
    // Send '0' as filler for other devices
    if ((_SPIcount - x) == SPIindex){
      txData = data;
    } else {
      txData = 0x00;
    }

    // Trasmit Data
    for (uint8_t y = 0; y < 8; y++)  {
      *clk_reg &= ~clk_mask;

      if (!!(txData & (1 << (7 - y))) > 0) {
        *mosi_reg |= mosi_mask;
      } else {
        *mosi_reg &= ~mosi_mask;
      }
      
      *clk_reg |= clk_mask;
    }
    
    // Wait for return data
    while (!(SPSR&(1<<SPIF)));
    
    // Read data and clear SPIF flag
    rxTemp = SPDR;
    
    // Save returned data from correct device
    if ((_SPIcount - x) == SPIindex) {
      rxData = rxTemp;
    }
    
  }
  
  // Set chip select high
  *cs_reg |= cs_mask;
  
  return rxData;
}

With these modifications the library would now be able transmit and receive data from multiple devices in a SPI chain as long as the number of instances of the library matches the number of devices in the chain. Below is an example Arduino project to show how this would be accomplished.

#include <AutoDriver.h>

// Pin Definitions
#define CLK_PIN  2
#define SS_PIN   3
#define MOSI_PIN 4
#define RST_PIN  5

// Global instances of AutoDriver Library
AutoDriver boardA(CLK_PIN, MOSI_PIN, SS_PIN, RST_PIN);
AutoDriver boardB(CLK_PIN, MOSI_PIN, SS_PIN, RST_PIN);
AutoDriver boardC(CLK_PIN, MOSI_PIN, SS_PIN, RST_PIN);

void setup() {                
  // Configure the AutoDriver boards
  dSPINConfig(&boardA);
  dSPINConfig(&boardB);
  dSPINConfig(&boardC);   
}

void loop() {
  // Move all steppers forward at different speeds
  boardA.run(FWD, 100);
  boardB.run(FWD, 200);
  boardC.run(FWD, 400);
  
  delay(1000);
  
  // Move all steppers reverse at different speeds.
  boardA.run(REV, 600);
  boardB.run(REV, 800);
  boardC.run(REV, 1000);
  
  delay(1000);
}

// Copied from the AutoDriver library example
void dSPINConfig(AutoDriver *board) {
  board->configSyncPin(BUSY_PIN, 0);// BUSY pin low during operations;
                                    //  second paramter ignored.
  board->configStepMode(STEP_FS);   // 0 microsteps per step
  board->setMaxSpeed(10000);        // 10000 steps/s max
  board->setFullSpeed(10000);       // microstep below 10000 steps/s
  board->setAcc(10000);             // accelerate at 10000 steps/s/s
  board->setDec(10000);
  board->setSlewRate(SR_530V_us);   // Upping the edge speed increases torque.
  board->setOCThreshold(OC_750mA);  // OC threshold 750mA
  board->setPWMFreq(PWM_DIV_2, PWM_MUL_2); // 31.25kHz PWM freq
  board->setOCShutdown(OC_SD_DISABLE); // don't shutdown on OC
  board->setVoltageComp(VS_COMP_DISABLE); // don't compensate for motor V
  board->setSwitchMode(SW_USER);    // Switch is not hard stop
  board->setOscMode(INT_16MHZ_OSCOUT_16MHZ); // for boardA, we want 16MHz
                                    //  internal osc, 16MHz out. boardB and
                                    //  boardC will be the same in all respects
                                    //  but this, as they will bring in and
                                    //  output the clock to keep them
                                    //  all in phase.
  board->setAccKVAL(255);           // We'll tinker with these later, if needed.
  board->setDecKVAL(255);
  board->setRunKVAL(255);
  board->setHoldKVAL(32);           // This controls the holding current; keep it low.
}

After modifying the code I proceed to test the setup with two AutoDriver boards and their associated shields. This worked great, after spending two weeks troubleshooting a bad driver chip that made it appear that the first device in the chain was always returning 0xFF, and I then moved on to test three AutoDrivers in the SPI chain. This also worked without any hiccups and I called this part of the project a complete success!!! As always the code can be found on my fork of the AutoDriver library on GitHub. I also have a branch of the AutoDriver library that contains only the daisy chain code that can be used on a regular SPI bus.

One final note, the current implementation only communicates with one device at a time, so if you need to send a command to the devices on the SPI bus an individual command must be sent to each device. It is possible to combine these commands so that all can be sent at the same time but I am saving that modification for a later date as it would require substantial modification of the AutoDriver library.

Share

One thought on “Daisy Chaining multiple AutoDrivers

  1. Wow. Just…wow. That’s an amazing thing. I was looking for answers like this. In my own project, I only need to go four feet, but even that is too much for SPI. Thank you so much for leading the way on this!

Leave a Reply

Your email address will not be published. Required fields are marked *