Introduction: Arduino Timer and Triggers - Automated Shutters

About: Check out my site at http://odedc.net

We live in a hot area where it can get quite unpleasant even at night, so we often leave the shutters open during the night and close them early in the morning when the sun comes out.

I wanted to automate the closing of the shutters without connecting to the 220V electricity system so I devised an apparatus that would mechanically close the shutters by pressing on the electricity switches. The smarts behind this solution is an Arduino device that acts like an alarm clock but instead of sounding an alarm, closes the shutters :)

The code behind the solution is generic and can be used as a trigger to any other operations / events you might want to perform. It is basically a digital clock where you set the trigger time (alarm) and an event is fired on that time.

In this instructable I will not describe the mechanical apparatus that turns off the shutters and focus on the Arduino circuit and code since it is general purpose and can be reused in other projects as well.

Step 1: The Components

The materials are very simple::

  1. Arduino (Uno, Nano or other)
  2. Real Time Clock (RTC) module - sample - I used a DS1302 module but you can also modify the code to use a DS3231 module.
  3. 128x64 0.96'' monochrome LCD screen - sample - you can use any other screen but you'd need to modify the code for that. Note that this one supports I2C while others support SPI.
  4. Servo motor (3 wires) - depending on what you want to do within the event. I used a motor to close the shutters.
  5. 4x TACT buttons
  6. 1x toggle switch
  7. Wires, breadboard

Note that you can change the code to use different digital pins.

Step 2: The Schematics

Here is the diagram for the entire project:

There are 4 buttons in this diagram:

  1. SET button - for setting the time
  2. TRIGGER SET - for setting the trigger time
  3. UP / DOWN - increase/decrease hours/minutes. The UP button is also used to wake up the screen if the screen saver is on

The switch is used to indicate whether the trigger is ON or OFF.

Step 3: Connecting and Using the RTC Module

The RTC module (DS1302) uses a library called DS1302 that you need to install on your Arduino IDE. It provides all the basic functions to control the module.

The RTC module connects using 5 wires:

  1. VCC - 5V from the Arduino
  2. GND - the Arduino GND
  3. SCLK - connects to a digital pin
  4. DATA - connects to a digital pin
  5. RESET (also called CE) - connects to a digital pin

The RTC comes with 31 bytes of RAM that can be used by applications. I used this memory space to store the trigger (alarm) time as well as an indication if the alarm is set or not. The advantage of this approach is that even if you disconnect your Arduino from the power supply, this data remains available (this is a non volatile memory since the module has a separate battery).

As you can see in the code, I defined 3 offsets within the RAM. You can change these offset if you like.

  • RAM_TRIGGER_SET_BYTE - where the trigger set flag is written
  • RAM_ALARM_HOUR_BYTE - where the trigger's hour is written
  • RAM_ALARM_MINUTE_BYTE - where the trigger's minutes are written
///////////////////////// DS1302 RTC<br>#define RTC_CE_PIN        2
#define RTC_DATA_PIN    	3
#define RTC_SCLK_PIN            4
#define RAM_SIZE_BYTES          31 /* Don't change - value from library */
#define RAM_TRIGGER_SET_BYTE    30
#define RAM_ALARM_HOUR_BYTE     0
#define RAM_ALARM_MINUTE_BYTE   4

DS1302 gRtc (RTC_CE_PIN, RTC_DATA_PIN, RTC_SCLK_PIN);
RTCTime   gRTCTime;		// Stores the current time

Initializing RTC is very simple. Create a DS1302 instance and do the following in the setup function:

gRtc.halt(false);
gRtc.writeProtect(false);

To get the current time use:

RTCTime gRTCTime;   // defined globally

Time t = gRtc.getTime();
gRTCTime.setTime(&t);

As you can see, I converted the Time class to a RTCTime class (implementation included in the project) that provides additional methods and capabilities.

You can use the RTCTime class to check time intervals (has the time passed, is it before...), to increment or decrement hours and minutes, to check if the time is valid (sometime the RTC module outputs garbage) and to format it as a string.

class RTCTime : public Time {
public: 
	RTCTime();
	RTCTime(Time *t);
	bool setTime(Time *t);
	bool isPassed(Time *tm);
	bool isBefore(Time *tm);
	void increaseHour();
	void decreaseHour();
	void decreaseMinute();
	bool isValid();
	void toStringHHMM(char buffer[], char *desc = "");
	void toStringHHMMSS(char buffer[], char *desc = "");
	void toString(char buffer[]);
};

Storing values in RAM

The DS1302 library provides 2 methods for reading and writing data to the module's RAM. The first is a burst mode where you write 31 bytes at ones and the other, which I used, is handling a single byte every time.

Here is an example of how to read a specific byte from RAM (in this case this is the flag that indicates whether the trigger is on or off)

//////////////////////////////////////////////////////////////////////////
// Loads the trigger (alarm) time from DS1302 RAM
// t - pointer to a time class
//////////////////////////////////////////////////////////////////////////
void loadTriggerTime(RTCTime *t) {
  t->hour = gRtc.peek(RAM_ALARM_HOUR_BYTE);
  t->min = gRtc.peek(RAM_ALARM_MINUTE_BYTE);
}

Writing data is very simple as well:

//////////////////////////////////////////////////////////////////////////
// Sets the trigger to on and off
// on - true for on
//////////////////////////////////////////////////////////////////////////
void setTriggerOnOff(bool on) {
  gRtc.poke(RAM_TRIGGER_SET_BYTE,(on ? 1 : 0));
}

Step 4: Connecting the Screen

I used an 0.96' mono screen using the great u8g2 library. The screen requires 4 wires:

  1. VCC - 5v from Arduino
  2. GND - Arduino GND
  3. CLK - digital pin
  4. DAT - digital pin

You need to choose the right u8g2 class for the specific screen that you are using and initialize it (see the u8g2 examples for all the options). Here is the one that I used:

#define LCD_CLK_PIN     10
#define LCD_DAT_PIN     9
U8G2_SSD1306_128X64_NONAME_F_SW_I2C gLCDScreen(U8G2_R0, LCD_CLK_PIN, LCD_DAT_PIN, U8X8_PIN_NONE);

This creates a class instance (gLCDScreen) through which we will control the screen.

The LCDFuncs.ino file contains all the functions for interacting with the screen. Note that every time before accessing the screen, the prepareLCD() function is called to prepare the screen for drawing.

void prepareLCD() {
  gLCDScreen.enableUTF8Print();   
  gLCDScreen.setFont(u8g2_font_ncenB08_tf);
  gLCDScreen.setFontDirection(0);
  gLCDScreen.setFontMode(0);
  gLCDScreen.setFontPosTop();
}

The screen saver

The application implements a screen saver functionality that turns off the screen after a certain time of no activity.

The screen saver can be disabled using the following define statement:

#define SCREEN_SAVER_ON     true

Here are the two functions controlling the screen saver

//////////////////////////////////////////////////////////////////////////
// Reset the screen saver timer to now
//////////////////////////////////////////////////////////////////////////
void resetScreenSaverTime() {
  gScreenOn = true;
  gLastClickMillis = millis();  
  gLCDScreen.setPowerSave(0);
  clearDisplay();
  delay(DELAY_RESET_SAVER);
}

//////////////////////////////////////////////////////////////////////////
// Starts the screen saver
//////////////////////////////////////////////////////////////////////////
void startScreenSaver() {
  if (gScreenOn && SCREEN_SAVER_ON) {
    gLCDScreen.setPowerSave(1);
    gScreenOn = false;
  }
}

We use the library's function setPowerSave() to turn on and off the screen but manage the timing by ourselves.

Writing to the screen is straightforward. I created a few functions that write to different portions of the screen. Here is one of them that prints the current time:

void printLCD_CurrentTime(bool autoDelete) {

if (!gScreenOn) // If screen saver on, return
    return;

  RTCTime t;
  Time now = gRtc.getTime();
  t.setTime(&now);
  if (!t.isValid()) {
    CONSOLE(F("Current time is not valid - aborting print"));
    return;
  }
  // Print current time
  char buffer[TIME_STR_BUFFER_SIZE];
  t.toStringHHMM(buffer);
  gLCDScreen.setFont(LCD_LARGE_TIME_FONT);
  int col = LCD_Centralize(buffer);
  gLCDScreen.drawUTF8(col,LCD_LARGE_TIME_ROW, buffer);
  
  // Print trigger time
  loadTriggerTime(&t);
  t.toStringHHMM(buffer);
  gLCDScreen.setFont(LCD_TIME_FONT);
  gLCDScreen.drawUTF8(LCD_TIME_COL,LCD_TRIGGER_ROW, buffer);
  if (gTriggerOn)
    gLCDScreen.drawUTF8(LCD_TRIGGER_COL,LCD_TRIGGER_ROW, "ON");
  else
    gLCDScreen.drawStr(LCD_TRIGGER_COL,LCD_TRIGGER_ROW, "OFF");
  LCD_DrawScreenSaverStatus();
  
  gLCDScreen.sendBuffer();
  if (autoDelete) {
    delay(TIME_TO_AUTO_CLEAR_LCD);
    gLCDScreen.clear();
  }
}

Step 5: Connecting the Servo Motor

Servo motors are easy to use with Arduino using the Servo library. We initialize the motor during setup()

///////////////////////// Servo
#define SERVO_PIN                 5
#define SERVO_CLOSED_POSITION     180
#define SERVO_INIT_POSITION       0

Servo shutterServo;

and move it when an event is fired:

void closeShutters() {
  if (gShuterClosed)
    return;
  shutterServo.attach(SERVO_PIN);  
  shutterServo.write(SERVO_CLOSED_POSITION);
  delay(1000);
  shutterServo.detach();
  gShuterClosed = true;  
}

void initServo() {
  shutterServo.attach(SERVO_PIN);  
  shutterServo.write(SERVO_INIT_POSITION);  
  delay(1000);
  shutterServo.detach();
  gShuterClosed = false;
}

Note that after moving the servo to a new position, we detach from it to cut off the power. This will also eliminate the noise coming out of the Servo engine even when not in use.

Step 6: The Main Loop

In the main loop, we take several steps:

  1. Get the current time, making sure it is a valid time
  2. Present the current time on the screen
  3. Check buttons' state and act upon it
  4. Activate screen saver if needed
  5. Fire an event if time has come

Setting the clock and trigger time

If either the SET TIME button or the SET TRIGGER button are clicked, we start an infinite while loop in which we detect the UP, DOWN and SET buttons and act accordingly to set hour, minutes and save the new time.

Here is the function that manages the loop for setting the time:

void handleSetTime() {  
  CONSOLE("handle set time");
  // Print header on LCD
  printLCD_Header("Set Time (hour)",true);  
  // Show the current ime
  RTCTime theTime;
  theTime.setTime(&(gRtc.getTime()));
  printLCD_SetTime(&theTime,false);
  // Wait for additional input
  bool hourSet = true;  
  while (true) {
    // Increase time
    if (digitalRead(BUTTON_UP) == BUTTON_PRESSED) {
      if (hourSet)
        theTime.increaseHour();
      else
        theTime.increaseMinute();
      printLCD_SetTime(&theTime, false);
    }
    // Decrease time
    if (digitalRead(BUTTON_DOWN) == BUTTON_PRESSED) {
      if (hourSet)
        theTime.decreaseHour();
      else
        theTime.decreaseMinute();
      printLCD_SetTime(&theTime,false);
   }
   // if the set button was pressed and we are setting minutes,
   // we need to save the new time
   if (!hourSet && digitalRead(BUTTON_SET) == BUTTON_PRESSED) {
      CONSOLE("Save time");
      gRtc.setTime(theTime.hour, theTime.min, 0);
      printLCD_Header("Time saved",true);  
      printLCD_SetTime(&theTime,false);
      delay(TIME_SHOW_DELAY);
      resetScreenSaverTime();
      clearDisplay();
      return;
   }
   // Set button is pressed and we are in Hour settings, move to minute settings
   if (hourSet && digitalRead(BUTTON_SET) == BUTTON_PRESSED) {
      hourSet = false;
      clearDisplay();
      gRtc.setTime(theTime.hour, theTime.min, 0);
      printLCD_Header("Set Time (min)",true);  
      RTCTime theTime;
      theTime.setTime(&(gRtc.getTime()));
      printLCD_SetTime(&theTime,false);
      delay(RTC_DELAY);
   }
  } // while
}

Step 7: The Buttons

We use 4 TACT buttons and one toggle switch. All of them are initialized with INPUT_PULLUP to use the Arduino's internal pull-up resistors, eliminating the need to add resistors to the circuit.

Setting up the buttons:

  // Init GPIO pins
  pinMode(BUTTON_SET, INPUT_PULLUP);
  pinMode(BUTTON_SET_TRIGGER, INPUT_PULLUP);
  pinMode(BUTTON_UP, INPUT_PULLUP);
  pinMode(BUTTON_DOWN, INPUT_PULLUP);
  pinMode(BUTTON_TRIGGER_ON, INPUT_PULLUP);

Reading the button's state:

#define BUTTON_PRESSED      0

bool setButtonPressed = (digitalRead(BUTTON_SET) == BUTTON_PRESSED);

Step 8: The Code

Attached is the code for the entire project. Have fun !!!

Step 9: Final Pictures (without Casing)

This is how it looks like without the casing and mechanical apparatus, front and back.