About Me Weekly Assignments Final Project
Kevin J Jijo
Week 10

10. Output Devices

Group Assignment

Results can be found on the group assignment page of our lab.

Individual Assignment

Output Devices Used

OLED Display

The SSD1306 is a single-chip CMOS OLED/PLED driver with an integrated controller designed for organic or polymer light emitting diode dot-matrix displays. The display used here is a 128 × 64 pixel module consisting of 128 segments and 64 commons, designed for common cathode type OLED panels.

This OLED display communicates over I2C, which keeps wiring simple while still allowing full graphical control. The plan was to use the display to show available tracks, playback status, and possibly volume or system feedback information.

Since the display is graphical rather than character based, it allows flexible UI design such as track lists, icons, animations, and system diagnostics during development.

Servo Motor

Servo motors are specialized motors that include an internal feedback mechanism allowing precise control of angular position, speed, and acceleration. A typical servo contains a small DC motor, a potentiometer for position sensing, and an internal control circuit.

The motor speed is proportional to the difference between the current position and the desired position. When the error is large, the motor moves quickly; as it approaches the target angle, movement slows down. This behavior is known as proportional control.

Servos are controlled using pulse width modulation (PWM). A control pulse is sent approximately every 20 ms, and the width of the pulse determines the angular position of the shaft.

Once commanded, the servo moves to the desired angle and actively holds that position. If an external force attempts to move it, the servo resists based on its torque rating. The position signal must continuously repeat; otherwise, the servo will stop maintaining position.

In this project, the servo motor is used to lift and lower the tonearm onto the vinyl record after the correct track position has been reached.

Stepper Motor

A stepper motor is a brushless DC motor that divides a full rotation into a large number of discrete steps. This makes it ideal for applications requiring precise positioning without needing complex feedback systems.

The motor consists of two main components: the stator, which contains electromagnetic coils, and the rotor, typically made from permanent magnets or soft magnetic material.

By energizing stator coils sequentially, magnetic fields are generated that pull the rotor from one step position to the next, producing controlled rotational motion.

Stepper Motor Driver — DRV8428PWPR

The DRV8428PWPR driver is used to control the stepper motor. The driver handles coil sequencing, current regulation, and efficient switching, allowing the microcontroller to command precise step movements using simple control signals.

The stepper motor is responsible for moving the tonearm horizontally across the record to reach the correct groove location. Once positioned, the servo motor lowers the tonearm.

A4953 Motor Driver

The A4953 is a compact motor driver capable of controlling DC motors and stepper motors. It operates across a wide voltage range from 4V to 40V and functions as an H-Bridge driver, allowing bidirectional motor control.

An H-Bridge allows current to flow through the motor in either direction. By controlling two input pins (IN1 and IN2), the motor can rotate forward or backward. Applying PWM signals enables speed control.

If both inputs are LOW, the motor free-runs and stops gradually. If both inputs are HIGH, the motor actively brakes. Proper passive components such as capacitors and resistors are required to ensure stable operation of the driver.

Circuit Design

The board needed to support multiple output devices including a stepper motor, servo motor, and OLED display. This required handling different voltage domains, communication interfaces, and motor control circuitry on a single PCB.

The design includes:

The schematic design:

Voltage Regulation

The stepper motor operates at 12V, while the ATtiny3226 microcontroller and logic circuitry operate at 5V. An external 12V supply is therefore stepped down using a voltage regulator to safely power the logic electronics.

If a voltage regulator is placed far from the power source, bypass capacitors are required to filter AC noise and ripple. These capacitors stabilize the input supply and ensure clean DC power for reliable microcontroller operation.

Double H-Bridge

The double H-Bridge configuration allows independent control of the stepper motor windings, enabling accurate stepping motion required for tonearm positioning.

12V Connector Socket

An electrolytic capacitor placed between 12V and ground acts as bulk storage and a decoupling filter. It smooths voltage fluctuations, absorbs switching noise, and supplies short bursts of current during motor load changes, preventing voltage dips.

PCB Design

The completed PCB integrates power regulation, motor drivers, display interface, and control inputs into a single board.

Larger track widths were used for the 12V and ground power lines. Higher voltage and motor currents require wider copper traces to reduce resistance and prevent overheating due to Joule heating.

Proper via sizes were configured in the design rules using predefined sizes.

Predefined via sizes were selected rather than using custom netclasses for this design.

Milling and Soldering

The PCB was milled and all components were assembled and soldered.

Components collected alongside the milled board:

Completed soldered board:

Preparing the Output Devices

Stepper Motor

The first step was preparing the stepper motor. The main requirement was to identify the two separate coil pairs (A+, A− and B+, B−). Using a multimeter, the pairs with low resistance were identified and each pair was connected to one motor driver output.

Reference video used: https://www.youtube.com/watch?v=kNyAcAHLET8

Blue and red wires, and green and blue wires showed low resistance between them, indicating the coil pairs. The measured resistance was approximately between 2–50 ohms.

Another method to verify the coil pairing is to short any two wires together and manually rotate the motor shaft. If increased resistance is felt while turning, those two wires belong to the same coil.

After identifying the coils and setting up the connector, the motor could be interfaced with the custom board.

These are the servo motor connections.

Code for Stepper Motor


        #define IN1 PIN_PC0
        #define IN2 PIN_PC1
        #define IN3 PIN_PC2
        #define IN4 PIN_PC3

        int stepDelay = 1;

        void setup() {
          pinMode(IN1, OUTPUT);
          pinMode(IN2, OUTPUT);
          pinMode(IN3, OUTPUT);
          pinMode(IN4, OUTPUT);
        }

        void stepForward() {
          digitalWrite(IN1,HIGH); digitalWrite(IN2,LOW);
          digitalWrite(IN3,HIGH); digitalWrite(IN4,LOW);
          delay(stepDelay);

          digitalWrite(IN1,LOW); digitalWrite(IN2,HIGH);
          digitalWrite(IN3,HIGH); digitalWrite(IN4,LOW);
          delay(stepDelay);

          digitalWrite(IN1,LOW); digitalWrite(IN2,HIGH);
          digitalWrite(IN3,LOW); digitalWrite(IN4,HIGH);
          delay(stepDelay);

          digitalWrite(IN1,HIGH); digitalWrite(IN2,LOW);
          digitalWrite(IN3,LOW); digitalWrite(IN4,HIGH);
          delay(stepDelay);
        }

        void stepBackward() {
          digitalWrite(IN1,HIGH); digitalWrite(IN2,LOW);
          digitalWrite(IN3,LOW); digitalWrite(IN4,HIGH);
          delay(stepDelay);

          digitalWrite(IN1,LOW); digitalWrite(IN2,HIGH);
          digitalWrite(IN3,LOW); digitalWrite(IN4,HIGH);
          delay(stepDelay);

          digitalWrite(IN1,LOW); digitalWrite(IN2,HIGH);
          digitalWrite(IN3,HIGH); digitalWrite(IN4,LOW);
          delay(stepDelay);

          digitalWrite(IN1,HIGH); digitalWrite(IN2,LOW);
          digitalWrite(IN3,HIGH); digitalWrite(IN4,LOW);
          delay(stepDelay);
        }

        void loop() {
          for(int i=0;i<200;i++) stepForward();
          delay(1000);

          for(int i=0;i<200;i++) stepBackward();
          delay(1000);
        }
        

Code for Servo Motor


        #include <Servo.h>

        Servo myServo;

        void setup() {
          myServo.attach(PIN_PA3);
        }

        void loop() {
          myServo.write(0);
          delay(1000);
          myServo.write(90);
          delay(1000);
          myServo.write(150);
          delay(1000);
        }
        

Code for OLED Display


        #include <Wire.h>
        #include <Adafruit_GFX.h>
        #include <Adafruit_SSD1306.h>

        #define SCREEN_WIDTH 128
        #define SCREEN_HEIGHT 64
        #define OLED_RESET -1

        Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

        #define ROTARY_A PIN_PA2
        #define ROTARY_B PIN_PA1

        int selectedTrack = 1;
        int lastA = HIGH;

        void setup() {
          pinMode(ROTARY_A, INPUT_PULLUP);
          pinMode(ROTARY_B, INPUT_PULLUP);
          lastA = digitalRead(ROTARY_A);

          Wire.begin();
          display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
          updateDisplay();
        }

        void loop() {
          int a = digitalRead(ROTARY_A);

          if (a == LOW && lastA == HIGH) {
            if (digitalRead(ROTARY_B) == LOW) selectedTrack++;
            else selectedTrack--;

            if (selectedTrack > 5) selectedTrack = 1;
            if (selectedTrack < 1) selectedTrack = 5;

            updateDisplay();
          }

          lastA = a;
          delay(2);
        }

        void updateDisplay() {
          display.clearDisplay();

          display.setTextSize(1);
          display.setTextColor(SSD1306_WHITE);
          display.setCursor(25,5);
          display.println("Record Tracks");
          display.drawLine(0,16,127,16,SSD1306_WHITE);

          for(int i=1;i<=5;i++){
            int y = 20 + (i-1)*9;

            if(i==selectedTrack){
              display.fillRect(0,y-1,128,10,SSD1306_WHITE);
              display.setTextColor(SSD1306_BLACK);
            } else {
              display.setTextColor(SSD1306_WHITE);
            }

            display.setCursor(10,y);
            display.print("Track ");
            display.print(i);
          }

          display.display();
        }
        

Prompt used to generate code:

Give me a simple code for an OLED display connected to SCL to PB0 and SDA to PB1. It should say Record Tracks as headings. Then track one, track two, track three, and track five, and you can choose which one it is based on a rotary encoder input with rotary B connected to PA1 and rotary A connected to PA2.

Integrated System Code


          #include 
#include 
#include 
#include 

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Rotary encoder & button
#define ROTARY_A    PIN_PA2
#define ROTARY_B    PIN_PA1
#define ROTARY_SW   PIN_PB4

// Stepper pins
#define IN1 PIN_PC0
#define IN2 PIN_PC1
#define IN3 PIN_PC2
#define IN4 PIN_PC3

// Servo pin
#define SERVO_PIN   PIN_PA3

// Remote Controls

#define MSG_UP      1
#define MSG_DOWN    2
#define MSG_SELECT  3
#define MSG_PAUSE   4

bool paused = false;

Servo myServo;

int selectedTrack  = 1;
int activeTrack    = 0;   // 0 = none selected yet
int lastA          = HIGH;
int lastButton     = HIGH;
int stepDelay      = 2;

// 5 equal stepper positions across 200 steps (0 to 200)
const int trackPositions[6] = {0, 0, 50, 100, 150, 200};
int currentStepperPos = 0;

void setup() {
  pinMode(ROTARY_A,  INPUT_PULLUP);
  pinMode(ROTARY_B,  INPUT_PULLUP);
  pinMode(ROTARY_SW, INPUT_PULLUP);
  lastA = digitalRead(ROTARY_A);

  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);

  myServo.attach(SERVO_PIN);
  myServo.write(90);        // start at 90 (open/lifted position)

  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  updateDisplay();
}

void loop() {
  handleEncoder();
  handleButton();
}

// ── Rotary encoder ────────────────────────────────────────
void handleEncoder() {
  int a = digitalRead(ROTARY_A);
  if (a == LOW && lastA == HIGH) {
    if (digitalRead(ROTARY_B) == LOW) {
      selectedTrack++;
    } else {
      selectedTrack--;
    }
    if (selectedTrack > 5) selectedTrack = 1;
    if (selectedTrack < 1) selectedTrack = 5;
    updateDisplay();
  }
  lastA = a;
  delay(2);
}

// ── Button press ──────────────────────────────────────────
void handleButton() {
  int btn = digitalRead(ROTARY_SW);
  if (btn == LOW && lastButton == HIGH) {
    delay(20);                            // debounce
    if (digitalRead(ROTARY_SW) == LOW) {
      goToTrack(selectedTrack);
    }
  }
  lastButton = btn;
}



// ── Move to selected track ────────────────────────────────
void goToTrack(int track) {
  // Step 1: lift servo from 0 to 90 (only if already placed)
  if (activeTrack != 0) {
    servoMove(0, 90);
  }

  // Step 2: move stepper to new position
  int targetPos = trackPositions[track];
  if (targetPos > currentStepperPos) {
    int steps = targetPos - currentStepperPos;
    for (int i = 0; i < steps; i++) stepForward();
  } else if (targetPos < currentStepperPos) {
    int steps = currentStepperPos - targetPos;
    for (int i = 0; i < steps; i++) stepBackward();
  }
  currentStepperPos = targetPos;

  // Step 3: lower servo from 90 to 0
  servoMove(90, 0);

  activeTrack = track;
  updateDisplay();
}

// ── Servo slow sweep ──────────────────────────────────────
void servoMove(int from, int to) {
  if (from < to) {
    for (int pos = from; pos <= to; pos++) {
      myServo.write(pos);
      delay(15);
    }
  } else {
    for (int pos = from; pos >= to; pos--) {
      myServo.write(pos);
      delay(15);
    }
  }
}

// ── Stepper ───────────────────────────────────────────────
void stepForward() {
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);  delay(stepDelay);
  digitalWrite(IN1, LOW);  digitalWrite(IN2, HIGH); digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);  delay(stepDelay);
  digitalWrite(IN1, LOW);  digitalWrite(IN2, HIGH); digitalWrite(IN3, LOW);  digitalWrite(IN4, HIGH); delay(stepDelay);
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);  digitalWrite(IN3, LOW);  digitalWrite(IN4, HIGH); delay(stepDelay);
}

void stepBackward() {
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);  digitalWrite(IN3, LOW);  digitalWrite(IN4, HIGH); delay(stepDelay);
  digitalWrite(IN1, LOW);  digitalWrite(IN2, HIGH); digitalWrite(IN3, LOW);  digitalWrite(IN4, HIGH); delay(stepDelay);
  digitalWrite(IN1, LOW);  digitalWrite(IN2, HIGH); digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);  delay(stepDelay);
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);  delay(stepDelay);
}

// ── Display ───────────────────────────────────────────────
void updateDisplay() {
  display.clearDisplay();

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(25, 5);
  display.println("Record Tracks");
  display.drawLine(0, 16, 127, 16, SSD1306_WHITE);

  for (int i = 1; i <= 5; i++) {
    int y = 20 + (i - 1) * 9;

    if (i == selectedTrack) {
      display.fillRect(0, y - 1, 128, 10, SSD1306_WHITE);
      display.setTextColor(SSD1306_BLACK);
    } else {
      display.setTextColor(SSD1306_WHITE);
    }

    display.setCursor(10, y);
    display.print("Track ");
    display.print(i);

    // show a marker on the active/loaded track
    if (i == activeTrack) {
      display.setTextColor(i == selectedTrack ? SSD1306_BLACK : SSD1306_WHITE);
      display.setCursor(90, y);
      display.print("<");
    }
  }

  display.display();
}
        

Prompt used:

I need a code that has the same display, but now when I click the rotary encoder switch, each of the 5 tracks has an equal location on the stepper motor, so it will go to one of the 5 locations defined, and then the servo motor will move from 90 to 0. When a new track is clicked, the servo will go from 0 to 90, and then the stepper will move to the other track's position.