Apparent timer conflict?

Need help resolving inaccurate readings from my US range finder. Reports a 20% low distance, consistently and repeatably, when I run the ultrasonic (US) code in my existing robot code. Run by itself, the US code gives accurate data. Incorporate the US code in the robot code and data is low by 20%.

Over time, with some experimenting, I believe I’ve isolated the cause to a timer conflict between the US range finder code and the IR receiver Library.

Project is to construct a robot, mainly just to learn, try out different options. Current goal is to make it autonomous with a remote control override. Some details:

• Intended for indoor environment only • Dagu Rover 5 chassis (4 motors) • 4 channel motor control (Dagu 4 motor board, Sparkfun part ROB-11593) • Arduino Uno • IR remote control (Sparkfun 9 button remote, receiver diode is a TSOP38238) • Ultrasonic Range Sensor (HC SR04)

The Code was originally written to provide remote control via the IR remote. My next step was to add some autonomous operation. The idea boing to be able to take over with the remote if the rover ran into trouble. So, I was experimenting with the US range finder, looking at the accuracy of the data, when I noticed an inconsistency.

I determined that the error is not hardware dependent and that it is present if I use the Ultrasonic.h library or not.

Reading up on the interweb left me with the conclusion that the two devices are trying to share a timer. Tried messing with the timer code in the IR Library but obviously don’t know what I’m doing (Damn it Jane, I’m an instrumentation geek, not a programmer).

So, any ideas on how to resolve this would be appreciated. Sources to learn more on the timers, or how to modify the timer code in the IR library.

In the end, it may all be moot as I’ve realized a serious shortcoming with IR control, line of sight. I was ultimately hoping to equip the robot with a camera and control it while it was out of sight, or let it operate autonomously and just be a tourist through the camera. None of this has any real purpose other than to occupy me through the winter while the bikes remain parked in the garage.

Just askin’ Barb

I believe I've isolated the cause to a timer conflict between the US range finder code and the IR receiver Library.

Reading up on the interweb left me with the conclusion that the two devices are trying to share a timer.

Where did you find that?

The library you cited--Ultrasonic.h--uses pulseIn(). The code for pulseIn() is in wiring_pulse.c. and wiring_pulse.S. The function does not use a timer, but rather counts processor clock cycles in a while loop waiting for a pin change.

The ultra sonic may be 20% low when running with the robot code, but it can't be due to a timer conflict.

Can you show us a simplified version of your code which demonstrates the problem.

EDIT: If the IR receiver library is using a lot of interrupts, I think that they will be executed during the period that the ultra sonic is timing and may affect it. Depending on how the robot is working, it may be possible to disable interrupts before calling pulseIn() and reenable when completed.

cattledog:
Where did you find that?

That was a guess on my part. Libraries are new to me. In fact C is new to me. I know I’m dating myself, but my first programming language was Fortran. I learned Basic on my own.

Anyway, that’s my excuse and I’m sticking to it.

So, Okay, now I know where the code for the US sensor is, thanks. The IRremote library I use is Ken Shirriff’s. It does use interrupts. I tried disabling the interrupts, but could not get the IR to function then. In the code, the IR sensor is started in the SETUP before the LOOP. Killing the Interrupts before the US sensor code runs gives me accurate distance readings, but kills the IR control.

Ultimately, this is a learning experience. I’ll likely have to use something other than an IR remote. Also, so long as the error is consistent, I can live with it while I figure out how to use the data for autonomous operation. But I do like to understand what is happening.

My Code:

/*
  IR Control of Dagu 5 , 4 motor robot chasis using an IR remote

  Code snippets stolen from various sources and edited/rewritten by Barb.  Fall 2015
  
  IR Receiver Breakout (SEN-8554): Supply voltage of 2.5V to 5.5V
    Attach
    OUT: To pin A0 on Arduino
    GND: GND
    VCC: 3.3 V
  
  This uses Ken Shirriff's code found on GitHub:
  https://github.com/shirriff/Arduino-IRremote/
  
Base IR Robot code:IR_robot_V3b
Setup to gather test data for US range finder performance
  evaluation.
V4_TUS_V1:
  Timing 2.5 second scan rate
  US Scan code:
   by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.
 modified 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Sweep
 Modified by Barb November 2015
 Development code for Dagu Rover 5 IR/autonomous project
 Serial output formatted to CSV format 
Version 1b - Use Ultrasonic code without Library 
 Movement controlled maunually 

HC-SR04 Ping distance sensor:
 VCC to arduino 5v 
 GND to arduino GND
 Echo to Arduino pin A5 
 Trig to Arduino pin A4 
*/

int Distance;
#define echoPin A5 // Echo Pin
#define trigPin A4 // Trigger Pin

int maximumRange = 200; // Maximum range needed
int minimumRange = 0; // Minimum range needed
long duration, distance; // Duration used to calculate distance

//IR setup
#include <IRremote.h>
int RECV_PIN = A0;
IRrecv irrecv(RECV_PIN);
decode_results results;
uint16_t lastCode = 0;  // This keeps track of the last code RX'd

//Movement cotrol variable setup
int SPEED = 200;        //Starting speed value for forward or reverse
int STOPTest = 0;       //Used to check last motor status
int LASTSpeed;          //Unused

//Low battery test setup
const float LOW_BATTERY_VOLTAGE = 5.8;
const byte BATTERY_CHECK = 2; // A2
const byte RED_LED = 11;  // warning LED

//IR Remote button codes
#define POWER 0x10EFD827      //No Function
#define A 0x10EFF807          //Slower
#define B 0x10EF7887          //Reset default speed
#define C 0x10EF58A7          //Faster
#define UP 0x10EFA05F         //Move forward
#define DOWN 0x10EF00FF       //Move backward
#define LEFT 0x10EF10EF       //Turn left
#define RIGHT 0x10EF807F      //Turn right
#define SELECT 0x10EF20DF     //Stop

//Set up the motor outputs
#define DIRECTIONA 4   //LF Channel 1
#define MOTORA 5
#define DIRECTIONC 7   //LR Channel 2
#define MOTORC 6
#define DIRECTIONB 8   //RF Channel 3
#define MOTORB 9
#define DIRECTIOND 12  //RR Channel 4
#define MOTORD 10

void setup()
{
//Initialize motor control output pins
   pinMode (MOTORA, OUTPUT);
   pinMode (DIRECTIONA, OUTPUT);
   pinMode (MOTORB, OUTPUT);
   pinMode (DIRECTIONB, OUTPUT);
   pinMode (MOTORC, OUTPUT);
   pinMode (DIRECTIONC, OUTPUT);
   pinMode (MOTORD, OUTPUT);
   pinMode (DIRECTIOND, OUTPUT);
   
   Serial.begin(9600);  //for trooubleshooting
   irrecv.enableIRIn(); // Start the IR receiver 
   pinMode (RED_LED, OUTPUT);  // low battery warning LED
   pinMode(trigPin, OUTPUT);  // US Sensor
   pinMode(echoPin, INPUT);   // US Sensor

}

void MotorControl ()
{
  analogWrite (MOTORA, SPEED);
  analogWrite (MOTORB, SPEED);
  analogWrite (MOTORC, SPEED);
  analogWrite (MOTORD, SPEED); 
return;
}

void loop() 
{
//Ultrasonic Range Finder   
/* The following trigPin/echoPin cycle is used to determine the
  distance of the nearest object by bouncing soundwaves off of it. */ 
  //noInterrupts();
  digitalWrite(trigPin, LOW); 
  delayMicroseconds(2); 
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10); 
  digitalWrite(trigPin, LOW);
  duration = pulseIn(echoPin, HIGH);
 
//Calculate the distance (in cm) based on the speed of sound.
  distance = duration/58.2;
 
  if (distance >= maximumRange || distance <= minimumRange){
    // Send a negative number to computer 
    Serial.println("-1");
    }
  else {
    // Send the distance to the computer using Serial protocol, and
     Serial.println(distance); 
    }
//Delay 50ms before next reading.
  delay(50);
  //interrupts();
  
//Low Voltage test routine
  int voltage = analogRead (BATTERY_CHECK);  
  // 1024 would be 5V on the pin
  // we have a voltage divider so the read voltage is half the battery
  float volts = voltage / 1024.0 * 5.0 * 2.0;
  
  if (volts < LOW_BATTERY_VOLTAGE)
    {
    SPEED = 0; 
    STOPTest = 0; 
    MotorControl();
    digitalWrite (RED_LED, HIGH);  // flash LED as warning
    delay (50);
    digitalWrite (RED_LED, LOW);
    delay (950);
    return;  // don't try to use motors now
    }    

//IR receive routine
  if (irrecv.decode(&results)) 
  {
  /* read the RX'd IR into a 16-bit variable: */
  uint16_t resultCode = (results.value & 0xFFFF);
  /* The remote will continue to spit out 0xFFFFFFFF if a 
     button is held down. If we get 0xFFFFFFF, let's just
     assume the previously pressed button is being held down */
  if (resultCode == 0xFFFF)
      resultCode = lastCode;
    else
    lastCode = resultCode;
  
 //COntrol sequence
  switch (resultCode)
    {
      case POWER:
        break;
      case A:     //Slower
        if (STOPTest == 1)
          if (SPEED > 90){
          SPEED = SPEED - 10;
          MotorControl();}
        break;
      case B:
        SPEED = 200;
        break;
      case C:     //Faster
        if (STOPTest == 1)
          if (SPEED < 250){ 
          SPEED = SPEED + 10;
          MotorControl();}
        break;
      case UP:
        if (STOPTest == 0) SPEED = 200;
        STOPTest = 1;
        digitalWrite (DIRECTIONA, 0);
        digitalWrite (DIRECTIONB, 0);
        digitalWrite (DIRECTIONC, 0);
        digitalWrite (DIRECTIOND, 0);
        MotorControl();
        break;
      case DOWN:
        if (STOPTest == 0) SPEED = 200;
        STOPTest = 1;
        digitalWrite (DIRECTIONA, 1);
        digitalWrite (DIRECTIONB, 1);
        digitalWrite (DIRECTIONC, 1);
        digitalWrite (DIRECTIOND, 1);
        MotorControl();
        break;
      case LEFT:
        if (STOPTest == 0){
          digitalWrite (DIRECTIONA, 1);
          digitalWrite (DIRECTIONB, 0);
          digitalWrite (DIRECTIONC, 1);
          digitalWrite (DIRECTIOND, 0);
          analogWrite (MOTORA, 150);
          analogWrite (MOTORB, 150);
          analogWrite (MOTORC, 150);
          analogWrite (MOTORD, 150);
        }
        else{
          analogWrite (MOTORA, 0);
          analogWrite (MOTORB, SPEED);
          analogWrite (MOTORC, 0);
          analogWrite (MOTORD, SPEED);
        }
        break;
      case RIGHT:
        if (STOPTest == 0){
          digitalWrite (DIRECTIONA, 0);
          digitalWrite (DIRECTIONB, 1);
          digitalWrite (DIRECTIONC, 0);
          digitalWrite (DIRECTIOND, 1);
          analogWrite (MOTORA, 150);
          analogWrite (MOTORB, 150);
          analogWrite (MOTORC, 150);
          analogWrite (MOTORD, 150);
        }
        else{
          analogWrite (MOTORA, SPEED);
          analogWrite (MOTORB, 0);
          analogWrite (MOTORC, SPEED);
          analogWrite (MOTORD, 0);
        }
        break;
      case SELECT:
        STOPTest = 0;
        SPEED = 0;
        MotorControl();
        break;
      default:
        break;        
    }       
    
    irrecv.resume();
  }
}

Barb

You can certainly tighten up the placement of the noInterrupts() interrupts() to specifically be around pulseIn(). The interrupts will only be disabled for the length of the time it takes to return a reading.

If that messes up the IR control for too long you might want to try reading length of the ultrasonic pulse with an external or pin change interrupt proceedure of its own rather than using pulseIn(). The two sets of interrupts should be able to stay out of each other’s way.

//Ultrasonic Range Finder   
/* The following trigPin/echoPin cycle is used to determine the
  distance of the nearest object by bouncing soundwaves off of it. */ 
  //noInterrupts();
  digitalWrite(trigPin, LOW); 
  delayMicroseconds(2); 
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10); 
  digitalWrite(trigPin, LOW);

  noInterrupts();
  duration = pulseIn(echoPin, HIGH);
  interrupts();
 
//Calculate the distance (in cm) based on the speed of sound.
  distance = duration/58.2;
 
  if (distance >= maximumRange || distance <= minimumRange){
    // Send a negative number to computer 
    Serial.println("-1");
    }
  else {
    // Send the distance to the computer using Serial protocol, and
     Serial.println(distance); 
    }
//Delay 50ms before next reading.
  delay(50);
  //interrupts();

Here is some external interrupt code for the Ultrasonic Range Sensor (HC SR04). I can provide a pin change interrupt version if you don't have the external interrrupt pins free.

volatile unsigned long LastPulseTime;
int duration;
const byte trigPin = 7;
const byte echoPin = 2;
//const byte echoPin = 3;
volatile boolean pulseCaptured = false;
boolean triggerFinished = false;

void setup()
{
  Serial.begin (9600);
  Serial.println("starting...");
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  attachInterrupt(0, EchoPinISR, CHANGE); // Pin 2 interrupt on any change
  // attachInterrupt(1, EchoPinISR, CHANGE);  // Pin 3 interrupt on any change
}

void loop()
{
  if (triggerFinished == false)
  {
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);
    triggerFinished = true;
  }

  if (pulseCaptured == true && triggerFinished == true)
  {
    pulseCaptured = false; //reset
    triggerFinished = false;
    Serial.print((LastPulseTime / 2) / 29.1, 1);
    Serial.println("cm");
  }
}

void EchoPinISR()
{
  static unsigned long startTime;

  if (digitalRead(echoPin)) // Gone HIGH
    startTime = micros();
  else  // Gone LOW
    LastPulseTime = micros() - startTime;
  pulseCaptured = true;
}

Cattledog, thank you ever so much. That bit of code you provided fixed the accuracy issue and I can now move on the next phase, how to use the data for autonomous operation.

More amazingly, I even understand what you did and learned something. But then, that really is the point.

So once again, thanks. Barb