#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <BasicStepperDriver.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Encoder variables
volatile long encoderCount = 0;
int encoderPinA = 3;
int encoderPinB = 12;
int switchPin = 11;
// IR sensor variables
volatile float rev = 0;
const int IR = 2;
volatile int currentstate = 0;
volatile int laststate = 0;
// Motor control
int MOTE = 5;
int MOTA = 4;
int degree = 0;
// Stepper motor
double MOTOR_STEPS = 200;
double RPM = 0;
int DIR = 7;
int STEP = 6;
double MICROSTEPS = 16;
double primaryrotation = 360;
BasicStepperDriver stepper(MOTOR_STEPS, DIR, STEP);
// Program state
int stateA = 0;
double OD = 0;
double turns = 0;
double rotation = 0;
double circumfarence = 0;
double pulse = 0;
double step_per_cirum = 4.225;
double turn_RPM_factor = 6.4;
// Variables for interrupt debouncing
volatile unsigned long lastEncoderInterruptTime = 0;
volatile unsigned long lastIRInterruptTime = 0;
const unsigned long DEBOUNCE_DELAY = 5; // ms
// Encoder ISR - optimized for performance
void encoderISR() {
unsigned long currentTime = millis();
// Debounce - ignore interrupts too close together
if (currentTime - lastEncoderInterruptTime < DEBOUNCE_DELAY) {
return;
}
lastEncoderInterruptTime = currentTime;
// Read both pins to determine direction (using direct port access for speed)
int pinAState = digitalRead(encoderPinA);
int pinBState = digitalRead(encoderPinB);
if (pinAState == pinBState) {
encoderCount--; // Clockwise
} else {
encoderCount++; // Counter-clockwise
}
}
// IR sensor ISR - optimized
void isr() {
unsigned long currentTime = millis();
// Debounce - ignore interrupts too close together
if (currentTime - lastIRInterruptTime < DEBOUNCE_DELAY) {
return;
}
lastIRInterruptTime = currentTime;
currentstate = digitalRead(IR);
if (currentstate != laststate) {
rev++;
laststate = currentstate;
}
}
void setup() {
lcd.begin(16,2);
lcd.backlight();
pinMode(IR, INPUT);
pinMode(encoderPinA, INPUT_PULLUP);
pinMode(encoderPinB, INPUT_PULLUP);
pinMode(switchPin, INPUT_PULLUP);
// Attach interrupts with proper debouncing
// Use FALLING or RISING instead of CHANGE for better stability if possible
attachInterrupt(digitalPinToInterrupt(IR), isr, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderPinA), encoderISR, CHANGE);
pinMode(MOTE, OUTPUT);
pinMode(MOTA, OUTPUT);
Serial.begin(9600);
lcd.setCursor(0, 0);
lcd.print("Winding Machine");
delay(2000);
lcd.setCursor(0, 1);
lcd.print(" MNG proSystems ");
delay(4000);
lcd.clear();
digitalWrite(MOTE, HIGH);
if (digitalRead(IR) == HIGH) {
digitalWrite(MOTA, HIGH);
delay(1000);
digitalWrite(MOTA, LOW);
}
}
void loop() {
// Read encoder value (divided by 4 because most encoders have 4 state changes per detent)
// Use atomic operation to avoid interrupt conflicts
long encoderValue;
noInterrupts();
encoderValue = encoderCount / 4;
interrupts();
switch(stateA) {
case 0: // Core outer diameter
lcd.setCursor(0, 0);
lcd.print("Core () Diameter");
lcd.setCursor(4, 1);
lcd.print("Millimeter");
lcd.setCursor(14, 1);
lcd.print(">>");
lcd.setCursor(0, 1);
lcd.print(encoderValue);
OD = encoderValue;
break;
case 1: // NUMBER OF TURNS
lcd.setCursor(0, 0);
lcd.print("Number of Turns");
lcd.setCursor(14, 1);
lcd.print(">>");
lcd.setCursor(0, 1);
lcd.print(encoderValue);
turns = encoderValue;
break;
case 2: // DEGREE OF WINDING
lcd.setCursor(0, 0);
lcd.print("Degree / Winding");
lcd.setCursor(14, 1);
lcd.print(">>");
lcd.setCursor(0, 1);
lcd.print(encoderValue * 10); // Scale for finer control
rotation = encoderValue * 10;
break;
case 3: // CONFIRMATION SCREEN
pulse = (MOTOR_STEPS * MICROSTEPS * 2.54 * (rotation / primaryrotation));
stepper.begin(20, MICROSTEPS);
degree = pulse / (turns * 2);
lcd.setCursor(0, 0);
lcd.print("OD");
lcd.setCursor(3, 0);
lcd.print("Turns");
lcd.setCursor(10, 0);
lcd.print("Degree");
lcd.setCursor(0, 1);
lcd.print(round(OD));
lcd.setCursor(4, 1);
lcd.print(round(turns));
lcd.setCursor(10, 1);
lcd.print(round(rotation));
lcd.setCursor(14, 1);
lcd.print(">>");
break;
case 4: // RUNNING
// Use atomic operation to read rev safely
float currentRev;
noInterrupts();
currentRev = rev;
interrupts();
if (round(currentRev/2) <= turns) {
digitalWrite(MOTA, HIGH);
stepper.move(round(degree));
lcd.setCursor(4, 0);
lcd.print("Winding!");
lcd.setCursor(0, 1);
lcd.print(round(currentRev/2));
lcd.setCursor(5, 1);
lcd.print("Turns");
} else {
digitalWrite(MOTA, LOW);
stepper.stop();
delay(500);
lcd.setCursor(0, 0);
lcd.print("Winding Complete");
lcd.setCursor(0, 1);
lcd.print("<<------------>>");
}
break;
}
// Handle button press to advance state with better debouncing
static unsigned long lastButtonPress = 0;
if (!digitalRead(switchPin) && (millis() - lastButtonPress > 300)) {
delay(10); // Simple debounce
if (!digitalRead(switchPin)) { // Confirm button is still pressed
lastButtonPress = millis();
stateA++;
// Reset encoder for next parameter
noInterrupts();
encoderCount = 0;
interrupts();
lcd.clear();
// Reset to beginning if we've completed all states
if (stateA > 4) {
stateA = 0;
noInterrupts();
rev = 0;
interrupts();
}
}
}
// Small delay to prevent LCD flickering
delay(50);
}