Toggling LED through button (ESP32 FreeRTOS) + binary semaphore

1.4k Views Asked by At

I had already done several projects using simple freertos ideas: led, button. Implementing semaphores, queues or some interrupt. I can't run this simple code tough.

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#include "driver/gpio.h"

#define BLINK_GPIO 21 //2
#define BUTTON_GPIO 0

void task_blink(void *pvParameters);
void task_botao(void *pvParameters);
//void wd_off_task(void *pvParameters);
SemaphoreHandle_t sem_sinc;

void app_main(void)
{
    gpio_pad_select_gpio(BLINK_GPIO); // Configura o pino como IO
    gpio_set_direction(BLINK_GPIO,GPIO_MODE_OUTPUT); // Configura o IO como saida

    gpio_pad_select_gpio(BUTTON_GPIO); // Configura o pino como IO
    gpio_set_direction(BUTTON_GPIO,GPIO_MODE_INPUT); // Configura o IO como entrada

    vSemaphoreCreateBinary(sem_sinc); // Cria o Semaforo
    xSemaphoreTake(sem_sinc,0); // Garante que inicializa com 0

    xTaskCreate(task_blink,"Task Blink",1024,NULL,2,NULL);
    printf("Task Blink Criada!!!\r\n");

    xTaskCreate(task_botao,"Task Botao",1024,NULL,2,NULL);
    printf("Task Botao Criada!!!\r\n");

    //xTaskCreate(wd_off_task,"Task desliga WD",1024,NULL,1,NULL);
}

void task_botao(void *pvParameters)
{
    while(1)
    {
        if(gpio_get_level(BUTTON_GPIO) == 0)
        {
            while(gpio_get_level(BUTTON_GPIO) == 0){}
            printf("Botao Pressionado!!!\r\n");
            xSemaphoreGive(sem_sinc);
            vTaskDelay(1);
        }
    }
}

void task_blink(void *pvParameters)
{
    while(1)
    {
        if(xSemaphoreTake(sem_sinc,portMAX_DELAY)==pdTRUE)
        {
            printf("Pisca Led!!!\r\n");
            if((gpio_get_level(BUTTON_GPIO) == 0))
                gpio_set_level(BLINK_GPIO, 1);
            else
                gpio_set_level(BLINK_GPIO, 0);

        }
    }
}

The issue:

The code is built nicely, and the same for the flashing to ESP. As I press the button, it shows in the terminal the designed messages. See, the only problem here lies on I can't set the LED's level, toggling it! Because of this, all I can get is the LED turning on and turning off afterwards quickly(every time the semaphore syncronizes the 2 tasks).

I suspect it's all about some kind of config, related to this GPIO. (Although I'm using the reset port to read the button, I still think this is not the matter, because the port was properly configured on the lines above)

2

There are 2 best solutions below

0
Clifford On BEST ANSWER

Your switch polling needs to detect transitions, but avoid erroneously detecting switch bounce as a valid transition. For example:

#define BUTTON_DN = 0 ;
#define BUTTON_UP = 1 ;
#define POLL_DELAY = 50 ;

void task_botao(void *pvParameters)
{
    int button_state = gpio_get_level( BUTTON_GPIO ) ;
    
    for(;;)
    {
        int input_state = gpio_get_level( BUTTON_GPIO ) ;
        
        // If button pressed...
        if( input_state == BUTTON_DN && 
            button_state != BUTTON_UP )
        {
            button_state = BUTTON_DN ;

            // Signal button press event.
            xSemaphoreGive(sem_sinc ) ;
        }
        // otherwise if button released...
        else if( input_state == BUTTON_UP && 
                 button_state != BUTTON_DN )  
        {
            button_state = BUTTON_UP ;
        }
        
        // Delay to yield processor and 
        // avoid switch bounce on transitions 
        vTaskDelay( POLL_DELAY );
    }
}

The blinking task need not be reading the button input at all; not is it unnecessary, it is also a bad design:

void task_blink(void *pvParameters)
{
    int led_state = 0 ;
    gpio_set_level( BLINK_GPIO, led_state ) ;

    for(;;)
    {
        if( xSemaphoreTake( sem_sinc, portMAX_DELAY ) == pdTRUE )
        {
            led_state = !led_state ; 
            gpio_set_level( BLINK_GPIO, led_state ) ;
        }
    }
}
6
RemyHx On

There are some things to consider. Your thinking is logical, but there are some issues.

A button is a mechanical device and while you press it, you think it will be a straightforward 0 instead of 1 it’s not. If you have an oscilloscope, I recommend you to check the voltage level on the gpio input. Or google button bounce. And floating pins. Those two concepts should be clear. The processor is very straightforward in interpreting the values.

Example: https://hackaday.com/wp-content/uploads/2015/11/debounce_bouncing.png

Now your functions are in fact constantly checking the button status, somehow at the cost of processor time. For small projects not of an issue, but when they get bigger they are.

What you want to do is to setup an interrupt to the button status: at the moment the level changes it will fire some code. And it doesn’t have to double check the gpio status in two tasks, with the chance it will miss the status in the second (because of delays). It’s important to realize you are checking the same level twice now.

Not a problem now but maybe later: the stack size of the tasks is somehow small, make it a good use to always check if it’s enough by checking the current free size. Vague problems arise if it’s not.