# AS5030 magnetic encoder: capturing a PWM signal with an ATSAMD21

It seems that I can’t avoid periodically ostracizing this page. Welp, let’s try to make it up.

# Context: the AS5030 magnetic encoder IC

In a project I’m currently working on (more about it in later posts, perhaps), I needed a halfway decent way of measuring the angular displacement of a small, manually-turnt wheel. I had originally mounted a small mechanical encoder (Bourns PEC11R) into my solution, which yielded about 96 pulses per-revolution (PPR). However, on a long run, contacting encoders are not the best pick for these applications due to mechanical wear.

Searching for a better solution I came across ams’ portfolio of magnetic encoders, which features interesting solutions for contactless position measurement. Amongst them, the AS5030 (datasheet) was the one that best met my requirements, providing an improved 256 PPR. The gist of it is simple: mount the IC, power it up and spin a magnet on top of it. The IC will generate a PWM signal with a pulse width proportional to the magnet’s angle in relation to the chip. Spoiler alert: not any magnet will work! You’ll have to get diametric magnets, such as this little guy here.

As you may see in the diagram above, the AS5030 also provides a serial interface. The thing, however, is a weird half-duplex SPI that operates on 21-bit long frames, making it odd to use. On top of that, the AS5030 is strictly 5V compatible, which means I would have to level-shift all the signals going to my 3v3 microcontroller. That would just add unnecessary parts and complexity, so I ditched the serial interface and went with PWM (and a resistive divider to shift the voltage for the uC).

The PWM signal frequency is rated at 1.72kHz, but may vary slightly with temperature. The duty cycle encodes the position, going from a spec’d minimum of 2.26us to a spec’d maximum of 578.56us, like shown in the picture below:

Getting the angular position accurately can be done via the ratio of the duty cycle ($t_{\text{on}}$) and the PWM’s period ($t_{\text{on}} + t_{\text{off}}$), as per the equation supplied in the datasheet:

$\text{angle}[{^\circ}] = \frac{360}{256}\large[\large(257\frac{t_{on}}{t_{\text{on}}+t_{\text{off}}}\large)-1\large]$

# ATSAMD21: peripheral bureaucracy

Measuring the pulse-width of a PWM signal is a textbook example of input capture. Capture operations allow you to record a signal edge together with a timestamp directly via hardware, without having to employ any CPU cycles. The vast majority of modern microcontrollers support this feature, and the ATSAMD21E18A employed in this project is no exception.

As we see in the ATSAMD21 datasheet (section 31.2), the TCC peripheral not only supports this sort of operation, but also has a dedicated mode for pulse-width capture, where “period T will be captured into CC0 and the pulse-width $t_p$ into CC1”. Unfortunately, there’s a catch. Usually, capture operations occur entirely within a timer peripheral, which detects the signal’s edge on a dedicated pin an stores the timestamp based on its internal counter. However, to increase flexibility, capture operations in the ATSAMD21 use the External Interrupt (EIC) peripheral to generate an event in the internal Event System (EVSYS), which gets relayed via the internal event channels to the Timer/Counter (TCC) peripheral, in turn triggering the capture. Wait, what? Let me sketch that out:

This certainly allows for a lot more flexibility, not tying the capture operation to a particular pin, since all pins have EIC Interrupt/Event capability. However, configuring it is quite a mouthful (and quite poorly documented, BTW).

# Talk is chip, show me the encode

I suck at puns. Ok, so let’s get a bloatw…I mean, ASF-free setup going. In my particular example, I’ll be using pin PA11, tied to EIC channel 4, but again, any pin can be used. Also, I’ll be using EVSYS’s channel 0, but you can use any of the available channels for any event. This will be done in four steps:

We’ll first configure the timer. It’ll run off the main GCLK (in this case, at 48MHz), counting up from 0 until 0xFFFFFFFF (then wrapping around):

Then, let’s setup the EIC. Sense is set to HIGH, and a tiny helper function configures the channel. Notice how the EVCTRL bit is set, which generates the EIC event:

Let’s then configure the EVSYS: EIC acts as a event generator on channel 0, and TCC1 is the event’s user (akin to a listener):

Last but not least,  let’s configure the pin. The PINMUX_PA11A_EIC_EXTINT11 value should be defined in the samd21.h header:

That’s it! Your device is now running PWM pulse-width capture on pin PA11, with no CPU cycles being used for it at all.

# Now what?

Now that everything’s configured, … Well, make sure the PWM signal is getting to the designated pin. The signal’s pulse-width and period are now constantly being fetched and saved into the CC0 and CC1 registers, respectively. To wrap it up with the AS5030, we can now compute the measured angle with the following function:

This function returns angles x10, (i.e. 15.4° = 154), so that no float support needs to be added (which can eat a lot of Flash on smaller devices). It’s also enough to properly deal with the AS5030’s 1.4° resolution.

Best part? It actually works:

You can optionaly use the TCC_INTFLAG_MC1 interruption if you don’t want to poll the registers for changes. Yeah.

’til next time.