Building a coil winder [Part 8] - Finished first iteration
In this video I show you the new version (v1.1) of the PCB which now seems to be flawless. I go through the most important parts of the circuit and the mechanism, and then I finally show you how set up the Arduino IDE to upload my source code to the STM32F401CCu6 microcontroller using an ST-link. I also explain the most important parts of the code. This is basically the first, more or less properly working version of the code, the circuit and the mechanism, so I call this as the "version I".
By clicking the picture below, you can directly order the PCB using PCBWay’s services!
Further relevant links:
Useful literature on orthocyclic winding from Philips - A very nice technical report about winding coils
Video on the mathematics of the winding - The video also shows an earlier iteration of the source code, but use the code on this page, as this one is more up to date!
STM32CubeProgrammer - The software which is needed to use the ST-LINK
Arduino IDE Board Manager URLs:
http://dan.drown.org/stm32duino/package_STM32duino_index.json
https://github.com/stm32duino/BoardManagerFiles/raw/master/package_stmicroelectronics_index.json
Most relevant parts
STM32 source code
//Last update: 2021-09-25 //Migrated the project to a STM32F401CCU6x //Board: Generic STM32F4 series //Board Part Number: BlackPill F401CC //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 //Reset and DC #define N5110_RST PA1 #define N5110_DC PA4 //SPI #define N5110_CS PA0 #define N5110_DIN PA7 #define N5110_CLK PA5 //Nokia 5110-related libraries #include "math.h" #include "N5110_SPI.h" #if USESPI==1 #include <SPI.h> #endif N5110_SPI lcd(N5110_RST, N5110_CS, N5110_DC, N5110_DIN, N5110_CLK); // RST,CS,DC,DIN,CLK #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 8; direction 9 AccelStepper mainshaftStepper(1, PA10, PA11); // steps 10; Direction 11 //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 = PB5; //Up button which is also a start button const int DownButton = PB4; //Down button which is also a stop button //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; //Rotary button timing unsigned long ButtonTime1; //UP/DOWN Button timing //Menu-related variables //Values int RotaryButtonValue; //Pressed or not pressed rotary switch volatile int menuCounter = 0; //this is used for counting inside the main menu (menu item number) // volatile int stepperMaxSpeed = 1600; //for setMaxSpeed - steps (1 turn/sec or 60 rpm) 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 float numberOfTurns = 130; //number of turns of the coil - must be an integer float numberOfTurnsDone = 0; //#of turns done during winding volatile float wireDiameter = 0.350; //diameter of the wire to be wound / 0.35 volatile int turnsPerLayer; //Number of turns within a layer - must be an integer float layersToDo; //Number that stores the layers to be done float numberOfLayers; //number of layers - must be an integer volatile float coilPitch = 1.0; //distance between two wires in the winding (distance between centers) - TBD volatile float bobbinWidth = 14.0; //width of the bobbin (or the coil) - unit is in mm / 14 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 //It is very important to match the hardware settings (i.e. jumpers) with these numbers! 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; //Lets the code know if the winding is in progress bool autoWinding_Selected = false; //Autowinding menu bool autoWinding_enabled = true; //Lets the code know if it should wind automatically or it should stop at the end of each layers char buf[15]; //buffer for printing on the LCD (strings) holds 25 characters char str_temp[7]; //for printing floats int directionMultiplier = 1; //variable used to flip the direction of the feeder (+/- 1) float mainStepperTimer = 0; //timer for updating the display int blinkTimer = 0; //timer for blinking the LED on the microcontroller bool blinkStatus = false; //programming via ST-LINK: Press BOOT button and hold it, press RST and release it, release BOOT. void setup() { //Definition of the pins //Rotary encoder pinMode(PB9, INPUT); //CLK pinMode(PB8, INPUT); //DT pinMode(PB7, INPUT); //SW //Buttons pinMode(PB5, INPUT); //UP pinMode(PB4, INPUT); //DOWN pinMode(PC13, OUTPUT); //MCU onboard LED //Store states of the encoder pins CLKPrevious = digitalRead(RotaryCLK); DTPrevious = digitalRead(RotaryDT); //Rotary encoder interrupt for the CLK pin 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); //Starting up the Nokia 5110 LCD lcd.init(); //initialize LCD delay(300); lcd.clrScr(); //clear the whole display delay(300); lcd.setContrast(57); lcd.setBias(4); lcd.displayMode(PCD8544_DISPLAYNORMAL); delay(300); lcd.setFont(c64enh); printLCD(); //print the welcome message //Serial.begin(9600); //All serial communication can be turned off if it is not needed //This is a "startup blinking" to indicate that the MCU works properly - you can remove it for(int i = 0; i < 10; i++) { digitalWrite(PC13, HIGH); delay(100); digitalWrite(PC13, LOW); delay(100); } } void loop() { //blink the onboard LED on the MCU every 1 s -> it indicates that the loop() works - you can remove it if(millis() - blinkTimer > 1000) { digitalWrite(PC13, blinkStatus); blinkStatus = !blinkStatus; blinkTimer = millis(); } if (menuChanged == true) { updateMenuPosition(); } if (updateValueSelection == true) { updateSelection(); updateValueSelection = false; } if (valueChanged == true) { updateValue(); valueChanged = false; } //Polling 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) { if(autoWinding_enabled == true) //auto winding => mainShaftTotalSteps is the global steps { numberOfTurnsDone = (mainShaftTotalSteps - mainshaftStepper.distanceToGo()) / (float)MainMicroStepping; //Example: (16000 - 4000) / 400 = 12000 / 400 = 30 } else { numberOfTurnsDone = ((2 * MainMicroStepping) + mainshaftStepper.currentPosition()) / (float)MainMicroStepping; //Here, the mainShaftTotalSteps is reset at the end of every layer. So, we store the steps done so far and add to the total //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 the value of the variable if (digitalRead(RotaryDT) == CLKNow) { stepperMaxSpeed++ ; //1 step size increment } else //otherwise, we decrease { 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 the recent CLK state } //---------------------------------------------------------------------------- //Stepper acceleration else if (stepperAcceleration_Selected == true) { CLKNow = digitalRead(RotaryCLK); if (CLKNow != CLKPrevious && CLKNow == 0) { if (digitalRead(RotaryDT) == CLKNow) { stepperAcceleration++; //1 step size increment } else { stepperAcceleration--; //1 step size decrement } valueChanged = true; } CLKPrevious = CLKNow; } //---------------------------------------------------------------------------- //Number of turns else if (numberOfTurns_Selected == true) { CLKNow = digitalRead(RotaryCLK); if (CLKNow != CLKPrevious && CLKNow == 0) { if (digitalRead(RotaryDT) == CLKNow) { numberOfTurns++; //1 step size increment (only integers are allowed (/make sense)) } 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; } //---------------------------------------------------------------------------- //Wire diameter else if (wireDiameter_Selected == true) { CLKNow = digitalRead(RotaryCLK); if (CLKNow != CLKPrevious && CLKNow == 0) { if (digitalRead(RotaryDT) == CLKNow) { wireDiameter = wireDiameter + 0.001; //0.001 step size increment (button allows larger jumps) } else { if (wireDiameter == 0.001) //when it is = 0.01 { // Don't decrease further, it should be at least 0.01 } else { wireDiameter = wireDiameter - 0.001; //0.001 step size decrement } } valueChanged = true; } CLKPrevious = CLKNow; } //---------------------------------------------------------------------------- //Coil pitch (the pitch function is not yet implemented! - 2021-09-20) else if (coilPitch_Selected == true) { CLKNow = digitalRead(RotaryCLK); if (CLKNow != CLKPrevious && CLKNow == 0) { 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; } //---------------------------------------------------------------------------- //Bobbin width else if (bobbinWidth_Selected == true) { CLKNow = digitalRead(RotaryCLK); if (CLKNow != CLKPrevious && CLKNow == 0) { 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; } //---------------------------------------------------------------------------- //Bobbin diameter - technically, the inner diameter of the coil else if (autoWinding_Selected == true) { CLKNow = digitalRead(RotaryCLK); if (CLKNow != CLKPrevious && CLKNow == 0) { if (digitalRead(RotaryDT) == CLKNow) { //ON autoWinding_enabled = true; } else { //OFF autoWinding_enabled = false; } valueChanged = true; } CLKPrevious = CLKNow; } //---------------------------------------------------------------------------- //Main shaft manual stepping else if (mainShaftStepper_Selected == true) { CLKNow = digitalRead(RotaryCLK); if (CLKNow != CLKPrevious && CLKNow == 0) { 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; 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); if (CLKNow != CLKPrevious && CLKNow == 0) { 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; feederStepper.moveTo(feederShaftManualSteps); } //------------------------------------------------------------------------------------------------------------------------------------- else if (feederShaftStepper_Selected == true) { CLKNow = digitalRead(RotaryCLK); if (CLKNow != CLKPrevious && CLKNow == 0) { 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; feederStepper.moveTo(feederShaftManualSteps); } //---------------------------------------------------------------------------- // else if (winding_Selected == true) { //do nothing } else //MENU COUNTER---------------------------------------------------------------------------- { CLKNow = digitalRead(RotaryCLK); if (CLKNow != CLKPrevious && CLKNow == 0) { if (digitalRead(RotaryDT) == CLKNow) { if (menuCounter < 10) //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 = 10; //9 comes after 0 when we decrease the numbers } menuChanged = true; } } CLKPrevious = CLKNow; } } 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) //1 second has to elapse between two button clicks to enter below ("debounce") { switch (menuCounter) //we check the menu position { 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: autoWinding_Selected = !autoWinding_Selected; break; // case 10: 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); dtostrf(numberOfTurns, 4, 0, str_temp); //dtostrf is used/necessary for floats snprintf(buf, 8, "%s", str_temp); 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, 5, 3, 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; //------------------------------- //ENABLE/DISABLE auto winding case 9: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "AUTO winding"); lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); if(autoWinding_enabled == true) { snprintf(buf, 8, "%s", "ON"); } else { snprintf(buf, 8, "%s", "OFF"); } lcd.printStr(15, 2, buf); break; // //START-STOP auto winding case 10: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Coil 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); feederStepper.setAcceleration(stepperAcceleration); 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, " "); dtostrf(numberOfTurns, 4, 0, str_temp); snprintf(buf, 8, "%s", str_temp); lcd.printStr(15, 2, buf); break; //------------------------------- //Wire diameter case 3: lcd.setFont(Term9x14); //Command values lcd.printStr(15, 2, " "); dtostrf(wireDiameter, 5, 3, 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; //------------------------------- //Main shaft stepping case 7: lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); snprintf(buf, 8, "%d", mainshaftStepper.currentPosition()); lcd.printStr(15, 2, buf); break; //------------------------------- //Feeder stepping case 8: lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); snprintf(buf, 8, "%d", feederStepper.currentPosition()); lcd.printStr(15, 2, buf); break; //------------------------------- case 9: //AUTO WINDING lcd.setFont(Term9x14); lcd.printStr(15, 2, " "); if(autoWinding_enabled == true) { snprintf(buf, 8, "%s", "ON"); } else { snprintf(buf, 8, "%s", "OFF"); } lcd.printStr(15, 2, buf); break; //Coil Winding case 10: lcd.setFont(c64enh); //line 2 - Layers left lcd.printStr(12, 2, " "); dtostrf(layersToDo, 4, 1, str_temp); snprintf(buf, 8, "%s", str_temp); 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 (autoWinding_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() - ButtonTime1 > 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 (wireDiameter_Selected == true) { wireDiameter = wireDiameter + 0.1; } else if (winding_Selected == true) { if(windingIsRunning == true) { //Do not let the button come here if the winding is already ongoing! } else { //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 - 1 layer equivalent for the feeder, because it is recalculated mainshaftStepper.moveTo(mainShaftTotalSteps - (2 * MainMicroStepping)); // instruct the main shaft to move the total steps (-2 turns) feederStepper.moveTo(feederShaftTotalSteps - (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 //1600/10 = 160 -> 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 ButtonTime1 = 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() - ButtonTime1 > 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 (wireDiameter_Selected == true) { if(wireDiameter > 0.1) { wireDiameter = wireDiameter - 0.1; } 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 //160 = 1600/10 = 0.1 turn. 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 ButtonTime1 = millis(); //update the value so the counting from 300 ms is started over from 0 } } } void calculateSpeeds() { 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 //The feeder stepper should move 1 wire diameter for each turn of the main shaft if(autoWinding_enabled == true) { //We let the main shaft run indefinitely and complete all the steps mainShaftTotalSteps = numberOfTurns * MainMicroStepping; //calculate the total amount of steps for the main shaft //Example: MSTS = 160 turns * 1600 steps/turn = 256000 steps total } else { mainShaftTotalSteps = turnsPerLayer * MainMicroStepping; //This has to be managed for the last layer! //Example: MSTS = 30 turns * 1600 steps/turn = 48000 } //Serial.print("Main shaft Total Steps - calculateSpeeds(): "); //Serial.println(mainShaftTotalSteps); //OK numberOfLayers = ceil((float)numberOfTurns / (float)turnsPerLayer); //total number of turns / turns per layer //Example: NOL = 160 turns / 40 turns per layer = 4 layers //Example: NOL = 50 turns / 40 turns per layer = 1.25 layers -> 2 layers //Notice: Always round the result UP (ceiling) //Serial.print("NumberOfLayers: "); //Serial.println(numberOfLayers); //Serial.print("numberOfTurns: "); //Serial.println(numberOfTurns); //OK layersToDo = numberOfLayers; //layersToDo stores a "dynamic" value //OK //FSTS is considered per layer! In every layer we calculate this value again, so it is enough to know the value for 1 layer if(numberOfLayers > 1) { feederShaftTotalSteps = bobbinWidth * (feederMicroStepping / 5); } else { feederShaftTotalSteps = wireDiameter * (numberOfTurns/numberOfLayers) * (feederMicroStepping / 5.0); } //Example = 0.35 * (50/2) * 320 = 2800 //Serial.print("feederShaftTotalSteps: "); //Serial.println(feederShaftTotalSteps); //Example: 0.5 mm * (50 turns / 5 layers) * 320 = 5*320 = 1600 //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 { if (layersToDo != 0) //if it was not the last layer { if(autoWinding_enabled == true) { winding(); //Let the feeder move automatically } else { while(mainshaftStepper.distanceToGo() != 0) { mainshaftStepper.runSpeedToPosition(); } while(digitalRead(UpButton) == 1) //as long as the button is NOT pressed we wait here { //The code below is performed after the button is finally pressed lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Coil winding"); lcd.printStr(12, 1, " "); lcd.printStr(12, 2, " "); lcd.printStr(12, 2, "Paused!"); lcd.printStr(12, 3, " "); lcd.printStr(12, 4, " "); lcd.printStr(12, 4, "Press UP to"); lcd.printStr(0, 5, " "); lcd.printStr(0, 5, "continue! "); } winding(); //continue the winding. if (layersToDo == 0) //layers are finished { updateMenuPosition(); updateValue(); //at each call of the winding() function we update the layers and the turns return; } else if(layersToDo == 1) //last layer { mainshaftStepper.move((numberOfTurns - ((numberOfLayers - 1) * turnsPerLayer)) * MainMicroStepping); //final layer can be an incomplete layer (e.g. 10 turns only instead of 40), so we calculate the turns to do //Set speeds again mainshaftStepper.setMaxSpeed(stepperMaxSpeed); mainshaftStepper.setSpeed(stepperMaxSpeed); } else { mainshaftStepper.move(mainShaftTotalSteps); //Any layer except the last one is a full layer, so we move a full layer equivalent steps (/turns) //Set speeds again mainshaftStepper.setMaxSpeed(stepperMaxSpeed); mainshaftStepper.setSpeed(stepperMaxSpeed); } } updateMenuPosition(); 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 && layersToDo == 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 numberOfTurnsDone = numberOfTurns; // A bit of a cheating, but we update the final turns with the originally entered value 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 { feederStepper.move(directionMultiplier * (feederShaftTotalSteps - (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 { 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"); } }