Passing an EventGroupHandle_t and esp_lcd_panel_handle_t to a Task

I apologize as this might be redundant and more of a general syntax question, but as it pertains to FreeRTOS task handling I think this might be the best place to ask as members here are more likely to be familiar with the concept. I modified the i2c_oled_examble in ESP-IDF which is an example for making use of a ssd1306 oled screen to print hello world. I modified it to be able to turn the screen on and off with a push button. The method I chose to do this is writing a function that waits for a button press and toggles a bit on and off in an EventGroupHandle_t.

void vTaskSetBits(void *pvParameters)
{
    EventGroupHandle_t params = (EventGroupHandle_t)pvParameters;
    EventBits_t uxBits;
    uxBits = xEventGroupClearBits(params, BIT_0);
    gpio_config_t io_conf = {
        .pin_bit_mask = (1ULL << GPIO_NUM_11),
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE};
    gpio_config(&io_conf);

    for (;;)
    {
        if (!(gpio_get_level(GPIO_NUM_11))) // Button Press
        {
            uxBits = xEventGroupGetBits(params);
            if ((uxBits & BIT_0) == 0) // BIT_0 not set
            {
                uxBits = xEventGroupSetBits(params, BIT_0); // Set BIT_0

                ESP_LOGI(TAG, "BIT_0 Set");
            }
            else
            {
                uxBits = xEventGroupClearBits(params, BIT_0); // clear BIT_0

                ESP_LOGI(TAG, "BIT_0 Cleared");
            }
        }

        vTaskDelay(pdTICKS_TO_MS(10));
    }
}

This works as intended and have tested and verified with another small app I wrote to toggle a LED on and off using this method. The function I want to use to turn the LED on and off needs the EventGroupHandle_t instance and the instance for esp_lcd_panel_handle_t as that’s what esp_lcd_panel_dis_on_off() takes as an argument to know which screen. To achieve this I created a struct with a member for each corresponding type.

typedef struct
{
    esp_lcd_panel_handle_t handle;
    EventGroupHandle_t event;
} lcd_control_t;

I then pass this to lcd_power()

void lcd_power(void *pvParameters)
{
    lcd_control_t *params = (lcd_control_t *)pvParameters;

    ESP_LOGI(TAG, "We're in lcd_power()");
    for (;;)
    {
        ESP_LOGI(TAG, "Before xEventGroupWaitBits");
        EventBits_t uxBits = xEventGroupWaitBits(
            params->event,
            BIT_0,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);

        if ((uxBits & BIT_0) != 0)
        {
            ESP_LOGI(TAG, "We're in lcd_power() if");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(params->handle, true));
        }
        else
        {
            ESP_LOGI(TAG, "We're in lcd_power() else");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(params->handle, false));
        }
    }
}

The relevant code in main() where I create the tasks is:

    lcd_control_t screen_on_off = {
        .event = xCreatedEventGroup,
        .handle = panel_handle,
    };
    xTaskCreate(lcd_power, "LCD Power", 2048, (void *)&screen_on_off, 1, NULL);
    xTaskCreate(vTaskSetBits, "IR Set Bits", 2048, (void *)xCreatedEventGroup, 1, NULL);

There’re no warnings or errors during compile. Screen turns on and functions properly. Debug prints out letting me know the bit is set and cleared. We make it into the for(;:wink: and it’ll print the “Before xEventGroupWaitBits” once then it hangs up somewhere in that function. vTaskSetBits() continues to function as intended the whole time.

Sample output:

I (441) LVGL: Starting LVGL task
I (441) BLINK: Display LVGL Scroll Text
I (441) BLINK: We're in lcd_power()
I (441) BLINK: Before xEventGroupWaitBits
I (451) main_task: Returned from app_main()
I (451) gpio: GPIO[11]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (17461) BLINK: BIT_0 Set
I (17461) BLINK: We're in lcd_power() if
I (24461) BLINK: BIT_0 Cleared
I (32461) BLINK: BIT_0 Set
I (33461) BLINK: BIT_0 Cleared

Any help or guidance would be greatly appreciated. Thank you very much.

Try moving screen_on_off out from main to ensure that it does not go out of scope.

1 Like

Do NOT define objects as local variables in main to pass to tasks. Many ports of FreeRTOS will reuse the main stack for the interrupt stack, and thus overwrite those variables.

Define those objects as globals (or file static) objects.

1 Like

I gave this a try really quick and while it didn’t solve the issue, it made me start to think closer in terms of what needs what and what would be in scope. Thank you for your suggestion/input!

Thank you! I noticed if I added a forever loop at the end of main I’d start getting a what appeared to be a memory leak? After the previous commentor mentioned what he did, I started thinking of taking the approach you’re suggesting as to making everything global.

I think I’ve figured out (kind of) what is happening, but I think it’s my (modified tutorial implementation so not really “mine”) my use of LVGL is incorrect in some manner. Looks like there will be some doc searching in my future.

Relevant code:

void lcd_power(void *pvParameters)
{
    /*Initial run everything appears to function properly
    until we reach the first call of esp_lcd_panel_on_off()
    in the if(BIT_0 == SET)*/
    ESP_LOGI(TAG, "We're in lcd_power()");
    for (;;)
    {
        ESP_LOGI(TAG, "Before xEventGroupWaitBits");
        EventBits_t uxBits = xEventGroupWaitBits(
            xCreatedEventGroup,
            BIT_0,
            pdFALSE,
            pdTRUE,
            portMAX_DELAY);
        ESP_LOGI(TAG, "xCreatedEventGroup BIT_0: %u", (uint8_t)uxBits);
        if ((uxBits & BIT_0) != 0)
        {
            //First run we get to the ESP_LOGI() and it prints. then stuck.
            ESP_LOGI(TAG, "We're in lcd_power() if");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
        }
        else
        {
            ESP_LOGI(TAG, "We're in lcd_power() else");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, false));
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

I’m curious as to whether something is messing up because the first iteration is calling esp_lcd_panel_disp_on_off() to turn on the screen when it’s already on.
Full code is below:

/*
 * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include <stdio.h>
#include <driver/gpio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "esp_lvgl_port.h"
#include "lvgl.h"

#if CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107
#include "esp_lcd_sh1107.h"
#else
#include "esp_lcd_panel_vendor.h"
#endif

static const char *TAG = "BLINK";

#define I2C_BUS_PORT 0

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// Please update the following configuration according to your LCD spec //////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define EXAMPLE_LCD_PIXEL_CLOCK_HZ (400 * 1000)
#define EXAMPLE_PIN_NUM_SDA 6
#define EXAMPLE_PIN_NUM_SCL 7
#define EXAMPLE_PIN_NUM_RST -1
#define EXAMPLE_I2C_HW_ADDR 0x3C

// The pixel number in horizontal and vertical
#define EXAMPLE_LCD_H_RES 128
#define EXAMPLE_LCD_V_RES CONFIG_EXAMPLE_SSD1306_HEIGHT

// Bit number used to represent command and parameter
#define EXAMPLE_LCD_CMD_BITS 8
#define EXAMPLE_LCD_PARAM_BITS 8

#define BIT_0 (1 << 0)

/*This works appropriately. Only need to run once
Displays scrolling "Hello World" to screen*/
extern void example_lvgl_demo_ui(lv_disp_t *disp);

/*   Declarations for i2c bus and Panel i/o        */
static i2c_master_bus_handle_t i2c_bus = NULL;
static i2c_master_bus_config_t bus_config = {
    .clk_source = I2C_CLK_SRC_DEFAULT,
    .glitch_ignore_cnt = 7,
    .i2c_port = I2C_BUS_PORT,
    .sda_io_num = EXAMPLE_PIN_NUM_SDA,
    .scl_io_num = EXAMPLE_PIN_NUM_SCL,
    .flags.enable_internal_pullup = true,
};

static esp_lcd_panel_io_handle_t io_handle = NULL;
static esp_lcd_panel_io_i2c_config_t io_config = {
    .dev_addr = EXAMPLE_I2C_HW_ADDR,
    .scl_speed_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
    .control_phase_bytes = 1,               // According to SSD1306 datasheet
    .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,   // According to SSD1306 datasheet
    .lcd_param_bits = EXAMPLE_LCD_CMD_BITS, // According to SSD1306 datasheet
    .dc_bit_offset = 6,                     // According to SSD1306 datasheet

};

static esp_lcd_panel_handle_t panel_handle = NULL;
static esp_lcd_panel_ssd1306_config_t ssd1306_config = {
    .height = EXAMPLE_LCD_V_RES,
};
static esp_lcd_panel_dev_config_t panel_config = {
    .bits_per_pixel = 1,
    .reset_gpio_num = EXAMPLE_PIN_NUM_RST,
    .vendor_config = &ssd1306_config,
};

const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
static lv_disp_t *disp;

EventGroupHandle_t xCreatedEventGroup;

void vTaskSetBits(void *pvParameters)
{
    EventBits_t uxBits;
    uxBits = xEventGroupClearBits(xCreatedEventGroup, BIT_0);
    gpio_config_t io_conf = {
        .pin_bit_mask = (1ULL << GPIO_NUM_11),
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE};
    gpio_config(&io_conf);

    for (;;)
    {
        if (!(gpio_get_level(GPIO_NUM_11))) // Button Press
        {
            uxBits = xEventGroupGetBits(xCreatedEventGroup);
            if ((uxBits & BIT_0) == 0) // BIT_0 not set
            {
                uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_0); // Set BIT_0

                ESP_LOGI(TAG, "BIT_0 Set");
            }
            else
            {
                uxBits = xEventGroupClearBits(xCreatedEventGroup, BIT_0); // clear BIT_0

                ESP_LOGI(TAG, "BIT_0 Cleared");
            }
            uxBits = xEventGroupGetBits(xCreatedEventGroup);
            ESP_LOGI(TAG, "uxBits after press: %u", (uint8_t)uxBits);
        }

        vTaskDelay(pdTICKS_TO_MS(10));
    }
}

void lcd_power(void *pvParameters)
{
    /*Initial run everything appears to function properly
    until we reach the first call of esp_lcd_panel_on_off()
    in the if(BIT_0 == SET)*/
    ESP_LOGI(TAG, "We're in lcd_power()");
    for (;;)
    {
        ESP_LOGI(TAG, "Before xEventGroupWaitBits");
        EventBits_t uxBits = xEventGroupWaitBits(
            xCreatedEventGroup,
            BIT_0,
            pdFALSE,
            pdTRUE,
            portMAX_DELAY);
        ESP_LOGI(TAG, "xCreatedEventGroup BIT_0: %u", (uint8_t)uxBits);
        if ((uxBits & BIT_0) != 0)
        {
            //First run we get to the ESP_LOGI() and it prints. then stuck.
            ESP_LOGI(TAG, "We're in lcd_power() if");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
        }
        else
        {
            ESP_LOGI(TAG, "We're in lcd_power() else");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, false));
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

void scroll_text()
{
    /*Take LVGL mutex, blocks indefinitely. True when
    taken. False not taken.*/
    if (lvgl_port_lock(0))
    {
        /*Prints scrolling Hello World text to screen*/
        example_lvgl_demo_ui(disp);
        // Release the mutex
        lvgl_port_unlock();
    }
}

void lvgl_init()
{
    ESP_LOGI(TAG, "Initialize LVGL");
    lvgl_port_init(&lvgl_cfg);

    /*Can't (Don't know how?) to declare this in Global scope as
    certain members require initialization and it needs to be const.
    I'm not sure if this is where my issue lies? I think I'm going to 
    have to look deeper into how LVGL functions.*/
    const lvgl_port_display_cfg_t disp_cfg = {
        .io_handle = io_handle,
        .panel_handle = panel_handle,
        .buffer_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES,
        .double_buffer = true,
        .hres = EXAMPLE_LCD_H_RES,
        .vres = EXAMPLE_LCD_V_RES,
        .monochrome = true,
        .rotation = {
            .swap_xy = false,
            .mirror_x = true,
            .mirror_y = true,
        }};
    disp = lvgl_port_add_disp(&disp_cfg);
    
        /* Rotation of the screen */
    lv_disp_set_rotation(disp, LV_DISP_ROT_NONE);

}

/*Initialization of global members. Figure imitate Arduino structure
as I only need to run this once. Declutter main() and reserve it for 
calling tasks/etc. */
void setup()
{
    ESP_LOGI(TAG, "Initialize I2C bus");
    ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &i2c_bus));

    ESP_LOGI(TAG, "Install panel IO");
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus, &io_config, &io_handle));

    ESP_LOGI(TAG, "Install SSD1306 panel driver");
    ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle));

    /*Need to reset panel before calling init(). Turn it on*/
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    if (!(xCreatedEventGroup = xEventGroupCreate()))
    {
        ESP_LOGE(TAG, "Error creating xCreatedEventGroup");
        return;
    }
}
void app_main(void)
{
    /*Initial setup*/
    setup();

    /*Start lvgl. I tried moving this into it's own function as
    I wasn't sure if disp_cfg going out of scope was an issue
    but if I make lvgl_init a task with a forever loop at the end
    that crashes it as well.*/
    lvgl_init();

    ESP_LOGI(TAG, "Display LVGL Scroll Text");
    scroll_text();

    /*Wait for BIT_0 to be set then toggle screen power on and off*/
    xTaskCreate(lcd_power, "LCD Power", 2048, (void *)xCreatedEventGroup, 1, NULL);

    /*Listen for button press and set/clear BIT_0 accordingly*/
    xTaskCreate(vTaskSetBits, "IR Set Bits", 2048, (void *)xCreatedEventGroup, 1, NULL);
}

Thank you both for your time. It is much appreciated!

Edit:
I forgot to include the output.

I (275) sleep: Enable automatic switching of GPIO sleep configuration
I (282) coexist: coex firmware version: 4482466
I (310) coexist: coexist rom version 5b8dcfa
I (311) main_task: Started on CPU0
I (311) main_task: Calling app_main()
I (311) BLINK: Initialize I2C bus
I (311) gpio: GPIO[6]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0
I (321) gpio: GPIO[7]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0 
I (331) BLINK: Install panel IO
I (331) BLINK: Install SSD1306 panel driver
I (441) BLINK: Initialize LVGL
I (441) LVGL: Starting LVGL task
I (441) BLINK: Display LVGL Scroll Text
I (441) BLINK: We're in lcd_power()
I (441) BLINK: Before xEventGroupWaitBits
I (451) gpio: GPIO[11]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (451) main_task: Returned from app_main()
I (63451) BLINK: BIT_0 Set
I (63451) BLINK: uxBits after press: 1
I (63451) BLINK: xCreatedEventGroup BIT_0: 1
I (63451) BLINK: We're in lcd_power() if
I (68451) BLINK: BIT_0 Cleared
I (68451) BLINK: uxBits after press: 0
I (72451) BLINK: BIT_0 Set
I (72451) BLINK: uxBits after press: 1

After the first button press we go into if(BIT_0 == SET) then we stick.

Glad that you figured out!

The following code sequence does not seem correct -

EventBits_t uxBits = xEventGroupWaitBits(
            xCreatedEventGroup,
            BIT_0,
            pdFALSE,
            pdTRUE,
            portMAX_DELAY);
        ESP_LOGI(TAG, "xCreatedEventGroup BIT_0: %u", (uint8_t)uxBits);
        if ((uxBits & BIT_0) != 0)
        {
            //First run we get to the ESP_LOGI() and it prints. then stuck.
            ESP_LOGI(TAG, "We're in lcd_power() if");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
        }
        else
        {
            ESP_LOGI(TAG, "We're in lcd_power() else");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, false));
        }

It seems that you are expecting xEventGroupWaitBits to return when BIT_0 is cleared so that you can turnoff the screen. This wont happen because xEventGroupWaitBits returns when a bit is set. You should use another bit BIT_1 to turn off the screen. Also, use xClearOnExit parameter of xEventGroupWaitBits so that you do not need to clear bit explicitly:

void vTaskSetBits(void *pvParameters)
{
    EventBits_t uxBits;
    uxBits = xEventGroupClearBits(xCreatedEventGroup, BIT_0);
    gpio_config_t io_conf = {
        .pin_bit_mask = (1ULL << GPIO_NUM_11),
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE};
    gpio_config(&io_conf);

    for (;;)
    {
        if (!(gpio_get_level(GPIO_NUM_11))) // Button Press
        {
            uxBits = xEventGroupGetBits(xCreatedEventGroup);
            if ((uxBits & BIT_0) == 0) // BIT_0 not set
            {
                uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_0); // Set BIT_0

                ESP_LOGI(TAG, "BIT_0 Set");
            }
            else
            {
                uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_1); // Set BIT_1

                ESP_LOGI(TAG, "BIT_1 Set");
            }
            uxBits = xEventGroupGetBits(xCreatedEventGroup);
            ESP_LOGI(TAG, "uxBits after press: %u", (uint8_t)uxBits);
        }

        vTaskDelay(pdTICKS_TO_MS(10));
    }
}

void lcd_power(void *pvParameters)
{
    /*Initial run everything appears to function properly
    until we reach the first call of esp_lcd_panel_on_off()
    in the if(BIT_0 == SET)*/
    ESP_LOGI(TAG, "We're in lcd_power()");
    for (;;)
    {
        ESP_LOGI(TAG, "Before xEventGroupWaitBits");
        EventBits_t uxBits = xEventGroupWaitBits(
            xCreatedEventGroup,
            BIT_0 | BIT_1,
            pdTRUE,  /* xClearOnExit */
            pdFALSE, /* xWaitForAllBits */
            portMAX_DELAY);
        ESP_LOGI(TAG, "xCreatedEventGroup bits: %u", (uint8_t)uxBits);
        if ((uxBits & BIT_0) != 0)
        {
            //First run we get to the ESP_LOGI() and it prints. then stuck.
            ESP_LOGI(TAG, "We're in lcd_power() if");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
        }
        if ((uxBits & BIT_1) != 0)
        {
            ESP_LOGI(TAG, "We're in lcd_power() else");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, false));
        }
    }
}
1 Like

Thank you very much for the input! I apologize for the delayed response. I unfortunately haven’t had the opportunity to work on this lately, so the problem has not been resolved. I will make the changes you suggest and see what the end result is. Either way I appreciate the time taken to point out the flaw.

This ended up being part of my issue. I thought xEventGroupWaitBits waited for a change in the specified bits. Was able to simplify the functions quite a bit now that I had a better understanding of the functionality. Thank you for your help!

void vTaskSetBits(void *pvParameters)
{
    //LCD is on initially so start with LCD_POWER_BIT set
    EventBits_t uxBits = xEventGroupSetBits(xCreatedEventGroup, LCD_POWER_BIT);

    //Loop waiting for button presses
    for (;;)
    {
        if (!(gpio_get_level(GPIO_NUM_11))) // Button Press
        {
            //Set BUTTON_PRESS so lcd_power() will do it's thing
            uxBits = xEventGroupSetBits(xCreatedEventGroup, BUTTON_PRESS);

            /*lcd_power() waits for BUTTON_PRESS to be set. If xEventGroupWaitBits()
            stops blocking lcd_power() will turn the screen off if LCD_POWER_BIT is
            set, as it's on. If it is set turn the LCD off. 
            Set or unset LCD_POWER_BIT based on current state.*/
            if ((uxBits & LCD_POWER_BIT) != 0)
                uxBits = xEventGroupClearBits(xCreatedEventGroup, LCD_POWER_BIT); 
            else
                uxBits = xEventGroupSetBits(xCreatedEventGroup, LCD_POWER_BIT);
        }

        vTaskDelay(pdTICKS_TO_MS(5));
    }
}

void lcd_power(void *pvParameters)
{

    EventBits_t uxBits;

    for (;;)
    {
        //Wait for BUTTON_PRESS 
        uxBits = xEventGroupWaitBits(
            xCreatedEventGroup,
            BUTTON_PRESS,
            pdTRUE,
            pdFALSE,
            portMAX_DELAY);


        if ((uxBits & LCD_POWER_BIT) != 0)  //LCD_POWER_BIT == 1 so LCD is on, turn it off
        {
            ESP_LOGI(TAG, "Power off LCD");
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, false));
        }
        else                                //LCD_POWER_BIT == 0 so LCD is off, turn it on
        {
            ESP_LOGI(TAG, "Power on LCD");       
            ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
        }
    }
}