OneButton library and function problem

Hello,

First post here so please be gentle... :slight_smile:

I thought I would ask for some help because I've spent a couple of frustrating days trying to figure out what's going on.

So, I have the following code, which seems to work fine when I use two (momentary) switches (A0, A1).

The idea is that I have 11 LEDs (D2-D12) and A0 selects a "range" from D2 to D12, while A1 will cycle from D2 all the way up to the selected range.

So, for example, if I use A0 to select the pin D7, the A1 will cycle from D2 to D7. I hope this makes sense... Here's the code:





// constants won't change. They're used here to set pin numbers:
const int range_button  = A0; // Button to select the range
const int scan_button  = A1; // Button to strt the scanning


const int array_PINS[] = {13, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // Pin13 is used as "dummy" pin for the bit-sifting
const int array_HEX[] = { 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800};


// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long previousMillis = 0; // Will be used to track how long since last event "fired"
unsigned long currentMillis = 0;
unsigned long scan_interval = 450; // (ms) Time intervals between LEDs
unsigned long rng_interval = 200; // (ms) Time intervals for the range button


// Variables will change
int LEDstate = 0x01;
int rng_LEDstate = 0x01;

// scanner() variables
int scan_counter = 0;   // counter for the number of button presses
int scan_buttonState = 0;         // current state of the button
int scan_lastButtonState = 0;     // previous state of the button

int idx = 0;   // array index

// Calling functions
void range();
void scanner();
void pins_state();

void setup()
{
  pinMode(range_button, INPUT);
  pinMode(scan_button, INPUT);


  // Set Pins with LEDs to OUTPUT. Includes Pin13
  for (int x = 0; x < sizeof(array_PINS); x++) {
    pinMode(array_PINS[x], OUTPUT);
  }
}

void loop() {

  range();
  scanner();

}

//---------------------------------------------------------------------------
//--------------------------------- FUNCTIONS -------------------------------

//---------------------------------------------------------------------------
// Function to select the number of steps/range of the LEDs
void range()
{
  currentMillis = millis();

  if (digitalRead(range_button) == HIGH)
  {
    if (idx < 12) {

      if ((currentMillis - previousMillis) >= rng_interval) {
        previousMillis = currentMillis;
        // Use "<<" to "bit-shift" everything to the left once
        rng_LEDstate = rng_LEDstate << 1;
        idx++;
        digitalWrite(array_PINS[idx], bitRead(rng_LEDstate,  idx));
        digitalWrite(array_PINS[idx - 1], bitRead(rng_LEDstate,  idx - 1));
      }
    } else {
      rng_LEDstate = 0x1;
      idx = 0;
      digitalWrite(13, HIGH);
    }
  }
}


//---------------------------------------------------------------------------
// Function to scan through the selected steps/range of the LEDs
void scanner()
{
  scan_buttonState = digitalRead(scan_button);

  if (scan_buttonState != scan_lastButtonState)
  {
    if (scan_buttonState == HIGH)
    {
      scan_counter++;
      delay(50);  // Delay a little bit to avoid bouncing
    }
  }
  scan_lastButtonState = scan_buttonState;

  if (scan_counter % 2 == 1 && LEDstate <= array_HEX[idx]) {
    pins_state();
    for (int x = 0; x <= idx; x++) {
      digitalWrite(array_PINS[x], bitRead(LEDstate, x));
    }
  } else {
    LEDstate = 0x1;
    scan_counter = 0;
    digitalWrite(13, HIGH);
  }
}

//---------------------------------------------------------------------------
// Function to turn ON/OFF the LEDs
void pins_state()
{
  currentMillis = millis();

  if ((currentMillis - previousMillis) >= scan_interval) {
    previousMillis = currentMillis;
    // Use "<<" to "bit-shift" everything to the left once
    LEDstate = LEDstate << 1;
  }
}



What I've been trying to do (but I have failed miserably) is to include the OneButton.h and use only A0 for both tasks. I have managed to use the range() function with a long press but the scanner() seems to not working properly (sort press). Here's what I've got so far:

#include "OneButton.h"

OneButton button(A1, false, false);

const int array_PINS[] = {13, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // Pin13 is used as "dummy" pin for the bit-sifting
const int array_HEX[] = { 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800};


// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long previousMillis = 0; // Will be used to track how long since last event "fired"
unsigned long currentMillis = 0;
unsigned long scan_interval = 450; // (ms) Time intervals between LEDs/ Relays
unsigned long rng_interval = 200; // (ms) Time intervals for the range button


// Variables will change
int LEDstate = 0x01;
int rng_LEDstate = 0x01;

// scanner() variables
int scan_counter = 0;   // counter for the number of button presses
int scan_buttonState = 0;         // current state of the button
int scan_lastButtonState = 0;     // previous state of the button

int idx = 0;   // array index

// Calling functions
void range();
void scanner();
void pins_state();

void setup()
{
  button.attachClick(scanner);                  // link the function to be called on a singleclick event.
  button.attachDuringLongPress(range);          // link the function to be called on a longpress event.
  button.setPressTicks(1000);                   // set time (ms) before long press triggers the function

  // Set Pins with LEDs to OUTPUT. Includes Pin13
  for (int x = 0; x < sizeof(array_PINS); x++) {
    pinMode(array_PINS[x], OUTPUT);
  }
}

void loop() {

  button.tick();

  delay(10); // not sure if this is needed
}
//---------------------------------------------------------------------------
//--------------------------------- FUNCTIONS -------------------------------

//---------------------------------------------------------------------------
// Function to select the number of steps/range of the LEDs/ Relays
void range()
{
  currentMillis = millis();

  //if (digitalRead(A1) == HIGH)
  //{
  if (idx < 12) {

    if ((currentMillis - previousMillis) >= rng_interval) {
      previousMillis = currentMillis;
      // Use "<<" to "bit-shift" everything to the left once
      rng_LEDstate = rng_LEDstate << 1;
      idx++;
      digitalWrite(array_PINS[idx], bitRead(rng_LEDstate,  idx));
      digitalWrite(array_PINS[idx - 1], bitRead(rng_LEDstate,  idx - 1));
    }
  } else {
    rng_LEDstate = 0x1;
    idx = 0;
    digitalWrite(13, HIGH);
  }
  //}
}


//---------------------------------------------------------------------------
// Function to scan through the selected steps/range of the LEDs/ Relays
void scanner()
{
  scan_buttonState = digitalRead(A1);

  if (scan_buttonState != scan_lastButtonState)
  {
    if (scan_buttonState == HIGH)
    {
      scan_counter++;
      delay(50);  // Delay a little bit to avoid bouncing
    }
  }
  scan_lastButtonState = scan_buttonState;

  if (scan_counter % 2 == 1 && LEDstate <= array_HEX[idx]) {
    pins_state();
    for (int x = 0; x <= idx; x++) {
      digitalWrite(array_PINS[x], bitRead(LEDstate, x));
    }
  } else {
    LEDstate = 0x1;
    scan_counter = 0;
    digitalWrite(13, HIGH);
  }
}

//---------------------------------------------------------------------------
// Function to turn ON/OFF the LEDs/ Relays
void pins_state()
{
  currentMillis = millis();

  if ((currentMillis - previousMillis) >= scan_interval) {
    previousMillis = currentMillis;
    // Use "<<" to "bit-shift" everything to the left once
    LEDstate = LEDstate << 1;
  }
}

Any help it's much appreciated! FWIW, I use an Arduino diecimila (I think!).

Also, as you can probably tell I'm far from being a programming wizard so any general suggestions/ improvements are more than welcome!

if D2 is selected, is there any way to select something other than D2?

not clear what the above is intended to do. the value of rng_LEDstate is being shifted left at the same time idx is being incremented and bitRead() being used to get the bit value in rng_LEDstate. in other words, isn't idx reading the same bit in rng_LEDstate as it's being shifted?

there are simpler ways to sequentially turn on a set of LEDs

sizeof returns the size in bytes. an int is 2 bytes

Thanks for the prompt reply gcjr. For some reason I can't find the way to do a proper quatation...

if D2 is selected, is there any way to select something other than D2?
Not sure if I understand what you mean.

not clear what the above is intended to do. the value of rng_LEDstate is being shifted left at the same time idx is being incremented and bitRead() being used to get the bit value in rng_LEDstate. in other words, isn't idx reading the same bit in rng_LEDstate as it's being shifted?

there are simpler ways to sequentially turn on a set of LEDs

There are definitely simpler ways to sequentially turn on a set of LEDs. The only problem is that after many (and I mean many) attempts this seems to work and therefore I settled on this solution. As I said, other simpler solutions are more than welcome

sizeof returns the size in bytes. an int is 2 bytes
This also seems to work but I changed the x < sizeof(array_PINS); to x < 12;

What is the task of this programme in real life?

The idea is to have 11 relays connected to D2-D12 and activate one relay at a time depending on how many relays I have selected. So, if I select 5 relays, D2-D6 will sequentially turn on and off.

Something similar to a scan card for a benchtop DMM if you're familiar with test equipment

Does it makes sense?

Is this code you wrote?

I see you've made no use of the serial monitor.

If you do, you can use it to print the values of key variables and confirm that they inform the flow through your code as you intended.

Primitive, effective and almost universally used with little programs like this.

I don't think I've gotten anything to function, and function according to my plan, without some kind of feedback.

Most of my sketches are very chatty, maybe even after I needed them to be to "see" what's really going on.

And what's with the dummy pin 13? Sounds fishy, can't look close at your code just now to see what you talking about.

a7

This is my code indeed. Well, I have borrowed parts from other code I have found online and I put them all together. I do use Serial.print indeed. I just deleted it form the code I uploded to make it easier/ less messy.

Pin 13 "dummy pin" is a LED connected to D13 and has multiple tasks like be "on" to indicate that the board is powered, "on" when the "scan" is finished, etc..

I have spent the last few days trying to reach to this point and quite frankly I'm a bit tired. This code seems to work OK but I just can't get it to work with using a single button instead of two.

I can run the range() with a long press but the scanner() doesn't run with a single (short) press.

If everything fails, I will just use two buttons as I'd rather spend some xmas time playing with my kids rather than swearing :slight_smile:

not sure what you're trying to do:

  • learn to program or learn to program better
  • just make the project work

??

select the text and then click where is says "Quote"

if the scan is only turning on D2 and the
if a range is selected by pressing the range select button when the corresponding LED is on, how can the range be changed if the scan only turns on LED 2?

why not have a variable indicating the range, the index of the last LED to turn on. when the timer expires

  • turn off the current LED
  • advance the index
  • check if it's > than the selected max and reset to 0
  • turn on the LED at the index
  • make the array bytes, or
  • n = sizeof(array) / sizeof(int);

Hello tinybart

Post a simple handdrawn timing diagram to show all states and transitions.

First of all, thank you all for your help!

Secondly, although learning a bit of programming wouldn't hurt at all (last time I did some C++ was 20 years ago at the uni) the main task for now is to finish a personal project I'm working on.

Is this of any help? The top button selects the "range" from D2 to D12 and the bottom button cycles through that range. Once the "scanning" is finished, D13 turns on to inidicate the competition of the scanning.

How do you want to implement the one-button selection of relays? For example; would you "short press" one button to indicate a relay, then "long press" to select the relay, and "double short-press" to start the cascade?

The range() would run only once at the beginning while the cascade/ scanner() would be a repetitive process. So, what I had in my mind was:

Long press: cycle through the pins to select the last active pin/relay (i.e trigger the range() function)

Short press: to start the cascade from D2 all the way up to the selected last pin

The second code I uploaded on the first post seem to do the long press part. Unfortunately you have to install the OneButton.h on the wokwi simulation/ ciruit I posted.

In other words, on the wokwi example I posted it would be:

Long press: top button (A0)
Short press bottom button (A1)

Note: if you keep the top button (A0) pressed, it cycles through the pins.

No. You can use the library manager tab and within a few keystrokes locate and make available the OneButton library. You need to # include it, natch.

Your wokwi will be useful. It will save me the trouble of plopping your code in there and wiring it up to see what is what. Not just now - she who must not be kept waiting has texted, so no time.

If your pin 13 LED serves a purpose different to the other pins, it should probably just be its own thing.

You may not yet have come to grips with the idea that in C/C++, zero (0) is a perfectly good number. And index. Mostly, arrays of size N use indices from 0 to N - 1. Loops run 0 to N - 1. Reset something is to set it back to zero. And so forth.

Your way works, but it is srsly swimming upstream. Also, it makes it harder to see the use of the stand-alone LED as opposed to the ones that are grouped together by the array.

Constants don't need to be expressed in HEX. Doing actually make readers think there is some reason you are calling attention to the number as possibly later wanting to be seen as bits rather than as numbers, or something. The contents of the variable are the same. Use whatever is natural.

int LEDstate = 1;
int rng_LEDstate = 1;

When you are relaxing in front of the fire or under the umbrella, you might enjoy reading what one of the big thinkers says about the zero thing:

https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html

I'll try your code when I can. Personally I don't like button UIs that do short/long/double tricks. At least for development, you may be better off just throwing a few more virtual pushbuttons on in there. When that all works, circle back and fix it for one or two button style if you want.

a7

LOL. I ran the simulation and it seemed to function OK, so I settled in to (re)read the thread to see what your problem is…

So you are at where you are now trying to take a step to using one button, got it.

Did you try any examples, or write a simple sketch for the button library just to get an idea of how it works?

Rather than hook the big functions to the callbacks, you could just use mechanism to set one or the other of two flags (Boolean variables here).

Then where you currently recognize and react to the two hand-coded buttons, replace the code with a check for the flag.

If a flag is set, do whatever pressing the old button would have done, and clear the flag.

It should be relatively minor surgery. Except for the holding the button down part. That should be something you can glean from some OneButton method that returns the current state of the button; it would be best to experiment with that outside the context of a larger program just to see how that works or doesn't.

a7

As I said I've spent a couple of days trying to figure out what's the problem. I tried boolean variables, smaller codes, etc... I'm sure it's a simple fix and most likely has to do with the millis() part.

If somebody can offer a solution to this specific problem described on the first post, it would be much appreciated. Otherwise I will call it a day since I'm not planning to become a programmer anyway.

BTW, here's the example with the onebutton. Long press seems to be working fine. Short press not really...

LOL.

Your first posted code, with two buttons, functions well. I can use one button to enter a length of the sequence, and the other to trigger a trip through. Between your description and the behaviour of the code, I think it is working.

However you got that far, good on you.

The range() function is suitable for being called "DuringLongPress", and you properly hacked it to just be called by the OneButton library mechanism.

Adding print statements, viz:

void range()
{
  Serial.println("range callback");
// ...

we see that the range function is repeatedly called as long as the button is held.

This is the same "oxygen", or attention from the microprocessor, that range() enjoys by virute of being called repeatedly by your loop() function. In the original, range() is alway called, but does nothing its button isn't pressed.

So all you had to do was remove the code checking the button. As a matter of code (!), you prolly didn't even need to remove the check - range() will only be called by OneButton if the button is down anyway... I'm too lazy to check, but pretty sure.

The case of scanner() is quite different.

Here, adding print statements,

void scanner()
{
  Serial.println("scanner callback");

we see scanner() called exactly once per short press (click). This is different to how it was designed to work - in the original, scanner() (along with range()) is called repeatedly always.

And plays two rolls.

One is to launch a scan, and the other is to provide oxygen processor attention to the task of advancing the scan from one to the range currently established.

If you don't continuously call scanner(), nothing makes the scan proceed. All you can accomplish is triggering a launch. There is nothing to make a launched sequence progress. All you can do is restart the scan.

So... you needa make two functions to replace scanner(). One might be called launch():

void launch()
{
// do everything you need to reset and prepare for a sequence to begin

which would be attached to the OneButton press of A1.

Then, in the loop(), you would need a function I'll call serviceScanner(), like

void loop() {

  button.tick();
  serviceScanner();

  delay(10); // not sure if this is needed - probably not but it will not hurt
}

with serviceScanner() reading

void serviceScanner()
{
// if a scan has been launched, or is in progress, advnace the scan at a pace
}

If a scan is not in progress, serviceScanner() does nothing but return. If launch() gets called, however, the service routine will see that it should get busy and make the scan progress. Subsequent calls see that a scan is in progress, and make progress if it is time. When serviceScanner() gets to the end of the scan, it goes back to a state wherein it will again do nothing if called. Until... the OneButton callback launch() sez get busy

Srsly, you are very close to getting this to function. The big problem, to repeat, is that your original scanner() had dual responsibilities, and by removing its button handling and the frequent call from the loop() you broke it.

It might be possible to write one scnnaer() function so it could be called by the OneButton mechanism and also "manually" by you from the loop(). I do not recommend trying that. I do not recommend trying that. I don't think it would be any fun at all, and would perhaps involve some kludges. Best to leave the callbacks for the OneButton.

Using one button is a bit of a nightmare here, but it is in the end easy to see why the code does exactly what you wrote, even as it is not yet what you want.

I have voted against the one button interface, partly for exactly why you got run over when you tried. I repeat, you have all the pieces here. I think you might reasonably take a stab at adjusting your code accordian to my remarks here.

That is to say if you actually understand how the original functioned. You've got timers and buttons an all kindsa good stuff.

BTW - in the wokwi, when your diagram window is active, hit 'g' and you'll get a nice snappy grid making laying out LEDs in a prettier way easy. And you can omit the resistors and save some wiring and reduce clutter - wokwi LEDs are unbreakable and do not need series current-limiting resistors.

I also should say that whatever else you've accomplished, learing to use the wokwi was a good investment. I've always claimed it is no harder than coming to grips with the IDE and uploading and stuff, but I have been hacking for decades so I am not always the best judge. To me it seemed intuitive and waaay easier than getting out the breadbox and lashing up real circuitry. I don't recall it having been any kind of real struggle.

HTH

a7

Thank you a7. I'll give it a try.

We did, for fun right from under the umbrella.

She who will not be named did the heavy lifting. Her launch function, used only by the OneButton callback mechanism ended up reading:

void launch()
{
      scan_counter++:
}

We also converted it to neopixels and ran the strip off one output pin.

Some ppl think it needs a speed control for the scanner process. :wink:

a7

Excellent! I think I got it working.

I'll test it in more detail later but it seems to do the job.

I also corrected a few mistakes on the code and most likely I missed as many.

Many thanks for all your help!

When you've reached a good point, please post the code you ended up with.

TIA

a7

1 Like