Tasks apparently running at the same time in Single Core operation

Yodaperor
Posts: 1
Joined: Thu Jun 20, 2024 1:20 am

Tasks apparently running at the same time in Single Core operation

Postby Yodaperor » Thu Jun 20, 2024 1:44 am

So I'm doing this automatic irrigation project and I've switched from arduino IDE to espressif IDE so I could be completely sure FreeRTOS is running on one core only. My objective is to acquire task starting and ending ticks so I can import this data to a python algorithm that plots a GANTT graph for better visualization of interruptions and whatnot.

For this, I've created a 2 column array for each task, the first column being the starting tick, and the second the ending tick; I've also used xTaskGetTickCount() both at the start of each task's function (the starting one right afther the while, and the ending one right before the vTaskDelay).

The thing is, when I look at the data it says 2 of the tasks always start and end at the same time. I've tried everything I could (I'm new to FreeRTOS and even newer to espressif IDE lol), and no matter what this won't change. I'd appreciate any help at all :mrgreen: .

Code: Select all

// #include <LiquidCrystal_I2C.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"
#include <driver/adc.h>
#include <driver/i2c.h>
#include <esp_log.h>
#include <string>
#include "sdkconfig.h"
#include "HD44780.h"

// Single core config -> ESP32's second core (app_cpu)
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif

// Queue config
static const int queue_len = 5;     // Size
static QueueHandle_t queue_handle;  // Handle

// Mutex config
static SemaphoreHandle_t mutex_handle;

// Timer config
static const TickType_t timer_delay = 200 / portTICK_PERIOD_MS;
static TimerHandle_t software_timer_handle = NULL;

// LCD config

#define rs    19
#define en    23
#define d4    18
#define d5    17
#define d6    16
#define d7    15

// Resistive moisture sensor config
#define umiAnalog ADC1_CHANNEL_0

volatile int umiR; // stores the moisture

// Solenoid valve
#define solenoid_pin GPIO_NUM_26

// ESP32 In-built led -> Testing
static const int led_pin = 2;

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////


const int l_size = 100; // Max tick data stored for each task
// First column of each array is for start ticks, second for end ticks
unsigned long l_umi[l_size][2]; // Moisture
int c_umi = 0; // index

unsigned long l_sol[l_size][2]; // Solenoid valve
int c_sol = 0;

unsigned long l_timer[l_size][2]; // Display timer
int c_timer = 0;

int aux_1 = 0; // Auxiliary variable for limiting array write

long converte(long x, long in_min, long in_max, long out_min, long out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

void tempo(uint32_t ms)
{
    // Convert milliseconds to ticks
    TickType_t delay_ticks = pdMS_TO_TICKS(ms);
    // Get the current tick count
    TickType_t current_ticks = xTaskGetTickCount();
    // Delay the task until the current tick count plus the delay
    vTaskDelayUntil(&current_ticks, delay_ticks);
}

void mostra_tempo(void *parameter){ // A function for printing one array on the serial monitor when it reaches l_size items (max size)
  while (true){
    if (aux_1 == 0 and c_umi == l_size){
      printf("\nSensor, %d\n", c_umi);
      for(int i = 0; i < l_size; i++){
		printf("%lu %lu\n", l_umi[i][0], l_umi[i][1]);
      }
      aux_1 = 1;
    }
    if (aux_1 == 1 and c_sol == l_size){
      printf("\nSolenoid, %d\n", c_sol);
      for(int i = 0; i < l_size; i++){
		printf("%lu %lu\n", l_sol[i][0], l_sol[i][1]);
      }
      aux_1 = 2;
    }
    if (aux_1 == 2 and c_timer == l_size){
      printf("\nTimer display, %d\n", c_timer);
      for(int i = 0; i < l_size; i++){
		printf("%lu %lu\n", l_timer[i][0], l_timer[i][1]);
      }
      aux_1 = 3;
    }
    vTaskDelay(60000 / portTICK_PERIOD_MS); // Delay for preventing this task from overwriting the others ever so often
  }
}


///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////


// Moisture sensor task
void leitura_sensorResistivo(void *parameter) {
  while(1) {
    ///////////////////////////////////////////////////////////////////////////
    if (c_umi < l_size){ // Stores the starting tick
      l_umi[c_umi][0] = xTaskGetTickCount();
    }
    ///////////////////////////////////////////////////////////////////////////
    if (xSemaphoreTake(mutex_handle, portMAX_DELAY) == pdTRUE) {
      umiR = adc1_get_raw(umiAnalog); // Reads moisture 
      if (xQueueSend(queue_handle, (void *)&umiR, 10) != pdTRUE) {
        printf("ERROR: Sending of data to Queue incomplete\n");
      }
      xSemaphoreGive(mutex_handle);
    } else {
      printf("ERROR: MUTEX acquisition incomplete at the sensor\n");
    }
    tempo(50); // So that the task takes more than a few ticks
    ///////////////////////////////////////////////////////////////////////////
    if (c_umi < l_size){ // Stores the ending tick
      l_umi[c_umi][1] = xTaskGetTickCount();
      c_umi++;
    }
    ///////////////////////////////////////////////////////////////////////////
    // Sensor delay
    vTaskDelay(150 / portTICK_PERIOD_MS);
  }
}

// Função para controle da válvula solenóide
void valv_control(void *parameter) {
  while(1) {
    ///////////////////////////////////////////////////////////////////////////
    if (c_sol < l_size){ // Stores the starting tick
      l_sol[c_sol][0] = xTaskGetTickCount();
    }
    ///////////////////////////////////////////////////////////////////////////
    int rcv_data;
    // Limite de umidade
    const int limite_umidade_superior = 50; // "Moist" (Upper) moisture limit
    const int limite_umidade_inferior = 35; // "Dry" (Lower) moisture limit    // Caso tenha algo novo na fila considere para fazer o controle da válvula
    if (xQueueReceive(queue_handle, (void *)&rcv_data, 10) == pdTRUE) {
      int umidade_medida = converte(umiR, 4095, 2100, 0, 100);
      if (umidade_medida < limite_umidade_inferior && gpio_get_level(solenoid_pin) == 0) {
        printf("Starting solenoid\n");
        gpio_set_level(solenoid_pin, 1);
      } else if (umidade_medida >= limite_umidade_superior && gpio_get_level(solenoid_pin) == 1) {
        printf("Shutting solenoid off\n");
        gpio_set_level(solenoid_pin, 0);
      }
    }
    tempo(50);
    ///////////////////////////////////////////////////////////////////////////
    if (c_sol < l_size){ // Stores the ending tick
      l_sol[c_sol][1] = xTaskGetTickCount();
      c_sol++;
    }
    ///////////////////////////////////////////////////////////////////////////
    // Task delay
    vTaskDelay(150 / portTICK_PERIOD_MS);
  }
}

// Timer callback
void softwareTimer_callback(TimerHandle_t xTimer) {
  ///////////////////////////////////////////////////////////////////////////
  if (c_timer < l_size){ // Stores the starting tick
    l_timer[c_timer][0] = xTaskGetTickCount();
  }
  ///////////////////////////////////////////////////////////////////////////
  if (xSemaphoreTake(mutex_handle, portMAX_DELAY) == pdTRUE) {
    // Moisture value at the display
    int umidade = converte(umiR, 4095, 2000, 0, 100); // Converts the digital value to moisture percentage
    //LCD_setCursor(0,1);
    //LCD_clearScreen();
    //LCD_writeStr("Moisture: ");
    char str_umi = static_cast<char>(umidade);
    //LCD_writeStr(str_umi);
    xSemaphoreGive(mutex_handle);
  } else {
    printf("ERROR: MUTEX acquisition incomplete at the Display\n");
  }
  tempo(50);
  ///////////////////////////////////////////////////////////////////////////
  if (c_timer < l_size){ // Stores the ending tick
    l_timer[c_timer][1] = xTaskGetTickCount();
    c_timer++;
  }
  ///////////////////////////////////////////////////////////////////////////
}

extern "C" void app_main() {

  // Delay
  vTaskDelay(500 / portTICK_PERIOD_MS);
  
  adc1_config_width(ADC_WIDTH_BIT_12);
  adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0);

  printf("-- Starting irrigation system --\n");

  // LCD configs
  //LCD_init(0x27, 19, 18, 16, 2);

  // Solenoid valve pin configuration
  // pinMode(solenoid_pin, OUTPUT);
  gpio_set_direction(solenoid_pin, GPIO_MODE_OUTPUT);
  gpio_set_level(solenoid_pin, 0); // Valve starts closed

  // Creating the queue
  queue_handle = xQueueCreate(queue_len, sizeof(int));
  if (queue_handle == NULL) {
    printf("ERROR: Failed queue creation\n");
    while (1);
  }

  // Creating the mutex
  mutex_handle = xSemaphoreCreateMutex();
  if (mutex_handle == NULL) {
    printf("ERROR: Failed mutex creation\n");
    while (1);
  }

  // Creating the software timer
  software_timer_handle = xTimerCreate("software_timer_handle", timer_delay, pdTRUE, (void *)0, softwareTimer_callback);  // Parâmetros: Name of timer, Period of timer (in ticks), Auto-reload, Timer ID, Callback function
  if (software_timer_handle == NULL) {
    printf("ERROR: Failed software timer creation\n");
    while (1);
  }

  // Starting the timer
  xTimerStart(software_timer_handle, portMAX_DELAY); // Blocks the task until a command is successfully given to the timer queue

  // Task creation
  xTaskCreatePinnedToCore(
              leitura_sensorResistivo,     // Function
              "leitura_sensorResistivo",   // Name
              1024,                        // Stack size (bytes in ESP32, words in FreeRTOS)
              NULL,                        // Parameters
              1,                           // Task priority (0 to configMAX_PRIORITIES - 1)
              NULL,                        // Handle
              app_cpu);                    // Single core

  xTaskCreatePinnedToCore(
              valv_control,            // // Function
              "valv_control",          // // Name
              1024,                    // // Stack size (bytes in ESP32, words in FreeRTOS
              NULL,                    // // Parameters
              1,                       // // Task priority (0 to configMAX_PRIORITIES - 1)
              NULL,                    // // Handle
              app_cpu);                // // Single core

  xTaskCreatePinnedToCore(
              mostra_tempo,
              "mostra_tempo",
              4096,
              NULL,
              2,
              NULL,
              app_cpu
  );

  // Delete "setup and loop" task
  vTaskDelete(NULL);
}
Thanks in advance! :mrgreen:

ESP_Sprite
Posts: 9764
Joined: Thu Nov 26, 2015 4:08 am

Re: Tasks apparently running at the same time in Single Core operation

Postby ESP_Sprite » Fri Jun 21, 2024 6:05 am

I can't exactly tell what you're doing, but vTaskDelay probably plays a role... say you have two tasks running. The first one will call vTaskDelay, which tells the scheduler that that task shouldn't run for a given amount of time. The CPU has now time to do something else, so it will start the 2nd task. (Note that this happens within the same FreeRTOS tick, as the 1st yielded early by calling vTaskDelay). The 2nd one also calls vTaskDelay, meaning the scheduler suspends it and the CPU just sits there twiddling its thumbs. Because both tasks delayed the same time, they also wake up at the same time and the entire thing starts over.

MicroController
Posts: 1734
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Tasks apparently running at the same time in Single Core operation

Postby MicroController » Mon Jun 24, 2024 10:50 am

so I could be completely sure FreeRTOS is running on one core only
What are you trying to achieve by running single-core?
Note that "Tasks apparently running at the same time" pretty accurately describes the whole point of a multi-tasking OS like FreeRTOS, which it makes happen irrespective of the number of CPUs/cores.

Who is online

Users browsing this forum: No registered users and 123 guests