[SOLVED] Software Timer .isActive() returns false even when enabled

I’ve been having trouble getting the Software Timer isActive() function to evaluate as true

I built a simple test program that demonstrates the problem. Can anyone figure out why “Waiting for timer to expire” is never printed to the Serial monitor in this code, but the on-board LED turns on after 5 seconds?

#include "Particle.h"

SYSTEM_MODE(SEMI_AUTOMATIC);

void timerCallback();  //Forward declaration of timer callback function

Timer myTimer(1000, timerCallback); //Declare/define the software timer

void timerCallback(){
  myTimer.stopFromISR();
  digitalWrite(D7, HIGH);
}

void setup(){

  //Start the serial monitor
  Serial.begin(9600);

  pinMode(D7, OUTPUT);
  digitalWrite(D7, LOW);

  //Wait for user to enter a character into the serial monitor before continuing
  while(!Serial.available()){};

  Serial.println("Configuring the timer...");

  //Configure the timer
  myTimer.changePeriod(5000);
  myTimer.reset();
  myTimer.start();

  Particle.process();

  Serial.println("Now loop until the timer is no longer active");

  //Loop until the myTimer.isActive() is false
  boolean keepLooping = true;

  while(keepLooping){

    if(myTimer.isActive()){
      Serial.println("Waiting for timer to expire");
      delay(250);
    }
    else{
      Serial.println("myTimer.isActive() = false \n Now exiting loop!");
      keepLooping = false;
    }
  };
}

void loop(){
}

Here is the resultant output to the Serial monitor:

Configuring the timer...
Now loop until the timer is no longer active
myTimer.isActive() = false
Now exiting loop!

Hmm… that’s strange. When I copied and pasted your code, I do get “Waiting for timer to expire” printed out 20 times, followed by “myTimer.isActive() = false Now exiting loop!”. What version of the software are you using?

1 Like

I’m using 0.5.3 you?

I’m also using 0.5.3, and I tested your code on a Photon.

I have just confirmed that I can get the expected behaviour if I compile the code and download the binary from the Web IDE.

This method works for 0.5.2 and 0.5.3

I am at a loss… I am not building locally, so shouldn’t the binary files be the same whether I am using Particle Dev or the Particle Web IDE?

They will be the same as long you target the same system and build the same project.

If it doesn't, there might be some other reason not yet considered.

A starting point could be to put the device into Safe Mode before flashing.
Also changing something obvious in your code (e.g. printing version number) to confirm your device is actually running the most recent code might be good.

1 Like

Okay, so now its not working again for some reason. It would appear that whatever caused it to work before was not related to whether I downloaded the binary from Web IDE vs using Particle Dev.

It would appear that it works sometimes and not others, and I can't figure out what I am doing differently when it does work.

How do you flash an Electron that is in safe mode? From what I can see, particle flash --usb and particle flash --serial don't work when the Electron is in safe mode... unless you meant over the air flash.

Particle Build is down at the moment so I'll have to wait til another time to try an OTA flash while is safe mode.

I thought you were talking about OTA.
And I had no clue you were talking about an Electron either.

Okay, here’s an interesting update. It would appear that if I put similar code in the main loop() that I get a mixed result; timer.isActive() doesn’t work the first time through the loop, but timer.isActive() does work the second time through the loop. It would appear that timer.isActive() only switches to true in between loops.

Here is my code:

#include "Particle.h"

SYSTEM_MODE(SEMI_AUTOMATIC);

void timerCallback();  //Forward declaration of timer callback function

Timer myTimer(1000, timerCallback); //Declare/define the software timer

void timerCallback(){
  myTimer.stopFromISR();
  digitalWrite(D7, HIGH);
}

void setup(){

  //Start the serial monitor
  Serial.begin(9600);

  pinMode(D7, OUTPUT);
  digitalWrite(D7, LOW);

  //Wait for user to enter a character into the serial monitor before continuing
  while(!Serial.available()){};

  Serial.println("Detected your keystroke!");

}

void loop(){

  Serial.println("Configuring the timer...");

  //Configure the timer
  myTimer.changePeriod(5000);
  myTimer.reset();
  myTimer.start();

  Serial.println("Now loop until the timer is no longer active");

  //Loop until the myTimer.isActive() is false
  boolean keepLooping = true;

  while(keepLooping){

    if(myTimer.isActive()){
      Serial.println("Waiting for timer to expire");
      delay(1000);
    }
    else{
      Serial.println("myTimer.isActive() = false \n Now exiting loop!");
      keepLooping = false;
    }
  };

}

And here is the output to the serial monitor:

Detected your keystroke!
Configuring the timer...
Now loop until the timer is no longer active
myTimer.isActive() = false
                            Now exiting loop!
Configuring the timer...
Now loop until the timer is no longer active
Waiting for timer to expire
Waiting for timer to expire
Waiting for timer to expire
Waiting for timer to expire
Waiting for timer to expire
myTimer.isActive() = false
                            Now exiting loop!
Configuring the timer...
Now loop until the timer is no longer active
myTimer.isActive() = false
                            Now exiting loop!
Configuring the timer...
Now loop until the timer is no longer active
Waiting for timer to expire
Waiting for timer to expire
Waiting for timer to expire
Waiting for timer to expire
Waiting for timer to expire
myTimer.isActive() = false
                            Now exiting loop!

Okay, so I wrote a really bare bones test program to figure out what’s going on with the timer.isActive() inconsistencies.

I have discovered that it takes about 800 µS after calling myTimer.start() before myTimer.isActive() will return true. So, if you want to use myTimer.isActive(), you need to make sure you wait at least 1 mS before making that call after starting the timer.

That would suggest that the status only gets updated when the FreeRTOS thread dealing with the timers gets to assess the respective timer’s state.

BTW: µS and mS would be microSiemens and milliSiemens (conductivity) - µs and ms would be time units :wink: (clever d… has spoken :hand:)

3 Likes

Aha, that sounds like an explanation:

I'm wondering if there is some kind of documentation that talks about the structure of the Spark Firmware and HAL so that I can come up with an explanation like that on my own next time I encounter oddities in firmware functionality. For example, how do you know that FreeRTOS is running on its own thread? How many threads are running usually?

I can imagine a scenario in which there are 3 threads:

  1. system thread
  2. application thread
  3. FreeRTOS thread

The role of the FreeRTOS thread would be to manage switching between system thread and application thread (in normal operation), or coordinating access to system resources if SYSTEM_THREAD(ENABLED) is set (i.e. when system thread and application thread are running in parallel). In other words, the FreeRTOS thread is running in parallel with the system thread and application thread regardless of what mode we set SYSTEM_THREAD to. Is this accurate?

If this is the general structure of Particle Firmware, then what is the nature of the FreeRTOS and the application thread? For example, in the following code, is the FreeRTOS thread running in parallel to this application code the whole time? Is it switching to the FreeRTOS thread only when calling delayMicroseconds? If I simply replaced delayMicroseconds with a big useless time wasting loop, would the FreeRTOS thread still be able to assess the software timer's state?

#include "Particle.h"

SYSTEM_MODE(SEMI_AUTOMATIC);

void timerCallback();  //Forward declaration of timer callback function

Timer myTimer(1000, timerCallback); //Declare/define the software timer

void timerCallback(){
  myTimer.stopFromISR();
  digitalWrite(D7, HIGH);
}

void setup(){

  //Start the serial monitor
  Serial.begin(9600);

  //Wait for user to enter a character into the serial monitor before continuing
  while(!Serial.available()){};

  //Start the software timer
  myTimer.start();

  while(!myTimer.isActive()){
    Serial.println(micros());
    delayMicroseconds(1);
  }

}

void loop(){

}

@jaza_tom, FreeRTOS doesn’t run on a thread as such in that it is the kernel and manages all aspects of the operating environment. The Cypress (aka Broadcom) WICED library uses FreeRTOS threads to run the communications stack and these run at some of the highest priorities. The Particle system firmware runs on a thread that is higher than the user application thread if enabled. Priorities allow for higher level threads to “preempt” lower level threads (thus the reason why user code can continue running while an OTA download is going on). Software Timers are run in a single FreeRTOS thread meaning all 10 allowable timers must play nice since they are “chained” and each one will not run until the previous timer’s callback has completed.

Below all this are hardware interrupts which are partially managed by FreeRTOS, the system firmware and (with low level programming) the user application. The fundamental hardware ‘tick’ timer which also drive the microseconds timer is always running.

On a single processor, threads don’t run in true parallel. Instead, they are “time sliced” by the FreeRTOS kernel, with each thread given runtime based on it priority level and a number of programmatic controls (eg waiting on a shared resource). The minimum time slice for FreeRTOS is 1ms or 1000us. So the minimum resolution for software timers is 1ms. Thus, the user loop() will be called every 1ms depending on what it’s doing and large delays will not stop the system firmware or FreeRTOS from running. So timer status is only updated every 1ms since both the timer thread and the user thread only run every 1ms.

Sorry for the long winded explanation. Real-time OS stuff is not always easy to explain. If you want to learn more about FreeRTOS, you can always did here!

3 Likes

Amazing explanation, thank you @peekay123 !!!

1 Like