Stepper FOC with just 12bit encoder

  // zero electric angle not known
  if(!_isset(zero_electric_angle)){
    
    // Move 1 shaft rotation open loop (pole_pairs * 2 pi electric angle) 

    // create a look up table with a offset for each polepair
    float sum[pole_pairs] = {};
    int count[pole_pairs] = {};

    for (int pp_i = 0; pp_i < pole_pairs; pp_i++) {
        // for every pole we want ~ 20 measurements;
        for ( int measure_i = 0; measure_i < 20; measure_i++) {
            // for every measurement we want to take 20 steps to get to the next one
            float angle = 0;
            for (int i = 0; i <=20; i++ ) {
              angle = _2PI * (measure_i * 20 + i) / 400.0f;
              setPhaseVoltage(voltage_sensor_align, 0,  angle);
            
              _delay(1);
            }
            _delay(2);
            sensor->update();
            float sensorAngle = sensor->getAngle() * pole_pairs * sensor_direction;
            float openLoopAngle = pp_i * _2PI + angle; 
            float angleOffset = sensorAngle - openLoopAngle;
            monitor_port->println(angleOffset);
            int bin = sensor->getMechanicalAngle() * pole_pairs / _2PI;
            sum[bin] += angleOffset;
            count[bin] += 1;
        }
    }

    for (int pp_i = 0; pp_i < pole_pairs; pp_i++) {
      zero_offset_nonlin[pp_i] = _normalizeAngle(sum[pp_i]/count[pp_i] - _PI_2);
    }

    if(monitor_port) {
      for (int pp_i = 0; pp_i < pole_pairs; pp_i++) {
        monitor_port->print(F("bin: "));
        monitor_port->print(pp_i);
        monitor_port->print(F(" sum: "));
        monitor_port->print(sum[pp_i]);
        monitor_port->print(F(" count: "));
        monitor_port->print(count[pp_i]);
        monitor_port->print(F(" mean: "));
        monitor_port->println(zero_offset_nonlin[pp_i]);
      }
    }

    zero_electric_angle = 0;
    // stop everything
    setPhaseVoltage(0, 0, 0);
    _delay(200);
  }else if(monitor_port) monitor_port->println(F("MOT: Skip offset calib."));
  return exit_flag;

There is a small update to the algorithm. The calibration didn’t work before when the offset would wrap around / cross 2PI. Also improved the calibration in cases when the sensor direction is anticlockwise.

Example calibration