Invalid response using ReadEncM1 with timer interrupt

General discussion of using Roboclaw motor controllers
Post Reply
nRobot
Posts: 5
Joined: Tue Dec 12, 2017 1:26 pm
Invalid response using ReadEncM1 with timer interrupt

Post by nRobot »

Hello,

I am using a RoboClaw 2x7A with a DFRobot CurieNano (an Arduino 101 variant with the Intel Curie module): https://www.dfrobot.com/product-1537.html

I'm using the CurieTimerOne library to run an interrupt service routine (ISR) at a set frequency of 100 Hz.
https://www.arduino.cc/en/Reference/CurieTimerOne

I can send a duty cycle command within the ISR with no problem, but if I try to read the encoder with the ReadEncM1 function, every third loop execution results in an "invalid" response. My code and serial monitor output are below.

Looking on an oscilloscope, I can see that the LED_BUILTIN pin stays high for 1.2ms during the successful reads and 11.4ms during the failed reads.

What could be causing this issue?

When I run the roboclawEncoderRead example (with a baudrate of 460800 on hardware Serial1), I can reduce the delay at the end of the loop to 2 ms and get zero invalid reads, so it seems possible to read the encoder at a fast rate. Is there a conflict with the CurieTimerOne library that affects reading from the roboClaw but not writing to the roboClaw?

Thanks for your time and help!

My Code:

Code: Select all

#include "CurieTimerOne.h"
#include "RoboClaw.h"

RoboClaw roboclaw(&Serial1,10000);
#define address 0x80

uint8_t status1;
bool valid1;

void timerIsr()   // callback function when interrupt is asserted
{
  digitalWrite(LED_BUILTIN, HIGH);
  
  int32_t enc1= roboclaw.ReadEncM1(address, &status1, &valid1);
  
  Serial.print("Encoder1:");
  if(valid1){
    Serial.print(enc1);
    Serial.print(" ");
    Serial.print(status1);
    Serial.print(" ");
  }else{
    Serial.print("invalid ");
  }
  Serial.println();

  // writing duty cycle works fine
//  roboclaw.DutyM1M2(address, 0, 0);
  
  digitalWrite(LED_BUILTIN,LOW);
}

void setup() {
  Serial.begin(115200);  //  initialize Serial communication
  pinMode(LED_BUILTIN, OUTPUT);
  roboclaw.begin(460800);
}

void loop() {
    CurieTimerOne.start(10000, &timerIsr);  // set timer and callback
    delay(1000);
}
Serial Monitor Output:

Code: Select all

Encoder1:0 128 
Encoder1:0 128 
Encoder1:invalid 
Encoder1:0 128 
Encoder1:0 128 
Encoder1:invalid 
Encoder1:0 128 
Encoder1:0 128 
Encoder1:invalid
(repeating)
User avatar
Basicmicro Support
Posts: 1594
Joined: Thu Feb 26, 2015 9:45 pm
Re: Invalid response using ReadEncM1 with timer interrupt

Post by Basicmicro Support »

Interrupts are always tricky. The arduino does use interrupts when working with the hardware serial ports even though it is much less than the software serial uses. It uses them to send and receive data since the hardware serial port only has a small buffer built in. The interrupts move the received data and sending data into and out of the registers as they are needed. This would usually not be a problem, however if you are already in an interrupt you may be blocking other interrupts from executing(I dont think arduino is setup for cascaded interrupts) which would prevent the read command from working properly. In fact this should be a problem for the Duty commands as well. If you were checking the return value of the Duty command I suspect you'd be getting failures there as well.

The general rule is never do anything in an interrupt that takes time. Reading and writing serial data takes a lot of time relative to the processor speed. If you have something that is going to take a while you should setup a flag that then informs your main loop to do the work. Also you should never read/write serial data in both an interrupt and the main loop so it needs to be all or nothing in your code.

I suggest you descibe in more detail what exactly you are trying to do and why. Then I can help you figure out the proper structure for your program.
nRobot
Posts: 5
Joined: Tue Dec 12, 2017 1:26 pm
Re: Invalid response using ReadEncM1 with timer interrupt

Post by nRobot »

Hello, thanks for the detailed response. The reason I am running a timer interrupt is to have a feedback control loop that executes at a set frequency, I'm guessing other people may have a similar need. I tried checking the return value of the Duty command when called from a timer interrupt and they were consistently valid.

It seems like my best bet may be to run a simple check in loop() to see if it's been longer than the desired period since the control code was run. The following code executes without any invalid returns from ReadEncM1 and ReadEncM2:

Code: Select all

//See BareMinimum example for a list of library functions

//Includes required to use Roboclaw library
#include "RoboClaw.h"

RoboClaw roboclaw(&Serial1,1000);

#define address 0x80
#define period 10000 // microseconds
unsigned long lastStart = 0;

void setup() {

  Serial.begin(115200);
  roboclaw.begin(460800);
  pinMode(LED_BUILTIN,OUTPUT);
}

void loop() {
  if ((micros() - lastStart) > period)
  {
    lastStart = micros();
    digitalWrite(LED_BUILTIN,HIGH);
    uint8_t status1,status2,status3,status4;
    bool valid1,valid2,valid3,valid4;
    
    //Read all the data from Roboclaw before displaying on Serial Monitor window
    //This prevents the hardware serial interrupt from interfering with
    //reading data using software serial.
    int32_t enc1= roboclaw.ReadEncM1(address, &status1, &valid1);
    int32_t enc2 = roboclaw.ReadEncM2(address, &status2, &valid2);
    int32_t speed1 = roboclaw.ReadSpeedM1(address, &status3, &valid3);
    int32_t speed2 = roboclaw.ReadSpeedM2(address, &status4, &valid4);
  
    Serial.print("Encoder1:");
    if(valid1){
      Serial.print(enc1);
      Serial.print(" ");
      Serial.print(status1);
      Serial.print(" ");
    }
    else{
      Serial.print("invalid ");
    }
    Serial.print("Encoder2:");
    if(valid2){
      Serial.print(enc2);
      Serial.print(" ");
      Serial.print(status2);
      Serial.print(" ");
    }
    else{
      Serial.print("invalid ");
    }
    Serial.print("Speed1:");
    if(valid3){
      Serial.print(speed1);
      Serial.print(" ");
    }
    else{
      Serial.print("invalid ");
    }
    Serial.print("Speed2:");
    if(valid4){
      Serial.print(speed2);
      Serial.print(" ");
    }
    else{
      Serial.print("invalid ");
    }
    Serial.println();
    
    digitalWrite(LED_BUILTIN,LOW);
  } // end if statement

  delayMicroseconds(1);
}


Does this seem like a reasonable approach? Do you have any suggestions? Thanks!
User avatar
Basicmicro Support
Posts: 1594
Joined: Thu Feb 26, 2015 9:45 pm
Re: Invalid response using ReadEncM1 with timer interrupt

Post by Basicmicro Support »

That or something similar is how I normally write a timed control loop when working with an arduino. The other option is to still use your interrupt but only set a flag that the main control loop will use to determine if it needs to operate the periodic function.
nRobot
Posts: 5
Joined: Tue Dec 12, 2017 1:26 pm
Re: Invalid response using ReadEncM1 with timer interrupt

Post by nRobot »

Thanks for the reply. I'll move forward without the timer interrupt.
nRobot
Posts: 5
Joined: Tue Dec 12, 2017 1:26 pm
Re: Invalid response using ReadEncM1 with timer interrupt

Post by nRobot »

I have now added a call to DutyM1M2() in the timed loop and it has produced some variable results. On power-up, sometimes the encoder data returns as valid, and sometimes as invalid. I've found that if I've detected an invalid packet, I can re-establish the hardware Serial1 link by calling Serial1.end() and roboclaw.begin(460800). Is this a valid approach? Is there a better way? Thanks!

Code: Select all

#include "RoboClaw.h"
RoboClaw roboclaw(&Serial1,1000);

#define ADDRESS 0x80
#define PERIOD 5000 // microseconds
unsigned long lastStart = 0;
int duty;
uint8_t status1,status2,status3,status4;
bool valid1,valid2,valid3,valid4,valid5;

void setup() {
  roboclaw.begin(460800);
  pinMode(LED_BUILTIN,OUTPUT);
  pinMode(12,OUTPUT);
  pinMode(11,OUTPUT);
}

void loop() {
  if ((micros() - lastStart) > PERIOD)
  {
    lastStart = micros();
    digitalWrite(12,HIGH);

    int32_t enc1= roboclaw.ReadEncM1(ADDRESS, &status1, &valid1);
    int32_t enc2 = roboclaw.ReadEncM2(ADDRESS, &status2, &valid2);
    int32_t speed1 = roboclaw.ReadSpeedM1(ADDRESS, &status3, &valid3);
    int32_t speed2 = roboclaw.ReadSpeedM2(ADDRESS, &status4, &valid4);

    if (valid1&&valid2&&valid3&&valid4)
      digitalWrite(LED_BUILTIN,HIGH);
    else
    {
      digitalWrite(LED_BUILTIN,LOW);
      Serial1.end();
      roboclaw.begin(460800);
    }

    duty = 5000*sin(millis()/250.0);
    valid5 = roboclaw.DutyM1M2(ADDRESS, duty, duty);
    digitalWrite(11,valid5);
    
    digitalWrite(12,LOW); // heartbeat
  } // end if statement

  delayMicroseconds(1);
}
User avatar
Basicmicro Support
Posts: 1594
Joined: Thu Feb 26, 2015 9:45 pm
Re: Invalid response using ReadEncM1 with timer interrupt

Post by Basicmicro Support »

This could indicate the 460800 timings are not matched up well enough. Try lowering the baudrate to 115200 and see if the intermittent command failures disappear.

I'm not sure why closing and opening the port(eg end(), begin()) is necessary. You probably could just clear the receive buffer. The roboclaw library automatically reads/clears the RX buffer if a command timesout IIRC.
nRobot
Posts: 5
Joined: Tue Dec 12, 2017 1:26 pm
Re: Invalid response using ReadEncM1 with timer interrupt

Post by nRobot »

Thanks for the reply. I tried lowering the baudrate to 115200 and the performance was actually worse (in addition to the fact that it couldn't complete all the commands within my desired 5ms period).

Per your suggestion, I switched to roboclaw.clear() instead of the end() and begin(), hoping it might be faster to clear the error, when it occurs.

If I do not call the DutyM1M2() command, I do not have any problem with reading valid results from the encoders. This makes me think it has something to do with the write_n() function, I am also getting an invalid response when sending the motor duty commands, even though the motors do spin. Perhaps the roboclaw isn't sending the 0xFF byte fast enough for the write_n() function to capture it, and then when it does send the 0xFF, it messes up the encoder data?

Also, I was using IonStudio to change the baud rate setting on the RoboClaw, but noticed that the change was lost when I cycled power. I started using the buttons on the board to set the baud rate (which did preserve the change through a power cycle). Am I missing some way to save settings in IonStudio to non-volatile memory?
User avatar
Basicmicro Support
Posts: 1594
Joined: Thu Feb 26, 2015 9:45 pm
Re: Invalid response using ReadEncM1 with timer interrupt

Post by Basicmicro Support »

You need to save the settings after changing them in IonStudio(Device menu , Save Settings). Otherwise they are only stored in ram in the Roboclaw. Once saved they are stored in eeprom and will be reloaded on power up.

There could be some kind of icompatibility with the CurieDuino board. You are going to have to debug this youself. We dont have access to one of those boards and I've never worked with another customer that used one. My guess is something doesnt work quite the same as a standard arduino that the roboclaw library is relying on.

Post Reply