Page 1 of 1

portYIELD_FROM_ISR() is not working

Posted: Tue Feb 18, 2020 12:16 am
by mikelisfbay
Hi anyone,

I have trouble with portYIELD_FROM_ISR(). I expect it to block the current running task2 so that the main loop task can immdiately process the data from External Input ISR.

Thank you,
Michael

Yellow wave : External Input (GPIO16)
Blue wave: Main Task Output Signal (GPIO17)
Black wave: Task 2 Output Signal (GPIO27)

Task 2 is not yielding to the main loop task as expected. portYIELD_FROM_ISR() seems to be not working.
esp32_portyield_issue.png
esp32_portyield_issue.png (21.39 KiB) Viewed 9453 times
  1. #define LED 27  // task led
  2. #define LED2 17  // task 2 led
  3.  
  4. const byte interruptPin = 16;
  5. volatile int interruptCounter = 0;
  6. int numberOfInterrupts = 0;
  7.  
  8. volatile SemaphoreHandle_t extSemaphore;
  9. portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
  10.  
  11. void IRAM_ATTR handleInterrupt() {
  12.   BaseType_t xHigherPriorityTaskWoken;
  13.  
  14.   xHigherPriorityTaskWoken = pdFALSE;
  15.  
  16.   portENTER_CRITICAL_ISR(&mux);
  17.   interruptCounter++;
  18.   portEXIT_CRITICAL_ISR(&mux);
  19.   // Give a semaphore that we can check in the loop
  20.   xSemaphoreGiveFromISR(extSemaphore, &xHigherPriorityTaskWoken);
  21.   // It is safe to use digitalRead/Write here if you want to toggle an output
  22.  
  23.   //portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  24.   portYIELD_FROM_ISR();
  25.   /*
  26.   if (xHigherPriorityTaskWoken) {
  27.     portYIELD_FROM_ISR();  
  28.   }
  29.   */
  30. }
  31.  
  32. void setup() {
  33.   pinMode(LED, OUTPUT);  
  34.   digitalWrite(LED, LOW);
  35.   pinMode(LED2, OUTPUT);
  36.   digitalWrite(LED2, LOW);
  37.  
  38.   Serial.begin(115200);
  39.   // Create semaphore to inform us when the external input trigger has been detected.
  40.   extSemaphore = xSemaphoreCreateBinary();  
  41.   Serial.println("Monitoring interrupts: ");
  42.   pinMode(interruptPin, INPUT_PULLUP);
  43.   attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);  
  44.  
  45.  #define ESP32_RUNNING_CORE  1 /* Core 1 required for OLED display */    
  46.  
  47.  xTaskCreatePinnedToCore(  
  48.       task2,                    /* Task function. */
  49.       "task2",                  /* name of task. */
  50.       10000,                    /* Stack size of task */
  51.       NULL,                     /* parameter of the task */
  52.       2,                        /* priority of the task */
  53.       NULL,                   /* Task handle to keep track of created task */
  54.       ESP32_RUNNING_CORE // Fix
  55.       );  
  56. }
  57.  
  58. void loop() {
  59.  
  60.   // If Timer has fired
  61.   if (xSemaphoreTake(extSemaphore, 0) == pdTRUE){  
  62.       uint32_t isrCount = 0;
  63.  
  64.       // Read the interrupt count
  65.       portENTER_CRITICAL(&mux);
  66.       isrCount = interruptCounter;  
  67.       portEXIT_CRITICAL(&mux);    
  68.  
  69.      
  70.       if (isrCount % 2 == 0)
  71.         digitalWrite(LED, LOW);
  72.       else
  73.         digitalWrite(LED, HIGH);        
  74.      
  75.       Serial.print("An interrupt has occurred. Total: ");
  76.       Serial.println(isrCount);
  77.   }
  78. }
  79.  
  80. /* this function will be invoked when additionalTask was created */
  81. void task2( void * parameter )
  82. {
  83.   /* loop forever */
  84.   for(;;){
  85.    
  86.     for (int i=0; i<500000; i++) {  // short active time
  87.         digitalWrite(LED2, HIGH);    // LED2 =1 (Task2 is running.
  88.     }
  89.     digitalWrite(LED2, LOW);    
  90.     delay(10);
  91.    
  92.   }
  93.   /* delete a task when finish,
  94.   this will never happen because this is infinity loop */
  95.   vTaskDelete( NULL );
  96. }

Re: portYIELD_FROM_ISR() is not working

Posted: Tue Feb 18, 2020 7:05 am
by mikelisfbay
Hi all,

I found that I made a mistake. The task that processes, the interrupt data from ISR, needs to have the highest priority. So, the portYIELD_FROM_ISR() can switch the context for that high priority task before the ISR's exit. I misunderstood the example in the FreeRTOS manual.

I changed my code. It is running good now. There is no problem with portYIELD_FROM_ISR().

Best,
Michael

Re: portYIELD_FROM_ISR() is not working

Posted: Wed Feb 19, 2020 1:05 pm
by idahowalker
Under freeRTOS loop() should be empty.

Code: Select all

/*
   Project, use solar cells to generate power
   2/2/2020

*/
// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_timer.html
///esp_timer_get_time(void) //gets time in uSeconds like Arduino Micros, not tested. see above link
//////// http://www.iotsharing.com/2017/09/how-to-use-arduino-esp32-can-interface.html
#include "sdkconfig.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
// #include "esp32/ulp.h"
// #include "driver/rtc_io.h"
#include <SimpleKalmanFilter.h>
#include <driver/adc.h>
#include "esp_sleep.h"
#include "driver/mcpwm.h"
const int TaskCore1 = 1;
const int TaskCore0 = 0;
const int TaskStack20K = 20000;
const int Priority3 = 3;
const int Priority4 = 4;
const int SerialDataBits = 115200;
volatile bool EnableTracking = true;
////
//
////
void setup()
{
  Serial.begin( 115200 );
  // https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
  // set up A:D channels
  adc_power_on( );
  vTaskDelay( 1 );
  adc1_config_width(ADC_WIDTH_12Bit);
  // ADC1 channel 0 is GPIO36 (ESP32), GPIO1 (ESP32-S2)
  adc1_config_channel_atten(ADC1_CHANNEL_0 , ADC_ATTEN_DB_11);
  // ADC1_CHANNEL_3  ADC1 channel 3 is GPIO39 (ESP32)
  adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11);
  // ADC1 channel 5 is GPIO33
  adc1_config_channel_atten(ADC1_CHANNEL_5, ADC_ATTEN_DB_11);
  // ADC1 channel 6 is GPIO34
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
  // adc for light dark detection, ADC1 channel 7 is GPIO35 (ESP32)
  adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11);
  //
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_NUM_4 ); // Azimuth
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_NUM_12 ); // Altitude servo
  //  ////
  mcpwm_config_t pwm_config = {};
  pwm_config.frequency = 50;    //frequency = 50Hz, i.e. for every servo motor time period should be 20ms
  pwm_config.cmpr_a = 0;    //duty cycle of PWMxA = 0
  pwm_config.cmpr_b = 0;    //duty cycle of PWMxb = 0
  pwm_config.counter_mode = MCPWM_UP_COUNTER;
  pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);    //Configure PWM0A timer 0 with above settings
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config);    //Configure PWM0A timer 1 with above settings
  //  ////
  fMoveAltitude( 1500 );
  vTaskDelay( 10 );
  fMoveAzimuth( 1500 );
  vTaskDelay(10);
  ////
  xTaskCreatePinnedToCore( TrackSun, "TrackSun", TaskStack20K, NULL, Priority3, NULL, TaskCore1 ); // assigned to core
  xTaskCreatePinnedToCore( fDaylight, "fDaylight", TaskStack20K, NULL, Priority3, NULL, TaskCore0 ); // assigned to core
  esp_sleep_enable_timer_wakeup( 5000000 ); // set timer to wake up once a second
}
//
void fDaylight( void * pvParameters )
{
  // int lightLevel = 0;
  while (1)
  {
    vTaskDelay( 1000 );
    if ( adc1_get_raw(ADC1_CHANNEL_7) <= 500 )
    {
      // if light level to low park boom
      fMoveAltitude( 1500 );
      vTaskDelay( 12 );
      fMoveAzimuth( 1500 );
      vTaskDelay(12);
      // put esp32 into light sleep
      EnableTracking = false;
      esp_light_sleep_start();
    } else {
      EnableTracking = true;
    }
  }
} // void fDaylight( void * pvParameters )
////
/**
   @brief Use this function to calcute pulse width for per degree rotation
   @param  degree_of_rotation the angle in degree to which servo has to rotate
   @return
       - calculated pulse width
*/
////
void mcpwm_gpio_initialize(void)
{
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_NUM_4 );
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_NUM_12 );
}
////
void TrackSun( void * pvParameters )
{
  int Altitude = 1500;
  int Azimuth = 1500;
  int maxAltitudeRange = 2144;
  int minAltitudeRange = 900;
  int maxAzimuthRange = 2144;
  int minAzimuthRange = 900;
  SimpleKalmanFilter kfAltitude0( 5.0, 10.0, .01 ); // kalman filter Altitude 0
  SimpleKalmanFilter kfAltitude1( 5.0, 10.0, .01 ); // kalman filter Altitude 1
  SimpleKalmanFilter kfAzimuth0( 5.0, 5.0, .01 ); // kalman filter Azimuth 0
  SimpleKalmanFilter kfAzimuth1( 5.0, 5.0, .01 ); // kalman filter Azimuth 1
  float filteredAltitude_0 = 0.0f;
  float filteredAltitude_1 = 0.0f;
  float filteredAzimuth_0 = 0.0f;
  float filteredAzimuth_1 = 0.0f;
  int64_t AzimuthEndTime = esp_timer_get_time();
  int64_t AzimuthStartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros,
  int64_t AltitudeEndTime = esp_timer_get_time();
  int64_t AltitudeStartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros,
  //  Serial.println( Azimuth );
  float AltitudeThreashold = 80.0f;
  float AzimuthThreashold = 60.0f;
  while (1)
  {
    if ( EnableTracking )
    {
      //Altitude
      AltitudeEndTime = esp_timer_get_time() - AltitudeStartTime; // produce elasped time for the simpleKalmanFilter
      kfAltitude0.setProcessNoise( (float)AltitudeEndTime / 1000000.0f ); //convert time of process to uS, update SimpleKalmanFilter q
      kfAltitude1.setProcessNoise( (float)AltitudeEndTime / 1000000.0f ); //convert time of process to uS, update SimpleKalmanFilter q
      filteredAltitude_0 = kfAltitude0.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_3) );
      filteredAltitude_1 = kfAltitude1.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_0) );
      if ( (filteredAltitude_0 > filteredAltitude_1) && (abs(filteredAltitude_0 - filteredAltitude_1) > AltitudeThreashold))
      {
        // Serial.println( "filteredAltitude_0 > filteredAltitude_1" );
        Altitude -= 1;
        if ( Altitude < minAltitudeRange )
        {
          Altitude = 1500;
        }
        fMoveAltitude( Altitude );
        vTaskDelay( 12 );
        AltitudeStartTime = esp_timer_get_time();
      }
      if ( (filteredAltitude_0 < filteredAltitude_1) && (abs(filteredAltitude_0 - filteredAltitude_1) > AltitudeThreashold) )
      {
        // Serial.println( "filteredAltitude_0 < filteredAltitude_1" );
        Altitude += 1;
        if ( Altitude >= maxAltitudeRange )
        {
          Altitude = 1500;
        }
        fMoveAltitude( Altitude );
        vTaskDelay( 12 );
        AltitudeStartTime = esp_timer_get_time();
      }
      // Serial.println();
      //// AZIMUTH
      AzimuthEndTime = esp_timer_get_time() - AzimuthStartTime; // produce elasped time for the simpleKalmanFilter
      kfAzimuth0.setProcessNoise( (float)AzimuthEndTime / 1000000.0f ); //convert time of process to uS, update SimpleKalmanFilter q
      kfAzimuth1.setProcessNoise( (float)AzimuthEndTime / 1000000.0f ); //convert time of process to uS, update SimpleKalmanFilter q
      filteredAzimuth_0 = kfAzimuth0.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_5) );
      if ( (filteredAzimuth_0 > filteredAzimuth_1) && (abs(filteredAzimuth_0 - filteredAzimuth_1)) > AzimuthThreashold )
      {
        Azimuth += 2;
        // Serial.println( Azimuth );
        if ( Azimuth >= maxAzimuthRange )
        {
          Azimuth = 900;
        }
        fMoveAzimuth( Azimuth );
        vTaskDelay( 12 );
        AzimuthStartTime = esp_timer_get_time();
      }
      //    //
      if ( (filteredAzimuth_0 < filteredAzimuth_1) && (abs(filteredAzimuth_1 - filteredAzimuth_0)) > AzimuthThreashold )
      {
        Azimuth -= 2;
        if ( (Azimuth >= maxAzimuthRange) || (Azimuth <= minAzimuthRange) )
        {
          Azimuth = 900;
        }
        fMoveAzimuth( Azimuth );
        vTaskDelay( 12 );
        AzimuthStartTime = esp_timer_get_time();
      }
    }
    // Serial.println();
    vTaskDelay( 30 );
    //
  } // while(1)
} //void TrackSun()
////
void fMoveAltitude( int MoveTo )
{
  mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, MoveTo);
}
//////
void fMoveAzimuth( int MoveTo )
{
  mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_OPR_A, MoveTo);
}
////
void loop() {}
////
One of the reasons, of several, is that loop() has the lowest priority,

Re: portYIELD_FROM_ISR() is not working

Posted: Wed Feb 19, 2020 2:00 pm
by mikelisfbay
Hi idahowalker,

Thank you. You are right. Expressif's official example also does not use the loop task. Good point. You cannot change the task priority level. Also, you cannot pass the handle to it. You cannot define the heap size. The loop task is basically unconfigurable.

I copied someone's tutorial example for convenience and use it to build my code.

Now, I learned that I can pin any task to core 1 on which the loop task runs. I agreed with you that the loop task is not needed. I can run a task on core 0 and another task on core 1 to achieve parallel task running without even the need of sharing the CPU time between the two running tasks like you had done in your example code.

Thank you for sharing and guidance.
Michael.

Re: portYIELD_FROM_ISR() is not working

Posted: Wed Feb 19, 2020 2:07 pm
by mikelisfbay
Hi idahowalker,

The most important thing you said is that the loop task has the lowest priority. For my semaphore example, I need to raise the priority level of the loop task to TWO to process the interrupt counter. I can't even do that with the loop task. I have modified my code like your code by creating a new task with a higher priority to replace the loop task.

Thank you. I forgot to say this in my previous reply to you.

Michael