Odrive Scooter Hub Motor Tuning

Hi All

I have just gotten my ODrives working with my scooter hub motors after having to flash them with an STLink to get them up to the latest firmware.

I can calibrate the motor and hall effect encoder and control the speed of the motor in closed loop mode.

What I am not understanding is why at lower speeds even when I stall out the motor the current and torque stays low. When running at a low speed at 30V input the current will drop from 130mA to 100mA when I stall the motor. This is not what I would expect for a closed loop motor controller. At higher speed when I try to stall the motor the current drain will raise to over 3A (my power supply’s limit).

My motors are these ones:
https://www.aliexpress.com/item/4-inch-double-shaft-brushless-electric-scooter-hub-motor-phub-29a/32523583494.html

I have tried to figure out the poles and encoder settings, but I am not confident I have them right. My suspicion is that this has something to do with my problem.

Advice would be appreciated.

Details:

In [82]: odrv0.axis1.config
Out[82]:
startup_motor_calibration = False (bool)
startup_encoder_index_search = False (bool)
startup_encoder_offset_calibration = False (bool)
startup_closed_loop_control = True (bool)
startup_sensorless_control = False (bool)
enable_step_dir = False (bool)
counts_per_step = 2.0 (float)
ramp_up_time = 0.4000000059604645 (float)
ramp_up_distance = 12.566370964050293 (float)
spin_up_current = 10.0 (float)
spin_up_acceleration = 400.0 (float)
spin_up_target_vel = 400.0 (float)

In [83]: odrv0.axis1.motor
Out[83]:
error = 0x0000 (int)
armed_state = 3 (int)
is_calibrated = True (bool)
current_meas_phB = 0.2699102759361267 (float)
current_meas_phC = 0.29183363914489746 (float)
DC_calib_phB = -0.5921880602836609 (float)
DC_calib_phC = -0.5132480263710022 (float)
phase_current_rev_gain = 0.012500000186264515 (float)
current_control:
  p_gain = 0.021729597821831703 (float)
  i_gain = 20.42594337463379 (float)
  v_current_control_integral_d = 0.006775654852390289 (float)
  v_current_control_integral_q = 1.9460315704345703 (float)
  Ibus = 0.05689043551683426 (float)
  final_v_alpha = 1.9309014081954956 (float)
  final_v_beta = 0.03602290526032448 (float)
  Iq_setpoint = 0.3829110264778137 (float)
  Iq_measured = 0.43184879422187805 (float)
  max_allowed_current = 35.999996185302734 (float)
gate_driver:
  drv_fault = 0 (int)
timing_log:
  TIMING_LOG_GENERAL = 0 (int)
  TIMING_LOG_ADC_CB_I = 793 (int)
  TIMING_LOG_ADC_CB_DC = 11301 (int)
  TIMING_LOG_MEAS_R = 0 (int)
  TIMING_LOG_MEAS_L = 0 (int)
  TIMING_LOG_ENC_CALIB = 0 (int)
  TIMING_LOG_IDX_SEARCH = 0 (int)
  TIMING_LOG_FOC_VOLTAGE = 0 (int)
  TIMING_LOG_FOC_CURRENT = 4751 (int)
config:
  pre_calibrated = True (bool)
  pole_pairs = 20 (int)
  calibration_current = 10.0 (float)
  resistance_calib_max_voltage = 4.0 (float)
  phase_inductance = 0.00021729597938247025 (float)
  phase_resistance = 0.20425942540168762 (float)
  direction = 1 (int)
  motor_type = 0 (int)
  current_lim = 10.0 (float)
  requested_current_range = 25.0 (float)
  current_control_bandwidth = 100.0 (float)
set_current_control_bandwidth(current_control_bandwidth: float)
In [85]: odrv0.axis1.encoder
Out[85]:
error = 0x0000 (int)
is_ready = True (bool)
index_found = False (bool)
shadow_count = 96759 (int)
count_in_cpr = 39 (int)
offset = 142 (int)
interpolation = 0.5380810499191284 (float)
phase = -0.9696226119995117 (float)
pos_estimate = 96759.875 (float)
pos_cpr = 39.81002426147461 (float)
hall_state = 6 (int)
pll_vel = 103.1333236694336 (float)
config:
  mode = 1 (int)
  use_index = False (bool)
  pre_calibrated = True (bool)
  idx_search_speed = 10.0 (float)
  cpr = 120 (int)
  offset = 142 (int)
  offset_float = 0.5390880703926086 (float)
  bandwidth = 100.0 (float)
  calib_range = 0.019999999552965164 (float)
1 Like

Success! I slept on it and had another go at it.

I noticed that if you hold the motor stalled the current does slowly ramp up over 10 or 20 seconds and the motor gradually puts out more torque.

So I have now been playing with the motor tuning parameters, and have had quite a bit of success. I ended up having to raise the integrator gain a lot to get the torque to ramp up quickly. The motor is now now has quite a lot of torque even at low speeds.

It is quite shaky at speeds below a vel_setpoint = 30 or is it just cogging?

I will include my new settings below.

One further question I have is:

At higher speeds when I load down the motor too much the odrive cuts the power to motor and I have to reboot it before it will work again. I suspect I am hitting a current limit cutout, or it is caused by overloading my power supply. How do I confirm why it is stopping?

Thanks
Martin

In [203]: odrv0.axis1.controller
Out[203]:
pos_setpoint = 0.0 (float)
vel_setpoint = 30.0 (float)
vel_integrator_current = 0.7711848616600037 (float)
current_setpoint = 0.0 (float)
config:
  control_mode = 2 (int)
  pos_gain = 1.0 (float)
  vel_gain = 0.05000000074505806 (float)
  vel_integrator_gain = 1.0 (float)
  vel_limit = 1000.0 (float)
set_pos_setpoint(pos_setpoint: float, vel_feed_forward: float, current_feed_forward: float)
set_vel_setpoint(vel_setpoint: float, current_feed_forward: float)
set_current_setpoint(current_setpoint: float)
start_anticogging_calibration()


In [215]: odrv0.axis1.motor
Out[215]:
error = 0x0000 (int)
armed_state = 3 (int)
is_calibrated = True (bool)
current_meas_phB = -0.41446298360824585 (float)
current_meas_phC = -0.0694076418876648 (float)
DC_calib_phB = -0.5928037762641907 (float)
DC_calib_phC = -0.5348424911499023 (float)
phase_current_rev_gain = 0.012500000186264515 (float)
current_control:
  p_gain = 0.010864798910915852 (float)
  i_gain = 10.212971687316895 (float)
  v_current_control_integral_d = -0.029702063649892807 (float)
  v_current_control_integral_q = 0.7134361863136292 (float)
  Ibus = 0.0052841659635305405 (float)
  final_v_alpha = -0.5647595524787903 (float)
  final_v_beta = 0.15072482824325562 (float)
  Iq_setpoint = -0.19505095481872559 (float)
  Iq_measured = 0.5880304574966431 (float)
  max_allowed_current = 35.999996185302734 (float)
gate_driver:
  drv_fault = 0 (int)
timing_log:
  TIMING_LOG_GENERAL = 0 (int)
  TIMING_LOG_ADC_CB_I = 721 (int)
  TIMING_LOG_ADC_CB_DC = 11253 (int)
  TIMING_LOG_MEAS_R = 0 (int)
  TIMING_LOG_MEAS_L = 0 (int)
  TIMING_LOG_ENC_CALIB = 0 (int)
  TIMING_LOG_IDX_SEARCH = 0 (int)
  TIMING_LOG_FOC_VOLTAGE = 0 (int)
  TIMING_LOG_FOC_CURRENT = 4729 (int)
config:
  pre_calibrated = True (bool)
  pole_pairs = 20 (int)
  calibration_current = 10.0 (float)
  resistance_calib_max_voltage = 4.0 (float)
  phase_inductance = 0.00021729597938247025 (float)
  phase_resistance = 0.20425942540168762 (float)
  direction = 1 (int)
  motor_type = 0 (int)
  current_lim = 20.0 (float)
  requested_current_range = 30.0 (float)
  current_control_bandwidth = 50.0 (float)
set_current_control_bandwidth(current_control_bandwidth: float)


In [216]: odrv0.axis1.encoder
Out[216]:
error = 0x0000 (int)
is_ready = True (bool)
index_found = False (bool)
shadow_count = 1693 (int)
count_in_cpr = 13 (int)
offset = 142 (int)
interpolation = 0.11777421087026596 (float)
phase = -2.2950730323791504 (float)
pos_estimate = 1694.420654296875 (float)
pos_cpr = 14.539641380310059 (float)
hall_state = 2 (int)
pll_vel = 72.79999542236328 (float)
config:
  mode = 1 (int)
  use_index = False (bool)
  pre_calibrated = True (bool)
  idx_search_speed = 10.0 (float)
  cpr = 120 (int)
  offset = 142 (int)
  offset_float = 0.5390880703926086 (float)
  bandwidth = 200.0 (float)
  calib_range = 0.019999999552965164 (float)

Hi Martin

Good to hear you have had some success so far. A few questions:

  • What version of odrive do you have (i.e. V3.5)?
  • When your motor cuts out are you able to read off the errors for us?
  • Do you have any more information about the hall effect encoder?

I suspect that your motor may be shaky at low speeds due to the encoders limited resolution but its hard to say without knowing more about it. One way to check could be run Liveplotter from odrivetool with start_liveplotter(lambda: [my_odrive.axis0.encoder.pll_vel]) and watch the velocity plot as your rotate the motor slowly by hand.If your plot follows your speed smoothly then then is not your problem. If your plot jumps periodically (when your encoder updates position) then this may be your issue.

Hi Richard

Thanks for your help.

I have two recent V3.5 48V Odrives. I have not done any testing on the second one yet. I flashed the latest firmware via git as of commit 20b2c8b. I did notice that when I tried to set ENCODER_MODE_HALL it complained it was not defined. I then set it to mode 1 (which I hope is right)

I checked the errors and it is axis error 0x3 which if I am understanding the source means DC bus low. I assume this could be being caused by my power supply? I do intend to run this project off large batteries once I get them.

I don’t know anything about the encoders other than they are presumably same type of hall effect encoders used in most of these hub motors. They have a 5 wire connector that seemed to be wired correctly to plug directly onto the ODrive.

Ok, running at 100 and then 30 it looks like:
Figure_1

turning by hand it looks like:
Figure_1-1

I am not sure if it my bad configuration, or if that is just how bad these hall effect encoders are?

I also have a youtube video of it running at various speeds including one where it is jumpy/stepping.

Any advice would be appreciated.

Thanks
Martin

EDIT
I just looked carefully again, and I think the 0x3 error happens when my 30V 3A bench supply hits 3A and switches from constant voltage to constant current mode.

I have played around with the motor tuning and can get it to run a bit smoother at lower speeds, but only at the cost of control at higher speeds. I think the low speed jerkyness is probably OK for my use, and probably related to the Hall effect encoders.

Another issue I am struggling with is the top speed. For some reason I can’t get it to go over 700RPM. It runs a little faster (maybe 720RPM) if I turn the current limit down to 1 (seems unintuitive). What is stopping the motor going any faster? Settings follow.

Thanks
Martin

In [257]: odrv0.axis1
Out[257]:
error = 0x0000 (int)
enable_step_dir = False (bool)
current_state = 8 (int)
requested_state = 0 (int)
loop_counter = 7257844 (int)
config:
  startup_motor_calibration = False (bool)
  startup_encoder_index_search = False (bool)
  startup_encoder_offset_calibration = False (bool)
  startup_closed_loop_control = True (bool)
  startup_sensorless_control = False (bool)
  enable_step_dir = False (bool)
  counts_per_step = 2.0 (float)
  ramp_up_time = 0.4000000059604645 (float)
  ramp_up_distance = 12.566370964050293 (float)
  spin_up_current = 10.0 (float)
  spin_up_acceleration = 400.0 (float)
  spin_up_target_vel = 400.0 (float)
get_temp()
motor:
  error = 0x0000 (int)
  armed_state = 3 (int)
  is_calibrated = True (bool)
  current_meas_phB = 0.043258726596832275 (float)
  current_meas_phC = -6.1145100593566895 (float)
  DC_calib_phB = -0.6075267791748047 (float)
  DC_calib_phC = -0.5721505880355835 (float)
  phase_current_rev_gain = 0.012500000186264515 (float)
  current_control: ...
  gate_driver: ...
  timing_log: ...
  config: ...
  set_current_control_bandwidth(current_control_bandwidth: float)
controller:
  pos_setpoint = 0.0 (float)
  vel_setpoint = 1000.0 (float)
  vel_integrator_current = 7.006492321624085e-44 (float)
  current_setpoint = 0.0 (float)
  config: ...
  set_pos_setpoint(pos_setpoint: float, vel_feed_forward: float, current_feed_forward: float)
  set_vel_setpoint(vel_setpoint: float, current_feed_forward: float)
  set_current_setpoint(current_setpoint: float)
  start_anticogging_calibration()
encoder:
  error = 0x0000 (int)
  is_ready = True (bool)
  index_found = False (bool)
  shadow_count = 359592 (int)
  count_in_cpr = 73 (int)
  offset = 142 (int)
  interpolation = 0.08597517013549805 (float)
  phase = 2.92978572845459 (float)
  pos_estimate = 359605.90625 (float)
  pos_cpr = 86.7869644165039 (float)
  hall_state = 6 (int)
  pll_vel = 708.5866088867188 (float)
  config: ...
sensorless_estimator:
  error = 0x0000 (int)
  phase = 0.8452032208442688 (float)
  pll_pos = 1.400231122970581 (float)
  pll_vel = 734.8687744140625 (float)
  config: ...

Ok, a further question. Why is my brake resistor getting hot while the motor is running? The faster the motor runs the hotter the brake resistor gets.

My guess is that 700RPM may be the base speed of the motor at 30V? Can you run the same test and give us what you see at odrv0.axis1.motor.current_control? You can do a test where you start with a lower speed, and then keep increasing the speed. look at Iq_setpoint and Iq_measured. If they are close to each other, you are below base speed. If they are significantly different, then the speed you are asking for is unattainable at this bus voltage.

BTW I had a look at the link of your motor, and it’s completely unclear what the base speed (no-load speed) of your motor should be…

Thanks Oskar I will run the test you recommend tonight.

I doubt I am hitting the motor’s top speed, it seems to have plenty of torque at that speed and will ramp up the current to remain there if you try to slow it down. I am using the 24V version of the motor and feeding it 30V and it is only drawing around 40W or so at this speed.

Is the brake resistor getting hot while running a sign of something in particular that I can look into? While running at maximum speed it gets too hot to touch in a few minutes.

I tried running odrv0.axis1.motor.current_control and each time I check the values for Iq_setpoint and Iq_measured seem to jump around randomly. Sometimes they are close and sometimes they are quite different, they also swing between positive and negative values. This behaviour seems to be the same at any speed.

The brake resistor also heats up significantly while running even at constant speeds well below the maximum.

For the hover board motors they are also shaky below 30. Between 20-30 it moves smoothly.
At least with the hover board the resistor does not get warm at all slow speed (100) ,grazy fast 900.
Must say the motor is unloaded.

Your speed PI is not stable. When you run at 100 the speed is between 80 and 110. I can imagine when the speed is around 110 the odrive will try to slow it down to 100 by using the brake resistor, it also undershoot too 80. Energie needs to go somewhere.

I have tried everything I can think of to make the speed more stable. It seems to be something inherent with the Hall Effect sensors, not with he way to motor is tuned.

I am not an expert in brushless motors but I don’t feel it is hitting its base speed at 720RPM. Again I think it is related to the speed instability. Though, when I put it in current control mode it does top out at the same speed. I wonder if the encoder offset calibration can be working correctly if the speed signal is so erratic?

Is there anything I can look at next to try and get this working properly?

Would it help if I try to capture the hall effect signals? I have a cheap logic analyser, but no oscilloscope.

We are using the same size and look hub motor, we have discovered there are lot of very similar looking ones. but outer tyre diameter is 4inch correct?
We found a version with AB encoder internal as well as hall sensors.
I’ll find the seller if you are interested.
MUCH smoother low speed than the hall sensors.

ZLtech- ZLLG40ASM100 in small quantities I purchased them through www.yoycart.com

Note there is only an AB encoder and no Index. We haven’t got to it but going to look at using one hall channel as the index.
I think the encoder version of this motor is made maybe 2 or 3mm wider, I haven’t confirmed because we are using it in a single sided mount configuration so it does not affect us. But the tyre is a little bit free to slide side to side on the rim, so I think the wheel is stretched in the centre to make room for the encoder.

Hi Martin, im getting the same error trying to Set " ```
odrv0.axis0.encoder.config.mode = ENCODER_MODE_HALL
how did you set to mode 1 after this and get the hub motors to rotate at all ? this is where i am stuck . Any help really appreciated.

Damn, I just ordered two more of the motors I am using. I could have tried those instead. They would definitely be an option for anyone else who is trying to do the same thing as I am (this is a drive for a 3D printed R2D2).

I simply ran:
odrv0.axis0.encoder.config.mode = 1

Not sure if this was the right thing to do or if that is the root of the problem I am having, but is seemed to be what ENCODER_MODE_HALL was defined as in tools/odrive/enums.py

I still can’t figure out how to stop my odrive spending half its time trying to slow my motor back down again, hence pumping a significant amount of power into the brake resistor and not reaching the top speed I would expect.

Then you are for sure hitting base speed. Your motor won’t go faster than this: I’d stay at 80% of this speed or lower to make sure that the velocity setpoint is achievable.

Can you show us a plot? I odrivetool run:

start_liveplotter(lambda: [odrv0.axis1.motor.current_control.Iq_setpoint, odrv0.axis1.motor.current_control.Iq_measured])`

You do this by lowering a combination of vel_gain, vel_integrator_gain and encoder.bandwidth. Did you use the recommended values in the hoverboard guide?
Can you paste for us what values are printed for:

  • odrv0.axis1.motor.config
  • odrv0.axis1.encoder.config
  • odrv0.axis1.controller.config

Hi Oskar

Fair enough, it is meant to be the 24V version of the motor, so I am surprised it it so slow even at 30V, but that’s what you get buying off chinese websites. It will be fast enough for my use.


This is running at 400RPM. Over the maximum speed the blue line diverges to the current limit, but the orange line stays bouncing between 2 and -2. This sounds like it confirms your suggestion that this is the motor’s base speed.

I have tried reducing all of these but either run into instability of a lack of torque at lower speeds. Looking at the Iq_setpoint, Iq_measured graph I think that with a physical load on the motor the current will move above the 0 point, and hence dump less/no power into the resistor?

I did start from the hoverboard suggestions, but my motors seemed to have absolutely no torque at low speeds, so I had to tweak things up a bit.

My settings are:

odrv0.axis1.motor.config

pre_calibrated = True (bool)
pole_pairs = 20 (int)
calibration_current = 10.0 (float)
resistance_calib_max_voltage = 4.0 (float)
phase_inductance = 0.00021778082009404898 (float)
phase_resistance = 0.20834951102733612 (float)
direction = 1 (int)
motor_type = 0 (int)
current_lim = 10.0 (float)
requested_current_range = 15.0 (float)
current_control_bandwidth = 100.0 (float)

odrv0.axis1.encoder.config

mode = 1 (int)
use_index = False (bool)
pre_calibrated = True (bool)
idx_search_speed = 10.0 (float)
cpr = 120 (int)
offset = 142 (int)
offset_float = 0.5745813846588135 (float)
bandwidth = 50.0 (float)
calib_range = 0.019999999552965164 (float)

odrv0.axis1.controller.config

control_mode = 2 (int)
pos_gain = 1.0 (float)
vel_gain = 0.019999999552965164 (float)
vel_integrator_gain = 0.20000000298023224 (float)
vel_limit = 1000.0 (float)

Okay looks good overall.
What is the resistance of your brake resistor? What value do you have at odrv0.brake_resistance?

I am using the 2ohm resistor that came with the ODrive and odrv0.config.brake_resistance is set to 2.0

Actually now that I think about it, why should the Iq_measured graph keep bouncing negative even when the setpoint is higher than the max speed of the motor?


This is if I set the speed to 800.

It is dumping a lot of energy into the resistor and it heats up quite quickly to hotter than you can comfortably touch.

In the situation above the speed setpoint versus the actual speed looks like this:

So I don’t understand why it should be trying to slow down at all, but that could be me misunderstanding the fundamentals on how BLDC motors work.