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

In this video I talk about the most recent updates and modifications of this device. I replaced the feeder mechanism with a prebuilt linear actuator, I built a new feeder and I wrote a few new lines in the code. The feeder now work more or less well, I just need to make a few little adjustments to make it perfect. In order to understand the whole source code, especially how the display, the rotary encoder and the buttons are handled, please refer to my other video where I built a control panel for stepper motors. I basically recycled that code and added some extra lines that take care of the winding.



Control Panel Schematics

This is basically the same wiring schematics that I used for my earlier stepper motor control panel.

This is basically the same wiring schematics that I used for my earlier stepper motor control panel.



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


Feeder 3d file

3d printed feeder. The material is PLA.

3d printed feeder. The material is PLA.

Feeder with the felt disc tensioners and grooved metal wheels. A 0.8 mm 3d printing nozzle was also used. The copper wire indicates the direction of the wire in the feeder/tensioner.

Feeder with the felt disc tensioners and grooved metal wheels. A 0.8 mm 3d printing nozzle was also used. The copper wire indicates the direction of the wire in the feeder/tensioner.


Previous
Previous

Building a coil winder [Part 5] - Coding and math

Next
Next

Arduino-based DIY camera slider