Measuring Gimbal Resistance, Inductance, and KV using SimpleFOC

I’m not sure how useful it is for the general public, but I wrote some functions to measure gimbal resistance, inductance (both Q and D), and KV in SimpleFOC.

(My doubts about usefulness are related to needing current and position sensors for this, and if you have those, you typically don’t really need to know these motor parameters.)

I plan to do some feed forward control on the current loop, only using the PID for the last bit of error.

With my GBM2804-100T I’m consistently getting 6.3Ω, 2.0mH q, 1.5mH d, and 220kv testing with 3V.

Here’s the code, might anyone ever need something similar:

// 20ms time constant filter
LowPassFilter currentFilter(0.02f);

// Function to measure phase resistance
float measureResistance(float testVoltage) {
    // Update sensor and electrical angle
    motor.sensor->update();
    motor.electrical_angle = motor.electricalAngle();

    // Set test voltage on d-axis
    motor.setPhaseVoltage(0, testVoltage, motor.electrical_angle);

    // Let current stabilize with filtering
    uint32_t timestamp = micros();
    float filteredCurrent = 0;

    while (micros() - timestamp < 100000) {
        filteredCurrent = currentFilter(motor.current_sense->getFOCCurrents(motor.electrical_angle).d);
        delayMicroseconds(500);
    }

    // Clear voltage and let system settle
    motor.setPhaseVoltage(0, 0, motor.electrical_angle);
    delayMicroseconds(50000);

    // Return calculated resistance
    return testVoltage / filteredCurrent;
}
// Enum for axis selection
enum Axis {
    AXIS_D,
    AXIS_Q
};

// Function to measure inductance on a specific axis
float measureAxisInductance(float testVoltage, float resistance, Axis axis) {
    // Calculate target current (63.2% of steady state)
    float steadyStateCurrent = testVoltage / resistance;
    float targetCurrent = steadyStateCurrent * 0.632f;

    // Clear voltage and let system settle
    motor.setPhaseVoltage(0, 0, motor.electrical_angle);
    delayMicroseconds(50000);

    // Update sensor and electrical angle
    motor.sensor->update();
    motor.electrical_angle = motor.electricalAngle();

    // Apply voltage to appropriate axis
    motor.setPhaseVoltage(axis == AXIS_D ? 0 : testVoltage, axis == AXIS_D ? testVoltage : 0, motor.electrical_angle);

    // Measure time to reach target current
    float current = 0;
    uint32_t endTime = 0;
    uint32_t startTime = micros();
    uint32_t timeout = startTime + 5000; // 5ms timeout

    while (current < targetCurrent && micros() < timeout) {
        // Get current on appropriate axis
        current = (axis == AXIS_D)
            ? motor.current_sense->getFOCCurrents(motor.electrical_angle).d
            : motor.current_sense->getFOCCurrents(motor.electrical_angle).q;

        endTime = micros();
    }

    // Clear voltage
    motor.setPhaseVoltage(0, 0, motor.electrical_angle);

    // Calculate time constant in seconds
    float timeConstant = (endTime - startTime) / 1000000.0f;

    // Return inductance
    return resistance * timeConstant;
}
LowPassFilter velocityFilter(0.05f);
LowPassFilter currentFilterKV(0.1f);

uint32_t measurement_duration = 1000000; // 1 second measurement
float filtered_velocity;
float filteredCurrent;

// Function to measure KV rating (RPM/volt)
float measureKV(float resistance, float testVoltage) {

    // Measure velocity over a period of time
    filtered_velocity = 0;
    uint32_t measurement_start = micros();

    //
    while (micros() - measurement_start < measurement_duration) {

        // Update sensor readings
        motor.sensor->update();
        motor.electrical_angle = motor.electricalAngle();

        // Filter the velocity
        filtered_velocity = velocityFilter(motor.sensor->getVelocity());

        float currentD = motor.current_sense->getFOCCurrents(motor.electrical_angle).d;
        float currentQ = motor.current_sense->getFOCCurrents(motor.electrical_angle).q;

        float totalCurrent = sqrtf(currentD * currentD + currentQ * currentQ);

        filteredCurrent = currentFilterKV(totalCurrent);

        // Update FOC algorithm to maintain proper field orientation
        motor.setPhaseVoltage(testVoltage, 0, motor.electrical_angle);

        // Brief delay for next measurement
        delayMicroseconds(100);
    }

    // Stop motor and wait for spindown
    motor.setPhaseVoltage(0, 0, motor.electrical_angle);
    delayMicroseconds(200000);

    // Convert rad/s to RPM (rad/s * 60 / 2π)
    float rpm = filtered_velocity * 60.0f / (TWO_PI);

    // Calculate KV rating (RPM per volt)
    return abs(rpm / (testVoltage - resistance * filteredCurrent));
}

// Main measurement function
void measureFOCQDRI(float testVoltage, float kv_test_voltage) {
    // Measure phase resistance
	resistance = measureResistance(testVoltage);

    // Measure inductances
	inductanceq = measureAxisInductance(testVoltage, resistance, AXIS_Q);
	inductanced = measureAxisInductance(testVoltage, resistance, AXIS_D);

    // Measure KV rating (using a lower voltage for safety)
	kv_rating = measureKV(resistance, kv_test_voltage);
}
3 Likes

I just played with the code since I really like the idea to have my motors automatically characterized, since I do not trust all datasheets. I am not 100% sure however, what is measured:

Resistance: Is that a single phase or phase to phase? My motor has the phase to phase resistance specified and I can confirm it with my Ohmmeter, the measurement with the code from above results in 1/2 of that, so it looks more like the single phase resistance of a star configuration (but I don’t see how that would work…).

Q/D inductance: How do they relate to the phase to phase inductance? The measured Lq is approximately 3* the specified phase to phase inductance.

Kv: Well, it is 70% higher than the spec, but this may be ok.

I was under the impression that knowing the motor inductance and KV would lead to better SimpleFOC performance, even in the presence of current and position sensor. If that’s the case, your code id definitely useful, if only to check the motor datasheet.

Interestingly, also the measured parameters strongly depend on the test voltage (B-G431B-ESC1 and a T-motor G100 kv10 (2.6Ohm, 2.35mH):

  11.0s LOG:    Measured motor parameters at U=2.0V:
  11.0s LOG:    ======================================
  11.0s LOG:    R = 1.42
  11.0s LOG:    Lq = 0.0071124
  11.0s LOG:    Ld = 0.0013059
  11.0s LOG:    kv = 17.12 @ 2.00V

  12.7s LOG: 
  12.7s LOG:    Measured motor parameters at U=4.0V:
  12.7s LOG:    ======================================
  12.7s LOG:    R = 1.39
  12.7s LOG:    Lq = 0.0069802
  12.7s LOG:    Ld = 0.0012833
  12.7s LOG:    kv = 17.46 @ 4.00V

  14.4s LOG: 
  14.4s LOG:    Measured motor parameters at U=-2.0V:
  14.4s LOG:    ======================================
  14.4s LOG:    R = 1.41
  14.4s LOG:    Lq = 0.0070530
  14.4s LOG:    Ld = 0.0070544
  14.4s LOG:    kv = 14.55 @ -2.00V

  16.1s LOG: 
  16.1s LOG:    Measured motor parameters at U=-4.0V:
  16.1s LOG:    ======================================
  16.1s LOG:    R = 1.36
  16.1s LOG:    Lq = 0.0068211
  16.1s LOG:    Ld = 0.0068224
  16.1s LOG:    kv = 15.50 @ -4.00V

  17.7s LOG: 
  17.7s LOG:    Measured motor parameters at U=6.0V:
  17.7s LOG:    ======================================
  17.7s LOG:    R = 2.11
  17.7s LOG:    Lq = 0.0015574
  17.7s LOG:    Ld = 0.0010495
  17.7s LOG:    kv = 18.92 @ 6.00V

  19.4s LOG: 
  19.4s LOG:    Measured motor parameters at U=8.0V:
  19.4s LOG:    ======================================
  19.4s LOG:    R = 4.61
  19.4s LOG:    Lq = 0.0013275
  19.4s LOG:    Ld = 0.0010003
  19.4s LOG:    kv = 26.39 @ 8.00V

The resistance and inductance are for indeed for a single phase - which actually makes the KV figure off by a factor of 2 - didn’t catch this earlier, sorry! That it measured a single phase is due to the way the Q and D currents are computed.

And another problem measuring high resistance motors is that the resistive losses are essentially subtracted from the backEMF voltage, which makes the KV off by another margin. Looking at the code I did try compensating for this. You could give it a try without and see how it lands.

return abs(rpm / (testVoltage - resistance * filteredCurrent));
return abs(rpm / testVoltage);

Voltage matters because with low voltages (and high supply voltages) the duty cycle is so small dead time offsets become quite significant. I suppose at high voltages other losses could start contributing to the reading being off. Clipping at a certain point as well. And motor heating, which increases the resistance?

Looks like these details do all deserve some more attention. I wrote it for my own open loop feed forward correction, which I wanted to have the parameters measured though its own electronics, so any offsets would be compensated for the other way automatically.

Q/D and inductance were 2.0mF and 1.5mF here, which was really close to double of what I measured as minimum and maximum inductance rotating the motor with an LRC meter attached to two of the leads.

I investigated further. My concern was not so much the kv measurement, but all others. I believe that the measured resistance misses a factor of 2.0, probably because the voltage set by motor.setPhaseVoltage() is centered (this is really just an assumption for now!). After I fixed that, the measured inductance is very close to the spec, just kv was still a factor of 2.0 too high, but if my theory is correct, that’s expected since the same motor.setPhaseVoltage() is used. I am a bit too lazy right now to validate it all with a scope, that’s not so easy with my setup…

You’re correct on the factor 2.0 - the Q and D voltage math specifies it for a single phase, but measuring over two leads you get two phases in series, so double that. That does indeed also mess up the kv rating. That was an error on my end.

Glad to hear it’s working other than that!