
Figure 1: The automated winch after its first pHake Lake trial

Figure 2: Winch Examples
“to try and build a winch type thing that could work off a boat or kayak”
-Professor Robert Sanchez of HMC
Patricia is a member of Professor Sanchez’s research group studying estuary dynamics.
CTD (Conductivity, Temperature, and Depth) devices are commonly used in this area
of research to examine observables in these environments. Initially, Professor Sanchez planned to use
a fishing rod to hoist the CTD, but with the development of our Arduino-controlled winch,
we obtain accurate depth readouts while raising and lowering the CTD securely.

Figure 3: The SonTek CastAway-CTD

UCTRONICS 0.96 Inch OLED Module Display
Arduino Uno
Nema23 stepper motor
TB6600 Current driver
DC-DC Buck Converter
18V Rechargeable Battery
3 Way Switch
Red Calibration Button
Figure 4: Patricia’s Circuit Diagram

### Arduino IDE Code for Corrina & Patricia's winch ###
### PACKAGES ###
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
### PINOUTS ###
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define YELLOW_HEIGHT 16
#define BUTTON_PIN 7
#define SWITCH_UP_PIN 9
#define SWITCH_DOWN_PIN 8
const int stepPin = 3;
const int dirPin = 2;
### SPOOL DETAILS ###
const float R0 = 31.44; // outer radius of spool in mm
const int PULSES_PER_REV = 21286;
### DEPTH CALCULATION & DISPLAY ###
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
volatile long stepCount = 0;
float displayDepth = 0.0;
float lastDisplayDepth = -1.0;
int lastButtonState = HIGH;
void countStep() {
if(digitalRead(dirPin) == HIGH)
stepCount++; // unwinding = going deeper
else
stepCount--; // winding = coming up
}
float calculateDepth() {
float theta = (float)stepCount / PULSES_PER_REV;
float depth_m = (2.0 * PI * R0 * theta) / 1000.0;
// Exponential correction factor
return depth_m * (1.0 - (0.00000001 * depth_m * depth_m));
}
void invertSection(int16_t y_start, int16_t y_end) {
display.fillRect(0, y_start, SCREEN_WIDTH, y_end - y_start, SSD1306_INVERSE);
}
void updateDisplay(float depth) {
display.clearDisplay();
// Yellow section — "Depth" label
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(50, 4);
display.println(F("Depth"));
invertSection(0, YELLOW_HEIGHT);
// Blue section — depth reading
display.setTextSize(2);
// Format as X.XX m
char depthStr[10];
dtostrf(depth, 4, 2, depthStr);
strcat(depthStr, "m");
// Center the text
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(depthStr, 0, 0, &x1, &y1, &w, &h);
display.setCursor((SCREEN_WIDTH - w) / 2, 35);
display.println(depthStr);
display.display();
}
void setup() {
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(SWITCH_UP_PIN, INPUT_PULLUP);
pinMode(SWITCH_DOWN_PIN, INPUT_PULLUP);
digitalWrite(dirPin, HIGH);
// Attach an interrupt to pin 3 (PUL) to count every step pulse
attachInterrupt(digitalPinToInterrupt(3), countStep, RISING);
// Timer2 PWM on pin 3
TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
TCCR2B = _BV(CS21); // Keep original prescaler
OCR2B = 64; // Keep original value
// Keep this line - it disables PWM at startup
TCCR2A &= ~_BV(COM2B1);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for(;;);
}
updateDisplay(0.0);
}
void loop() {
int buttonState = digitalRead(BUTTON_PIN);
int switchUpState = digitalRead(SWITCH_UP_PIN);
int switchDownState = digitalRead(SWITCH_DOWN_PIN);
// Red calibration button resets depth to 0
if(buttonState == LOW && lastButtonState == HIGH) {
stepCount = 0;
updateDisplay(0.0);
delay(50);
lastButtonState = buttonState;
// Motor direction + start/stop
if(switchUpState == LOW) {
TCCR2A &= ~_BV(COM2B1);
digitalWrite(dirPin, HIGH);
delayMicroseconds(1);
TCCR2A |= _BV(COM2B1);
} else if(switchDownState == LOW) {
TCCR2A &= ~_BV(COM2B1);
digitalWrite(dirPin, LOW);
delayMicroseconds(1);
TCCR2A |= _BV(COM2B1);
} else {
TCCR2A &= ~_BV(COM2B1);
digitalWrite(stepPin, LOW);
}
// Update display only when depth changes by 0.01m
displayDepth = calculateDepth();
if(abs(displayDepth - lastDisplayDepth) >= 0.01) {
updateDisplay(displayDepth);
lastDisplayDepth = displayDepth;
}
delay(10);
}






