Categories
Arduino electronics hardware software

Relay Timer with LED Display

Relay_Timer_Ortho_ViewAs part of a bigger project, I needed to make a timer that would activate a relay for a set time to switch power on/off to another device. Rather than buy one I decided to build my own from electronic bits and pieces that were on hand in my workshop, wrote some software for a ‘spare’ Arduino Pro Mini that was lying around and packaged it all up in a small box. It turned out to be quite functional, so I decided to document the build in case someone else finds this useful.

What have I got?

I wanted to minimize the number of components to make this project and use what I had at hand. Looking through my workshop supplies I settled on an assortment of modules and components:

  • Arduino Pro Mini as the brains of the operation. As this was going to fit in a small box I needed a small form factor, but basically any Arduino should be able to run the software.
  • 4 digit seven segment display. Mine was a common cathode model from Tayda Electronics. Any similar type will do, including common anode, as the software can be made to compensate for any differences.
  • Rotary encoder with built-in switch. These are commonly available – mine are component-only encoders from eBay. These from ICStation are similar, sold as modules. I also had a plastic knob to fit the encoder.
  • Relay module. These are also very common, and I had one left over from a previous experiment. This one from ICStation is similar. Basically it is triggered on/off by the Arduino software and needs to be able to switch the voltage and current that you expect to be controlling.
  • Buck Converter used to power the Arduino Mini Pro and the display. This takes in up to 24V DC and converts it to 5V output. The model I have is good for up to 2A, which is plenty for the display and processor.
  • A small plastic box to house the project and a couple of 2.1mm DC power jack sockets.
  • A 12V 3A power supply that was rescued from the dump sometime in the past.

System Design

With the basic kit of parts identified, time to work out how they will hang together – a system block diagram – that will help in the software design and putting it all together later.

Relay_Timer_Block_Diagram
System Block Diagram

To minimize the number of components, I decided to use the Mini Pro to drive the display directly, multiplexing each digit every time through loop() to create a Persistence of Vision (POV) effect. This makes it look like the digits are illuminated when in fact they spend most of the time turned off.

As the first pass of the design I included a 74HC595 serial to parallel buffer to drive the segments as I thought there would not be enough spare digital I/O to run the display directly (the first implementation of the software uses the 595 and SPI). Subsequently, some thinking allowed me to free up enough I/O to enable me to use direct connections between the Arduino I/O and the display, removing the need for the 595.

This also led me to decide I could get away without current limiting resistors for each display segment, as the average power through each segment is quite low, further reducing the component count.

relay_timer_schematic
Schematic – Click to enlarge

The rotary encoder and the selection switch are also wired directly to Arduino inputs, using the built-in pull-up resistors to stabilize the signals. I have existing (and much used) libraries for these simple devices, so integrating them into the system was straightforward.

Finally, the output relay needs a single output to toggle it on/off. My relay seemed to work well off the Arduino output, further reducing component count.

Software Design

The software needs to take care of 3 major functions – running the LED display, managing the user interface and executing the timer.

The LED display is multiplexed and all digits are refreshed every time through the loop() function. For each digit, the LED segments representing the current time, or message, are illuminated for a very short time. A circuit is created by setting the digit selection I/O HIGH (or LOW, depending on whether common anode or cathode) and the corresponding segments to the opposite setting if they are on – the voltage difference then allows current to flow between the common and the segments. The segment patterns used for displaying the LED segments are stored in look up tables and include all the digits and the alpha characters needed for this project.

The remaining two functions (User interface and Timer) are built into the Finite State Machine (FSM) that is also executed each time through loop().

Relay_Timer_State_DiagramThe FSM transition diagram shown on the left has 6 states – INIT, IDLE, START, RUNNING, PAUSE and END – and is implemented in the code as a switch/case FSM.

The INIT state is used to initialize an actual time register from the encoder setpoint. Each encoder ‘click’ corresponds to a (compile time configurable) fixed amount of time. The encoder setting is maintained separately from the timer value so that one timer value can be used for display and countdown without ‘forgetting’ the current set point. Some boundary checks are also carried out to ensure the set point does not exceed the maximum displayable (99 minutes and 99 seconds). Setting the encoder ‘click’ to larger values reduces the timer resolution but allows for faster setting of any time, as the encoder needs fewer rotations.

The IDLE state is one where the FSM waits for user input to either

  • start the timer with a switch press, progressing to the START state, or
  • change the setpoint using the encoder, going back the INIT state to recalculate the displayed timer value.

The START state activates the relay output and initializes the internal counting timer for this run of the relay timer. It then progresses to the RUNNING state.

In the RUNNING state the main function is to detect when one second has elapsed and adjust the display timer values accordingly. When the timer reaches zero the FSM progresses to the END state. Whilst in the RUNNING state, the key switch is also checked to see if a press is detected, in which case the FSM moves to the PAUSE state.

PAUSE suspends all timer processing and either moves back to RUNNING on a short key press or directly to END on a long press.

The END state is the end of the timer sequence, so the relay output is turned off and the FSM goes back to the INIT state.

The software sketch can be found at my code repository.

Putting it all Together

I decided to make a custom lid for the plastic box using the technique I described in a previous blog. I pushed the process too quickly and some of the white paint bled under the acrylic paper protection. Good enough for my own use…

The final step was to wire together all the hardware components and work out how to fit them in the box. Modules and parts were hot-glued in place and it all seemed to fit nicely. I put the Mini Pro on the base of the lid as it drastically reduced the number of wires between the lid and the main part of the box. The final assembly shows that this is not the neatest project, but once the box is shut, no-one can see the mess!

 

Relay_Timer_Open_View - labelled
Final Assembly

27 replies on “Relay Timer with LED Display”

Hello Marco,
Thank you so much for sharing your timer. it works wonderfully. I want to use this timer to control a pump and I want to add a function.
I started to learn Arduino thanks to your project 3 weeks ago. I’m starting to get lost and I wonder if this operation is feasible:

-Addition of an input push button
-Press push button > start the timer at 1H
-During the count> the pump activates every 7 minutes for 1 min
-End of the count> returns to its initial state

I can’t figure out where to make the change without breaking everything…
Do I have to create a new state in “enum state” to call it in “switch state”?
Can you give me some idea please?
Sincerely, MR

Like

You are on the right track. The additional states are probably best thought of as a different ‘set’ as they work independently from the current set – really the functionality is totally different. I would create one state for ‘waiting to activate’ simply counting down 7 minutes, then another state while ‘activated’ for one minute and switch between the two as many times as will fit inside your 60 minutes, then exit bask to whatever the idle state is.

Like

Hi Marco,
This is a really nice project. I am new to Arduino stuffs and this is my first project with LED display multiplexing coding. I did your setup and load the code into Arduino Nano. It compiled without any error and loaded the code into the Arduino Nano, but it just displays garbage. My debugs up to now: 1) I unplug D4 on the Arduino and the leftmost digit turn off, unplug D5 and the next leftmost digit turn off, so on for D6 and D7, so the common cathode connections are OK. 2) I unplug D12, D13, A0 to A5 and the proper segment turns off, so the segment connections are OK. 3) I turn the rotary encoder and the display changes (but unreadable). 4) I click on the push button and it starts counting (unreadable of course). What possibly wrong? Please Help.
Thanks
Duc

Like

This is not the right forum for tech support as it does not allow attachments. If you want help, please raise a query on the Arduino Forum (Programming section is a good one) and let me know the link to the post so I can respond there.
‘displays garbage’ is not a really helpful description. Perhaps on the forum post you can post a photo of the display.

Like

I tried to do that (‘ static int8_t minutes = 8, seconds = 0;), but still I have 00.00 on startup.

Like

Looks like the values are actually initialized in the INIT case, so you need to put an appropriate value in setPoint

‘ uint16_t totalSeconds = setPoint * TIME_INTERVAL;
‘ seconds = totalSeconds % 60;
‘ minutes = totalSeconds / 60;

Like

Thank you very much for help. If I type setpoint = [my value] the counter starts properly, but then the encoder function does not work. I would like to start with 8min0sec and add or subtract time with an encoder at the same time.

Like

Sorry for another reply. 🙂 I did it! 😀
void loop()
{
static state_t state = S_INIT;
static uint16_t setPoint = 16; //default: 0 – this is my mod for “TIME_INTERVAL = 30;”
static int8_t minutes = 0, seconds = 0;
static uint32_t timeStart = 0;
static boolean inMessage = false;

I also added a buzzer that signals the end of the countdown. Thank you very much 🙂

Like

Hi
I’m having an issue when trying to verify the code.
It looks like it won’t acknowledge the MD Switch library even though I uploaded the latest version from Github and included it in the Arduino libraries.

Below is the error message, any suggestions?

Thanks
Shoukry

Arduino: 1.8.12 (Windows 10), Board: “Arduino Uno”

Timer_ver_2:69:10: fatal error: MD_REncoder.h: No such file or directory

#include

Multiple libraries were found for “MD_UISwitch.h”
^~~~~~~~~~~~~~~

Used: C:\Users\Robotgrass\Documents\Arduino\libraries\MD_UISwitch
compilation terminated.

Not used: C:\Users\Robotgrass\Documents\Arduino\libraries\MD_UISwitch-master
exit status 1
MD_REncoder.h: No such file or directory

This report would have more information with
“Show verbose output during compilation”
option enabled in File -> Preferences.

Like

Two problems.
1. You have not installed MD_REncoder library.
2. You seem to have more than one UISwitch library installed.

Suggest you remove all UISwitch you can find and reinstall using the Arduino IDE library manager.

Like

Thank you Marco.

I used the add zip function to add:
MD_REncoder, MD_KeySwitch, & MD_UISwitch to the Arduino Library list, and it compiled.

Like

hello marco_c, could you help me modify the code, to save the last programmed time in eeprom memory, so that when the arduino is disconnected and connected it starts from that last programmed time, for example 20 sec, or 10 sec or the last time selected.
please could you help me

Like

When the software goes into ‘start’ mode for a timing, just save the EEPROM time then. In setup() load the time from the eeprom instead of leaving it at 0. You will need to deal with the situation when the time has not been defined yet (ie, first time run).

Like

Hello, I have made this, it works nice, but i have made a switch on it that you can see to count it down (normal mode) or count it UP. (you can switch while it is counting. up of down). And the timer can be set in seconds or in minutes now. That is easy for the greater times.
The timer can be set in seconds, not the 5 seconds.
And by the original software you have always 1 second more than you have set. That is also corrected. (tested with a counter, to see on our facebook site) https://www.facebook.com/arduino.leiderdorp.9
greetings Ruud vd Meer.

Liked by 1 person

Marco,

There is a problem with the schematic. The (d1,2,3,4) numbers of the display are wrong.
In the program the numbers are good.
Therefore the number direction is wrong.
In the schematic, the lsb is left and the msb is on the right.
But the numbers in the sw are good.

Small problem. Thanks for the nice and simple working program.

Like

Same problem was here.

I just modified 1 line in the source.

old: const uint8_t digitPin[] = {4, 5, ,6, 7};
new: const uint8_t digitPin[] = {7, 6, 5, 4};

Like

Built and working perfectly, thanks for sharing.
I tried to use an OLED display, (I did not have one of 7 segments) but the time needed for the OLED is very long and it did not work correctly, I got one of 7 segments and everything was fine.

Like

Yes!! I made it ! Thank you so much for this simple and wonderful project.
Everything is working fine. Except the long press to end the timer. Pause is working just fine. Later when i am long pressing the button it will display pause and remain time back and forth !!
What is the problem? I am using the second version that is display is driven directly by Arduino.
Can you please help me out ?
Thank you in advance.

Like

It may be that the KeySwitch library is getting into auto-repeat mode. This project was done some time ago (for me). It is possible that auto repeat was not a feature in the library at the time. In any case, try putting SW.enableRepeat(FALSE) just after SW.begin() in setup() and see if that helps.

Like

Leave a comment