// Version 1.0 - Tip gutters and write to serial and LCD
// Version 1.1 - + Add push button press and hold to tip gutters
// Version 1.2 - + Add potentiometer to set time to tip
// Include libraries for motor controller board
// Install libraries in the IDE by going to Sketch-Manage Libraries andi include "Cytron Motor Drivers Library"
#include <CytronMotorDriver.h>
// Include LCD libraries
// Install libraries in IDE by including DF_Robot_RGBLCD1602
#include <DFRobot_RGBLCD1602.h>
//#include <LiquidCrystal_I2C_STEM.h>
// create LCD object
// Note: The robot controller board requires serial port drivers on windows that can be found here: cdn.cytron.io/makeruno/CH341SER.EXE
// After driver installation you can see this as USB-SERIAL CH340 in Windows Device Manager under Ports on COM5 (after reboot)
// To upload code to the board, choose COM5 in the Tools menu in the IDEs
// In IDE also choose Boards Manager - "Arduino Robot Motor"
// The DF Robot LCD is compatible with I2C protcol
//LiquidCrystal_I2C_STEM lcd(0x2D,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display
DFRobot_RGBLCD1602 lcd(/*RGBAddr*/ 0x2D, /*lcdCols*/ 16, /*lcdRows*/ 2); //16 characters and 2 lines of show
/*
The one I purchased is the v2.00 model
Change the RGBaddr value based on the hardware version
-----------------------------------------
Moudule | Version| RGBAddr|
-----------------------------------------
LCD1602 Module | V1.0 | 0x60 |
-----------------------------------------
LCD1602 Module | V1.1 | 0x6B |
-----------------------------------------
LCD1602 RGB Module | V1.0 | 0x60 |
-----------------------------------------
LCD1602 RGB Module | V2.0 | 0x2D |
-----------------------------------------
*/
// variables for the amount of time we want before gutters next tip
long seconds_before_stip = 15;
long minutes_before_tip = 0;
long hours_before_tip = 0;
long days_before_tip = 0;
long MILLISECONDS = 1000;
// calc the total seconds
double DELAY_BEFORE_TIP = (double) seconds_before_stip + (minutes_before_tip * 60) + (hours_before_tip * 60 * 60) + (days_before_tip * 24 * 60 * 60);
unsigned long TIP_DOWN_WAIT_TIME = 5; // amount of time to wait when the gutters are tipped down
unsigned long TIP_TIME = 10; // amount of time to run the motors when tipping up or down
unsigned long DISPLAY_INTERVAL = 1; // amount of time between updating the display in seconds
// set up variables for button. Assign it to pin 2. Only buttons 2 or 3 can be used for interrupts. Some pins only support some types of change
int buttonPin = 2;
// volatile must be used for values set within an interrupt
volatile int button_press_flag = 0;
int button_press_times = 0;
int debounceTime = 20;
// set up variables for potentiometer which will enable us to increase and decrease the time interval for tipping
int potPin = A0;
// Configure the motor driver.
CytronMD motorL(PWM_DIR, 5, 4);
CytronMD motorR(PWM_DIR, 6, 7);
// Wiring Connections on board
#define LED0 0 // Onboard LED 0
#define LED1 1 // Onboard LED 1
void setup() {
// this code is automatically called once when the Arduino starts
int result = 0;
// Set the Arduino board LEDs to Output mode
pinMode(LED0, OUTPUT);
pinMode(LED1, OUTPUT);
// Turn off the LEDs.
// These LEDs are active with LOW setting.
digitalWrite(LED0, HIGH);
digitalWrite(LED1, HIGH);
// set up a button with an interrupt. The use of INPUT_PULLUP configures a built in resistor on the Arduino board on to the pin
pinMode(buttonPin, INPUT_PULLUP);
// set up the interrupt for the button. Assign it to pin 2 and assign it to run the code in function ISR_buttonPress when the button is pressed
// INPUT_PULLUP requires a resistor to be between the button and the arduino. It removed noise and ensures that there is a clean and discrete 5V or 0V signal
// this uses active low logic. The button voltage will be 0 when pressed and 1 when NOT being pressed
// https://youtu.be/OZGMLOwHYf8
attachInterrupt(digitalPinToInterrupt(buttonPin), ISR_buttonPress, INPUT_PULLUP);
// set up potentiometer
pinMode(potPin, INPUT);
// Open serial port for debugging and set to 9600 baud. Using 9600 because Wokwi Simulator requires 9600
Serial.begin(9600);
Serial.println("Setup........");
// Configure LCD
result = initLCD();
Serial.println("LCD init........");
lcd.setCursor(0,0);
lcd.print("LCD init........");
}
void loop() {
// this code is automatically called once when the Arduino starts after the Setup() function. It run repeatedly
int result = 0;
Serial.println("Init controller.");
// lcd.setCursor(0,0);
// lcd.print("Init controller.");
// Wait for a defined period of time and update the LCD
result=waitForTip();
Serial.println("Init tipping....");
lcd.setCursor(0,0);
lcd.print("Init tipping....");
// Tip the gutters by controlling the motors
result=tipGutters();
Serial.println("Finished tipping");
lcd.setCursor(0,0);
lcd.print("Finished tipping");
// wait 2 seconds so the tipping event can be seen
}
int initMotors() {
// Make sure the motors are stopped.
Serial.println("Init motors.....");
lcd.setCursor(0,0);
lcd.print("Init motors.....");
motorL.setSpeed(0);
motorR.setSpeed(0);
return true;
}
int initLCD() {
// DFRobot Gravity I2C LCD1602 with RGB Backlight Display can display 2x16 characters and support functions like scrolling-displaying, cursor movement and backlight color adjustment
// initialize the LCD and master IIC
Serial.println("Initialising LCD");
// Start calling initialisation functions
lcd.init();
// Turn on the display
lcd.display();
// clear the display and return the cursor to the initial position (position 0)
lcd.clear();
// return the cursor to the initial position (0,0)
lcd.home();
// send value to LCD
return true;
}
int tipWait() {
// this functions waits with the gutters tipped down for x seconds to allow leaves to fall out
double tipping = 0.00;
// Turn on the LEDs on the Arduino board.
digitalWrite(LED0, LOW);
digitalWrite(LED1, LOW);
for (tipping=TIP_DOWN_WAIT_TIME; tipping>=0; tipping-=DISPLAY_INTERVAL) {
// print the delay amount
lcd.setCursor(0,0);
lcd.print("Down for (s)....");
lcd.setCursor(0,1);
lcd.print(" "); // Sixteen spaces to clear the LCD line
lcd.setCursor(0,1);
lcd.print(String((long) tipping));
Serial.println("Down for (s): "+String((long) tipping));
// wait before next updating the display
delay(DISPLAY_INTERVAL*MILLISECONDS);
}
return true;
}
int tipUp() {
// this function tips the gutters up
double tipping = 0.0;
// Turn on the LEDs on the Arduino board.
digitalWrite(LED0, LOW);
digitalWrite(LED1, LOW);
// activate the motors in reverse direction
motorL.setSpeed(-150);
motorR.setSpeed(-150);
// apply the power to tip the gutters up
// wait with the gutters tipped down for x seconds to allow leaves to fall out
for (tipping=TIP_TIME; tipping>=0; tipping-=DISPLAY_INTERVAL) {
// print the delay amount
lcd.setCursor(0,0);
lcd.print("Raising (s).....");
lcd.setCursor(0,1);
lcd.print(" "); // Sixteen spaces to clear the LCD line
lcd.setCursor(0,1);
lcd.print(String((long) tipping));
Serial.println("Raising (s): "+String((long) tipping));
// wait before next updating the display
delay(DISPLAY_INTERVAL*MILLISECONDS);
}
// Turn off the LEDs
// These LEDs are active with LOW setting.
digitalWrite(LED0, HIGH);
digitalWrite(LED1, HIGH);
motorL.setSpeed(0);
motorR.setSpeed(0);
return true;
}
int tipDown() {
// this function tips the gutters down
double tipping = 0.0;
// activate the motors
motorL.setSpeed(150);
motorR.setSpeed(150);
// let the motors run for x seconds to tip the gutters
for (tipping=TIP_TIME; tipping>=0; tipping-=DISPLAY_INTERVAL) {
// print the tipping time
lcd.setCursor(0,0);
lcd.print("Lowering (s)....");
lcd.setCursor(0,1);
lcd.print(" "); // Sixteen spaces to clear the LCD line
lcd.setCursor(0,1);
lcd.print(String((long) tipping));
Serial.println("Lowering (s): "+String((long) tipping));
// wait before next updating the display
delay(DISPLAY_INTERVAL*MILLISECONDS);
}
// Turn off the LEDs.
// These LEDs are active with LOW setting.
digitalWrite(LED0, HIGH);
digitalWrite(LED1, HIGH);
// pause the motors
motorL.setSpeed(0);
motorR.setSpeed(0);
}
int tipGutters() {
// this function tips the gutters down, waits, then tips the gutters up again
int result = 0;
result = tipDown();
result = tipWait();
result = tipUp();
return true;
}
int waitForTip() {
// This functions waits for a defined period of time before the function to tip the gutters is called
// It updates the LCD display on a regular basis so that the status is known to the user
double loopval=0;
int result = 0;
// print the delay amount
lcd.setCursor(0,0);
lcd.print("Wait for tip....");
Serial.println("Wait for tip....");
// create a countdown timer based on the amount of time configured before the next tip is required
for (loopval=DELAY_BEFORE_TIP; loopval>=0; loopval=loopval) {
// check to see if button has been pressed. If it has, we want to tip the gutters manually instead of on a timer
int results = checkIfButtonPressed();
// check if the potentiometer has been turned. If it has, we want to adjust the loopval
//******loopval = readAndSetTipTimeViaPot(loopval);
// call the function that prints the number of days, hours, minutes and seconds before the gutters will tip
result = calcAndPrintTime(loopval);
// wait before next updating the display
delay(DISPLAY_INTERVAL*MILLISECONDS);
if (button_press_times==1) {
// don't decrement the loop because the button has been pressed to manually override the time. Wwe want to pause the timer
loopval = loopval;
}
else {
// decrement the timer
loopval-=(double) DISPLAY_INTERVAL;
}
}
return true;
}
double readAndSetTipTimeViaPot(double loopval) {
// this function takes an input of the current time left on the count down clock
// it the potentiometer has been used, then it adjusts the tipping delay time accordingly and then returns to the the delay timer function with a new value
// See this page if hardware fluctations happen when not touching the pot
// https://www.norwegiancreations.com/2015/10/tutorial-potentiometers-with-arduino-and-filtering/
int potValue = 0;
int mappedValue =0;
double increment = 0.00;
potValue = analogRead(potPin);
// the map function takes the input value and maps it across a spectrum of values (the last two parameters). So this is how we can split the potentiometer values into positive and negative
// https://roboticsbackend.com/arduino-potentiometer-complete-tutorial/
mappedValue = (double) map(potValue, 0, 1023, -512, 512);
// check to see if the lowest and highest values have been set. If they have override the value so it can't go too high or too low
if (mappedValue!=0) {
// only do something if the potentiometer is set to a value other than 0
increment = pow(2, abs(mappedValue/32)); // increment using an exponential value
if (mappedValue <=0) {
increment = -1*increment;
}
loopval = loopval + increment;
// multiple squaring the number will always result in positive. So check to see if it is negative and turn the value negative if required
lcd.setCursor(0,0);
lcd.print(" "); // Sixteen spaces to clear the LCD line
lcd.setCursor(0,0);
lcd.print("Adj (s): " + String((long) increment));
Serial.println("Pot value: " + String(mappedValue) + " Increment: " + String ((long) increment));
if (loopval<=3.00)
{
// make 1 second the shortest amount of time that can be set
loopval=3.00;
}
else if (loopval>=2592000.00 && mappedValue!=0) {
// make 30 days the longest interval that can be set
loopval=2592000.00;
}
// also set the global tipping delay value so that when the loop next starts it will take the last set value. Only set this value if the potentiometer is set to a non-zero value
DELAY_BEFORE_TIP = loopval;
}
else {
lcd.setCursor(0,0);
lcd.print("Await tip......."); // Sixteen spaces to clear the LCD line
}
// print the delay amount
// lcd.print("Local timer: " + String(loopval) + " Global timer: " + String(DELAY_BEFORE_TIP));
// Serial.println("Local timer: " + String(loopval) + " Global timer: " + String(DELAY_BEFORE_TIP));
// return the value to the delay timer function
return loopval;
}
int calcAndPrintTime(double inputval) {
// this function takes a long values in MILLISECONDS and calculates the days, hours, minutes, and seconds of that value
// doubles are used for the calc because the number of millis can be very large
long days = 0;
long hours = 0;
long minutes = 0;
long seconds = 0;
double sInDay = 24.0 * 60.0 * 60.0;
double sInHour = 60.0 * 60.0;
double sInMinute = 60.0;
days = (long) floor(inputval / sInDay);
// calc remaining hours
hours = (long) floor((inputval - (days * sInDay)) / sInHour);
// calc remaining minutes
minutes =(long) floor((inputval - (days * sInDay) - (hours*sInHour)) / sInMinute);
// calc remaining seconds
seconds =(long) floor(inputval - (days * sInDay) - (hours*sInHour) - (minutes * sInMinute));
// set the cursor col, row
lcd.setCursor(0,1);
lcd.print(" "); // Sixteen spaces to clear the LCD line
lcd.setCursor(0,1);
lcd.print(String(days) + "d "+ String(hours) + "h "+ String(minutes) +"m " + String(seconds) + "s");
Serial.println(String(days) + "d "+ String(hours) + "h "+ String(minutes) +"m " + String(seconds) + "s");
return true;
}
int checkIfButtonPressed() {
// check to see if the button has been pressed.
// If it has been pressed once, tipping the gutters down
// If it has been pressed a second time, tip the gutters up
// Otherwise do nothing
int result = 0;
// Serial.println("Checking if button is pressed.");
int buttonState = 1; // 1 indicates digital 5V which is the NOT pressed button setting
// button_press_flag is set by the interrupt handler function.
if (button_press_flag == 1 && button_press_times == 0) {
// if this is the first time the button has been pressed, then tip the gutter down
// check the button state
// a button has two states; 5V (not pressed) and 0V (pressed). So we only want to invoke our code on one of those states. So we read the button pin to check if the state of the button is pressed (i.e. value zero indicating 0V)
// the button state will only read zero volts if the button is pressed and held
buttonState = digitalRead(buttonPin);
lcd.setCursor(0,0);
lcd.print("Button pressed..");
Serial.println("Button pressed. State was: " + String(buttonState));
if (buttonState == 0) {
// the button has been pressed (0V indicates pressed when active low state logic is used)
lcd.setCursor(0,0);
lcd.print("Manual override.");
Serial.println("Button was pressed first time. Tipping gutters down.");
// call the function to tip the gutters
result = tipDown();
button_press_times=1; // increment this variable to indicate that the button was pressed once
}
// set the button press flag back to false so that we don'tdo anything until the button is next pressed
button_press_flag = 0;
}
else if (button_press_flag == 1 && button_press_times==1) {
// if this is the second time the button has been pressed tip the gutter up
// check the button state
buttonState = digitalRead(buttonPin);
lcd.setCursor(0,0);
lcd.print("Button pressed..");
Serial.println("Button pressed second time. State was: " + String(buttonState));
if (buttonState == 0) {
// the button has been pressed (0V indicates pressed when active low state logic is used)
lcd.setCursor(0,0);
lcd.print("Manual override.");
Serial.println("Button was pressed a second time. Tipping gutters up.");
// call the function to tip the gutters
int result = tipUp();
button_press_times = 0; // reset times pressed to 0
}
// set the button press flag back to false so that we don't invoke the gutter tipping routine until the button is next pressed
button_press_flag = 0;
}
}
void ISR_buttonPress() {
// This is an Interrupt Service Request function. It gets invoked when a button is pressed
// It is essential to keep the code very light in these functions because all other functions on the micro controller pause during interrupt processing
button_press_flag = 1;
}