Questions about basic button-toggle-led program

Scalpel78
Posts: 51
Joined: Sun Feb 26, 2017 7:31 am

Questions about basic button-toggle-led program

Postby Scalpel78 » Sun Feb 26, 2017 8:41 am

Hello,
I'm on day 1 of my ESP32 programming, and could use some guidance on a simple test-app I want to make for my "Sparkfun ESP32 Thing".

I got Blinky example working, and now I want to rewrite my application so that the LED is triggered from one of the buttons instead.

Below is what I've got so far. As far as I can tell the button and LED should be configured, but I'm uncertain if the interrupt code is correct.

I use "make app-flash" to upload the code, and then "make monitor" to see whats going on. It gives me this output;
Button configured
LED configured
Interrupt configured
Task watchdog got triggered. The following tasks did not feed the watchdog in time:
- IDLE (CPU 0)
Tasks currently running:
CPU 0: buttonToLED
CPU 1: IDLE
Task watchdog got triggered. The following tasks did not feed the watchdog in time:
- IDLE (CPU 0)
Tasks currently running:
CPU 0: buttonToLED
CPU 1: IDLE
Also, when I press the button nothing happens.

Would you mind commenting on what I'm doing wrong?

Code: Select all

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "driver/gpio.h"
#include <esp_log.h>

#define LED_GPIO GPIO_SEL_5
#define BUTTON_GPIO GPIO_SEL_0

void button_pressed_handler(void *args)
{
  printf("Button pressed");
  int btn_state = gpio_get_level(BUTTON_GPIO);
  gpio_set_level(LED_GPIO,btn_state);
}

void  setup_button_to_led(void *pvParameter)
{
  //Configure button
  gpio_config_t btn_config;
  btn_config.intr_type = GPIO_INTR_POSEDGE; //Enable interrupt on positive edge
  btn_config.mode = GPIO_MODE_INPUT;        //Input
  btn_config.pin_bit_mask = BUTTON_GPIO;    //Set pin where button is connected
  btn_config.pull_up_en = GPIO_PULLUP_DISABLE; //Disable pullup
  btn_config.pull_down_en = GPIO_PULLDOWN_ENABLE; //Enable pulldown
  gpio_config(&btn_config);
  printf("Button configured\n");

  //Configure LED
  gpio_pad_select_gpio(LED_GPIO);
  gpio_set_direction(LED_GPIO, GPIO_MODE_OUTPUT);
  printf("LED configured\n");

  //Configure interrupt and add handler
  gpio_install_isr_service(0);
  gpio_isr_handler_add(BUTTON_GPIO, button_pressed_handler, NULL);
  printf("Interrupt configured\n");

  //Wait for button press
  while (1)
  {
    ESP_LOGD("test", "Waiting for button press....");
  }
}

void app_main()
{
  xTaskCreate(&setup_button_to_led, "buttonToLED", 4096, NULL, 5, NULL);
}

ESP_igrr
Posts: 2072
Joined: Tue Dec 01, 2015 8:37 am

Re: Questions about basic button-toggle-led program

Postby ESP_igrr » Sun Feb 26, 2017 2:57 pm

1. pin_bit_mask member of gpio_config_t must be set to a bit mask, not to the pin number:

Code: Select all

btn_config.pin_bit_mask = (1 << BUTTON_GPIO);
Actually if you check serial output, you will see that an error is printed on the console during the gpio_config call, that pin mask is not correct.

2. Don't use printf in interrupt handlers. Another thing: standard output streams are buffered, so lines from printf (and other functions which write to stdout/stderr) will appear on serial console immediately only if you terminate them with "\n".

3. Your code sets up interrupt on a positive edge and then sets LED to the state of GPIO. Because GPIO is high after a positive edge, LED will never go low (except, maybe, for the case of a short positive pulse on a button input pin). Consider changing to GPIO_INTR_ANYEDGE, then the LED will follow the state of the button.

4. The reason "task watchdog" warning is printed is that your code goes into an infinite loop and never yields to lower-priority tasks. Task watchdog detects this and warns about this issue. Consider changing the code to something like:

Code: Select all

  while (1)
  {
      ESP_LOGD("test", "Waiting for button press....");
      vTaskDelay(1000/portTICK_PERIOD_MS);
  }

Scalpel78
Posts: 51
Joined: Sun Feb 26, 2017 7:31 am

Re: Questions about basic button-toggle-led program

Postby Scalpel78 » Sun Feb 26, 2017 5:17 pm

Thank you very much for an excellent reply. I got it working now.

In addition to your observations I also changed the definitions from

Code: Select all

#define LED_GPIO GPIO_SEL_5
#define BUTTON_GPIO GPIO_SEL_0
to

Code: Select all

#define LED_GPIO GPIO_NUM_5
#define BUTTON_GPIO GPIO_NUM_0
For the next newbie to come along, here is the fully functioning program:

Code: Select all

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "driver/gpio.h"
#include <esp_log.h>
#include "sdkconfig.h"

#define LED_GPIO GPIO_NUM_5
#define BUTTON_GPIO GPIO_NUM_0

void isr_button_pressed(void *args)
{
  int btn_state = gpio_get_level(BUTTON_GPIO);
  gpio_set_level(LED_GPIO,btn_state);
}

void task_button_to_led(void *pvParameter)
{
  //Configure button
  gpio_config_t btn_config;
  btn_config.intr_type = GPIO_INTR_ANYEDGE; 	//Enable interrupt on both rising and falling edges
  btn_config.mode = GPIO_MODE_INPUT;        	//Set as Input
  btn_config.pin_bit_mask = (1 << BUTTON_GPIO); //Bitmask
  btn_config.pull_up_en = GPIO_PULLUP_DISABLE; 	//Disable pullup
  btn_config.pull_down_en = GPIO_PULLDOWN_ENABLE; //Enable pulldown
  gpio_config(&btn_config);
  printf("Button configured\n");

  //Configure LED
  gpio_pad_select_gpio(LED_GPIO);					//Set pin as GPIO
  gpio_set_direction(LED_GPIO, GPIO_MODE_OUTPUT);	//Set as Output
  printf("LED configured\n");

  //Configure interrupt and add handler
  gpio_install_isr_service(0);						//Start Interrupt Service Routine service
  gpio_isr_handler_add(BUTTON_GPIO, isr_button_pressed, NULL); //Add handler of interrupt
  printf("Interrupt configured\n");

  //Wait
  while (1)
  {
      ESP_LOGI("test", "Waiting for button press....");
	  vTaskDelay(1000/portTICK_PERIOD_MS);
  }
}

void app_main()
{
  xTaskCreate(&task_button_to_led, "buttonToLED", 2048, NULL, 5, NULL);
}
I have a followup question - in the function "task_button_to_led", I have to have the endless loop at the end. Otherwise I get this error:
Guru Meditation Error of type IllegalInstruction occurred on core 0. Exception was unhandled.
Register dump:
PC : 0x400f1e41 PS : 0x00060730 A0 : 0x00000000 A1 : 0x3ffb93a0
A2 : 0x00000000 A3 : 0x00000000 A4 : 0x00000000 A5 : 0x00000000
A6 : 0x00000000 A7 : 0x00000000 A8 : 0x800f1e41 A9 : 0x3ffb9380
A10 : 0x0000000a A11 : 0x3ffb4868 A12 : 0x00000000 A13 : 0x00000000
A14 : 0x00000001 A15 : 0x00000001 SAR : 0x00000013 EXCCAUSE: 0x00000000
EXCVADDR: 0x00000000 LBEG : 0x400014fd LEND : 0x4000150d LCOUNT : 0xfffffffa

Backtrace: 0x400f1e41:0x3ffb93a0 0x00000000:0x3ffb93e0

Rebooting...
Are we not supposed to quit from a task-function? Or do they need to quit in a special way? I've seen some references to vTaskDelete, but not sure when it is needed.

And another question - when I added the call to vTaskDelay in the loop in "task_button_to_led" I got weird crashes until I increased the usStackDepth parameter in the xTaskCreate call from 512 to 2048. Is there a good way to know how big the stack should be?

ESP_Angus
Posts: 2344
Joined: Sun May 08, 2016 4:11 am

Re: Questions about basic button-toggle-led program

Postby ESP_Angus » Mon Feb 27, 2017 12:05 am

Scalpel78 wrote: Are we not supposed to quit from a task-function? Or do they need to quit in a special way? I've seen some references to vTaskDelete, but not sure when it is needed.
Correct, the task function should never return. ("app_main" is an exception, as it's called from a parent function which handles it returning.)

vTaskDelete(NULL) will delete the current running task, so you can use this as an alternative to returning.
Scalpel78 wrote:And another question - when I added the call to vTaskDelay in the loop in "task_button_to_led" I got weird crashes until I increased the usStackDepth parameter in the xTaskCreate call from 512 to 2048. Is there a good way to know how big the stack should be?
You can call uxTaskGetStackHighWaterMark(NULL) which will return the smallest amount of free stack that has been available to that task so far (or you can check other tasks, by passing the task's handle in place of NULL).

So, a good approach is to start high then check uxTaskGetStackHighWaterMark(NULL) and reduce the value proportionately, while keeping some safety overhead.

printf() is a bit greedy in terms of stack usage, which is probably why you needed to bump it up in the first place.

It's worth remembering that if you're not running low on free heap RAM then it may not be necessary to shrink the stack at all, it doesn't otherwise hurt to allocate more stack than a task needs.

Scalpel78
Posts: 51
Joined: Sun Feb 26, 2017 7:31 am

Re: Questions about basic button-toggle-led program

Postby Scalpel78 » Mon Feb 27, 2017 11:33 am

Thanks for an execellent reply!

Would it make sense to do something like this in the last loop of a task to detect when things are about to fail? (At work, so I haven't been able to test).

Code: Select all

while (1)
{
  vTaskDelay(1000 / portTICK_PERIOD_MS);
  if (uxTaskGetStackHighWaterMark(NULL) < 10)
    ESP_LOGW("Close to running out of stack space!");
}
Also, how much stackspace does the ESP32 have in total? Theoretically, if I just had one task, how much stackspace could it use?

ESP_Angus
Posts: 2344
Joined: Sun May 08, 2016 4:11 am

Re: Questions about basic button-toggle-led program

Postby ESP_Angus » Mon Feb 27, 2017 11:31 pm

Scalpel78 wrote:Thanks for an execellent reply!

Would it make sense to do something like this in the last loop of a task to detect when things are about to fail? (At work, so I haven't been able to test).

Code: Select all

while (1)
{
  vTaskDelay(1000 / portTICK_PERIOD_MS);
  if (uxTaskGetStackHighWaterMark(NULL) < 10)
    ESP_LOGW("Close to running out of stack space!");
}
You can do something like this, although it's tricky. I'd probably want to see more than 10 bytes overhead, as interrupts can occur at any time and these also use stack overhead. Maybe you've already seen all the possible timing combinations occur to leave you with 10 bytes breathing space, but maybe there's some sequence of events that hasn't happened yet and will blow that...

In general, this type of last-ditch check shouldn't be necessary. There are some features in esp-idf to detect stack overflows when they happen. You can configure these under "Component Config" -> "FreeRTOS" in menuconfig, but the default "canary" stack checker is pretty good (it puts some bytes at the end of the stack and checks on each context switch to see if they are overwritten. If they are, it will abort the firmware.)

There is an even more precise stack overflow checker option (kudos to Ivan & Jeroen for this one), which uses a hardware watchpoint to immediately trip an exception at the exact cycle the end of the stack is overwritten. This is currently (by mistake I think) hidden under the "Debug FreeRTOS Internals" option, but there's a change pending to move it up near the other stack checker options. Turning this on is also a good idea if you are concerned about stack usage.
Also, how much stackspace does the ESP32 have in total? Theoretically, if I just had one task, how much stackspace could it use?
Stack for each task is allocated from the main heap in RAM. The xPortGetFreeHeapSize() function will tell you how much heap is free in your firmware. You can keep adding to task stacks until you run out of heap. :)

(Note that because of heap fragmentation, you may not be able to allocate the entire xPortGetFreeHeapSize() as a single task stack. I think there's a feature pending which will tell you how big the largest continuous section of free heap is.)

This is all a little different from "non-OS" C microcontroller programming, where the stack and heap are both unbounded and grow towards each other until they collide and you crash. That approach doesn't scale beyond a single task! This RTOS-style approach is also more deterministic, if you can determine your worst-case memory usage scenario (with some "wiggle room" on top) then you can allocate memory so that you know that no sequence of events should be able to cause you to run out. At least not without memory corruption bugs or other troublesome things...

Angus

Who is online

Users browsing this forum: No registered users and 100 guests