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


Notes and drawings


Previous
Previous

SZBK07 300 W DC-DC Converter quiescent current

Next
Next

Building a coil winder [Part 4] - Updated feeder mechanism