heliosproj / HeliOS

A community delivered, open source embedded operating system project.
http://www.heliosproj.org
GNU General Public License v2.0
352 stars 42 forks source link

Dynamically Timed Tasks? #20

Closed niektb closed 2 years ago

niektb commented 3 years ago

Hi all!

I'm trying this RTOS to write the software for a digital guitar effect pedal but I ran across some issues (other than the docs not being up-to-date) with regards to a dynamically timed task...

First, I have a timed task that runs every 10ms which reads a pot value (between 0 and 1023) and calculates a delay tempo from it. This sets the timer of the next task accordingly.

long delay_time_us = long(map(delay_time, 0, 1023, 53, 626)) * 1000;
xTaskSetTimer(xTaskGetId("TASKTEMPO"), delay_time_us);

This "TASKTEMPO" is a very simple task that toggles a LED:

void taskTempo(xTaskId id_) {
  if (smode == SMODE_TEMPO)
    analogWrite(pin_rgb_g, 127 * tempo_blink);

  tempo_blink = !tempo_blink;
}

If I only have those two tasks, it works fine. However, when I add a couple more tasks, things start to get a little different. The timing seems still alright but often the task completely skips... Meaning that the LED blinks irregularly... Based on the concept of cooperative scheduling I would assume that the priority is high (as the taskTempo is very short) but it doesn't seem to be the case... Can I somehow assign priorities to tasks or is there a different solution to the issue?

MannyPeterson commented 3 years ago

@niektb I can be more helpful if I can see your code. Are you able to post it? Also, what dev board/MCU are you targeting?

niektb commented 3 years ago

Of course I can! Sorry if it's a bit rough round the edges but it is a heavy work-in-progress (currently working to remove a SPI digipot so you might still see some remnants in the code) :)

Right now I'm targeting an Arduino Nano (with the old bootloader)!

#include <HeliOS_Arduino.h>
#include <avr/pgmspace.h>
#include <SPI.h>

/* PIN DEFINITIONS */
const uint8_t pin_footswitch  = 2;
const uint8_t pin_rgb_b       = 3;
const uint8_t pin_rgb_r       = 5;
const uint8_t pin_rgb_g       = 6;
const uint8_t pin_delay_mod   = 9;
const uint8_t pin_delay_pwm   = 10;
const uint8_t pin_tails       = 8;
const uint8_t pin_delay_time  = A0;
const uint8_t pin_mod_speed   = A1;

/* MODE PARAM */
bool bypass = true;
bool tempo_blink = false;
const uint8_t SMODE_BYPASS  = 0;
const bool BYPASS_NORM    = 0;
const bool BYPASS_TAIL    = 1;
const uint8_t SMODE_TEMPO   = 1;
const bool    TEMPO_POT     = 0;
const bool    TEMPO_TAP     = 1;
const uint8_t SMODE_MOD     = 2;
volatile double mod_speed     = 0.5;
const uint8_t MODE_CONFIG = 0;
const uint8_t MODE_PLAY   = 1;
volatile uint8_t mode = MODE_PLAY;
volatile uint8_t smode = SMODE_TEMPO;

/* MODULATION PARAMETERS */
const uint8_t TRIANGLE = 0;
const uint8_t SINE = 1;
const uint8_t SQUARE = 2;
volatile uint8_t waveform = TRIANGLE;
const PROGMEM uint8_t triangle_wave[1024] = {/*ommitted for readability*/};
const PROGMEM uint8_t sine_wave[1024] = {/*ommitted for readability*/};
const PROGMEM uint8_t square_wave[1024] = {/*ommitted for readability*/};
volatile double wp = 0;

/* DELAY PARAMETERS */
#define MCP_NOP 0b00000000
#define MCP_WRITE 0b00010001
#define MCP_SHTDWN 0b00100001
const uint8_t ssMCPin = 10;

volatile int delay_time = 250;
volatile int delay_time_prev = 0;
unsigned long t_ms = 0;
unsigned long prev_t = 0;

unsigned long first_tap_time;
unsigned long first_release_time;
bool wait_for_release = false;

/* DEBOUNCE PARAMETERS */
const uint8_t debounce_delay = 20;
unsigned long last_debounce_time = 0;
bool fsstate;
bool last_fsstate = HIGH;
bool button_prev = HIGH;

/* HELPER FUNCTIONS */
// SPI write the command and data to the MCP IC connected to the ssPin
void SPIWrite(uint8_t cmd, uint8_t _data, uint8_t ssPin) {
  SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0));
  digitalWrite(ssPin, LOW); // SS pin low to select chip
  SPI.transfer(cmd);        // Send command code
  SPI.transfer(_data);      // Send associated value
  digitalWrite(ssPin, HIGH);// SS pin high to de-select chip
  SPI.endTransaction();
}

double map_mod_speed(int aRead) {
  return pow(10, (double)aRead/1023) * 0.8 - 0.8 + 0.0125;
}

void wp_inc() {
  mod_speed = map_mod_speed(analogRead(pin_mod_speed));
  wp = wp + (1 / mod_speed);

  if (wp > 1023) {
    wp = wp - 1023;
  }
  if (wp < 0)
    wp = 0;
}

const uint8_t * get_wavetable_ptr() {
  if (waveform == TRIANGLE) {
    return triangle_wave;
  } else if (waveform == SINE) {
    return sine_wave;
  } else {  // waveform == SQUARE
    return square_wave;
  }
}

/* TASKS */
void taskButton(xTaskId id_)
{
  // do debounce with ISR
  int button = digitalRead(pin_footswitch);

  // reset debouncing timer if the switch changed, due to noise or pressing:
  if (button != button_prev) {
    last_debounce_time = millis();
  }

  if ((millis() - last_debounce_time) > debounce_delay) {
    if (button != fsstate)
      fsstate = button;

    if (fsstate == LOW && last_fsstate == HIGH)
    { // FALLING EDGE
      first_tap_time = millis();
      wait_for_release = true;
      last_fsstate = fsstate;
    }
    else if (wait_for_release && ((millis() - first_tap_time) >= 1000))
    {
      wait_for_release = false;
      xTaskNotify(xTaskGetId("TASKMODEMAN"), 4, (char *)"SMOD" );
    }
    else if (fsstate == HIGH && last_fsstate == LOW) // RISING EDGE
    {
      if (wait_for_release)
      {
        first_release_time = millis();
        xTaskNotify(xTaskGetId("TASKMODEMAN"), 4, (char *)"STEP" );
        wait_for_release = false;
      }
      last_fsstate = fsstate;
    }
    else { // nothing changed}
  }
  button_prev = button;
}

void update_led_status() {
  analogWrite(pin_rgb_r, 255 * (smode == SMODE_BYPASS));
  analogWrite(pin_rgb_b, 255 * (smode == SMODE_MOD));

  if (smode == SMODE_BYPASS) {
    analogWrite(pin_rgb_g, 127 * bypass);
  } else {}
}

void taskModeMan(xTaskId id_) {
  xTaskGetNotifResult res = xTaskGetNotif(id_);
  if (res) {

    if (strcmp(res->notifyValue, "SMOD") == 0)
    {
      smode++;
      if (smode > SMODE_MOD)
        smode = SMODE_BYPASS;
    }
    else if (strcmp(res->notifyValue, "STEP") == 0)
    {
      if (smode == SMODE_BYPASS)
      {
        bypass = !bypass;
        if (BYPASS_TAIL)
        {
          digitalWrite(pin_tails, bypass);
        }
        else
        {
          digitalWrite(pin_tails, !bypass);
        }

      }
      else if (smode == SMODE_TEMPO)
      {
        xTaskGetInfoResult tres = xTaskGetInfo(xTaskGetId("TASKTEMPO"));
        if (tres) 
        {
          String str = "";
          str += tres->timerInterval;
          Serial.println(str);
        }      
      }
      else // smode == SMODE_MOD
      {}
    }
    update_led_status();
  }
  xMemFree(res);
  xTaskNotifyClear(id_);
}

// Triggers every ~4ms
void taskMod(xTaskId id_)
{
  uint8_t _val = pgm_read_word_near(get_wavetable_ptr() + (int)ceil(wp));
  analogWrite(pin_delay_mod, _val);

  if (smode == SMODE_MOD)
    analogWrite(pin_rgb_b, map(_val, 0, 255, 0, 64));

  wp_inc();
}

void taskDelay(xTaskId id_) {
  delay_time = analogRead(pin_delay_time);

  if (abs(delay_time - delay_time_prev) < 3)
    return;

  uint8_t res = map(delay_time, 0, 1023, 0, 255);
  long delay_time_us = long(map(delay_time, 0, 1023, 53, 626)) * 1000;
  xTaskSetTimer(xTaskGetId("TASKTEMPO"), delay_time_us);

  analogWrite(pin_delay_pwm, res);

  prev_t = t_ms;
  delay_time_prev = delay_time;
}

void taskTempo(xTaskId id_) {
  if (smode == SMODE_TEMPO)
    analogWrite(pin_rgb_g, 127 * tempo_blink);

  tempo_blink = !tempo_blink;
}

void taskSerial(xTaskId id_) {
  xTaskGetNotifResult res = xTaskGetNotif(id_);

  if (res)
    Serial.println(res->notifyValue);

xMemFree(res);
  xTaskNotifyClear(id_);
}

/* MAIN FUNCTIONS */
void setup() {
  Serial.begin(115200);
  Serial.println(F("[VDD Debug Stream]"));
  TCCR1B = (TCCR1B & 0b11111000) | 0x01; // Set PWM Frequency to 31kHz

  pinMode(pin_footswitch, INPUT_PULLUP);
  pinMode(pin_delay_time, INPUT);
  pinMode(pin_mod_speed, INPUT);
  pinMode(pin_delay_pwm, OUTPUT);

  // Initialize RGB LED
  pinMode(pin_rgb_r, OUTPUT);
  pinMode(pin_rgb_g, OUTPUT);
  pinMode(pin_rgb_b, OUTPUT);
  pinMode(pin_tails, OUTPUT);

  update_led_status();

  pinMode (ssMCPin, OUTPUT);
  digitalWrite(ssMCPin, HIGH);
  digitalWrite(pin_tails, HIGH);

  xTaskId id = 0;
  xHeliOSSetup();

  id = xTaskAdd("TASKBUTTON", &taskButton);
  xTaskStart(id);

  id = xTaskAdd("TASKMODEMAN", &taskModeMan);
  xTaskWait(id);

  id = xTaskAdd("TASKMOD", &taskMod);
  xTaskWait(id);
  xTaskSetTimer(id, 4000); // 4ms

  id = xTaskAdd("TASKDELAY", &taskDelay);
  xTaskWait(id);
  xTaskSetTimer(id, 10000); // 10ms

  id = xTaskAdd("TASKTEMPO", &taskTempo);
  xTaskWait(id);
  delay_time = analogRead(pin_delay_time);
  long delay_time_us = long(map(delay_time, 0, 1023, 53, 626)) * 1000;
  xTaskSetTimer(id, delay_time_us);

  id = xTaskAdd("TASKSERIAL", &taskSerial);
  xTaskWait(id);
}

void loop() {
  xHeliOSLoop();
}
niektb commented 3 years ago

Any clues as to where this might go wrong?

MannyPeterson commented 3 years ago

So one quick thing is to make sure you free the mem for tres:

xTaskGetInfoResult tres = xTaskGetInfo(xTaskGetId("TASKTEMPO"));

So taskTempo() starts to execute irregularly right? Have you tried adding one task at a time to see which task may be the culprit? It may be taskSerial() as serial IO takes a lot of clock ticks.

As far as priority goes. Tasks in a waiting state (event driven) always get priority in the scheduler. Tasks in a started state (cooperative) are not always scheduled for execution each cycle. But it appears you only have one cooperative task - taskButton().

Try adding tasks one by one to see if you can find the culprit and let me know what you find. My apologies for the delay getting back to you. I will keep my eyes open for your response.

MannyPeterson commented 3 years ago

@niektb one other thing I noticed is you call xTaskSetTimer() in taskDelay(). Calling xTaskSetTimer() resets the timer.

But it seems you have the below code block to prevent unintended resets.

if (abs(delay_time - delay_time_prev) < 3) return;

Make sure this isn’t causing unintended resets.

niektb commented 3 years ago

@MannyPeterson No probs for the delay! Thanks for coming back to me!

So one quick thing is to make sure you free the mem for tres: xTaskGetInfoResult tres = xTaskGetInfo(xTaskGetId("TASKTEMPO"));

Hmmm, I added the serial.println for debugging in that if-statement. I indeed forgot to free the mem, the issue persists with or without :/

So taskTempo() starts to execute irregularly right? Have you tried adding one task at a time to see which task may be the culprit? It may be taskSerial() as serial IO takes a lot of clock ticks.

Yes indeed! taskSerial isn't called atm (as I removed all debugging print statements via that task, to rule that out :)) it definitely seems to help to reduce the amount of tasks, but couldn't find a particular task that breaks everything :)

As far as priority goes. Tasks in a waiting state (event driven) always get priority in the scheduler. Tasks in a started state (cooperative) are not always scheduled for execution each cycle. But it appears you only have one cooperative task - taskButton().

Try adding tasks one by one to see if you can find the culprit and let me know what you find. My apologies for the delay getting back to you. I will keep my eyes open for your response.

But if multiple tasks are waiting, which tasks get priority? If timed tasks are configured to run at the same interval, which task gets the priority? The one that's started first?

@niektb one other thing I noticed is you call xTaskSetTimer() in taskDelay(). Calling xTaskSetTimer() resets the timer.

But it seems you have the below code block to prevent unintended resets.

if (abs(delay_time - delay_time_prev) < 3) return;

Make sure this isn’t causing unintended resets.

Hmm good point, when I tested it, the ADC reading didn't fluctuate any more than '1', but maybe it's become more noisy since I made other changes in the circuit.

Is there a way to update the timer value without resetting it? That would probably fix the issue as well :)

MannyPeterson commented 2 years ago

@niektb going to look at adding a function call to update the timer value. Adding to the development backlog.

MannyPeterson commented 2 years ago

@niektb my apologies for the time it has taken to get to this - life distractions unfortunately have kept me away. In the next release there will be a function call that allows the user to set the task timer interval without resetting the start time. You will see it in 0.2.8. Closing this for now. Thanks!

void xTaskSetTimerWOReset(TaskId_t, Time_t);
niektb commented 2 years ago

Cool! So what I'm curious about; what is the behaviour if you set the timer to a time that already is expired? :)