Implementing Anti-Cogging on the ODrive Robotics Controller

This post is a companion post to my YouTube video on how to enable anti-cogging on your ODrive robotics High Performance Motor controller.

You can find the source coee from the video below. Note these are based on my settings from the ODrive D5065 Motor so your settings will vary from mine.

Quick disclaimer: We are playing with high currents and strong motors. take every safety precaution possible. make sure your equipment is secure, and not able to hurt anyone. I take no responsibility for you following this guide, do your own research, check my code and always test carefully.

The code is just below the video

This is my first real YouTube Video! please be nice and Subscribe, Like and Comment to help me along.

ODrive Anti-Cogging Code

# Start the ODrive Tool odrivetool shell #Wipe the configuration odrv0.erase_configuration() #Set up some basic parameters, these work for me on my D5065 odrv0.axis0.motor.config.current_lim = 40 odrv0.axis0.controller.config.vel_limit = 15 odrv0.axis0.motor.config.calibration_current = 20 odrv0.config.brake_resistance = 2 odrv0.axis0.motor.config.pole_pairs = 7 #This is the ratio of torque produced by the motor per Amp of current delivered to the motor. This should be set to 8.27 / (motor KV). # In my case this is 8.27 / 270 odrv0.axis0.motor.config.torque_constant = (8.27 / 270) odrv0.axis0.motor.config.motor_type = MOTOR_TYPE_HIGH_CURRENT #Set up some encoder parameters odrv0.axis0.encoder.config.cpr = 8192 odrv0.axis0.encoder.config.use_index =True #Save the config odrv0.save_configuration() #Proper Tuning odrv0.axis0.controller.config.vel_gain=0.32/2 #[Nm/(turn/s)] odrv0.axis0.controller.config.vel_integrator_gain = (0.5 * 10 * 0.32/2 ) #[Nm/((turn/s) * s)] odrv0.axis0.controller.config.pos_gain=15 #[(turn/s) / turn] #Run the calibration odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE #At this point you can test with the following commands to see how the motor performs, it should be stuttery odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL odrv0.axis0.controller.input_vel = 0.2 #Save the config and reboot odrv0.save_configuration() odrv0.reboot() # Anticogging Setup # https://github.com/odriverobotics/ODrive/blob/ebd237673a483dec87b0d372dd09dfbf0bc7dd64/docs/anticogging.md #tell the odrive we are pr-calibrated so we don't need to recalibrate every time odrv0.axis0.encoder.config.pre_calibrated = True odrv0.axis0.encoder.config.use_index = True odrv0.axis0.motor.config.pre_calibrated = True #NOTE: THE FOLLOWING COMMANDS MIGHT MAKE YOUR MOTOR SPIN WILDLY FOR A SECOND IF YOU HAVE MOVED IT SINCE TURNING IT ON #Put the controller in position control mode odrv0.axis0.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL odrv0.axis0.controller.config.input_mode = INPUT_MODE_PASSTHROUGH odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL #Temporarily increase the gain so that the calibration happens quickly odrv0.axis0.controller.config.pos_gain = 200.0 odrv0.axis0.controller.config.vel_integrator_gain = (0.5 * 10 * 0.32/2 )*10 #THIS IS THE MAGIC - Start the calibration odrv0.axis0.controller.start_anticogging_calibration() #Put the gain back to what it was before odrv0.axis0.controller.config.vel_integrator_gain = (0.5 * 10 * 0.32/2 ) odrv0.axis0.controller.config.pos_gain=15 # Wait until odrv0.axis0.controller.config.anticogging.calib_anticogging == False # you can type # odrv0.axis0.controller.config.anticogging.calib_anticogging # and just up arrow and enter till it turns false #Now tell the ODrive we are pre-calibrated for anti-cogging so we don't need to do it every time we power on odrv0.axis0.controller.config.anticogging.pre_calibrated = True #Save the new config and reboot odrv0.save_configuration() odrv0.reboot() # ######################################### # Startup procedure from now on # ######################################### # Do an index search so the controller can orient the cogging map odrv0.axis0.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH # Put the controller in velocity mode odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL #try a slow speed - it should be much smoother! odrv0.axis0.controller.input_vel = 0.2
Code language: PHP (php)

Arduino RC Controller Input – Part 1

As I continue to build my robot, one of the important elements is having a controller that I can use to manually move and test the motors.

There are a few examples of projects others have done that make controllers that are Arduino or pi based for example James Bruton’s smart robot remote below

Building a Touchscreen Smart Robot Remote – YouTube

For myself, I want to use an RC remote that I already have, so I am keen to hook up the RC receiver to the Arduino and read in the signal parameters so I can use them to control motors and so forth right from the Arduino.

I will be using the Radiolink AT10II 2.4G 12 Channels RC Transmitter and Receiver in this project but any RC controller and receiver should work just fine.

This RC combo is very featured and works very well. For the price I paid I am extremely happy with the quality and features.

For the Arduino board, I am going to use an Arduino MEGA 2560. This is a workhorse for me, I have used the board in many projects, 3d printers and so on. It is well featured, fast and has plenty of memory for whatever I want to throw at it. It also has a number of interrupt pins we can use – These are pins. that detect when something changes, and lets us react to that in code. So when I move the stick on the RC controller, the Arduino detects it, and reads the new value.

Arduino MEGA 2650, workhorse of the maker

Wiring

The Arduino Mega 2560 has 6 interrupt pins available (2, 3, 18, 19, 20, 21). we are going to connect four of these to our receiver to read the signal. Other Arduino boards have different interrupt pins, you can see the pins on this Arduino article for other boards. attachInterrupt() – Arduino Reference

We only need 6 wires for our demo. We connect any of the ‘-‘ pins to the Arduino ground. we connect any of the ‘+’ pins to the Arduino 5V since the receiver takes 4.6v to 10v. Then we connect pin 1 to 18, 2 to 19, 3 to 20 and 4 to 21. If we wanted more channels then we could use pins 2 and 3 on the Arduino also, but for our demo this is enough.

I used fritzing to generate this image. Its a great tool to lay out PCB designs quickly and easily and even get them made!

Code

The first thing we need to do before laying down some code is to import the EnableInterupt library. To do this – click the sketch menu > include libraries > manage library and search for the enable interrupts library and install it as below:

Now we can start coding. The first thing we are going to do is set out the pins for our input from the RC Receiver. I also added a variable for our baud rate, and some variables to hold the data needed to decode each RC channel. You’ll note I annotated my channels with what they correspond to on the controller I’m using. Your controller may not use the same channels.

#include <EnableInterrupt.h> // Set the size of the arrays (increase for more channels) #define RC_NUM_CHANNELS 4 // Set up our receiver channels - these are the channels from the receiver #define RC_CH1 0 // Right Stick LR #define RC_CH2 1 // Right Stick UD #define RC_CH3 2 // Left Stick UD #define RC_CH4 3 // Left Stick LR // Set up our channel pins - these are the pins that we connect to the receiver #define RC_CH1_INPUT 18 // receiver pin 1 #define RC_CH2_INPUT 19 // receiver pin 2 #define RC_CH3_INPUT 20 // receiver pin 3 #define RC_CH4_INPUT 21 // receiver pin 4 // Set up some arrays to store our pulse starts and widths uint16_t RC_VALUES[RC_NUM_CHANNELS]; uint32_t RC_START[RC_NUM_CHANNELS]; volatile uint16_t RC_SHARED[RC_NUM_CHANNELS];
Code language: Arduino (arduino)

The pins correspond to the wiring we performed earlier. The next step is to attach the interrupts. for this we need to create some basic functions to call when our pin changes value. In the code below we first set up our serial connection to the computer so we can send and retrieve values. Then we set our input pins to all be Input only. Finally we enable the interrupt on each of the pins. We also say that the interrupt needs to call a function for each pin so the code knows what to do when the interrupt is hit

// Setup our program void setup() { // Set the speed to communicate with the host PC Serial.begin(SERIAL_PORT_SPEED); // Set our pin modes to input for the pins connected to the receiver pinMode(RC_CH1_INPUT, INPUT); pinMode(RC_CH2_INPUT, INPUT); pinMode(RC_CH3_INPUT, INPUT); pinMode(RC_CH4_INPUT, INPUT); // Attach interrupts to our pins attachInterrupt(digitalPinToInterrupt(RC_CH1_INPUT), READ_RC1, CHANGE); attachInterrupt(digitalPinToInterrupt(RC_CH2_INPUT), READ_RC2, CHANGE); attachInterrupt(digitalPinToInterrupt(RC_CH3_INPUT), READ_RC3, CHANGE); attachInterrupt(digitalPinToInterrupt(RC_CH4_INPUT), READ_RC4, CHANGE); }
Code language: Arduino (arduino)

So the four pins will call four functions (READ_RC1, READ_RC2, READ_RC3, READ_RC4). So our next step us to write these four functions. Since each function is just going to measure the input, we can actually send all four of these functions to one place – in this case another function Read_Input().

// Thee functions are called by the interrupts. We send them all to the same place to measure the pulse width void READ_RC1() { Read_Input(RC_CH1, RC_CH1_INPUT); } void READ_RC2() { Read_Input(RC_CH2, RC_CH2_INPUT); } void READ_RC3() { Read_Input(RC_CH3, RC_CH3_INPUT); } void READ_RC4() { Read_Input(RC_CH4, RC_CH4_INPUT); }
Code language: Arduino (arduino)

Read_Input() is probably the most complicated part of our whole process. To understand what it is doing, you have to understand a little about PWM, and this could be a whole post of its own. What you should know is that RC controllers communicate by sending pulses of varying width down the wire. the width of the pulse defines the value. when a pin goes high (gets voltage) the pulse starts, when it goes low (voltage turns off) the pulse ends.

Every time a pin changes, it is going to trigger the interrupt, and so we can store the start of the pulse and then when the interrupt is triggered for the pulse ending we can report how many milliseconds have elapsed and this defines the value on that channel.

// This function reads the pulse starts and uses the time between rise and fall to set the value for pulse width void Read_Input(uint8_t channel, uint8_t input_pin) { if (digitalRead(input_pin) == HIGH) { RC_START[channel] = micros(); } else { uint16_t rc_compare = (uint16_t)(micros() - RC_START[channel]); RC_SHARED[channel] = rc_compare; } }
Code language: Arduino (arduino)

So far, the interrupts will fire independent of our main program loop, but as we loop we want to use these values, so one final step is to copy the values from the arrays used for the interupts and turn them into something easy to use. Every time the program loops, we will run the rc_read_values() function to pull the values and then I’m converting the values into floats for ease of use.

void loop() { // read the values from our RC Receiver rc_read_values(); }
Code language: Arduino (arduino)

Here’s the read values function that does the array copy for us. While it runs it disables the interrupts so it can complete uninterrupted (HA!).

// this function pulls the current values from our pulse arrays for us to use. void rc_read_values() { noInterrupts(); memcpy(RC_VALUES, (const void *)RC_SHARED, sizeof(RC_SHARED)); interrupts(); }
Code language: Arduino (arduino)

So every loop our four variables will be filled with their values from our RC controller. RC values are supposed to range from 800 to 2200 with the mid point being around 1600, so you can expect the values of the four variables to fall within this range. With my controller I’m seeing values range from around 1085 to 1920 with a mid point of 1500 so your mileage may vary here and its important to tune to your setup.

Visualizing Inputs

We will cover the usage and refinement of these values in the next post, but I wanted share with you a trick for visualizing the values.

First, we add the following to the end of our loop. You can write this section any way you choose, all we are doing is writing our four values separated by commas out to the serial port

// output our values to the serial port in a format the plotter can use Serial.print( RC_VALUES[RC_CH1]); Serial.print(","); Serial.print( RC_VALUES[RC_CH2]); Serial.print(","); Serial.print( RC_VALUES[RC_CH3]); Serial.print(","); Serial.println(RC_VALUES[RC_CH4]);
Code language: Arduino (arduino)

Next we are going to use the Serial Plotter.

Serial Monitor is something you use all the time, but have you tried serial plotter?

What this will do is take any values that it is given over the serial channel and plot them on a chart. As you move the sticks on the RC, you should see the values change on the chart as you do. The serial plotter is a fantastic way to visualize what is happening in your code.

Here’s what the visualizer looks like in action:

I used ScreenToGif (https://www.screentogif.com/) to record this image

Full Code

You can download the full code for this blog article from my github below.

ArduinoRCControllerInput/ArduinoRCControllerInput-Lesson1 at master ยท andyman198/ArduinoRCControllerInput (github.com)

Next time

Now we are able to read our RC inputs, we need to make them useful. In the next article I am going to show you how to implement dead zones so the sticks don’t feel so twitchy. I am also going to implement fail safe so that we can tell if the controller got disconnected or ran out of battery and we can react to that. I will also show you how to control a motor with the values we are getting from the RC controller.