How to transfer multiple bytes to write 16 bit registers using SPI with Arduino UNO

Hi

I am absolutely novice at Arduino. I am trying to control a PiezoElectric motor with Arduino Uno. Arduino is communicating with the company provided motor driver through SPI.
There are 02 16bit Control registers in the driver. I want to initialize the registers with following values to start the motor at a particular speed:

Ctrl Register 1 address : 0x1A
Ctrl Register 1 data : 1000000000000000
Ctrl Register 1 address : 0x1B
Ctrl Register 1 data : 0100000111110100

There is another register to read the encoder status but it is not required yet.
I am sending 04 bytes one by one but I don't know which byte to send first and which register will it go to.

Here is the link to the Manual by the company :

(==>page 5,7,8)

I tried the following code but motor doesn't start:

#include <SPI.h>

//Arduino Pin definitions
#define DATAOUT 11 //COPI
#define DATAIN  12 //CIPO
#define SPICLOCK  13 //sck
#define CS 10 //cs

//PSMD-PCC Registers Addresses:
const int control_1 = 0x1A;   // Control 3 register
const int control_2 = 0x1B;   // Control 2 register
const int status_1 = 0x1C;    // Status register

byte c11 = 0b10000000;
byte c12 = 0b00000000;
byte c21 = 0b01000001;
byte c22 = 0b11110100;




void setup() {

  pinMode(CS, OUTPUT);

  digitalWrite(CS, HIGH);
  Serial.begin(9600);
  Serial.println("Starting motor at 500...");
  delay(2000);
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);

  digitalWrite(CS, LOW);
  SPI.transfer(c11);
  SPI.transfer(c12);
  SPI.transfer(c21);
  SPI.transfer(c22);
  digitalWrite(CS, HIGH);
}

void loop() {

}

Generally, you would send the register's address first:

  digitalWrite(CS, LOW);
  SPI.transfer(control_1);
  SPI.transfer(c11);
  SPI.transfer(c12);
  SPI.transfer(c21);
  SPI.transfer(c22);
  digitalWrite(CS, HIGH);

Many devices let you set consecutive registers in a single transaction but I don't know if your device will do that. You may need to write the two registers separately:

  digitalWrite(CS, LOW);
  SPI.transfer(control_1);
  SPI.transfer(c11);
  SPI.transfer(c12);
  digitalWrite(CS, HIGH);

  digitalWrite(CS, LOW);
  SPI.transfer(control_2);
  SPI.transfer(c21);
  SPI.transfer(c22);
  digitalWrite(CS, HIGH);

@fanique

Upload the following sketch in UNO and check if the Motor is rotating CW at about half of the maximum speed. Assumption: Device ID = 00.

#include <SPI.h>

void setup()
{
  Serial.begin(9600);
  SPI.begin(); //default bit rate: 4 Mbits/sec; MODE0
  digitalWrite(SS, LOW);  //driver is selected
  //-------------------------------------------
  byte y1 = SPI.transfer(0b10011010);//W=1, ID=00, TA=11010
  delayMicroseconds(50);
  byte y2 = SPI.transfer(0x80);  //HighByte of data
  delayMicroseconds(50);
  byte y3 = SPI.transfer(0x00);  //LowByte of data
  delayMicroseconds(50);
  //------------------------------------------------
  byte y4 = SPI.transfer(0b10011011);//W=1. ID=00, TA=11011
  delayMicroseconds(50);
  byte y5 = SPI.transfer(0x47);  //HighByte of data
  delayMicroseconds(50);
  byte y6 = SPI.transfer(0xD0);  //LowByte of data
  delayMicroseconds(50);
}

void loop() 
{

}

Thanks for your response. I tried both of the approaches you suggested. It didn't work. To verify what was sent to registers, I added the following additional snippet (after first transfer) to read and print it later:

  digitalWrite(CS, LOW);
  SPI.transfer(control_1);
  byte A = SPI.transfer(c11);
  byte B = SPI.transfer(c12);
  digitalWrite(CS, HIGH);
  
  digitalWrite(CS, LOW);
  SPI.transfer(control_2);
  byte C = SPI.transfer(c21);
  byte D = SPI.transfer(c22);
  digitalWrite(CS, HIGH);

  Serial.println(A,BIN);
  Serial.println(B,BIN);
  Serial.println(C,BIN);
  Serial.println(D,BIN);

Following was serial monitor output text:

23:23:39.926 -> 11000000
23:23:39.926 -> 0
23:23:39.926 -> 0
23:23:39.926 -> 0

I even tried following just in case:

digitalWrite(CS, LOW);
  SPI.transfer(control_1);
  SPI.transfer(c11<<8);
  SPI.transfer(c12);
  digitalWrite(CS,HIGH);
  
  digitalWrite(CS, LOW);
  SPI.transfer(control_2);
  SPI.transfer(c21<<8);
  SPI.transfer(c22);
  digitalWrite(CS,HIGH);

Motor doesn't start and serial monitor output is still unchanged.
I changed c11,c12,c21,c22 values to random numbers but still the serial monitor shows the same output. As if data is not being delivered to registers and we are just reading the pre-stored values.

Thanks Mostafa for your help. I tried this code. Nothing moved. :confused:
I added following in the end to see the y* values in the serial monitor:

  Serial.println(y2,BIN);
  Serial.println(y3,BIN);
  Serial.println(y5,BIN);
  Serial.println(y6,BIN);

and the serial monitor output came to be :

00:03:12.022 -> 11000000
00:03:12.022 -> 0
00:03:12.022 -> 11111111
00:03:12.069 -> 11111111

So data is actually being sent but in a very strange way perhaps. Why would the first byte have two 1s on left side when we sent 10000000..?

The data sheet of the driver has given the format how to send Header and Data. I have followed that to the level of my understanding. Please check the wiring between UNO and the connector of the driver and also the Device ID selection switch.

UNO                    Driver Connector
DPin-10 (SS)           7 (SPI_CS)
DPin-11 (MOSI)         9 (SPI_DT_IN)
DPin-12 (MISO)         10 (SPI_DT_OUT)
DPin-13 (SCK)          8 (SPI_CLK)
GND                    6 (GND)

Device ID Check the following switch to get the correct Device ID which nneds to be entered into the Header.
deviceID
h

I forgot you need to set Bit 7 in the address byte when you want to Write a register.

#include <SPI.h>

const byte CS = 10; // ChipSelect

//PSMD-PCC Registers Addresses:
const int RegControl_1 = 0x1A;   // Control 3 register
const int RegControl_2 = 0x1B;   // Control 2 register
const int RegStatus_1 = 0x1C;    // Status register

void WriteRegister16(uint8_t Address, uint16_t Data)
{
  digitalWrite(CS, LOW);
  SPI.transfer(Address | 0x80);
  SPI.transfer(Data >> 8);  // MSB
  SPI.transfer(Data & 0xFF);  // LSB
  digitalWrite(CS, HIGH);
}

uint16_t ReadRegister16(uint8_t Address)
{
  uint16_t data;
  digitalWrite(CS, LOW);
  SPI.transfer(Address & 0x7F);
  data = SPI.transfer(0);  // MSB
  data <<= 8;
  data |= SPI.transfer(0);  // LSB
  digitalWrite(CS, HIGH);

  return data;
}

void ShowRegisters()
{
  Serial.print("  RegControl_1: ");
  Serial.println(ReadRegister16(RegControl_1) & 0xFFFFul, BIN);
  Serial.print("  RegControl_2: ");
  Serial.println(ReadRegister16(RegControl_2) & 0xFFFFul, BIN);
  Serial.print("  RegStatus_1: ");
  Serial.println(ReadRegister16(RegStatus_1) & 0xFFFFul, BIN);
}

void setup()
{
  Serial.begin(9600);
  delay(200);

  digitalWrite(CS, HIGH);
  pinMode(CS, OUTPUT);


  Serial.println("Starting motor at 500...");
  delay(2000);


  SPI.begin();
  SPI.setBitOrder(MSBFIRST);


  Serial.println("Previous value of registers: ");
  ShowRegisters();

  WriteRegister16(RegControl_1, 0b1000000000000000);
  WriteRegister16(RegControl_2, 0b0100000111110100);
  
  Serial.println("Modified values of registers: ");
  ShowRegisters();
}

void loop() {}

Thanks for your effort to go through the datasheet. I double checked this already and connections and switches are according to the instructions given.

Thank you so much. This code worked and system is finally running.
Could you please explain three lines of WriteRegister16 function. I don't understand how | and & are helping here and where do 0x80 and 0xFF come from. Also shouldn't we shift our bits to the left (using<<) for MSB ?
It'll help me modify my code.

Write:

  // Set the top bit of the Address to indicate a WRITE
  SPI.transfer(Address | 0x80);

  // Send MSB first
  SPI.transfer(Data >> 8);  // MSB

  // Then send LSB
  SPI.transfer(Data & 0xFF);  // LSB

Read:

  // Clear the top bit of the Address to indicate READ
  SPI.transfer(Address & 0x7F);

  // Read the MSB first
  data = SPI.transfer(0);  // MSB

  // Move the MSB left to the top 8 bits
  data <<= 8;

  // Read the LSB 
  data |= SPI.transfer(0);  // LSB
1 Like

0x80 = 1000 0000 and | is the OR operator so you are taking the address and ORing it with 10000000 which just sets the most significant bit as @johnwasser stated when you are intended you write to a register.

Data is a 16 bit quantity so you first shift it right by 8 bits (>> 8) which gives you just the upper bytes to transfer. You than AND the data with 0xFF which gives clears out the upper byte and gives you only the lower byte to transfer.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.