/*
  Copyright (C) 2024, Pablo César Galdo Regueiro.
  info.wokwi(at)pcgaldo.com

  Project in editing process. Operation may be limited.

  License

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <https://www.gnu.org/licenses/>
*/

#include <LiquidCrystal_I2C.h>

// LCD configuration
#define I2C_ADDR    0x27
#define LCD_COLUMNS 16
#define LCD_LINES   2

LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);

// Encoder input pins
#define ENCODER_CLK 2
#define ENCODER_DT  3

// Global variables
volatile long counter = 0; // Counter for encoder pulses
unsigned long lastTime = 0; // To store the last time when RPM was calculated
unsigned long currentTime = 0; // To store the current time
float rpm = 0; // Variable to store the RPM value
float speed_m_min = 0; // Variable to store the speed in meters per minute

// Constants for encoder and roller
const int pulsesPerRevolution = 500; // Pulses per revolution of the encoder
const float rollerDiameter = 100.0;  // Diameter of the roller in mm

void setup() {
  // Set up encoder pins as input with internal pull-up resistors
  pinMode(ENCODER_CLK, INPUT_PULLUP);
  pinMode(ENCODER_DT, INPUT_PULLUP);

  // Initialize the LCD
  lcd.init();
  lcd.backlight();

  // Set up interrupts for encoder signal changes
  attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), ai0, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ENCODER_DT), ai1, CHANGE);

  // Initialize the time reference
  lastTime = millis();
}

void loop() {
  // Calculate RPM and speed every second
  currentTime = millis();
  if (currentTime - lastTime >= 1000) {
    // Calculate RPM: (counter * 60) / (pulses per revolution)
    rpm = (counter * 60.0) / pulsesPerRevolution;
    
    // Calculate speed in m/min: (RPM * π * diameter) / 1000
    speed_m_min = (rpm * 3.14159 * rollerDiameter) / 1000.0;
    
    // Reset counter for the next measurement
    counter = 0;
    
    // Display RPM and speed on the LCD
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("SPEED");
    lcd.setCursor(0, 1);
    lcd.print(speed_m_min);
    lcd.print(" m/min");
    
    // Update the last time reference
    lastTime = currentTime;
  }
}

// Interrupt service routine for encoder channel A
void ai0() {
  // If both signals are the same, the encoder is rotating forward
  if (digitalRead(ENCODER_CLK) == digitalRead(ENCODER_DT)) {
    counter++;
  }
}

// Interrupt service routine for encoder channel B
void ai1() {
  // If both signals are different, the encoder is rotating forward
  if (digitalRead(ENCODER_CLK) != digitalRead(ENCODER_DT)) {
    counter++;
  }
}