Giter VIP home page Giter VIP logo

as5600's Introduction

Arduino CI Arduino-lint JSON check GitHub issues

License: MIT GitHub release PlatformIO Registry

AS5600

Arduino library for AS5600 and AS5600L magnetic rotation meter.

Description

AS5600

AS5600 is a library for an AS5600 / AS5600L based magnetic rotation meter. More exact, it measures the angle (rotation w.r.t. reference) and not RPM. Multiple angle measurements allows one to calculate or estimate the RPM.

The AS5600 and AS5600L sensors are pin compatible (always check your model's datasheet).

Warning: experimental

The sensor can measure a full rotation in 4096 steps. The precision of the position is therefore limited to approx 0.1°. Noise levels are unknown, but one might expect that the sensor is affected by electric and or magnetic signals in the environment. Also unknown is the influence of metals near the sensor or an unstable or fluctuating power supply.

Please share your experiences.

0.5.0 Breaking change

Version 0.5.0 introduced a breaking change. You cannot set the SDA and SCL pins in begin() any more. This reduces the dependency of processor dependent Wire implementations. The user has to call Wire.begin() and can optionally set the I2C pins before calling AS5600.begin().

Related libraries

Hardware connection

The I2C address of the AS5600 is always 0x36.

The sensor should connect the I2C lines SDA and SCL and the VCC and GND to communicate with the processor. Do not forget to add the pull up resistors to improve the I2C signals.

The AS5600 datasheet states it supports Fast-Mode == 400 KHz and Fast-Mode-Plus == 1000 KHz.

Pull ups

I2C performance tests with an AS5600L with an UNO failed at 400 KHz. After investigation it became clear that pull ups are mandatory. The UNO expects 5 Volt I2C signals from the AS5600. However the device only provides 3V3 pulses on the bus. So the signal was not stable fast enough (not "square enough"). After applying pull ups the AS5600L worked up to 1000 KHz.

DIR pin

From datasheet, page 30 - Direction (clockwise vs. counter-clockwise)

The AS5600 allows controlling the direction of the magnet rotation with the DIR pin. If DIR is connected to GND (DIR = 0) a clockwise rotation viewed from the top will generate an increment of the calculated angle. If the DIR pin is connected to VDD (DIR = 1) an increment of the calculated angle will happen with counter-clockwise rotation.

This AS5600 library offers a 3rd option for the DIR (direction) pin of the sensor:

  1. Connect to GND ==> fixed clockwise(*). This is the default.
  2. Connect to VCC ==> fixed counter-clockwise.
  3. Connect to an IO pin of the processor == Hardware Direction Control by library.

In the 3rd configuration the library controls the direction of counting by initializing this pin in begin(directionPin), followed by setDirection(direction). For the parameter direction the library defines two constants named:

  • AS5600_CLOCK_WISE (0)
  • AS5600_COUNTERCLOCK_WISE (1)

(*) if begin() is called without directionPin or with this parameter set to 255, Software Direction Control is enabled.

See Software Direction Control below for more information.

OUT pin

The sensor has an output pin named OUT. This pin can be used for an analogue or PWM output signal (AS5600), and only for PWM by the AS5600L.

See Analogue Pin and PWM Pin below.

Examples are added to show how to use this pin with setOutputMode().

See more in the sections Analog OUT and PWM OUT below.

Note: (From Zipdox2 - See issue #36)

Some AS5600 modules seem to have a resistor between PGO and GND. This causes the AS5600 to disable the output (to use it for programming, see datasheet). This resistor needs to be removed to use the OUT pin.

PGO pin

Not tested. ==> Read the datasheet!

PGO stands for ProGramming Option, it is used to calibrate and program the sensor. As the sensor can be programmed only a few times one should use this functionality with extreme care. See datasheet for a detailed list of steps to be done.

See Make configuration persistent below.

I2C

The I2C address of the AS5600 is always 0x36.

Address

sensor address changeable
AS5600 0x36 NO
AS5600L 0x40 YES, check setAddress()

To use more than one AS5600 on one I2C bus, see Multiplexing below.

The AS5600L supports the change of I2C address, optionally permanent. Check the setAddress() function for non-permanent change.

I2C multiplexing

Sometimes you need to control more devices than possible with the default address range the device provides. This is possible with an I2C multiplexer e.g. TCA9548 which creates up to eight channels (think of it as I2C subnets) which can use the complete address range of the device.

Drawback of using a multiplexer is that it takes more administration in your code e.g. which device is on which channel. This will slow down the access, which must be taken into account when deciding which devices are on which channel. Also note that switching between channels will slow down other devices too if they are behind the multiplexer.

Alternative could be the use of a AND port for the I2C clock line to prevent the sensor from listening to signals on the I2C bus.

Finally the sensor has an analogue output OUT. This output could be used to connect multiple sensors to different analogue ports of the processor.

Warning: If and how well this analog option works is not verified or tested.

Performance

board sensor results notes
Arduino UNO AS5600 up to 900 KHz. #22
Arduino UNO AS5600L up to 300 KHz.
ESP32 AS5600 no data
ESP32 AS5600L up to 800 KHz

No other boards tested yet.

ESP32 and I2C

When polling the AS5600 with an ESP32 to measure RPM an issue has been reported. See #28

The problem is that the ESP32 can be blocking for up to one second if there is a problem in the connection with the sensor. Using setWireTimeout() does not seem to solve the problem (2023-01-31). In the issue the goal was to measure the turns of a rotating device at around 3800 RPM.

3800 RPM == 64 rounds / second.

To measure speed one need at least 3 angle measurements per rotation. This results in at least 192 measurements per second which is about 1 per 5 milliseconds.

Given that the ESP32 can block for a second, it can not be guaranteed to be up to date. Not for speed, but also not for total number of rotations.

Interface

#include "AS5600.h"

Constants

Most important are:

//  setDirection
const uint8_t AS5600_CLOCK_WISE         = 0;  //  LOW
const uint8_t AS5600_COUNTERCLOCK_WISE  = 1;  //  HIGH

//  0.087890625;
const float   AS5600_RAW_TO_DEGREES     = 360.0 / 4096;
//  0.00153398078788564122971808758949;
const float   AS5600_RAW_TO_RADIANS     = PI * 2.0 / 4096;
//  4.06901041666666e-6
const float   AS5600_RAW_TO_RPM         = 1.0 / 4096 / 60;

//  getAngularSpeed
const uint8_t AS5600_MODE_DEGREES       = 0;
const uint8_t AS5600_MODE_RADIANS       = 1;
const uint8_t AS5600_MODE_RPM           = 2;

See AS5600.h file (and datasheet) for all constants. Also Configuration bits below for configuration related ones.

Constructor + I2C

  • AS5600(TwoWire *wire = &Wire) Constructor with optional Wire interface as parameter.
  • bool begin(uint8_t directionPin = AS5600_SW_DIRECTION_PIN) set the value for the directionPin. If the pin is set to AS5600_SW_DIRECTION_PIN, the default value, there will be software direction control instead of hardware control. See below.
  • bool isConnected() checks if the address 0x36 (AS5600) is on the I2C bus.
  • uint8_t getAddress() returns the fixed device address 0x36 (AS5600).

Direction

To define in which way the sensor counts up.

  • void setDirection(uint8_t direction = AS5600_CLOCK_WISE) idem.
  • uint8_t getDirection() returns AS5600_CLOCK_WISE (0) or AS5600_COUNTERCLOCK_WISE (1).

See Software Direction Control below for more information.

Configuration registers

Please read the datasheet for details.

  • bool setZPosition(uint16_t value) set start position for limited range. Value = 0..4095. Returns false if parameter is out of range.
  • uint16_t getZPosition() get current start position.
  • bool setMPosition(uint16_t value) set stop position for limited range. Value = 0..4095. Returns false if parameter is out of range.
  • uint16_t getMPosition() get current stop position.
  • bool setMaxAngle(uint16_t value) set limited range. Value = 0..4095. Returns false if parameter is out of range. See datasheet Angle Programming
  • uint16_t getMaxAngle() get limited range.

Configuration bits

Please read datasheet for details.

  • bool setConfigure(uint16_t value) value == 0..0x3FFF Access the register as bit mask. Returns false if parameter is out of range.
  • uint16_t getConfigure() returns the current configuration register a bit mask.
Bit short Description Values
0-1 PM Power mode 00 = NOM, 01 = LPM1, 10 = LPM2, 11 = LPM3
2-3 HYST Hysteresis 00 = OFF, 01 = 1 LSB, 10 = 2 LSB, 11 = 3 LSB
4-5 OUTS Output Stage 00 = analog (0-100%), 01 = analog (10-90%), 10 = PWM
6-7 PWMF PWM frequency 00 = 115, 01 = 230, 10 = 460, 11 = 920 (Hz)
8-9 SF Slow Filter 00 = 16x, 01 = 8x, 10 = 4x, 11 = 2x
10-12 FTH Fast Filter Threshold 000 - 111 check datasheet
13 WD Watch Dog 0 = OFF, 1 = ON
15-14 not used

The library has functions to address these fields directly.

The setters() returns false if parameter is out of range.

  • bool setPowerMode(uint8_t powerMode)
  • uint8_t getPowerMode()
  • bool setHysteresis(uint8_t hysteresis) Suppresses "noise" on the output when the magnet is not moving. In a way one is trading precision for stability.
  • uint8_t getHysteresis()
  • bool setOutputMode(uint8_t outputMode)
  • uint8_t getOutputMode()
  • bool setPWMFrequency(uint8_t pwmFreq)
  • uint8_t getPWMFrequency()
  • bool setSlowFilter(uint8_t mask)
  • uint8_t getSlowFilter()
  • bool setFastFilter(uint8_t mask)
  • uint8_t getFastFilter()
  • bool setWatchDog(uint8_t mask)
  • uint8_t getWatchDog()

Read Angle

  • uint16_t rawAngle() returns 0 .. 4095. (12 bits) Conversion factor AS5600_RAW_TO_DEGREES = 360 / 4096 = 0.087890625 or use AS5600_RAW_TO_RADIANS if needed.
  • uint16_t readAngle() returns 0 .. 4095. (12 bits) Conversion factor AS5600_RAW_TO_DEGREES = 360 / 4096 = 0.087890625 or use AS5600_RAW_TO_RADIANS if needed. The value of this register can be affected by the configuration bits above. This is the one most used.
  • bool setOffset(float degrees) overwrites the existing offset. It sets an offset in degrees, e.g. to calibrate the sensor after mounting. Typical values are -359.99 - 359.99 probably smaller. Larger values will be mapped back to this interval. Be aware that larger values will affect / decrease the precision of the measurements as floats have only 7 significant digits. Verify this for your application. Returns false if degrees > 360000.
  • float getOffset() returns offset in degrees.
  • bool increaseOffset(float degrees) adds degrees to the existing offset. If setOffset(20) is called first and increaseOffset(-30) thereafter the new offset is -10 degrees. Returns false if degrees > 360000.

In issue #14 there is a discussion about setOffset(). A possible implementation is to ignore all values outside the -359.99 - 359.99 range. This would help to keep the precision high. User responsibility.

In #51 increaseOffset is discussed.

//  offset == 0;
as.setOffset(20);
//  offset == 20;
as.setOffset(-30);
//  offset = -30;

//  versus

//  offset == 0;
as.setOffset(20);
//  offset == 20;
as.increaseOffset(-30);
//  offset = -10;

Angular Speed

  • float getAngularSpeed(uint8_t mode = AS5600_MODE_DEGREES) is an experimental function that returns an approximation of the angular speed in rotations per second. The function needs to be called at least four times per rotation or once per second to get a reasonably precision.
mode value description notes
AS5600_MODE_RADIANS 1 radians /sec
AS5600_MODE_DEGREES 0 degrees /sec default
other - degrees /sec

Negative return values indicate reverse rotation. What that exactly means depends on the setup of your project.

Note: the first call will return an erroneous value as it has no reference angle or time. Also if one stops calling this function for some time the first call after such delays will be incorrect.

Note: the frequency of calling this function of the sensor depends on the application. The faster the magnet rotates, the faster it may be called. Also if one wants to detect minute movements, calling it more often is the way to go.

An alternative implementation is possible in which the angle is measured twice with a short interval. The only limitation then is that both measurements should be within 180° = half a rotation.

Cumulative position (experimental)

Since 0.3.3 an experimental cumulative position can be requested from the library. The sensor does not provide interrupts to indicate a movement or revolution Therefore one has to poll the sensor at a frequency at least three times per revolution with getCumulativePosition()

The cumulative position (32 bits) consists of 3 parts

bit meaning notes
31 sign typical + == CW, - == CCW
30-12 revolutions
11-00 raw angle call getCumulativePosition()

Functions are:

  • int32_t getCumulativePosition() reads sensor and updates cumulative position.
  • int32_t getRevolutions() converts last position to whole revolutions. Convenience function.
  • int32_t resetPosition(int32_t position = 0) resets the "revolutions" to position (default 0). It does not reset the delta (rotation) since last call to getCumulativePosition(). Returns last position (before reset).
  • int32_t resetCumulativePosition(int32_t position = 0) completely resets the cumulative counter. This includes the delta (rotation) since last call to getCumulativePosition(). Returns last position (before reset).

As this code is experimental, names might change in the future (0.4.0)? As the function are mostly about counting revolutions the current thoughts for new names are:

int32_t updateRevolutions()  replaces getCumulativePosition()
int32_t getRevolutions()
int32_t resetRevolutions()   replaces resetPosition()

Status registers

  • uint8_t readStatus() see Status bits below.
  • uint8_t readAGC() returns the Automatic Gain Control. 0..255 in 5V mode, 0..128 in 3V3 mode.
  • uint16_t readMagnitude() reads the current internal magnitude. (page 9 datasheet) Scale is unclear, can be used as relative scale.
  • bool detectMagnet() returns true if device sees a magnet.
  • bool magnetTooStrong() idem.
  • bool magnetTooWeak() idem.

Status bits

Please read datasheet for details.

Bit short Description Values
0-2 not used
3 MH overflow 1 = magnet too strong
4 ML underflow 1 = magnet too weak
5 MD magnet detect 1 = magnet detected
6-7 not used

Error handling

Since 0.5.2 the library has added experimental error handling. For now only lowest level I2C errors are checked for transmission errors. Error handling might be improved upon in the future.

Note: The public functions do not act on error conditions. This might change in the future. So the user should check for error conditions.

int e = lastError();
if (e != AS5600_OK)
{
  //  handle error
}
  • int lastError() returns the last error code. After reading the error status is cleared to AS5600_OK.
Error codes value notes
AS5600_OK 0 default
AS5600_ERROR_I2C_READ_0 -100
AS5600_ERROR_I2C_READ_1 -101
AS5600_ERROR_I2C_READ_2 -102
AS5600_ERROR_I2C_READ_3 -103
AS5600_ERROR_I2C_WRITE_0 -200
AS5600_ERROR_I2C_WRITE_1 -201

Make configuration persistent. BURN

Read burn count

  • uint8_t getZMCO() reads back how many times the ZPOS and MPOS registers are written to permanent memory. You can only burn a new Angle 3 times to the AS5600, and only 2 times for the AS5600L. This function is safe as it is readonly.

BURN function

The burn functions are used to make settings persistent. These burn functions are permanent, therefore they are commented in the library. Please read datasheet twice, before uncomment them.

USE AT OWN RISK

Please read datasheet twice as these changes are not reversible.

The risk is that you make your AS5600 / AS5600L USELESS.

USE AT OWN RISK

These are the two "unsafe" functions:

  • void burnAngle() writes the ZPOS and MPOS registers to permanent memory. You can only burn a new Angle maximum THREE times to the AS5600 and TWO times for the AS5600L.
  • void burnSetting() writes the MANG register to permanent memory. You can write this only ONE time to the AS5600.

Some discussion about burning see issue #38
(I have no hands on experience with this functions)

USE AT OWN RISK

Software Direction Control

Experimental 0.2.0

Normally one controls the direction of the sensor by connecting the DIR pin to one of the available IO pins of the processor. This IO pin is set in the library as parameter of the begin(directionPin) function.

The directionPin is default set to 255, which defines a Software Direction Control.

To have this working one has to connect the DIR pin of the sensor to GND. This puts the sensor in a hardware clockwise mode. Then it is up to the library to do the additional math so the readAngle() and rawAngle() behave as if the DIR pin was connected to a processor IO pin.

The user still calls setDirection() as before to change the direction of the increments and decrements.

The advantage is one does not need that extra IO pin from the processor. This makes connecting the sensor a bit easier.

TODO: measure performance impact.

TODO: investigate impact on functionality of other registers.

Analog OUT

(details datasheet - page 25 = AS5600)

The OUT pin can be configured with the function:

  • bool setOutputMode(uint8_t outputMode)

AS5600

When the analog OUT mode is set the OUT pin provides a voltage which is linear with the angle.

VDD mode percentage output 1° in V
5V0 0 0 - 100% 0.0 - 5.0 0.01388889
5V0 1 10 - 90% 0.5 - 4.5 0.01111111
3V3 0 0 - 100% 0.0 - 3.3 0.00916667
3V3 1 10 - 90% 0.3 - 3.0 0.00750000

To measure these angles a 10 bit ADC or higher is needed.

When analog OUT is selected readAngle() will still return valid values.

AS5600L

The AS5600L does NOT support analog OUT. Both mode 0 and 1 will set the OUT pin to VDD (+5V0 or 3V3).

PWM OUT

(details datasheet - page 27 = AS5600)

The OUT pin can be configured with the function:

  • bool setOutputMode(uint8_t outputMode) outputMode = 2 = PWM

When the PWM OUT mode is set the OUT pin provides a duty cycle which is linear with the angle. However they PWM has a lead in (HIGH) and a lead out (LOW).

The pulse width is 4351 units, 128 high, 4095 angle, 128 low.

Angle HIGH LOW HIGH % LOW % Notes
0 128 4223 2,94% 97,06%
10 242 4109 5,56% 94,44%
20 356 3996 8,17% 91,83%
45 640 3711 14,71% 85,29%
90 1152 3199 26,47% 73,53%
135 1664 2687 38,24% 61,76%
180 2176 2176 50,00% 50,00%
225 2687 1664 61,76% 38,24%
270 3199 1152 73,53% 26,47%
315 3711 640 85,29% 14,71%
360 4223 128 97,06% 2,94% in fact 359.9 something as 360 == 0

Formula:

based upon the table above angle = map(dutyCycle, 2.94, 97.06, 0.0, 359.9);

or in code ..

t0 = micros();  // rising;
t1 = micros();  // falling;
t2 = micros();  // rising;  new t0

//  note that t2 - t0 should be a constant depending on frequency set.
//  however as there might be up to 5% variation it cannot be hard coded.
float dutyCycle = (1.0 * (t1 - t0)) / (t2 - t0);  
float angle     = (dutyCycle - 0.0294) * (359.9 / (0.9706 - 0.0294));

PWM frequency

The AS5600 allows one to set the PWM base frequency (~5%)

  • bool setPWMFrequency(uint8_t pwmFreq)
mode pwmFreq step in us 1° in time
0 115 Hz 2.123 24.15
1 230 Hz 1.062 12.77
2 460 Hz 0.531 6.39
3 920 Hz 0.216 3.20

Note that at the higher frequencies the step size becomes smaller and smaller and it becomes harder to measure. You need a sub-micro second hardware timer to measure the pulse width with enough precision to get the max resolution.

When PWM OUT is selected readAngle() will still return valid values.


AS5600L class

  • AS5600L(uint8_t address = 0x40, TwoWire *wire = &Wire) constructor. As the I2C address can be changed in the AS5600L, the address is a parameter of the constructor. This is a difference with the AS5600 constructor.

Setting I2C address

  • bool setAddress(uint8_t address) Returns false if the I2C address is not in valid range (8-119).

Setting I2C UPDT

UPDT = update page 30 - AS5600L

  • bool setI2CUPDT(uint8_t value)
  • uint8_t getI2CUPDT()

These functions seems to have only a function in relation to setAddress() so possibly obsolete in the future. If you got other insights on these functions please let me know.


Operational

The base functions are:

AS5600 as5600;

void setup()
{
  Serial.begin(115200);
  ...
  as5900.begin(4);     //  set the direction pin
  as5600.setDirection(AS5600_CLOCK_WISE);
  ...
}

void loop()
{
...
  Serial.println(as5600.readAngle());
  delay(1000);
...
}

See examples.

Future

Some ideas are kept here so they won't get lost. priority is relative.

Must

  • re-organize readme
  • rename revolution functions
    • to what?

Should

  • implement extended error handling in public functions.
    • will increase footprint !! how much?
    • call writeReg() only if readReg() is OK ==> prevent incorrect writes
      • if (_error != 0) return false;
      • idem readReg2()
    • set AS5600_ERROR_PARAMETER e.g. setZPosition()
    • a derived class with extended error handling?
  • investigate readMagnitude()
    • combination of AGC and MD, ML and MH flags?
  • investigate OUT behaviour in practice
    • analogue
    • PWM
    • need AS5600 on breakout with support
  • check / verify Power-up time
    • 1 minute (need HW)
  • check Timing Characteristics (datasheet)
    • is there improvement possible.

Could

  • investigate PGO programming pin.
  • check for compatible devices
    • AS5200 ?
  • investigate performance
    • basic performance per function
    • I2C improvements
    • software direction
  • write examples:
    • as5600_calibration.ino (needs HW and lots of time)
    • different configuration options

Wont (unless)

  • fix for AS5600L as it does not support analog OUT.
    • type field?
    • other class hierarchy?
      • base class with commonalities?
    • ==> just ignore for now.
  • add mode parameter to offset functions.
    • see getAngularSpeed()

Support

If you appreciate my libraries, you can support the development and maintenance. Improve the quality of the libraries by providing issues and Pull Requests, or donate through PayPal or GitHub sponsors.

Thank you,

as5600's People

Contributors

robtillaart avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

as5600's Issues

detectMagnet always false

Hi everyone, i've tried the AS5600_demo example, added a simple
if(as5600.detectMagnet()) Serial.print("TRUE\t");
else Serial.print("FALSE\t");
In the loop, but it always gives me false.

getAngularSpeed() fails when over 360 degrees

  int      angle   = readAngle();
  uint32_t deltaT  = now - _lastMeasurement;
  int      deltaA  = angle - _lastAngle;   //  fails mod 360

if lastAngle == 355 and angle == 2 the deltaA should be 7 not -353

Need a construction that takes into account that we do not know if the thing runs CW or CCW.

To be investigated a.s.a.p so it can be part of 0.2.0

Bug in setConfigure

The current code misses 2 bits when setting configuration register.

void AS5600::setConfigure(uint16_t value)
{
  writeReg(AS5600_CONF, (value >> 8) & 0x0F);
  writeReg(AS5600_CONF + 1, value & 0xFF);
}

should be

void AS5600::setConfigure(uint16_t value)
{
  writeReg(AS5600_CONF, (value >> 8) & 0x2F);   //  <<<<<<<<<<< 0x2F
  writeReg(AS5600_CONF + 1, value & 0xFF);
}

found while investigating #6

revolution counter "edges" are incorrect

There is one condition missing in the determination of a +1 -1 revolution.
The angle must be smaller than or equal to180°
current edges do not test for that constrain.

Set position when using Cumulative Position

I really like the way you added the cumulative position feature, it's really neat and thank you for taking the time to do all of this.

Rather than resetting the position to 0, I'd like to set my own position but am having some trouble implementing the feature and cannot figure out why it won't work as expected.

In the Source file, I added this

int32_t AS5600::setPosition(int32_t pos)
{
  _lastPosition = 0;
  _position  = pos;
  return _position;
}

And in my Arduino sketch, I run this line of code to set the position to begin at "777"

as5600.setPosition(777);

However, when running the AS5600_position example, it will not set to "777" when starting the loop. Here is the full sketch:

#include "AS5600.h"
#include "Wire.h"

AS5600 as5600;   //  use default Wire

void setup()
{
  Serial.begin(115200);
  as5600.begin(10, 9); //ESP32
  as5600.setDirection(AS5600_CLOCK_WISE);  // default, just be explicit.
  delay(1000);
  as5600.setPosition(777); //  NEW CODE to set the initial value to 777
}


void loop()
{
  static uint32_t lastTime = 0;

  //  set initial position
  as5600.getCumulativePosition();

  //  update every 100 ms
  if (millis() - lastTime >= 100)
  {
    lastTime = millis();
    Serial.println(as5600.getCumulativePosition());
  }
}

Any idea what I may be doing wrong? Thanks again

Feature request: zero-position offset

In my particular application the AS5600 board does not sit square to the reference point once mounted, so I needed a way in software to compensate for the difference in angle. I thought this might be a handy feature to include in the library and wanted to share my solution.

`float direction;
float offset = 356.8;

void loop()
{
int16_t angle = as5600.rawAngle() * AS5600_RAW_TO_DEGREES;
direction = angle + (360.0 - offset);
if (direction >= 360) direction -= 360.0;
if (direction < 0) direction += 360.0;
}`

I assume something like this could be easily implemented in the library as something like setOffset(float offset) to modify the return values of readAngle() and rawAngle(), and there are probably better ways to restrain the values between 0-359.999, but this was a quick solution I put together to get the job done.

Optimization

While analyzing #6

Some getter / setter functions can be optimized by using readReg2() instead of 2x readReg() and do the masking on 2 byte level. This will reduces I2C communication.

low prio, => need a sensor before implementing this to see if it breaks something.

Getting range of 18-4095

Hello

Great to find a fresh library for this chip. I switched using this library, and ran across a weird thing.

I get a range of 18-4095. Any idea why this is happening?

Im moving a stepper motor slowly, and reading it via serial monitor. It shows all the steps clearly, with 1 increments. Just somehow starts at 18.

Can the filters affect this? These are my settings:

as5600.setPowerMode(AS5600_POWERMODE_NOMINAL);
as5600.setHysteresis(AS5600_HYST_OFF);
as5600.setSlowFilter(AS5600_SLOW_FILT_2X);
as5600.setFastFilter(AS5600_FAST_FILT_LSB10);

Burn functions not working?

I have been working with your library and a handful of AS5600s to try and program them to generate a PWM output signal with permanently burned zero and max angle settings. I can get the PWM to work, and can confirm the settings I write are working, but when I try to burn them (CONF first, and then ZPOS, MPOS). The angles seem to change after burning. I had to uncomment the burn functions, and correct a small typo. Do you have experience burning the angles? What did you do to make it work?

Failure detection on 3839 readout.

At some point i posted here about the weird hiccups i had with this sensor, i have now noticed that it will always output 3839 on error.

This can also be confirmed from other posts, like this:

https://forum.arduino.cc/t/analogwrite-causes-i2c-encoder-lockup/674080/13

"Sadly, after a couple of minutes running, the encoder stops communicating, and returns the value 3839, which is the same as you would see if you unplugged it."

So every time the encoder fails to get a reading, it outputs 3839.

For me, it simply occasionally outputs 3839. And this causes these weird hiccups in the linear reading.

It would be good if there was a possibility to filter for this, mainly in my case this is an issue in the linear output. As on other cases i can filter this out easily.

Even though i expect this is a problem on my wiring/power filtering, as im running two of these and the other has 0 of these hiccups. But still, it might be wort while having a possibility of filtering this one readout out.

Oscillations in speed measurements

I have looked through other issues and while some are similar, none are exactly the same.

I am trying to measure two things on my stationary bike: speed and total distance. I have made an initial setup and while the readouts for angles and rotations seem to work fine, speed gives regular oscillations, like here:

Zrzut ekranu 2023-07-13 171559

and here:

Zrzut ekranu 2023-07-13 172114

The difference between the two is in the delay value: 10 ms and no delay.

I am using ESP32 now (Devkit v1), but it was similar with Arduino Pro Micro (which no longer works, unfortunately...).

AS5600_COUNTERCLOCK_WISE fails

If you issue as5600.setDirection(AS5600_COUNTERCLOCK_WISE) at setup, as5600.rawAngle() * AS5600_RAW_TO_DEGREES always returns 337 degrees. AS5600_CLOCK_WISE works correctly. I'm just performing a simple measurement for a wind vane so I haven't tried anything more than the basic code.

To work around this for reading CCW, you can find the corrected value with angle = 360 - angle; (adjusting for ints, floats, or whatever variable type you use).

Some examples marked as AS5600 use the AS5600L

While testing the AS5600 example, I could not get this to work.
Checking the I2C/Wire data with my scope showed that the slave address used was 0x40 - this is due to as5600 object being created from the AS5600L class instead of the AS5600.

I noticed this is also done in the following examples:
AS5600_angular_speed, AS5600_demo_ESP32, AS5600_demo_RP2040, AS5600_demo_status, AS5600_demo_STM32, AS5600_I2C_frequency and AS5600_resetCumulativeCounter examples.

Change DIR value after programming.

Let's begin with, congrats for the nice work in the library.

Imagine the following scenario:

I program de AS5600 to give max output value at 10 degrees and min output value at 170 degress. It works fine, however, shouldn't it give me min output at 10 degreens and max output at 170 if I change the value of the direction pin?

What is the expected behaviour in this case? I've been looking for such information in the technical documents of AS5600, however there is little information about how the DIR pin affects the behaviour of an already programmed sensor.

Thanks for any advice.

Repeatable jumps in angle value

Hello

I use this library in with a Seeed Xiao ESP32C3. The AS5600 measures the angle of a DC motor (rpm: about 1400 rpm). I'm interested in the absolute position of the motor (i.e. values will continue at 360° and not start again at 1° after each rotation). Therefore im using the if-statement in my loop function to detect a full rotation and calculate the cumulative angle:

void loop() {
  ledcWrite(pwmChannel1, motorSpeed);
  ledcWrite(pwmChannel2, 0);

  raw_ang = ams5600.getRawAngle();
  ang = (raw_ang * 360)/4096; //angle in degrees

  if (old_ang > 340 && ang < 20)
    {
      revs++; // number of rotations/revolutions
    }
   if (old_ang < 20 && ang > 340)
    {
      revs--;
    }

  abs_ang = revs * 360 + ang; //cumulative angle over several revolutions
  Serial.print(ang);
  Serial.print("\t");
  Serial.print(raw_ang);
  Serial.print("\t");
  Serial.print(abs_ang);
  Serial.print("\t");
  Serial.print(revs);
  Serial.println();
  old_ang = ang;
}

In my tests, I use the motor at constant speed and log the values seen in the Serial.print part of the loop via PuTTY. The problem can be seen if the logged data is plotted. At all of the red markers in the plot, the angle value jumps about 45° from on measurment to the next. The normal delta between two back-to-back measurements is 4° to 5°.
jumps
Detail view of a jump:
jumps_detail
This problem is also seen in the plot of the raw sensor angle and the angle in degrees:
raw_signal and angle
The strange thing is, that the distance in time respectively the number of measuring points between two jumps is always almost identical. (This can be seen by the x values in the first picture. The distance between two red markers is about 2945 points every time).

Has somebody experienced the same problem? Or can somebody give me a hint what the cause for the jumps is?

I also tried different motor speeds but the distance between the jumps in the angle stays at about 2945 measuring points.

Hardwired direction pin not used

Thanks for making this great library. I have a hardwired pullup on the direction pin on my AS5600 circuit board and I am setting the direction in the code to "AS5600_COUNTERCLOCK_WISE" to let the AS5600 code know. There is no direction output from the Arduino to the AS5600 circuit board though. There does not appear to be a way not to assign a direction output pin in the code, it looks like you have to use a direction pin. Is there anyway around this?

Teensy 4.1 cannot connect to AS5600

I'm using this library with teensy 4.1,
I have found some issue on the library version 0.5.1
and the latest version work with my board is version 0.4.1
The issue is I cannot get any data via I2C connection.

After investigation I found something in the PR no. #47
in AS5600.cpp file

bool AS5600::begin(uint8_t directionPin)
{
  _directionPin = directionPin;
  if (_directionPin != AS5600_SW_DIRECTION_PIN)
  {
    pinMode(_directionPin, OUTPUT).
  }
  setDirection(AS5600_CLOCK_WISE);

  if (! isConnected()) return false;
  return true;
}

So I try restore some code from version 0.4.1 and it's work for my teensy 4.1

bool AS5600::begin(uint8_t directionPin)
{
  _directionPin = directionPin;
  if (_directionPin != AS5600_SW_DIRECTION_PIN)
  {
    pinMode(_directionPin, OUTPUT).
  }
  setDirection(AS5600_CLOCK_WISE);

  _wire->begin();   // I restore this line..
  if (! isConnected()) return false;
  return true;
}

Sqk~
Thanks

Here is the code for AS5600 library, two sensors on one bus. Maybe you can add it in the examples.

#include "AS5600.h"
#include "Wire.h"

AS5600 as5600_R;   //  use default Wire

AS5600L as5600_L;   //  use default Wire
int Raw_R;
int Raw_Prev;

float Deg_R,  Deg_L;
float Deg_Prev_R, Deg_Prev_L;
static uint32_t lastTime = 0;
void setup()
{
  Serial.begin(230400);
  Serial.println(__FILE__);
  Serial.print("AS5600_LIB_VERSION: ");
  Serial.println(AS5600_LIB_VERSION);
 Wire.begin();
  // as5600_L.setAddress(0x40);  // AS5600L only
  as5600_L.begin(15);  //  set direction pin.
  as5600_L.setDirection(AS5600_CLOCK_WISE);  //
  delay(1000);
  as5600_R.begin(14);  //  set direction pin.
  as5600_R.setDirection(AS5600_CLOCK_WISE);  // default, just be explicit.

  delay(1000);
  Serial.print ("Address For AS5600 R ");
  Serial.println(as5600_R.getAddress());
  Serial.print ("Address For AS5600 L ");
  Serial.println(as5600_L.getAddress());

  int b = as5600_R.isConnected();
 
  Serial.print("Connect_R: ");
  Serial.println(as5600_R.isConnected() ? "true" : "false");

    Serial.print("Connect device LEFT: ");
  Serial.println(as5600_L.isConnected() ? "true" : "false");

  delay(1000);
  as5600_R.resetPosition();
  as5600_L.resetPosition();
}


void loop()
{
  

 Deg_R = convertRawAngleToDegrees(as5600_R.getCumulativePosition());
 Deg_L = convertRawAngleToDegrees(as5600_L.getCumulativePosition());
  //  update every 100 ms
  //  should be enough up to ~200 RPM
  if (millis() - lastTime >= 100 and Deg_R != Deg_Prev_R)
  {
    lastTime = millis();
   
    Serial.println("REV R: "+ String(as5600_R.getRevolutions()));
       Serial.println("REV L: "+ String(as5600_L.getRevolutions()));
    Serial.println("DEG R= "+String(Deg_R, 0) + "°" );
    Serial.println("DEG L= " +String(Deg_L, 0) + "°" );
    Deg_Prev_R = Deg_R;
  }

  //  just to show how reset can be used
  if (as5600_R.getRevolutions() >= 1 )
  {
    as5600_R.resetPosition();

    if ( as5600_R.getRevolutions() <= -1 )
    {
      as5600_R.resetPosition();
    }
  }
}

float convertRawAngleToDegrees(uint32_t newAngle)
{
  /* Raw data reports 0 - 4095 segments, which is 0.087890625 of a degree */
  float retVal = newAngle * 0.087890625;
  retVal = round(retVal);
  // retVal=retVal/10;
  return retVal;
}
// -- END OF FILE --

getCumulativePosition messed up by http.POST

Hi,
not sure if this is a bug or a feature. I am reading getCumulativePosition from 8 sensors every 100 ms.
After some time, I upload the data using http.POST (ESP Async Web Server). If sensor moved during the upload, getCumulativePosition skips (didn't notice the movement).
Best,
I

Handle CW and CCW in software

From #6

  • default pin for begin should be 255/-1
  • used as indicator for software 'clockwise'
  • extra variable to hold cw or ccw mode
  • at lowest level invert 1-4095 to 4095-1
    Note 0 should be 0

Solves a shortage of pins.

DIR pin should be connected to GND so it is CW mode

Intermittent 0 degree angle returned (wrong value)

When using the as5600::rawAngle method, I sometimes receive a zero value intermittently, although the magnet position is not at zero.
This happens every 30 or so reads, when probing around 2 times a second.

I'm using a teensy 4.1 and the as5600 board is connected via i2c. The cable length is about 1 foot, the supply voltage is a steady 3.3V and the dir pin is connected to GND.

I've read i2c on teensy 4.1 need external pull-ups, but I have been successful without using them before.

Anyone has thoughts on this ?

Thanks,

How to use Wire1?

I'd like to use 2 AS5600's on seperate I2C interfaces. All of the examples call AS5600 with the "default Wire".
I tried: AS5600 as5600(&Wire1) with no luck.

What is the Maximum Rotation Speed ​​in RPM?

Hello,

I was able to successfully test the position sketch, but offset deviation is occurring with each course reversal.

I'm trying to read the motor shaft position from a motorized potentiometer. Ref.: https://a.aliexpress.com/_mrwDFDs

When manually rotating the axis slowly, the position offset does not deviate. But if it's too fast, a detour occurs.

Being the 12-bit AS5600, and the 150us sampling. How many rpm is the rev limit?

If each lap has 4096 positions. Does each position need at least 150us to be read?

Reverse rotation gives incorrect (huge) numbers.

          Rob, it is Mitch again, (Laptophead)

Having 2 sensors on one bus works. But there is still the issue of not getting good initial readings.

Despite the
as5600_R.resetPosition();
as5600_L.resetPosition();

when there are 2 sensors I get erroneous readings such as
REV R: -1
REV L: 0
DEG R= 377487200°
DEG L= 132°

Rolling the motors forward increases the angles as expected, but rolling them back past 0 deg. is
resulting in those huge numbers . Ideally it would go to 359 , 358 etc degrees ,

Would you know what to do about that?
Thanks
mitch

Originally posted by @laptophead in #45 (comment)

Use lib without dir pin

In some projects dir pin is permanently connected to GND or 5V. Method begin need to set dir pin. How use lib in this case?

Testing

Hi,
I have uploaded the "AS5600_demo.ino" file:
Note that I dont have the dir pin hooked up so cant test it.

It seems to work but not sure about "as5600.readAngle()"

`18:35:42.815 -> 32336 4095 24.53
18:35:43.832 -> 33338 4095 24.53
18:35:44.846 -> 34340 4095 25.05
18:35:45.831 -> 35342 4095 359.38
18:35:46.846 -> 36344 0 96.88
18:35:47.831 -> 37346 0 151.03
18:35:48.848 -> 38348 0 145.41
18:35:49.831 -> 39349 4095 56.00
18:35:50.847 -> 40351 4095 12.48
18:35:51.829 -> 41353 4095 358.15
18:35:52.845 -> 42355 4095 343.91
18:35:53.862 -> 43357 4095 7.12
18:35:54.844 -> 44359 4095 54.95
18:35:55.861 -> 45361 0 94.15
18:35:56.843 -> 46363 0 102.59
18:35:57.860 -> 47364 0 103.56
18:35:58.843 -> 48366 0 103.65
18:35:59.861 -> 49368 0 105.05
18:36:00.844 -> 50370 4095 55.91
18:36:01.863 -> 51372 4095 2.11
18:36:02.880 -> 52374 4095 5.63
18:36:03.863 -> 53376 4095 6.07
18:36:04.880 -> 54377 4095 24.79
18:36:05.863 -> 55379 0 87.65
18:36:06.878 -> 56381 0 101.98
18:36:07.860 -> 57383 0 101.71
18:36:08.877 -> 58385 0 101.89
18:36:09.893 -> 59387 0 98.55
18:36:10.877 -> 60389 4095 54.77
18:36:11.894 -> 61390 4095 15.38
18:36:12.875 -> 62392 4095 15.65
18:36:13.891 -> 63394 4095 64.70
18:36:14.875 -> 64396 0 109.45
18:36:15.892 -> 65398 0 107.43
18:36:16.875 -> 66400 0 105.93
18:36:17.892 -> 67402 0 113.05
18:36:18.907 -> 68403 0 200.53
18:36:19.890 -> 69405 0 202.20
18:36:20.906 -> 70407 0 223.30
18:36:21.886 -> 71409 2722 251.52
18:36:22.896 -> 72411 4095 272.09
18:36:23.912 -> 73413 1763 244.31
18:36:24.895 -> 74414 1148 239.74
18:36:25.910 -> 75416 71 231.74
18:36:26.894 -> 76418 0 228.57
18:36:27.909 -> 77420 0 223.47
18:36:28.926 -> 78422 0 214.24
18:36:29.907 -> 79424 0 205.27
18:36:30.926 -> 80426 0 120.62
18:36:31.908 -> 81428 0 115.08
18:36:32.925 -> 82430 0 100.66
18:36:33.909 -> 83431 0 88.00
18:36:34.925 -> 84433 4095 59.25
18:36:35.942 -> 85435 4095 55.12
18:36:36.922 -> 86437 0 142.15
18:36:37.940 -> 87439 0 196.22
18:36:38.921 -> 88441 0 205.54
18:36:39.939 -> 89443 0 206.68
18:36:40.920 -> 90445 0 206.59
18:36:41.937 -> 91446 0 206.15
18:36:42.953 -> 92448 0 172.92
18:36:43.936 -> 93450 0 126.59
18:36:44.954 -> 94452 0 105.41
18:36:45.935 -> 95454 0 101.54
18:36:46.950 -> 96456 0 82.29
18:36:47.964 -> 97458 4095 57.41
18:36:48.947 -> 98459 4095 34.73
18:36:49.964 -> 99461 4095 8.79
18:36:50.945 -> 100463 4095 350.07
18:36:51.963 -> 101465 4095 336.35
18:36:52.945 -> 102467 4095 336.79
18:36:53.962 -> 103469 4095 334.07
18:36:54.946 -> 104471 4095 334.07
18:36:55.963 -> 105472 4095 334.86
18:36:56.981 -> 106474 4095 332.40
18:36:57.964 -> 107476 4095 332.48
18:36:58.979 -> 108478 4095 30.86
18:36:59.961 -> 109480 0 78.68
18:37:00.979 -> 110482 0 111.56
18:37:01.960 -> 111484 0 137.41
18:37:02.974 -> 112486 0 106.02
18:37:03.987 -> 113487 0 84.31
18:37:04.972 -> 114489 0 85.80
18:37:05.985 -> 115491 0 85.89
18:37:07.001 -> 116493 0 90.46
18:37:07.981 -> 117495 0 90.46
18:37:08.997 -> 118497 0 90.46
18:37:09.980 -> 119499 0 90.46
18:37:11.001 -> 120500 0 90.46
18:37:11.983 -> 121502 0 90.46
18:37:13.000 -> 122504 0 90.46
18:37:14.017 -> 123506 0 90.46

`

setOffset() fails when called again

The setOffset() function does not work as expected when called more than once after the program starts. This makes it difficult to zero again when the program is running.

The current workaround is to use setOffset() in conjunction with getOffset() but the user has to implement the math for it.

Connecting multiple encoders

I'm working on my robotics project and I'm using a library to check the position of stepper motors. Can you help me and tell me how to connect two or more encoders to a single arduino board?

Weird noise in cumulative position reading

I have this weird noise on the cumulative position output:

image

image

The output is :

readAngle() / rawAngle()
getCumulativePosition()

Also, it seems you are using rawAngle output for the cumulativeposition, wouldn't it be better to use the filtered readAngle output? So i could choose the filter settings? Now my filter settings have no effect on the cumulative output.

EDIT:
I think the problem was starting a task pinned to core on ESP32, when i started the task without pinning it to a core.. the noise is gone. Ill investigate more and close this issue later if this was the actual cause.

Though i still recommend using readAngle, instead of rawAngle on the cumulative output. Especially the hysteris might be good, as without it there is the possibility of bouncing between 0 and 4095 and that could F up the revolutions counter.

Support for other I2C addresses/AS5600L

The AS5600L is a newer variant of the AS5600, it supports burning a custom I2C address to each individual chip, and it also has a different default I2C address (0x40 instead of 0x36). Would it be possible to add a feature that allows you to pass a I2C address other than 0x36, so that multiple sensors can be used without multiplexing?

i2c reading gets stuck sometimes

Hi, thanks so much for this library!

As you mentioned "Please share your experiences.", I figured I post here about an issue I'm having.

Every now and then (about once every half minute on average) the reading code blocks and stops for 1002ms.
I put this in my loop:

long readAngleTime = millis();

  rEncoderRaw = rAs5600.readAngle();

  if (millis() - readAngleTime > 1){
    Serial.print("angle time: ");
    Serial.println(millis() - readAngleTime);
  }

And it outputs 1002 every now and then.

This is especially problematic as my sensor is on a shaft that spins up to about 3800rpm, and multiple revolutions may pass in this 1 second so I have lost track of the amount of full revolutions it has made.

I am using the sensor on a wiper motor, from this tutorial: https://hackaday.io/project/187675-strong-servo-motor-with-a-wiper-motor
And the code runs on an ESP32.

I am clueless why readAngle() times out, nor do I have any idea on how to reduce the timeout time.

I will attempt to solve this issue by switching to pwm output, as suggested to me on the Arduino discord, but perhaps someone can look into this to help others in the future!

Thanks

Bug with using second I2C bus on ESP32 for two AS5600 encoders

I have been trying to implement two AS5600's on the same ESP32 using its two I2C bus interfaces. However, after running this code here:

#include "AS5600.h"
#include "Wire.h"

const int SCL_1 = 23; 
const int SDA_1 = 22;
const int SCL_2 = 19;
const int SDA_2 = 18; 

AS5600 as5600_0(&Wire);
AS5600 as5600_1(&Wire1);


void setup()
{
  // Initialise Serial Connection
  Serial.begin(115200); 

  // Initialise as5600_0 Connection
  as5600_0.begin(SDA_1, SCL_1); 
  Serial.print("Connect device 0: ");
  Serial.println(as5600_0.isConnected() ? "true" : "false");
  
  // Initialise as5600_1 Connection
  as5600_1.begin(SDA_2, SCL_2); 
  Serial.print("Connect device 1: ");
  Serial.println(as5600_1.isConnected() ? "true" : "false");
}


void loop()
{
  Serial.print(millis());
  Serial.print("\t");
  Serial.println(as5600_0.rawAngle() * AS5600_RAW_TO_DEGREES);
  Serial.print("\t");
  Serial.println(as5600_1.rawAngle() * AS5600_RAW_TO_DEGREES);
  Serial.print("\n");
  delay(100);
}

It caused the 2nd AS5600 is to copy the readings of the 1st AS5600. I found that temporary fix is to comment out line 68 of AS5600.cpp:

// _wire = &Wire;

AS5600.h Library , trouble with a second sensor

I am trying to implement 2 sensors on the same IC2 bus. I bought the one called AS5600L that has an address of 0x40. Tried to implement it using setAddress, but the library won't execute it.
Says there is no member. Please help.
Mitch

AS5600_position_M_01_TwoSensors:27:11: error: 'class AS5600' has no member named 'setAddress'; did you mean 'getAddress'?
as5600L.setAddress(0x40); // AS5600L only

AS5600 module came with a resitor between PGO and GND

I spent several hours trying to change the output mode to PWM. Both with the library and bare I2C. Actually, the output wasn't working at all. I discovered that the module has a resistor between PGO and GND, and that it was causing the AS5600 to disable the output (to use it for programming, check datasheet). This resistor needs to be removed to use the out pin.

This obviously has nothing to do with the library, but I think it could save some people a lot of trouble if you would add a disclaimer/warning somewhere in the README (under the OUT pin section) for people to check whether this is the case, and to remove the resistor if they intend to use the out pin.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.