arkhipenko / TaskScheduler

Cooperative multitasking for Arduino, ESPx, STM32, nRF and other microcontrollers
http://playground.arduino.cc/Code/TaskScheduler
BSD 3-Clause "New" or "Revised" License
1.21k stars 221 forks source link

fluently change task interval, within another tasks periodicity #161

Closed sadilekivan closed 1 year ago

sadilekivan commented 1 year ago

I'm setting a task interval with a 100ms read task for an analog input. This analog input calculates the interval of another task that is enabled and disabled by buttons. I can't seem to get the desired result with the Task::setInterval function as it is now. Setting the Interval every 100 ms just keeps my task from ever running again, because it recalculates the delay. Commenting out delay(); helped my issue.

I just wanted to mention my case of it being useful to just change the interval of a task, without recalculating iDelay. I expected that behavior from setInterval, but had to fiddle with it. Perhaps there would be a use for two functions to set the Interval. One that does it with no precautions and one that recalculates the delay.

My changes:

/** Sets the execution interval.
 * Task execution is delayed for aInterval
 * Use  enable() to schedule execution ASAP
 * @param aInterval - new execution interval
 */
void Task::setInterval (unsigned long aInterval) {
#ifdef _TASK_THREAD_SAFE
    iMutex++;
#endif  // _TASK_THREAD_SAFE

    iInterval = aInterval;
    //delay(); // iDelay will be updated by the delay() function

#ifdef _TASK_THREAD_SAFE
    iMutex--;
#endif  // _TASK_THREAD_SAFE
}

I would also mention that I can get my desired behavior as is by changing the interval only when the analogue value changes, but it still makes some delays that add up when I make a big change or there is noise. And driving a pump with it makes calibrating the frequency a bit more noisy.

For reference here is my code:

#include <Arduino.h>
#include <EasyButton.h>
#include <ResponsiveAnalogRead.h>
#include <TaskScheduler.h>

#define PULSE_BUTTON_PIN 3
#define TOGGLE_BUTTON_PIN 4
#define SOLID_STATE_RELAY_PIN 2
#define DELAY_TRIMMER_PIN A6

#define DELAY_LOWER_LIMIT 10
#define DELAY_UPPER_LIMIT 2000

Scheduler runner;

void button_read_function();
void drive_pump_function();

Task button_read_task(5, TASK_FOREVER, &button_read_function);
Task drive_pump_task(DELAY_UPPER_LIMIT, TASK_FOREVER, &drive_pump_function);

EasyButton button_pulse(PULSE_BUTTON_PIN);
EasyButton button_toggle(TOGGLE_BUTTON_PIN);
ResponsiveAnalogRead trimmer_delay(DELAY_TRIMMER_PIN, true);

bool drive_pump = false;
bool toggle_state = false;
bool pump_state = false;
int drive_pump_delay = 0;

void start_pump() {
  Serial.print("Starting pump, delay ");
  Serial.print(drive_pump_delay);
  Serial.println(" ms");
  drive_pump_task.enable();
}

void stop_pump() {
  Serial.println("Stopping pump");
  drive_pump_task.disable();
  pump_state = false;
  digitalWrite(SOLID_STATE_RELAY_PIN, false);
}

void button_read_function() {
  button_pulse.read();
  button_toggle.read();
  trimmer_delay.update();

  drive_pump_delay = map(trimmer_delay.getValue(), 0, 1023, DELAY_LOWER_LIMIT, DELAY_UPPER_LIMIT);
  // Had to comment out delay from the function below for this to work
  drive_pump_task.setInterval(drive_pump_delay);
  // Or this would have worked too
  /*if (drive_pump_task.getInterval() != drive_pump_delay) {
    drive_pump_task.setInterval(drive_pump_delay);
  }*/

  if (!toggle_state) {
    if (button_pulse.wasPressed()) {
      start_pump();
    } else if (button_pulse.wasReleased()) {
      stop_pump();
    }
  }

  if (button_toggle.wasPressed()) {
    toggle_state = !toggle_state;
    if (toggle_state) {
      start_pump();
    } else {
      stop_pump();
    }
  }
}

void drive_pump_function() {
  pump_state = !pump_state;
  digitalWrite(SOLID_STATE_RELAY_PIN, pump_state);
  //drive_pump_task.setInterval(drive_pump_delay);
}

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

  runner.init();
  runner.addTask(button_read_task);
  runner.addTask(drive_pump_task);
  button_read_task.enable();
}

void loop() {
  runner.execute();
}
arkhipenko commented 1 year ago

hi @sadilekivan. I understand your use case.

However, it is not trivial: The task schedule is driven by a current requested delay, which can be the same as the currently requested interval or different in case an explicit delay(delayTime) was called. If the task for which you are changing the interval is not active, then it is simple - delay and interval are the same. However (and I realize this may not be your case, but I have to think about the general use of the library), the task you are changing the interval for is currently delayed for a different interval - what should be done then? Example: let's say your interval is 100 ms and your current delay is 50 ms, and you are 25 ms into the interval. Your task should run in 25 ms. If you change the interval to 200 ms, what happens to the current task? Should it still run in 25 ms? Or should it also be recalculated to be 125 ms from now? I think the safe way is to give programmers a choice:

I will push v3.8.0 into the test branch soon for you to play with and confirm

sadilekivan commented 1 year ago

Thanks for the explanation, I understand the point of delay and agree with the solution of having a choice. Happy to test when it will be possible!

arkhipenko commented 1 year ago

Just pushed 3.8.0 into testing branch - whenever you have time!

arkhipenko commented 1 year ago

void Task::setIntervalNodelay (unsigned long aInterval, unsigned int aOption) {

Options:

#define TASK_INTERVAL_KEEP      0
#define TASK_INTERVAL_RECALC    1
#define TASK_INTERVAL_RESET     2
sadilekivan commented 1 year ago

Sorry for the day delay. The function works great and since I have no specific task delays in my code I keep the default argument TASK_INTERVAL_KEEP, works just as I'd expect, thanks!