martx
Published © GPL3+

Rower Console with Hall Sensor

A simple Rower Console for my old rower.

BeginnerWork in progress238
Rower Console with Hall Sensor

Things used in this project

Hardware components

Espressif ESP32 NodeMCU Module WLAN WiFi Development Board avec CP2102
×1
HD44780 2004 LCD Display Green Bundle 4x20 Characters with I2C
×1
KY-024 Linear Magnetic Hall Sensor
×1
KY-040 Rotary Angle Encoder Rotary Encoder Module
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
5kR or 10kR
×1
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
×2
5 mm LED: Green
5 mm LED: Green
×1
Resistor 10k ohm
Resistor 10k ohm
×2
Resistor 221 ohm
Resistor 221 ohm
×1
Solderless Breadboard Full Size
Solderless Breadboard Full Size
×1
Jumper wires (generic)
Jumper wires (generic)
×1

Story

Read more

Custom parts and enclosures

RowerConsole_V0.1

Schematics

RowerConsole_V0.1

RowerConsole V0.1

Code

RowerConsole_V0.1

Arduino
//  ---------------------------------------------------------------------------------------------------------------------
//  Rower Console With Hall Sensor
//  Written by Martin Maillardet for ESP32 Dev Module
//  V0.1  August 2021
//
//  Hardware requirements:
//  * ESP32-WROOM-32
//  * 20 x 4 character LCD screen.
//  * Push button for pause/resume. (on digital pin 2)
//  * Push button for cycling through display modes. (on digital pin 3)
//  * Rotary Encoder
//  * Hall sensor which should be attached rower to sense when the rolling sit pass over. (on digital pin 4)
//
//  Noteworthy features:
//  * Computes time counted in ascending order (in hours, minutes and seconds), distance in kilometers,
//    speed in kilometers per hour.
//  * Computes strokes, average strokes per hour and maximum strokes per hour, rowing time, kilometers rowed,
//    average kilometers per hour, maximum kilometers per hour, speed, maximum speed and average speed for all sessions/periods recorded.
//    (shown in "S##" data when looking at session data in pause mode)
//  * Computes total strokes, average strokes per hour and maximum strokes per hour, total time, total kilometers rowed,
//    average kilometers per hour and maximum kilometers per hour rowed for all sessions/periods recorded.
//    (shown in "Tot" data when looking at session data in pause mode)
//  * Stores up to 49 sessions/periods which can be viewed when in pause mode by pressing the Display Mode button
//    NOTE: 50th session gets recorded in position for session 49 thus overriding data for 49th session.
//  * No data is being recorded while in pause mode.
//
//
//  Thanks to Alan De Windt (alan_dewindt@yahoo.com), the coding of distance, speed and lap counter is based on his own "Bicycle Odometer & Speedometer"
//  (https://create.arduino.cc/projecthub/alan_dewindt/bicycle-odometer-and-speedometer-with-99-lap-period-recorder-331d2b?ref=tag&ref_id=speedometer&offset=0)
//  Thanks to Cimanes for the cadence code based on his "Bike trainer logger"
//  (https://create.arduino.cc/projecthub/cimanes/bike-trainer-logger-8d97d6?ref=search&ref_id=bike&offset=3)
//  Thanks to Curious Scientist, the code part of menu is based on his "Advanced menu system with rotary encoder"
//  (https://curiousscientist.tech/blog/20x4lcd-rotaryencoder-menu)
//
//  ---------------------------------------------------------------------------------------------------------------------


#include "AiEsp32RotaryEncoder.h"
#define ROTARY_ENCODER_A_PIN 32
#define ROTARY_ENCODER_B_PIN 33
#define ROTARY_ENCODER_BUTTON_PIN 25
#define ROTARY_ENCODER_STEPS 4
AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, -1, ROTARY_ENCODER_STEPS);


#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4); //------- Set the LCD address to 0x27 for a 20 chars and 4 line lcd

#define EEPROM_SIZE 512

//---------------------------------------------- Menu Variables
int menuCounter = 0; //counts the clicks of the rotary encoder between menu items (0-3 in this case)

bool distBetwin2Strokes_selected = false; //enable/disable to change the value of menu item
bool resistanceLevel_selected = false;
bool strokeCountSessionTarget_selected = false;
bool strokeCountTotalTarget_selected = false;
//Note: if a menu is selected ">" becomes "X".

bool refreshLCD = true; //refreshes values
bool refreshSelection = false; //refreshes selection (> / X)

//---------------------------------------------- Counter Variables
float distBetwin2Strokes = 6.1; //------- Distance betwin too strokes expressed in Meters
float resistanceLevel = 2.2; //------- Resistance Level of the Rower
int strokeCountSessionTarget = 500;
int strokeCountTotalTarget = 1500;

const int PAUSEBUTTON = 16;
boolean lastPauseButton = LOW;
boolean currentPauseButton = LOW;

const int DISPLAYMODEBUTTON = 17;
boolean lastDisplayModeButton = LOW;
boolean currentDisplayModeButton = LOW;

const int STROKESENSOR = 18;
boolean lastStrokeSensor = LOW;
boolean currentStrokeSensor = LOW;

boolean startShown = HIGH;
unsigned long startShownTime = 0;
unsigned long blinkStartMessageDelay = 3000;

boolean paused = LOW;
boolean pausedShown = LOW;
unsigned long pausedStartTime = 0;

boolean strokeActionShown = LOW;
unsigned long strokeActionShownStartTime = 0;

boolean letsGoShown = LOW;
unsigned long letsGoStartTime = 0;

unsigned long lastStrokeStartTime = 0;
unsigned long strokingTime = 0;
unsigned long lastMinuteStrokeStartTime = 0;

int currentDisplayMode = 0;
int showSession = 0;
int sessionCurrentlyShown = 50;
int currentSession = 0;

float currentDistance;
unsigned long currentDuration;
int currentMaximumKPH;
int currentAverageKPH;
int currentKPH;
int averageDistancePerMinute;

float arrayDistance[50];
unsigned long arrayDuration[50];
int arrayMaximumKPH[50];
int arrayAverageKPH[50];
int arrayDistancePerMinute[50];
unsigned long arrayStrokeCount[50];
byte arrayMaximumCadence[50];
byte arrayAverageCadence[50];
byte arrayCadence[50];

unsigned int currentSitCount = 0;
unsigned int currentStrokeCount = 0;
unsigned int lastStrokeCount = 0;
unsigned int lastMinuteStrokeCount = 0;
unsigned int lastMinuteStrokeCounter = 0;
unsigned long currentTime = 0;
unsigned long sessionStartTime = 0;

float km = 0.00;
float kph = 0.00;
int intHours;
int intMinutes;
int intSeconds;

const unsigned long MILLISECONDSINSECOND = 1000;
const unsigned long MILLISECONDSINMINUTE = 60000;
const unsigned long MILLISECONDSINHOUR = 3600000;

//---------------------------------------------- Cadence Variables

const int DELAYTIME2CADENCEPULSEMAX     = 5000 ; // Max allowed time delay between cadence pulses
const int DELAYTIME2CADENCEPULSEMIN  =  200 ; // Min time delay between cadence pulses
const int DELAYTIMEPRINTCADENCE   = 1000 ; // Time delay for serial print refresh

unsigned long tprint = 0 ;   // Time reference in miliseconds for signal print

byte  currentCadence   = 0 ;        // Cadence (rpm)
byte  Cadence0  = 0 ;        // Cadence reference for filter
byte  Dcad      = 10 ;        // Max cadence difference

byte  currentAverageCadence = 0 ;            // Average cadence (pedals / min)
byte  currentMaximumCadence = 0 ;            // Maximum cadence (pedals / min)

const byte HALL_SENSOR_LED = 26;

//***********************************************************************************************************************************************
//                                                                   SETUP                                                                      *
//***********************************************************************************************************************************************

void setup() 
{
  Serial.begin(115200); // begin in 115200 baud

  //---------------------------------------------- Initialize Rotary
  rotaryEncoder.begin();
  rotaryEncoder.setup(
      [] { rotaryEncoder.readEncoder_ISR(); },
      [] { rotary_onButtonClick(); });

  //---------------------------------------------- Configure digital input pins for push buttons and Hall sensor
  pinMode (STROKESENSOR, INPUT);
  pinMode (PAUSEBUTTON, INPUT);
  pinMode (DISPLAYMODEBUTTON, INPUT);
  pinMode (HALL_SENSOR_LED, OUTPUT);

  //---------------------------------------------- Initialize maximum KPH in totals as this may not be calculated if no maximum was computed for sessions
  //---------------------------------------------- and there may be random data in memory location
  arrayMaximumKPH[0] = 0;

  //---------------------------------------------- Initialize LCD screen

  lcd.begin(20, 4);
  lcd.init();
  lcd.backlight();
  lcd.clear();

  EEPROM.begin(EEPROM_SIZE);
  delay(500);

  distBetwin2Strokes = EEPROM.readFloat(0);
  resistanceLevel = EEPROM.readFloat(8);
  strokeCountSessionTarget = EEPROM.readInt(16);
  strokeCountTotalTarget = EEPROM.readInt(24);
}

//***********************************************************************************************************************************************
//                                                                   LOOP                                                                       *
//***********************************************************************************************************************************************

void loop() 
{

  //---------------------------------------------- Get current millis
  currentTime = millis();

  //---------------------------------------------- show "PRESS BUTTON TO START"
  if (startShown)
  { 
    StartMessage();
  }

  //---------------------------------------------- Read Stroke Hall sensor
  currentStrokeSensor = debounce(lastStrokeSensor, STROKESENSOR);
  if (lastStrokeSensor == HIGH && currentStrokeSensor == LOW)
  {

    //---------------------------------------------- If initial "PRESS BUTTON TO START" is not displayed and not currently paused...
    if (!startShown && !paused)
    {
      //---------------------------------------------- Increase Stroke count
      currentSitCount++;
      digitalWrite(HALL_SENSOR_LED, HIGH);
      delay(20);
      digitalWrite(HALL_SENSOR_LED, LOW);
      if (currentSitCount%2) 
      {
        currentStrokeCount++;

        //---------------------------------------------- Display "+" to show that one stroke was recorded
        lcd.setCursor(4, 0);
        lcd.print(F("+"));
        strokeActionShown = HIGH;
        strokeActionShownStartTime = currentTime;
    
        //---------------------------------------------- Compute millis it took for this latest stroke
        if (lastStrokeStartTime > 0) 
        {
          strokingTime = currentTime - lastStrokeStartTime;
          
          //---------------------------------------------- Compute current speed in kilometers per hour based on time it took to complete last stroke
          kph = (3600000 / strokingTime) * distBetwin2Strokes / 1000;
          currentKPH = kph;

          //---------------------------------------------- If current speed is new maximum speed for this session then store it
          if (currentMaximumKPH < currentKPH)
          {
            currentMaximumKPH = currentKPH;
          }
        }
      lastStrokeStartTime = currentTime;
      }
    }
  }
  lastStrokeSensor = currentStrokeSensor;


  //---------------------------------------------- Calculate Cadence
  
  //---------------------------------------------- Cadence calculation upon cadence pulse
  if ((currentStrokeCount > 1) and strokingTime > DELAYTIME2CADENCEPULSEMIN)
  {  
    if (Cadence0 < 15) Dcad = 8;
    else Dcad = 5;
    
    //---------------------------------------------- Calculate the cadence
    currentCadence = (60000 / strokingTime);

    //---------------------------------------------- Filter
    currentCadence = constrain(currentCadence, max(0, Cadence0 - Dcad), Cadence0 + Dcad); 
    Cadence0 = currentCadence;                      //------- Refresh Cadence reference for filter

    //----------------------------------------------If current cadence is new maximum cadence for this session then store it
    if (currentMaximumCadence < currentCadence)
    {
      currentMaximumCadence = currentCadence;
    }

    //----------------------------------------------Force cadence to "0" when no pulses are detected for the pre-selected time delay:
    if ((currentTime - lastStrokeStartTime) > DELAYTIME2CADENCEPULSEMAX) currentCadence = 0;

    //----------------------------------------------Calculate average values
    if (currentTime - strokingTime < 2000) currentAverageCadence = currentCadence;    //------- Delay before begin calculation of average cadence (2000)
    else currentAverageCadence = (currentStrokeCount * 60000 / (currentTime - sessionStartTime))  ;    //------- Average cadence (strokes / min)

    //----------------------------------------------Calculate strokes last minute
    if (currentTime - sessionStartTime < 60000)
    {   //------ Delay before begin calculation of strokes last minute (60000)
      lastMinuteStrokeCounter = currentStrokeCount;
      lastMinuteStrokeStartTime = sessionStartTime;
    }
    if (currentTime - sessionStartTime > 60000)
    {
      if (currentTime - lastMinuteStrokeStartTime >= 60000) 
      {
        lastMinuteStrokeCount =  (currentStrokeCount - lastStrokeCount);
        lastMinuteStrokeStartTime = currentTime;
        lastStrokeCount = currentStrokeCount;
      }
      lastMinuteStrokeCounter = (currentStrokeCount - lastStrokeCount);
    }
  }

  PressPauseButton();
  PressModeButton();
  MessageTiming();

  //---------------------------------------------- If "PUSH BUTTON TO START" is not showing and not currently paused...
  if (!startShown && !paused)
  {
    //---------------------------------------------- Compute milliseconds since start of session
    currentDuration = currentTime - sessionStartTime;

    //---------------------------------------------- Compute distance and average kilometers per hour if stroke has moved
    if (currentStrokeCount > 0)
    {
      //---------------------------------------------- Compute kilometers rowed
      //---------------------------------------------- Distance betwin too strokes is in meters
      currentDistance = currentStrokeCount * distBetwin2Strokes / 1000;

      //---------------------------------------------- Compute average kilometers per hour since start of session
      currentAverageKPH = currentDistance * 3600000 / currentDuration;
    }
  }

  //---------------------------------------------- If no messages are currently showing then update data on display
  if (!startShown && !letsGoShown && !pausedShown)
  {
    if (currentDisplayMode < 3)
    {
      if (currentDisplayMode == 1)
      {
        UpdateDataOnDisplay();
        rotaryEncoder.disable();
      }

      //---------------------------------------------- When Pressing Mode Button
      else
      {
        rotaryEncoder.enable();
        rotary_loop();
        if(refreshLCD == true) //If we are allowed to update the LCD ...
        {
          updateLCD(); // ... we update the LCD ...

          //... also, if one of the menus are already selected...
          if(distBetwin2Strokes_selected == true || resistanceLevel_selected == true || strokeCountSessionTarget_selected == true || strokeCountTotalTarget_selected == true)
          {
          // do nothing
          }
          else
          {
            rotaryEncoder.setBoundaries(0, 3, false); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
            rotaryEncoder.setAcceleration(0);
            if (menuCounter != rotaryEncoder.readEncoder())
            {
              rotaryEncoder.reset (menuCounter);
            }
            updateCursorPosition(); //update the position
          }
          refreshLCD = false; //reset the variable - wait for a new trigger
        }
       if(refreshSelection == true) //if the selection is changed
       {
          updateSelection(); //update the selection on the LCD
          refreshSelection = false; // reset the variable - wait for a new trigger
       }
      }     
    }

    //----------------------------------------------  Otherwise device is paused so show historical session information
    else
    {
      ShowHistoricalSessionsInformations();
      rotaryEncoder.enable();
      RotaryHistoricalSessionsInformations();
    }
  }
}
//***********************************************************************************************************************************************
//                                                                   START MESSAGE                                                              *
//***********************************************************************************************************************************************

void StartMessage ()
{
  if ((currentTime - startShownTime > 0) && (currentTime - startShownTime < blinkStartMessageDelay))
  {
    lcd.setCursor(0, 1);
    lcd.print(F("----PRESSEZ SUR-----"));
    lcd.setCursor(0, 2);
    lcd.print(F("----DEBUT/PAUSE-----"));
  }
  if ((currentTime - startShownTime > blinkStartMessageDelay) && (currentTime - startShownTime < blinkStartMessageDelay + 50))
  {
    lcd.clear();
  }
  if ((currentTime - startShownTime > blinkStartMessageDelay + 100) && (currentTime - startShownTime < blinkStartMessageDelay * 2))
  {   
    lcd.setCursor(0, 0);
    lcd.print(F("Dist/CdR (m) -> "));
    lcd.setCursor(16,0); //1st line, 10th block
    lcd.print(F("   ")); //erase the content by printing space over it
    lcd.setCursor(16,0); //1st line, 10th block
    if (distBetwin2Strokes < 10)
    {
      lcd.print (F(" "));
      lcd.print(distBetwin2Strokes,1);
    }
    else
   {
     lcd.print(distBetwin2Strokes,1); //print the value of distBetwin2Strokes variable
    }
    //-----------------------------------
    lcd.setCursor(0, 1);
    lcd.print(F("Niv de Force -> "));
    lcd.setCursor(16,1);
    lcd.print(F("   "));
    lcd.setCursor(16,1);
    if (resistanceLevel < 10)
    {
      lcd.print (F(" "));
      lcd.print(resistanceLevel,1);
    }
    else
    {
      lcd.print(resistanceLevel,1);
    }
    //---------------------
    lcd.setCursor(0, 2);
    lcd.print(F("CdR/Seance   -> "));
    lcd.setCursor(16,2);
    lcd.print(F("   "));
    lcd.setCursor(16,2);
    if (strokeCountSessionTarget < 10)
    {
      lcd.print (F("   "));
      lcd.print(strokeCountSessionTarget);
    }
    else if (strokeCountSessionTarget < 100)
    {
      lcd.print (F("  "));
      lcd.print(strokeCountSessionTarget);
    }
    else if (strokeCountSessionTarget < 1000)
    {
      lcd.print (F(" "));
      lcd.print(strokeCountSessionTarget);
    }
    else
    {
      lcd.print(strokeCountSessionTarget); //
    }
    //---------------------
    lcd.setCursor(0, 3);
    lcd.print(F("Obj CdR Tot  -> "));
    lcd.setCursor(16,3);
    lcd.print(F("   "));
    lcd.setCursor(16,3);
    if (strokeCountTotalTarget < 10)
    {
      lcd.print (F("   "));
      lcd.print(strokeCountTotalTarget);
    }
    else if (strokeCountTotalTarget < 100)
    {
      lcd.print (F("  "));
      lcd.print(strokeCountSessionTarget);
    }
    else if (strokeCountTotalTarget < 1000)
    {
      lcd.print (F(" "));
      lcd.print(strokeCountTotalTarget);
    }
    else
    {
      lcd.print(strokeCountTotalTarget); //
    }
    }
    if (currentTime - startShownTime > blinkStartMessageDelay * 2)
    {
      lcd.clear();
      startShownTime = currentTime;
    }
}


//***********************************************************************************************************************************************
//                                                                   COMPUTE TIME                                                               *
//***********************************************************************************************************************************************


//---------------------------------------------- Compute hours, minutes and seconds for given duration expressed in milliseconds
void computeHMS(unsigned long duration)
{

  float floatHours;
  float floatMinutes;
  float floatSeconds;

  intHours = 0;
  intMinutes = 0;
  intSeconds = 0;

  if (duration >= 1000)
  {
    floatSeconds = duration / MILLISECONDSINSECOND % 60;
    intSeconds = floatSeconds;

    floatMinutes = duration / MILLISECONDSINMINUTE % 60;
    intMinutes = floatMinutes;

    floatHours = duration / MILLISECONDSINHOUR % 24;
    intHours = floatHours;
  }
}


//***********************************************************************************************************************************************
//                                                                   START/PAUSE BUTTON                                                         *
//***********************************************************************************************************************************************

//---------------------------------------------- When Pause Button is pressed
void PressPauseButton()
{
  //---------------------------------------------- Read PAUSE/RESUME push button
  currentPauseButton = debounce(lastPauseButton, PAUSEBUTTON);
  if (lastPauseButton == LOW && currentPauseButton == HIGH)
  {

    //---------------------------------------------- If "PRESS BUTTON TO START" message has been showing then we now need to start 1st session/period
    if (startShown)
    {

      startShown = LOW;

      //---------------------------------------------- Show "C'EST PARTI!" message
      ShowLetsGo();
      letsGoShown = HIGH;
      letsGoStartTime = currentTime;

      currentSession = 1;
      resetSessionVariables();
      currentDisplayMode = 1;

    }
    else
    {
      //---------------------------------------------- Otherwise if pause is active then we need to take it out of pause and start new session/period
      if (paused)
      {

        paused = LOW;

        //---------------------------------------------- Show "C'EST PARTI!" message
        ShowLetsGo();
        letsGoShown = HIGH;
        letsGoStartTime = currentTime;

        //---------------------------------------------- Increment session counter
        currentSession++;

        //---------------------------------------------- If we are starting a 100th session/period then we should write data into 99th array position (overwriting this session)
        //---------------------------------------------- as we can only keep track of 99 sessions/periods in total
        if (currentSession > 49)
        {
          currentSession = 49;
          //---------------------------------------------- Pretend session 50 (out-of-bounds value) is currently shown (even though 99 is currently shown)
          //---------------------------------------------- to force display of new data for session 49
          sessionCurrentlyShown = 50;
        }

        resetSessionVariables();
        currentDisplayMode = 1;
      }

      //---------------------------------------------- Otherwise pause is not currently active so we need to save session/period data and activate pause
      else
      {
        paused = HIGH;

        //---------------------------------------------- Calculate duration
        currentDuration = currentTime - sessionStartTime;

        //---------------------------------------------- If session duration is less than 2 seconds (which means user pressed the pause button while "LET'S GO" message
        //---------------------------------------------- was shown) then do not store the session/ignore it
        if (currentDuration < 3000) 
        {
          currentSession--;
        }
        //---------------------------------------------- Otherwise store the session
        else
        {

          //---------------------------------------------- Compute distance and average kilometers per hour if stroke moved
          if (currentStrokeCount > 0)
          {
            currentDistance = currentStrokeCount * distBetwin2Strokes / 1000;
            currentAverageKPH = currentDistance * 3600000 / currentDuration;
            averageDistancePerMinute = (currentDistance * 60000 * 1000) / currentDuration;
          }

          //---------------------------------------------- Store data for session/period into array
          arrayDistance[currentSession] = currentDistance;
          arrayDuration[currentSession] = currentDuration;
          arrayAverageKPH[currentSession] = currentAverageKPH;
          arrayMaximumKPH[currentSession] = currentMaximumKPH;
          arrayStrokeCount[currentSession] = currentStrokeCount;
          arrayMaximumCadence[currentSession] = currentMaximumCadence;
          arrayAverageCadence[currentSession] = currentAverageCadence;
          arrayDistancePerMinute[currentSession] = averageDistancePerMinute;

          // Update totals for all sessions/periods
          arrayDistance[0] = arrayDistance[0] + currentDistance;
          arrayDuration[0] = arrayDuration[0] + currentDuration;
          arrayAverageKPH[0] = arrayDistance[0] * 3600000 / arrayDuration[0];
          if (currentMaximumKPH > arrayMaximumKPH[0])
          {
            arrayMaximumKPH[0] = currentMaximumKPH;
          }
          arrayStrokeCount[0] = arrayStrokeCount[0] + currentStrokeCount;
          if (currentMaximumCadence > arrayMaximumCadence[0])
          {
            arrayMaximumCadence[0] = currentMaximumCadence;
          }
          arrayAverageCadence[0] = arrayAverageCadence[0] + currentAverageCadence;
          arrayDistancePerMinute[0] = (arrayDistance[0] * 60000 * 1000) / arrayDuration[0];
        }

        //---------------------------------------------- In case "LET'S GO!" has been showing, turn it off now since we want to show "PAUSED!" message
        //---------------------------------------------- and we don't want it to be removed when "LET'S GO!" times out
        letsGoShown = LOW;

        //---------------------------------------------- Show "PAUSED!" message
        showPaused();
        pausedShown = HIGH;
        pausedStartTime = currentTime;

        //---------------------------------------------- We will need to show data for session which was just finished
        showSession = currentSession;
        currentDisplayMode = 3;

        //---------------------------------------------- Set out-of-bounds value to sessionCurrentlyShown to force session data to be shown
        sessionCurrentlyShown = 50;
      }
    }
  }
  lastPauseButton = currentPauseButton;
}


//***********************************************************************************************************************************************
//                                                                   ROTARY ENCODER LOOP                                                        *
//***********************************************************************************************************************************************

void rotary_loop()
{
    if(distBetwin2Strokes_selected == true)
    { 
      if (rotaryEncoder.encoderChanged())
      {
        distBetwin2Strokes = (float)rotaryEncoder.readEncoder() / 10; // / 10.0
        updateLCD();      
      }
    }

    else if(resistanceLevel_selected == true)
    {
      if (rotaryEncoder.encoderChanged())
      {
        resistanceLevel = (float)rotaryEncoder.readEncoder() / 10; // / 10.0
        updateLCD();  
      }
    }

    else if(strokeCountSessionTarget_selected == true)
    {
      if (rotaryEncoder.encoderChanged())
      {
        strokeCountSessionTarget = rotaryEncoder.readEncoder();
        updateLCD();    
      }
    }

    else if(strokeCountTotalTarget_selected == true)
    {
      if (rotaryEncoder.encoderChanged())
      {
        strokeCountTotalTarget = rotaryEncoder.readEncoder();
        updateLCD();     
      }
    }

    else 
    {
      if (rotaryEncoder.encoderChanged())
      {
        menuCounter = rotaryEncoder.readEncoder();
        //Serial.println(menuCounter);
        refreshLCD = true; //reset the variable - wait for a new trigger
      }
    }
}


//***********************************************************************************************************************************************
//                                                                   ROTARY ENCODER BUTTON                                                      *
//***********************************************************************************************************************************************

void rotary_onButtonClick()
{
    static unsigned long lastTimePressed = 0;
    if (millis() - lastTimePressed < 200)
        return;
    lastTimePressed = millis();
    

  switch(menuCounter)
  {
     case 0:
     distBetwin2Strokes_selected = !distBetwin2Strokes_selected; //we change the status of the variable to the opposite
     break;

     case 1:
     resistanceLevel_selected = !resistanceLevel_selected;
     break;

     case 2:
     strokeCountSessionTarget_selected = !strokeCountSessionTarget_selected;
     break;

     case 3:
     strokeCountTotalTarget_selected = !strokeCountTotalTarget_selected;
     break;
  }
  refreshLCD = true; //Refresh LCD after changing the value of the menu
  refreshSelection = true; //refresh the selection ("X")
}


//***********************************************************************************************************************************************
//                                                                   MODE BUTTON                                                                *
//***********************************************************************************************************************************************

//---------------------------------------------- When Mode Button is pressed
void PressModeButton()
{
  //---------------------------------------------- Read DISPLAY MODE push button
  currentDisplayModeButton = debounce(lastDisplayModeButton, DISPLAYMODEBUTTON);
  if (lastDisplayModeButton == LOW && currentDisplayModeButton == HIGH)
  {

    //---------------------------------------------- Otherwise if "LET'S GO!" message is not shown nor is "PAUSED!" message shown...
    if (!startShown && !letsGoShown && !pausedShown)
    {

      //---------------------------------------------- If not currently paused (so session is ongoing)...
      if (!paused)
      {

        //---------------------------------------------- Flip between the two different display modes available
        if (currentDisplayMode == 1)
        {
          currentDisplayMode = 2;
          updateLCD();
        }
        else
        {
          currentDisplayMode = 1;
          eepromSave();
        }

        //---------------------------------------------- Clear display and show appropriate labels
        showLabels(currentDisplayMode);
      }

      //---------------------------------------------- Otherwise we are in paused mode so cycle through session data available, including totals page
      else
      {
        currentDisplayMode = 3;
        showSession++;
        if (showSession > currentSession)
        {
          showSession = 0; // Show totals
        }
      }
    }
  }
  lastDisplayModeButton = currentDisplayModeButton;

  if (currentDisplayMode == 1)
  {
    distBetwin2Strokes_selected = false;
    resistanceLevel_selected = false;
    strokeCountSessionTarget_selected = false;
    strokeCountTotalTarget_selected = false;
  }
  
}


//***********************************************************************************************************************************************
//                                                                   UPDATE LCD                                                                 *
//***********************************************************************************************************************************************

void updateLCD()
{
  //MENU 0
  lcd.setCursor(16,0); //1st line, 10th block
  lcd.print(F("   ")); //erase the content by printing space over it
  lcd.setCursor(16,0); //1st line, 10th block
  if (distBetwin2Strokes < 10)
  {
    lcd.print (F(" "));
    lcd.print(distBetwin2Strokes,1);
  }
  else
  {
    lcd.print(distBetwin2Strokes,1); //print the value of distBetwin2Strokes variable
  }

  //MENU 1
  lcd.setCursor(16,1);
  lcd.print(F("   "));
  lcd.setCursor(16,1);
  if (resistanceLevel < 10)
  {
    lcd.print (F(" "));
    lcd.print(resistanceLevel,1);
  }
  else
  {
    lcd.print(resistanceLevel,1);
  }

  //MENU 2
  lcd.setCursor(16,2);
  lcd.print(F("   "));
  lcd.setCursor(16,2);
  if (strokeCountSessionTarget < 10)
  {
    lcd.print (F("   "));
    lcd.print(strokeCountSessionTarget);
  }
  else if (strokeCountSessionTarget < 100)
  {
    lcd.print (F("  "));
    lcd.print(strokeCountSessionTarget);
  }
  else if (strokeCountSessionTarget < 1000)
  {
    lcd.print (F(" "));
    lcd.print(strokeCountSessionTarget);
  }
  else
   {
  lcd.print(strokeCountSessionTarget); //
   }

  //MENU 3
  lcd.setCursor(16,3);
  lcd.print(F("   "));
  lcd.setCursor(16,3);
  if (strokeCountTotalTarget < 10)
  {
    lcd.print (F("   "));
    lcd.print(strokeCountTotalTarget);
  }
  else if (strokeCountTotalTarget < 100)
  {
    lcd.print (F("  "));
    lcd.print(strokeCountSessionTarget);
  }
  else if (strokeCountTotalTarget < 1000)
  {
    lcd.print (F(" "));
    lcd.print(strokeCountTotalTarget);
  }
  else
   {
  lcd.print(strokeCountTotalTarget); //
   }

  refreshLCD = true;
}


//***********************************************************************************************************************************************
//                                                                   PRINT & MENU NAVIGATION                                                    *
//***********************************************************************************************************************************************

void updateCursorPosition()
{
  //Clear display's ">" parts 
  lcd.setCursor(0,0); //1st line, 1st block
  lcd.print(F(" ")); //erase by printing a space
  lcd.setCursor(0,1);
  lcd.print(F(" ")); 
  lcd.setCursor(0,2);
  lcd.print(F(" ")); 
  lcd.setCursor(0,3);
  lcd.print(F(" ")); 


     
  //Place cursor to the new position
  switch(menuCounter) //this checks the value of the counter (0, 1, 2 or 3)
  {
    case 0:
    lcd.setCursor(0,0); //1st line, 1st block
    lcd.print(F(">")); 
    break;
    //-------------------------------
    case 1:
    lcd.setCursor(0,1); //2nd line, 1st block
    lcd.print(F(">")); 
    break;
    //-------------------------------    
    case 2:
    lcd.setCursor(0,2); //3rd line, 1st block
    lcd.print(F(">")); 
    break;
    //-------------------------------    
    case 3:
    lcd.setCursor(0,3); //4th line, 1st block
    lcd.print(F(">")); 
    break;
  }
}

void updateSelection()
{
  //When a menu is selected ">" becomes "X"

  if(distBetwin2Strokes_selected == true)
  {
    lcd.setCursor(0,0); //1st line, 1st block
    lcd.print(F("X"));
    rotaryEncoder.setBoundaries(0, 200, false); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
    rotaryEncoder.setAcceleration(100);
    if (distBetwin2Strokes != rotaryEncoder.readEncoder())
    {
      rotaryEncoder.reset (distBetwin2Strokes*10);
    }
   
  }
  //-------------------
   if(resistanceLevel_selected == true)
  {
    lcd.setCursor(0,1); //2nd line, 1st block
    lcd.print(F("X"));
    rotaryEncoder.setBoundaries(0, 70, false); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
    rotaryEncoder.setAcceleration(50);
    if (resistanceLevel != rotaryEncoder.readEncoder())
    {
      rotaryEncoder.reset (resistanceLevel*10);
    } 
  }
  //-------------------
  if(strokeCountSessionTarget_selected == true)
  {
    lcd.setCursor(0,2); //3rd line, 1st block
    lcd.print(F("X")); 
    rotaryEncoder.setBoundaries(0, 9999, false); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
    rotaryEncoder.setAcceleration(1000);
    if (strokeCountSessionTarget != rotaryEncoder.readEncoder())
    {
      rotaryEncoder.reset (strokeCountSessionTarget);
    }
  }
  //-------------------
  if(strokeCountTotalTarget_selected == true)
  {
    lcd.setCursor(0,3); //4th line, 1st block
    lcd.print(F("X"));
    rotaryEncoder.setBoundaries(0, 9999, false); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
    rotaryEncoder.setAcceleration(1000);
    if (strokeCountTotalTarget != rotaryEncoder.readEncoder())
    {
      rotaryEncoder.reset (strokeCountTotalTarget);
    }
  }
}


//***********************************************************************************************************************************************
//                                                                   MESSAGES DURATION                                                          *
//***********************************************************************************************************************************************

//---------------------------------------------- Set Duration Messages
void MessageTiming()
{
  //---------------------------------------------- If stroke indicator has been showing, take if off if it has been 250 millis or more
  if (strokeActionShown && !startShown && !paused && (currentTime >= (strokeActionShownStartTime + 200)))
  {
    strokeActionShown = LOW;
    lcd.setCursor(4, 0);
    lcd.print(F(" "));
  }

  //---------------------------------------------- If stroke indicator has been showing, take current KPH to 0 if it has been 3000 millis or more
  if (!startShown && !paused && (currentTime >= (lastStrokeStartTime + 3000)) && currentKPH > 0)
  {
    currentKPH = 0;
  }

  //---------------------------------------------- If "LET'S GO!" has been showing, take it off if it has been 2 seconds or more
  if (letsGoShown && (currentTime >= (letsGoStartTime + 2000)))
  {
    letsGoShown = LOW;
    showLabels(currentDisplayMode);
  }

  //---------------------------------------------- If "Paused!" has been showing, take it off if it has been 2 seconds or more
  if (pausedShown && (currentTime >= (pausedStartTime + 2000)))
  {
    pausedShown = LOW;
    showLabels(currentDisplayMode);
  }
}


//***********************************************************************************************************************************************
//                                                                   RESET COUNTER VARIABLES                                                    *
//***********************************************************************************************************************************************

//---------------------------------------------- Reset all variables used for calculating current/ongoing session 
void resetSessionVariables()
{
  currentStrokeCount = 0;

  sessionStartTime = currentTime;

  currentDistance = 0;
  currentDuration = 0;
  currentMaximumKPH = 0;
  currentAverageKPH = 0;
  currentStrokeCount = 0;
  currentMaximumCadence = 0;
  currentAverageCadence = 0;
}


//***********************************************************************************************************************************************
//                                                                   PRINT                                                                      *
//***********************************************************************************************************************************************


//---------------------------------------------- Show "LET'S GO!" Message 
void ShowLetsGo()
{
  lcd.clear();
  lcd.setCursor(7, 1);
  lcd.print(F("C'EST"));
  lcd.setCursor(7, 2);
  lcd.print(F("PARTI!"));
}

//---------------------------------------------- Show "PAUSED!" Message
void showPaused()
{
  lcd.clear();
  lcd.setCursor(7, 1);
  lcd.print(F("PAUSE!"));
}

//---------------------------------------------- Show appropriate labels for current mode
void showLabels(int currentDisplayMode)
{

  lcd.clear();
  switch (currentDisplayMode)
  {
    case 1:
      lcd.setCursor(0, 0);
      lcd.print(F("CdR:"));
      lcd.setCursor(0, 1);
      lcd.print(F("CPM"));
      lcd.setCursor(8, 1);
      lcd.print(F("Mx"));
      lcd.setCursor(15, 1);
      lcd.print(F("Mo"));
      lcd.setCursor(0, 2);
      lcd.print(F("Vit"));
      lcd.setCursor(8, 2);
      lcd.print(F("Mx"));
      lcd.setCursor(15, 2);
      lcd.print(F("Mo"));
      lcd.setCursor(11, 3);
      lcd.print(F("D:"));
      lcd.setCursor(0, 3);
      lcd.print(F("CDM"));
      break;
    case 2:
      lcd.setCursor(1, 0);
      lcd.print(F("Dist/CdR (m):"));
      lcd.setCursor(1, 1);
      lcd.print(F("Niv de Force:"));
      lcd.setCursor(1, 2);
      lcd.print(F("CdR/Seance:"));
      lcd.setCursor(1, 3);
      lcd.print(F("Obj CdR Tot:"));
      break;
  }
}


/*
   Serial.print("  currentStrokeCount:");
   Serial.print(currentStrokeCount);
   Serial.print("  currentCadence:");
   Serial.print(currentCadence);
   Serial.print("  Cadence0:");
   Serial.print(Cadence0);
   Serial.print("  Dcad:");
   Serial.print(Dcad);
   Serial.print("  tprint:");
   Serial.print(tprint);
   Serial.print("  currentAverageCadence:");
   Serial.println(currentAverageCadence);
   Serial.print("  lastStrokeStartTime:");
   Serial.print(lastStrokeStartTime);
   Serial.print("  sessionStartTime:");
   Serial.println(sessionStartTime);
   Serial.print("  lastStrokeCount:");
   Serial.print(lastStrokeCount);
   Serial.print("  lastMinuteStrokeCount:");
   Serial.print(lastMinuteStrokeCount);
   Serial.print("  strokingTime:");
   Serial.print(strokingTime);
   Serial.print("  lastMinuteStrokeStartTime:");
   Serial.println(lastMinuteStrokeStartTime);
*/

void UpdateDataOnDisplay()
{
  //---------------------------------------------- Current Distance
  lcd.setCursor(13, 3);
  if (currentDistance < 10)
  {
    lcd.print(F("0"));
    lcd.print(currentDistance);
  }
  else
  {
    lcd.print(currentDistance);
  }
  lcd.print(F("km"));

  //---------------------------------------------- Current Duration
  computeHMS(currentDuration);
  lcd.setCursor(12, 0);
  if (intHours < 10)
  {
    lcd.print(F("0"));
  }
  lcd.print(intHours);

  lcd.print(F(":"));
  if (intMinutes < 10)
  {
    lcd.print(F("0"));
  }
  lcd.print(intMinutes);

  lcd.print(F(":"));
  if (intSeconds < 10)
  {
    lcd.print(F("0"));
  }
  lcd.print(intSeconds);

  //---------------------------------------------- Current Speed
  lcd.setCursor(3, 2);
  if (currentKPH < 10)
  {
    lcd.print(F("00"));
    lcd.print(currentKPH);
  }
  else if (currentKPH < 100)
  {
    lcd.print(F("0"));
    lcd.print(currentKPH);
  }
  else
  {
    lcd.print(currentKPH);
  }

  //---------------------------------------------- Current Maximum Speed
  lcd.setCursor(10, 2);
  if (currentMaximumKPH < 10)
  {
    lcd.print(F("00"));
    lcd.print(currentMaximumKPH);
  }
  else if (currentMaximumKPH < 100)
  {
    lcd.print(F("0"));
    lcd.print(currentMaximumKPH);
  }
  else
  {
    lcd.print(currentMaximumKPH);
  }

  //---------------------------------------------- Current Average Speed
  lcd.setCursor(17, 2);
  if (currentAverageKPH < 10)
  {
    lcd.print(F("00"));
    lcd.print(currentAverageKPH);
  }
  else if (currentAverageKPH < 100)
  {
    lcd.print(F("0"));
    lcd.print(currentAverageKPH);
  }
  else
  {
    lcd.print(currentAverageKPH);
  }

  //---------------------------------------------- Print Cadence

  //---------------------------------------------- Cadence Print Refresh
  if (currentTime - tprint > DELAYTIMEPRINTCADENCE)
  {

    //---------------------------------------------- Current Stroke Number
    lcd.setCursor(5, 0);
    if (currentStrokeCount < 10)
    {
      lcd.print(F("000"));
      lcd.print(currentStrokeCount);
    }
    else if (currentStrokeCount < 100)
    {
      lcd.print(F("00"));
      lcd.print(currentStrokeCount);
    }
    else if (currentStrokeCount < 1000)
    {
      lcd.print(F("0"));
      lcd.print(currentStrokeCount);
    }
    else
    {
      lcd.print(currentStrokeCount);
    }

    //---------------------------------------------- Current Cadence
    lcd.setCursor(3, 1);
    if (currentCadence < 10)
    {
      lcd.print(F("00"));
      lcd.print(currentCadence);
    }
    else if (currentCadence < 100)
    {
      lcd.print(F("0"));
      lcd.print(currentCadence);
    }
    else
    {
      lcd.print(currentCadence);
    }

    //---------------------------------------------- Current Maximum Cadence
    lcd.setCursor(10, 1);
    if (currentMaximumCadence < 10)
    {
      lcd.print(F("00"));
      lcd.print(currentMaximumCadence);
    }
    else if (currentMaximumCadence < 100)
    {
      lcd.print(F("0"));
      lcd.print(currentMaximumCadence);
    }
    else
    {
      lcd.print(currentMaximumCadence);
    }

    //---------------------------------------------- Current Average Cadence
    lcd.setCursor(17, 1);
    if (currentAverageCadence < 10)
    {
      lcd.print(F("00"));
      lcd.print(currentAverageCadence);
    }
    else if (currentAverageCadence < 100)
    {
      lcd.print(F("0"));
      lcd.print(currentAverageCadence);
    }
    else
    {
      lcd.print(currentAverageCadence);
    }

    //---------------------------------------------- Last Minute Strokes

    lcd.setCursor(3, 3);
    if (lastMinuteStrokeCounter < 10)
    {
      lcd.print(F("0"));
      lcd.print(lastMinuteStrokeCounter);
    }
    else
    {
      lcd.print(lastMinuteStrokeCounter);
    }

    lcd.setCursor(5, 3);
    lcd.print(F("("));
    lcd.setCursor(6, 3);
    if (lastMinuteStrokeCount < 10)
    {
      lcd.print(F("0"));
      lcd.print(lastMinuteStrokeCount);
    }
    else
    {
      lcd.print(lastMinuteStrokeCount);
    }
    lcd.setCursor(8, 3);
    lcd.print(F(")"));

    tprint = currentTime;
  }
}

void RotaryHistoricalSessionsInformations()
{
    int16_t encoderDelta = rotaryEncoder.encoderChanged();

    //optionally we can ignore whenever there is no change
    if (encoderDelta == 0)
        return;

    //for some cases we only want to know if value is increased or decreased (typically for menu items)
    if (encoderDelta > 0)
        showSession++;
        if (showSession > currentSession)
        {
          showSession = 0; // Show totals
        }
    if (encoderDelta < 0)
        showSession--;
        if (showSession < 0)
        {
          showSession = currentSession;
        }
        if (showSession > currentSession)
        {
          showSession = 0; // Show totals
        }
}




void ShowHistoricalSessionsInformations()
{
  //---------------------------------------------- Update display only if we need to show data for different session to that currently shown
  //---------------------------------------------- this way display is not constantly cleared and refreshed with same data which would
  //---------------------------------------------- cause display to flicker and is not needed anyway as data is not changing
  if (sessionCurrentlyShown != showSession) 
  {
    sessionCurrentlyShown = showSession;

    lcd.clear();

    lcd.setCursor(0, 0);
    if (showSession == 0)
    {
      lcd.print(F("Tot"));
    }
    else
    {
      lcd.print(F("S"));
      if (showSession < 10 )
      {
        lcd.print(F("0"));
        lcd.print(showSession);
      }
      else
      {
        lcd.print(showSession);
      }
    }

    //---------------------------------------------- Print Labels
    lcd.setCursor(0, 1);
    lcd.print(F("Cps/min"));
    lcd.setCursor(8, 1);
    lcd.print(F("Mx"));
    lcd.setCursor(15, 1);
    lcd.print(F("Mo"));
    lcd.setCursor(0, 2);
    lcd.print(F("Vitesse"));
    lcd.setCursor(8, 2);
    lcd.print(F("Mx"));
    lcd.setCursor(15, 2);
    lcd.print(F("Mo"));
    lcd.setCursor(11, 3);
    lcd.print(F("D:"));
    lcd.setCursor(0, 3);
    lcd.print(F("DMPM"));

    //---------------------------------------------- Sessions Average Speed
    lcd.setCursor(17, 2);
    if (arrayAverageKPH[showSession] < 10)
    {
      lcd.print(F("00"));
      lcd.print(arrayAverageKPH[showSession]);
    }
    else if (arrayAverageKPH[showSession] < 100)
    {
      lcd.print(F("0"));
      lcd.print(arrayAverageKPH[showSession]);
    }
    else
    {
      lcd.print(arrayAverageKPH[showSession]);
    }

    //---------------------------------------------- Sessions Maximum Speed
    lcd.setCursor(10, 2);
    if (arrayMaximumKPH[showSession] < 10)
    {
      lcd.print(F("00"));
      lcd.print(arrayMaximumKPH[showSession]);
    }
    else if (arrayMaximumKPH[showSession] < 100)
    {
      lcd.print(F("0"));
      lcd.print(arrayMaximumKPH[showSession]);
    }
    else
    {
      lcd.print(arrayMaximumKPH[showSession]);
    }

    //---------------------------------------------- Sessions Distance
    lcd.setCursor(13, 3);
    if (arrayDistance[showSession] < 10)
    {
      lcd.print(F("0"));
      lcd.print(currentDistance);
    }
    else
    {
      lcd.print(arrayDistance[showSession]);
    }
    lcd.print(F("km"));

    //---------------------------------------------- Sessions Duration
    computeHMS(arrayDuration[showSession]);
    lcd.setCursor(12, 0);
    if (intHours < 10)
    {
      lcd.print(F("0"));
    }
    lcd.print(intHours);
    lcd.print(F(":"));
    if (intMinutes < 10)
    {
      lcd.print(F("0"));
    }
    lcd.print(intMinutes);
    lcd.print(F(":"));
    if (intSeconds < 10) 
    {
      lcd.print(F("0"));
    }
    lcd.print(intSeconds);

    //---------------------------------------------- Sessions Stroke Number
    lcd.setCursor(5, 0);
    if (arrayStrokeCount[showSession] < 10)
    {
      lcd.print(F("000"));
      lcd.print(arrayStrokeCount[showSession]);
    }
    else if (arrayStrokeCount[showSession] < 100)
    {
      lcd.print(F("00"));
      lcd.print(arrayStrokeCount[showSession]);
    }
    else if (arrayStrokeCount[showSession] < 1000)
    {
      lcd.print(F("0"));
      lcd.print(arrayStrokeCount[showSession]);
    }
    else
    {
      lcd.print(arrayStrokeCount[showSession]);
    }

    //---------------------------------------------- Sessions Maximum Cadence
    lcd.setCursor(10, 1);
    if (arrayMaximumCadence[showSession] < 10)
    {
      lcd.print(F("00"));
      lcd.print(arrayMaximumCadence[showSession]);
    }
    else if (arrayMaximumCadence[showSession] < 100)
    {
      lcd.print(F("0"));
      lcd.print(arrayMaximumCadence[showSession]);
    }
    else
    {
      lcd.print(arrayMaximumCadence[showSession]);
    }

    //---------------------------------------------- Sessions Average Cadence
    lcd.setCursor(17, 1);
    if (arrayAverageCadence[showSession] < 10)
    {
      lcd.print(F("00"));
      lcd.print(arrayAverageCadence[showSession]);
    }
    else if (arrayAverageCadence[showSession] < 100)
    {
      lcd.print(F("0"));
      lcd.print(arrayAverageCadence[showSession]);
    }
    else
    {
      lcd.print(arrayAverageCadence[showSession]);
    }


    //---------------------------------------------- Sessions Average Distance Per Minute
    lcd.setCursor(4, 3);
    if (arrayDistancePerMinute[showSession] < 10)
    {
      lcd.print(F("00"));
      lcd.print(arrayDistancePerMinute[showSession]);
    }
    else if (arrayDistancePerMinute[showSession] < 100)
    {
      lcd.print(F("0"));
      lcd.print(arrayDistancePerMinute[showSession]);
    }
    else
    {
      lcd.print(arrayDistancePerMinute[showSession]);
      lcd.print(F("m"));
    }
  }
}

void eepromSave()
{
  EEPROM.writeFloat(0,distBetwin2Strokes);
  EEPROM.writeFloat(8,resistanceLevel);
  EEPROM.writeInt(16,strokeCountSessionTarget);
  EEPROM.writeInt(24,strokeCountTotalTarget);
  EEPROM.commit();
}




//---------------------------------------------- A debouncing function that can be used for any button
boolean debounce(boolean last, int pin)
{
  boolean current = digitalRead(pin);
  if (last != current)
  {
    delay(10);
    current = digitalRead(pin);
  }
  return current;
}

Credits

martx
0 projects • 0 followers
Contact

Comments

Please log in or sign up to comment.