Getting 0 values from a rotary position sensor - SOLVED!

Dear all,

I'm trying to receive angle data from a rotary position sensor using a SPI protocol.
I think I have connected all the wires correctly but I'm keep receiving 0 values and sometimes 255 for a couple seconds.
The wiring and the code I used are below.
The datasheet of the sensor I'm using can be found at https://www.piher.net/pdf/PST-360gen2.pdf
I am new to using the SPI protocol, so any help would be appreciated.

#include <SPI.h>

const int CS_PIN = 10;
int sensorValue;

void setup() {
  Serial.begin(9600);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);
  SPI.begin();
}

void loop() {
  digitalWrite(CS_PIN, LOW);
  sensorValue = SPI.transfer(0x00);
  digitalWrite(CS_PIN, HIGH);
  
  Serial.println(sensorValue);
  delay(50);
}


Please post a photo of a hand drawn wiring diagram, with pins and wires clearly labeled.

SPI transfer returns only 8 bits per call. You will need to do two transfers to acquire the full output range of the encoder. Which, by the way, is very poorly described in the data sheet.

Search the web to see if someone has already implemented the SPI protocol on Arduino for this encoder.

1 Like

Can't read the model of the sensor fully from the photo, so could you please confirm that you checked the model of your sensor against page 4 of the datasheet to make sure it is indeed the SPI enabled model?

1 Like

Thank you for your reply, I will ask to the manufacturer for more detail.

I have enquired to the manufacturer with the photo of the sensor, and they confirmed that it is the SPI version.

Here is a clearer diagram of the wiring.


Also, how can I edit the code so that I acquire the full output if the resolution is 14bit?
Thank you.

In the Arduino chose File>Examples>SPI>BarometricPressureSensor (or better, read the library documentation) to see how to read multibyte variables.

The encoder data sheet gives no useful information on the SPI protocol or organization of the register set, so any suggestions forum members make will likely be just guesses.

let's see if we can solve your first issue first, right? :wink:

if it was me, I wold have connected the sensor MOSI line (black wire) to the UNO's MISO line(pin12)

maybe give that a go and report back! :slight_smile:

also I found this regarding the SPI data fomat for the sensor:
PST360G2--1-52031__(PST360G2-1S-C0000-ERA360-05K)_-_Instructions_for_use.pdf (305.4 KB)

That instruction sheet is a great find, but I don't see how the standard SPI library (four wire protocol) on Arduino can support a bidirectional three wire protocol like this one.

The PST-360 features a digital Serial protocol mode. The PST-360 is considered as a Slave node.
The serial protocol of the PST-360 is a three wires protocol (/SS, SCLK, MOSI-MISO):

See this page: Interfacing with 3-wire SPI - Total Phase

there the 4 to 3 wire SPI solution out there! :wink:

like this one for example:
https://arduino.stackexchange.com/questions/66076/read-write-with-half-duplex-spi


another thing OP should try!

and in terms of code, something like this maybe:
(Compiles, NOT tested!)

#include <SPI.h>

const uint8_t CS_PIN = 10;
uint8_t sensorValue;

void setup() {
  Serial.begin(9600);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);
  SPI.begin();
  Serial.print("Ready!");
}

void loop() {
  /*A data frame consists of 10 bytes:
    ā€¢ 2 start bytes (AAh followed by FFh)
    ā€¢ 2 data bytes (DATA16 ā€“ most significant byte first)
    ā€¢ 2 inverted data bytes (/DATA16 ā€“ most significant byte first)
    ā€¢ 4 all-Hi bytes
  */

  digitalWrite(CS_PIN, LOW);
  delay(1); //arbitrary delay
  sensorValue = SPI.transfer(0xAA); //send start byte
  Serial.println(" ");
  Serial.print(sensorValue,HEX);
  //send subsequent 9 bytes
  for (uint8_t i = 1; i < 10; ++i) {
    sensorValue = SPI.transfer(0xFF);
    Serial.print(" ");
    Serial.print(sensorValue, HEX);
  }
  delay(1); //arbitrary delay
  digitalWrite(CS_PIN, HIGH);

  delay(500);
}

good luck!

Thank you for your help! However I tried the sensor MOSI line to the UNO's MISO, and also the solution for 3 wire with a resistor with your code but they both did not work.

When I did MOSI-MISO, all the 10 bytes received are 00, and when I used the resistor, all of them were FF.

The serial output was this:

Ready!
FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF
...

The code posted by @sherzaad is a start, but does not follow the timing rules in the instruction sheet (linked in post #8) and cannot be expected to work properly.

Read those instructions very carefully and modify the code as required.

For example, slave startup takes 16 ms. You should wait at least 20 ms before initiating communications.

Also see this:

  1. Slave Reset
    To synchronize communication, the master deactives /SS high for at least t5 (1.5ms). In this case, the Slave will be ready to receive a new frame. The master can re-synchronize at any time, even in the middle of a byte transfer.
    Note: Any time shorter that t5 leads to an undefined frame state, because the Slave may or may not have seen /SS inactive.

I think I have included all the required delays and followed the instructions. The code currently is:

#include <SPI.h>

const uint8_t CS_PIN = 10;
uint8_t sensorValue;

void setup() {
  Serial.begin(9600);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH); 
  SPI.begin();
  delay(20);
  Serial.print("Ready!");
}

void loop() {
  /*A data frame consists of 10 bytes:
    ā€¢ 2 start bytes (AAh followed by FFh)
    ā€¢ 2 data bytes (DATA16 ā€“ most significant byte first)
    ā€¢ 2 inverted data bytes (/DATA16 ā€“ most significant byte first)
    ā€¢ 4 all-Hi bytes
  */

  digitalWrite(CS_PIN, LOW);
  delayMicroseconds(100);
  sensorValue = SPI.transfer(0xAA); //send start byte
  Serial.println(" ");
  Serial.print(sensorValue,HEX);
  delayMicroseconds(100);
  //send subsequent 9 bytes
  for (uint8_t i = 1; i < 10; ++i) {
    sensorValue = SPI.transfer(0xFF);
    Serial.print(" ");
    Serial.print(sensorValue, HEX);
    delayMicroseconds(100);
  }
  digitalWrite(CS_PIN, HIGH);

  delay(500);
}

I have tried the wiring @sherzaad posted and the wiring provided in the instruction.
None of them worked and only showed 10 bytes of FF. :smiling_face_with_tear:

showed 10 bytes of FF

You should be seeing AA as the first byte.

I still don't see how this can work with Arduino. MOSI is by default on Arduino almost certainly NOT open drain, as the protocol appears to require.

I don't see anything obviously wrong with the code, but the print statements insert very long, variable length delays (about 1 ms per character printed).

Try something like this, which defers printing until the entire transaction is completed.

#include <SPI.h>

const uint8_t CS_PIN = 10;
uint8_t sensorValue;

void setup() {
  Serial.begin(9600);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);
  SPI.begin();
  delay(20);
  Serial.print("Ready!");
}

void loop() {
  /*A data frame consists of 10 bytes:
    ā€¢ 2 start bytes (AAh followed by FFh)
    ā€¢ 2 data bytes (DATA16 ā€“ most significant byte first)
    ā€¢ 2 inverted data bytes (/DATA16 ā€“ most significant byte first)
    ā€¢ 4 all-Hi bytes
  */
  char data[10] = {0};
  int index = 0;
  digitalWrite(CS_PIN, LOW);
  delayMicroseconds(100);
  data[index++] = SPI.transfer(0xAA); //send start byte
  delayMicroseconds(100);
  //send subsequent 9 bytes
  for (uint8_t i = 1; i < 10; ++i) {
    data[index++] = SPI.transfer(0xFF);
    delayMicroseconds(100);
  }
  digitalWrite(CS_PIN, HIGH);
  for (uint8_t i = 0; i < 10; ++i) Serial.println(data[i], HEX);

  delay(500);
}

I just noticed that this sensor is very slow, with t1 = 7 us (minimum period of any bit). The default clock speed of SPI on Arduino is 4 MHz, which is way too fast.

Try using SPIsettings() to reduce the clock speed to 125 kHz, which is the lowest you can go. Also study the DATA ORDER and MODE parameters, to see if those are correct.

Thank you so much, this worked and now I am receiving a signal!
The code I'm using now is this:

#include <SPI.h>

const uint8_t CS_PIN = 10;

void setup() {
  Serial.begin(9600);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH); 
  SPI.begin();
  delay(20);
}

void loop() {
  /*A data frame consists of 10 bytes:
    ā€¢ 2 start bytes (AAh followed by FFh)
    ā€¢ 2 data bytes (DATA16 ā€“ most significant byte first)
    ā€¢ 2 inverted data bytes (/DATA16 ā€“ most significant byte first)
    ā€¢ 4 all-Hi bytes
  */

  //min time between each bit transfer is 7 microseconds so set to 125kHz which is the lowest
  SPI.beginTransaction(SPISettings(125000, MSBFIRST, SPI_MODE0));   

  //Start transaction
  digitalWrite(CS_PIN, LOW);
  delayMicroseconds(20);
  
  //Send start bytes
  SPI.transfer(0xAA); 
  SPI.transfer(0xFF); 
  delayMicroseconds(50);

  //Send and receive data bytes
  uint8_t b1 = SPI.transfer(0xFF); 
  delayMicroseconds(40);
  uint8_t b2 = SPI.transfer(0xFF); 
  
  //Extract and calculate angle
  uint16_t data_bytes = (b1 << 8) | b2;  // Combine two bytes and shift left
  uint16_t mask = 0x3FFF;  // Mask to extract first 14 bits
  uint16_t angle_raw = data_bytes &= mask;  // Extract first 14 bits
  float angle = (float)(angle_raw / 16384.0 * 360);  // Divide by 2^14 and multiply by 360 to get degrees
  Serial.println(angle);

  //End transaction
  digitalWrite(CS_PIN, HIGH);
  SPI.endTransaction();

  delay(150);
}

However I am getting a weird angle value like the graph shown below, which is the result when I do one full 360deg rotation. It increases faster then it should, suddenly drops and increase to 360 again.
I am going to try a different connection to see how that changes the result.

Why are you reading fewer than 10 bytes?

1 Like

I thought it didn't matter because the rest of them are all FF.
Also it was mentioned in the sensor datasheet that the master can re-synchronize at any time, even in the middle of a byte transfer.

That isn't working as you expect. Question your assumptions and do the tests.
Looking at the data sheet, MODE1 might be the best choice.

Thank you, I have modified the code.

#include <SPI.h>

const uint8_t CS_PIN = 10;

void setup() {
  Serial.begin(9600);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH); 
  SPI.begin();
  delay(20);
}

void loop() {
  /*A data frame consists of 10 bytes:
    ā€¢ 2 start bytes (AAh followed by FFh)
    ā€¢ 2 data bytes (DATA16 ā€“ most significant byte first)
    ā€¢ 2 inverted data bytes (/DATA16 ā€“ most significant byte first)
    ā€¢ 4 all-Hi bytes
  */
  uint8_t data[10] = {0};
  int index = 0;

  //min time between each bit transfer is 7 microseconds so set to 125kHz which is the lowest
  SPI.beginTransaction(SPISettings(125000, MSBFIRST, SPI_MODE1));   

  //Start transaction
  digitalWrite(CS_PIN, LOW);
  delayMicroseconds(20);
  
  //Send start byte
  data[index++] = SPI.transfer(0x55);
  delayMicroseconds(50);

  //Send and receive data bytes
  for(uint8_t i = 1; i < 10; i++){
    data[index++] = SPI.transfer(0x00); 
    delayMicroseconds(40);
  }
  
  //End transaction
  digitalWrite(CS_PIN, HIGH);
  SPI.endTransaction();
  
  //Print dataframe
  for(uint8_t i = 0; i < 10; i++){
    Serial.print(data[i], HEX);
    Serial.print(" ");
  }

  //Extract and calculate angle
  uint16_t data_bytes = (data[2] << 8) | data[3];  // Combine two bytes
  uint16_t angle_raw = data_bytes >> 2; // Extract first 14 bits
  for (int i = 13; i >= 0; i--) Serial.print((angle_raw & (1 << i)) ? "1" : "0"); //Print in binary
  float angle = (float)(angle_raw / 16384.0 * 360);  // Divide by 2^14 and multiply by 360 to get degrees
  Serial.print(" ");
  Serial.println(angle);

  delay(150);
}

I am sending 0x55 as the start byte and 0x00 as the subsequent 9 bytes because in the circuit diagram below, I used an n-channel MOSFET. If I send 0xAA and 0xFF, I only receive FF.

So now the angle I am receiving is 37deg to 323deg. The angle at 180 seems to be correct, but at the origin it's either 37 or 323. I think now I can now just correct the angle by mapping.

EDIT: Actually it is normal that the angle is from 37deg to 323deg because the output shows 10-90% for full rotation according to the datasheet. So now the sensor is working very well. Thanks for your help!!