OneButton icon indicating copy to clipboard operation
OneButton copied to clipboard

Library not working on Nano ESP32 Board

Open pietsmar opened this issue 2 years ago • 10 comments

Hi,

i am migrating a sketch from Arduino Nano / Nano Every to a Nano ESP32 board and had some challenges (current lib version). The button clicks were not properly recognized. It seems that the button assignment to INPUT_PULLUP does not work correctly. The digitalRead() of the respective Pin switches to 0, if pressed (LOW-active), however, it never goes back to 1 when released.

Maybe it is related to the Pin numbers of the ESP32 and the matching to traditional Nano pins.

A workaround (see line 65) that I found was to add for every used Pin the PinMode (Pin, INPUT_PULLUP) in setup().

`/* This is a sample sketch to show how to use the OneButtonLibrary to detect double-click events on a button. The library internals are explained at http://www.mathertel.de/Arduino/OneButtonLibrary.aspx

Setup a test circuit:

  • Connect a pushbutton to pin A1 (ButtonPin) and ground.
  • The pin 13 (StatusPin) is used for output attach a led and resistor to ground or see the built-in led on the standard arduino board.

The sketch shows how to setup the library and bind the functions (singleClick, doubleClick) to the events. In the loop function the button.tick function must be called as often as you like. */

// 03.03.2011 created by Matthias Hertel // 01.12.2011 extension changed to work with the Arduino 1.0 environment

#include "OneButton.h"

#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO_EVERY) // Example for Arduino UNO with input button on pin 2 and builtin LED on pin 13 #define PIN_INPUT 2 #define PIN_LED 13

#elif defined(ESP8266) // Example for NodeMCU with input button using FLASH button on D3 and using the led on -12 module (D4). // This LED is lighting on output level LOW. #define PIN_INPUT D3 #define PIN_LED D4

#elif defined(ESP32) // Example pin assignments for a ESP32 board // Some boards have a BOOT switch using GPIO 0. #define PIN_INPUT 4 // Attach a LED using GPIO 25 and VCC. The LED is on when output level is LOW. #define PIN_LED 13

#endif

// Setup a new OneButton on pin PIN_INPUT // The 2. parameter activeLOW is true, because external wiring sets the button to LOW when pressed. OneButton button(PIN_INPUT, true, true);

// In case the momentary button puts the input to HIGH when pressed: // The 2. parameter activeLOW is false when the external wiring sets the button to HIGH when pressed. // The 3. parameter can be used to disable the PullUp . // OneButton button(PIN_INPUT, false, false);

// current LED state, staring with LOW (0) int ledState = LOW;

// setup code here, to run once: void setup() { Serial.begin(115200); delay(2000); Serial.println("One Button Example with polling.");

// enable the standard led on pin 13. pinMode(PIN_LED, OUTPUT); // sets the digital pin as output

// Workaround for ESP32 usage // !!! Without this line the digitalRead() of the Pin never goes back to 1 even if button released !!! pinMode(4, INPUT_PULLUP);

// enable the standard led on pin 13. digitalWrite(PIN_LED, ledState);

// link the Click function to be called on a single click event. button.attachClick(Click); } // setup

// main code here, to run repeatedly: void loop() { // keep watching the push button: button.tick(); Serial.println(digitalRead(PIN_INPUT)); // You can implement other code in here or just wait a while delay(50); } // loop

// this function will be called when the button was pressed 2 times in a short timeframe. void Click() { Serial.println("Click");

ledState = !ledState; // reverse the LED digitalWrite(PIN_LED, ledState); } // doubleClick

// End`

pietsmar avatar Sep 02 '23 09:09 pietsmar

Did you try the examples?

eastwife avatar Dec 13 '23 14:12 eastwife

yes, I used the examples as a basis. In the meantime I switched to another library that solved my problem.

pietsmar avatar Dec 13 '23 16:12 pietsmar

an hypothesis on why this happens

the library does not implement a begin() function that would actually set the pin mode to INPUT_PULLUP. this is done in the constructor.

In C++ the constructor is called super early, way before main() (let alone setup()) is called

It could be possible that the Nano ESP32 init phase in its main() removes the pullup and just sets all pins to INPUT again.

➜ proper solution would require to implement a begin() method and have the user call begin() in the setup. This would impact all existing examples.

(EDIT: confirmed that by forcing again the PULLUP mode in the setup solves the issue)

JM-FRANCE avatar Jan 10 '24 12:01 JM-FRANCE

an hypothesis on why this happens

the library does not implement a begin() function that would actually set the pin mode to INPUT_PULLUP. this is done in the constructor.

In C++ the constructor is called super early, way before main() (let alone setup()) is called

It could be possible that the Nano ESP32 init phase in its main() removes the pullup and just sets all pins to INPUT again.

➜ proper solution would require to implement a begin() method and have the user call begin() in the setup. This would impact all existing examples.

(EDIT: confirmed that by forcing again the PULLUP mode in the setup solves the issue)

How to forcing again the PULLUP mode in the setup?

zenz avatar Feb 21 '24 13:02 zenz

you add pinMode(buttonPIn, INPUT_PULLUP); in the setup but best would be to add a begin() method to the library that would do so.

JM-FRANCE avatar Feb 21 '24 14:02 JM-FRANCE

It could be possible that the Nano ESP32 init phase in its main() removes the pullup and just sets all pins to INPUT again.

confirmed.

wltue avatar Feb 22 '24 06:02 wltue

yes, I used the examples as a basis. In the meantime I switched to another library that solved my problem.

@pietsmar Hi, what is the library you used, to solve your problem? I'm having exact same issue. Boris

paelzer avatar Jun 28 '24 22:06 paelzer

I am using the Button2 lib.

https://github.com/LennartHennigs/Button2

pietsmar avatar Jun 29 '24 06:06 pietsmar

@pietsmar Hi, what is the library you used, to solve your problem? I'm having exact same issue. Boris

If you don’t want to change your code, add pinMode(buttonPIn, INPUT_PULLUP); in the setup

JM-FRANCE avatar Jun 29 '24 07:06 JM-FRANCE

Thanks a lot, will give it a try :-)

paelzer avatar Jun 29 '24 13:06 paelzer

I invested some time into this topic and found the root cause in the Arduino ESP board framework.

This was done using the settings

  • Board: "Arduino Nano ESP32"
  • Pin Numbering: "By Arduino pin (default)"
  • USB Mode: "Normal mode (TinyUSB)"

Root Cause Analysis

When using a global variable of type OneButton like:

OneButton button(PIN_INPUT, false);

The initialization code is executed before the setup() function is called. The framework however initializes all ports immediately before calling the setup() function and therefore overwrites the setup by the OneButton library.

This behavior differs from the behaviors of other board implementations (e.g. Espressif ESP32-S3). Other libraries might also be effected by this behavior.

The workaround from @JM-FRANCE re-initializes solves the problem be again re-initializing according to what is required. Thanks for the hint.

Proposed Solution

In the SimpleOneButton example I have implemented another approach by defining a global variable as a pointer to the OneButton instance and explicit creating the OneButton instance in the setup(). Changed lines are:

// global variable,
OneButton *button;

// in setup()
button = new OneButton(PIN_INPUT, true);
button->attachDoubleClick(doubleClick);

// in loop()
button->tick();

or see commit 93ca0ae

This will be part of the next release.

mathertel avatar Jul 31 '24 17:07 mathertel

Using dynamic allocation with new is not a recommended practice in C++ when you can do otherwise and it also makes it more difficult to just build an array of buttons and have its size calculated at compile time to have static asserts etc. I would not recommend to have this as an example.

The C++ norm states that constructors for global instances are called before main() gets a chance to be executed so indeed let alone setup() hence the behavior you see as the setup() does configure the board and thus you should not rely on the constructor for anything hardware related.

That’s why many libraries have a begin() or init() function. This should be the best practice you encourage.

I understand that adding begin() now departs from your initial approach and the codes that are out there for the AVR platform do not have it but it would prepare your library for the 21st century.

User’s Codes for AVR would still be working as the setup() does not modify the pull-ups if they were set early and having a call to begin() would allow for a standard solution for the other platforms.

of course it’s your library so your call.

JM-FRANCE avatar Aug 01 '24 06:08 JM-FRANCE

@JM-FRANCE : I like this comment. I am not fully signing your statement but I see your point. Actually I already thought about adding an setup() function - so I did in commit.

Deferred initialization

In the SimpleOneButton example I have used another approach by defining a OneButton as a global variable that is not yet initialized and use the new setup() function to set the hardware parameters.

This even works when the board implementation resets the hardware configuration before the main setup() is called.

In the new setup(...) function the pinMode can be given in the 2. parameter as requested by #138 to have more flexibility.

// global variable,
OneButton button;

// in setup()
button.setup(PIN_INPUT, INPUT_PULLUP, true);

or see commit 70c4b58

This will be part of the next release

mathertel avatar Aug 01 '24 17:08 mathertel

Great - I think that brings the needed feature

JM-FRANCE avatar Aug 02 '24 06:08 JM-FRANCE