A simplified  timer for the washing machines in my dorm building.

The puck is a small handheld timer with one button along the top to activate and deactivate it.

Living in a dorm building where the laundry room is at the other end of the hall compared to my apartment, I’m prone to forget my clothes in the washing machine long after they’re done being cleaned. Recently I’ve used a timer on my phone to remind me to pick up my laundry, but occasionally I still forget to set it.

The goal of this project is to make a system that will remind me when my laundry is done, but is also as easy to use, and remember to use as possible. The resultant device is a highly simplified timer. One button press is all that’s needed to activate it, and the timer progress is shown on LEDs under the surface of the button. The timer vibrates to indicate that my laundry is finished, and can be cancelled early by holding down the button for a second or longer.

The internal circuitry of the puck, on the left is a power management circuit, which limits battery drain when the device is off.

Three different levels of the timer. (10 minutes left, 20 minutes left and 30 minutes left, left to right)

Process

The two design considerations which significantly shaped this project were based on the context in which this device would be used. Specifically, this device needed to be portable, so that it could be taken to the laundry room, and the puck was only expected to be used a few times each month. The concerns for portability introduced new challenges to the project. For example, to make the timer portable, it would need to be small and easy to hold. This prompted the simplified rounded design, as well as the need for the circuitry of the device to be miniaturized from a breadboard down to a 40x30mm protoboard.

Furthermore, being portable required the puck to run on battery power, and in an effort to preserve battery power (given the device would be used infrequently), I designed a power management circuit to minimize battery drain between uses.

The initial test setup for the power management circuit. Here the button could turn on the Arduino, and the Arduino could turn itself off.

While the power circuit was relatively simple to implement, many problems emerged from the level of miniaturization required by this project. Specifically, using the ATTiny85 IC in place of the Arduino Uno board introduced a few complications. Interfacing the ATTiny with the NeoPixel LED ring proved to be difficult due to an unnoticed clock-rate conflict, causing the ATTiny to operate 16 times slower than intended. This unintuitive issue was eventually resolved by burning new firmware into the chip, which adjusted its clock rate from the default 1 MHz to the expected 16 MHz.

The LED ring exhibiting erratic behavior, the result of insufficient filtering on the vibration motor, and a clock rate conflict with the ATTiny85.

The most difficult aspect of this miniaturization though, was recreating the circuitry of the prototype device at such a small scale. Given the complexity of the circuit, wiring it onto a small protoboard proved to be a difficult task. Erroneous connections and weak solder joints were common issues, but I was able to track all of these issues down by using the continuity testing features of the multi-meters.

The final circuit board of the TimerPuck. Wiring at this scale proved to be tremendously difficult.

The first test of the circuit after it was fully soldered to a circuit board.

In terms of the physical construction of the puck, I ended up underestimating the size of my circuitry, which meant that the first completed iteration of the Laundry Puck was not fully enclosed within the case designed for it. Thankfully this was only visible from the bottom side, and the button portion still functioned as planned. After taking new measurements of my circuit board, I reprinted the case to be 50% larger, which allowed all the components to fit properly.

The original case design proved to be too small to contain the circuit board and battery.

The puck fully assembled in the small case, performing its first full test.

The new 3D printed case, 50% larger than the original design.

Discussion

Overall, I am mostly satisfied with how this project turned out, though the in-class criticisms do raise some good points which I intend to address. As one commenter stated:

“I am not sure if this is pocket-able, have you also considered fully committing to the jack-o’-lantern aesthetic?”

I agree that, in the state the puck was in for the in-class presentation, it certainly wasn’t ready to be placed in my pocket, given the exposed circuitry. Since then, I’ve 3D printed a larger enclosure though, that makes the puck more visually appealing. The larger case makes the device less pocket-sized than the original version, but considering this was only meant to be portable in that I could bring it with my laundry to the washing machine, I’d argue the size isn’t that much of an issue.

Similarly, another commenter responded as follows:

“Not sure exactly how this would be less effective than a phone timer, given that your phone would also be on you at all times.”

While an understandable critique, I’d have to disagree with this comment, given that the primary motivation behind this project was that timing the laundry with my phone proved to be ineffective. The entire purpose of the project was to replace my hard to use phone timer with something so simple I wouldn’t forget to use it.

So far, I haven’t been able to test the final version of the puck in daily life to see how well it performs, but I’m currently pleased with the simplicity of the device, and expect it will be able to help solve my problem.

Some complications did arise in the construction of this device which proved to be learning experiences. Specifically, while the electronics, and even more so the software proved to be relatively simple to implement, the physical construction of a device meant to be handheld proved to be rather unruly. I had a tendency to underestimate the size of my device, which resulted in it not fitting its original case. While the new case does fit the electronics properly now, it does serve as an excellent lesson on the importance of leaving some wiggle room in a physical design.

Generally I’m mostly satisfied with this project, though if I were to make some improvements, they would mostly be towards further improving the device’s battery life. Given that the original test versions ran an LED ring off of a 9 volt battery, the puck couldn’t be expected to run for much more than two or three cycles on the timer before misbehaving. For this device to be more practical, it would be best if it could be used many times before needing a battery replacement. Possibly, an even better option would be to add a rechargeable battery instead.

Technical Information

  #include <Adafruit_NeoPixel.h>

/* Project Two - Laundry Timer Puck
 * George Ralph (gdr)
 * 8 Hours
 *
 * Collaboration: None
 * 
 * Challenge: Miniaturizing the circuitry for this project to make it fit
 *  in a handheld case proved rather difficult. Eventually, this was resolved
 *  by creating a larger case, and careful soldering to a protoboard.
 * 
 * Next time: I'll leave more room for error in my designs so that components 
 *   aren't too large for their enclosures.
 * 
 * Summary: A timer for the washing machines in my dorm building.
 * 
 * Using a custom power management circuit, the device powers on with the press
 * of the main button. From there, the software displays the current time
 * remaining until the timer is complete. At any point, the user can cancel
 * the timer and turn it off by holding down the main button for one second.
 * 
 *  Inputs: 
 *  
 *  Arduino Pin | Input
 *     1          Button      
 *  
 *  Outputs:
 *  
 *  Arduino pin | Output
 *      2         Vibration Motor
 *      3         NeoPixel LED Ring
 *      4         Power Control Relay
 */
 
const int BUTTON_PIN = 1;
const int MOTOR_PIN = 2;
const int LED_PIN = 3;
const int RELAY_PIN = 4;

const int LED_COUNT = 16;
const int BRIGHTNESS = 10; //20% brightness

//Duration of the timer in minutes
const long duration = 34;
//Duration of the timer in milliseconds
const unsigned long durMillis = duration * 60 * 1000;

unsigned long startTime;
unsigned long buttonDownTime;
bool buttonDown;

// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pinMode(MOTOR_PIN, OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  
  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(BRIGHTNESS); // Set BRIGHTNESS to about 1/5 (max = 255)
  
  //Play wind-up animation, and grab start time
  windUp();
  startTime = millis();
}

void loop() {
  //Figure out how long this timer has been running
  unsigned long elapsedTime = millis() - startTime;
  
  //When we pass the duration we need to wait, trigger the alarm
  if(elapsedTime > durMillis) {
    alarm();
  }

  //Compute timeFrac in terms of seconds, since we don't need to be
  //that precise, and we get an overflow otherwise
  int timeFrac = map(elapsedTime / 100, 0, durMillis / 100, 32767, 0);
  drawTimer(timeFrac);

  bool buttonState = !digitalRead(BUTTON_PIN);

  //On buttonDown
  if(buttonState && !buttonDown) {
    buttonDownTime = millis();
  }

  //If the button is down
  if(buttonState) {
    //And it's been down for at least a second
    if(millis() - buttonDownTime >= 1000) {
      //Cancel the timer
      windDown(timeFrac);
      shutDown();
    }
  }
  
  buttonDown = buttonState;
}

/* Called when the timer has finished */
void alarm() {
  digitalWrite(MOTOR_PIN, 1);
  
  //Wait until the button is pressed
  while(digitalRead(BUTTON_PIN)) {
    fillTo(LED_COUNT, strip.Color(255, 0, 0));
    delay(500);

    //Check if the button is pressed halfway through the loop
    if(!digitalRead(BUTTON_PIN)) break;
    
    fillTo(LED_COUNT, strip.Color(255, 255, 255));
    delay(500);
  }

  shutDown();
}

/* Triggers the shutdown relay and cuts power */
void shutDown() {
  digitalWrite(RELAY_PIN, 1);
  delay(500);
}

//Circles the lights in a "wind-up" animation
//Winds up from zero to the fully filled circle
void windUp () {
  digitalWrite(MOTOR_PIN, 1);
  
  for(int i = 0; i <= 255; i++) {
    drawTimer(i * 128);
    delay(5);
  }
  
  digitalWrite(MOTOR_PIN, 0);
}

//Circles the lights in a "wind-down" animation
//Winds down from a specified start time
void windDown (int startTime) {
  digitalWrite(MOTOR_PIN, 1);
  
  for(int i = (startTime / 128); i >= 0; i--) {
    drawTimer(i * 128);
    delay(5);
  }
  
  digitalWrite(MOTOR_PIN, 0);
}

/* Draws the current timer state on the NeoPixel ring 
   Accepts times from 0-32767 */
void drawTimer(int timeFrac) {
  //Get the number of LEDs completely filled
  int filled = timeFrac / (32768L / LED_COUNT);
  //And the level to set the last (fractional) one to
  int frac   = timeFrac % (32768L / LED_COUNT);
  //Map frac to 0-255
  frac = map(frac, 0, (32768L / LED_COUNT), 0, 255);

  //Choose the color for the timer (32767 -> blue, 0 -> red)
  uint32_t color = strip.ColorHSV(max(0, timeFrac - 2048), 255, 255);

  //Draw the filled ones with full brightness
  fillTo(filled, color);

  //Fade in the fractional LED
  color = strip.ColorHSV(max(0, timeFrac - 2048), 255, frac);
  strip.setPixelColor(filled, color);
  
  strip.show();
}
   
/* Fills the first cnt LEDs to the given color */
void fillTo(int cnt, uint32_t color) {
  //Clear whatever was on the strip before
  strip.fill(strip.Color(0, 0, 0));
  
  for(int i = 0; i < cnt; i++) {
    strip.setPixelColor(i, color);
  }
  strip.show();
}