Candas1 / Split_Hoverboard_SimpleFOC

Split Hoverboards with C++ SimpleFOC (not yet)
MIT License
6 stars 5 forks source link

HallSensor interpolation/smoothing/prediction #9

Open RoboDurden opened 11 months ago

RoboDurden commented 11 months ago

continuing from https://github.com/Candas1/Split_Hoverboard_SimpleFOC/issues/6

@Candas1 applying https://github.com/dekutree64/Arduino-FOC-drivers/blob/dev/src/encoders/smoothing/SmoothingSensor.cpp#L11 wrote: The extrapolation seems to work at constant speed at least now.

Nice visualization tool you have: https://user-images.githubusercontent.com/20670049/256005760-0de8c63f-a36a-48b2-a815-bd4770bc186d.png

You may also want to check max speed with this new inline function HallSensor.h :

float getRpm(){return direction * (60000000.0f / pulse_diff) / (cpr); };

and in main.cpp

OUT2T("KV",sensor.getRpm() / driver.voltage_power_supply )

Candas1 commented 11 months ago

[Moving the message here] Some signs were wrong in my case: image

The extrapolation seems to work at constant speed at least now. image

I need to do more tests this evening.

Candas1 commented 11 months ago

I did some tests, it seems the zero_electrical_angle was still off by 0.17 something (should be 1.92 instead of 2.09), it needed to be tweaked. The way the sensor alignment procedure is done in simplefoc, it cannot be precise, it's meant for encoders mainly, so for hall sensors it has to be tweaked by hand. I believe you also played around with this parameter. That's what I meant when I said the hoverboard firmware are already tweaked for hoverboard wheels, those aspects are probably hardcoded or baked into the firmware.

I might have to work on a better sensor alignment for hall sensor. With bad alignment, FOC will probably be bad even if the current sensing is done right.

RoboDurden commented 11 months ago

With my 2.0 board the sensor alignment procedure never did work at all. Only with the Gen1 stm32F103 did it succeed. That is why i never understood how you obtained the 2.09. Yes i did try to optimize your 2.09 but did not find a better value:

unsigned long iOptimize = 0;
float fOptimize = 0;
..
  OUT2T(fOptimize*1000, motor.zero_electric_angle*1000)
  fOptimize = -0.2  * (ABS( (float)((iOptimize++ + 20) % 80) - 40) - 20)/20;
  motor.zero_electric_angle = 2.09 + fOptimize;

I might have to work on a better sensor alignment for hall sensor.

Might be good if you would be more prescise. We have used the word "alignment" in so many contexts by now, so i do not really understand what you mean with "better sensor alignment". Only parameter is this zero_electric_angle. So you want to write a better auto zero_electric_angle detection ?

Candas1 commented 11 months ago

Sensor alignment is what happens here.

This will work well with an encoder, but with a hall sensor it just gives you a hint.

I need to try this as well.

Candas1 commented 11 months ago

the motor is blocking in the other direction if I am using 1.92. I need to investigate more.

Candas1 commented 11 months ago

Ok I think I found the solution, and I have learned more, I summarized it all here I am curious how Eferu compares with those number but I wouldn't be able to check before this weekend. Eferu doesn't have such compensation (or I couldn't find it in the model), probably it's optimized only for some of the speed range.

RoboDurden commented 11 months ago

Nice. I do not really understand a lot of your post over there. But i am working with my Arduino-FOC that i forkerd from your Arduino-FOC. So if you publish your local code changes to your repo, i can sync my fork to test your work with my test setup :-)

(today i worked on a 20A 80V ESP32 OLED mppt charger..)

Candas1 commented 11 months ago

I updated the dev branch.

RoboDurden commented 11 months ago

Now that is a bit confusing (for me). You seem to work on three different branches and the latest _configure6PWM is in main but smoothedSensor in another branch ?

Candas1 commented 11 months ago

I am talking about the dev branch of this repository: image

It uses the dev-gd32 branch of my Arduino-foc fork, and the dev branch of my arduino-foc-drivers fork.

Candas1 commented 11 months ago

I did a pull request to simplefoc dev branch with the direction fix and they accepted it. So I think the smoothing is complete. I will work on improving the autodetect only after the current sensing.

RoboDurden commented 11 months ago

Okay @Candas1 here my test comparison between EFeru and your firmware here on a Gen1 board:

  driver.voltage_power_supply = 26; // 3.6 * BAT_CELLS; // power supply voltage [V]
  motor.voltage_limit = driver.voltage_power_supply * 0.58; // should be half the power supply

STM32 Gen1 board:

bin target  [rpm/V] [A]

smFOC   T5+E    -5.4    0.17
smFOC   T-5+E   +5.3    0.17
EFeru   +390    -5.4    0.25
EFeru   390 +5.4    0.23

smFOC   T15+E   -11.4   0.31
smFOC   T-15+E  +11.1   0.30
EFeru   +760    -11.3   0.38
EFeru   -760    +11.3   0.37

smFOC   T26 -13.9   0.68
smFOC   T-26    +12.9   0.48
EFeru   1000    -14.6   0.50
EFeru   -1000   +15.0   0.51

no SmoothingSensor:

smFOC   T5+E0   -5.5    0.19
smFOC   T-5+E0  +5.3    0.19
smFOC   T15+E0  -11.8   0.49
smFOC   T-15+E0 +11.1   0.38

This inital nasty "push" might be because some class variable is not initialized to 0..

To make the Commander class work nicely with the short uart cable of the gen1 board, it would be nice if you would update your config.h to

// Define to prevent recursive inclusion
#ifndef CONFIG_H
#define CONFIG_H

#if defined(PLATFORMIO)
  #ifdef STM32F103RC
    #define HOVER_GEN   1
    #define HOVER_LAYOUT    0

    // too late here, has to be a build flag #define SIMPLEFOC_PWM_LOWSIDE_ACTIVE_HIGH false

    // call motor.initFOC() without parameters to auto align sensor and copy values from debug log
    #define MOTOR_zero_electric_offset  4.19  
    #define MOTOR_sensor_direction  Direction::CCW

    // log debug output over master/slave uart
    //#define DEBUG_UART  Serial
    HardwareSerial oSerialSteer(PB11, PB10);  // short cable 5VT EFeru USART3 GPIO Configuration
    #define DEBUG_UART oSerialSteer

  #else
    #define HOVER_GEN   2
    #define HOVER_LAYOUT    0

    // call motor.initFOC() without parameters to auto align sensor and copy values from debug log
    #define MOTOR_zero_electric_offset  2.09
    #define MOTOR_sensor_direction  Direction::CCW

    // SEGGER RTT Debug
    #define DEBUG_STLINK rtt              // Uncomment to enable DEBUG over stlink dongle

  #endif
#else
  // LAYOUT_x_y is used in defines.h
  #define HOVER_GEN   2
  #define HOVER_LAYOUT 0
  //  2_0   // https://github.com/flo199213/Hoverboard-Firmware-Hack-Gen2
  //  2_1   // https://github.com/krisstakos/Hoverboard-Firmware-Hack-Gen2.1
  //  2_2   // 2023/05/11 only MASTER and TEST_SPEED: motor is spinning but needs a push to startup :-/
  //  2_4   // NOT READY !!! https://github.com/RoboDurden/Hoverboard-Firmware-Hack-Gen2.x/issues/3
  //  2_5   // NOT READY !!! https://github.com/RoboDurden/Hoverboard-Firmware-Hack-Gen2.x/issues/11

  //  1_0   // old Gen 1 boards with two motors

    // call motor.initFOC() without parameters to auto align sensor and copy values from debug log
    #define MOTOR_zero_electric_offset  2.09
    #define MOTOR_sensor_direction  Direction::CCW

    // SEGGER RTT Debug
    #define DEBUG_STLINK rtt              // Uncomment to enable DEBUG over stlink dongle
    // log debug output over master/slave uart
    //#define DEBUG_UART  Serial2

#endif

#define BLDC_POLE_PAIRS   15    // all hoverboard motors have 15 pole pairs ?
#define BAT_CELLS         7     // battery number of cells. mostly 10 = 10s = 36V. Sometimes 7 = 7s = 25V

#ifdef DEBUG_UART
  #define DEBUG_UART_BAUD   115200    // [-] Baud rate for HoverSerial (used to communicate with the hoverboard)
#endif

#define TIME_SEND           10000         // [ms] Sending time interval

#endif //  CONFIG_H

https://github.com/Candas1/Split_Hoverboard_SimpleFOC/blob/dev/src/main.cpp#L88 would be changed to

Commander command = Commander(SERIALDEBUG); The auto calibration did again not work for me. I got this MOTOR_zero_electric_offset 4.19 from an older version of our code when the auto calibration did work. By now i have the same identical bldc motors installed in the gen1 test setup and the gen2.0 setup.

Lower case comands were more suitable for me:

  command.add('t', doTarget, "target voltage");
  // add smoothing enable/disable command E (send E0 to use hall sensor alone, or E1 to use smoothing)
  command.add('e', enableSmoothing, "enable smoothing");
  command.add('p', doPhaseCorrection, "phase correction");
  command.add('z', doZero, "zero electrical angle");
  command.add('f', doLPF, "LPF");

I also added a low pass to target:

    fSpeed = 0.999 * fSpeed + 0.001 * target;

    //GPIO_BOP(GPIOB) = (uint32_t)GPIO_PIN_7;
    motor.move(fSpeed);
    //GPIO_BC(GPIOB) = (uint32_t)GPIO_PIN_7;

maybe this line is missing your main.cpp:

driver.voltage_limit = motor.voltage_limit; // stupid bug to have two voltage_limit in different places

Candas1 commented 11 months ago

Thank you. Great so SFOC is more efficient, but not as fast.

It could be the inductance value has to be tweaked on different motors. I am using one with 5 coil wires and 30mm magnets. I am thinking about buying a LCR meter to measure it.

I tried the low pass filter class from SFOC some time ago but couldn't get it to work for the target.

No driver and voltage limit are equal by default.

RoboDurden commented 11 months ago

I don't think so:

void BLDCMotor::init() {
...
  // sanity check for the voltage limit configuration
  if(voltage_limit > driver->voltage_limit) voltage_limit =  driver->voltage_limit;

motor::voltage_limit can be smaller than driver::voltage_limit

But i do no longer want to think about that stupidity to have two different voltage_limit and on top both (seemingly to me) being used at random in BLDCMotor.cpp :-(

Candas1 commented 11 months ago

https://github.com/simplefoc/Arduino-FOC/blob/05954cb9691534dd478407505d810a68f1673a5e/src/drivers/BLDCDriver6PWM.cpp#L61

RoboDurden commented 11 months ago
LowPassFilter LPF_target(0.5);  //  the higher the longer new values need to take effect

void loop()
{
...
    motor.move(LPF_target(target));
Candas1 commented 11 months ago

If you uncommented the curent sense, maybe the current sense alignment is running

RoboDurden commented 11 months ago

Your last update to Candas1/Arduino-FOC/tree/dev-gd32 was two weeks ago. So i do not understand what you mean with "... maybe the current sense alignment is running"

RoboDurden commented 11 months ago

I have tested the smoothed sensor with 25V sine (stm Gen1 board): stmstudio smooth Sine25V

    float fTarget = LPF_target(target) * (ABS((float)(((millis()-iLoopStart)/10 + 250) % 1000) - 500) - 250)/250;
    motor.move(fTarget);

    fAnglePred = fmod(smooth.angle_prev * 57.295779513082320876798154814105 + 360.0 , 360.0);
    fAngleReal = fmod(sensor.angle_prev * 57.295779513082320876798154814105 + 360.0 , 360.0);

Looks like i need to also set an optimized phase correction for SmoothingSensor::update():

  // Apply phase correction if needed
  if (phase_correction != 0) {
    if (_motor.shaft_velocity < -0) angle_prev -= _motor.sensor_direction * phase_correction / _motor.pole_pairs;
    else if (_motor.shaft_velocity > 0) angle_prev += _motor.sensor_direction * phase_correction / _motor.pole_pairs;
  }

I am not too happy with this more general SmoothingSensor approach that can wrap any kind of rotation-position sensor as it can not make use of the interrupt driven HallSensor method HallSensor::updateState(). Instead it must rely on the Sensor:.update() function which is called in motor.loopFOC() far more often (every single blue dot in the curve of fAnglePred) than HallSensor::updateState() (the coarse 4° steps of the grey curve fAngleReal). And SmoothingSensor does not implement its own lowpass but cleverly uses _motor.shaft_velocity which has a lowpass:

float FOCMotor::shaftVelocity() {
  // if no sensor linked return previous value ( for open loop )
  if(!sensor) return shaft_velocity;
  return sensor_direction*LPF_velocity(sensor->getVelocity());
}

This method is called in void BLDCMotor::move(float new_target) and therefore motor.move(target) has to be called at the same rate as motor.loopFOC().

I think this is NOT intuitive as people might want to call motor.move only a few times per second to update a new target speed.

So yes, SmoothingSensor is working but all it basically does is adding a low pass to the angle and does a linear prediction

  float dt = (_micros() - angle_prev_ts) * 1e-6f;
  angle_prev += _motor.sensor_direction * _constrain(_motor.shaft_velocity * dt, -_PI_3 / _motor.pole_pairs, _PI_3 / _motor.pole_pairs);

Implement that "two lines" directly into HallSensor might be the better way...

RoboDurden commented 11 months ago

SmoothingSensor.h

    // For hall sensors, the predicted angle is always 0 to 60 electrical degrees ahead of where it would be without
    // smoothing, so offset it backward by 30 degrees (set this to -PI_6) to get the average in phase with the rotor
    float phase_correction = 0;

Setting it to +0.1 already had a bad effect. Setting it to -0.52 = -Pi/6 was okay but does not really help: stmstudio smooth Sine25V with phase correction

RoboDurden commented 11 months ago

here the downside of a low pass: it takes some time the direction change is noticed:

stmstudio smooth Sine25V 0 5Hz with phase correction

Candas1 commented 11 months ago

I already discussed with the contributor, I told him it's not optimal for hall sensors. But that's the best solution we have now and it works most of the time.

EFeru would stop predicting at slow speed or direction change, this is easy to do.

RoboDurden commented 11 months ago

I guess i will spend half a day to test my direct HallSensor impelementation against these SmoothedSensor results here :-)

Candas1 commented 11 months ago

You could have checked already weeks ago. Feel free to talk to him. I don't want to be a proxy between you and him.

RoboDurden commented 11 months ago

Again, i do not want to pose problems when i can not offer solutions.

And i already figured out that linear prediction with a speed average is sufficient. The inaccuracies i showed in the pics above are only at direction changes, so low speed an we do not really have to care for.

It is possible to give SmoothingSensor.h its own speed low pass

    float shaft_velocity;//!< current motor velocity 
    LowPassFilter LPF_velocity{DEF_VEL_FILTER_Tf};//!<  parameter determining the velocity Low pass filter configuration     

but as the speed will not change when motor.move() is not called, it is okay to use the lowpass filtered motor.shaft_velocity which gets updated by motor.move().

And when i only call motor.move at 10 Hz and some heave torque on the motor affects the shaft_velocity we have a lowpass anyway and do not see that when smoothing.

So everything is okay with SmoothingSensor :-)

Candas1 commented 11 months ago

OK then we are aligned. I know it's not a perfect solution but it's good enough for now. When all works we can check what is worth improving.

RoboDurden commented 11 months ago

I think it is already perfect for us. In the beginning i thought that a higher grade polynomial would better predict future speed changes like in my screenshots.. (That is why i did not want to wait for SmoothingSensor...)

But this is only needed at very slow speeds and we do not need to care for that. At normal speed when noise and efficiency is important, the refresh rate is so high that a linear prediction is sufficient. (That is why i stopped with my HallSensor-prediction.)

Now seeing that SmoothedSensor is more silent than my linear prediction, i happily close that chapter.

I guess when you have finished your channel3 timer0 adc, you have successfully ported SFOC to GD32F130.

I would still like to have a callback when adc has finished to trigger motor.loopFOC() every second call :-)