New mini project: adapt the sensorless commutation sub section of odrive!(funded :)

Depending on your MCU type, using double will work, or it will be just the same as using floats.

I believe on the 32bit MCUs like ESP32 or STM32s you will get double precision (64bit) float maths. But the issue in using it is that the MCUs only have 32 bit FPUs (if they have FPUs at all).

ESP32 has 32bit FPUs, and so do the Cortex-M0+, Cortex-M4 and Cortex-M7 ARM cores, which covers a large majority of the STM32 and Arduino type 32bit boards out there.

You have to go to a pretty large MPU type processor before you will find a double precision floating point unit.

Why care? Performance. Double precision maths will take much longer without the hardware acceleration, and since most algorithms are composed of many mathematical operations the final impact on run-time is very significant.

It’s an M4 core, so we have floating point hardware, I don’t know if double takes longer than single.

It’s doing something, but the “phase”, which is supposed to represent the position in the cycle that the rotor is in, is definitely not reading right. It oscillates around but the center of the wave drifts, and the peak to peak is not right at all. I tried observer gains from 10 to 30,000 and some will lead to various instabilities, but nothing correct.

I think part of the problem may be inaccurate current data. I noticed the DC current is >45 mA even when the motor is not connected. The current waveform for each phase is centered around close to zero and seems to be about right but it’s pretty hard to check.

I doubt it is an adc calibration issue, though, I think it may be shoot through on the inverter stage. It’s low side sensing so the current going in on the low side of the inverter is what is measured, right. I may need inline and better, sensing… crap, pretty hard to know.

Maybe I should get on with the flagship board and then circle back to this when I know the hardware is better, also the effort to calibrate things won’t be useless.


^ that’s what appears to happen when I put the gain above 10,000 or so. The gain is compensated so it should not matter how often the observer function is run.

latest code:

#include <SimpleFOC.h>
#include <math.h>

// NUMBER OF POLE PAIRS, NOT POLES, specific to the motor being used!
BLDCMotor motor = BLDCMotor(7); 
//this line must be changed for each board
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
LowsideCurrentSense currentSense = LowsideCurrentSense(0.003, -64.0/7.0, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT);
LowPassFilter diff_filter = LowPassFilter(0.05);
PhaseCurrent_s current1 = currentSense.getPhaseCurrents();
float goal_speed =0;
float v=2;
float v_diff=1;
float accel = 92;// in rads per second per second
float v_per_radsPS = 0.0232;
float accel_v_boost = 0.5;// voltage is increased during acceleration and deacceleration by this amount
bool voltage_override = 1;
float power_figure = 1.5;
float power_coeff = 0.00043;// the serial communicator could actually use an extra digit for this one.
float A, B, C;
float currentlf_now =0;
float prop_V= 0;
float min_V = 1;
float v_limit = 19;
float current_limit_slope = 1.8;// this is in milliamps pre rad per second
float current_limit_o_term = 200;//this is the current limit at zero rps, it may not trip with stall 
float maybe_o = 1;
float Va = 0;
float Vb = 0;
float motortiming = 0;
void SerialComm(){ 
  if (Serial.available() > 0){
  switch(Serial.peek()){
      case 't': Serial.read(); Serial.print("t"); Serial.println(goal_speed); break;
      case 'c': Serial.read(); Serial.print("c"); Serial.println(accel); break;
      case 'v': Serial.read(); Serial.print("v"); Serial.println(motor.voltage_limit, 4); break;
      case 'n': Serial.read(); Serial.print("n"); Serial.println(v_diff); break;
      case 'p': Serial.read(); Serial.print("p"); Serial.println(v_per_radsPS, 4); break;
      case 'b': Serial.read(); Serial.print("b"); Serial.println(accel_v_boost); break;
      case 'o': Serial.read(); Serial.print("o"); Serial.println(voltage_override); break;
      case 's': Serial.read(); Serial.print("s"); Serial.println(motor.target); break;
      case 'f': Serial.read(); Serial.print("f"); Serial.println(power_coeff, 6); break;
      case 'g': Serial.read(); Serial.print("g"); Serial.println(currentSense.getDCCurrent(), 5); break;
      case 'i': Serial.read(); Serial.print("i"); Serial.println(get_mA(), 4); break;
      case 'j': Serial.read(); Serial.print("j"); Serial.println(min_V); break;
      case 'w': Serial.read(); Serial.print("w"); Serial.println(driver.voltage_power_supply); break;
      case 'k': Serial.read(); Serial.print("k"); Serial.println(v_limit); break;
      case 'y': Serial.read(); Serial.print("y"); Serial.println(current_limit_slope); break;
      case 'u': Serial.read(); Serial.print("u"); Serial.println(current_limit_o_term); break;
      case 'e': Serial.read(); Serial.print("e"); if (motor.shaft_angle >= 0){
           Serial.println(motor.shaft_angle, 3);
           }
           if (motor.shaft_angle < 0){
           Serial.println((_2PI-(-1*motor.shaft_angle)), 3);
           }
           break;
  case 'T': break;
  case 'C': break;
  case 'V':  break;
  case 'P':  break;
  case 'B':  break;
  case 'Y':  break;
  case 'U':  break;
  case 'O': break;
  case 'F':  break;
  case 'J':  break;
  case 'W': ;break;
  case 'K': ;break;
  default: Serial.read(); break; //if anything we don't recognize got in the buffer, clear it out or it will mess things up.
       
  }
}
  if (Serial.available() >= 9){
  switch(Serial.read())
  { 
    
  case 'T': goal_speed = Serial.parseFloat();break;
  case 'C': accel = Serial.parseFloat();break;
  case 'V': v_diff = Serial.parseFloat(); break;
  case 'P': v_per_radsPS = Serial.parseFloat(); break;
  case 'K': v_limit = Serial.parseFloat(); break;
  case 'B': accel_v_boost = Serial.parseFloat(); break;
  case 'Y': current_limit_slope = Serial.parseFloat(); break;
  case 'U': current_limit_o_term = Serial.parseFloat(); break;
  case 'O': 
    maybe_o = Serial.parseFloat(); // just in case the wrong data gets in somehow we don't want the voltage going crazy
    if (maybe_o < 1){
      voltage_override = 0;
      } 
    if (maybe_o >= 0.999){ 
      voltage_override = 1;
    }
    break;// if it's not one of these, ignore it.
  case 'F': power_coeff = Serial.parseFloat();break;

  
  }
  }
}
void overcurrent_trip(){// if it stalls this won't help except at higher powers, probably. Just helps prevent disaster
  float current_cap = current_limit_o_term + fabs(motor.target)*current_limit_slope;
  if (get_mA() > current_cap){
    voltage_override = 0;
  }
}
float track_return_motor_timing(float v_pA, float v_pB, float current_A, float current_B, float current_C, int outputorno) {
    // Algorithm based on paper: Sensorless Control of Surface-Mount Permanent-Magnet Synchronous Motors Based on a Nonlinear Observer
    // http://cas.ensmp.fr/~praly/Telechargement/Journaux/2010-IEEE_TPEL-Lee-Hong-Nam-Ortega-Praly-Astolfi.pdf
    // In particular, equation 8 (and by extension eqn 4 and 6).
    // The V_alpha_beta applied immedietly prior to the current measurement associated with this cycle
    // is the one computed two cycles ago. To get the correct measurement, it was stored twice:
    // once by final_v_alpha/final_v_beta in the current control reporting, and once by V_alpha_beta_memory.
    // don't engage the system by calling this function until a suitable RPM is achieved or yu will just get nonsense results
    //the basis of the code here is just dumbly adapted from the odrive implementation, with the removal of unnecessary features like the pll for speed tracking.
    static float V_alpha_beta_memory_[] = {0.0f,0.0f};
    static double flux_state_[] = {0.0f,0.0f};
    static float last_v_alpha = 0.0f;
    static float last_v_beta = 0.0f;
    static float observer_gain = 1500.0f; //1500 seems to be about right, 3000 is too high? not clear actually. phase becomes nan somewhere between 10,000 and 15,000 could still be way too low. IDK what a reasonable starting point is, 1000 is what odrive uses. They use 2500 and 8000 in the pdf doc.  Too high and it will bounce around, too low and it will take a long time to catch up with changes.
    static float O_phase_resistance = 2.9f;// in ohms *presumably
    static float O_phase_inductance = 0.0006455f;  // in henries *presumably
    static float O_flux_linkage = 0.00931f;// it's in units of webers *presumably, which is ampere-turns? Can be calculated from the peak to peak per hz, which is easy to measure, so find the equation for that and use that to calc it before then use that.*is this per phase or across terminals*??? I measured it across the terminals
    static unsigned long last_run_ts = micros();
    unsigned long mcs_since_last_run = ticks_diff(micros(),last_run_ts) ;
    float s_since_last_run = float(mcs_since_last_run)/1000000.0f ;
    float current_meas_period = s_since_last_run ;
    static float one_by_sqrt3 = 0.5773502691f;

    if ((current_meas_period * 1000 > 1.0f)) { // what should the coefficient be here? Just run it really fast for now. you can't because you have to be able to debug it.  well ten times per oscillation should be fine, 60 rads per second test speed is 67 hz.  so at least every 1/670 seconds
      flux_state_[0] = 0.0f;
      flux_state_[1] = 0.0f;
      V_alpha_beta_memory_[0] = 0.0f;
      V_alpha_beta_memory_[1] = 0.0f;
     Serial.print("cmp too long:");
     Serial.println(current_meas_period*1000000);
     last_run_ts = micros();
      return false;
    }
            if (get_mA()<70){ //assume measurement is invalid
      current_A = 0.0f;
      current_B = 0.0f;
      flux_state_[0] = 0.0f;
      flux_state_[1] = 0.0f;
      V_alpha_beta_memory_[0] = 0.0f;
      V_alpha_beta_memory_[1] = 0.0f;
     //Serial.print("current too low:");
     //Serial.println(get_mA());
     last_run_ts = micros();
      return false;
        }
    float I_alpha_beta[2] = {current_A, one_by_sqrt3 * (current_B -current_C)};

    // alpha-beta vector operations
    double eta[2] = {0.0f,0.0f};

    for (int i = 0; i <= 1; ++i) {
        // y is the total flux-driving voltage (see paper eqn 4)
        float y = -O_phase_resistance * I_alpha_beta[i] + V_alpha_beta_memory_[i];
        // flux dynamics (prediction)
        float x_dot = float(y);
        // integrate prediction to current timestep
        flux_state_[i] += x_dot * float(current_meas_period);
        // eta is the estimated permanent magnet flux (see paper eqn 6)
        eta[i] = flux_state_[i] - O_phase_inductance * float(I_alpha_beta[i]);
        }

    // Non-linear observer (see paper eqn 8):
    double pm_flux_sqr = O_flux_linkage * O_flux_linkage;

    double est_pm_flux_sqr = (eta[0] * eta[0]) + (eta[1] * eta[1]);
    double bandwidth_factor = 1 / pm_flux_sqr;
    double eta_factor = 0.5f * (observer_gain * bandwidth_factor) * (pm_flux_sqr - est_pm_flux_sqr);
    
    // alpha-beta vector operations
    for (int i = 0; i <= 1; ++i) {
        // add observer action to flux estimate dynamics
        double x_dot = eta_factor * eta[i];
        // convert action to discrete-time
        flux_state_[i] += x_dot * current_meas_period;
        // update new eta
        eta[i] = flux_state_[i] - O_phase_inductance * I_alpha_beta[i];
    }
    // Flux state estimation done, store V_alpha_beta for next timestep
    V_alpha_beta_memory_[0] = last_v_alpha; // still have to figure out exactly what voltage this is so we can sub in the right one
    V_alpha_beta_memory_[1] = last_v_beta; // same here
    last_v_alpha = v_pA;
    last_v_beta = v_pB;
    last_run_ts = micros();
    float phase = atan2(eta[1], eta[0]); // do we have fast_atan available? no
    float stator_field_angle = atan2(I_alpha_beta[0],I_alpha_beta[1]); // these might be the wrong way around fuck. This is the most likely.
    float motor_timing = handle_wraparound_angle(phase, stator_field_angle, return_sign(motor.target));
   if (outputorno == 1){
      Serial.print("sfa:");
      Serial.print(I_alpha_beta[0]);
      Serial.print(", v_pA:");
      Serial.print(v_pA);
      Serial.print(", phase:");
      Serial.println(phase);
      return motor_timing;
    }
    if (outputorno == 0){
      return motor_timing; 
    }
};

float handle_wraparound_angle(float angle_behind, float angle_ahead, int dir){
    static float piepie = 6.2831853f;
    if (dir == 1){  //clockwise drive
      if (angle_behind < angle_ahead){ // they are in the same cycle
        return (angle_ahead-angle_behind);
      }
      if (angle_behind > angle_ahead){ // the angle_ahead has gone past the end and wrapped around
        return (piepie-(angle_behind-angle_ahead));
      }
    }
      if (dir == 0){  //counter-clockwise drive
      if (angle_behind > angle_ahead){ // they are in the same cycle
        return (angle_ahead-angle_behind);
        }
      if (angle_behind < angle_ahead){ // the angle_ahead has gone past the end and wrapped around
        return ((-1*piepie)-(angle_behind-angle_ahead));
        }
      }
}
unsigned long int ticks_diff(unsigned long int t2,unsigned long int t1){ //t2 should be after t1, this is for calculating clock times.
  if (t2<t1){//t2 must have wrapped around after t1 was taken
     return (4294967295-(t1-t2));
  }
     return (t2-t1);
} 
float get_mA(){// this is the estimated current being drawn from the power supply, not the actual motor current which is a bit different
   float x =0;
   x = currentlf_now*motor.voltage_limit/24;
   return  1000*((4.0384440932900223e-002)+3.4514090071108776e-002*x*30);// this is off by like 12 percent in some cases a polynomial of third order fits the data better but might flake out at higher than 500 mA so I didn't try it.
}
void setup() {
  Serial.begin(1000000);
  Serial.println("test serial2");
  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 24;
  driver.init();
  // link the motor and the driver
  motor.linkDriver(&driver);
  currentSense.linkDriver(&driver);
  currentSense.init();
  currentSense.skip_align = true;
  FOCModulationType::SinePWM;
  motor.voltage_limit = 1;   // [V]
  motor.velocity_limit = 300; // [rad/s]
  motor.controller = MotionControlType::velocity_openloop;
  motor.init();
  motor.voltage_limit = 2;
  goal_speed = 2;
}

float run_observer(){
     current1 = currentSense.getPhaseCurrents();
     A = current1.a;
     B = current1.b;
     C = current1.c;
     Va = driver.dc_a * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     Vb = driver.dc_b * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     return track_return_motor_timing(Va, Vb, A, B, C, 0);
}
float run_observer_with_output(){
     current1 = currentSense.getPhaseCurrents();
     A = current1.a;
     B = current1.b;
     C = current1.c;
     Va = driver.dc_a * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     Vb = driver.dc_b * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     return track_return_motor_timing(Va, Vb, A, B, C, 1);
}
int return_sign(float number){
  if (number > 0){
    return 1;
  }
  if (number <= 0){
    return 0;
  }
}
void loop() {
     static unsigned long int loop_clock_in = micros();
     unsigned long int loop_time = 0;
     float loop_time_s = 0;     
     loop_time = ticks_diff(micros(), loop_clock_in);
     loop_clock_in=micros();
     loop_time_s = float(loop_time)/1000000;
     if (motor.target < goal_speed-(accel*loop_time_s*1.5)){//rps not positive enough
           if (motor.target < 0){//counterclockwise rotation, deaccelerating
      motor.target = motor.target+accel*loop_time_s*0.7;
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     }
     run_observer();
          if (motor.target >= 0){ //clockwise rotation, accelerating
      motor.target = motor.target+accel*loop_time_s;
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     }
     }
     
run_observer();
     
     if (motor.target>=goal_speed-(accel*loop_time_s*1.5)){//steady run phase
      if (motor.target<=goal_speed+(accel*loop_time_s*1.5)){ 
        motor.move();
      prop_V = (v_diff+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override; //constant run
      }
     }
     
     
     if (motor.target > goal_speed + (accel*loop_time_s*1.5)){ //rps too positive
           if (motor.target > 0){ //clockwise rotation, deaccelerating
      motor.target = motor.target-accel*loop_time_s*0.7; 
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     } 
run_observer();
          if (motor.target <= 0){
      motor.target = motor.target-accel*loop_time_s; //counterclockwise rotation, accelerating
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     }

run_observer();

     }
     if (prop_V < min_V){
      motor.voltage_limit = min_V*voltage_override;
     }
     else {
      motor.voltage_limit = prop_V;
     }
          if (prop_V > v_limit){
      motor.voltage_limit = v_limit;
     }
     
     for (int i=0;i<10;i++){ // shouldloop at about 37 khz on b-g431 board
     for (int q=0;q<5;q++){
     motor.move();
     motor.move();
     motor.move();
     run_observer();
     motor.move();
     motor.move();
     
     motortiming = run_observer();
     
     }     
     SerialComm();
     }

  //Serial.println(motortiming);
     run_observer_with_output();
     currentlf_now = currentSense.getDCCurrent();
     currentlf_now = diff_filter(currentlf_now);// this updates the current for the get dc current, that function won't work unless this is called each loop
     overcurrent_trip();
}

Another issue is that the observer may need to be run like clockwork, with the exact same time period in between runs every time. I don’t know how to arrange for this, it is a rather big function and I know you aren’t supposed to run such functions with interrupts, but maybe that is the only practical way for me. I can’t really write the whole program so that it runs the observer only once per main loop and then adjust the gain accordingly, there is too much else to do, and there will be more in the future, I don’t think the observer would run often enough.

I am thinking at least 10 times per electrical cycle is a reasonable frequency to run the observer? So at 10 rotations per second with a 7 pole pair motor 70*10 = 700 hz, or once every 1.4 msec. It is running more frequently than that now, I think, several times faster.

Tried getting it to run like clockwork, doesn’t really change anything, I don’t know how fast it’s going but when it prints it creates a glitch, which I can hear, which is about 200 hz, so should be 300*200 = 60,000 per second that it’s going through the inner loop.

#include <SimpleFOC.h>
#include <math.h>

// NUMBER OF POLE PAIRS, NOT POLES, specific to the motor being used!
BLDCMotor motor = BLDCMotor(7); 
//this line must be changed for each board
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
LowsideCurrentSense currentSense = LowsideCurrentSense(0.003, -64.0/7.0, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT);
LowPassFilter diff_filter = LowPassFilter(0.05);
PhaseCurrent_s current1 = currentSense.getPhaseCurrents();
float goal_speed =0;
float v=2;
float v_diff=1;
float accel = 92;// in rads per second per second
float v_per_radsPS = 0.0232;
float accel_v_boost = 0.5;// voltage is increased during acceleration and deacceleration by this amount
bool voltage_override = 1;
float power_figure = 1.5;
float power_coeff = 0.00043;// the serial communicator could actually use an extra digit for this one.
float A, B, C;
float currentlf_now =0;
float prop_V= 0;
float min_V = 1;
float v_limit = 19;
float current_limit_slope = 1.8;// this is in milliamps pre rad per second
float current_limit_o_term = 200;//this is the current limit at zero rps, it may not trip with stall 
float maybe_o = 1;
float Va = 0;
float Vb = 0;
float motortiming = 0;
void SerialComm(){ 
  if (Serial.available() > 0){
  switch(Serial.peek()){
      case 't': Serial.read(); Serial.print("t"); Serial.println(goal_speed); break;
      case 'c': Serial.read(); Serial.print("c"); Serial.println(accel); break;
      case 'v': Serial.read(); Serial.print("v"); Serial.println(motor.voltage_limit, 4); break;
      case 'n': Serial.read(); Serial.print("n"); Serial.println(v_diff); break;
      case 'p': Serial.read(); Serial.print("p"); Serial.println(v_per_radsPS, 4); break;
      case 'b': Serial.read(); Serial.print("b"); Serial.println(accel_v_boost); break;
      case 'o': Serial.read(); Serial.print("o"); Serial.println(voltage_override); break;
      case 's': Serial.read(); Serial.print("s"); Serial.println(motor.target); break;
      case 'f': Serial.read(); Serial.print("f"); Serial.println(power_coeff, 6); break;
      case 'g': Serial.read(); Serial.print("g"); Serial.println(currentSense.getDCCurrent(), 5); break;
      case 'i': Serial.read(); Serial.print("i"); Serial.println(get_mA(), 4); break;
      case 'j': Serial.read(); Serial.print("j"); Serial.println(min_V); break;
      case 'w': Serial.read(); Serial.print("w"); Serial.println(driver.voltage_power_supply); break;
      case 'k': Serial.read(); Serial.print("k"); Serial.println(v_limit); break;
      case 'y': Serial.read(); Serial.print("y"); Serial.println(current_limit_slope); break;
      case 'u': Serial.read(); Serial.print("u"); Serial.println(current_limit_o_term); break;
      case 'e': Serial.read(); Serial.print("e"); if (motor.shaft_angle >= 0){
           Serial.println(motor.shaft_angle, 3);
           }
           if (motor.shaft_angle < 0){
           Serial.println((_2PI-(-1*motor.shaft_angle)), 3);
           }
           break;
  case 'T': break;
  case 'C': break;
  case 'V':  break;
  case 'P':  break;
  case 'B':  break;
  case 'Y':  break;
  case 'U':  break;
  case 'O': break;
  case 'F':  break;
  case 'J':  break;
  case 'W': ;break;
  case 'K': ;break;
  default: Serial.read(); break; //if anything we don't recognize got in the buffer, clear it out or it will mess things up.
       
  }
}
  if (Serial.available() >= 9){
  switch(Serial.read())
  { 
    
  case 'T': goal_speed = Serial.parseFloat();break;
  case 'C': accel = Serial.parseFloat();break;
  case 'V': v_diff = Serial.parseFloat(); break;
  case 'P': v_per_radsPS = Serial.parseFloat(); break;
  case 'K': v_limit = Serial.parseFloat(); break;
  case 'B': accel_v_boost = Serial.parseFloat(); break;
  case 'Y': current_limit_slope = Serial.parseFloat(); break;
  case 'U': current_limit_o_term = Serial.parseFloat(); break;
  case 'O': 
    maybe_o = Serial.parseFloat(); // just in case the wrong data gets in somehow we don't want the voltage going crazy
    if (maybe_o < 1){
      voltage_override = 0;
      } 
    if (maybe_o >= 0.999){ 
      voltage_override = 1;
    }
    break;// if it's not one of these, ignore it.
  case 'F': power_coeff = Serial.parseFloat();break;

  
  }
  }
}
void overcurrent_trip(){// if it stalls this won't help except at higher powers, probably. Just helps prevent disaster
  float current_cap = current_limit_o_term + fabs(motor.target)*current_limit_slope;
  if (get_mA() > current_cap){
    voltage_override = 0;
  }
}
float track_return_motor_timing(float v_pA, float v_pB, float current_A, float current_B, float current_C, int outputorno) {
    // Algorithm based on paper: Sensorless Control of Surface-Mount Permanent-Magnet Synchronous Motors Based on a Nonlinear Observer
    // http://cas.ensmp.fr/~praly/Telechargement/Journaux/2010-IEEE_TPEL-Lee-Hong-Nam-Ortega-Praly-Astolfi.pdf
    // In particular, equation 8 (and by extension eqn 4 and 6).
    // The V_alpha_beta applied immedietly prior to the current measurement associated with this cycle
    // is the one computed two cycles ago. To get the correct measurement, it was stored twice:
    // once by final_v_alpha/final_v_beta in the current control reporting, and once by V_alpha_beta_memory.
    // don't engage the system by calling this function until a suitable RPM is achieved or yu will just get nonsense results
    //the basis of the code here is just dumbly adapted from the odrive implementation, with the removal of unnecessary features like the pll for speed tracking.
    static float V_alpha_beta_memory_[] = {0.0f,0.0f};
    static float I_last_alpha_beta[] = {0.0f,0.0f};
    static float last_etas[]= {0.0f,0.0f};
    static double flux_state_[] = {0.0f,0.0f};
    static float last_v_alpha = 0.0f;
    static float last_v_beta = 0.0f;
    static float last_phase = 0.0f;
    static float observer_gain = 500.0f; //1500 seems to be about right, 3000 is too high? not clear actually. phase becomes nan somewhere between 10,000 and 15,000 could still be way too low. IDK what a reasonable starting point is, 1000 is what odrive uses. They use 2500 and 8000 in the pdf doc.  Too high and it will bounce around, too low and it will take a long time to catch up with changes.
    static float O_phase_resistance = 2.9f;// in ohms *presumably
    static float O_phase_inductance = 0.0006455f;  // in henries *presumably
    static float O_flux_linkage = 0.00931f;// it's in units of webers *presumably, which is ampere-turns? Can be calculated from the peak to peak per hz, which is easy to measure, so find the equation for that and use that to calc it before then use that.*is this per phase or across terminals*??? I measured it across the terminals
    static unsigned long last_run_ts = micros();
    unsigned long mcs_since_last_run = ticks_diff(micros(),last_run_ts) ;
    float s_since_last_run = float(mcs_since_last_run)/1000000.0f ;
    float current_meas_period = s_since_last_run ;
    static float one_by_sqrt3 = 0.5773502691f;
       if (outputorno == 1){
      Serial.print("c:");
      Serial.print(I_last_alpha_beta[0]);
      Serial.print(", v_pA:");
      Serial.print(last_v_alpha);
      Serial.print(", phase:");
      Serial.println(last_phase);
      return false;
    }
    if ((current_meas_period * 1000 > 1.0f)) { // what should the coefficient be here? Just run it really fast for now. you can't because you have to be able to debug it.  well ten times per oscillation should be fine, 60 rads per second test speed is 67 hz.  so at least every 1/670 seconds
      flux_state_[0] = 0.0f;
      flux_state_[1] = 0.0f;
      V_alpha_beta_memory_[0] = 0.0f;
      V_alpha_beta_memory_[1] = 0.0f;
     Serial.print("cmp too long:");
     Serial.println(current_meas_period*1000000);
     last_run_ts = micros();
      return false;
    }
            if (get_mA()<70){ //assume measurement is invalid
      current_A = 0.0f;
      current_B = 0.0f;
      flux_state_[0] = 0.0f;
      flux_state_[1] = 0.0f;
      V_alpha_beta_memory_[0] = 0.0f;
      V_alpha_beta_memory_[1] = 0.0f;
     //Serial.print("current too low:");
     //Serial.println(get_mA());
     last_run_ts = micros();
      return false;
        }
    float I_alpha_beta[2] = {current_A, one_by_sqrt3 * (current_B -current_C)};

    // alpha-beta vector operations
    double eta[2] = {0.0f,0.0f};

    for (int i = 0; i <= 1; ++i) {
        // y is the total flux-driving voltage (see paper eqn 4)
        float y = -O_phase_resistance * I_alpha_beta[i] + V_alpha_beta_memory_[i];
        // flux dynamics (prediction)
        float x_dot = float(y);
        // integrate prediction to current timestep
        flux_state_[i] += x_dot * float(current_meas_period);
        // eta is the estimated permanent magnet flux (see paper eqn 6)
        eta[i] = flux_state_[i] - O_phase_inductance * float(I_alpha_beta[i]);
        }

    // Non-linear observer (see paper eqn 8):
    double pm_flux_sqr = O_flux_linkage * O_flux_linkage;

    double est_pm_flux_sqr = (eta[0] * eta[0]) + (eta[1] * eta[1]);
    double bandwidth_factor = 1 / pm_flux_sqr;
    double eta_factor = 0.5f * (observer_gain * bandwidth_factor) * (pm_flux_sqr - est_pm_flux_sqr);
    
    // alpha-beta vector operations
    for (int i = 0; i <= 1; ++i) {
        // add observer action to flux estimate dynamics
        double x_dot = eta_factor * eta[i];
        // convert action to discrete-time
        flux_state_[i] += x_dot * current_meas_period;
        // update new eta
        eta[i] = flux_state_[i] - O_phase_inductance * I_alpha_beta[i];
    }
    // Flux state estimation done, store V_alpha_beta for next timestep
    V_alpha_beta_memory_[0] = last_v_alpha; // still have to figure out exactly what voltage this is so we can sub in the right one
    V_alpha_beta_memory_[1] = last_v_beta; // same here
    last_v_alpha = v_pA;
    last_v_beta = v_pB;
    last_run_ts = micros();
    last_etas[0] = eta[0];
    last_etas[1] = eta[1];
    I_last_alpha_beta[0] = I_alpha_beta[0];
    I_last_alpha_beta[1] = I_alpha_beta[1];
    float phase = atan2(eta[1], eta[0]); // do we have fast_atan available? no
    last_phase = phase;
    float stator_field_angle = atan2(I_alpha_beta[0],I_alpha_beta[1]); // these might be the wrong way around fuck. This is the most likely.
    float motor_timing = handle_wraparound_angle(phase, stator_field_angle, return_sign(motor.target));

    if (outputorno == 0){
      return motor_timing; 
    }
};

float handle_wraparound_angle(float angle_behind, float angle_ahead, int dir){
    static float piepie = 6.2831853f;
    if (dir == 1){  //clockwise drive
      if (angle_behind < angle_ahead){ // they are in the same cycle
        return (angle_ahead-angle_behind);
      }
      if (angle_behind > angle_ahead){ // the angle_ahead has gone past the end and wrapped around
        return (piepie-(angle_behind-angle_ahead));
      }
    }
      if (dir == 0){  //counter-clockwise drive
      if (angle_behind > angle_ahead){ // they are in the same cycle
        return (angle_ahead-angle_behind);
        }
      if (angle_behind < angle_ahead){ // the angle_ahead has gone past the end and wrapped around
        return ((-1*piepie)-(angle_behind-angle_ahead));
        }
      }
}
unsigned long int ticks_diff(unsigned long int t2,unsigned long int t1){ //t2 should be after t1, this is for calculating clock times.
  if (t2<t1){//t2 must have wrapped around after t1 was taken
     return (4294967295-(t1-t2));
  }
     return (t2-t1);
} 
float get_mA(){// this is the estimated current being drawn from the power supply, not the actual motor current which is a bit different
   float x =0;
   x = currentlf_now*motor.voltage_limit/24;
   return  1000*((4.0384440932900223e-002)+3.4514090071108776e-002*x*30);// this is off by like 12 percent in some cases a polynomial of third order fits the data better but might flake out at higher than 500 mA so I didn't try it.
}
void setup() {
  Serial.begin(1000000);
  Serial.println("test serial2");
  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 24;
  driver.init();
  // link the motor and the driver
  motor.linkDriver(&driver);
  currentSense.linkDriver(&driver);
  currentSense.init();
  currentSense.skip_align = true;
  FOCModulationType::SinePWM;
  motor.voltage_limit = 1;   // [V]
  motor.velocity_limit = 300; // [rad/s]
  motor.controller = MotionControlType::velocity_openloop;
  motor.init();
  motor.voltage_limit = 2;
  goal_speed = 2;
}

float run_observer(){
     current1 = currentSense.getPhaseCurrents();
     A = current1.a;
     B = current1.b;
     C = current1.c;
     Va = driver.dc_a * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     Vb = driver.dc_b * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     return track_return_motor_timing(Va, Vb, A, B, C, 0);
}
float run_observer_with_output(){
     current1 = currentSense.getPhaseCurrents();
     A = current1.a;
     B = current1.b;
     C = current1.c;
     Va = driver.dc_a * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     Vb = driver.dc_b * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     return track_return_motor_timing(Va, Vb, A, B, C, 1);
}
int return_sign(float number){
  if (number > 0){
    return 1;
  }
  if (number <= 0){
    return 0;
  }
}
void loop() {
for (int i=0;i<300;i++){
     static unsigned long int loop_clock_in = micros();
     unsigned long int loop_time = 0;
     float loop_time_s = 0;     
     loop_time = ticks_diff(micros(), loop_clock_in);
     loop_clock_in=micros();
     loop_time_s = float(loop_time)/1000000;
     if (motor.target < goal_speed-(accel*loop_time_s*1.5)){//rps not positive enough
           if (motor.target < 0){//counterclockwise rotation, deaccelerating
      motor.target = motor.target+accel*loop_time_s*0.7;
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS)));
     }

          if (motor.target >= 0){ //clockwise rotation, accelerating
      motor.target = motor.target+accel*loop_time_s;
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS)));
     }
     }
     

     
     if (motor.target>=goal_speed-(accel*loop_time_s*1.5)){//steady run phase
      if (motor.target<=goal_speed+(accel*loop_time_s*1.5)){ 
      prop_V = (v_diff+fabs((motor.target*v_per_radsPS))); //constant run
      }
     }
     
     
     if (motor.target > goal_speed + (accel*loop_time_s*1.5)){ //rps too positive
           if (motor.target > 0){ //clockwise rotation, deaccelerating
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS)));
     } 
          if (motor.target <= 0){
      motor.target = motor.target-accel*loop_time_s; //counterclockwise rotation, accelerating
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS)));
     }

     }
     if (prop_V < min_V){
      motor.voltage_limit = min_V*voltage_override;
     }
     else {
      motor.voltage_limit = prop_V;
     }
          if (prop_V > v_limit){
      motor.voltage_limit = v_limit;
     }
     motor.move();
     motortiming = run_observer();
     
}
      SerialComm();
     run_observer_with_output();
     currentlf_now = currentSense.getDCCurrent();
     currentlf_now = diff_filter(currentlf_now);// this updates the current for the get dc current, that function won't work unless this is called each loop
     overcurrent_trip();
}

update:
I think the flux linkage might be wrong, which would help explain the issues. When I reduce it from 0.00931 to 0.004 the supposed flux started varying by a larger peak to peak (should be +/- pi, or 0-2pi, really), and even larger at 0.002. I calculated the flux linkage using a formula I saw several times on the web including on this forum. In python:

import math
print (math.pi)
peak_to_peak_voltage = 2.68 
freq = 26.45
pole_pairs = 7
RPS = freq/pole_pairs
RPM = RPS*60
kv_rating = RPM/peak_to_peak_voltage
industry_standard_kv_rating = kv_rating/0.95

flux_linkage = 60/(math.sqrt(3) * math.pi * kv_rating * pole_pairs*2) # should be in webers

print ("flux linkage:", flux_linkage)
#is this the flux linkage of the motor or each phase?  Because they may not be the same.

latest program. What it prints should basically be steady at a given rpm.

#include <SimpleFOC.h>
#include <math.h>

// NUMBER OF POLE PAIRS, NOT POLES, specific to the motor being used!
BLDCMotor motor = BLDCMotor(7); 
//this line must be changed for each board
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
LowsideCurrentSense currentSense = LowsideCurrentSense(0.003, -64.0/7.0, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT);
LowPassFilter diff_filter = LowPassFilter(0.05);
PhaseCurrent_s current1 = currentSense.getPhaseCurrents();
float goal_speed =0;
float v=2;
float v_diff=1;
float accel = 92;// in rads per second per second
float v_per_radsPS = 0.0232;
float accel_v_boost = 0.5;// voltage is increased during acceleration and deacceleration by this amount
bool voltage_override = 1;
float power_figure = 1.5;
float power_coeff = 0.00043;// the serial communicator could actually use an extra digit for this one.
float A, B, C;
float currentlf_now =0;
float prop_V= 0;
float min_V = 1;
float v_limit = 19;
float current_limit_slope = 1.8;// this is in milliamps pre rad per second
float current_limit_o_term = 200;//this is the current limit at zero rps, it may not trip with stall 
float maybe_o = 1;
float Va = 0;
float Vb = 0;
float motortiming = 0;
void SerialComm(){ 
  if (Serial.available() > 0){
  switch(Serial.peek()){
      case 't': Serial.read(); Serial.print("t"); Serial.println(goal_speed); break;
      case 'c': Serial.read(); Serial.print("c"); Serial.println(accel); break;
      case 'v': Serial.read(); Serial.print("v"); Serial.println(motor.voltage_limit, 4); break;
      case 'n': Serial.read(); Serial.print("n"); Serial.println(v_diff); break;
      case 'p': Serial.read(); Serial.print("p"); Serial.println(v_per_radsPS, 4); break;
      case 'b': Serial.read(); Serial.print("b"); Serial.println(accel_v_boost); break;
      case 'o': Serial.read(); Serial.print("o"); Serial.println(voltage_override); break;
      case 's': Serial.read(); Serial.print("s"); Serial.println(motor.target); break;
      case 'f': Serial.read(); Serial.print("f"); Serial.println(power_coeff, 6); break;
      case 'g': Serial.read(); Serial.print("g"); Serial.println(currentSense.getDCCurrent(), 5); break;
      case 'i': Serial.read(); Serial.print("i"); Serial.println(get_mA(), 4); break;
      case 'j': Serial.read(); Serial.print("j"); Serial.println(min_V); break;
      case 'w': Serial.read(); Serial.print("w"); Serial.println(driver.voltage_power_supply); break;
      case 'k': Serial.read(); Serial.print("k"); Serial.println(v_limit); break;
      case 'y': Serial.read(); Serial.print("y"); Serial.println(current_limit_slope); break;
      case 'u': Serial.read(); Serial.print("u"); Serial.println(current_limit_o_term); break;
      case 'e': Serial.read(); Serial.print("e"); if (motor.shaft_angle >= 0){
           Serial.println(motor.shaft_angle, 3);
           }
           if (motor.shaft_angle < 0){
           Serial.println((_2PI-(-1*motor.shaft_angle)), 3);
           }
           break;
  case 'T': break;
  case 'C': break;
  case 'V':  break;
  case 'P':  break;
  case 'B':  break;
  case 'Y':  break;
  case 'U':  break;
  case 'O': break;
  case 'F':  break;
  case 'J':  break;
  case 'W': ;break;
  case 'K': ;break;
  default: Serial.read(); break; //if anything we don't recognize got in the buffer, clear it out or it will mess things up.
       
  }
}
  if (Serial.available() >= 9){
  switch(Serial.read())
  { 
    
  case 'T': goal_speed = Serial.parseFloat();break;
  case 'C': accel = Serial.parseFloat();break;
  case 'V': v_diff = Serial.parseFloat(); break;
  case 'P': v_per_radsPS = Serial.parseFloat(); break;
  case 'K': v_limit = Serial.parseFloat(); break;
  case 'B': accel_v_boost = Serial.parseFloat(); break;
  case 'Y': current_limit_slope = Serial.parseFloat(); break;
  case 'U': current_limit_o_term = Serial.parseFloat(); break;
  case 'O': 
    maybe_o = Serial.parseFloat(); // just in case the wrong data gets in somehow we don't want the voltage going crazy
    if (maybe_o < 1){
      voltage_override = 0;
      } 
    if (maybe_o >= 0.999){ 
      voltage_override = 1;
    }
    break;// if it's not one of these, ignore it.
  case 'F': power_coeff = Serial.parseFloat();break;

  
  }
  }
}
void overcurrent_trip(){// if it stalls this won't help except at higher powers, probably. Just helps prevent disaster
  float current_cap = current_limit_o_term + fabs(motor.target)*current_limit_slope;
  if (get_mA() > current_cap){
    voltage_override = 0;
  }
}
float track_return_motor_timing(float v_pA, float v_pB, float current_A, float current_B, float current_C, int outputorno) {
    // Algorithm based on paper: Sensorless Control of Surface-Mount Permanent-Magnet Synchronous Motors Based on a Nonlinear Observer
    // http://cas.ensmp.fr/~praly/Telechargement/Journaux/2010-IEEE_TPEL-Lee-Hong-Nam-Ortega-Praly-Astolfi.pdf
    // In particular, equation 8 (and by extension eqn 4 and 6).
    // The V_alpha_beta applied immedietly prior to the current measurement associated with this cycle
    // is the one computed two cycles ago. To get the correct measurement, it was stored twice:
    // once by final_v_alpha/final_v_beta in the current control reporting, and once by V_alpha_beta_memory.
    // don't engage the system by calling this function until a suitable RPM is achieved or yu will just get nonsense results
    //the basis of the code here is just dumbly adapted from the odrive implementation, with the removal of unnecessary features like the pll for speed tracking.
    static float V_alpha_beta_memory_[] = {0.0f,0.0f};
    static float I_last_alpha_beta[] = {0.0f,0.0f};
    static float last_etas[]= {0.0f,0.0f};
    static float flux_state_[] = {0.0f,0.0f};
    static float last_v_alpha = 0.0f;
    static float last_v_beta = 0.0f;
    static float last_phase = 0.0f;
    static float observer_gain = 750.0f; //1500 seems to be about right, 3000 is too high? not clear actually. phase becomes nan somewhere between 10,000 and 15,000 could still be way too low. IDK what a reasonable starting point is, 1000 is what odrive uses. They use 2500 and 8000 in the pdf doc.  Too high and it will bounce around, too low and it will take a long time to catch up with changes.
    static float O_phase_resistance = 2.9f;// in ohms *presumably
    static float O_phase_inductance = 0.0006455f;  // in henries *presumably
    static float O_flux_linkage = 0.004;// it's in units of webers *presumably, I measured it across the terminals and calculated it with the python program. let's try half what it was (was 0.00931).:
    //import math
    //print (math.pi)
//peak_to_peak_voltage = 2.68 
//freq = 26.45
//pole_pairs = 7
//RPS = freq/pole_pairs
//RPM = RPS*60
//kv_rating = RPM/peak_to_peak_voltage
//industry_standard_kv_rating = kv_rating/0.95
//
//flux_linkage = 60/(math.sqrt(3) * math.pi * kv_rating * pole_pairs*2) # should be in webers
//print ("flux linkage:", flux_linkage)
//#is this the flux linkage of the motor or each phase?  Because they may not be the same.
    static unsigned long last_run_ts = micros();
    unsigned long mcs_since_last_run = ticks_diff(micros(),last_run_ts) ;
    float s_since_last_run = float(mcs_since_last_run)/1000000.0f ;
    float current_meas_period = s_since_last_run ;
    static float one_by_sqrt3 = 0.5773502691f;
       if (outputorno == 1){
//      Serial.print("c:");
//      Serial.print(I_last_alpha_beta[0]);
//      Serial.print(", v_pA:");
//      Serial.print(last_v_alpha);
      Serial.print("mt:");
      Serial.println(handle_wraparound_angle(last_phase, atan2(I_last_alpha_beta[0],I_last_alpha_beta[1]), return_sign(motor.target)));
      return false;
    }
    if ((current_meas_period * 1000 > 1.0f)) { // what should the coefficient be here? Just run it really fast for now. you can't because you have to be able to debug it.  well ten times per oscillation should be fine, 60 rads per second test speed is 67 hz.  so at least every 1/670 seconds
      flux_state_[0] = 0.0f;
      flux_state_[1] = 0.0f;
      V_alpha_beta_memory_[0] = 0.0f;
      V_alpha_beta_memory_[1] = 0.0f;
     Serial.print("cmp too long:");
     Serial.println(current_meas_period*1000000);
     last_run_ts = micros();
      return false;
    }
            if (get_mA()<70){ //assume measurement is invalid
      current_A = 0.0f;
      current_B = 0.0f;
      flux_state_[0] = 0.0f;
      flux_state_[1] = 0.0f;
      V_alpha_beta_memory_[0] = 0.0f;
      V_alpha_beta_memory_[1] = 0.0f;
     //Serial.print("current too low:");
     //Serial.println(get_mA());
     last_run_ts = micros();
      return false;
        }
    float I_alpha_beta[2] = {current_A, one_by_sqrt3 * (current_B -current_C)};

    // alpha-beta vector operations
    float eta[2] = {0.0f,0.0f};

    for (int i = 0; i <= 1; ++i) {
        // y is the total flux-driving voltage (see paper eqn 4)
        float y = -O_phase_resistance * I_alpha_beta[i] + V_alpha_beta_memory_[i];
        // flux dynamics (prediction)
        float x_dot = y;
        // integrate prediction to current timestep
        flux_state_[i] += x_dot * current_meas_period;
        // eta is the estimated permanent magnet flux (see paper eqn 6)
        eta[i] = flux_state_[i] - O_phase_inductance * I_alpha_beta[i];
        }

    // Non-linear observer (see paper eqn 8):
    float pm_flux_sqr = O_flux_linkage * O_flux_linkage;

    float est_pm_flux_sqr = (eta[0] * eta[0]) + (eta[1] * eta[1]);
    float bandwidth_factor = 1 / pm_flux_sqr;
    float eta_factor = 0.5f * (observer_gain * bandwidth_factor) * (pm_flux_sqr - est_pm_flux_sqr);
    
    // alpha-beta vector operations
    for (int i = 0; i <= 1; ++i) {
        // add observer action to flux estimate dynamics
        float x_dot = eta_factor * eta[i];
        // convert action to discrete-time
        flux_state_[i] += x_dot * current_meas_period;
        // update new eta
        eta[i] = flux_state_[i] - O_phase_inductance * I_alpha_beta[i];
    }
    // Flux state estimation done, store V_alpha_beta for next timestep
    V_alpha_beta_memory_[0] = last_v_alpha; // still have to figure out exactly what voltage this is so we can sub in the right one
    V_alpha_beta_memory_[1] = last_v_beta; // same here
    last_v_alpha = v_pA;
    last_v_beta = v_pB;
    last_run_ts = micros();
    last_etas[0] = eta[0];
    last_etas[1] = eta[1];
    I_last_alpha_beta[0] = I_alpha_beta[0];
    I_last_alpha_beta[1] = I_alpha_beta[1];
    float phase = atan2(eta[1], eta[0]); // do we have fast_atan available? no
    last_phase = phase;
    float stator_field_angle = atan2(I_alpha_beta[0],I_alpha_beta[1]); // these might be the wrong way around fuck. This is the most likely.
    float motor_timing = handle_wraparound_angle(phase, stator_field_angle, return_sign(motor.target));

    if (outputorno == 0){
      return motor_timing; 
    }
};

float handle_wraparound_angle(float angle_behind, float angle_ahead, int dir){
    static float piepie = 6.2831853f;
    if (dir == 1){  //clockwise drive
      if (angle_behind < angle_ahead){ // they are in the same cycle
        return (angle_ahead-angle_behind);
      }
      if (angle_behind > angle_ahead){ // the angle_ahead has gone past the end and wrapped around
        return (piepie-(angle_behind-angle_ahead));
      }
    }
      if (dir == 0){  //counter-clockwise drive
      if (angle_behind > angle_ahead){ // they are in the same cycle
        return (angle_ahead-angle_behind);
        }
      if (angle_behind < angle_ahead){ // the angle_ahead has gone past the end and wrapped around
        return ((-1*piepie)-(angle_behind-angle_ahead));
        }
      }
}
unsigned long int ticks_diff(unsigned long int t2,unsigned long int t1){ //t2 should be after t1, this is for calculating clock times.
  if (t2<t1){//t2 must have wrapped around after t1 was taken
     return (4294967295-(t1-t2));
  }
     return (t2-t1);
} 
float get_mA(){// this is the estimated current being drawn from the power supply, not the actual motor current which is a bit different
   float x =0;
   x = currentlf_now*motor.voltage_limit/24;
   return  1000*((4.0384440932900223e-002)+3.4514090071108776e-002*x*30);// this is off by like 12 percent in some cases a polynomial of third order fits the data better but might flake out at higher than 500 mA so I didn't try it.
}
void setup() {
  Serial.begin(1000000);
  Serial.println("test serial2");
  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 24;
  driver.init();
  // link the motor and the driver
  motor.linkDriver(&driver);
  currentSense.linkDriver(&driver);
  currentSense.init();
  currentSense.skip_align = true;
  FOCModulationType::SinePWM;
  motor.voltage_limit = 1;   // [V]
  motor.velocity_limit = 300; // [rad/s]
  motor.controller = MotionControlType::velocity_openloop;
  motor.init();
  motor.voltage_limit = 2;
  goal_speed = 2;
}

float run_observer(){
     current1 = currentSense.getPhaseCurrents();
     A = current1.a;
     B = current1.b;
     C = current1.c;
     Va = driver.dc_a * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     Vb = driver.dc_b * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     return track_return_motor_timing(Va, Vb, A, B, C, 0);
}
float run_observer_with_output(){
     current1 = currentSense.getPhaseCurrents();
     A = current1.a;
     B = current1.b;
     C = current1.c;
     Va = driver.dc_a * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     Vb = driver.dc_b * driver.voltage_power_supply-(driver.voltage_power_supply/2);
     return track_return_motor_timing(Va, Vb, A, B, C, 1);
}
int return_sign(float number){
  if (number > 0){
    return 1;
  }
  if (number <= 0){
    return 0;
  }
}
void loop() {
for (int i=0;i<50;i++){
     static unsigned long int loop_clock_in = micros();
     unsigned long int loop_time = 0;
     float loop_time_s = 0;     
     loop_time = ticks_diff(micros(), loop_clock_in);
     loop_clock_in=micros();
     loop_time_s = float(loop_time)/1000000;
     if (motor.target < goal_speed-(accel*loop_time_s*1.5)){//rps not positive enough
           if (motor.target < 0){//counterclockwise rotation, deaccelerating
      motor.target = motor.target+accel*loop_time_s*0.7;
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS)));
     }

          if (motor.target >= 0){ //clockwise rotation, accelerating
      motor.target = motor.target+accel*loop_time_s;
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS)));
     }
     }
     

     
     if (motor.target>=goal_speed-(accel*loop_time_s*1.5)){//steady run phase
      if (motor.target<=goal_speed+(accel*loop_time_s*1.5)){ 
      prop_V = (v_diff+fabs((motor.target*v_per_radsPS))); //constant run
      }
     }
     
     
     if (motor.target > goal_speed + (accel*loop_time_s*1.5)){ //rps too positive, the (accel*loop_time_s etc stuff is to give a hysterises
           if (motor.target > 0){ //clockwise rotation, deaccelerating
            motor.target = motor.target-accel*loop_time_s*0.7; 
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS)));
     } 
          if (motor.target <= 0){
      motor.target = motor.target-accel*loop_time_s; //counterclockwise rotation, accelerating
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS)));
     }

     }
     if (prop_V < min_V){
      motor.voltage_limit = min_V*voltage_override;
     }
     else {
      motor.voltage_limit = prop_V;
     }
          if (prop_V > v_limit){
      motor.voltage_limit = v_limit;
     }
     motor.move();
     motortiming = run_observer();
     
}
     SerialComm();
     run_observer_with_output();
     currentlf_now = currentSense.getDCCurrent();
     currentlf_now = diff_filter(currentlf_now);// this updates the current for the get dc current, that function won't work unless this is called each loop
     overcurrent_trip();
}


that’s at 60 rads/second

Also the “phase” i.e. the green line, should not be a smooth wave like that, it should go up (or down) to 2 pi and then restart. It’s supposed to be the position of hte system within the cycle. Blue is the position of the current waveform, red is the position of the voltage. Interesting to see how the blue is displaced to the left, as we would expect. It is delayed in time, as inertia would cause.

When I set the flux_linkage to 0.0007, which should be way too low, I get waveforms that look like this at 60 rads per second (printing every 5th loop now, so I can see the wave better):

The first is with a power supply voltage of 24 volts. The second is 21.5 volts. If you look closely, the peak of the green line is noticeably further away from the peak of the blue line. If I could just measure that distance, I may be able to get a once per cycle measure of motor timing that is normative, at least. But it might not scale with RPM etc. suitably, that would suck. But it appears to be information, which is better than it was. Still, pretty crude and hacky.

I think the motor might have salience. That could explain what’s up. It thinks the flux is changing in a way that is not correct because more or less voltage is being induced, depending on the cycle. It is a ring magnet, it’s a ceramic ring, not a bunch of flat plates, and the back voltage appears to be a nice sine wave.

Ok, I was thinking, all I need is an index for the cycle start of the permanent magnet flux. I don’t really need anything else. I also don’t really need the cycle start point for the current, just the voltage really. The cycle period is of course almost always the same as the period of the voltage wave. I don’t really need to know the motor timing at any given instant, I just need a nice averaged out figure once every few cycles. If the green line was reading fully properly to reflect the angle of the rotor, it would be a straight, slanted line. Well if I know the starting point and period, that’s the same information.

So basically as long as the observer is giving me the start point of the cycle for the flux passing through the coils, that’s all I really need, although it would be a more responsive system if I could know the motor timing at any given moment, that’s not really needed here.

I think I will just get some kind of micros() timestamp for the start of the voltage cycle, and then have a thing in the observer which watches to determine when the flux cycle starts/ends, and give it a micros timestamp.

Then I can just handle the various possible wraparounds, and subtract one from the other, then divide that value by the total period, and that should give me a value from 0 to 1. 0 would be no load on the motor, and 0.25 would I think be close to the point of maximal torque. Just filter that value a little with a low pass filter, and use a PI control loop to adjust the voltage to regulate that value, basically.

I think that should work. Then this whole thing can work with motors that have salience, pretty crude. The one major issue that could arise is if the value is not correlated constantly with motor timing across voltages and rpm. I think it should be though. The flux observer is getting confused in the middle regions, but the indication for when the cycle starts and ends should be consistent, I think.

Nah, just two systems, nearly the same, to watch the alpha current and eta[0] values, and just mark the right side, after the peak, when we are sure it’s on it’s way down. No need for atan2, even, I think. All I need is waveform indexes.

Update: I guess this is kind of like having just a single digital hall sensor or something.

1 Like

Can anyone see a serious issue with that before I huff and puff for 5 hours to try it?:slight_smile:

I’m planning on trying this next:
print the eta[0] and he current for phase A, monitor them in serial plotter

Make sure they look sensible

Then make a thing which detects the falling edge, one for each. Just check to see if the waveform is dropping, and if it changes from rising to falling, drop a time stamp.

This is the index point on the waveform.

Calculate difference between micros timestamps and as a function of period time.