Dual Core RT on ESP32

val_carrara
Posts: 3
Joined: Wed Mar 25, 2020 9:09 pm

Dual Core RT on ESP32

Postby val_carrara » Thu Mar 26, 2020 1:54 am

Hi everybody. First at all, I'm newbie and I apologize for this post. Maybe it's just a silly question. I'm coding in ESP32 with Arduino IDE. My application needs two cores, and I'm using semaphores to control data flowing between both cores. In short, an ISR gives the semaphore to a task running on Core 0 and this gives a second semaphore to a task running on Core 1. I realized that Core 1 does its job for 1 ms and then, for another 1 ms it spins or maybe the FreeRTOS just block the task. The code is attached bellow. Together it is shown the output of this program captured by an oscilloscope, for a run with data going through Core 1 only. It should be a sine wave, but it isn't. The output for a task running on Core 0 only shows a perfect sine wave. You can change the tasks by comment/uncomment the lines of semaphores at the end of ISR onTimer(). On serial monitor, the last two values show the number of times the tasks (0 or 1) was activated in one second. It should be 44100 for both, but this is true only for Core 0. Core 1 gives 22100, approximately. I guess (I'm not sure of that) that there is other tasks competing with my task on Core 1 (like loop(), for instance). Is there a way to overcome with this delay on Core 1 using ESP32-Arduino-FreeRTOS?

Code: Select all

// Include section -------------------------------------------
#include "soc/sens_reg.h"


// Define section---------------------------------------------
#define   adcRes    12
#define   dacPin    25    // DAC Channel 1 | GPIO25 | DAC_CHANNEL_1

// Structs section---------------------------------------------


// Library parameters ---------------------------------------------
// FreeRTOS
TaskHandle_t    th_Task0, th_Task1, th_TaskS;
portMUX_TYPE    timerMux = portMUX_INITIALIZER_UNLOCKED;
volatile SemaphoreHandle_t  task0Semaphore, task1Semaphore;

// ESP32-IDF
hw_timer_t  *timer = NULL;
volatile uint32_t time_scale = 1814;
volatile uint32_t time_count = 1;

// Variables section---------------------------------------------
// Variables
volatile uint32_t isrCounter = 0, tsk0count = 0, tsk1count = 0;
volatile uint32_t lastIsrAt = 0;
volatile uint32_t isrCount = 0, isrTime = 0;

// ADC & DAC
volatile int16_t  adcData;
volatile int16_t  adcData0;
volatile uint8_t  dacData = 127;

uint16_t   dacInd = 0;
uint16_t   sine[100];    // 440 Hz

// ****************************************************************
// Prototypes

// ****************************************************************

void IRAM_ATTR onTimer()
{
    portENTER_CRITICAL_ISR(&timerMux);
    adcData   = sine[dacInd];
    dacInd++;
    if (dacInd == 100) dacInd = 0;

    dacData   = adcData0;  
    dacFastWrite25(dacData);    // 3
    
    isrCounter++;
  
    portEXIT_CRITICAL_ISR(&timerMux);
  
    //xSemaphoreGiveFromISR(task0Semaphore, NULL);
    xSemaphoreGiveFromISR(task1Semaphore, NULL);
}

// ****************************************************************

void Task1(void* _par)
{
  while(1)
  {
    if (xSemaphoreTake(task1Semaphore, 0) == pdTRUE)
    {
      isrTime   = micros();
      tsk1count++;
      portENTER_CRITICAL(&timerMux);
      adcData0    = adcData;
      portEXIT_CRITICAL(&timerMux);
      lastIsrAt += micros() - isrTime;
    }
  }
  vTaskDelete(NULL);
}

// ****************************************************************

void Task0(void* _par)
{
  while(1)
  {
    if (xSemaphoreTake(task0Semaphore, 0) == pdTRUE)
    {
      tsk0count++;
      portENTER_CRITICAL(&timerMux);
      adcData0  = adcData;
      portEXIT_CRITICAL(&timerMux);
    }
  }
  vTaskDelete(NULL);
}


// ****************************************************************

void startTimer()
{
    timer = timerBegin(0, time_scale, true);  // 1814 = 44100 kHz = 80000000/44100
    timerAttachInterrupt(timer, &onTimer, true);
    timerAlarmWrite(timer, time_count, true); 
    timerAlarmEnable(timer);
    return;
}

// ****************************************************************

void setup() 
{
    BaseType_t  bt_Task0, bt_Task1, bt_TaskS;
  
    Serial.begin(115200);

    adcData     = 0;

    for (int i = 0; i < 100; i++)
    {
      sine[i] = 127*sin((float)i*3.141516/50) + 127;
    }

    // Create semaphore to inform us when the timer has fired
    task0Semaphore = xSemaphoreCreateBinary();
    task1Semaphore = xSemaphoreCreateBinary();

    bt_Task0  = xTaskCreatePinnedToCore(Task0, "Task0", 1024, NULL, 1,
      &th_Task0, 0);

    bt_Task1  = xTaskCreatePinnedToCore(Task1, "Task1", 1024, NULL, 1,
      &th_Task1, 1);

    startTimer();
    dacAttachPin(dacPin);
     
    // Disable Watch Dog Timer on Core 0, otherwise it keeps reseting ESP32
    disableCore0WDT();
}

// ****************************************************************

void loop() 
{
    if (isrCounter > 44100)
    {
      portENTER_CRITICAL_ISR(&timerMux);
      Serial.print("ISR duty cycle (%):");
      Serial.print(lastIsrAt*100./1000000.);
      Serial.print(" at ");
      Serial.print(millis()/1000.);
      Serial.print("  ");
      Serial.print(tsk0count);
      Serial.print("  ");
      Serial.print(tsk1count);
      Serial.println();
      lastIsrAt   = 0;
      isrCounter = 0;
      tsk0count   = 0;
      tsk1count   = 0;
      portEXIT_CRITICAL_ISR(&timerMux);
    }
}

void dacAttachPin(uint8_t pin)
{
  if(pin < 25 || pin > 26)
  {
    return;
  }
  pinMode(pin, ANALOG);
  
  CLEAR_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN);
  if(pin == 25)
  {
    CLEAR_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M);
    SET_PERI_REG_BITS(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_DAC, 0, RTC_IO_PDAC1_DAC_S);
    SET_PERI_REG_MASK(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_XPD_DAC | RTC_IO_PDAC1_DAC_XPD_FORCE);
  }
  else
  {
    CLEAR_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN2_M);
    SET_PERI_REG_BITS(RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_DAC, 0, RTC_IO_PDAC2_DAC_S);
    SET_PERI_REG_MASK(RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_XPD_DAC | RTC_IO_PDAC2_DAC_XPD_FORCE);
  }
}

void dacFastWrite25(uint8_t value)
{
  SET_PERI_REG_BITS(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_DAC, value, RTC_IO_PDAC1_DAC_S);
}

void dacFastWrite26(uint8_t value)
{
  SET_PERI_REG_BITS(RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_DAC, value, RTC_IO_PDAC2_DAC_S);
}
Attachments
sine_esp32.jpg
sine_esp32.jpg (35.11 KiB) Viewed 6416 times

boarchuz
Posts: 566
Joined: Tue Aug 21, 2018 5:28 am

Re: Dual Core RT on ESP32

Postby boarchuz » Thu Mar 26, 2020 6:24 am

1. Your infinite loops are not making good use of FreeRTOS functionality:

Code: Select all

while(1)
  {
    if (xSemaphoreTake(task0Semaphore, 0) == pdTRUE)
    {
      //...
    }
  }
That's a very busy loop when it doesn't need to be. How about portMAX_DELAY instead of 0?

2. Your loop() is very busy when it doesn't really need to be. Given that it's pinned to core 1, this might be responsible for the issue you're seeing. Maybe the ISR can give a semaphore if isrCounter > 44100?

3. Following on from that, loopTask has a priority of 1, same as your tasks. Suggest increasing the priorities of your tasks (especially if they're on core 1).

4. The second parameter of xSemaphoreGiveFromISR can indicate to you if it has unblocked another task, otherwise that task will only resume upon the next tick (1ms with Arduino) which also might explain the issue you're seeing.

val_carrara
Posts: 3
Joined: Wed Mar 25, 2020 9:09 pm

Re: Dual Core RT on ESP32

Postby val_carrara » Thu Mar 26, 2020 2:28 pm

In reply to boarchuz:
Thank you for your comments.
1) "That's a very busy loop when it doesn't need to be. How about portMAX_DELAY instead of 0?"
I'm just trying to make Core 1 process data as soon as the semaphore is available. Moreover, I don't need any more tasks running on Core 1, so this should be as fast as it could be. Changing delay to portMAX_DELAY produced a even worse performance of Core 1.
2) " Your loop() is very busy when it doesn't really need to be."
The loop just print some diagnosis info, at minimum rate (once per second). It really don't need to be busy, and it isn't. By changing the if (isrCounter > 44100) by a semaphore it produced only a marginal increasing in Core 1 rate, from 22100 to 22150.
3) "Following on from that, loopTask has a priority of 1, same as your tasks."
Indeed. That's really a good suggestion, and it should work, ... but doesn't. I'm just doing something wrong, I believe. The maximum available priority as for configMAX_PRIORITIES is 25 (checked at running time), but ESP32 keeps resetting ESP32 for any priority greater than 1:

E (12988) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (12988) task_wdt: - IDLE0 (CPU 0)
E (12988) task_wdt: Tasks currently running:
E (12988) task_wdt: CPU 0: Task0
E (12988) task_wdt: CPU 1: Task1
E (12988) task_wdt: Aborting.
abort() was called at PC 0x400d4393 on core 0

Backtrace: 0x4008d9c0:0x3ffbe170 0x4008dbed:0x3ffbe190 0x400d4393:0x3ffbe1b0 0x40086d2d:0x3ffbe1d0 0x400d0ecf:0x3ffb8850 0x4008a675:0x3ffb8870

Rebooting...
ets Jun 8 2016 00:22:57

rst:0xc (SW_CPU_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1216
ho 0 tail 12 room 4
load:0x40078000,len:9720
ho 0 tail 12 room 4
load:0x40080400,len:6352
entry 0x400806b8

4) "The second parameter of xSemaphoreGiveFromISR can indicate to you if it has unblocked another task,..."
That seems to be feasible, but it doesn't explain why Core 0 works fine with the same setup.

Sorry, I still have no guess on what's happening with task running on Core 1.

boarchuz
Posts: 566
Joined: Tue Aug 21, 2018 5:28 am

Re: Dual Core RT on ESP32

Postby boarchuz » Sun Mar 29, 2020 7:46 am

1. Let FreeRTOS handle task switching for you, don't try to fight against it. If that seems to make things worse, the problem is almost certainly somewhere else.
2. Your loop() doesn't need to be busy, but it is. It's checking if that semaphore is immediately ready over and over and over without yielding to other tasks that might be waiting to do work. It's a perfect situation to use a semaphore and let FreeRTOS give it some cycles to do what it needs do when (and only when) it needs to. Use portMAX_DELAY.
3. Maximum is (configMAX_PRIORITIES - 1). Generally 5-15 is a safe 'user' space.
4. Core 0 isn't fighting against your busy loopTask (which is pinned to core 1) for CPU cycles. Core 1 is. Put a portYIELD_FROM_ISR(); after you've given the semaphore(s).

val_carrara
Posts: 3
Joined: Wed Mar 25, 2020 9:09 pm

Re: Dual Core RT on ESP32

Postby val_carrara » Mon Mar 30, 2020 9:54 pm

Hi boarchuz
It took me some time to realize what your mean when you say: "Your loop() is very busy when it doesn't really need to be". I finally realized (after trying unsuccessfully using portMAX_DELAY with semaphores) that is was only a matter of giving the scheduler some time to process other tasks (as I said, I'm really a newbie) on Core 1. I just inserted a vTaskDelay on loop() and presto, things started to run as I expected. Problem solved. Thank you very much.

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

Re: Dual Core RT on ESP32

Postby ESP_Sprite » Tue Mar 31, 2020 8:01 am

Don't do vTaskDelay; it's a hack that will kill your latency. Waiting for the semaphore with portMAX_DELAY should work just fine; I'd suggest you try to figure out why that didn't work for you instead.

Who is online

Users browsing this forum: Bing [Bot] and 67 guests