Skip to content

ESP32HWEncoder: no way to seed the counter from a known absolute angle #100

Description

@sylque

Problem

init() clears the counter, and nothing public can set it afterwards. So the reported angle is always relative to wherever the shaft rested at power-up, and it changes on every boot.

This blocks a natural setup: an MT6835 (driver is in this repo) gives an absolute position over SPI, while its ABZ output gives fast hardware counting. But the ABZ half cannot be absolute, so anything saved in encoder coordinates — an end-stop range, a zero_electric_angle — is wrong after a restart. The zero_electric_angle error is multiplied by pole_pairs.

angleOverflow and angleCounter are protected, and only the index-pin ISR resets them, which needs a Z channel.

Checked against current master.

Suggested fix

A public method to place the counter at a known angle:

void ESP32HWEncoder::setAbsoluteAngle(float angle)
{
    if(!initialized){return;}

    int32_t count = (int32_t) lroundf(angle * cpr / _2PI);
    count %= cpr;
    if (count < 0){ count += cpr; }

    taskENTER_CRITICAL(&spinlock);
    pcnt_counter_clear(pcnt_config.unit);
    angleOverflow = count;
    angleCounter  = 0;
    angleSum      = count;
    taskEXIT_CRITICAL(&spinlock);
}

A setCounter(int32_t) in raw counts would work just as well.

One detail worth building in

Wrap the result into [0, 2PI). Sensor::update() treats a jump above 0.8 * 2PI as a full turn, so an unwrapped offset corrupts the full-turn counter.

A related problem that seeding does NOT solve

Worth stating explicitly, because it looks like part of this request but isn't.

getAngle() is full_rotations * 2PI + angle. full_rotations only counts crossings of the 0/2PI wrap point since boot, so it always starts at 0 wherever the shaft happens to rest. Seeding the counter fixes the within-turn part; it does nothing about the turn count. If the shaft boots on the far side of the wrap point from where a position was recorded, getAngle() is still a full turn off.

The correct fix is what single-turn absolute drives do: at startup, don't trust the turn counter — reconcile it against the stored reference and pick the whole turn that puts the shaft nearest it. Unambiguous for any axis travelling under half a turn, works from any resting position, and leaves full_rotations free to wrap normally during operation, so velocity is unaffected.

That belongs on Sensor, not here — it applies to every single-turn sensor. Something like a Sensor::addFullRotations(int32_t) (shifting vel_full_rotations too, so the correction isn't read as motion) would be enough. Happy to raise it separately against Arduino-FOC.

Workaround without changing the library

getSensorAngle() is public and virtual, so a subclass can store the offset once and add it back on every read.

Happy to open a PR — radians or raw counts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions