Context of ESP32 WiFi Callbacks

So, if you set up callback functions such as:

    server.on("/", handleRoot);
    WiFi.onEvent(wifiEvent);

Do those callback functions run in interrupt context or as a task. If a task, at what priority?

The answer determines what kind of processing should be done in the task and also how semaphores, queues, etc are handled.

Thanks.

Do those callback functions run in interrupt context or as a task

Since the server.on() appears to be related to the ‘normal’ webserver (as opposed to the Async) it is only executed during ‘handleCilent()’
For the event i am not 100% sure but i actually suspect that during ‘scheduled tasks’ the task gets executed if the event has occurred. (i think that’s how the Async webserver does it as well) So that would be during any yield() . Priority ? well i dunno.

OK, thanks. So, the server.on() callback would run in the same task (and hence at the same priority) as the function that called handleCilent().

Not sure I understand your answer for WiFi.onEvent(). But, it sounds like you're saying that the callback runs in some task and not during an ISR. The priority of said task is TBD.

freeRTOS will have the interrupt registered, the current running task will be tasked switched away after completion on of the currently running line of code. The interrupt will be loaded and ran under its own stack space and then the last running task will be resumed.

You can speed up an interrupt task like WiFi by making the interrupt static

static void WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
    case SYSTEM_EVENT_STA_CONNECTED:
      log_i("Connected to access point");
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      log_i("Disconnected from WiFi access point");
      break;
    case SYSTEM_EVENT_AP_STADISCONNECTED:
      log_i("WiFi client disconnected");
      break;
    default: break;
  }
}

Which can make a slight positive difference when it comes to something like receiving MQTT data

static void mqttCallback(char* topic, byte * payload, unsigned int length)
{
  xSemaphoreTake( sema_MQTT_Parser, portMAX_DELAY);
  str_eTopic = topic;
  int i = 0;
  for ( i; i < length; i++)
  {
    strPayload[i] = ((char)payload[i]);
  }
  strPayload[i] = NULL;
  //log_i( "topic %s payload %s" ,str_eTopicPtr, strPayloadPtr );
  xSemaphoreGive ( sema_MQTT_Parser );
  xEventGroupSetBits( eg, evtParseMQTT ); // trigger tasks
} // void mqttCallback(char* topic, byte* payload, unsigned int length)

Which can be ran many times. Static saves on load and unload times of the function.

So, you're saying the WiFi.onEvent() callback runs as part of an ISR? If so, shouldn't the function definition include IRAM_ATTR?

I've had the same question. I'm going to test that supposition after sending this message.

I removed the static attribute, placed the call back before setup, complies, uploads, and works.

void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
    case SYSTEM_EVENT_STA_CONNECTED:
      log_i("Connected to access point");
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      log_i("Disconnected from WiFi access point");
      break;
    case SYSTEM_EVENT_AP_STADISCONNECTED:
      log_i("WiFi client disconnected");
      break;
    default: break;
  }
}[color=#222222]

A method I much prefer. Thanks for your question.

Don't forget to use the ISR components of freeRTOS when using the IRAM_ATTR on a callback.

OK, thanks. So then, if the processing I want to do in response to the event is inappropriate for an ISR, I need to synchronize a task to the the ISR (with a queue, for example). And, I should also check if the task that wakes up has higher priority than the one originally interrupted by the WiFi Event ISR. If so, there should be a context switch. That gives me:

void IRAM_ATTR wifiEvent(WiFiEvent_t event) {
  BaseType_t xHigherPriorityTaskWoken;
  xQueueSendToBackFromISR(wiFiEventQueue, &event, &xHigherPriorityTaskWoken);
  if (xHigherPriorityTaskWoken) {
    portYIELD_FROM_ISR();
  }
}

Does that look kosher?

OK, cool.

By using the IRAM_ATTR on an ISR/callback the callback is assigned freeRTOS tick hooks, which makes the OS more responsive to the ISR.

See freeRTOS API https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos_additions.html?highlight=iram_attr#component-specific-properties, hooks.

looks good. I've not found the need for yields in freeRTOS. I used to use them but found freeRTOS does a bang up job.

If the ISR task is very important would you not just create a single Queue and overwrite instead of send to back?

Idahowalker: If the ISR task is very important would you not just create a single Queue and overwrite instead of send to back?

Yea, or use a semaphore. That was just an example. The handler for the event may not be SUPER important, but it might be time-consuming and inappropriate for use in an ISR. And, while not super important, it could be more important than the task originally interrupted. Hence the check for context switch.

or use SendToFront.

I found one way to do round robin task switching is through the use of an event trigger and a queue.

gfvalvo: OK, thanks. So then, if the processing I want to do in response to the event is inappropriate for an ISR, I need to synchronize a task to the the ISR (with a queue, for example). And, I should also check if the task that wakes up has higher priority than the one originally interrupted by the WiFi Event ISR. If so, there should be a context switch.

I agree with the above for a single core application. I am still thinking about and mostly agree with the above on a dual core when using the other other core to run functions.

gfvalvo:
So, if you set up callback functions such as:

    server.on("/", handleRoot);

WiFi.onEvent(wifiEvent);



Do those callback functions run in interrupt context or as a task. If a task, at what priority?

The answer determines what kind of processing should be done in the task and also how semaphores, queues, etc are handled.

Thanks.

And do remember that the ESP32 WiFI code is based around using freeRTOS.

Idahowalker: I am still thinking about and mostly agree with the above on a dual core when using the other other core to run functions.

I'm only a few days into ESP32 and FreeRTOS. I don't yet know/understand the relationship between the two processor cores, what code runs in which core (and how to control that), and how tasks in the two cores communicate.

It is best to use Queues for task-to-core-to-tasks communications. The relations between cores will be the frrRTOS OS as modified by ESPRESSIF. The syncs on core to core are the semaphores, events, and message queues.


Almost forgot RingBuffers is another core to core communications medium.

Using global variables is a very poor choice for comms between 2 tasks on differing cores.

OK, thanks. So, the server.on() callback would run in the same task (and hence at the same priority) as the function that called handleCilent().

basically yes. handleClient() just calls the callback, and the callback in this case is therefore not an ISR.