Skip to content

11. Output Devices

This week I worked on defining my final project idea and started to getting used to the documentation process.

All final code files can be found here

Stepper Motor

I used a NEMA 17HS4401S stepper motor and a L298N motor driver.

Arduino Test

I first did a test with an arduino before milling a board to make sure I can get it working.

First, I tried to directly manipulate the stepper motor without the arduino library since I remember trying to use the stepper motor library and failing a couple years ago. I used this site with the following diagram to get the phases of the stepper motor.

To wire the stepper motor correctly, I used diagrams from these sites: https://blog.moonsindustries.com/2018/12/19/how-to-connect-stepper-motor-to-your-drive/, and https://datasheetspdf.com/pdf/1310364/Handson/17HS4401S/1.

I connected the black, green, red, and blue wires of the stepper motor to outputs 1, 2, 3, and 4 of the motor driver respectively. I connected pins 0, 1, 2, and 3 of the arduino mega into inputs 1, 2, 3, and 4 of the motor driver respectively. Lastly, I connected a 5 volt 10 amp power source into the power of the motor driver and shared a ground with the arduino.

I uploaded the following code controlling the stepper motor directly without any libraries, but it didn’t work and just continuously did 1 step forward then 1 step back.

void setup() {
  // put your setup code here, to run once:
  for (int i = 0; i < 4; ++i) {
    pinMode(i, OUTPUT);
  }
  Serial.begin(9600);

}

int stepPos = 0;

void loop() {
  // put your main code here, to run repeatedly:
  for (int i = 0; i < 50; ++i) {

    stepForward(stepPos, 0, 1, 2, 3);
  }
  delay(1000);
  for (int i = 0; i < 50; ++i) {
    stepBack(stepPos, 0, 1, 2, 3);
    //Serial.println(stepPos);
  }
  delay(1000);
}


void stepForward(int& stepPos, int pin1, int pin2, int pin3, int pin4) {
  stepPos = (stepPos + 1) % 4;

  switch(stepPos) {
    case 0:
    digitalWrite(pin1, HIGH);
    digitalWrite(pin2, LOW);
    digitalWrite(pin3, HIGH);
    digitalWrite(pin4, LOW);
    break;
    case 1:
    digitalWrite(pin1, LOW);
    digitalWrite(pin2, HIGH);
    digitalWrite(pin3, HIGH);
    digitalWrite(pin4, LOW);
    break;
    case 2:
    digitalWrite(pin1, LOW);
    digitalWrite(pin2, HIGH);
    digitalWrite(pin3, LOW);
    digitalWrite(pin4, HIGH);
    break;
    case 3:
    digitalWrite(pin1, HIGH);
    digitalWrite(pin2, LOW);
    digitalWrite(pin3, LOW);
    digitalWrite(pin4, HIGH);
    break;
  }
  delay(100);
}

void stepBack(int& stepPos, int pin1, int pin2, int pin3, int pin4) {
  --stepPos;
  if (stepPos < 0) stepPos += 4;

  switch(stepPos) {
    case 0:
    digitalWrite(pin1, HIGH);
    digitalWrite(pin2, LOW);
    digitalWrite(pin3, HIGH);
    digitalWrite(pin4, LOW);
    break;
    case 1:
    digitalWrite(pin1, LOW);
    digitalWrite(pin2, HIGH);
    digitalWrite(pin3, HIGH);
    digitalWrite(pin4, LOW);
    break;
    case 2:
    digitalWrite(pin1, LOW);
    digitalWrite(pin2, HIGH);
    digitalWrite(pin3, LOW);
    digitalWrite(pin4, HIGH);
    break;
    case 3:
    digitalWrite(pin1, HIGH);
    digitalWrite(pin2, LOW);
    digitalWrite(pin3, LOW);
    digitalWrite(pin4, HIGH);
    break;
  }
  delay(100);
}

I then tried to use the library with the following code:

#include <Stepper.h>

Stepper myStepper(200, 0, 1, 2, 3);

void setup() {
  // put your setup code here, to run once:

  myStepper.setSpeed(60);
}

void loop() {
  // put your main code here, to run repeatedly:
  myStepper.step(200);
  delay(1000);
  myStepper.step(-200);
  delay(1000);
}

After uploading, it did work.

Upon getting the code with the library working, I went back to writing to the stepper motor without the library, and I tried changing the pins I used to PWM pins, and it worked.

Attiny1614

Kicad Design

I started off with the schematic, and since I just needed to connect it to the motor driver with jumper wires, I just needed to have some header pins. I made a section of 3 pins for the programmer as well as a section of 3 connected pins for sharing a ground. To allow this board to be used more for other things, I added additional pins so that I could use this board for other things and for testing. To prevent the electrical rules checker from giving errors, I added power flags and made all the unused pins of the 1614 disconnected and invisible in the symbols editor.

I then loaded it into the PCB editor and ended up with the following design after rounding the edges of the edge cut.

After exporting the gerbers, I pulled them up in the gerber view to make sure everything looked right.

I then milled it and soldered the components on it.

I uploaded the following code written with baremetal to control the stepper motor. I used function pointers as parameters since the stepper was using pins from different ports, so I couldn’t just pass a register and different shifts for the pins.

#define STEPPERARGS stepPos, &pin1out, &pin1clr, &pin2out, &pin2clr, &pin3out, &pin3clr, &pin4out, &pin4clr

int stepPos = 0;

void setup() {
  // put your setup code here, to run once:
  PORTA.DIRSET |= 1 << 4;
  PORTA.DIRSET |= 1 << 5;
  PORTA.DIRSET |= 1 << 3;
  PORTB.DIRSET |= 1 << 2;
}

void loop() {
  // put your main code here, to run repeatedly:
  for (int i = 0; i < 200; ++i) {
    stepCW(STEPPERARGS);
  }
  delay(1000);
  for (int i = 0; i < 200; ++i) {
    stepCCW(STEPPERARGS);
  }
  delay(1000);
}

void pin1out() {
  PORTA.OUT |= 1 << 4;
}

void pin1clr() {
  PORTA.OUT &= ~(1 << 4);
}

void pin2out() {
  PORTA.OUT |= 1 << 5;
}

void pin2clr() {
  PORTA.OUT &= ~(1 << 5);
}

void pin3out() {
  PORTB.OUT |= 1 << 2;
}

void pin3clr() {
  PORTB.OUT &= ~(1 << 2);
}

void pin4out() {
  PORTA.OUT |= 1 << 3;
}

void pin4clr() {
  PORTA.OUT &= ~(1 << 3);
}

void stepCW(int& stepPos, void (*out1)(), void (*clear1)(), void (*out2)(), void (*clear2)(), void (*out3)(), void (*clear3)(), void (*out4)(), void (*clear4)()) {
  stepPos = (stepPos + 1) % 4;
  switch (stepPos) {
    case 0:
    out1();
    clear2();
    out3();
    clear4();
    break;
    case 1:
    clear1();
    out2();
    out3();
    clear4();
    break;
    case 2:
    clear1();
    out2();
    clear3();
    out4();
    break;
    case 3:
    out1();
    clear2();
    clear3();
    out4();
    break;
  }
  delay(10);
}

void stepCCW(int& stepPos, void (*out1)(), void (*clear1)(), void (*out2)(), void (*clear2)(), void (*out3)(), void (*clear3)(), void (*out4)(), void (*clear4)()) {
  --stepPos;
  if (stepPos < 0) stepPos = 3;
  switch (stepPos) {
    case 0:
    out1();
    clear2();
    out3();
    clear4();
    break;
    case 1:
    clear1();
    out2();
    out3();
    clear4();
    break;
    case 2:
    clear1();
    out2();
    clear3();
    out4();
    break;
    case 3:
    out1();
    clear2();
    clear3();
    out4();
    break;
  }
  delay(10);
}

I had it do a full revolution back and forth, and it worked.

My teacher, Mr. Durrett, recommended that I put this all in a class instead to make the code make more sense and eliminate all the function pointers, and so I ended up with the following code.

class Stepper {
  private:
  int pin1shift, pin2shift, pin3shift, pin4shift;
  volatile byte* reg1;
  volatile byte* reg2;
  volatile byte* reg3;
  volatile byte* reg4;
  int stepPos;
  int delayTime;

  public:
  Stepper(volatile byte* r1, int p1, volatile byte* r2, int p2, volatile byte* r3, int p3, volatile byte* r4, int p4, int del): pin1shift(p1), reg1(r1), pin2shift(p2), reg2(r2), pin3shift(p3), reg3(r3), pin4shift(p4), reg4(r4), delayTime(del) {
    stepPos = 0;
  }

  private:
  void out1() {
    *reg1 |= 1 << pin1shift;
  }

  void clear1() {
    *reg1 &= ~(1 << pin1shift);
  }

  void out2() {
    *reg2 |= 1 << pin2shift;
  }

  void clear2() {
    *reg2 &= ~(1 << pin2shift);
  }

  void out3() {
    *reg3 |= 1 << pin3shift;
  }

  void clear3() {
    *reg3 &= ~(1 << pin3shift);
  }

  void out4() {
    *reg4 |= 1 << pin4shift;
  }

  void clear4() {
    *reg4 &= ~(1 << pin4shift);
  }

  void runStep() {
    switch (stepPos) {
      case 0:
      out1();
      clear2();
      out3();
      clear4();
      break;
      case 1:
      clear1();
      out2();
      out3();
      clear4();
      break;
      case 2:
      clear1();
      out2();
      clear3();
      out4();
      break;
      case 3:
      out1();
      clear2();
      clear3();
      out4();
      break;
    }
    delay(delayTime);
  }

  public:
  void stepCW(int numSteps) {
    for (int i = 0; i < numSteps; ++i) {
      stepPos = (stepPos + 1) % 4;
      runStep();
    }
  }

  void stepCCW(int numSteps) {
    for (int i = 0; i < numSteps; ++i) {
      --stepPos;
      if (stepPos < 0) stepPos = 3;
      runStep();
    }
  }
};

void setup() {
  // put your setup code here, to run once:
  PORTA.DIRSET |= 1 << 4;
  PORTA.DIRSET |= 1 << 5;
  PORTA.DIRSET |= 1 << 3;
  PORTB.DIRSET |= 1 << 2;
}

Stepper myStepper(&PORTA.OUT, 4, &PORTA.OUT, 5, &PORTB.OUT, 2, &PORTA.OUT, 3, 10);

void loop() {
  // put your main code here, to run repeatedly:
  myStepper.stepCW(50);
  delay(1000);
  myStepper.stepCCW(50);
  delay(1000);
}

To further optimize the code, I changed the volatile byte pointers to volatile byte references so that the code wouldn’t have to go through the step of dereferencing the pointer.

class Stepper {
  private:
  int pin1shift, pin2shift, pin3shift, pin4shift;
  volatile byte& reg1;
  volatile byte& reg2;
  volatile byte& reg3;
  volatile byte& reg4;
  int stepPos;
  int delayTime;

  public:
  Stepper(volatile byte& r1, int p1, volatile byte& r2, int p2, volatile byte& r3, int p3, volatile byte& r4, int p4, int del): pin1shift(p1), reg1(r1), pin2shift(p2), reg2(r2), pin3shift(p3), reg3(r3), pin4shift(p4), reg4(r4), delayTime(del) {
    stepPos = 0;
  }

  private:
  void out1() {
    reg1 |= 1 << pin1shift;
  }

  void clear1() {
    reg1 &= ~(1 << pin1shift);
  }

  void out2() {
    reg2 |= 1 << pin2shift;
  }

  void clear2() {
    reg2 &= ~(1 << pin2shift);
  }

  void out3() {
    reg3 |= 1 << pin3shift;
  }

  void clear3() {
    reg3 &= ~(1 << pin3shift);
  }

  void out4() {
    reg4 |= 1 << pin4shift;
  }

  void clear4() {
    reg4 &= ~(1 << pin4shift);
  }

  void runStep() {
    switch (stepPos) {
      case 0:
      out1();
      clear2();
      out3();
      clear4();
      break;
      case 1:
      clear1();
      out2();
      out3();
      clear4();
      break;
      case 2:
      clear1();
      out2();
      clear3();
      out4();
      break;
      case 3:
      out1();
      clear2();
      clear3();
      out4();
      break;
    }
    delay(delayTime);
  }

  public:
  void stepCW(int numSteps) {
    for (int i = 0; i < numSteps; ++i) {
      stepPos = (stepPos + 1) % 4;
      runStep();
    }
  }

  void stepCCW(int numSteps) {
    for (int i = 0; i < numSteps; ++i) {
      --stepPos;
      if (stepPos < 0) stepPos = 3;
      runStep();
    }
  }
};

void setup() {
  // put your setup code here, to run once:
  PORTA.DIRSET |= 1 << 4;
  PORTA.DIRSET |= 1 << 5;
  PORTA.DIRSET |= 1 << 3;
  PORTB.DIRSET |= 1 << 2;
}

Stepper myStepper(PORTA.OUT, 4, PORTA.OUT, 5, PORTB.OUT, 2, PORTA.OUT, 3, 10);

void loop() {
  // put your main code here, to run repeatedly:
  myStepper.stepCW(200);
  delay(1000);
  myStepper.stepCCW(200);
  delay(1000);
}

Group Work

Our group work for this week can be found here.

I worked as part of group A1 on testing a water pump and made a graph for the results with python and matplotlib.


Last update: May 25, 2022