Interfacing With the AS5600 Magnetic Encoder

by joshua796 in Circuits > Sensors

234 Views, 1 Favorites, 0 Comments

Interfacing With the AS5600 Magnetic Encoder

Screenshot 2026-03-27 at 23.53.24.png

This project is a guide on intefacing a microcontroller (pico 2) with the AS5600 rotary magnetic encoder.

This project also aims to teach creating your own hardware abstraction layers (HALs).

Supplies

Screenshot 2026-03-21 at 23.28.58.png
Screenshot 2026-03-21 at 23.30.27.png
Screenshot 2026-03-21 at 23.32.04.png
  1. AS5600 magnetic encoder
  2. a magnet specifically made for the AS5600
  3. raspberry pi pico 2 or a microcontroller of your choice.
  4. jumper wires
  5. Data sheets for the as5600 sensor which can be found below,
  6. and documentation for the pico c/c++ sdk library which can be found using this link -> https://www.raspberrypi.com/documentation/microcontrollers/c_sdk.html

For your reference I have included pictures of some of the components that you will need.

The CODE

Screenshot 2026-03-21 at 23.35.48.png
Screenshot 2026-03-21 at 23.33.55.png
Screenshot 2026-03-21 at 23.34.54.png
Screenshot 2026-03-21 at 23.35.02.png

We can start by creating a new header file called AS5600.h in the project folder you want to use the as5600. You can use any code editor or IDE of your choice.

We will be using the I2C interface of the AS5600 for this project.

the as5600 operates as a slave on the I2C bus and has a 7-bit slave address of 0x36 in hexadecimal.

the maximum clock signal i.e SCL frequency supported is 1MHz.

To start off the project we can create a hardware abstraction layer that contains all of the registers and bit masks that we need. You can use pages 18-20 for this. I have attached the c code below.

Keep in mind that the following code can be used for any microcontroller its only a HAL.


For example in the data sheet the ANGLE register has a word address of 0x0E, when writing to or reading from a register you first have to specify the slave address to the native i2c function of your chosen microcontroller, and next you include the address of the register you want to access (this is referred to as word address in the data sheet).

according to the data sheet the only data stored in this register is the absolute angle value in the range 0 to 360; which is stored as a 12-bit value since this is a 12-bit sensor.

To extract this value you need a bit mask for those specific bits in the register; here the bit mask for the ANGLE register is 0x0FFF.

If a register has more than one address like for CONF we only need to take the top/ first address as the as5600 automatically reads the whole register including the part that is tied to the second address (data contained at this address is also called the Low byte whereas the first address has data called the high byte).

for CONF we create a bit mask for each part of the register that supports a certain function

i.e one for the PM bits, one for the HYST bits and so on.


/*you dont need to worry about the second word adress in the documentation for some of these registers
*as the AS5600 automatically provides data from both sub addresses for each register so only need
*to call the first address given also known as the high byte.
*for example in the ZPOS register the first word address is 0x01 and the second word adress is 0x02, you only need to use the first word adress
*/

const uint8_t as5600_chipID = 0x36;

const uint8_t as5600_ZMCO = 0x00;
const uint16_t ZMCO_mask = 0x0300;

const uint8_t as5600_ZPOS = 0x01;
const uint8_t ZPOS_mask_H = 0x0F;
const uint8_t ZPOS_mask_L = 0xFF;

const uint8_t as5600_MPOS = 0x03;
const uint8_t MPOS_mask_H = 0x0F;
const uint8_t MPOS_mask_L = 0xFF;

const uint8_t as5600_MANG = 0x05;
const uint16_t MANG_mask = 0xFFF;

const uint8_t as5600_CONF = 0x07;
const uint16_t CONF_PM_mask = 0x03;
const uint16_t CONF_HYST_mask = 0x0C;
//const uint8_t CONF_L_OUTS_mask = 0x30;
//const uint8_t CONF_L_PWMF_mask = 0xC0;

//const uint8_t CONF_H_WD_mask = 0x20;

const uint8_t as5600_RAW_ANGLE = 0x0C;
const uint16_t RAW_ANGLE_mask = 0x0FFF;

const uint8_t as5600_ANGLE = 0x0E;
const uint16_t ANGLE_mask = 0x0FFF;

const uint8_t as5600_STATUS = 0x0B;
const uint8_t STATUS_MH_mask = 0x08;
const uint8_t STATUS_ML_mask = 0x10;
const uint8_t STATUS_MD_mask = 0x20;

const uint8_t as5600_AGC = 0x1A;
const uint8_t AGC_mask = 0xFF;

const uint8_t as5600_MAGNITUDE = 0x1B;
const uint16_t MAGNITUDE_mask = 0x0FFF;


Bit masks are like a sheet of paper with a hole in it that only allows certain parts of a register to be seen,

similarly a bit mask along with bitwise and shift operators can be used to extract the values of certain bits in a register. Refer to the data sheet.


now lets create a few functions to actually read and write to these registers.

here I will be implementing code specific to the pico 2 but only the I2C read and write commands, the rest of the function's logic is the same irrespective of which microcontroller you use.

we first need to include <stdio.h> and "hardware/i2c.h" inorder to be able to use the i2c functions for the pico2

so in this example to read the ANGLE register lets create a function called readAS5600_Angle() as below


double inline static readAS5600_Angle()
{

}


next in the {} brackets lets first create an array called raw_data to store the 2 bytes (high byte and low byte) of data that we receive .

double inline static readAS5600_Angle()
{
uint8_t raw_data[2];
}

next to actually perform the read

we first have to write to the ANGLE register asking it to transmit the data stored in the ANGLE register

and then read the data that the as5600 transmits back to us over I2C.

i2c_write_blocking(i2c0, as5600_chipID, &as5600_ANGLE,1 , true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);

the above two functions are specific to the pico c/c++ sdk but the logic is the same for any other microcontroller;

In i2c_write_blocking() function You first specify which i2c port (here its i2c0) you are using, next specify the slave address of the as5600 ic (which we defined to be 0x36) , and then provide the register that you want to access (the ANGLE register which has address of 0x0E, after which we say true to nostop which ensures that the host in this case the microcontroller still has control over the I2C Bus and enables the AS5600 to transmit the data, after which we specify false for nostop in the i2c_read_blocking() function so that the microcontroller relinquishes control. the 2 bytes of data received gets stored in raw_data.


Now we need a way to extract the angle information from the two bytes, this is where our bit masks come in handy.

each byte has 8 bits so two bytes of data contain 16 bits. we need to place both bytes side by side with the high byte first and then the low byte so that it forms a single 16 bit value which we store in a variable called angle.

uint16_t angle = (((uint16_t)raw_data[0] <<8) | raw_data[1]);

what the above line does is it converts the high byte (raw_data[0]) into a 16 bit and then shifts it to the left by 8 bits using << 8 and then combines this with the low byte (raw_data[1]) using the binary | operator.

now that we have a single 16 bit long number its time we extracted the 12-bit angle value from these 16 bits.

this is where the ANGLE_mask bit mask comes in.

we use binary & operator to leave us with only the 12 bits and converts the rest of the 4 bits in the 16 bit number to 0.

uint16_t angle = (((uint16_t)raw_data[0] <<8) | raw_data[1])&ANGLE_mask;

now that we have our angle value as binary, we need to convert this into a value between 0 and 360, for this we can use the following expression ->. angle*360.0/4096.0

and then return the angle value from the function.

double degrees = angle*360.0/ 4096.0;
return degrees;


so overall the function readAS5600_Angle() looks like

double inline static readAS5600_Angle()
{
uint8_t raw_data[2];
i2c_write_blocking(i2c0, as5600_chipID, &as5600_ANGLE,1 , true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
uint16_t angle = (((uint16_t)raw_data[0] <<8) | raw_data[1])&ANGLE_mask;
double degrees = angle*360.0/ 4096.0;
return degrees;
}


Below I have provided all the other functions along with all of the bit mask definitions combined into a single AS5600.h file.


#ifndef TMCDRIVER_AS5600_H
#define TMCDRIVER_AS5600_H

const uint8_t as5600_chipID = 0x36;

const uint8_t as5600_ZMCO = 0x00;
const uint16_t ZMCO_mask = 0x0300;

const uint8_t as5600_ZPOS = 0x01;
const uint8_t ZPOS_mask_H = 0x0F;
const uint8_t ZPOS_mask_L = 0xFF;

const uint8_t as5600_MPOS = 0x03;
const uint8_t MPOS_mask_H = 0x0F;
const uint8_t MPOS_mask_L = 0xFF;

const uint8_t as5600_MANG = 0x05;
const uint16_t MANG_mask = 0xFFF;

const uint8_t as5600_CONF = 0x07;
const uint16_t CONF_PM_mask = 0x03;
const uint16_t CONF_HYST_mask = 0x0C;
//const uint8_t CONF_L_OUTS_mask = 0x30;
//const uint8_t CONF_L_PWMF_mask = 0xC0;

//const uint8_t CONF_H_WD_mask = 0x20;

const uint8_t as5600_RAW_ANGLE = 0x0C;
const uint16_t RAW_ANGLE_mask = 0x0FFF;

const uint8_t as5600_ANGLE = 0x0E;
const uint16_t ANGLE_mask = 0x0FFF;

const uint8_t as5600_STATUS = 0x0B;
const uint8_t STATUS_MH_mask = 0x08;
const uint8_t STATUS_ML_mask = 0x10;
const uint8_t STATUS_MD_mask = 0x20;

const uint8_t as5600_AGC = 0x1A;
const uint8_t AGC_mask = 0xFF;

const uint8_t as5600_MAGNITUDE = 0x1B;
const uint16_t MAGNITUDE_mask = 0x0FFF;

#include <stdio.h>
#include "hardware/i2c.h"

double inline static readAS5600_Angle()
{
uint8_t raw_data[2];
i2c_write_blocking(i2c0, as5600_chipID, &as5600_ANGLE,1 , true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
uint16_t angle = (((uint16_t)raw_data[0] <<8) | raw_data[1])&ANGLE_mask;
double degrees = (float)angle*360.0/ 4096.0;
return degrees;
}

int static inline readAS5600_Status()
{
uint8_t raw_data[1];
i2c_write_blocking(i2c0, as5600_chipID, &as5600_STATUS,1 , true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 1, false);
int MD = raw_data[0] & STATUS_MD_mask;
int ML = raw_data[0] & STATUS_ML_mask;
int MH = raw_data[0] & STATUS_MH_mask;
if (ML ==1)
{
printf("Magnet too weak\n");
return 0;
}
else if (MH ==1)
{
printf("Magnet too strong\n");
return 0;
}
return MD;
}

int static inline readAS5600_AGC()
{
uint8_t raw_data[1];
i2c_write_blocking(i2c0, as5600_chipID, &as5600_AGC,1 , true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 1, false);
int AGC = raw_data[0];
return AGC;
}

int static inline readAS5600_Magnitude()
{
uint8_t raw_data[2];
i2c_write_blocking(i2c0, as5600_chipID, &as5600_MAGNITUDE,1 , true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
int magnitude = ((uint16_t)raw_data[0] <<8) | raw_data[1];
return magnitude;
}

void static inline writeAS5600_HYST(int lsb)
{
uint8_t raw_data[2];
uint8_t send_data[3];
i2c_write_blocking(i2c0, as5600_chipID, &as5600_CONF,1 , true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
send_data[0] = as5600_CONF;
send_data[1] = raw_data[0];
send_data[2] = (raw_data[1] & ~CONF_HYST_mask)| (lsb & CONF_HYST_mask);
i2c_write_blocking(i2c0, as5600_chipID, send_data, 3, true);
i2c_write_blocking(i2c0, as5600_chipID, &as5600_CONF, 1, true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
int check = raw_data[1]&CONF_HYST_mask;
if (check != lsb)
{
printf("HYST write failed\n");
}
}

void static inline writeAS5600_ZPOS(uint8_t zpos[2])
{
uint8_t raw_data[2];
uint8_t send_data[3];
i2c_write_blocking(i2c0, as5600_chipID, &as5600_ZPOS,1 , true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
send_data[0] = as5600_ZPOS;
send_data[1] = (raw_data[0] & ~ZPOS_mask_H) | zpos[0];
send_data[2] = (raw_data[1] & ~ZPOS_mask_L) | zpos[1];
i2c_write_blocking(i2c0, as5600_chipID, send_data, 3, false);
//uncomment the following if you want to confirm the write procedure
i2c_write_blocking(i2c0, as5600_chipID, &as5600_ZPOS, 1, true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
raw_data[0] = raw_data[0]&ZPOS_mask_H;
raw_data[1] = raw_data[1]&ZPOS_mask_L;
if (raw_data != zpos)
{
printf("ZPOS write failed\n");
}
}

void static inline writeAS5600_MPOS(uint8_t mpos[2])
{
uint8_t raw_data[2];
uint8_t send_data[3];
i2c_write_blocking(i2c0, as5600_chipID, &as5600_MPOS,1 , true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
send_data[0] = as5600_MPOS;
send_data[1] = (raw_data[0] & ~MPOS_mask_H) | mpos[0];
send_data[2] = (raw_data[1] & ~MPOS_mask_L) | mpos[1];
i2c_write_blocking(i2c0, as5600_chipID, send_data, 3, false);
//uncomment the following if you want to confirm the write procedure
i2c_write_blocking(i2c0, as5600_chipID, &as5600_MPOS, 1, true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
raw_data[0] = raw_data[0]&MPOS_mask_H;
raw_data[1] = raw_data[1]&MPOS_mask_L;
if (raw_data != mpos)
{
printf("ZPOS write failed\n");
}
}

void static inline writeAS5600_PM(int mode)
{
uint8_t raw_data[2];
uint8_t send_data[3];
i2c_write_blocking(i2c0, as5600_chipID, &as5600_CONF,1 , true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
send_data[0] = as5600_CONF;
send_data[1] = raw_data[0];
send_data[2] = (raw_data[1] & ~CONF_PM_mask)| (mode & CONF_PM_mask);
i2c_write_blocking(i2c0, as5600_chipID, send_data, 3, false);
//uncomment the following if you want to confirm the write procedure
i2c_write_blocking(i2c0, as5600_chipID, &as5600_CONF, 1, true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 2, false);
int check = raw_data[1]&CONF_PM_mask;
if (check != mode)
{
printf("PM write failed\n");
}
}

int static inline readAS5600_ZMCO()
{
uint8_t raw_data[1];
i2c_write_blocking(i2c0, as5600_chipID, &as5600_ZMCO, 1, true);
i2c_read_blocking(i2c0, as5600_chipID, raw_data, 1, false);
int check = raw_data[0] & ZMCO_mask;
if (check != 0)
{
printf("AS5600 already Burned/Programmed\n");
}
return check;
}


// definitely not including the burn register as i dont wish to permanently program my AS5600 thus rendering it un-reusable.
#endif //TMCDRIVER_AS5600_H


now to actually test this out #include "AS5600.h" into your main .c file and lets write some basic code to get stuff working.

#include "AS5600.h"
#include "pico/stdio.h"

int main()
{
stdio_init_all();
sleep_ms(2000); //sleep for 2 seconds
gpio_set_function(8,GPIO_FUNC_I2C);
gpio_set_function(9, GPIO_FUNC_I2C);
i2c_init(i2c0, 400000);
while (true) {
printf("%f", readAS5600_Angle());//this prints the angle value over usb serial
}
}


to connect the AS5600 to the pico 2, connect the SDA pin on as5600 to GPIO pin 8 on the pico 2 and the SCL pin to GPIO pin 9 on the pico 2. the dir pin can be used to control the direction of rotation of the magnet that should be taken as positive, GND is connected to the ground pin / GND on the pico 2 and power is taken from the 3v3 power output on the pico 2. refer to the pico2 data sheet provided in this guide.

Pictures to Guide You With Wiring and Connections.

Screenshot 2026-03-21 at 23.25.03.png

Note:- the AS5600 breakout board used in this guide already has pull up resistors integrated for I2C communication

GO Further

You now have the know how to get started with creating your own HALs and interfacing with sensors.

If you want to take your skills further why don't you check out my instructable that use the AS5600 along with a pico 2 to build a FOC like stepper controller.