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.
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.
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?
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.
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):
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 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:
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 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.
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.
#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!!