Saturday, March 13, 2021

0000 0000 0101 1000

Rowing Clock

I've been giving the rowing machine a bit of a hammering over the last year or so. The plan is to shed my perpetual winter plumage (aka fat) and trim down to a point where I don't have to continually invest in a whole new wardrobe.

The problem (as a numbers guy) has been the ... er ... numbers on the machine. It's a comprehensive readout that comes with the machine and it successfully distracts me away from the business of proper form and zen-like concentration on the actual rowing. I'm always chasing the numbers.

After a couple of visits to the physiotherapist to confirm that I am old and fat and shouldn't be chasing numbers I switched off the readout and used instead my phone countdown to measure the time rowed. It worked semi-well until my weird brain started to panic and as I was sweating away I became distracted through wondering if the damn phone was set correctly, and whether or not I was going to be rowing until Christmas.

So what I needed was a visual way of seeing the rowing but without the actual numbers. Inspired by a pileofstuff video I planned out a little 3D printed box full of goodies including an LED Ring, a 4-digit 7-segment display (TM1637), rotary encoder and buzzer as follows.

I also used a spreadsheet to draw the pattern I had imagined, just to make the coding part of the project a little easier to construct.
The code was in several sections:
  1. Set time to row
  2. Countdown to rowing start
  3. Countdown to rowing finish
  4. Warmdown
  5. Shutdown
#include <OneButton.h>
#include <Rotary.h>
#include <TM1637Display.h>
#include <FastLED.h>
#include "LowPower.h"

#define NUM_LEDS 16
#define DATA_PIN 7
#define CLK 11
#define DIO 10
#define buzzer 9

uint32_t intervaldelay = 0;

CRGB leds[NUM_LEDS];

volatile byte rowingmins = 1;
byte minmins = 1;
byte maxmins = 180;
volatile byte cw = 0;
volatile byte clicked = 0;
byte buzzdelay = 200;

TM1637Display display(CLK, DIO);
Rotary r = Rotary(3, 2);
OneButton button(4, true);

void setup() {

  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(20);
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();
  pinMode(buzzer, OUTPUT);
  button.attachClick(singleclick);
  display.setBrightness(4);
  setupcolour(0, 0, 0, 0);
}

void setupcolour(byte numgreen, byte numyellow, byte numred, byte numoff) {
  byte whichlight = 0;
  byte endlight = numgreen;
  for (byte countgreen = 0; countgreen < endlight; countgreen++) {
    leds[countgreen] = 0x00CC00;
    whichlight++;
  }
  endlight = whichlight + numyellow;
  for (byte countyellow = whichlight; countyellow < endlight; countyellow++) {
    leds[countyellow] = 0xFF9900;
    whichlight++;
  }
  endlight = whichlight + numred;
  for (byte countred = whichlight; countred < endlight; countred++) {
    leds[countred] = 0xCC0000;
    whichlight++;
  }
  endlight = whichlight + numoff;
  for (byte countblack = whichlight; countblack < endlight; countblack++) {
    leds[countblack] = 0x000000;
    whichlight++;
  }
  FastLED.show();
}

void precount() {

  for (byte showcount = 0; showcount <= 16; showcount++)  {
    setupcolour(showcount, 0, 0, 16 - showcount);
    delay(80);
  }

  setupcolour(0, 0, 0, 16);
  delay(500);

  setupcolour(16, 0, 0, 0);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(8000);

  setupcolour(12, 0, 0, 4);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(8000);

  setupcolour(8, 0, 0, 8);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(8000);

  setupcolour(4, 0, 0, 12);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(1000);

  setupcolour(3, 0, 0, 13);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(1000);

  setupcolour(2, 0, 0, 14);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(1000);

  setupcolour(1, 0, 0, 15);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(1000);

  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);

}

void warmdown() {

  for (byte downcount = 1; downcount < 4; downcount++) {
    setupcolour(0, 0, 16, 0);
    tone(buzzer, 2000);
    delay(buzzdelay);
    noTone(buzzer);
    delay(250);
    setupcolour(0, 0, 0, 16);
    delay(250);
  }

  for (byte warmcount = 16; warmcount >= 0; warmcount -= 1) {
    setupcolour(0, warmcount, 0, 16 - warmcount);
    delay(4000);
  }
}

ISR(PCINT2_vect) {
  unsigned char result = r.process();
  if ((result == DIR_CW) && (rowingmins < 180)) {
    rowingmins++;
  }
  else if ((result == DIR_CCW) && (rowingmins > 1)) {
    rowingmins--;
  }
}

void singleclick() {
  clicked = true;
}

void settime() {

  while (!clicked) {
    button.tick();
    display.showNumberDec(rowingmins);
  }
  PCICR = 0; //disable pin change interrupts

}

void loop() {

  settime();
  precount();
  display.clear();
  intervaldelay = rowingmins * 60000 / 32;

  for (byte countdown = 16; countdown > 0; countdown--) {
    setupcolour(countdown - 1, 1, 0, 16 - countdown);
    delay(intervaldelay);
    setupcolour(countdown - 1, 0, 1, 16 - countdown);
    delay(intervaldelay);
  }
  warmdown();
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);

}


Next I needed to prototype the setup on a breadboard to make sure that all the components (and code) were working as expected.

Finally I soldered up the whole shebang and shoehorned the lot into the 3D enclosure. The only addition in the end was a little ice-blue LED on the side to indicate that the box is "live" just in case I forget to turn in off as I have my post-workout heart attack.

It works exactly as needed and it was nice to build such a useful project with so many different aspects coming together to make the final result.









No comments:

Post a Comment