Scheduling Tasks

I gave it a try and wrote the code it’s working for me I don’t see any issues so far. I used this link/reference [ https://www.freertos.org/Documentation/02-Kernel/04-API-references/10-Semaphore-and-Mutexes/12-xSemaphoreTake ] as a starting point and also checked a few other resources online to make sure I understood the semaphore and FreeRTOS task flow correctly.

#include <Arduino.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

// NS traffic light pins
const int R1_pin = 15;
const int G1_pin = 2;
const int Y1_pin = 4;

// EW traffic light pins
const int R2_pin = 18;
const int G2_pin = 19;
const int Y2_pin = 21;

// Binary semaphore ensures only one direction can be active at a time
SemaphoreHandle_t trafficSemaphore;

// functions: separate "what each light state means" 
void NS_Green()  { 
    digitalWrite(G1_pin, HIGH); 
    digitalWrite(Y1_pin, LOW); 
    digitalWrite(R1_pin, LOW); 
}

void NS_Yellow() { 
    digitalWrite(G1_pin, LOW);  
    digitalWrite(Y1_pin, HIGH); 
    digitalWrite(R1_pin, LOW); 
}

void NS_Red()    { 
    digitalWrite(G1_pin, LOW);  
    digitalWrite(Y1_pin, LOW);  
    digitalWrite(R1_pin, HIGH); 
}

void EW_Green()  { 
    digitalWrite(G2_pin, HIGH); 
    digitalWrite(Y2_pin, LOW);  
    digitalWrite(R2_pin, LOW);  
}

void EW_Yellow() { 
    digitalWrite(G2_pin, LOW);  
    digitalWrite(Y2_pin, HIGH); 
    digitalWrite(R2_pin, LOW);  
}

void EW_Red()    { 
    digitalWrite(G2_pin, LOW);  
    digitalWrite(Y2_pin, LOW);  
    digitalWrite(R2_pin, HIGH); 
}


// NS Task: acquires semaphore before turning Green, releases after complete cycle
void NS_Task(void *pvParameters) {
  while (1) {
    // Ensure NS does not conflict with EW: only proceed when semaphore is available
    if (xSemaphoreTake(trafficSemaphore, portMAX_DELAY) == pdTRUE) {

      NS_Green();                        // NS goes Green because it acquired semaphore
      vTaskDelay(pdMS_TO_TICKS(50000));  // stay Green for 50 seconds per traffic rules

      NS_Yellow();                       // Transition to Yellow: warning phase
      vTaskDelay(pdMS_TO_TICKS(10000));  // stay Yellow for 10 seconds

      NS_Red();                          // NS goes Red: now safe for EW to go Green

      // Release semaphore for EW: only after NS completed full cycle
      xSemaphoreGive(trafficSemaphore);
    }
  }
}

// EW Task: symmetric logic to NS Task
void EW_Task(void *pvParameters) {
  while (1) {
    // Ensure EW does not conflict with NS: only proceed when semaphore is available
    if (xSemaphoreTake(trafficSemaphore, portMAX_DELAY) == pdTRUE) {

      EW_Green();                        // EW goes Green after acquiring semaphore
      vTaskDelay(pdMS_TO_TICKS(50000));  // Maintain Green for 50 seconds

      EW_Yellow();                       // Transition to Yellow
      vTaskDelay(pdMS_TO_TICKS(10000));  // Maintain Yellow for 10 seconds

      EW_Red();                          // EW goes Red: handover back to NS

      // Release semaphore for NS
      xSemaphoreGive(trafficSemaphore);
    }
  }
}

void setup() {
  // Initialize all traffic light pins as outputs
  pinMode(R1_pin, OUTPUT); pinMode(G1_pin, OUTPUT); pinMode(Y1_pin, OUTPUT);
  pinMode(R2_pin, OUTPUT); pinMode(G2_pin, OUTPUT); pinMode(Y2_pin, OUTPUT);

  // Start both directions Red initially: safe default state
  NS_Red(); 
  EW_Red();

  // Create binary semaphore: initially available to whichever task runs first
  trafficSemaphore = xSemaphoreCreateBinary();
  xSemaphoreGive(trafficSemaphore);  

  // Create FreeRTOS tasks with equal priority
  xTaskCreate(NS_Task, "NS_Task", 2048, NULL, 1, NULL);
  xTaskCreate(EW_Task, "EW_Task", 2048, NULL, 1, NULL);
}

void loop() {
   // Empty because FreeRTOS tasks control all traffic light behavior.
  // loop() is not needed; the scheduler manages task execution.
}

Looks good so far. You might try thinking of variations that are a bit more complicated to try.

Also, I think your processor is multi-core. One test you could do is pin both tasks to the same core and see if it still works, and if not, what might need to be changed.

1 Like

Thanks a lot for your help. Would it be better to try using a message queue here? Since we already have two independent tasks, I was thinking about creating a third “controller” task that sends messages to NS and EW, basically telling them when to change lights.

Do you think that approach would make more sense?

Putting the “smarts” into one task and the “work” in others, is one option. You may need to remind yourself that this is an exercise, and the fact that the controller could simply change the lights isn’t allowed. You could also have the controller not command yellows, just reds and greens, and the tasks add the yellow phase as part of its work. This does mean the controller needs to wait for completion after a command, which is useful training.

The alternative is that each light task is “smart”, and communicates with the other light tasks (and there might be no master control task) and part of the problem is working out rules that meet the needs (like some direction getting underserved).

1 Like

Okay so the two tasks (NS and EW) coordinate by handshaking through queues.

Each task waits for a START message in its own queue.

When a task receives START , it runs its full cycle (Green, Yellow, Red).

After finishing Red, it sends START to the other task’s queue.

NS Task

  • Wait for START in NS Queue

  • Green (50s)

  • Yellow (10s)

  • Red

  • Send START to EW Queue

EW Task

  • Wait for START in EW Queue

  • Green (50s)

  • Yellow (10s)

  • Red

  • Send START to NS Queue