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!

PCB, manufactured by PCBWay. Click on the image to order it from PCBWay!

Populated panel. Looks neat!

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");
  }
}

Previous
Previous

SZBK07 DC-DC converter with fine/coarse potentiometers

Next
Next

Building a coil winder [Part 7] - New electronics