#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
// PID constants
float kp = 2.0; // Increased proportional gain
float ki = 0.1;
float kd = 0.5;
float integral = 0.0;
float previous_error = 0.0;
float dispensed_volume = 0.0;
volatile int flow_frequency; // Measures flow sensor pulses
float flow_rate; // Calculated flow rate in liters per minute (L/min)
float target_volume = 0.0; // Target volume to dispense in milliliters (mL)
float target_time = 0.0; // Target time to dispense in seconds
float target_flow_rate = 0.0; // Calculated target flow rate in liters per minute
unsigned char flowsensor = 2; // Sensor Input
unsigned long currentTime;
unsigned long cloopTime;
unsigned long startTime;
//Motor Controller Pins
int enablePin = 3;
int in1 = 4;
int in2 = 5;
//LED Pins
int powerLedPin = 6;
int flowLedPin = 7;
int idleLedPin = 8;
int modeALedPin = 9;
int modeBLedPin = 10;
// Buzzer pin
int buzzerPin = 45;
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 20, 4); // Initialize 20x4 LCD
enum InputMode { VOLUME, TIME, DISPENSE };
InputMode inputMode = VOLUME;
volatile long pulse;
unsigned long lastTime;
float volume;
float desiredFlowRate = 5.0; // Default desired flow rate
float desiredVolume = 0.0; // Variable to store the desired volume set by the user
bool backlightOn = true; // Variable to track the backlight status
// configure keypad
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}};
const byte rowPins[ROWS] = {22, 24, 26, 28};
const byte colPins[COLS] = {30, 32, 34, 36};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// Rolling average variables
const int numReadings = 5; // Number of readings for rolling average
float readings[numReadings]; // Array to hold the readings
int readIndex = 0; // Current reading index
float total = 0; // Sum of readings
float averageFlowRate = 0; // Calculated average flow rate
bool motorRunning = false;
float tolerance = 0; // Tolerance range for maintaining pump speed (L/min)
int maxSpeed = 255; // Maximum PWM value for pump speed
int minSpeed = 0; // Minimum PWM value for pump speed
int currentSpeed1 = 90; // Current PWM value for pump speed
int currentSpeed2 = 0;
int animationStep = -2000; // Initial animation step with larger waves
const unsigned long animationDuration = 5000; // Duration of animation in milliseconds
bool menuDisplayed = false; // Track if the menu is currently displayed or not
bool animationRunning = true; // Flag to control animation running
char selectedMode = ' '; // Default selected mode is none
bool flowDetectionRunning = false; // Flag to indicate if flow detection is running
void flow() // Interrupt function
{
flow_frequency++;
}
void animationSetup()
{
for (int i = 0; i < 8; i++)
{
byte charLine[8];
for (int j = 0; j < 8; j++)
{
if (j > i)
charLine[7 - j] = B00000;
else
charLine[7 - j] = B11111;
}
lcd.createChar(i, charLine);
}
}
void setup()
{
Serial.begin(9600);
lcd.init();
lcd.backlight();
animationSetup();
startTime = millis();
// Set motor control pins to outputs
pinMode(enablePin, OUTPUT);
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
// Set LED pins to outputs
pinMode(powerLedPin, OUTPUT);
pinMode(flowLedPin, OUTPUT);
pinMode(idleLedPin, OUTPUT);
pinMode(modeALedPin, OUTPUT);
pinMode(modeBLedPin, OUTPUT);
// Set in2 to 0
digitalWrite(in2, LOW);
// Set up flow sensor
pinMode(flowsensor, INPUT);
digitalWrite(flowsensor, HIGH); // Optional Internal Pull-Up
attachInterrupt(digitalPinToInterrupt(flowsensor), flow, RISING); // Setup Interrupt
sei(); // Enable interrupts
// Initialize the LCD
lcd.init();
lcd.backlight();
// Set up buzzer
pinMode(buzzerPin, OUTPUT);
// Set up keypad
keypad.addEventListener(keypadEvent);
// Initialize the rolling average array to zero
for (int i = 0; i < numReadings; i++) {
readings[i] = 0;
}
// Indicate power is on
digitalWrite(powerLedPin, HIGH);
digitalWrite(idleLedPin, HIGH); // Indicate system is idle
}
void displayMenu()
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("A: Continuous");
lcd.setCursor(0, 1);
lcd.print("B: Automatic");
lcd.setCursor(0, 2);
lcd.print("C: Return");
lcd.setCursor(0, 3);
lcd.print("D: Backlight On/Off");
menuDisplayed = true;
}
void stopAnimation()
{
animationRunning = false;
}
void animation3()
{
if (!animationRunning)
return; // If animation not running, exit
byte character[2];
unsigned long currentTime = millis();
unsigned long elapsedTime = currentTime - startTime;
if (elapsedTime >= animationDuration)
{
// Stop the animation after 5 seconds
animationRunning = false;
lcd.clear();
lcd.setCursor(6, 0);
lcd.print("Flow Rate");
lcd.setCursor(3, 1);
lcd.print("Control System");
lcd.setCursor(1, 2);
lcd.print("Press '*' to Start");
lcd.setCursor(3, 3);
lcd.print("and Select Mode");
return;
}
for (int column = 0; column < 20; column++)
{
// Adjusted coefficients for large to medium waves
int actualValue = cos(animationStep * (0.1 + column * 0.01)) * 8.5 + 9;
if (actualValue == 17)
{
character[0] = byte(255);
character[1] = byte(255);
}
else if (actualValue > 9)
{
character[0] = byte(actualValue - 10);
character[1] = byte(255);
}
else if (actualValue == 9)
{
character[0] = byte(32);
character[1] = byte(255);
}
else if (actualValue == 8)
{
character[0] = byte(32);
character[1] = byte(255);
}
else if (actualValue == 0)
{
character[0] = byte(32);
character[1] = byte(32);
}
else
{
character[0] = byte(32);
character[1] = byte(actualValue - 1);
}
lcd.setCursor(19 - column, 2); // Adjust cursor position for 20x4 LCD (starting from row 3)
lcd.write(character[0]);
lcd.setCursor(19 - column, 3); // Adjust cursor position for 20x4 LCD (starting from row 4)
lcd.write(character[1]);
}
animationStep++;
}
void loop() {
animation3();
char key = keypad.getKey(); // Get the key pressed
if (key != NO_KEY) {
tone(buzzerPin, 1300, 50); // Play tone for keypress
if (key == '*') { // If asterisk (*) key is pressed
if (!menuDisplayed) { // If menu is not already displayed
displayMenu(); // Display the menu
stopAnimation(); // Stop the animation
}
} else if (key == '#') { // If '#' key is pressed
stopMode(); // Stop the current mode and display mode selection
} else if (key == 'D') { // If 'D' key is pressed
// Toggle the backlight of the LCD
if (backlightOn) {
lcd.noBacklight(); // Turn off backlight
backlightOn = false; // Update backlight status
} else {
lcd.backlight(); // Turn on backlight
backlightOn = true; // Update backlight status
}
} else if (menuDisplayed) { // If menu is displayed
if (key >= 'A' && key <= 'C' && selectedMode == ' ') { // If a valid mode key is pressed and no mode is currently running
selectedMode = key;
switch (selectedMode) {
case 'A':
modeA(); // Enter Mode A
break;
case 'B':
modeB(); // Enter Mode B
break;
case 'C':
modeC(); // Enter Mode C
break;
}
} else if (selectedMode != ' ') { // If mode is selected
// Handle mode-specific button presses here
}
}
}
}
void stopMode() {
tone(buzzerPin, 1300, 50);
// Stop the current mode
if (flowDetectionRunning) {
stopFlowDetection(); // Stop flow detection if running
}
// Reset mode-related variables and return to mode selection screen
selectedMode = ' ';
menuDisplayed = false;
animationRunning = true;
target_flow_rate = 0.0;
}
void stopFlowDetection() {
// Stop the flow detection process
// You may need to implement this function based on your requirements
// Reset PWM to zero
analogWrite(enablePin, 0);
flowDetectionRunning = false; // Reset flow detection flag
}
bool firstHashPress = false;
void displayModeA() {
lcd.clear();
lcd.setCursor(7, 0);
lcd.print("Mode A");
lcd.setCursor(0, 1);
lcd.print("Flow Rate:");
lcd.setCursor(0, 2);
lcd.print("Actual: 0.00 L/min");
lcd.setCursor(0, 3);
lcd.print("Set Point: 0.00");
}
void updateSetPoint(float setPoint) {
lcd.setCursor(10, 3);
lcd.print(" "); // Clear the previous set point
lcd.setCursor(10, 3);
lcd.print(setPoint, 2); // Display the updated set point
}
void updateActualFlowRate(float flowRate) {
lcd.setCursor(8, 2);
lcd.print(flowRate, 2); // Print flow rate with 2 decimal places
lcd.print(" L/min "); // Clear the rest of the line
}
void modeA() {
digitalWrite(modeALedPin, HIGH); // Turn on mode A LED
displayModeA(); // Display Mode A menu
while (selectedMode == 'A') {
currentTime = millis();
char key = keypad.getKey(); // Check if a key is pressed
// Keypad handling code...
if (key != NO_KEY) {
// Keypad event handling
if (key == '#') {
if (motorRunning) {
// Stop the pump
motorRunning = false;
digitalWrite(enablePin, LOW); // Disable the motor driver
currentSpeed1 = 90; // Reset PWM code to 90
digitalWrite(flowLedPin, LOW); // Turn off flow LED
digitalWrite(idleLedPin, HIGH); // Turn on idle LED
firstHashPress = true;
} else if (firstHashPress) {
// Reset the set point input
target_flow_rate = 0.0;
updateSetPoint(target_flow_rate);
firstHashPress = false;
} else {
// Allow the user to input a new set point
target_flow_rate = 0.0;
updateSetPoint(target_flow_rate);
}
// Sound the buzzer
tone(buzzerPin, 1500); // Set the buzzer frequency to 1.5kHz
delay(300); // Adjust as needed for the desired duration of the beep
noTone(buzzerPin);
} else if (key == '*') {
if (!motorRunning) {
// Start operation and turn on the pump
motorRunning = true;
digitalWrite(enablePin, HIGH); // Enable the motor driver
digitalWrite(idleLedPin, LOW); // Turn off idle LED
digitalWrite(flowLedPin, HIGH); // Turn on flow LED
}
// Sound the buzzer
tone(buzzerPin, 1500); // Set the buzzer frequency to 1.5kHz
delay(1000); // Adjust as needed for the desired duration of the beep
noTone(buzzerPin);
} else if (key == 'D') {
// Return to the display menu
selectedMode = ' '; // Set selectedMode to a value other than 'A' to exit the while loop
digitalWrite(modeALedPin, LOW); // Turn off mode A LED
displayMenu(); // Display the menu again
return; // Exit the function
} else if (key >= '0' && key <= '9') {
// If a digit key is pressed, update the target flow rate
target_flow_rate = target_flow_rate * 10 + (key - '0');
updateSetPoint(target_flow_rate);
}
}
if (currentTime >= (cloopTime + 500)) { // Update every 500ms
cloopTime = currentTime; // Updates cloopTime
// Calculate flow rate in L/min
flow_rate = calculateFlowRate(flow_frequency);
// Adjust pump speed to reach the target flow rate if the pump is running
if (motorRunning) {
adjustPumpSpeed(flow_rate);
}
// Display flow rate on LCD
updateActualFlowRate(flow_rate);
// Reset flow frequency counter
flow_frequency = 0;
}
}
}
void keypadEvent(KeypadEvent eKey)
{
// Keypad event handling
}
float calculateFlowRate(int flowFrequency) {
// Update the rolling average
total -= readings[readIndex];
readings[readIndex] = (flowFrequency / 6.39) * (1000.0 / 500.0); // Adjusted based on experimental results and time reading
total += readings[readIndex];
readIndex = (readIndex + 1) % numReadings;
averageFlowRate = total / numReadings;
return averageFlowRate;
}
void adjustPumpSpeed(float averageFlowRate) {
float error = target_flow_rate - averageFlowRate;
integral += error * 0.5; // Integral term calculation with time step 0.5s
float derivative = (error - previous_error) / 0.5; // Derivative term calculation with time step 0.5s
// PID output calculation
float output = kp * error + ki * integral + kd * derivative;
// Adjust the current speed based on PID output
currentSpeed1 += output;
currentSpeed1 = constrain(currentSpeed1, minSpeed, maxSpeed);
// Apply PWM value to the motor driver
analogWrite(in1, currentSpeed1);
previous_error = error;
}
void modeB() {
digitalWrite(modeBLedPin, HIGH); // Turn on mode B LED
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("V: T:");
lcd.setCursor(0, 1);
lcd.print("Time Elapsed:");
lcd.setCursor(0, 2);
lcd.print("Dispensed:");
currentTime = millis();
while (selectedMode == 'B') {
unsigned long loopStartTime = millis(); // Record the start time of the loop
char key = keypad.getKey(); // Check if a key is pressed
// Keypad handling code...
if (key != NO_KEY) {
tone(buzzerPin, 1300, 50);
if (key == '#') {
target_volume = 0.0;
target_time = 0.0;
inputMode = VOLUME;
lcd.setCursor(3, 0); // Move cursor to the volume value position
lcd.print(" "); // Clear the volume value
lcd.setCursor(14, 0); // Move cursor to the time value position
lcd.print(" "); // Clear the time value
lcd.setCursor(14, 1);
lcd.print(" "); // Clear elapsed time display
lcd.setCursor(12, 2);
lcd.print(" "); // Clear dispensed volume display
lcd.setCursor(0, 3);
lcd.print(" "); // Clear dispense done message
if (motorRunning) {
stopPump();
}
// Sound the buzzer
tone(buzzerPin, 1300, 300);
} else if (key == '*') {
if (inputMode == VOLUME) {
inputMode = TIME;
lcd.setCursor(14, 0);
lcd.print(" "); // Clear the previous time
tone(buzzerPin, 1300, 100);
} else if (inputMode == TIME) {
inputMode = DISPENSE;
// Calculate the target flow rate
target_flow_rate = (target_volume / 1000.0) / (target_time / 60.0); // Convert mL to L and seconds to minutes
startPump();
tone(buzzerPin, 1300, 1000);
}
} else if (key == 'D') {
// Turn off mode B LED
digitalWrite(modeBLedPin, LOW);
// Return to the display menu
selectedMode = ' '; // Set selectedMode to a value other than 'B' to exit the while loop
displayMenu(); // Display the menu again
return; // Exit the function
} else if (key >= '0' && key <= '9') {
// If a key is pressed, update the target volume or time
if (inputMode == VOLUME) {
target_volume = target_volume * 10 + (key - '0');
lcd.setCursor(3, 0);
lcd.print(target_volume, 0); // Display the updated volume in mL
lcd.print(" mL"); // Clear any extra characters
} else if (inputMode == TIME) {
target_time = target_time * 10 + (key - '0');
lcd.setCursor(14, 0);
lcd.print(target_time, 0); // Display the updated time in seconds
lcd.print(" s"); // Clear any extra characters
}
}
// Do nothing if 'A', 'B', or 'C' are pressed
}
currentTime = millis();
if (currentTime >= (cloopTime + 200)) { // Update every 200ms
cloopTime = currentTime; // Updates cloopTime
// Calculate flow rate in L/min
flow_rate = calculateFlowRate(flow_frequency);
// Adjust pump speed to reach the target flow rate if the pump is running
if (motorRunning) {
adjustPumpSpeed(flow_rate);
// Check if the set volume has been dispensed
float elapsedTime = (millis() - startTime) / 1000.0; // Elapsed time in seconds
float dispensed_volume = (averageFlowRate / 60.0) * elapsedTime * 1000; // Calculate dispensed volume in mL
// Update the display with elapsed time and dispensed volume
lcd.setCursor(14, 1);
lcd.print(elapsedTime, 1); // Print elapsed time with 1 decimal place
lcd.print(" s"); // Clear any extra characters
lcd.setCursor(12, 2);
lcd.print(dispensed_volume, 1); // Print dispensed volume with 1 decimal place
lcd.print(" mL"); // Clear any extra characters
if (dispensed_volume >= target_volume || elapsedTime >= target_time) {
// Stop the pump
stopPump();
lcd.setCursor(0, 3);
lcd.print("Dispense done!");
}
}
// Reset flow frequency counter
flow_frequency = 0;
}
}
}
void modeC() {
bool debounceC = false; // Flag to handle debounce for key 'C'
while (selectedMode == 'C') {
currentTime = millis();
char key = keypad.getKey();
if (key != NO_KEY) {
tone(buzzerPin, 1300, 50);
if (key == '#') {
// Stop the current operation
motorRunning = false;
digitalWrite(enablePin, LOW);
currentSpeed1 = 90;
digitalWrite(flowLedPin, LOW);
digitalWrite(idleLedPin, HIGH);
} else if (key == 'C') {
if (!debounceC) {
debounceC = true; // Set debounce flag
// Return to the initial display
lcd.clear();
lcd.setCursor(6, 0);
lcd.print("Flow Rate");
lcd.setCursor(3, 1);
lcd.print("Control System");
lcd.setCursor(1, 2);
lcd.print("Press '*' to Start");
lcd.setCursor(3, 3);
lcd.print("and Select Mode");
selectedMode = ' ';
menuDisplayed = false;
animationRunning = false; // Ensure animation does not restart
}
} else {
debounceC = false; // Reset debounce flag for key 'C' if any other key is pressed
}
if (key == 'D') {
// Toggle backlight
if (backlightOn) {
lcd.noBacklight();
backlightOn = false;
} else {
lcd.backlight();
backlightOn = true;
}
}
}
}
}
float calculateFlowRate1(int flowFrequency) {
// Update the rolling average
total -= readings[readIndex];
readings[readIndex] = (flowFrequency / 6.39) * (1000.0 / 200.0); // Adjusted based on experimental results and time reading
total += readings[readIndex];
readIndex = (readIndex + 1) % numReadings;
averageFlowRate = total / numReadings;
return averageFlowRate;
}
void adjustPumpSpeed1(float averageFlowRate) {
float error = target_flow_rate - averageFlowRate;
integral += error * 0.2; // integral term accumulation, 0.2 is the loop interval in seconds
float derivative = (error - previous_error) / 0.2; // derivative term
currentSpeed2 += kp * error + ki * integral + kd * derivative;
previous_error = error;
// Constrain the speed to valid PWM range
currentSpeed2 = constrain(currentSpeed2, minSpeed, maxSpeed);
// Ensure in2 is always LOW
digitalWrite(in2, LOW);
// Apply PWM value to the motor driver
analogWrite(in1, currentSpeed2);
}
void startPump() {
motorRunning = true;
digitalWrite(enablePin, HIGH); // Enable the motor driver
digitalWrite(idleLedPin, LOW); // Turn off idle LED
digitalWrite(flowLedPin, HIGH); // Turn on flow LED
startTime = millis(); // Record the start time
}
void stopPump() {
motorRunning = false;
digitalWrite(enablePin, LOW); // Disable the motor driver
digitalWrite(flowLedPin, LOW); // Turn off flow LED
digitalWrite(idleLedPin, HIGH); // Turn on idle LED
integral = 0; // Reset integral term
}
void updateDisplay() {
float elapsedTime = (millis() - startTime) / 1000.0; // Elapsed time in seconds
float dispensed_volume = (averageFlowRate / 60.0) * elapsedTime * 1000; // Calculate dispensed volume in mL
// Update the display with elapsed time and dispensed volume
lcd.setCursor(14, 2);
lcd.print(elapsedTime, 1); // Print elapsed time with 1 decimal place
lcd.print(" s "); // Clear any extra characters
lcd.setCursor(6, 3);
lcd.print(dispensed_volume, 1); // Print dispensed volume with 1 decimal place
lcd.print(" mL "); // Clear any extra characters
}
void soundBuzzer(int duration) {
tone(buzzerPin, 1500); // Set the buzzer frequency to 1.5kHz
delay(duration); // Adjust as needed for the desired duration of the beep
noTone(buzzerPin);
}
float getSetVolume() {
// Get the desired volume from the user using the keypad
// This function should wait until the user presses '*' to confirm the input
float volume = 0.0;
bool volumeEntered = false;
while (!volumeEntered) {
char key = keypad.getKey();
if (key >= '0' && key <= '9') {
volume = volume * 10 + (key - '0');
lcd.setCursor(10, 2);
lcd.print(volume);
} else if (key == '*') {
volumeEntered = true;
flowDetectionRunning = true; // Set flow detection flag
}
}
return volume;
}
void detectFlow(float setVolume) {
unsigned long lastTime = 0;
unsigned long interval = 1000; // Interval for flow rate calculation (in milliseconds)
unsigned long totalPulses = 0;
float totalVolume = 0.0;
lcd.setCursor(8, 3);
lcd.print(" "); // Clear previous value
while (true) {
// Check for '#' character during operation
if (keypad.getKey() == '#') {
stopMode(); // Stop the current mode and return to mode selection
return;
}
unsigned long currentTime = millis();
if (currentTime - lastTime >= interval) {
// Calculate flow rate every 'interval' milliseconds
float flowRate = (pulse - totalPulses) * 60.0 / (interval * 7.5); // Assuming 7.5 pulses per liter
totalPulses = pulse;
lastTime = currentTime;
// Update total volume
totalVolume += flowRate / 1000.0; // Convert flow rate from L/min to L/s, then accumulate
// Update LCD with actual accumulated volume under "Actual: " section
lcd.setCursor(8, 3);
lcd.print(totalVolume, 1); // Print actual accumulated volume with 1 decimal place
lcd.print(" mL");
// Check if accumulated volume meets or exceeds set volume
if (totalVolume >= setVolume) {
// If accumulated volume meets or exceeds set volume, stop the pump
analogWrite(enablePin, 0); // Stop the pump
flowDetectionRunning = false; // Reset flow detection flag
menuDisplayed = false; // Return to mode selection
break;
}
}
}
}
void increase()
{
pulse++;
}