Building a coil winder [Part 5] - Coding and math
In this video I guide you through the coding and mathematics part of the coil winder. It is a bit more theoretical topic, but you can get a general idea about how the motors should be synchronized in order to make a nice solenoid coil.
Circuit Schematics
Arduino/STM32 source code
//Last update: 2021-07-30 //The LCD-related code was based on the following library: //https://github.com/cbm80amiga/N5110_GUI_encoder_demo_STM32 //Defining the pins for the Nokia 5110 LCD #define N5110_RST PA0 #define N5110_CS PA4 #define N5110_DC PA1 #define N5110_BACKLIGHT PA2 //Nokia pins: //RST: PA0 //CS: PA4 //DC: PA1 //BL: PA2 (optional) //DIN: PA7 //CLK: PA5 #include "math.h" #include "N5110_SPI.h" #if USESPI==1 #include <SPI.h> #endif N5110_SPI lcd(N5110_RST, N5110_CS, N5110_DC); // RST,CS,DC #include "c64enh_font.h" //smaller font #include "term9x14_font.h" //larger font //make sure that the font files are in the same folder as the project file! //AccelStepper #include <AccelStepper.h> AccelStepper feederStepper(1, PA8, PA9); // steps 9; direction 8 - TB6600 AccelStepper mainshaftStepper(1, PA10, PA11); // steps 11; Direction 10 - DRV8825 //Defining pins const int RotaryCLK = PB9; //CLK pin on the rotary encoder const int RotaryDT = PB8; //DT pin on the rotary encoder const int RotarySW = PB7; //SW pin on the rotary encoder (Button function) const int UpButton = PB6; //Up button which is also a start button const int DownButton = PB5; //Down button which is also a stop button const int enableMainshaft = PB10; //enable pin for the main shaft (GND = enabled, VCC = disabled) const int enableFeederShaft = PB11; //enable pin for the weaver shaft (GND = enabled, VCC = disabled) //Statuses of the DT and CLK pins on the encoder int CLKNow; int CLKPrevious; int DTNow; int DTPrevious; //Timing related variables, act mainly as debouncing float RotaryTime1; //Rotary encoder timing float RotaryTime2; unsigned long previousInterrupt; //Button timing //Menu-related variables //Values int RotaryButtonValue; //Pressed or not pressed rotary switch volatile int menuCounter = 0; //this is for counting the main menu (menu item number) // volatile int stepperMaxSpeed = 1600; //for setMaxSpeed - steps volatile int stepperAcceleration = 800; //for setAcceleration volatile float feederSpeed = 0.0; //speed of feeder in turns per second. It will be recalculated before winding volatile int numberOfTurns = 160; //number of turns of the coil - must be an integer float numberOfTurnsDone = 0; //#of turns done during winding volatile float wireDiameter = 0.35; //diameter of the wire to be wound volatile int turnsPerLayer; //Number of turns within a layer - must be an integer volatile float stepsPerLayer_feeder; //#of steps per layer for the feeder int layersToDo; //Number that stores the layers to do int numberOfLayers; //number of layers - must be an integer volatile float coilPitch = 1.0; //distance between two wires in the winding (distance between centers) volatile float bobbinWidth = 14.0; //width of the bobbin (or the coil) - unit is in mm volatile float bobbinDiameter = 20.0; // diameter of the bobbin (or the coil) - unit is in mm float totalWireLength = 0; //Total used wire length float mainShaftTotalSteps = 0; //Number of TOTAL steps for the main shaft volatile float mainShaftManualSteps = 0; float feederShaftTotalSteps = 0; //number of TOTAL steps for the secondary task volatile float feederShaftManualSteps = 0; float stepperButtonSteps = 0; // This keeps track of the steps done with the buttons int MainMicroStepping = 1600; //Value of microstepping. This amount of steps will result in 1 turn of the shaft int feederMicroStepping = 1600; //Value of microstepping. This amount of steps will result in 1 turn of the shaft //booleans bool stepperMaxSpeed_Selected = false; //for setMaxSpeed bool stepperAcceleration_Selected = false; //for setSpeed bool numberOfTurns_Selected = false; //for setAcceleration bool wireDiameter_Selected = false; //for the wire diameter bool coilPitch_Selected = false; //Coil pitch menu bool bobbinWidth_Selected = false; //0.1 mm precision bool bobbinDiameter_Selected = false; //0.1 mm precision bool mainShaftStepper_Selected = false; //precisely step the main shaft with buttons bool feederShaftStepper_Selected = false; //precisely step the weaver shaft with buttons bool winding_Selected = false; //Step size menu bool menuChanged = false; //Switched between menus bool valueChanged = false; //changed the value of a menu item bool updateValueSelection = false; //if we want to change the value of a variable, it becomes true bool windingIsRunning = false; char buf[25]; //buffer for printing on the LCD (strings) char str_temp[6]; //for printing floats int directionMultiplier = 1; //variable used to flip the direction of the feeder float mainStepperTimer = 0; //for updating the display void setup() { //Definition of the pins //Rotary encoder pinMode(PB9, INPUT); //CLK pinMode(PB8, INPUT); //DT pinMode(PB7, INPUT); //SW //Buttons pinMode(PB6, INPUT); //UP pinMode(PB5, INPUT); //DOWN //Enable-Disable pin for the stepper drivers pinMode(PB10, OUTPUT); //Main shaft enable pin pinMode(PB11, OUTPUT); //Feeder shaft enable pin //Store states CLKPrevious = digitalRead(RotaryCLK); DTPrevious = digitalRead(RotaryDT); //Rotary encoder interrupts attachInterrupt(digitalPinToInterrupt(RotaryCLK), RotaryEncoder, CHANGE); //Stepper speed setup mainshaftStepper.setMaxSpeed(stepperMaxSpeed); //SPEED = Steps / second mainshaftStepper.setAcceleration(stepperAcceleration); //ACCELERATION = Steps /(second)^2 feederStepper.setMaxSpeed(stepperMaxSpeed); feederStepper.setAcceleration(stepperAcceleration); digitalWrite(PB10, LOW); //disable main shaft power digitalWrite(PB11, LOW); //disable weaver shaft power lcd.init(); //initialize LCD lcd.clrScr(); //clear the whole display printLCD(); //print the welcome message MainMicroStepping = 1600; feederMicroStepping = 1600; //SFU1605 moves 5 mm for 1 turn -> 1 mm = 320 steps Serial.begin(115200); //All serial communication can be turned off if it is not needed } void loop() { if (menuChanged == true) { updateMenuPosition(); } if (updateValueSelection == true) { updateSelection(); updateValueSelection = false; //next loop() iteration will not enter } if (valueChanged == true) { updateValue(); valueChanged = false; } //Looping buttons CheckRotaryButton(); UpButtonPressed(); DownButtonPressed(); checkSteps(); //check the number of steps done //We don't need accelerations if we use slow speeds anyway, so I used the "acceleration-less" functions: mainshaftStepper.runSpeedToPosition(); //If a step is due, this function will do a step in each loop() iteration feederStepper.runSpeedToPosition(); //If a step is due, this function will do a step in each loop() iteration if (windingIsRunning == true) { //calculate the number of turns and refresh the display during winding every 1 sec //Printing might cause little "click" sounds when the motor is turning --> this part perhaps could have been done in a more elegant way if (millis() - mainStepperTimer > 1000) { numberOfTurnsDone = (mainShaftTotalSteps - mainshaftStepper.distanceToGo()) / (float)MainMicroStepping; //Example: (16000 - 4000) / 400 = 12000 / 400 = 30 updateValue(); mainStepperTimer = millis(); } } } void RotaryEncoder() { //Stepper speed - defined in steps/sec if (stepperMaxSpeed_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 0) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) == CLKNow) { stepperMaxSpeed++ ; //1 step size increment } else { if (stepperMaxSpeed == 160) //160, depends on the microstepping! { // Don't decrease further, it should be at least 160 // 1600/10 = 160 -> 0.1 turns/sec is the minimum speed } else { stepperMaxSpeed--; //1 step size decrement } } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //---------------------------------------------------------------------------- //Stepper acceleration else if (stepperAcceleration_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 0) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) == CLKNow) { stepperAcceleration++; //1 step size increment } else { stepperAcceleration--; //1 step size decrement } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //---------------------------------------------------------------------------- //Number of turns else if (numberOfTurns_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 0) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) == CLKNow) { numberOfTurns++; //1 step size increment } else { if (numberOfTurns == 1) //when it is = 1 { // Don't decrease further, it should be at least 1 } else { numberOfTurns--; //1 step size decrement } } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //---------------------------------------------------------------------------- //Wire diameter else if (wireDiameter_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 0) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) == CLKNow) { wireDiameter = wireDiameter + 0.01; //0.01 step size increment } else { if (wireDiameter == 0.01) //when it is = 0.01 { // Don't decrease further, it should be at least 0.01 } else { wireDiameter = wireDiameter - 0.01; //0.01 step size decrement } } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //---------------------------------------------------------------------------- //Coil pitch (the pitch function is not yet implemented! - 2021-07-30) else if (coilPitch_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 0) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) == CLKNow) { coilPitch = coilPitch + wireDiameter; //wire diameter step size increment } else { if (coilPitch == wireDiameter) //when it is = wire diameter { // Don't decrease further, it should be at least 1 diameter } else { coilPitch = coilPitch - wireDiameter; //wire diameter step size increment } } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //---------------------------------------------------------------------------- //Bobbin width else if (bobbinWidth_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 0) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) == CLKNow) { bobbinWidth = bobbinWidth + 0.1; //0.1 mm step size increment } else { if (bobbinWidth == 0.1) //when it is = 0.1 { // Don't decrease further, it should be at least 0.1 } else { bobbinWidth = bobbinWidth - 0.1; //0.01 step size decrement } } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //---------------------------------------------------------------------------- //Bobbin diameter - technically, the inner diameter of the coil else if (bobbinDiameter_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 0) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) == CLKNow) { bobbinDiameter = bobbinDiameter + 0.1; //0.1 mm step size increment } else { if (bobbinDiameter == 0.1) //when it is = 0.1 { // Don't decrease further, it should be at least 0.1 } else { bobbinDiameter = bobbinDiameter - 0.1; //0.01 step size decrement } } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //---------------------------------------------------------------------------- //Main shaft manual stepping else if (mainShaftStepper_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 0) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) == CLKNow) { mainShaftManualSteps = mainshaftStepper.currentPosition() + 160; //160 step size increment relative to the current position (depends on the microstepping) //160 steps is 0.1 turn, decrease the number if the movement is too much } else { mainShaftManualSteps = mainshaftStepper.currentPosition() - 160; //160 step size decrement relative to the current position } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state mainshaftStepper.moveTo(mainShaftManualSteps); //move to because we move relatively in a sense that we add a number to the absolute position } //---------------------------------------------------------------------------- //Feeder shaft manual stepping else if (feederShaftStepper_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 0) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) == CLKNow) { feederShaftManualSteps = feederStepper.currentPosition() + 160; //160 step size increment relative to the current position //320 steps is 1 mm with 1600 steps/turn microstepping ---> 160 steps is 0.5 mm } else { feederShaftManualSteps = feederStepper.currentPosition() - 160; //160 step size decrement relative to the current position } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state feederStepper.moveTo(feederShaftManualSteps); } //---------------------------------------------------------------------------- // else if (winding_Selected == true) { //do nothing } else //MENU COUNTER---------------------------------------------------------------------------- { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 0) { // If the DT state is different than the CLK state then // the encoder is rotating CCW so increase if (digitalRead(RotaryDT) == CLKNow) { if (menuCounter < 9) //10 menu items (0, 1, 2....9) { menuCounter++; } else { menuCounter = 0; //0 comes after 8, so we move in a "ring" } menuChanged = true; } else { // Encoder is rotating CW so decrease if (menuCounter > 0) { menuCounter--; } else { menuCounter = 9; //9 comes after 0 when we decrease the numbers } menuChanged = true; } } CLKPrevious = CLKNow; // Store last CLK state } } void printLCD() //Prints a "welcome screen" { lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, "Digital"); lcd.printStr(ALIGN_CENTER, 1, "Solenoid"); lcd.printStr(ALIGN_CENTER, 3, "Coil"); lcd.printStr(ALIGN_CENTER, 4, "Winder"); lcd.printStr(ALIGN_CENTER, 5, "C. Scientist"); } void CheckRotaryButton() { RotaryButtonValue = digitalRead(RotarySW); //read the button state if (RotaryButtonValue == 0) //0 and 1 can differ based on the wiring { RotaryTime1 = millis(); if (RotaryTime1 - RotaryTime2 > 1000) { switch (menuCounter) { case 0: stepperMaxSpeed_Selected = !stepperMaxSpeed_Selected; //we change the status of the variable to the opposite break; // case 1: stepperAcceleration_Selected = !stepperAcceleration_Selected; break; // case 2: numberOfTurns_Selected = !numberOfTurns_Selected; break; // case 3: wireDiameter_Selected = !wireDiameter_Selected; break; // case 4: coilPitch_Selected = !coilPitch_Selected; break; // case 5: bobbinWidth_Selected = !bobbinWidth_Selected; break; // case 6: bobbinDiameter_Selected = !bobbinDiameter_Selected; break; // case 7: mainShaftStepper_Selected = !mainShaftStepper_Selected; break; // case 8: feederShaftStepper_Selected = !feederShaftStepper_Selected; break; // case 9: winding_Selected = !winding_Selected; break; } updateValueSelection = true; RotaryTime2 = millis(); } } } void updateMenuPosition() { lcd.clrScr();//delete the entire screen switch (menuCounter) //this checks the value of the counter (0, 1, 2 or 3) { //MAX SPEED case 0: lcd.setFont(c64enh); //set the font (small one) lcd.printStr(ALIGN_CENTER, 0, " "); //erase previous text lcd.printStr(ALIGN_CENTER, 0, "Speed "); //print new text lcd.setFont(Term9x14); //Set another (larger) font snprintf(buf, 8, "%d", stepperMaxSpeed); //the decimal value of the variable is in the buf lcd.printStr(15, 2, buf); //set the cursor to 2nd line 15th pixel and print the buffer break; //------------------------------- //ACCELERATION case 1: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Acceleration"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", stepperAcceleration); lcd.printStr(15, 2, buf); break; //------------------------------- //Number of turns case 2: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Turns"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", numberOfTurns); lcd.printStr(15, 2, buf); break; //------------------------------- //Wire diameter case 3: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Wire diameter"); lcd.setFont(Term9x14); dtostrf(wireDiameter, 4, 2, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(15, 2, buf); break; //------------------------------- //Coil pitch case 4: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Pitch"); lcd.setFont(Term9x14); dtostrf(coilPitch, 4, 2, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(15, 2, buf); break; //------------------------------- //Bobbin width case 5: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Bobbin width"); lcd.setFont(Term9x14); dtostrf(bobbinWidth, 4, 2, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(15, 2, buf); break; //------------------------------- //Bobbin diameter case 6: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Bobbin dia."); lcd.setFont(Term9x14); dtostrf(bobbinDiameter, 4, 2, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(15, 2, buf); break; //------------------------------- //MOVEMENT OF THE MAIN SHAFT (Button or rotary) case 7: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Main shaft"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", mainshaftStepper.currentPosition()); //the decimal value of the variable is in the buf lcd.printStr(15, 2, buf); break; //------------------------------- //MOVEMENT OF THE FEEDER SHAFT (Button or rotary) case 8: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Feeder shaft"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", feederStepper.currentPosition()); lcd.printStr(15, 2, buf); break; //------------------------------- //START-STOP auto winding case 9: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Auto winding"); lcd.printStr(12, 1, "Layers left:"); lcd.printStr(12, 2, "0"); lcd.printStr(12, 3, "Turns done: "); lcd.printStr(12, 4, "0"); lcd.printStr(0, 5, "Length: "); break; } menuChanged = false; //next loop() iteration will not enter again } void updateValue() { switch (menuCounter) //this checks the value of the counter (0, 1, 2 or 3) { //Max speed menu case 0: lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); snprintf(buf, 8, "%d", stepperMaxSpeed); lcd.printStr(15, 2, buf); break; //------------------------------- //Acceleration menu - In this version, the acceleration does not play any role case 1: mainshaftStepper.setAcceleration(stepperAcceleration); //both shafts accelerate in the same way feederStepper.setAcceleration(stepperAcceleration); //it has to be tested lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); snprintf(buf, 8, "%d", stepperAcceleration); lcd.printStr(15, 2, buf); break; //------------------------------- //Number of turns case 2: lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); snprintf(buf, 8, "%d", numberOfTurns); lcd.printStr(15, 2, buf); break; //------------------------------- //Wire diameter case 3: lcd.setFont(Term9x14); //Command values lcd.printStr(15, 2, " "); dtostrf(wireDiameter, 4, 2, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(15, 2, buf); break; //------------------------------- //Coil pitch case 4: lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); dtostrf(coilPitch, 4, 2, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(15, 2, buf); break; //------------------------------- //Bobbin width case 5: lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); dtostrf(bobbinWidth, 4, 2, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(15, 2, buf); break; //------------------------------- //Bobbin diameter case 6: lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); dtostrf(bobbinDiameter, 4, 2, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(15, 2, buf); break; //------------------------------- case 7: lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); snprintf(buf, 8, "%d", mainshaftStepper.currentPosition()); lcd.printStr(15, 2, buf); break; //------------------------------- case 8: lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); snprintf(buf, 8, "%d", feederStepper.currentPosition()); lcd.printStr(15, 2, buf); break; //------------------------------- //Auto Winding case 9: lcd.setFont(c64enh); //line 2 - Layers left lcd.printStr(12, 2, " "); snprintf(buf, 8, "%d", layersToDo); lcd.printStr(12, 2, buf); //line 4 - Turns left lcd.printStr(12, 4, " "); dtostrf(numberOfTurnsDone, 4, 1, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(12, 4, buf); //line 5 - consumed wire length lcd.printStr(45, 5, " "); dtostrf(totalWireLength, 4, 1, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(45, 5, buf); break; } valueChanged = false; //next loop() iteration will not enter again } void updateSelection() { lcd.setFont(Term9x14); //Large font size for the cursor (same as of the values) if (stepperMaxSpeed_Selected == true) { snprintf(buf, 8, "%s", ">"); //print the ">" as the cursor, the ">" indicates the selection/activation lcd.printStr(0, 2, buf); //put it on the display: 2nd line 0 pixel return; } else if (stepperAcceleration_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); return; } else if (numberOfTurns_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); return; } else if (wireDiameter_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); return; } else if (coilPitch_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); return; } else if (bobbinWidth_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); return; } else if (bobbinDiameter == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); return; } else if (mainShaftStepper_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); return; } else if (feederShaftStepper_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); return; } else if (winding_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); return; } else //If a menu item is unselected, this else branch is performed and the ">" symbol is erased { snprintf(buf, 8, "%s", " "); lcd.printStr(0, 2, buf); return; } } void UpButtonPressed() { if (digitalRead(UpButton) == 0) //1 or 0, depends on your wiring also! { if (millis() - previousInterrupt > 300) //debounce, sort of. If another click comes within 300 ms, we don't care about it { //Increase values - buttons increase/decrease the input values with larger steps than the rotary encoder if (stepperMaxSpeed_Selected == true) { stepperMaxSpeed = stepperMaxSpeed + 40; } else if (stepperAcceleration_Selected == true) { stepperAcceleration = stepperAcceleration + 40; } else if (numberOfTurns_Selected == true) { numberOfTurns = numberOfTurns + 10; } else if (bobbinWidth_Selected == true) { bobbinWidth = bobbinWidth + 1.0; } else if (bobbinDiameter_Selected == true) { bobbinDiameter = bobbinDiameter + 1; } else if (winding_Selected == true) { //reset positions if there was a previous movement - this will ease things for the moveTo() function (and for us) mainshaftStepper.setCurrentPosition(0); feederStepper.setCurrentPosition(0); //Recalculate the speeds for the feeder calculateSpeeds(); //Define the amount of steps to move for both motors mainshaftStepper.moveTo(mainShaftTotalSteps - (2 * MainMicroStepping)); // instruct the main shaft to move the total steps (-2 turns) feederStepper.moveTo(stepsPerLayer_feeder - (2 * wireDiameter * (feederMicroStepping / 5.0)) ); // instruct the feeder to move 1 bobbin width distance //We also subtract 2 turns equivalent of distance from the total steps because those are done manually when we start the coil //Set speeds feederStepper.setMaxSpeed(feederSpeed); feederStepper.setSpeed(feederSpeed); // mainshaftStepper.setMaxSpeed(stepperMaxSpeed); mainshaftStepper.setSpeed(stepperMaxSpeed); windingIsRunning = true; } //------------------------------------------------------------- //Button movements - you can place them outside of the if(), it will be faster else if (mainShaftStepper_Selected == true) { stepperButtonSteps = mainshaftStepper.currentPosition() + 160; //increase the target position by 1 stepsize: 0.1 turn mainshaftStepper.setMaxSpeed(400); mainshaftStepper.moveTo(stepperButtonSteps); //move the main shaft } else if (feederShaftStepper_Selected == true) { stepperButtonSteps = feederStepper.currentPosition() + 32; //increase the target position by 1 stepsize: 0.1 mm feederStepper.setMaxSpeed(400); feederStepper.moveTo(stepperButtonSteps); //move the secondary shaft } valueChanged = true; //display has to be updated previousInterrupt = millis(); //update the value so the counting from 300 ms is started over from 0 } } } void DownButtonPressed() { if (digitalRead(DownButton) == 0) //1 or 0, depends on your wiring also! { if (millis() - previousInterrupt > 300) { //Decrease values if (stepperMaxSpeed_Selected == true) //add 10 to the number { if (stepperMaxSpeed > 40) { stepperMaxSpeed = stepperMaxSpeed - 40; } else { //do nothing } } else if (stepperAcceleration_Selected == true) { if (stepperAcceleration > 40) { stepperAcceleration = stepperAcceleration - 40; } else { //do nothing } } else if (numberOfTurns_Selected == true) { if (numberOfTurns > 10) { numberOfTurns = numberOfTurns - 10; } else { //do nothing } } else if (bobbinWidth_Selected == true) { if (bobbinWidth > 1.0) { bobbinWidth = bobbinWidth - 1.0; } else { //do nothing } } else if (bobbinDiameter_Selected == true) { if (bobbinDiameter > 1.0) { bobbinDiameter = bobbinDiameter - 1.0; } else { //do nothing } } else if (winding_Selected == true) { windingIsRunning = false; mainshaftStepper.stop(); feederStepper.stop(); } //------------------------------------------------------------- //Button movements else if (mainShaftStepper_Selected == true) { stepperButtonSteps = mainshaftStepper.currentPosition() - 160; //increase the target position by 1 stepsize mainshaftStepper.setMaxSpeed(400); //we must set a speed because the winding restarts it mainshaftStepper.moveTo(stepperButtonSteps); //move the main shaft } else if (feederShaftStepper_Selected == true) { stepperButtonSteps = feederStepper.currentPosition() - 32; //increase the target position by 1 stepsize feederStepper.setMaxSpeed(400); feederStepper.moveTo(stepperButtonSteps); //move the secondary shaft } valueChanged = true; //display has to be updated previousInterrupt = millis(); //update the value so the counting from 300 ms is started over from 0 } } } void calculateSpeeds() { //The feeder stepper should move 1 wire diameter for each turn of the main shaft mainShaftTotalSteps = numberOfTurns * MainMicroStepping; //calculate the total amount of steps for the main shaft //Example: MSTS = 160 turns * 1600 steps/turn = 256000 steps total //Serial.print("MainshaftTotalSteps: "); //Serial.println(mainShaftTotalSteps); //OK turnsPerLayer = bobbinWidth / wireDiameter; //How many wires can be fitted next to each other within the bobbin width //Example: TPL = 14 mm / 0.35 mm = 40 turns per layer //Serial.print("TurnsPerLayer: "); //Serial.println(turnsPerLayer); //OK numberOfLayers = numberOfTurns / turnsPerLayer; //total number of turns / turns per layer //Example: NOL = 160 turns / 40 turns per layer = 4 layers //Notice: Always round the result UP (ceiling) //Serial.print("NumberOfLayers: "); //Serial.println(numberOfLayers); //OK layersToDo = numberOfLayers; //layersToDo stores a "dynamic" value //Serial.print("Layers to do: "); //Serial.println(layersToDo); //OK feederShaftTotalSteps = numberOfLayers * bobbinWidth * (feederMicroStepping / 5); // division by 5 = this much step results in 1 mm movement of the feeder (ballscrew) //Example2: FSTL = 4 * 14 mm * 1600 steps per turn / 5 = 17920 //Serial.print("feederShaftTotalSteps: "); //Serial.println(feederShaftTotalSteps); //OK stepsPerLayer_feeder = feederShaftTotalSteps / (numberOfLayers); //Serial.print("feederStepsPerLayer: "); //Serial.println(stepsPerLayer_feeder); //Example: SPL_f = 17920 steps / 4 layers = 4480 steps/layer //OK feederSpeed = ((float)stepperMaxSpeed / (float)MainMicroStepping) * (float)feederMicroStepping * wireDiameter / 5.0 ; //division by 5 is related to the pitch of the ballscrew //Example: FS = (1600/1600) * 1600 * 0.35 mm / 5 = 1 * 320 * 0.35 mm = 112 steps / second //Serial.print("feederSpeed: "); //Serial.println(feederSpeed); //OK //Serial.println("Calculation finished."); //Serial.println("---------------------"); directionMultiplier = 1; //reset direction multiplier to 1. totalWireLength = 0; //reset wire length in case there was a winding without device reset } void checkSteps() { if (windingIsRunning == true) { if (feederStepper.distanceToGo() == 0) //if feeder finished a layer { //Serial.print("Main stepper position: "); //Serial.println(mainshaftStepper.currentPosition()); if (layersToDo != 0) //if it was not the last layer { //Serial.print("Layers to do: "); //Serial.println(layersToDo); winding(); updateValue(); //at each call of the winding() function we update the layers and the turns } } //The main issue is to sync the two motors together so they accelerate and move in a synced fashion if (mainshaftStepper.distanceToGo() == 0) //if the main shaft finished { windingIsRunning = false; //In the very last step, we add the final length of the last layer totalWireLength += ((float)(numberOfTurns - turnsPerLayer * (numberOfLayers - 1)) * 6.2832 * ((bobbinDiameter/2) + wireDiameter / 2 + ((float)(numberOfLayers - layersToDo) * 1.732 * (wireDiameter/2) ))) / (float)1000; //sqrt(3) = 1.732 //1000 division -> mm to m conversion updateValue(); } } else { //skip everything } } void winding() { //After we switched directions, we can add the full layer's length totalWireLength += ((float)turnsPerLayer * 6.2832 * ((bobbinDiameter/2) + (wireDiameter / 2) + ( (float)(numberOfLayers - layersToDo) * 1.732 * (wireDiameter/2) ))) / (float)1000; //Serial.print("Temp length: "); //Serial.println(totalWireLength); layersToDo--; //When the code enters this part, 1 layer is already laid. So we decrease this variable and then decide what to do next. directionMultiplier = -1 * directionMultiplier; //We flip the direction each time we enter. (-1 * 1 = -1, then -1 * -1 = 1...etc.) if (layersToDo > 1) //if there are more than 1 layers have to be done, we proceed as usual { //Serial.println("Layers to do > 1"); //Serial.print("Layers to do: "); //Serial.println(layersToDo); //Serial.print("stepsPerLayer_feeder"); //Serial.println(stepsPerLayer_feeder); //Serial.print("Adjusted steps"); //Serial.println(directionMultiplier * (stepsPerLayer_feeder - 2 * wireDiameter * (feederMicroStepping / 5.0))); feederStepper.move(directionMultiplier * (stepsPerLayer_feeder - (2 * wireDiameter * (feederMicroStepping / 5.0))) ); //Subtracting 2 wire diameter equivalent of distance because we will not go to the edge of the bobbin (the 2x multiplier is somewhat experimental) feederStepper.setSpeed(feederSpeed); } else if (layersToDo == 1) //if there is only 1 layer (last one) is left, we also have to consider how much turns we need on the last layer { //Serial.println("Layers to do == 1"); feederStepper.move(directionMultiplier * (wireDiameter * (feederMicroStepping / 5.0) * (numberOfTurns - (numberOfLayers - 1) * turnsPerLayer ))); //EX: 1 * 0.35 * (1600/5) * (135 - (4-1) * 40 = 0.35 * 320 * (135-120) = 1680 steps // feederStepper.setSpeed(feederSpeed); } else //this is when layersToDo == 0. We are finished, we do not move anywhere anymore { //Serial.println("Layers to do == 0 - feeder has finished"); } }