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

Background & Mission

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

Electronics

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

Mechanical Design

Components

  • Fishing Line
  • Aluminum Rod Fixer
  • 3D Printed Bushing
  • Swivel
  • Carabiner
  • Plastic Toolbox Base
  • Clear Acrylic Display Cover
  • Rod
  • Spool

Arduino Code

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

Data

Goodies from pHake Lake