Adding Pause Functionality to BPM Counter

Hi,

I'm following this project that's a BPM calculator with tap functionality. The creator of this project says that he wrote this code with much help from others. I asked the following question to him but he said he didn't know much about how to implement it.

It's actually pretty simple. When the encoder button is being held for a second, the bpm counter shall pause, and once released, it'll start from the beginning. I need this functionality to sync with the rest of my band on live stage.

#define USE_TIMER_1     false
#define USE_TIMER_2     true
#define USE_TIMER_3     false
#define USE_TIMER_4     false
#define USE_TIMER_5     false

#include <TimerInterrupt.h>
#include <ISR_Timer.h>
#include <ClickEncoder.h>
#include <TimerOne.h>
#include <ArduinoTapTempo.h>
#include <LiquidCrystal.h>

#define LCD_RS       8
#define LCD_RW       9
#define LCD_EN      10
#define LCD_D4       4
#define LCD_D5       5
#define LCD_D6       6
#define LCD_D7       7
#define LCD_CHARS   16
#define LCD_LINES    2

LiquidCrystal lcd(LCD_RS, LCD_RW, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7);

#define LED 2
#define MAX_BPM 480
#define MIN_BPM 40

#define MULT1 11
#define MULT2 12


const int BUTTON_PIN = 3;
ArduinoTapTempo tapTempo;

ClickEncoder *encoder;
int16_t last, value, bpmtemp, encoderpos, interval;
float period;
uint16_t counter = 0;
unsigned long lastUpdate = 0;
uint8_t multiplier = 1; 

int multplusState = 0;
int multminState = 0;
int lastmultplusState = 0;
int lastmultminState = 0;

void timerIsr() {
  encoder->service();
}

//--PULSE INTERRUPT--//

void PulseHandler()
{

  counter+=2;
  if (counter >= period)
  {
    counter = 0;
    digitalWrite(LED, !digitalRead(LED));
  }
}



void displayAccelerationStatus() {
  lcd.setCursor(0, 0);
  lcd.print("BEATS PER MINUTE: ");
}


void setup()
{

  // IO INIT
  pinMode(LED, OUTPUT);
  pinMode(BUTTON_PIN, INPUT);
  digitalWrite(BUTTON_PIN, HIGH);
  pinMode(MULT1, INPUT_PULLUP);
  pinMode(MULT2, INPUT_PULLUP);

  // ENCODER INIT
  encoder = new ClickEncoder(A1, A0, A2, 4);

  // LCD INIT
  lcd.begin(LCD_CHARS, LCD_LINES);
  lcd.clear();
  displayAccelerationStatus();

  // VARIABLES
  value = 120;                    // The BPM value
  bpmtemp = 0;                    // Temporary BPM
  encoderpos = 0;                 // Encoder position
  last = -1;                      // Last BPM (unused, potentially usable to only update LCD and period once the BPM actually changed)
  interval = 1;                   // Period of BPM timer in ms (2ms or double speed because a pulse consists of an upgoing and downgoing slewage)
  period = 30000 / value * 2;   // Convert BPM to desired output period

  // TIMER INIT
  Timer1.initialize(1000);            // For rotary acceleration
  Timer1.attachInterrupt(timerIsr); 
  ITimer2.init();                     // For output pulses
  if (ITimer2.attachInterruptInterval(interval, PulseHandler));

  tapTempo.setMinBPM((float)MIN_BPM);
  tapTempo.setMaxBPM((float)MAX_BPM);

  //tapTempo.disableSkippedTapDetection();
  //tapTempo.setTotalTapValues(4);
  tapTempo.setBeatsUntilChainReset(10);
  
  Serial.begin(9600);
}

void loop() {
  boolean buttonDown = digitalRead(BUTTON_PIN) == LOW; // if pressed, buttondown = true
  tapTempo.update(buttonDown); // update if buttondown = true

  
  if ((uint16_t)tapTempo.getBPM() != bpmtemp)
  {
    encoderpos = 0;
    bpmtemp = (uint16_t)tapTempo.getBPM();
  }
  encoderpos += encoder->getValue();

  // MIGHT BE ABLE TO PUT LINES 119 THROUGH 131 INTO AN IF ELSE STATEMENT
  
  value = bpmtemp + encoderpos;

  if (value >= MAX_BPM) // Restrict to max bpm
  {
    encoderpos = MAX_BPM - bpmtemp;
    value = MAX_BPM;
  }

  if (value <= MIN_BPM) // Restrict to min bpm
  {
    encoderpos = MIN_BPM - bpmtemp;
    value = MIN_BPM;
  }

  // Alter the output pulse period based on the bpm
  period = (30000 / value * 2) / multiplier;

  multplusState = digitalRead(MULT2);

 // compare the buttonState to its previous state
  if (multplusState != lastmultplusState) {
    // if the state has changed, increment the counter
    if (multplusState == HIGH) {
      // if the current state is HIGH then the button
      // wend from off to on:
    multiplier += 1;

    if(multiplier >4)
    {
      multiplier = 4; 
    }

    lcd.setCursor(11, 1);
    lcd.print(multiplier);
    } 
  }
  // save the current state as the last state,
  //for next time through the loop
  lastmultplusState = multplusState;

  multminState = digitalRead(MULT1);

 // compare the buttonState to its previous state
  if (multminState != lastmultminState) {
    // if the state has changed, increment the counter
    if (multminState == HIGH) {
      // if the current state is HIGH then the button
      // wend from off to on:
    multiplier -= 1;

    if(multiplier <1)
    {
      multiplier = 1;
    }
    lcd.setCursor(11, 1);
    lcd.print(multiplier);
    } 
  }
  // save the current state as the last state,
  //for next time through the loop
  lastmultminState = multminState;
  
  




    
   // Show the current BPM on the LCD
if (lastUpdate - millis() > 16 && last != value)
{
  lastUpdate = millis();
  last = value;
    Serial.println("JA");
    lcd.setCursor(0, 1);
    lcd.print("                ");
    
    lcd.setCursor(6, 1);
    lcd.print(value);
    
    lcd.setCursor(10, 1);
    lcd.print("x");

    lcd.setCursor(11, 1);
    lcd.print(multiplier);
    
  
    if (value == MAX_BPM)
    {
      lcd.setCursor(13, 1);
      lcd.print("MAX");
    }
  
    if (value == MIN_BPM)
    {
      lcd.setCursor(0, 1);
      lcd.print("MIN");
    }
}
 




  ClickEncoder::Button b = encoder->getButton();
  if (b != ClickEncoder::Open) {
    Serial.print("Button: ");
#define VERBOSECASE(label) case label: Serial.println(#label); break;
    switch (b) {
        VERBOSECASE(ClickEncoder::Pressed);
        VERBOSECASE(ClickEncoder::Held)
        VERBOSECASE(ClickEncoder::Released)
        VERBOSECASE(ClickEncoder::Clicked)
      case ClickEncoder::DoubleClicked:
        Serial.println("ClickEncoder::DoubleClicked");
        encoder->setAccelerationEnabled(!encoder->getAccelerationEnabled());
        Serial.print("  Acceleration is ");
        Serial.println((encoder->getAccelerationEnabled()) ? "enabled" : "disabled");


#ifdef WITH_LCD
        displayAccelerationStatus();
#endif
        break;
    }
  }
}

Do not lead with this....

You have a button pin defined. Test that button pin, and when pressed, make "x" readings (100, for example) and then wait for the next button press.

What i meant by easy was that what i want, not the task itself.

I checked the encoder library and saw that theres a function for hold, but im not sure how would i implement it.

In this case, I would not use it.

The button on an encoder is usually called SEL for Select. Do you know how to read the button/SEL?

Cant say i do. Its normally assigned to tap tempo in the above code. What i wish is to add a pause functionality in addition to all of that. On short, repetitive clicks, it changes the bpm, but on a press and hold, it can pause the bpm counter, thays what i need.

I would rename loop() to xloop(). Then, make a new loop() function and inside the new `loop(), read the button and wait for a LOW from the button (press = LOW, not press = HIGH).

void loop() {
  while (!digitalRead(BUTTON_PIN) {
    xloop();
}

And now, put xloop() in a 100 count loop to get 100 readings.

void loop() {
  while (!digitalRead(BUTTON_PIN) {
    for (int i = 0; i < 100; i++)
      xloop();
    }
}

After 100 readings, the "while" will be waiting for the next button press.

I think I could not express myself clearly, or understand this code's purpose myself.

What I need is -in addition to the above code & functionalities and without breaking them-, to add a "hold" function to the encoder button. Say, I powered the board, and the bpm counter started to count and led blinks accordingly. I hold down the encoder button for more than 1 second and this count/blinking should stop. Once released, it should continue and start counting again.

I'm merely a novice in coding, but as I check the code I understand from the logic that, with some if clauses, theoretically it should be handled.

You want a "pause" button that you hold until you want to "un-pause."

1 Like

That is affirmative, sir.

Short Tap on Encoder Button = Tap Tempo (as it is now)
Long Tap/Hold >500ms or 1000ms = Pause/Hold the Counter,
Release = Restart the counter.

The trick here is that, say I set the BPM on stage for a live song for 110, but it's out of sync a bit when played live, I wish to pause the counter and count it and release the button so that BPM will -hopefully- sync the live tempo.

Any ideas, anyone?

Don't bump.

You have not shown your attempts. Included in the solutions I have proposed is an interrupt on rising edge (assuming buttons are LOW on press, HIGH on release).

I thought someone would help with the coding here, as I don't have any experience in making a workaround. I well understand the logic, just don't know how to write it. I also see the interrupt's data that on Nano, it can be used with 2nd or 3rd digital pins, which is suitable for the current scenario (encoder button is assigned to D3)

Also, Is it a must to have knowledge of coding in this forum in order to ask for assistance? Because every time I ask for some assistance, I'm getting kicked around. If this forum for people that has some coding knowledge, then let me know so that I'd never ask for anything again..

No, you don't need coding knowledge to ask for assistance, but you do need a willingness to learn coding. My interpretation, which could wrong, of what you are asking is that you want someone to make the changes to your code for you, rather than help you learn to do it yourself. If you want someone to do it for you then please explicitly say so. There are people here who will write code for you, but generally they expect payment. If I am mistaken then the people trying to help you except to see some evidence of you trying to learn.

If you were hoping someone would make the changes for you for free then I'm afraid that's unlikely to happen, and perhaps this isn't the right forum for you.

Since when does asking for a small additional code -which is not an atom science- "cost" people?

Look, sir, in my daily routine, I, unfortunately, can not spare enough time to delve into coding. It's been so for years. If I could, trust me, I'd not write these sentences above. But trust me, I can not..

This is a project that I thought would be useful for my live gigs, I can live with it somehow as it is. I've already asked the creator of it about this and he said he also gotten help from other people to write these codes above. So I wanted to ask for assistance in the one and only Arduino forum, expecting some answers like "hey, you can add these below loop section as if blablabla and it would" but instead I'm getting life lessons.

The implementation I'm asking is not something that will wash the dishes, do the chores, please the wife, and empty the trash bin. It's just an addition. To me; I'd simply go like "if button3 high >500ms, then pause, or interrupt,(or whatever);

That's my logic, but as you can see its not good to go with.

What am I, pâte de canard?

Yes, this is true.

@Slammer88 - Show your effort... and doors will open. More advice from "pâte de canard" is "Start with the simple parts" (you did call this "simple"... remember?) which is to say, rather than a complex, interrupt driven, DJ beatbox... start with a "reading a button" sketch, then then "reading an encoder" sketch, then "using an LCD sketch" then use the ?TapTempo? sketch. As you might know, example sketches are installed with your IDE, and more advanced examples are installed with your libraries.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.