Access class (library) variable from ISR

Hi, I'm having trouble when trying to access a variable declared on a class from an ISR.
The variable is declared as volatile and static, this library is expected to have only one instance running.

The main issue is that the compiler tells everything is fine but the microcontroller keeps restarting, this is caused by a single line inside the ISR (if I remove it everything works fine). The line has the following content:

float spdRPS = robotOnLine::_speedRPS;

If I replace "robotOnLine::_speedRPS" by a number the microcontroller doesn't restart anymore.

This makes me believe the problem is caused by the use (or misuse) of the scope resolution operator.
I have another ISR (triggered by external interrupts) where I used this operator and everything went well.

.h file (simplified)

//...

class robotOnLine
{
  public:
  //...

  volatile static float _speedRPS;

  private:

  //...
};

.cpp file (simplified)

// Timer setup 
hw_timer_t * timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

//...

volatile float robotOnLine::_speedRPS = 0;

// functions...

// timer ISR (500ms)
void IRAM_ATTR onTimer()
{ // Increment the counter and set the time of ISR

  portENTER_CRITICAL_ISR(&timerMux);



  portEXIT_CRITICAL_ISR(&timerMux);
}

begin()
{

  //...

  /*  Timer configuration  */
  // Use 1st timer of 4 (counted from zero).
  // Set 80 divider for prescaler (see ESP32 Technical Reference Manual for more
  // info).
  timer = timerBegin(0, 80, true);

  // Attach onTimer function to our timer.
  timerAttachInterrupt(timer, &onTimer, true);

  // Set alarm to call onTimer function every 500 ms (value in microseconds).
  // Repeat the alarm (third parameter)
  timerAlarmWrite(timer, 500000, true);
  
  // Start an alarm
  timerAlarmEnable(timer);

}

Once the class is defined you need to create an instance of it before you can access any of its contents

Indeed, my bad I didn't describe that part.

The following code is the main application:

#include <robotOnLine.h>

robotOnLine robot;

void setup()
{
  Serial.begin(115200); 

  robot.begin();
  robot.beginAutoDrive();
  robot.disableNear();
  robot.disableUltrasonic();
  //robot.beginAutoDrive();
  pinMode(BUILTIN_LED, OUTPUT);
  delay(100);
  
}

void loop()
{
  robot.autoDrive(0);
  delay(100);
}

But regarding instances, when I use the scope operator I'm specifying a class (please correct me if I'm wrong), but no instance is specified, may that be related to this issue?

There is no variable robotOnLine::_speedRPS. robotOnLine is only the definition of a class, it's not an actual variable.

I think you want to do robot._speedRPS

No no, that's not it.
There is a variable named "_speedRPS" declared in the .h file of the robotOnLine library.
The issue is accessing it from an ISR (In-System Routine) function associated to a timer that is constantly being called with an interval of 500 milliseconds.

I believe there are issues using floats inside ISRs on ESP32, you can search for the details.

Please post a small, complete MRE that will actually compile.

float spdRPS = robotOnLine::_speedRPS;So where is this line ? in an ISR, ok, but in an ISR inside the class ? Then it should just be float spdRPS = _speedRPS; or as a part of the main sketch ?, in that case it should be as in #3.
It looks as if you've change a private variable into a public one (with the _ in front of the name it is convention that it is private)
A safer way would be to add a function

float ReturnSPRPS() {
  return _speedRPS;
}

and then you can do float spdRPS = robot.ReturnSPRPS();anywhere without issue.

arduarn:
I believe there are issues using floats inside ISRs on ESP32, you can search for the details.

arduarn:
I believe there are issues using floats inside ISRs on ESP32, you can search for the details.

Thanks, I didn't know about that.
I noticed two float variables when making the MRE, after removing them everything works well.

gfvalvo:
Please post a small, complete MRE that will actually compile.

First time I heard about MRE. Nice concept. I'll be sure to use it in the future.

Deva_Rishi:
float spdRPS = robotOnLine::_speedRPS;So where is this line ? in an ISR, ok, but in an ISR inside the class ? Then it should just be

The ISR function isn't inside the library class, I will try to join it.

Deva_Rishi:
It looks as if you've change a private variable into a public one (with the _ in front of the name it is convention that it is private)
A safer way would be to add a function

float ReturnSPRPS() {

return _speedRPS;
}


and then you can do `float spdRPS = robot.ReturnSPRPS();`anywhere without issue.

I just named all global variables used in the library with the underscore in front of the name, I just followed the Arduino library guide and learned about the underscore somewhere else. A bad Idea to name all witht he underscore I see.

The ESP32 really has issues with floats, I guess I'll have to make use of two integer variables to do a workaround.

F1_:
The ESP32 really has issues with floats, I guess I'll have to make use of two integer variables to do a workaround.

Well, you could also use a double which I think has a software implementation and won't cause the issue, but really the usual advice is to keep your ISRs as short and fast as possible, so doing a load of FP arithmetic is not recommended. You could just set a flag variable in the ISR, then poll on it and do the processing in the non-ISR code.

arduarn:
Well, you could also use a double which I think has a software implementation and won't cause the issue, but really the usual advice is to keep your ISRs as short and fast as possible, so doing a load of FP arithmetic is not recommended. You could just set a flag variable in the ISR, then poll on it and do the processing in the non-ISR code.

The float was just to have more accuracy, integers will do the job as well.
In this kind of library, I really can't set a flag because depending on how other people use it this flag may not be checked.

The ESP32 really has issues with floats, I guess I'll have to make use of two integer variables to do a workaround.

accessing and modifying multi-byte variables within an ISR always has the risk that part of the variable has already been processed, but the other part not, though i think in this case it is not completely relevant.

The ISR function isn't inside the library class, I will try to join it.

Yes please do, and while you are at it, please elaborate on why you didn't to begin with.

The float was just to have more accuracy, integers will do the job as well.

you could always store the 4 bytes of the float into a 32-bit variable of any kind using memcpy, and convert it back inside the ISR.

Deva_Rishi:
accessing and modifying multi-byte variables within an ISR always has the risk that part of the variable has already been processed, but the other part not

That's backwards. It's only a risk to access a variable in code that could be interrupted by an ISR that modifies that variable. Even then it's only a risk if said access is non-atomic. For instance access to 32-bit variables is atomic on a 32-bit processor.

If necessary, risk is handled by temporarily disabling interrupts long enough to do the access.

F1_:
First time I heard about MRE. Nice concept. I'll be sure to use it in the future.

Why not do it now?

Deva_Rishi:
accessing and modifying multi-byte variables within an ISR always has the risk that part of the variable has already been processed, but the other part not, though I think in this case it is not completely relevant.

I disabled the interrupts when the main application access the same variables the ISR do, it should do fine.

Deva_Rishi:
Yes please do, and while you are at it, please elaborate on why you didn't to begin with.

The thing is, the way the timer is initialized doesn't allow for the function to be associated with a class. Or at least that's what I understood from the following message:

"{
"resource": "/d:/Users/abelt/Documents/PlatformIO/Projects/LineRobot Rev2/lib/RobotOnLine/robotOnLine.cpp",
"owner": "C/C++",
"severity": 8,
"message": "argument of type \"void (robotOnLine::*)()\" is incompatible with parameter of type \"void (*)()\"",
"startLineNumber": 1515,
"startColumn": 31,
"endLineNumber": 1515,
"endColumn": 31
}"

I may switch to a different kind of interrupt, I was hoping to do PWM changes inside the ISR but after reading this post I learned it wasn't possible.

gfvalvo:
Why not do it now?

The issue has been solved already, it was the use of a float variable inside the ISR function.