Help request with hub motor and encoder polespairs\cpr mismatch error

I am using https://skysedge.com/robotics/robowheel170/RoboWheel170_Datasheet.pdf a white label alibaba motor that has an encoder included. I have successfully gotten through odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION by following the hover board guide, only needing to change the calibration current odrv0.axis0.motor.config.calibration_current = 2 and the odrv0.axis0.motor.config.requested_current_range = 45. In all honesty I don’t know why those numbers worked but using the odrv0.axis0.motor.config.requested_current_range = 25 resulted in a bunch of errors.

Anyways when I don’t see any errors in dump_errors, I move on to configuring the encoder using:

odrv0.axis0.encoder.config.mode = ENCODER_MODE_INCREMENTAL
odrv0.axis0.encoder.config.cpr = 12800  # 3200 ppr x 4 also tried 3200
odrv0.axis0.encoder.config.bandwidth = 100

but I keep getting ENCODER_ERROR_CPR_POLEPAIRS_MISMATCH after odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION. The motor only moves forward before stopping. Then can see the error in dump_errors. I have tried multiple permutations of saving config, rebooting, erasing config, rebooting again, etc. The shadow_count does show 3200 on a full manual revolution of the motor. Can anyone help me sort this out?

The exact steps I am following after erasing configuration are:

odrv0.config.enable_brake_resistor = True  # Using included 2ohm resistor
odrv0.axis0.motor.config.calibration_current = 4 # highest integer value where I heard the square wave
odrv0.axis0.motor.config.pole_pairs = 27
odrv0.axis0.motor.config.resistance_calib_max_voltage = 6  # motor config still worked with this
odrv0.axis0.motor.config.requested_current_range = 45  # 25 didn't work from the motor guide
odrv0.axis0.motor.config.current_control_bandwidth = 100.0
odrv0.axis0.motor.config.torque_constant = 8.27 / 10  # KV 10

# encoder
odrv0.axis0.encoder.config.mode = ENCODER_MODE_INCREMENTAL
odrv0.axis0.encoder.config.cpr = 3200
odrv0.axis0.encoder.config.bandwidth = 100

odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
odrv0.axis0.controller.config.pos_gain = 1
odrv0.axis0.controller.config.vel_gain = 0.02 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
odrv0.axis0.controller.config.vel_integrator_gain = 0.1 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
odrv0.axis0.controller.config.vel_limit = 10

# BREAK HERE
odrv0.save_configuration()
odrv0.reboot()

odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION

dump_errors(odrv0) 

odrv0.axis0.motor.config.pre_calibrated = True

# BREAK HERE
odrv0.save_configuration()
odrv0.reboot()


# Encoder 
odrv0.axis0.encoder.config.calib_range = 0.05
odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION

You’ve probably fixed this by now, but my recommendation would be to see if you could get it working with just the hall sensor instead of the encoder. The motor datasheet shows the green, blue, and yellow wires should do it.

This is totally just a side quest, but it might help you keep moving until somebody smarter comes along and actually can help you. I’m not sure what you’re trying to make, but a cpr of 81 isn’t terrible for a wheel driving around.

Thanks @BenM unfortunately I have not fixed it yet. If I do I will post a solution here too for future readers.

Side quest is a good way to put it. I think it may be the route I go to unblock myself. At 27 pole pairs * 6 phase states I should have a CPR of 162 though right?

I am building a differential drive robot powered by a Jetson AGX. I have a zed depth camera on loan and have compiled for aarch64 libs/deps for the Nvidia Nemo toolkit. I describe the project as an autonomous Amazon Alexa on wheels, but really I am doing it so my daughters have a voice operated robot buddy as they grow up. Even trying to build a deepfake of my own voice for the text to speech model, but that is another side quest.

1 Like

Yep, good catch. Multiple of 6 for pole pairs and encoder. If you end up with an ILLEGAL_HALL_STATE error, you might need to filter the signal with some caps from the signal wires to ground. Just a thing us non-enoder plebs run into sometimes, but it’s been documented around.

Good luck!

Hi @androiddrew
I just bought a set of Robowheels as well and struggling with them. Did you able to run then using the Odrive controller? Can you please share your config here?

@expert-bot

I am pretty sure this is what I used. I did not go through the tuning operation yet. There is some humming of the motor and sometimes it gets a little vibration, but it’s not too bad so I have just been running it as is on my robot. In all honesty, I don’t really know how to do the tuning operation. If you can in return write up the steps you followed that would be really helpful to me :slight_smile:

The most critical thing I found was that the pole pairs posted on the Skysedge docs were wrong. It never worked with 27 pole pairs. I didn’t want to tear down my motor so I just tossed in 15 pole pairs and BAM! it worked. I honestly don’t know what some of the values like resistance_calib_max_voltage do. I just did multiple iterations over and over again. If you know better values to use please let me know.

I have a micropython program that can use an ESP32 and a Bluetooth app on my iPhone to control it. I’ll try and clean that up this weekend and post the project here for you or anyone else who comes across it. Good luck!

Extra Note: This setup used the encoder pins, not the hall sensor pins. Pretty sure since this encoder doesn’t have an index pin we will always need to run the encoder setup once it powers on.

odrv0.config.enable_brake_resistor = True  # Using included 2ohm resistor
odrv0.axis0.motor.config.calibration_current = 2 # highest integer value where I heard the square wave was 4
odrv0.axis0.motor.config.pole_pairs = 15
odrv0.axis0.motor.config.resistance_calib_max_voltage = 6  # motor config still worked with this set to 6
odrv0.axis0.motor.config.requested_current_range = 35   # 35 was the lowest value that produced and audible square wave.
odrv0.axis0.motor.config.current_control_bandwidth = 100.0
odrv0.axis0.motor.config.torque_constant = 8.27 / 10  # KV 10
odrv0.axis1.motor.config.calibration_current = 2 # highest integer value where I heard the square wave was 4
odrv0.axis1.motor.config.pole_pairs = 15
odrv0.axis1.motor.config.resistance_calib_max_voltage = 6  # motor config still worked with this set to 6
odrv0.axis1.motor.config.requested_current_range = 35   # 35 was the lowest value that produced and audible square wave.
odrv0.axis1.motor.config.current_control_bandwidth = 100.0
odrv0.axis1.motor.config.torque_constant = 8.27 / 10  # KV 10

# encoder
odrv0.axis0.encoder.config.mode = ENCODER_MODE_INCREMENTAL
odrv0.axis0.encoder.config.cpr = 3200
odrv0.axis0.encoder.config.bandwidth = 1000
odrv0.axis1.encoder.config.mode = ENCODER_MODE_INCREMENTAL
odrv0.axis1.encoder.config.cpr = 3200
odrv0.axis1.encoder.config.bandwidth = 1000

odrv0.axis0.controller.config.input_mode = INPUT_MODE_VEL_RAMP
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
odrv0.axis0.controller.config.pos_gain = 1
odrv0.axis0.controller.config.vel_gain = 0.002 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
odrv0.axis0.controller.config.vel_integrator_gain = 0.01 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
odrv0.axis0.controller.config.vel_limit = 5
odrv0.axis1.controller.config.input_mode = INPUT_MODE_VEL_RAMP
odrv0.axis1.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
odrv0.axis1.controller.config.pos_gain = 1
odrv0.axis1.controller.config.vel_gain = 0.002 * odrv0.axis1.motor.config.torque_constant * odrv0.axis1.encoder.config.cpr
odrv0.axis1.controller.config.vel_integrator_gain = 0.01 * odrv0.axis1.motor.config.torque_constant * odrv0.axis1.encoder.config.cpr
odrv0.axis1.controller.config.vel_limit = 5

# BREAK HERE
odrv0.save_configuration()
odrv0.reboot()

odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION

dump_errors(odrv0) 

odrv0.axis1.requested_state = AXIS_STATE_MOTOR_CALIBRATION

odrv0.axis0.motor.config.pre_calibrated = True
odrv0.axis1.motor.config.pre_calibrated = True

# BREAK HERE
odrv0.save_configuration()
odrv0.reboot()


# Encoder 
odrv0.axis0.encoder.config.calib_range = 0.05
odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
odrv0.axis1.encoder.config.calib_range = 0.05
odrv0.axis1.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION

# Movement
odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
odrv0.axis1.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
odrv0.axis0.controller.input_vel = 0.5
odrv0.axis1.controller.input_vel = 0.5
# Your motor should spin here
odrv0.axis0.controller.input_vel = 0
odrv0.axis1.controller.input_vel = 0
odrv0.axis0.requested_state = AXIS_STATE_IDLE
odrv0.axis1.requested_state = AXIS_STATE_IDLE
1 Like

They are the same pins… For a Hall-sensored motor you need to connect halls U,V,W to the encoder input on A,B,Z. Order and polarity shouldn’t matter - the ODrive should be able to figure that out on its own.

Please tell me that you used all three inputs, you didn’t use just two of the Halls as a quadrature encoder input with ENCODER_MODE_INCREMENTAL, because that would explain why you get a different pole_pairs, and it would also severely affect control performance - in fact it would be amazing if this works at all :joy:

Edit: I notice the Sky’s Edge motors specifically have quadrature encoders, not Hall sensors like most motors of this kind. So using with ENCODER_MODE_INCREMENTAL is fine. :slight_smile:

@towen

This motor has both an encoder and hall effect sensor. I started using the hall effect sensor then decided that I’d rather have the increased resolution of the encoder. It works really well.

2 Likes

androiddrew

I am using 27 pole pairs on my RW170 wheels with Hall sensors, but one of my motors will quit.
Would like to use the encoder but am at a lost as to how to set up the encoder wires.
Would you be willing to share your wiring set up?

Thank you!

1 Like

@andyinyakima

On the odrive silk screen you will see the labels for the GPIO headers

Red wire -> 5v
Black wire -> GND
White wire -> A
Grey wire -> B
1 Like

Thanks androiddrew

Will give this a shot

1 Like

Hi androiddrew

Thought I would let you know that your set up worked. Thanks.

The one problem I encountered was with the code for

odrv0.axis0.encoder.config.calib_range = 0.05
odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
odrv0.axis1.encoder.config.calib_range = 0.05
odrv0.axis1.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION

Wanted to incorporate code into my control program which uses ASCII protocol.
I eliminated odrv0.axis0.encoder.config.calib_range = 0.05 and odrv0.axis1.encoder.config.calib_range = 0.05

and using ASCII protocol

    odrv_0->write("w axis0.requested_state 7\n");
    odrv_0->write("w axis1.requested_state 7\n");

This worked when I had the wheels elevated or on level surface with out impediment. The wheels need to free wheel or it will go into destructive behavior.

I see you are using Jetson AGX, I am using Jetson Nano B01 with production module that uses the emmc chip versus SD card. I will have to take a look at the specs on the AGX and see if I can improve the latency on my camera.

Thanks again for you configuration procedure.

2 Likes