ESP-IDF mit 2 Tasks und einem Interrupt

ESP-IDF-Beitragsbild

Was soll hier gezeigt werden?

ESP_Test_Board

Für mich war die ESP-IDF neu, darum habe ich folgendes ausprobiert, um festzustellen, wie elegant sich das lösen lässt. Folgende Punkte sollen gezeigt werden:

  • Aufbau mit mehreren Dateien
  • Externe Variable
  • Interrupt-Routine
  • Tasks auf unterschiedlichen CPU- Kernen
  • Semaphore
  • I/O Benutzung

Als Hardware habe ich mein Eval-Board benutzt.

Für mich war die ESP-IDF neu, darum habe ich folgendes ausprobiert, um festzustellen, wie elegant sich das lösen lässt. Folgende Punkte sollen gezeigt werden:

  • Aufbau mit mehreren Dateien
  • Externe Variable
  • Interrupt-Routine
  • Tasks auf unterschiedlichen CPU- Kernen
  • Semaphore
  • I/O Benutzung

Als Hardware habe ich mein Eval-Board benutzt.

void app_main(void)

Die Funktion „app_main(void)“ wird nach dem Reset aufgerufen, sie ist vergleichbar mit der „setup()“ Funktion bei arduino. Somit werden hier zuerst die Grundsettings vorgenommen.
Aber es gibt keine „loop()“ Funktion, sondern es wird eine „while(1){}“ Schleife in „app_main()“ installiert, die die „loop()“ Funktion ersetzt.

				
					
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <driver/gpio.h>
#include <esp_timer.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "esp_task_wdt.h"
#include "task_2.h"
#include "freertos/semphr.h"

#define TWDT_TIMEOUT_MS         10000
#define TASK_RESET_PERIOD_MS    1500
#define MAIN_DELAY_MS           10000

#define BUTTON_GPIO GPIO_NUM_4       // Pushbutton GPIO
#define YELLOW_LED GPIO_NUM_13       // LED GPIO
#define RED_LED GPIO_NUM_14         // LED GPIO
#define GREEN_LED GPIO_NUM_12       // LED GPIO
#define DEBOUNCE_DELAY_US 200000ULL  // Debounce delay in microseconds (200 ms)

static volatile uint64_t last_isr_time = 0;
static volatile uint32_t counter = 0;
static QueueHandle_t button_queue;
int button_counter=0;

extern int ext_data;

SemaphoreHandle_t xGlobalMutex=NULL;

				
			
				
					// Task 1
void task_func(void *arg)
{
    while (1) {
        printf("Task1 is running and LED is on.\n");
        gpio_set_level(GREEN_LED, 0); // Turn on green LED to indicate task is running
        vTaskDelay(pdMS_TO_TICKS(TASK_RESET_PERIOD_MS));
        printf("Task1 is running and LED is off.\n");
         gpio_set_level(GREEN_LED, 1); // Turn off green LED to indicate task is running
        vTaskDelay(pdMS_TO_TICKS(TASK_RESET_PERIOD_MS));
        set_counter(button_counter++);
        if (xSemaphoreTake(xGlobalMutex, portMAX_DELAY)) {
            ext_data++;
            xSemaphoreGive(xGlobalMutex);
        }
         printf("Number Task 2 %d\n", reverse_count());

    }

}

				
			
				
					// Interrupt Service Routine (ISR) for button press, placed in IRAM for low latency
static void IRAM_ATTR button_isr(void *arg) {
    uint64_t now = esp_timer_get_time(); // Get current time in microseconds
    // Check if debounce period has passed, then process the button press
    if (now - last_isr_time > DEBOUNCE_DELAY_US) {
        counter++;
        uint32_t cnt = counter;
        BaseType_t higher_priority_task_woken = pdFALSE;
        xQueueSendFromISR(button_queue, &cnt, &higher_priority_task_woken); // Send counter to queue from ISR
        last_isr_time = now;
        if (higher_priority_task_woken) {
            portYIELD_FROM_ISR();
        }
    }
}

				
			
				
					void app_main(void) {
// Init Hardware and data
    xGlobalMutex = xSemaphoreCreateMutex();   
    printf("Press the button on GPIO %d.\n", BUTTON_GPIO);

    gpio_reset_pin(YELLOW_LED);
    gpio_reset_pin(RED_LED);
    gpio_reset_pin(GREEN_LED);
    gpio_set_direction(YELLOW_LED, GPIO_MODE_OUTPUT);
    gpio_set_direction(RED_LED, GPIO_MODE_OUTPUT);
    gpio_set_direction(GREEN_LED, GPIO_MODE_OUTPUT);


    // Create a queue to hold up to 10 uint32_t items
    button_queue = xQueueCreate(10, sizeof(uint32_t));

    // Configure Button GPIO
    gpio_config_t io_conf = {
        .intr_type = GPIO_INTR_POSEDGE, // Rising edge interrupt trigger
        .mode = GPIO_MODE_INPUT,
        .pin_bit_mask = (1ULL << BUTTON_GPIO),
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .pull_up_en = GPIO_PULLUP_ENABLE
    };
    gpio_config(&io_conf);

    // Install GPIO ISR service
    gpio_install_isr_service(0);

    // Add ISR handler for button
    gpio_isr_handler_add(BUTTON_GPIO, button_isr, NULL);

    // Variable to receive counter from queue
    uint32_t button_counter;

    gpio_set_level(GREEN_LED, 0); // Turn on green LED to indicate program is running   
    gpio_set_level(YELLOW_LED, 1); // Turn off yellow LED to indicate program is running   
    gpio_set_level(RED_LED, 1); // Turn off red LED to indicate program is running
    
    printf("Task creation.\n");

//Init tasks    
    xTaskCreatePinnedToCore(task_func, "task", 2048, xTaskGetCurrentTaskHandle(), 10, NULL, 0);
    init_task_2();
    // Keep program running

//loop() function
    while (1) {
        // Wait indefinitely for an item in the queue
        //gpio_set_level(YELLOW_LED, 1); // Turn on yellow LED to indicate waiting for button press
        
        if (xQueueReceive(button_queue, &button_counter, portMAX_DELAY)) {
            //gpio_set_level(YELLOW_LED, 0); // Turn off yellow LED
            gpio_set_level(RED_LED, 0); // Turn on red LED to indicate button press
            printf("Button pressed %lu times.\n", button_counter);
            vTaskDelay(200 / portTICK_PERIOD_MS); // Delay to keep red LED on for 200ms
            gpio_set_level(RED_LED, 1); // Turn off red LED
        }
    }
}

				
			

Wie funktioniert das mit dem Interrupt?


Die Interrupt-Routine überwacht einen Taster, der beim Auslösen eine „xQueueSendFromISR auslöst und diesen an die Loop-Funtion in „app_main()“ sendet, die mit „xQueueReceive()“ darusf wartet, wass  dann die rote LED für 200Ms leuchten läßt.

Was macht Task 1?


Task 1 lässt die grüne LED für 1,5 Sek leuchten. Danach wird die externe Funktion „set_counter()“ aufgerufen, die den aktuellen „button_counter“ sendet.
Dann wird unter Mutex-Sperre „semaphoreTake()“ und semaphoreGive()“  die externe Variable erhöht.

				
					// File: data.c
int ext_data = 0;
				
			
				
					// File: task_2.h
void init_task_2(void) ;
void set_counter (int cnt);
				
			
				
					// File: task_2.c
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <driver/gpio.h>
#include <esp_timer.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "esp_task_wdt.h"
#include "task_2.h"
#include "freertos/semphr.h"

#define TWDT_TIMEOUT_MS         10000
#define TASK_RESET_PERIOD_MS1    1000
#define MAIN_DELAY_MS           10000

#define YELLOW_LED GPIO_NUM_13       // LED GPIO
#define RED_LED GPIO_NUM_14         // LED GPIO
#define GREEN_LED GPIO_NUM_12       // LED GPIO
#define DEBOUNCE_DELAY_US 200000ULL  // Debounce delay in microseconds (200 ms)
int button_counter1=0;
extern int ext_data;
extern SemaphoreHandle_t xGlobalMutex;

void set_counter(int cnt) {
    button_counter1 = cnt;
    printf("Counter updated to %d\n", button_counter1);
}

void task_func_2(void *arg)
{
    while (1) {
        printf("Task2 is running and LED is on.\n");
        gpio_set_level(YELLOW_LED, 0); // Turn on yellow LED to indicate task is running
        vTaskDelay(pdMS_TO_TICKS(TASK_RESET_PERIOD_MS1));
        printf("Task2 is running and LED is off.\n");
         gpio_set_level(YELLOW_LED, 1); // Turn off yellow LED to indicate task is running
        vTaskDelay(pdMS_TO_TICKS(TASK_RESET_PERIOD_MS1));
        printf("Task2: BUTTON_COUNTER1 = %d\n", button_counter1);
        if (xSemaphoreTake(xGlobalMutex, portMAX_DELAY)) {
            printf("Task2: external_data = %d\n",ext_data);
            xSemaphoreGive(xGlobalMutex);
        }

    }
}

void init_task_2(void) {
    xTaskCreatePinnedToCore(task_func_2, "task_2", 2048, NULL, 10, NULL, 1);
}   
				
			

Was macht Task2?

Task2 macht periodisch die gelbe LED an und aus.
Auch liest sie in jedem Zyklus der Schleife die externe Variable „ext_data“ aus , die in der Task 1 hochgezählt wird. Natürlich durch Mutex geschützt.

„init_task_2()“ wird zum Starten der Task 2 aus „app_main()“ aufgerufen.

				
					//CMAKELists.txt

idf_component_register(SRCS "data.c" "main.c" "task_2.c" "data.c"
                    INCLUDE_DIRS ".")
				
			

Zum Schluss zeige ich noch den Makefile für das Projekt.

Als Basis diente der RNT Kurs „ESP-IDF: ESP32 GPIO-Interrupts (Taster mit Entprellung)

Verwandte Beiträge

Steckdosen-Box

Steckdosen mit Timer

Die Idee ist, Steckdosen mit Timer extern steuern zu können. Dafür wurde ein ESP32-S3 und eine Echtzeituhr DS3231 mit I2C Interface genutzt. Ein 4×20 LCD-Display

Weiterlesen »

Kommentar