Problems with variable access in interrupts

Dear all,

first of all, please excuse my broken english, I’m no native speaker.

I’m actually working on a sketch to improve the behaviour of the handling of my RC Truck. At the moment the Truck is driving forward as long as you move the stick forward, regardless of any obstacles. If an obstacle comes up, it will crash… :~

My idea: The Arduino board receives the servo signal from the receiver and forwards the signal to the drive control module. This is done in an interrupt. Parallel the loop() function checks the distance to a possible obstacle by using a parallax Ping sensor. If an abstacle is nearer than 4 inch, a variable is set to true. This variable is checked by the interrupt and if it is set, the signal is not forwarded, it is replaced by a continou LOW signal. This simulates a lost RC connection and the drive control module will stop.

I already discussed with Udo Klein and circuit19 in the german forum section. Now I’d like to contact the international Arduino fans also.

The variable “bObstacleDetected” is defined as volatile. It is initialized in the setup() function. As long as the variable is not changed, the interrupt callback can access and read the variable. But if the loop() function modifiles the variable with a new or the same value, the interrupt function does not read the correct value anymore until the end of times.

The sensor is detecting the obstacle correctly and the variable is set correctly. It can be seen by the switching LEDs. But the interrupt funtion does not read resp. process the correct value.

I’d like to present my code here, your help and comments are much appreciated.

I also uploaded a video on youtube to describe the issue. Please refer to http://www.youtube.com/watch?v=R4Nzc-skUZU

Here’s my code:

#include "Arduino.h"

#define INPUTPIN  2
#define OUTPUTPIN  13

#define LEDREDPIN  4
#define LEDGREENPIN 5

#define SENSORPIN  7

static volatile uint8_t *pinOutPort;
static volatile uint8_t *pinInPort;
static volatile uint8_t bObstacleDetected;
unsigned long ulNextDistanceCheck;

void quicfunc() 
{
  const uint8_t pinOutHighMask = digitalPinToBitMask(OUTPUTPIN);
  const uint8_t pinOutLowMask  = pinOutHighMask^0xFF;
  
  if ( bObstacleDetected != 0 )
  {
    *pinOutPort  &= pinOutLowMask;   //  Output to LOW wenn pin auf LOW ist oder wenn ein Hindernis im Weg ist.
 }
  else
  {
    if ( ( ( *pinInPort & digitalPinToBitMask(INPUTPIN) ) == 0 ) )
     *pinOutPort  &= pinOutLowMask;   //  Output to LOW wenn pin auf LOW ist oder wenn ein Hindernis im Weg ist.
    else
     *pinOutPort  |= pinOutHighMask;        //  Output to HIGH
  }
}

void setup() {
  bObstacleDetected    = true;
  ulNextDistanceCheck  = millis();
  
  pinMode(INPUTPIN, INPUT); digitalWrite(INPUTPIN, HIGH);
   pinMode(OUTPUTPIN, OUTPUT); digitalWrite(OUTPUTPIN, LOW);
   pinMode(LEDREDPIN, OUTPUT); 
   pinMode(LEDGREENPIN, OUTPUT); 
  // *****************************************************************************
  // set up ports for output ************ 
  // *****************************************************************************
  pinOutPort=portOutputRegister(digitalPinToPort(OUTPUTPIN)); // output port
  pinInPort=portInputRegister(digitalPinToPort(INPUTPIN)); // input port

  attachInterrupt(0, quicfunc, CHANGE);
}

void loop() 
{
  if ( ulNextDistanceCheck < millis() )
  {
    pinMode(SENSORPIN, OUTPUT);
    digitalWrite(SENSORPIN, LOW);
    delayMicroseconds(2);
    digitalWrite(SENSORPIN, HIGH);
    delayMicroseconds(5);
    digitalWrite(SENSORPIN, LOW);
  
    pinMode(SENSORPIN, INPUT);
        
    int nDistance = pulseIn(SENSORPIN, HIGH) / 29 / 2;

      uint8_t oldSREG = SREG;
      cli();

      bObstacleDetected = ( ( nDistance < 10 ) ? 1 : 0 );
      digitalWrite( LEDREDPIN, bObstacleDetected );
      digitalWrite( LEDGREENPIN, !bObstacleDetected );  

      SREG = oldSREG;

    ulNextDistanceCheck  = millis() + 500;
  }
}

Kind regards
Peter

Hi,

Seems like a complicated way of doing something simple unless I am missing something.

What kind of a truck is it ? if its hobby quality, why don't you use the servo library, Servo.writeMicroseconds(1500); in loop will stop your truck.

Here is something I have done to actively control the yaw of an RC Car -

http://rcarduino.blogspot.com/2012/07/rcarduino-yaw-control-part-2.html

It has code which you could use for the basis of a solution, I am also working on a library which will include fail safes.

Duane B

rcarduino.blogspot.com

Hi,

I have a Tamiya Truck type Knight Hauler with a Servonaut S20 speed control module.

The first evolution step is just to block the signal so that the speed control module runs into the failsafe mode. The next evolution step is to analyse the incoming signal. If an obstacle is detected, the Arduino must send a server signal for "Stop".

The next evolution step is to analyze the incoming signal while an obstacle is detected. If the driver moves the pin back, the signal should not be blocked, because I want allow to move backwarts away from the obstacle.

A single read and write of the signal in a loop will not work. The receiver is sending 50 pulses per second. If I read the first, and write it to the out pin, the receiver will send the next pulse while I'm busy with writing. After that I'll be busy with reading the next pulse, during this time no pulse it written out. So this method will forward only 25 pulses per second.

I'll have a detailed look at your code.

many thanx in advance.

Peter

A single read and write of the signal in a loop will not work. The receiver is sending 50 pulses per second. If I read the first, and write it to the out pin, the receiver will send the next pulse while I'm busy with writing. After that I'll be busy with reading the next pulse, during this time no pulse it written out. So this method will forward only 25 pulses per second.

You can easily read and write 700 pulses per second, really easily, the Arduino will still be sat around waiting for more work to do for 90% of the time.

Have a look at the yaw control project just to see what can be done at speed and then read these for an idea of how to do all this work more or less in the background -

http://rcarduino.blogspot.com/2012/01/how-to-read-rc-receiver-with.html http://rcarduino.blogspot.com/2012/01/can-i-control-more-than-x-servos-with.html http://rcarduino.blogspot.com/2012/04/how-to-read-multiple-rc-channels-draft.html

Duane B

rcarduino.blogspot.com

Hi,

but my primary problem isn't the way of reading/writing the pulses, my primary problem is that the variable "bObstacleDetected" can't be used correctly in the interrupt function.

Peter

I think that using interrupts is a red herring. Um, in English that means it's leading you down a path that is not important to your problem.

Your radio control system will be providing a signal for the speed channel and I assume that it takes the form of a standard servo PWM signal. You can connect this to a pin on the Arduino and read the corresponding servo 'position' (which represents a speed in this case) and the code to do this is easy to find.

You can have your sketch operate a ping sensor and the code to do this and calculate the distance to an obstacle is also easy to find.

It is trivial for the sketch to decide whether there is an obstacle and decide whether to use the incoming speed signal, or to override that and stop the vehicle.

The servo library can be used to output whatever servo 'position' your sketch selects. Put these together and you have something that sits between a standard RC receiver and speed controller which stops the vehicle if an obstacle is detected.

Hi,

but this all can’t be done in a serial job. The request for the distance to an obstacle can take up to 500ms and more. All incoming servo pulses during this time will be lost and during this time no servo writes will be done to the output. This might cause an execution of failsafe procedures in the speed control module.

Please keep in mind that the sensor request is a blocking procedure.

Following this it is recommended to put the read/write of th servo and the distance control into two seperate process lines whch communicate via variables.

Peter

gismow:
but my primary problem isn’t the way of reading/writing the pulses, my primary problem is that the variable “bObstacleDetected” can’t be used correctly in the interrupt function.

It can be. Your interpretation must be at fault. First can you rewrite this:

void quicfunc() 
{
  const uint8_t pinOutHighMask = digitalPinToBitMask(OUTPUTPIN);
  const uint8_t pinOutLowMask  = pinOutHighMask^0xFF;
  
  if ( bObstacleDetected != 0 )
  {
    *pinOutPort  &= pinOutLowMask;   //  Output to LOW wenn pin auf LOW ist oder wenn ein Hindernis im Weg ist.
 }
  else
  {
    if ( ( ( *pinInPort & digitalPinToBitMask(INPUTPIN) ) == 0 ) )
     *pinOutPort  &= pinOutLowMask;   //  Output to LOW wenn pin auf LOW ist oder wenn ein Hindernis im Weg ist.
    else
     *pinOutPort  |= pinOutHighMask;        //  Output to HIGH
  }
}

more simply?

Like, do mean:

void quicfunc() 
{
  if ( bObstacleDetected)
    digitalWrite (OUTPUTPIN, LOW);
  else
    digitalWrite (OUTPUTPIN, digitalRead (INPUTPIN));
}

Hi Nick,

it's still the same problem. The loop() function sets the variable "bObstacleDetected", the LED switches to Red and the srvo signal is still transfered by the callback function.

Greetinx Peter

I can't see this working, but its going to take 1 second to try -

try removing the static keyword from bObstacle detected, static my be allowing the compiler to try some unintended optimisations.

Duane B.

Hi,

I removed the static but it didn't help.

May be there's a compiler switch that causes the trouble ? It should be standard behaviour that global variables are accessable in the interrupts. If saw it in multiple code samples.

I tested with another board and the issue still exists, so it's no problem with the board.

Any ideas ?

Greetinx Peter

gismow: the LED switches to Red and the srvo signal is still transfered by the callback function.

Can you post your revised code please?

... the srvo signal is still transfered ...

I don't understand that.

gismow: All incoming servo pulses during this time will be lost and during this time no servo writes will be done to the output. This might cause an execution of failsafe procedures in the speed control module.

Is it really necessary to do the 'ping' in a blocking way? That makes things awkward. But you probably could still read the incoming servo signal if you have it connected to an input with an interrupt on change, and I'm pretty sure the servo output will continue (isn't that implemented using the hardware timers?).

gismow: I removed the static but it didn't help.

May be there's a compiler switch that causes the trouble ?

There is no compiler switch that makes volatile variables not work. Why would you want one?

Your original code is incredibly convoluted. Please do what I suggest and replace by what I posted or similar. I suspect that the code simply has a bug. This is nothing to do with the variable in question.

      uint8_t oldSREG = SREG;
      cli();

      bObstacleDetected = ( ( nDistance < 10 ) ? 1 : 0 );
      
      ...
      SREG = oldSREG;

I don’t believe you need to disable interrupts here. Writing to a byte will be atomic. There is no reason to turn interrupts off.

True, but the masking of interrupts does ensure that the ISR shouldn’t “see” bObstacleDetected until the LEDs
have changed colors.

I looked at the compiled assembler output of the original code. I didn’t see anything that looked out of place.
It’s got me stumped and very curious at this point.

I think I’m going to try simulating the environment using my SR04 transducer and then using a PWM pin
to feed into pin 2 to simulate the servo input signal.
I’ll look at all the signals with the logic analyzer and it should be fairly obvious what is happening.

It must be something silly we are all overlooking…

— bill

Ok,
so I simulated the environment using a PWM signal to feed the input pin
and used a HRs04 transducer for the distance.

The code is working as expected. The PWM signal signal feed to the input pin
shows up on the output pin. When the distance is smaller than 10 cm
bObstacleDetected is set and the PWM signal stops show up on the output pin.
When the distance increases beyond 10 cm bObstacleDetected is cleared
and the PWM signal shows up again.

The code I used is attached. It is changed very little from the original code.

I don’t know how the original transducer works, but one thing that might
cause some strangeness is if the reading is too large because of nDistance
being an int
It would be better to make it an unsigned long or at least and unsigned int
to avoid getting a negative value for large distances.

— bill

vOB.pde (2.22 KB)

Hi,

please find here the actual version of the code.

#include "Arduino.h"

#define INPUTPIN  2
#define OUTPUTPIN  13

#define LEDREDPIN  4
#define LEDGREENPIN 5

#define SENSORPIN  7

volatile byte bObstacleDetected;
unsigned long ulNextDistanceCheck;

void quicfunc() 
{
  if ( bObstacleDetected != 0 )
    digitalWrite (OUTPUTPIN, LOW);
  else
    digitalWrite (OUTPUTPIN, digitalRead (INPUTPIN));
}

void setup() {
  bObstacleDetected    = 1;
  ulNextDistanceCheck  = millis();
  
  pinMode(INPUTPIN, INPUT); digitalWrite(INPUTPIN, HIGH);
   pinMode(OUTPUTPIN, OUTPUT); digitalWrite(OUTPUTPIN, LOW);
   pinMode(LEDREDPIN, OUTPUT); 
   pinMode(LEDGREENPIN, OUTPUT); 

  attachInterrupt(0, quicfunc, CHANGE);
}

void loop() 
{
  if ( ulNextDistanceCheck < millis() )
  {
    pinMode(SENSORPIN, OUTPUT);
    digitalWrite(SENSORPIN, LOW);
    delayMicroseconds(2);
    digitalWrite(SENSORPIN, HIGH);
    delayMicroseconds(5);
    digitalWrite(SENSORPIN, LOW);
  
    pinMode(SENSORPIN, INPUT);
        
    unsigned long ulDistance = pulseIn(SENSORPIN, HIGH) / 29 / 2;

    uint8_t oldSREG = SREG;
    cli();

    bObstacleDetected = ( ( ulDistance < 10 ) ? 1 : 0 );
    digitalWrite( LEDREDPIN, bObstacleDetected );
    digitalWrite( LEDGREENPIN, !bObstacleDetected );  

    SREG = oldSREG;
    
    ulNextDistanceCheck  = millis() + 500;
  }
}

Attached you’ll also find the disassembled code. May be you can find something. My knowledge regarding assembler is located in the 65xx (Commodore C64) and 680x0 (Commodore Amiga). I only can suggest what’s happening.

Many thanx in advance for your assistance.

@Nick: My question regarding compiler switches was located in the context handling. I did the following:

  1. I commented out the code in the loop() function that accesses the bObstacleDetected variable. So the only code that accesses the variable is the initialization in the setup() function and the quickfunc callback function.
  2. I initialized the bObstacleDetected variable in the setup() function with 0 and the callback function forwarded the incoming servo pulse as requested.
  3. I initialized the bObstacleDetected variable in the setup() function with 1 and the callback function blocked all servo impulses and it always sends a LOW to the output pin.

after that I comment in the changing code in the loop() function. Now I could see that the callback function could access the variable as long as the loop() function had access to it. From that moment on that the loop() function accessed the variable, the callback function could no longer work with it.
I tested it with a serial output (I know it’s no good solution), but I could see outputs from the callback function until that time the loop() function accessed the variable the first time.

So my assumption was, that may be the context the of the bObstaceDetected changes so that the callback function can no longer access the variable.

Your oppinion, comments and thoughts are much appreciated.

Peter

ServoInterrupt_6.dump (45 KB)

I have a hypothesis that pulseIn is hanging. Can you please add a timeout and try again?

[quote author=Nick Gammon link=topic=117286.msg884482#msg884482 date=1344290183] I have a hypothesis that pulseIn is hanging. Can you please add a timeout and try again? [/quote]

Me too. That's about the only thing left. Hanging or not providing the intended time/distance.

The original code (slightly modified) worked for me. The only difference with my code was that I'm talking to the ultrasonic sensor slightly differently. There was no problem accessing the variable in the ISR. The code worked as intended. I also reviewed all the compiled code. All the code looked normal and nothing was optimized out.

gismow, What IDE and gcc toolset are you using? long ago there were some issues with the avr-gcc tools in the Ubuntu repositories. (It caused AVR register corruption in ISRs)

what type of ultrasonic sensor are you using?

Once you see the red led light from obstacle detection, will it go away when the object is moved further away? And then come back again?

This will ensure that the loop() code is not hung.

--- bill