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.
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.
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(; 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.
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.
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.
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));
}
}
}