Electronics: Pomodoro Timer

Now that my law degree is taking a bit of a backseat (a lot has been delayed because of the COVID-19 pandemic), I’ve dipped back into my electronics hobby. I’d basically put that on a backburner to favor the law degree. So what am I to do with so much time? Well, use my very basic electronics skills to make something to manage time!

I’d had the idea before to make a pomodoro timer but I’d never gotten around to actually designing one. So, the past couple of days, I felt the urge to do something, and I put that energy into prototyping one. I started to work on a breadboard to just hook up the things I figured I’d want out of a final project, connect them to an ATMega328 I had lying around in my knock-off Arduino Uno, and program away.

I’d try to make one single feature work first, before moving on to the next. The first question was how to actually display the time. I could, of course, do a traditional 7-segment display, but that would add quite a few components. When I had the idea before, Fub recommended a good rule in electronics: don’t make it any more complex than it has to be. The fewer components you can add, the less chance things will go wrong. So, I followed his advice to make it display the time in binary (“for extra nerd points”, he argued). I hooked up the lights and set to coding, made it display an integer number in binary, and was pleased.

As I started to add more features, one at a time, testing to see if it all would work, I realized I would run out of pins. Fortunately, I had some 74HC393 ICs lying around, and I could use that IC to decrease the number of pins I was using for the LEDs from 8 to 4. That gave me the space to add a buzzer to indicate state changes, an LED to indicate when it’s paused, and a button to pause and reset the actual process. Once I hooked these all up, it was time to actually program the thing to work.

The final product on the breadboard. What a mess of wires! You can hardly even see that IC hiding there. It’s currently being powered from the Arduino Uno board via the USB.

I’d keep adding a thing to the main program, make it work, and then separate it out to a function of its own, so as to keep the main program loop clean. This turned out to be a really good idea, because later in the programming phase, I discovered that I wasn’t approaching timing correctly. I was using the Arduino delay() function to cycle through the loop 1ms at a time. However, that neglects the fact that the looping itself takes a little bit of time too. With my program (and printing feedback to Serial to understand what was going on), it ended up being 4 seconds delay per minute—horrible! It was then that I learned the Atmega328 has its own internal timer that you can reference using millis(). So, I had to recode my main loop to use that as a reference time, and then based on that call my other processes. If I hadn’t separated out all my tasks into functions, that would have required me to rewrite everything rather than just adjust. I’ll post the whole final code at the bottom of this.

What I’m left with for now is the question of how exactly I’m going to put this on a prototype. I had a quick jaunt with KiCad to refamiliarize myself with it, and sketched out a quick schematic. That both helps me remember exactly how I’d set things up in case something goes wrong with the breadboard, as well as leaves me the opportunity of building it out into a PCB at one point if I’d wanted to.

The schematic that I’ve quickly sketched up for it. I had to make a new diagram for the 74HC393, as it wasn’t in the libraries I had. If I end up making a PCB for it, I’ll have to provide a footprint. I may just put it on a prototyping board, though.

There is one major thing left, that is a little out of my current skill level, though (I’m already happy that I managed to design this fully from scratch, without reference, and have it work!). I can see so far that this design is wasteful. I’m not using a whole bunch of the pins on the ATMega328, so why would I use that chip? Right now, the simple answer is: that’s the one I had lying around. To improve, I could see about getting a smaller chip that would be programmable with Arduino code (I’m not delving into programming those things directly—that’s a little to far for me right now). Surely there’d be one with two power pins and eight programmable pins.

Another way to reduce the waste is to make it more efficient. I’d read some remarks that there are power states for these chips as well as cycle speeds, and to get the most out of the chip, you can tune those down massively. Again: if you don’t need it, then don’t have it there. I’ve read things about people making something last for four years on a single 9v battery, simply by tuning it all down. However, a lot of that seems well above my skill level right now. It goes into a lot of specifics of the chip at a point where I’m happy I just made this work. I don’t even know how to search for the right information on how to improve it right now. So, I think I may end up leaving it powered via an external supply for now. Perhaps the next step in this will be to learn how to power things by USB, or how to incorporate rechargeable batteries.

Oh yes, below you can find the code I wrote for it. For some reason WordPress prints it in this strange way. I’m not too sure what to do about that. Anyway, perhaps somebody can take the design and the code, and make something better out of it.

EDIT 03/05/20: Okay, a better overview is on my GitHub page here.

/* 
Pomodoro Timer v1.0
by Paulo De Tiège
25 April 2020

AtMega328P connected to 
- 74HC393 decade counter connected to
  - Four 5mm red LEDs
  - Three 3mm red LEDs
- One 5mm red LED
- Buzzer
- Button
- One 3mm yellow LED
*/

//// Globals ////

// Pin definitions
const int decade_clock    = 2;        // Decade counter 1-15 input pin
const int decade_reset    = 3;        // Decade counter reset pin
const int led_bin16       = 4;        // LED for 16+ numbers
const int decade_clock_2  = 5;        // Pomodoro counter
const int decade_reset_2  = 6;        // Pomodoro counter reset
const int led_pause       = 7;        // Yellow pause led
const int buzzer          = 8;        // Buzzer
const int button          = 9;        // Pause button

// Set the intervals
const long pomodoro       = 25;       // One pomodoro is 25 minutes
const long small_break    = 05;       // A small break is 5 minutes
const long big_break      = 30;       // A big break is 30 minutes
const long interval       = 60000;      // Set the interval to 1 minute

// Trackers
int countdown = pomodoro;             // Start with a pomodoro
int number_pomodoros = 0;             // Number of pomodoros run
int state = 0;                        // 0 = pomodoro, 1 = break
int previous_state = 0;               // Tracker for pause resume
long previous_millis = 0;              // Timer

void setup() {
  // Set up pins
  pinMode(decade_clock, OUTPUT);      // 4-bit binary counter
  pinMode(decade_reset, OUTPUT);      // Reset
  pinMode(led_bin16, OUTPUT);         // Binary counter 5th bit
  pinMode(decade_clock_2, OUTPUT);    // Pomodoro counter
  pinMode(decade_reset_2, OUTPUT);    // Pomodoro counter reset
  pinMode(led_pause, OUTPUT);         // Yellow pause led
  pinMode(buzzer, OUTPUT);            // Buzzer
  pinMode(button, INPUT_PULLUP);      // Pause button

  // Reset any counters to clear initial static
  digitalWrite(decade_reset, HIGH);
  digitalWrite(decade_reset, LOW);
  digitalWrite(decade_reset_2, HIGH);
  digitalWrite(decade_reset_2, LOW);

  Serial.begin(9600);

  // Initialize the first pomodoro
  write_binary_time(countdown);
  buzz_pomodoro();
  previous_millis = millis();
}

void write_binary_time(int number){
    // Wipe the current display
    digitalWrite(decade_reset, HIGH);
    digitalWrite(decade_reset, LOW);
    
    // Check if we need to light the 15+ LED
    if (number > 15) digitalWrite(led_bin16, HIGH);
    else digitalWrite(led_bin16, LOW);
    
    // Count up to the current number
    for (number; number > 0; number--) {
      digitalWrite(decade_clock, HIGH);
      digitalWrite(decade_clock, LOW); 
    }
}
  
void write_pomodoros(){
  // Wipe the current display
  digitalWrite(decade_reset_2, HIGH);
  digitalWrite(decade_reset_2, LOW);

  // Increment the counter
  int i = number_pomodoros;
  for (i; i > 0; i--) {
    digitalWrite(decade_clock_2, HIGH);
    digitalWrite(decade_clock_2, LOW);
  }
}

void buzz_pause(){
  for (int x = 0; x < 2; x++) {
    digitalWrite(buzzer, HIGH);
    delay(75);
    digitalWrite(buzzer, LOW);
    delay(75);  
  }
}

void buzz_pomodoro() {
  digitalWrite(buzzer, HIGH);
  delay(300);
  digitalWrite(buzzer, LOW);
}

void pause() {
  buzz_pause();
  unsigned long pause_start = millis();
  while (digitalRead(button) == HIGH) {
    if (millis() - pause_start >= 500) {
      if (digitalRead(led_pause) == LOW) digitalWrite(led_pause, HIGH);
      else digitalWrite(led_pause, LOW);
      pause_start = millis();
    }
  }
  buzz_pomodoro();  
}

void loop() {
  if (millis() - previous_millis >= interval) {
    countdown-=1;
    write_binary_time(countdown);
    previous_millis = millis();
  }
  
  // Check if the user wants to pause or reset
  if (digitalRead(button) == LOW) {
    unsigned long start_press = millis();
    while (digitalRead(button) == LOW) {};

    // Less than half a second is a pause
    if (millis() - start_press < 500) pause();

    // More than half a second is a reset
    else {
      if (state == 0) {
        countdown = pomodoro +1;
        buzz_pomodoro();
      } else {
        countdown = small_break +1; // Always shorten to small break
        buzz_pause();
      }
      previous_millis = millis();
    }
  }

  // Turn on the pause LED if it's a break
  if (state == 1) digitalWrite(led_pause, HIGH);
  else digitalWrite(led_pause, LOW);

  // If a pomodoro is done, update relevant counters, and start a pause
  if (state == 0 && countdown == 0) {
    number_pomodoros += 1;
    write_pomodoros();
    state = 1;
    // Reset the timer, adding one to correct the off-by-one  
    previous_millis = millis();
    if (number_pomodoros == 4) countdown = big_break + 1;
    else countdown = small_break + 1; 
    buzz_pause();
  }

  // If a pause is done, update relevant counters, and start a pomodoro
  if (state == 1 && countdown == 0) {
    if (number_pomodoros == 4) number_pomodoros = 0;
    write_pomodoros();
    state = 0;
    // Reset the timer, adding one to correct the off-by-one
    previous_millis = millis();
    countdown = pomodoro + 1;
    buzz_pomodoro();
  }
}

Leave a Reply

Your email address will not be published.