Multi-threading with the ESP32

Saw this project Three LEDs with Dirty Delay (ESP32) - #21 by Gunner on multi-threading with the ESP32 and thought I have a crack at a non-blocking second thread reading analog and digital inputs

The only trap I found is that ADC1 uses CORE1 and ADC2 uses CORE0. Can’t find documentation on that but I can confirm it is the case (after several hours of hair pulling)

It’s a simple loop with a single 50mS duty to de-bounce the buttons then a counter for 2 second Blynk updates. Works well. Short video and the code below…

Cheers

/* ESP32 Blynk multi threading example
 * IE: Blink runs on Core 0, User tasks on core 1 
 * Tip: ADC's are split acroos the two cores so be warned =)
 * Functions: 
 * 1 x analogRead (10K or larger POT if its an experiment) 
 * 2 x digitalRead (Button 1 = Count Up, Button 2 = Zero Counter)
 * Duty cycle for updates = 2 seconds
 *  
 */

// Template ID, Device Name and Auth Token are provided by the Blynk.Cloud
// See the Device Info tab, or Template settings
#define BLYNK_TEMPLATE_ID "Your Template ID"
#define BLYNK_DEVICE_NAME "Your Device Name"
#define BLYNK_AUTH_TOKEN "Your Device Token"

// Comment this out to disable prints and save space
#define BLYNK_PRINT Serial

#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>

char auth[] = BLYNK_AUTH_TOKEN;

// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "Your WiFi SSID";
char pass[] = "Your WiFi Password";

// Constants
const int countPin = 15, zeroPin = 25, voltPin = 34;
#define DUTY 50L
#define REF_VOLTS (float)3.33
#define ADC_MAX (uint16_t)4095
#define TWO_SEC 40
//Variables
byte ledState = LOW, countState, lastCountState = HIGH,
       zeroState, lastZeroState = HIGH;
uint16_t pressCount = 0, dutyCount = 0;
uint32_t prevTime = 0;
bool countPress = false, zeroPress = false;

// Just practicing strings
String calculateVolts(uint16_t i){         
    float x = ((float)i*(REF_VOLTS/ADC_MAX));
    return String(x);                             // Returns calculated result as a string
}

void loop2(void *pvParameters){    // Core 1 loop - User tasks
  while (1){
    countState = digitalRead(countPin);
    zeroState = digitalRead(zeroPin);
        if(countState != lastCountState){
            prevTime = millis();
            countPress = true;
        }
        if(zeroState != lastZeroState){
            prevTime = millis();
            zeroPress = true;
        }
        if(millis() - prevTime >= DUTY){
            prevTime = millis();
            dutyCount++;
                if(countPress){
                    if(countState == LOW){
                        pressCount++;
                        dutyCount = 0;
                        countPress = false;
                    }
                }
                if(zeroPress){
                    if(zeroState == LOW){
                        pressCount = 0;
                        dutyCount = 0;
                        zeroPress = false;
                    }
                }
                if(dutyCount >= TWO_SEC){                    
                    uint16_t reading = analogRead(voltPin);
                    String volts = calculateVolts(reading);
                    Blynk.virtualWrite(V0, reading);
                    Blynk.virtualWrite(V1, volts);
                    Blynk.virtualWrite(V2, pressCount);
                    Serial.printf("Reading: %u", reading);
                    Serial.printf("\tVoltage reading: %s", volts);
                    Serial.printf("\tPress No: %u", pressCount);
                    Serial.println("");
                    dutyCount = 0;
                }
        }
        lastCountState = countState;
        lastZeroState = zeroState;
  }
}

void loop1(void *pvParameters){    // Core 0 - Blink loop
  while (1) {
     Blynk.run();
     delay(1);
  }
}

void setup(){
  // Debug console
  Serial.begin(115200);
  Serial.println("Connecting to Blynk Server\n");
  Blynk.begin(auth, ssid, pass);

  pinMode(countPin, INPUT_PULLUP);
  pinMode(zeroPin, INPUT_PULLUP);
  pinMode(voltPin, INPUT);
  
  xTaskCreatePinnedToCore(loop2, "loop2", 4096, NULL, 1, NULL, 1);
  xTaskCreatePinnedToCore(loop1, "loop1", 4096, NULL, 1, NULL, 0);
  Serial.println("");
  Serial.println("Connected and Ready - Waiting for Button Press\n");
}

void loop(){}
4 Likes

Thanks for this - Just what I was looking for. My current setup is to use an extra Arduino to run non-blocking tasks and communicating with an ESP32 through I2C. So many people say that’s pointless, due to the multicore on the ESP32, but the problem is that there’s so little examples online of how to use these 2 cores effectively.

thanks!

what is the function of the “3rd” emoty void loop?

It’s a requirement of the Arduino IDE language syntax even if it’s empty it must be present or you will get a compile error.

1 Like

I confess to my limited understanding of how to fully use FreeRTOS API as well but when I cam across an old beta example I thought posting an updated example might help others as it did for me.

If you like to know more about the FreeRTOS API and all it’s functions the espressif documentation is worth a read

Cheers

i need to use an interrupt, how can i set up on which core to run?

thanks!

Since the ESP32 uses and RTOS, you don’t actually need to use both cores, which can sometimes introduce its own problems. You just need to run your “dirty” code and your Blynk.run() ; statement as separate tasks. The RTOS will continue to run the clean loop while waiting the for delay in the dirty loop to expire.