For loop w/o delay

It's not related to long term behaviour. It's a localized effect, so if for example you had a "blink" type action it might go like this in milliseconds:
1000
1000
...
1000
1000
743
1000
1000
...

and that would happen every 49 days. If it's just an indicator light, yeah, who is going to notice or care?...

A warning for anyone is a special feeling of accomplishment when a few lines of code does something clever or obscure

Yeah, and if you really want to exercise those muscles, do it on something that really needs it, like some incredibly time sensitive low level operation that has zero chance to work any other way. It has no place in high level code, however... when I really can't resist the temptation, what I do, is write both the readable, literal if you like code, and the hack. Then I put both in the code and comment out the readable, literal code. This has two benefits. It makes it easy to understand the hack, and it makes it easy to replace the hack in short time if some problem develops from it later.

Is this acceptable ??

`
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t blinkTask_handle;
const int ledPin = 4;
uint32_t prev;

void blinkTask(void *param)
{
static bool state = false;
while (1)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY ); //called from wait for PID loop
state = !state;
digitalWrite(ledPin, state);
}
}

void setup() {
Serial.begin(115200);
xTaskCreate( blinkTask, "blinkTask", 2048, NULL, 2, &blinkTask_handle);
pinMode(ledPin, OUTPUT);
}

void loop()
{
static int count ;

xTaskNotifyGive(blinkTask_handle);
vTaskDelay( 250 / portTICK_RATE_MS );
count++;

if (count == 10)
{
count = 0;
vTaskDelay(1000 / portTICK_RATE_MS );
}

}
`

Not really, without code tags... :slight_smile: or indentation :slight_smile:

I swear I put code tags in there


#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t blinkTask_handle;
const int ledPin = 4;
uint32_t prev;

void blinkTask(void *param)
{
  static bool state = false;
  while (1)
  {
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY );   //called from wait for PID loop
    state = !state;
    digitalWrite(ledPin, state);
  }
}

void setup() {
  Serial.begin(115200);
  xTaskCreate( blinkTask, "blinkTask", 2048, NULL, 2, &blinkTask_handle);
  pinMode(ledPin, OUTPUT);
}



void loop() {
  static int count ;

  xTaskNotifyGive(blinkTask_handle);
  vTaskDelay( 250 / portTICK_RATE_MS );
  count++;

  if (count == 10)
  {
    count = 0;
    vTaskDelay(1000 / portTICK_RATE_MS );
  }


}

see next post- this code is not right

#define LED 13
bool LEDstate = 0;

void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
}

void loop() {
  if (!(millis()%1000)) {
    LEDstate = !LEDstate;
    // Serial.print(LEDstate);
    digitalWrite(LED, LEDstate);
  }
}
*see next post- this code is not right*

My slow computer was not showing me multiple on/off when millis() was at a 1000 multiple.

@xfpd's example does not blink five times, then pause. Nevermind.

More importantly, it provides an example that illustrates some common problems using millis().

First, the loop can be so fast that the LED is on very briefly. Or off very briefly.

In fact, every time it is turned on, it actually is turned on and off multiple times.

Every time it is turned off, it actually is turned off and on multiple times.

Sometimes it is an even number of times, and the LED stays on or off.

Second, millis() increments by two occasionally. The test will fail, and the LED will be left on (or off) for an additional 1000 ms.

Untitled

Logic analyser trace showing what happens when the LED is turned on. That's a 12.5 kHz pulse train, showing the rate at which the loop is looping. So fast that millis() does not increment.

a7

@noweare, I am not able to compile and run your code.

What exact FreeRTOS library are you using, and what board are you testing on?

a7

Hello,
It is an esp32 dev board made by Espressif. FreeRtos is built-in with esp-arduino core.

Here is another way of doing it where it looks a lot like the original posters

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t blinkTask_handle;
const int ledPin = 4;
uint32_t prev;

void blinkTask(void *param)
{
  int i; //static bool state = false;
  while (1)
  {
    // ulTaskNotifyTake(pdTRUE, portMAX_DELAY );   //called from wait for PID loop
    for (i = 0; i < 5 ; i++)
    {
      digitalWrite(ledPin, HIGH);
      vTaskDelay( 250 / portTICK_RATE_MS );
      digitalWrite(ledPin, LOW);
      vTaskDelay( 250 / portTICK_RATE_MS );
    }
    vTaskDelay(1000);
  }
}

void setup() {
  Serial.begin(115200);
  xTaskCreate( blinkTask, "blinkTask", 2048, NULL, 2, &blinkTask_handle);
  pinMode(ledPin, OUTPUT);
}



void loop() {

}

@noweare your code in #24 does blink and pause.

I did find, however, something odd.

void loop() {
  static int count ;

  Serial.println(millis());

// rest of the loop as was

I could not understand how the RTOS solution functioned. I added the Serial.print() for another reason, but see that it executes not freely as the loop() should run, rather it reports times at the top of the loop to be

505
754
1004
1254
1504

It looks like something is throttling the loop. Was that by design?

Can you give brief explanation of how the RTOS solution times the blinks and the pause?

Could you write it so the loop would run faster?

TIA

a7

@noweare's code in #28 leaves the loop() blank, and the loop() will nicely spam if it has a print statement.

The task for the RTOS is easy to understand, and it seems like using an RTOS at its simplest gives one the ability to delay without blocking, so it would be very easy to take many delay-based sketches and translate them directly to the looks like delay but does not block idea.

a7

Just so I am clear. You are asking why loop only runs every 250 msecs instead of anytime the processor is not busy with other tasks ?

Ok, I see what your saying. That is kinda strange. I will take a look at it.

The vTaskDelay() basically tells the scheduler to go run the highest priority task that is in the ready state.

I was holding back learning freeRTOS because I thought it was gonna be a struggle to re-learn how to program using it.

It was a pretty straight forward and easy to understand. Props to Mr. Barry who wrote the manual and the guide.

Using freeRTOS makes the processor efficient, only running when it has a task in the ready state.

I like software timers

// non-blocking delay another trial if you like struct

enum timer_states { STOP = 0x00,
                    EN = 0x01,
                    RUN = 0x02,
                    CPL = 0x04 };

typedef struct
{
  uint16_t time_is;
  uint16_t time_old;
  uint16_t time_preset;
  uint16_t time_elapsed;
  uint8_t status;
} tTON;

void software_timer(tTON *ton);

tTON ton1, ton2, ton3, ton4;
uint8_t loop_counter_i = 0;
uint8_t led_value = 0;

void setup() {
  ton1.status = EN;
  ton4.status = CPL;  // for debug purpose only via serial.print
  Serial.begin(19200);
}

void loop() {
  // copy actual time
  ton1.time_is =
    ton2.time_is =
      ton3.time_is =
        ton4.time_is = millis();

  // start timer 1
  if (ton1.status == EN) {
    digitalWrite(LED_BUILTIN, HIGH);
    led_value = 1;  // for debug only

    ton1.status = RUN;
    ton1.time_old = ton1.time_is;
    ton1.time_preset = 250;
  }

  // if timer1 goes off
  if (ton1.status == CPL) {
    ton1.status = STOP;

    digitalWrite(LED_BUILTIN, LOW);
    led_value = 0;  // for debug only

    ton2.status = RUN;
    ton2.time_old = ton2.time_is;
    ton2.time_preset = 250;
  }

  // if timer2 goes off
  if (ton2.status == CPL) {
    ton2.status = STOP;

    loop_counter_i++;
    if (loop_counter_i < 5) {
      // looping again
      ton1.status = EN;
    } else {
      // or add extra 1 second
      ton3.status = RUN;
      ton3.time_old = ton3.time_is;
      ton3.time_preset = 1000;
    }
  }

  // if timer3 goes off
  if (ton3.status == CPL) {
    ton3.status = STOP;

    // repeat forever
    loop_counter_i = 0;
    ton1.status = EN;
  }


  // ...
  // here is any non-blocking code

  // debug only
  if (ton4.status == CPL) {
    ton4.status = RUN;
    ton4.time_old = ton4.time_is;
    ton4.time_preset = 100;

    Serial.print(led_value);
    Serial.print("\tton1 = ");
    Serial.print(ton1.time_elapsed);
    Serial.print("\t");
    Serial.print("ton2 = ");
    Serial.print(ton2.time_elapsed);
    Serial.print("\t");
    Serial.print("ton3 = ");
    Serial.print(ton3.time_elapsed);
    Serial.println();
  }

  // put this at the end of loop
  software_timer(&ton1);
  software_timer(&ton2);
  software_timer(&ton3);
  software_timer(&ton4);
}

void software_timer(tTON *ton) {
  uint8_t status;
  uint16_t time_elapsed;

  status = ton->status;
  time_elapsed = ton->time_elapsed;

  if (status == RUN) {
    time_elapsed = ton->time_is - ton->time_old;
    if (time_elapsed >= ton->time_preset) {
      status = CPL;
    }
  }

  ton->status = status;
  ton->time_elapsed = time_elapsed;
}
// EOF

I'll look when I can, but I think you need to set the pin mode of the build in LED to OUTPUT.

a7

In post #24 loop() delays 250ms because I have the statement vTaskDelay( 250 / portTICK_RATE_MS ); in it.

I see. So both vTaskDelay()s operate identically to just using delay().

By placing those RTOS mediated delays in the separate task, we leave other tasks and the loop() running unfettered.

a7

Here's one adapted from Millis() instead of delay and loop() instead of for-loop - #21 by DaveX

You could add an external switch instead of the boolean constant to toggle between blocking an non-blocking modes.

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN,OUTPUT);
}

void loop() {
  const bool doBlocking = true; // choose between blocking or non-blocking code

  const unsigned long quickInterval = 250; //
  const unsigned long slowInterval = 1000;

  if (doBlocking == true) { // use delay() and for(;;)
    for (int i = 0 ; i < 4 ; ++i) {
      toggleLed();
      Serial.print(i);
      Serial.print(" ");
      delay(quickInterval);
    }
    Serial.println("-- blocking");
    delay(slowInterval);

  }  else { // unblocked loop() by:
    // * replacing delays with millis() edge-detectors/state machines
    // * replacing for(;;) loop with a counter state machine

    // state variables to persist values past the end of loop()'s scope
    static int i = 0; // digit to print
    static unsigned long lastDigitMs = -quickInterval; // backdate by -quickInterval for immediate operation
    static unsigned long lastLineMs = 0; // state variable for edge-detecting slow delay

    unsigned long now = millis(); // remember the loop time

    // edge detecting state machine {i,now-las}
    if (i < 4 && now - lastDigitMs >= quickInterval) { // edge-detect interval change
      lastDigitMs = now; // measure digit delay from last digit print
      toggleLed();
      Serial.print(i);
      Serial.print(" ");
      ++i;
      lastLineMs = now; // measure line ending delay from last print time
    }
    // edge-detecting state machine
    if (i >= 4  && now - lastLineMs >= slowInterval) {
      Serial.println( "-- nonblocking");
      i = 0; // reset the count
    }
  }
}

void toggleLed(void){
  digitalWrite(LED_BUILTIN,digitalRead(LED_BUILTIN)==HIGH?LOW:HIGH);
}

Yes, delay() and vTaskDelay() are the same

That gives two flashes.

So 10 gives 5 flashes, but it cheats the last flash out of the extra 250 milliseconds that the OP's code does, and that at least some example code faithfully replicates.

So you gotta turn that up to 11.

Also, no matter the slide switch, it starts in blocking mode. Just heard the food signal, gotta jet.

a7

Oops. I misread the problem, and tried to quickly force it into code from a different discussion. Solving by toggling 11 times would toggle the end state for the pause. Maybe toggle 10 times and absorb the tail-end 250ms into the long delay:

void setup() {
  pinMode(12, INPUT_PULLUP);
  Serial.begin(115200);
  pinMode(LED_BUILTIN,OUTPUT);
}

void loop() {
  bool doBlocking = digitalRead(12)==HIGH; // choose between blocking or non-blocking code

  const unsigned long quickInterval = 250; //
  const unsigned long slowInterval = 1000 +quickInterval;

  if (doBlocking == true) { // use delay() and for(;;)
    for (int i = 0 ; i < 10 ; ++i) {
      toggleLed();
      Serial.print(i);
      Serial.print(" ");
      delay(quickInterval);
    }
    Serial.println("-- blocking");
    delay(slowInterval);

  }  else { // unblocked loop() by:
    // * replacing delays with millis() edge-detectors/state machines
    // * replacing for(;;) loop with a counter state machine

    // state variables to persist values past the end of loop()'s scope
    static int i = 0; // digit to print
    static unsigned long lastDigitMs = -quickInterval; // backdate by -quickInterval for immediate operation
    static unsigned long lastLineMs = 0; // state variable for edge-detecting slow delay

    unsigned long now = millis(); // remember the loop time

    // edge detecting state machine {i,now-las}
    if (i < 10 && now - lastDigitMs >= quickInterval) { // edge-detect interval change
      lastDigitMs = now; // measure digit delay from last digit print
      toggleLed();
      Serial.print(i);
      Serial.print(" ");
      ++i;
      lastLineMs = now; // measure line ending delay from last print time
    }
    // edge-detecting state machine
    if (i >= 10  && now - lastLineMs >= slowInterval) {
      Serial.println( "-- nonblocking");
      i = 0; // reset the count
    }
  }
}

void toggleLed(void){
  digitalWrite(LED_BUILTIN,digitalRead(LED_BUILTIN)==HIGH?LOW:HIGH);
}

Or maybe its better to use a more clear set of state variables than {iteration, timer} to replace the InstructionPointer state variable of the OP code. Perhaps {iteration, led_state, timer} would be enough.

Heh -- I think it is a Wokwi-slide-switch initialization feature: On startup, the simulated hardware acts as if it toggles and bounces. (in the default switch"attrs": {"value":"0","bounce":"1"} where one toggles the switch in the UI to closed during one run and restarts the sketch for the next run)

Debouncing the switch in the .json works:

{
  "version": 1,
  "author": "DaveX",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-uno", "id": "uno", "top": 0, "left": 0, "attrs": {} },
    { "type": "wokwi-slide-switch", "id": "sw1", "top": -80.77, "left": 107.63, "attrs": {"bounce":"0"} }
  ],
  "connections": [
    [ "sw1:2", "uno:GND.1", "green", [ "v23.95", "h-3.01" ] ],
    [ "sw1:3", "uno:12", "green", [ "v0" ] ]
  ],
  "dependencies": {}
}