Projects

Final Countdown Timer

ATtiny85-Based Interval Timer

Johann Wyss

Issue 40, November 2020

This article includes additional downloadable resources.
Please log in to access.

Log in

An easy to use programmable timer with repeat function to countdown in seconds, minutes, days, or weeks.

BUILD TIME: 1 HOUR
DIFFICULTY RATING: Intermediate

If you were to browse your favourite electronics retail store that sells electronics kits, you will surely see a wide range of kits that are specifically designed for timing applications. This is arguably because a programmable timer is an incredibly versatile piece of equipment used in countless industries.

In automotive, timers can be used to shut down an engine after a set period of time. A turbo timer is a good example, or activating glow plugs on a diesel engine for a specific period after the ignition key is turned.

In plumbing, timers are used to electronically control taps and valves for watering applications. We use them in the home to turn off fans and appliances after a certain amount of time, in fact, once you start imagining uses for an electronic timer, you will likely struggle to think of an industry that does not have electronic timers used for automation purposes.

For the most part, these kits all follow the same basic idea.

Most are programmed using a series of jumper switches which change the resistor and capacitor values attached to a timer/oscillator IC such as the LM555, and others rely on a more complex arrangement of discrete and integrated electronics.

THE BROAD OVERVIEW

For our timer project, we have designed a basic programmable timer based on our favourite ATtiny85 8-pin microcontroller. By using the ATtiny85, we get the flexibility to create a timer with a range anywhere between seconds to days, or potentially even weeks, while using fewer components than the traditional RC based oscillator circuits.

However, it does mean timing needs to be programmed before going out into the field. There aren’t any simple jumper switches to change programming in the timer like some timer kits have, however, to counter this, we have attempted to give our timer a little more flexibility.

It enables you to pre-program four different durations that you may want the timer to be used with. To select one of the four preset times, you simply press the onboard tactile pushbutton. Two onboard LEDs indicate the time setting.

So, for example, you may want a timer that will actuate something every 10 mins but you also may want to use the same timer to control something you want to actuate every hour or 2 hours, or day, etc. It may even be a timer for games night where different participants are given different times.

Our timer project starts with a prototype that uses an Arduino Nano to make programming easy, and then we build our timer using an ATtiny85 with a transistor output to trigger all sorts of devices. In both cases, we have used a breadboard, however, you could apply the same circuit to a perfboard or design your own custom PCB.

HOW IT WORKS

THE ATTINY85 MICROCONTROLLER

To keep costs down we are using the most inexpensive microcontroller we can get from our local electronics components distributor, the ATtiny85.

This micro has 2 digital pins with PWM capabilities and 3 analog pins, which can also double as a digital input or output. There is an additional digital pin (reset) that can be used as an I/O but limits the ability to program with an in-circuit serial programmer (ICSP).

THE ARDUINO MILLIS FUNCTION

The heart of this project is the Arduino Millis() function. This function is an Arduino specific function that simply returns the number of milliseconds that have elapsed since the microcontroller has booted.

It uses an unsigned long 32-bit memory location, and as such, the maximum number it can hold/return is 4,294,967,295. This is roughly equal to 50 days, after which, the variable will return to zero.

In general, the function uses the inbuilt timers on the microcontroller, along with the internal oscillator/resonator to trigger an interrupt. This interrupt is always running in the background of your Arduino sketch and is used for many Arduino specific functions.

A good way to demonstrate this is with the following sketch.

unsigned long MiliSec;
void setup() {
  Serial.begin(9600);
}
void loop() {
  Serial.print("Miliseconds elapsed =:  ");
  MiliSec = millis();
  Serial.println(MiliSec); 
  delay(1000);          
}

In this sketch, we simply read the current value of Millis() and then display it on the serial monitor before having a 1000 millisecond (1 second) delay. If we look at this sketch via the serial monitor, we see the following output.

As expected, the counter starts at zero and on each loop of the program increments by about 1000. We say about 1000 because, as we can see, it’s not perfect and by 15 loops it is out by 5 milliseconds.

However, if we look closely at the timestamp, we can see that there is some variation in how long the program is running. Take, for example, the first three lines on the serial monitor.

We can see the first loop started at 13:37:53.553 or 1:37 pm 53 seconds and 553 milliseconds. We can see that the second line was recorded 994 milliseconds later and line three was 1003 milliseconds after that.

This variation is caused by inaccuracies in the crystal, and as such, this simple timer, like most simple timers, can’t be used for devices which need very precise timing.

However, it is also a symptom of the timer structure itself. We are using a 16MHz internal oscillator/resonator and a prescaler to divide this clock down to 1024µs.

As such, we can’t actually even get a precise millisecond output. This is why quartz wrist watches use an oddly specific 32,768Hz crystal. If you divide 32,768 by 2, 15 times you get precisely 0.9998.

In either case, whilst the millis() function improves in accuracy over longer periods it isn’t an incredibly precise method. If you need a high level of precision, a timer using a real time clock (RTC) featuring a 32,768Hz crystal is what you’re looking for.

To display the current user defined setting, we will use two LEDs. A pushbutton will allow the user to toggle between the predefined settings. Without pressing the pushbutton, both LEDs are off, and in this mode, the timer will be using TIMERA.

If you press the pushbutton once, a red LED will illuminate and this will indicate that you are using TIMERB.

Press the pushbutton once more and the red LED will extinguish and the green LED will illuminate. This indicates you’re using TIMERC.

Press the pushbutton once more and both the red and green LEDs will illuminate showing you’re using TIMERD.

The four timers can be programmed by the user, but we strongly suggest that you make TIMERA your default timer as this is the value the code starts at on boot.

Note: We have made a suggestion at the end of this project showing how you could implement a way to store the previous value into the electronically erasable programmable read only memory (EEPROM).

The Prototype:

Arduino Nano-based Timer

Parts Required:Jaycar
Arduino Nano or Compatible BoardXC4414
1 x 5mm Green LEDZD0170
1 x 5mm Red LEDZD0150
1 x 5mm Blue LEDZD0185
1 x Tactile switchSP0600
3 x 180Ω Resistors*RR0554
1 x 10K Resistor*RR0596
Breadboard Power Supply^PB8819

* Quantity required, may only be sold in packs. A breadboard and prototyping hardware is also required. ^ This power supply is optional however it makes powering the breadboard circuit nice and simple. # Part is not identical to the part we used but a very worthy substitute.

For the prototype, we will use our usual method of creating the project using the Arduino Nano development board, and then transferring / porting the code to work on the ATtiny85. This way, we get the benefit of the easy programming that comes with the Arduino development board, and don’t need to constantly remove the microcontroller for programming and placing it back in circuit for testing.

THE CODE

As you can no doubt see, the electronics for this project are incredibly simple. Everything is done in the microcontroller. For this to work, there are a few user defined variables which you may want to modify so the device suits your unique and specific functional requirements.

For example, the first 4 lines:

const int  TIMERA = 1;                 
  // (00) both Led off = 1 second selection 0
const unsigned long TIMERB = 10;       
  // (01) red led on 1 min
const unsigned long TIMERC = 30;       
  // (10) grn led on 1 hour
const unsigned long TIMERD = 60;       
  // (11) both leds on 12 hours

By default, they are set as follows:

To change the duration of the timer, you simply change the value in the program which represents that timer.

Thus, if you want TIMERA to be triggered one every hour you need to change the value to the number of seconds in an hour, 3600 seconds.

Thus, if you want to calculate hours into second you can just use the equation:

Seconds = hours x 3600

Let’s say you wanted a 2.5 hour timer for TIMERB. The calculation would be:

2.5 x 3600 = 9000

So, you would change TIMERB to 9000.

The next variable of interest is the following line.

int onDuration = 500;
// time in milliseconds output stays high

This line dictates how long the output will stay high after the timer has elapsed.

Note: We kept the code in milliseconds as we were demonstrating with small time frames of 1 second. You are more than likely wanting a device to be powered for much more than half a second. As such you may want to change this variable.

For this variable you will need to convert the unit you’re intending on using. But this time, it will be to milliseconds.

Keep in mind there are:

1000 milliseconds in a second.

60000 in a minute, and

360,000 in an hour.

By modifying these variables you get to dictate the time between triggering as well as the time active, and should be the most common values you’re likely to want to change.

As usual, the entire code will be available for download from our website. For the prototype, you will want the code named: DIYODE_attiny85_timer_nano.ino

That’s it!

With that done, you have a custom four interval timer that you can use to control various devices. We can now show you how to do the same using an ATtiny85.

The Main Build:

ATtiny85-based Timer

Parts Required:Jaycar
1 x ATtiny85ZZ8721
1 × Piezo BuzzerAB3459
1 × 2N2222A NPN Transistor or equivalentZT2298
1 x 1KΩ Resistor*RR0572
1 × 10µF Electrolytic CapacitorRE6066
1 x 0.1µF Capacitor*RC5496

* Quantity required, may only be sold in packs. A breadboard and prototyping hardware is also required.

Follow the Fritzing diagram shown here to wire the circuit for an ATtiny85.

PROGRAMMING THE ATTINY85

To program the ATtiny microcontroller, we used the DIYODE ATDEV kit which you can purchase from Altronics (K9815). You can find information on this project in Issue 14 or via the following link: https://diyodemag.com/projects/atdev_for_attiny

This kit makes programming the ATtiny nice and simple by using an Arduino Uno as an in-circuit serial programmer. It isn’t completely necessary though. If you don’t have that kit you can still program the ATtiny using an Arduino Uno. Simply refer to the same link shown above.

Making it do things

Having a timer that flashes an LED when the time runs out can be quite limiting. It may be fine while playing board games, but isn’t practical for most practical timer applications. To make our timer a little more functional, we need to be able to actuate a device. In this section, we will very briefly modify the circuit to allow for a little more power.

As you can see in the Fritzing diagram, we have made a very slight modification to the circuit by adding a piezo buzzer and an NPN transistor.

The buzzer is to simulate a small load, which could be a relay coil or actuator, etc.

The ATtiny85 can only output a maximum current of 40mA from any I/O pin before damage could occur. This is much lower than you would expect from a relay coil or other such electromechanical switch. This means we need a way to increase the current output to a level that would suffice. For this, we will use 2N2222A NPN bipolar junction transistor (BJT) as shown in the schematic here.

Note: We are showing the buzzer here simply as a resistive load. The buzzer can be replaced with a relay coil, actuator, or even a small DC motor.

In this situation, the microcontroller will provide about 5mA into the base of the transistor and depending on the hfe for the specific 2N2222 which has a range between 35 and 300 will amplify the current.. You can increase Ic by reducing Rb. Note the maximum continuous collector current for a 2N2222 is 600mA, so you want to keep it below this.

For us, in this orientation, we were able to get a max Ic of about 100mA with a load of about 50Ω. If you need a higher switching current then you may want to change the BJT properties.

Where to from here?

We’ve provided you with the basis of a simple programmable timer that you can expand upon. The transistor output enables you to connect to various devices.

If your application is important, you should consider the following because the timer will revert back to its default state if power is lost. In our example, that would mean it defaults to a 1 second timer with half second duration. If you had this connected to a sprinkler system, you wouldn’t want your sprinkler tap actuating at such a fast rate. It would waste water and possibly damage your electronic valve.

An ideal solution to this would be to implement a system that stores the user value to the electrically erasable programmable read-only memory (EEPROM) of the microcontroller. The ATtiny85 microcontroller at the heart of this project has three types of memory:

8KB OF FLASH MEMORY: For program space. This is the memory that contains your program.

512 BYTES OF SRAM: Static Random Access Memory (SRAM), which does not hold memory when power is removed. This is the memory where your variables are manipulated.

512 BYTES OF EEPROM: Electronically erasable programmable read-only memory (EEPROM). This memory space can be used to store long term data even after power to the device is removed.

Both the EEPROM and flash memory are non-volatile memory. This means that the data stored in them is retained even after a power cycle, whereas, the information stored in the SRAM is lost anytime power is removed.

You can only write to the flash memory / PROGMEM during the program burn time, and thus, it’s not possible to manipulate the data once the program is running.

This means, if we want the ability to recall the previous state on bootup, we need the ability to read and write to the EEPROM. The good news, of course, is that this is a trivial task thanks to the EEPROM library.

To read from the EEPROM we can simply call the EEPROM read function:

EEPROM.read(x);

Where: x is an 8-bit value between 0 and 255, representing the address in the EEPROM that we are requesting the data from. For this hypothetical, we will arbitrarily pick address 100.

Similarly, we can write to the EEPROM using the EEPROM write function:

EEPROM.write(x, y);

Where x is the address and y is the value we wish to store in that address.

In practice, a program that can read and write to the EEPROM will look like this.

#include <EEPROM.h>
void setup() {
  Serial.begin(9600);
  EEPROM.write(100, 25);
  delay(100);
  Serial.print(EEPROM.
read(100));
  Serial.println();
}
void loop() {
    // Nothing to do here
  }

This program populates the EEPROM address 100 with the value 25 and then reads that value and displays it on the serial monitor.

You may notice that we placed the code in the setup loop. This is to limit the number of times we write to the address to only once per boot.

This is because the EEPROM has a limited number of write cycles/lifespan. The datasheet states “Write/Erase Cycles: 10,000 Flash/100,000 EEPROM”.

This means the microcontroller is only guaranteed to handle 100,000 erases/writes to any EEPROM address.

You should be careful how you write your program, as an improperly written code could easily use up that lifespan in a matter of seconds destroying that address.

If you’re intending on writing to the EEPROM much more often than this, you need to program in a way that the address will change to the next possible address when the 100,000 cycles have been exceeded. This is called EEPROM wear levelling.

To add EEPROM / recall functionality, we simply need a way to write the current selection to the EEPROM for it to be recalled on boot up and revert to that state.

One way we could do this is to detect if the button has been held down for, let’s say, 5 seconds. If it has the program, it could then write the current value in the selection variable to the EEPROM.

On boot, it can then take the stored value in the EEPROM and apply it to the variable selection so that on boot up the timer has the same duration.

This is one way to implement such a recall function, but we would be keen to see how you approach it. Be sure to share your thoughts on our webpage.

Of course, if you only want it to work at a single specific time you can just set TIMERA to that value and tie the button pin permanently to ground via a 10K resistor.

That would nullify any need for such a fix.

Johann Wyss

Johann Wyss

DIYODE Staff Technical Writer