Coding for AS5147 Rotary Sensor by AMS

Hi,

I am trying to code the as5147 by ams to read an angle and print the output on the serial monitor in decimal. I am trying to use the SPI interface to do so. However, I'm not too sure whats going wrong. I am attaching my code as well as the datasheet for the chip if that is any help.

/*AMS Rotary Sensor AS5147
 Measures absolute angle position referenced at a set NORTH

 Circuit
 UNO: MOSI pin 11
      MISO pin 12 
      CLK  pin 13
      CSN  pin 10

 Mega: MOSI pin 51
       MISO pin 50 
       CLK  pin 52
       CSN  pin 53  
 */

#include <SPI.h>

//Set Slave Select Pin
//MOSI, MISO, CLK are handeled automatically
int CSN = 10;
//SPI Settings
SPISettings settings(10000000, MSBFIRST, SPI_MODE1);


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

  //Set Slave Select Pin Directions
  pinMode(CSN, OUTPUT);
  //Set Slave Select High to Start i.e disable chip
  digitalWrite(CSN, HIGH);
  //Initialize SPI 
  SPI.begin();
}

//Function to read data from the encoder
unsigned int readRegister(unsigned int address){

  //Set SPI parameters according to AS5147
  SPI.beginTransaction(settings);
  //Enable chip
  digitalWrite(CSN, LOW);
  
  SPI.transfer16(address);

  
  // send a value of 0 to read the first byte returned:
  unsigned int angle = SPI.transfer16(0);

  angle &= 0xCFFF;

  return angle;
  
  //Wait 10 miliseconds for chip to write data (Time between CSn falling edge and CLK rising edge is 350ns)
  delay(100);
  
  //disable device
  digitalWrite(CSN, HIGH);
  
  SPI.endTransaction();
  
}


void loop() {

  unsigned int result = readRegister(0xFFFF);
  
  Serial.println(result);

  delay(1000);
  
}

Thanks!

AS5147_Datasheet_EN_v3.pdf (565 KB)

However, I'm not too sure whats going wrong.

We aren't too sure either, because you forgot to tell us what happens when you run the program.

Sorry about that @jremington. alright so I updated the code slightly:

Here's the modified code:

/*AMS Rotary Sensor AS5147
 Measures absolute angle position referenced at a set NORTH

 Circuit
 UNO: MOSI pin 11
      MISO pin 12 
      CLK  pin 13
      CSN  pin 10

 Mega: MOSI pin 51
       MISO pin 50 
       CLK  pin 52
       CSN  pin 53  
 */

#include <SPI.h>

//Set Slave Select Pin
//MOSI, MISO, CLK are handeled automatically
int CSN = 10;
//SPI Settings
SPISettings settings(10000000, MSBFIRST, SPI_MODE1);


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

  //Set Slave Select Pin Directions
  pinMode(CSN, OUTPUT);
  //Set Slave Select High to Start i.e disable chip
  digitalWrite(CSN, HIGH);
  //Initialize SPI 
  SPI.begin();
}

//Function to read data from the encoder
unsigned int readRegister(unsigned int address){

  //Set SPI parameters according to AS5147
  SPI.beginTransaction(settings);
  //Enable chip
  digitalWrite(CSN, LOW);

  unsigned int angle = SPI.transfer16(address);

  angle = (angle & 0x3FFF);

  
  //Wait 10 miliseconds for chip to write data (Time between CSn falling edge and CLK rising edge is 350ns)
  delay(10);
  
  //disable device
  digitalWrite(CSN, HIGH);
  
  SPI.endTransaction();

  return angle;
  
}


void loop() {

  unsigned int result = readRegister(0x3FFF);

  int pos = map(result, 0, 16383, 0, 360);
  
  Serial.println(pos);

  delay(1000);
  
}

I am now transferring 16 bits directly using the transfer function and also give the command input at the same time that i read the angle.

So when I attach the chip according to the pins I enumerate, and bring the magnet close to the chip. the serial monitor just displays two values. 0 when the magnet is far and 360 when the magnet is close. Well, more or less anyway. The behavior is rather sporadic.

Print out the values returned as "result". Do they make sense?

Do you realize that it takes two SPI read operations to return the angle data? The first sets the address and the second reads out the data.

jremington:
Do you realize that it takes two SPI read operations to return the angle data? The first sets the address and the second reads out the data.

On page 13 of the datasheet, it looks like the sensor will send data at the same time the command is issued. The data returned will be the sensor information corresponding to the previous read command. It doesn't look like two reads are needed as long as one is will to accept old(ish) data. This assumes I'm reading the datasheet correctly (which isn't always a safe assumption).

@CheeseDontDie, what kind of magnet are you using? Is it an appropriate diamagnetically magnetized magnet? Most round magnets are axially magnetized. An axially magnetized magnet can sort of be made to work by turning it on its side. A magnet turned on its side won't work nearly as well as a diametrically magnetized one.

There are several registers in the device, so the device will return whatever data were requested by the previous read (not necessarily angle data).

jremington:
There are several registers in the device, so the device will return whatever data were requested by the previous read (not necessarily angle data).

It looks like the main loop of the program only reads the one register. I just noticed there's a one second delay in loop. The encoder data will always be one second old as the program is currently written.

I've used the AS5055 sensors a lot. I averaged 16 samples while the magnet was moving and 64 samples when the magnet was stationary. I found averaging the samples gave a much more stable reading.

Here's a video I made reading from two sensors.

As I mentioned earlier, it's possible to use axially magnetized round magnets but the magnet needs to held on end. Here's a link to a thread in the Parallax forum about the AS5055 sensor. I used the sensor with a Propeller microcontroller but there might be some useful information in the thread to apply to using this sensor with an Arduino.

Thank you for replying guys. @DuaneDegn: I thought that one second delay was to print the angle on the screen every one second. I'm rather certain I'm using a Diametrically Magnetized magnet.

So in the code, i am reading a 16 bit value, out of which the 15th and 14th bit are not part of the angle measurement, so I try to mask the values to get a 14 bit number.

Can you guys suggest edits to the code ? I'm kinda new to this and am a little lost right now.

In my opinion the map() function is not useful. The problems have been discussed many times, see map() function equation wrong - Suggestions for the Arduino Project - Arduino Forum

I use the AS5040 instead of the AS5147 device, but my reading of the AS5147 data sheet suggests that instead of this:

unsigned int result = readRegister(0x3FFF);

int pos = map(result, 0, 16383, 0, 360);

Serial.println(pos);

you might use something like the following to get the current angle (not one delayed by one second):

unsigned int result = readRegister(0x3FFF); //set up register address
result = readRegister(0x3FFF); //read the data

int pos = ( (unsigned long) result)*360UL/16384UL;

Serial.println(pos);

The long operations are suggested simply because 360 is not conveniently represented by an integer fraction of 16384.

Thanks for the reply @jremington ! I will let you know how this turns out.

Okay, so I made adjustments to my code. I added what the code above; seemed to make sense. I also tinkered with the function. I first transfer the 16 bit address as command bytes and then transfer nothing just to read the angle. Here's the modified code:

/*AMS Rotary Sensor AS5147
 Measures absolute angle position referenced at a set NORTH

 Circuit
 UNO: MOSI pin 11
      MISO pin 12 
      CLK  pin 13
      CSN  pin 10

 Mega: MOSI pin 51
       MISO pin 50 
       CLK  pin 52
       CSN  pin 53  
 */

#include <SPI.h>

//Set Slave Select Pin
//MOSI, MISO, CLK are handeled automatically
int CSN = 10;
//SPI Settings
SPISettings settings(10000000, MSBFIRST, SPI_MODE1);


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

  //Set Slave Select Pin Directions
  pinMode(CSN, OUTPUT);
  //Set Slave Select High to Start i.e disable chip
  digitalWrite(CSN, HIGH);
  //Initialize SPI 
  SPI.begin();
}

//Function to read data from the encoder
unsigned int readRegister(unsigned int address){

  //Set SPI parameters according to AS5147
  SPI.beginTransaction(settings);
  //Enable chip
  digitalWrite(CSN, LOW);

  SPI.transfer16(address);
  unsigned int angle = SPI.transfer16(0x0000);

  angle = (angle & 0x3FFF);
  
  //disable device
  digitalWrite(CSN, HIGH);
  
  SPI.endTransaction();

  return angle;
  
}

//Function to write data to servo



//---------------------------------------

void loop() {

  unsigned int result = readRegister(0x3FFF); //set up register address
  result = readRegister(0x3FFF); //read the data

  int pos = ( (unsigned long) result)*360UL/16384UL;

  Serial.println(pos);

  delay(1000);
  
}

Now, the sensor just throws the max (now 359) at me the entire time.

You shouldn't have tinkered with the readRegister function.

SPI.transfer16(address);
  unsigned int angle = SPI.transfer16(0x0000);

Now you read the device twice in readRegister and twice in the main loop, with differing register addresses each time. I'm not sure what that accomplishes, but clearly it does not work.

I think I have it working now. Here's the final code!

/*AMS Rotary Sensor AS5147
 Measures absolute angle position referenced at a set NORTH

 Circuit
 UNO: MOSI pin 11
      MISO pin 12 
      CLK  pin 13
      CSN  pin 10

 Mega: MOSI pin 51
       MISO pin 50 
       CLK  pin 52
       CSN  pin 53  
 */

#include <SPI.h>

//Set Slave Select Pin
//MOSI, MISO, CLK are handeled automatically
int CSN = 10;
int SO = 12;
int SI = 11;
int CLK = 13 ;
unsigned int angle;

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

  //Set Pin Modes
  pinMode(CSN, OUTPUT);
  pinMode(SI, OUTPUT);
  pinMode(SO, INPUT);
  pinMode(CLK, OUTPUT);
  //Set Slave Select High to Start i.e disable chip
  digitalWrite(CSN, HIGH);
  //Initialize SPI 
  SPI.begin();
}

void loop() {

  SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE1));
  
  //Send the Command Frame
  digitalWrite(CSN, LOW);
  delayMicroseconds(1);
  SPI.transfer16(0xFFFF);
  digitalWrite(CSN,HIGH);

  //Read data frame
  digitalWrite(CSN, LOW);
  delayMicroseconds(1);
  angle = SPI.transfer16(0xC000);
  digitalWrite(CSN, HIGH);
  SPI.endTransaction;

  angle = (angle & (0x3FFF));
  
  int pos = ( (unsigned long) angle)*360UL/16384UL;

  Serial.println(pos);
  

  delay(1000);
  
}

Thank you for all your help!

Hello, people of the future!

It might be worth noting that there's a few ways to shorten the time to receive angle values.

I can send the command in the same frame that I'm receiving data. Also the default command to receive data is all high bits, so you can leave MOSI high.

/**
 * @param result where to store the returned value.  may be changed even if method fails.
 * @return 0 on fail, 1 on success.
 * @see https://ams.com/documents/20143/36005/AS5147_DS000307_2-00.pdf/ figure 12
 */
boolean getSensorRawValue(uint16_t &result) {
  result=0;
  uint8_t input,parity=0;

  // send the request for the angle value (command 0xFFFF)
  digitalWrite(PIN_MOSI,HIGH);

  // Collect the 16 bits of data from the sensor
  digitalWrite(PIN_SELECT,LOW);
  
  for(int i=0;i<16;++i) {
    digitalWrite(PIN_CLOCK,HIGH);
    // this is here to give a little more time to the clock going high.
    // only needed if the arduino is *very* fast.  I'm feeling generous.
    result <<= 1;
    digitalWrite(PIN_CLOCK,LOW);
    
    input = digitalRead(PIN_MISO);
    result |= input;

    // Could probably do something clever like parity += (i==0) & input to remove the branch
    if(i>0) parity += input;  
  }

  digitalWrite(PIN_SELECT,HIGH);
  
  // check the parity bit
  return ( (parity&1) != (result>>15) );
}

The full code for this example is @ GitHub - MarginallyClever/AS5147Test: testing AS5147 sensor