AS5048A 14-bit magnetic position encoder

In this video I show you how the AS5048A encoder differs from the previously introduced AS5600 encoder. Once I clarified the differences, I show you how the AS5048A works and how you can program a microcontroller to communicate with it.

The most important (to me) differences are the following:

  • The AS5048A has a 14-bit resolution AD converter (AS5600 has 12-bit)

  • The AS5048A communicates via SPI (AS5600 uses i2C)

  • The AS5048A is about 4x more expensive

AS5048 datasheet



Schematics

The circuit is demonstrated with an Arduino Nano, but it should work with any i2C and SPI compatible microcontroller. The 16x2 LCD is connected via i2C (SDA: A4, SCL: A5) and the AS5048A is connected via SPI. The pin order is the following: 1: GND, …

The circuit is demonstrated with an Arduino Nano, but it should work with any i2C and SPI compatible microcontroller. The 16x2 LCD is connected via i2C (SDA: A4, SCL: A5) and the AS5048A is connected via SPI. The pin order is the following: 1: GND, 2: VCC (3.3 V to 5 V), 3: MISO (D12), 4: MOSI (D11), 5: CLK (D13), 6: CS (D10). Both devices are compatible with 3.3 and 5 V, but in this example, I used 3.3 V.



Arduino source code

#include <SPI.h>
/*PINS
   Arduino SPI pins
   MOSI = 11, MISO = 12, SCK = 13, CS = 10
   STM32 SPI pins
   MOSI = PA7, MISO = PA6, SCK = PA5, CS = PA4
*/

//16x2 LCD
#include <LiquidCrystal_I2C.h>
/*PINS

   Arduino i2C pins
   SDA = A4, SCL = A5

   STM32 i2C pins
   SDA = PB7, SCL = PB6
*/

LiquidCrystal_I2C lcd(0x27, 16, 2);

const byte CS_pin = 10; //Chip select pin for manual switching

uint16_t rawData = 0; //bits from the encoder (16 bits, but top 2 has to be discarded)
float degAngle = 0; //Angle in degrees
float startAngle = 0; //starting angle for reference
float correctedAngle = 0; //tared angle value (degAngle-startAngle)
float totalAngle = 0; //total accumulated angular displacement
float numberofTurns = 0;
float rpmCounter = 0; //counts the number of turns for a certain period
float revsPerMin = 0; //RPM
int quadrantNumber = 0;
int previousquadrantNumber = 0;

uint16_t command = 0b1111111111111111; //read command (0xFFF)

float timer = 0; //timer for updating the LCD and sending the data through the serial port
float rpmTimer = 0; //timer for estimating the RPM

void setup()
{
  SPI.begin();
  Serial.begin(9600);
  Serial.println("AS5048A - Magnetic position encoder");
  pinMode(CS_pin, OUTPUT); //CS pin - output

  //------------------------------------------------------
  lcd.begin();                      // initialize the lcd
  lcd.backlight();
  //------------------------------------------------------
  lcd.setCursor(0, 0); //Defining positon to write from first row,first column .
  lcd.print("AS5048A Encoder");
  lcd.setCursor(0, 1); //2nd row, 1st block
  lcd.print("SPI Demo");
  delay(2000); //wait 2 sec
  printLCD(); //print permanent parts
  //------------------------------------------------------

  //Checking the initial angle
  readRegister();
  startAngle = degAngle;
}

void loop()
{
  readRegister(); //read the position of the magnet
  correctAngle(); //normalize the previous reading
  checkQuadrant(); //check the direction of the rotation and calculate the final displacement

  if (millis() - timer > 250)
  {
    Serial.print("Turns: ");
    Serial.println(numberofTurns);

    Serial.print("Total Angle: ");
    Serial.println(totalAngle, 2);

    updateLCD(); //print the new contents on the LCD (only the numbers)
    timer = millis();
  }

  if (millis() - rpmTimer > 15000) //check and calculate RPM every 15 sec
  {
    revsPerMin = 4 * rpmCounter; //60000/15000 = 4 (we assume the same speed for the whole minute)

    rpmCounter = 0; //reset
    rpmTimer = millis();
  }
}

void readRegister()
{
  SPI.beginTransaction(SPISettings(3000000, MSBFIRST, SPI_MODE1));

  //--sending the command
  digitalWrite(CS_pin, LOW);
  SPI.transfer16(command);
  digitalWrite(CS_pin, HIGH);

  delay(10);

  //--receiving the reading
  digitalWrite(CS_pin, LOW);
  rawData = SPI.transfer16(command);
  digitalWrite(CS_pin, HIGH);

  SPI.endTransaction();

  rawData = rawData & 0b0011111111111111; //removing the top 2 bits (PAR and EF)

  degAngle = (float)rawData / 16384.0 * 360.0; //16384 = 2^14, 360 = 360 degrees

  //Serial.print("Deg: ");
  //Serial.println(degAngle);
}

void correctAngle()
{
  //recalculate angle
  correctedAngle = degAngle - startAngle; //this tares the position

  if (correctedAngle < 0) //if the calculated angle is negative, we need to "normalize" it
  {
    correctedAngle = correctedAngle + 360; //correction for negative numbers (i.e. -15 becomes +345)
  }
  else
  {
    //do nothing
  }
  //Serial.print("Corrected angle: ");
  //Serial.println(correctedAngle, 2); //print the corrected/tared angle
}

void checkQuadrant()
{
  /*
    //Quadrants:
    4  |  1
    ---|---
    3  |  2
  */

  //Quadrant 1
  if (correctedAngle >= 0 && correctedAngle <= 90)
  {
    quadrantNumber = 1;
  }

  //Quadrant 2
  if (correctedAngle > 90 && correctedAngle <= 180)
  {
    quadrantNumber = 2;
  }

  //Quadrant 3
  if (correctedAngle > 180 && correctedAngle <= 270)
  {
    quadrantNumber = 3;
  }

  //Quadrant 4
  if (correctedAngle > 270 && correctedAngle < 360)
  {
    quadrantNumber = 4;
  }
  //Serial.print("Quadrant: ");
  //Serial.println(quadrantNumber); //print our position "quadrant-wise"

  if (quadrantNumber != previousquadrantNumber) //if we changed quadrant
  {
    if (quadrantNumber == 1 && previousquadrantNumber == 4)
    {
      numberofTurns++; // 4 --> 1 transition: CW rotation
      rpmCounter++;
    }

    if (quadrantNumber == 4 && previousquadrantNumber == 1)
    {
      numberofTurns--; // 1 --> 4 transition: CCW rotation
      rpmCounter--;
    }
    //this could be done between every quadrants so one can count every 1/4th of transition

    previousquadrantNumber = quadrantNumber;  //update to the current quadrant

  }
  //Serial.print("Turns: ");
  //Serial.println(numberofTurns); //number of turns in absolute terms (can be negative which indicates CCW turns)

  //after we have the corrected angle and the turns, we can calculate the total absolute position

  totalAngle = (numberofTurns * 360) + correctedAngle; //number of turns (+/-) plus the actual angle within the 0-360 range
  //Serial.print("Total angle: ");
  //Serial.println(totalAngle, 2); //absolute position of the motor expressed in degree angles, 2 digits
}

void printLCD()
{
  //printing the permanent parts
  lcd.setCursor(0, 0); // first line
  lcd.print("RPM: ");
  lcd.setCursor(5, 0);
  lcd.print("           ");
  lcd.setCursor(5, 0);
  lcd.print(revsPerMin,0);

  lcd.setCursor(0, 1); // second line
  lcd.print("Total: ");
  lcd.setCursor(7, 1);
  lcd.print("         ");
  lcd.setCursor(7, 1);
  lcd.print(totalAngle,1);
}

void updateLCD()
{
  //printing the variable parts
  lcd.setCursor(5, 0); // first line
  lcd.print("     ");
  lcd.setCursor(5, 0);
  lcd.print(revsPerMin,0);

  lcd.setCursor(7, 1); // second line
  lcd.print("            ");
  lcd.setCursor(7, 1);
  lcd.print(totalAngle,1);
}


3D-printable bracket for NEMA17 stepper motor

NEMA 17-compatible mount for the AS5048A magnetic position encoder

NEMA 17-compatible mount for the AS5048A magnetic position encoder


Previous
Previous

Updates for the stepper motor control panel

Next
Next

MINIQ BG6300 milling table with stepper motors and AS5600 encoder