Hello everyone hope you all are having a great summer ....
Brief intro
I found a project to be 3d printed that came with it's own arduino software and aldo it works there are a few things i would like to chage.
For example:
When a mode is selected, it gives you the option to tell it for how long it will work ( using a rotary encoder) and the countdown starts and it all works, howerver.... it does not show you the actual countdon, it only shows "minutes left" to the end... instaid of 02:00>01:59->01:58 ... etc, it only shows 02:00 left (after 60sec) -> 01:00 left.... etc
i am no programer and before asking for help i have tryed all demos i could find and they all work... but i lack the skill to merge/adapt any of them to my proj.
I would apreciate any help from you guys.
Thank you in advanced
Regars
Xmodpt
// Open clean and cure project
// Made by Jeremy Vallet
// Before upload this sketch, make sure to have correctly set tension on your DCDC converter. It should output 5V
// Also make sure to have correctly set the tension of your driver, my driver is a A4988 with res of 0.1 ohm, I set my Vref to 0.5V. If your driver have a different rsense (Rcs) value, please read the documentation at https://www.pololu.com/product/1182
// Imax = Vref / (8*Rcs) = 0,5/0.8 = 625mA in my case
// So with a Rcs of 0.05 you may set your Vref too 250mV
// And with a Rcs of 0.068 you may set your Vref too 340mV
// If this tension is set to high, your stepper can overheat, your driver too and you may damage something.
// As said in the guide, you can also tweak your current limit on the DCDC 5V converter (one potentiometer is for voltage, and the other for current) to make sure your LED won't overheat (the classic 10W 5V pannel seems to run relatively cold but I prefered to set a current limitation)
// If you encounter issue with speed rotation, you can tweak values MAX_STEPPERSPEED_WASH and MAX_STEPPERSPEED_CURE with the sketch file nammed "StepperCalibration.ino".
// Visit my Cult3D for more projects ! https://cults3d.com/fr/utilisateurs/jeremyV
// or Thingiverse : https://www.thingiverse.com/jvjv/designs
//////////////////////////////////////////////////////////////////////////////////////
//
//
// PROGRAMING Updated by Xmodpt
//
// v.1.0.1 - Added Buzzer for sound alarm when task finishes
// Corrected the bug with "CURE" timer
#include <AccelStepper.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// UV LEDs pannel
#define PIN_UVLEDS 4
// rotary encoder
#define CLK 6 //CLK //s1
#define DT 5 //DT //s2
#define RE_KEY 2
// stepper and driver pins
#define PIN_DIR 7
#define PIN_STEP 8
#define PIN_RESET 10
#define PIN_SLEEP 8
#define PIN_ENABLE 11
#define MS1 13
#define MS2 12
#define MS3 3
// speed and acceleration of stepper
#define MAX_STEPPERSPEED_WASH 713 //FULL STEP - value is in step/s - you can this value with the SpeedCalibration.ino file
#define MAX_STEPPERSPEED_CURE 106 // Sixteenth step - 106 is equal to 30sec for 360° of rotation (my NEMA 17 is 200 steps per revolution, microstepping by /16 so 200*16 = 3200, 30s (cause I want 30s for a full rotation) --> 3200 steps, 1s --> 106 steps) - you can tweak this value with the SpeedCalibration.ino file
#define STEPPER_ACCEL 2000 // step/s/s
AccelStepper stepper = AccelStepper(AccelStepper::DRIVER, PIN_STEP, PIN_DIR);
LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for a 16 chars and 2 line display
// ADDED BUZZER
constexpr int buzzer =A1;
// custom full black char for blinking
byte fullBlack[] = {
0x1F,
0x1F,
0x1F,
0x1F,
0x1F,
0x1F,
0x1F,
0x1F
};
String modes[3] = {"Wash", "Cure", "Dry"}; // availables modes
int highlightMode = 0; // index of the current hilighted mode
int mode = -1; // index of the current mode
bool modeSelectionState = false;
bool timeSelectionState = false;
bool modeConfigurationState = false;
// encoder variables
int currentStateCLK;
int lastStateCLK;
unsigned long previousClickMillis = 0; // will store last time buttton was pushed (for cancel function)
// variables for blinking mode
unsigned long previousMillis = 0; // will store last time LCD was updated (for blibnking)
const long interval = 500; // interval at which to blink (milliseconds)
bool blinkState = false;
int t = 0; // minute
int remainM = t; // remaining time in minutes
void setup() {
// LCD init
lcd.init();
lcd.backlight();
lcd.setCursor(1, 0);
lcd.print(F("Wash&Cure v1.0.1"));
lcd.setCursor(1, 1);
lcd.print(F("by Xm0dpt 2022"));
lcd.createChar(0, fullBlack); // fullBlack char creation
delay(1000);
// set LEDs as output
pinMode(PIN_UVLEDS, OUTPUT);
// set encoder pins as inputs
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
pinMode(RE_KEY, INPUT);
// BUZZER
/**** buzzer pins *****/
pinMode(buzzer,OUTPUT);
// set stepper driver pins as outputs
pinMode(PIN_DIR, OUTPUT);
pinMode(PIN_STEP, OUTPUT);
pinMode(PIN_RESET, OUTPUT);
pinMode(PIN_SLEEP, OUTPUT);
pinMode(PIN_ENABLE, OUTPUT);
// set microstepping settings pins as output
pinMode(MS1, OUTPUT);
pinMode(MS2, OUTPUT);
pinMode(MS3, OUTPUT);
// uvleds are off at start
digitalWrite(PIN_UVLEDS, LOW);
// stepper is in sleep mode at start
digitalWrite(PIN_SLEEP, LOW);
digitalWrite(PIN_RESET, LOW);
digitalWrite(PIN_ENABLE, HIGH);
// set stepper acceleration
stepper.setAcceleration(STEPPER_ACCEL);
// read the initial state of CLK
lastStateCLK = digitalRead(CLK);
// display available modes
displayModesMenu();
}
void loop() {
// read the rotary encoder to change highlighted mode
rotaryEncoderRead(highlightMode);
if (modeSelectionState)
{
// set limits of hightlightmode -- only 3 modes availables
if (highlightMode < 0)
highlightMode = 0;
if (highlightMode > 2)
highlightMode = 2;
// define offset for blinking start char
int offset = 0;
for (int i = 0; i < highlightMode; i++)
offset += (modes[i].length() + 2);
// use millis() instead of delay() to not block process
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time selected mode was blinked
previousMillis = currentMillis;
// inverse blinking state
blinkState = ! blinkState;
if (blinkState)
{
// write fullBlack char on all letters of the selected mode
for (int i = 0; i < modes[highlightMode].length(); i++)
{
lcd.setCursor(i + offset, 1);
lcd.write(0); // 0 = custom blackChar
}
}
else
{
// reprint all modes normally
lcd.setCursor(0, 1);
lcd.print(modes[0] + F(" ") + modes[1] + F(" ") + modes[2]);
}
}
// if user press rotary encoder button
if (!digitalRead(RE_KEY))
{
// wait release of RE_KEY
while (!digitalRead(RE_KEY));
// then enter config mode to set duration
modeConfigurationState = true;
configureMode(highlightMode);
}
}
}
void configureMode(int desiredMode)
{
modeSelectionState = false; // exit modeSelectionState
bool clickTimeInited = false;
lcd.clear();
lcd.setCursor(3, 0);
lcd.print(modes[desiredMode] + F(" time"));
lcd.setCursor(4, 1);
lcd.print("00 : min");
while (modeConfigurationState)
{
// read and assign new value of t
int old_t = t; //save t
rotaryEncoderRead(t);
// set limits of duration time
if (t < 0)
t = 0;
if (t > 60)
t = 60;
// display new value of t
if (t != old_t)
{
lcd.setCursor(4, 1);
if (t < 10)
{
lcd.print("0");
lcd.setCursor(5, 1);
}
lcd.print(t);
}
if (!digitalRead(RE_KEY))
{
delay(100); //anti-rebound
if (!clickTimeInited)
{
previousClickMillis = millis();
clickTimeInited = true;
}
if (digitalRead(RE_KEY) && (millis() - previousClickMillis <= 1000) && t > 0)
{
clickTimeInited = false;
previousClickMillis = millis();
mode = desiredMode; // set the desired mode as actual mode
startMode(mode, t); // start the mode with desired duration
}
else if (millis() - previousClickMillis >= 1000)
{
clickTimeInited = false;
previousClickMillis = millis();
mode = -1; // reinit mode index
modeConfigurationState = false; // exit configuration mode
displayModesMenu(); // return to main menu
}
}
}
}
void startMode(int modeIndex, int t)
{
// Mode 0 : Wash --> Only motor at fast speed
// Mode 1 : Cure --> Motor at slow speed + UVLEDs
// Mode 2 : Light --> Only UVLEDs (to cure resin residues in alcohool and make them fall to easier filtering)
// MS1 MS2 MS3 Microstep Resolution
// L L L Full step
// H L L Half step
// L H L Quarter step
// H H L Eighth step
// H H H Sixteenth step
modeConfigurationState = false;
if (modeIndex == 0)
{
// WASH MODE -- MOTOR TURN FOR A SPECIFIED TIME. DIRECTION IS REVESED ALL MINUTES
// Set motor direction clockwise
digitalWrite(PIN_DIR, HIGH);
// set sleep and reset at FALSE and enable at TRUE (inverted states here)
digitalWrite(PIN_SLEEP, HIGH);
digitalWrite(PIN_RESET, HIGH);
digitalWrite(PIN_ENABLE, LOW);
// set full step for high speed
digitalWrite(MS1, LOW);
digitalWrite(MS2, LOW);
digitalWrite(MS3, LOW);
stepper.setMaxSpeed(MAX_STEPPERSPEED_WASH);
// set remaining time
remainM = t;
float nstep = (float)MAX_STEPPERSPEED_WASH * 60.0; // set a 1min cycle (acceleration is not take account to simplify)
while (remainM > 0)
{
refreshTimeRemaining(); // refresh display
stepper.move(nstep); // tell stepper to rotate for 1 minute
while (stepper.distanceToGo() != 0)
{
stepper.run();
}
// reverse rotation direction
nstep *= -1;
// decrease minutes counter
remainM --;
// wait a bit to not damage motor
if (remainM != 0)
delay(2000);
}
handleFinishedMode();
}
else if (modeIndex == 1)
{
// WASH MODE -- MOTOR TURN FOR A SPECIFIED TIME. DIRECTION IS REVESED ALL MINUTES
// Set motor direction clockwise
digitalWrite(PIN_DIR, HIGH);
// set sleep and reset at FALSE and enable at TRUE (inverted states here)
digitalWrite(PIN_SLEEP, HIGH);
digitalWrite(PIN_RESET, HIGH);
digitalWrite(PIN_ENABLE, LOW);
// turn on UV LED pannel
digitalWrite(PIN_UVLEDS, HIGH);
// set full step for high speed
digitalWrite(MS1, HIGH);
digitalWrite(MS2, HIGH);
digitalWrite(MS3, HIGH);
stepper.setMaxSpeed(MAX_STEPPERSPEED_CURE);
// set remaining time
remainM = t;
float nstep = (float)MAX_STEPPERSPEED_WASH * 10.0; // set a 1min cycle (acceleration is not take account to simplify)
while (remainM > 0)
{
refreshTimeRemaining(); // refresh display
stepper.move(nstep); // tell stepper to rotate for 1 minute
while (stepper.distanceToGo() != 0)
{
stepper.run();
}
// reverse rotation direction
nstep *= -1;
// decrease minutes counter
remainM --;
// wait a bit to not damage motor
if (remainM != 0)
delay(2000);
}
handleFinishedMode();
}
else if (modeIndex == 2)
{
// LIGHT MODE - TURN ON UN LED FOR A SPECIFIED TIME
digitalWrite(PIN_UVLEDS, HIGH);
// set remaining time
remainM = t;
// define duration in ms
unsigned long tt = (unsigned long)remainM * 1000 * 60;
delay(tt);
handleFinishedMode();
}
}
void handleFinishedMode()
{
lcd.clear();
lcd.setCursor(6, 1);
lcd.print(modes[mode]);
lcd.setCursor(4, 1);
lcd.print("Finished");
buzzer_alarm();
buzzer_alarm();
buzzer_alarm();
// enter sleep mode
digitalWrite(PIN_SLEEP, LOW);
digitalWrite(PIN_RESET, LOW);
digitalWrite(PIN_ENABLE, HIGH);
// turn off uv leds
digitalWrite(PIN_UVLEDS, LOW);
// let user get back to main menu by pressing encoder btn
//wait a press
while (digitalRead(RE_KEY)) delay(50);
// wait the release before proceed
while (!digitalRead(RE_KEY)) delay(50);
mode = -1; // reinit mode
t = 0;
displayModesMenu(); // display modeMenu
}
void printTime(int m)
{
//print remaining minutes on two digits
lcd.setCursor(6, 1);
if (m < 10)
{
lcd.print("0");
lcd.setCursor(7, 1);
}
lcd.print(m);
lcd.setCursor(8, 1);
lcd.print(" min");
}
void refreshTimeRemaining()
{
lcd.clear();
lcd.setCursor(1, 0);
lcd.print(F("Time"));
lcd.setCursor(2, 1);
lcd.setCursor(1, 1);
lcd.print(F("Left"));
lcd.setCursor(7, 1);
//printTime(remainM);
lcd.print (remainM);
}
void displayModesMenu()
{
lcd.clear();
lcd.setCursor(2, 0);
lcd.print(F("Select mode"));
lcd.setCursor(0, 1);
lcd.print(modes[0] + F(" ") + modes[1] + F(" ") + modes[2]);
modeSelectionState = true;
// wait the release before continue
while (!digitalRead(RE_KEY)) delay(50);
}
//modify the passed value according to the rotation direction (CW/CCW)
void rotaryEncoderRead(int &val)
{
//I wrote nothing myself here
//CODE FROM : https://lastminuteengineers.com/rotary-encoder-arduino-tutorial/
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
if (currentStateCLK != lastStateCLK && currentStateCLK == 1) {
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(DT) != currentStateCLK) {
val --;
} else {
// Encoder is rotating CW so increment
val ++;
}
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
}
void buzzer_alarm() {
// Sounds the buzzer at the frequency relative to the note C in Hz
tone(buzzer,5000, 500);
// Waits some time to turn off
delay(1100);
//Turns the buzzer off
noTone(buzzer);
// Sounds the buzzer at the frequency relative to the note D in Hz
return;
}