Hardware components | ||||||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
I could not find an easy way how to open a window with low cost components, so I decided to share my solution. Previously I made a CO2Cube that measures exhaled air in the room and warns me when to open a window, because more CO2 in the air operates like a poison in the body (CO2Cube is published too). This project opens and closes a window automaticaly. It is welcome in the winter season, when we always have a fresh air during sleeping, but we do not risk a very cold room.
The main components are Arduino Nano and 12V stepper motor 28BYJ-48 with ULN2003 driver. I joined a screw 10x140 to the motor with a 5mm hole in the screw head and a strong epoxy glue. The screw nut has an iron part for the connection with the window and a stopper from another side.
The stopper on the screw nut pushes a switch that stops the motor when the window is already closed. The magnet on the window is a brilliant part that allows to take off the device from the window very quickly and open it manualy.
Very important: the motor and the magnet on the window must be able to rotate independantly. The magnet is connected with the window with 1 screw in the middle so it rotates. The motor is on the pedestal that can spin around too.
This stepper motor is very slow and opens the window around 2 minutes, but it is quite quiet and does its job so it is OK for me during my sleeping.
Below I attach all the actual code without modifications. It containes more tabs so you must load all of them. Adapt them for your needs as you like.The tab "PIN_scheme" describes all connections - the final mess you can see here:
#define dataPin A2 //PIN 14 of 1 shift register
#define latchPin A3 //PIN 12 of both shift registers
#define clockPin A1 //PIN 11 of both shift registers
#define resetPin 10 //PIN 10 of both shift registers
/*
* 2 Shift registers 74HC595 for displaying the clock on the 7segment4digit display
*
* Q0 - PIN 15 of shift register I
* Q1-Q7 - PINS 1-7 of shift register I
* +5V - PIN 16 of shift register
* GND - PIN 8 of shift register
* PIN 9 of shift register I to PIN 14 of shift register II
* Both shift registers share latchPin, clockPin, resetPin
*
* MOSFET IRF520N:
* Gates to pin 1-4 of shift register II
* Sources to ground
* Drains do Digit1-Digit4 of 7segmet4digit display
*/
void InitializeShiftRegister() {
DDRC |= B00001110; //pinMode(dataPin, OUTPUT);
//pinMode(clockPin, OUTPUT);
//pinMode(latchPin, OUTPUT);
DDRB |= B00000100; //pinMode(resetPin, OUTPUT);
ClearShiftRegister(); //digitalWrite(resetPin, HIGH);
//digitalWrite(latchPin, HIGH);
}
void SetShiftRegister(byte digitID, byte digitValue) {
PORTC &= B11110101; //digitalWrite(latchPin, LOW);
//digitalWrite(clockPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, digitID); //shift out high byte, alternative: LSBFIRST / MSBFIRST
PORTC &= B11111101; //digitalWrite(clockPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, digitValue); //shift out low byte, alternative: LSBFIRST / MSBFIRST
PORTC |= B00001000; //digitalWrite(latchPin, HIGH);
}
void ClearShiftRegister() {
PORTB &= B11111011; //digitalWrite(resetPin, LOW);
PORTB |= B00000100; //digitalWrite(resetPin, HIGH);
PORTC &= B11110111; //digitalWrite(latchPin, LOW);
PORTC |= B00001000; //digitalWrite(latchPin, HIGH);
}
/*
* Receives the signal from MyCO2Cube and opens / closes a window
*/
#define releaseButton 3
bool openWindow = false;
bool released = false;
volatile bool releaseButtonPushed = false;
bool set = false;
bool locked = false;
unsigned long lockTime, infoTime;
int lockedInfo;
void setup() {
InitializeOledDisplay();
InitializeMyClock(); //contains InitializeShiftRegister();
InitializeReceiver();
InitializeStepMotor();
InitializeFan();
InitializeCO2CubeWindow();
DisplayText("SETTING", false, true);
}
void InitializeCO2CubeWindow() {
pinMode(releaseButton, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(releaseButton), ReleaseButtonPushed, LOW);
lockTime = millis();
}
void loop() {
if (set) {
GetSignal();
if (releaseButtonPushed) { //switches between Normal, Release and Locked mode
detachInterrupt(digitalPinToInterrupt(releaseButton));
releaseButtonPushed = false;
if (locked) {
locked = false;
TurnMyClockOFF();
DisplayOpenClose();
}
else {
released = !released;
if ((millis() - lockTime) < 800) locked = true;
lockTime = millis();
if (locked) {
DisplayText("LOCKED", false, true);
released = false;
infoTime = millis();
lockedInfo = 0;
} else {
if (released) DisplayText("RELEASE", false, true);
else {
DisplayOpenClose();
}
TurnMyClockOFF();
}
}
delay(700);
attachInterrupt(digitalPinToInterrupt(releaseButton), ReleaseButtonPushed, LOW);
}
if (locked) {
CloseWindow();
if (millis() - infoTime > 7000) {
DisplayLockedInfo(); //shows some statistics
infoTime = millis();
}
}
else {
if (released) ReleaseWindow();
else {
if (openWindow) OpenWindow();
else CloseWindow();
}
}
DisplayMyClock(); //shows the clock
}
else CloseWindow(); //sets device after turning on
}
void ReleaseButtonPushed() {
releaseButtonPushed = true;
}
#define fanONOFFPin A0
bool fanON = true;
void InitializeFan() {
pinMode(fanONOFFPin, OUTPUT);
TurnFanOFF();
}
void TurnFanON() {
if (!fanON) {
digitalWrite(fanONOFFPin, HIGH);
fanON = true;
}
}
void TurnFanOFF() {
if (fanON) {
digitalWrite(fanONOFFPin, LOW);
fanON = false;
}
}
/*
* MyClock for 7segment4digit display:
* 1st up row: D4, A, F, D3, D2, B
* 2nd bottom row: E, D, DP, C, G, D1
*
* DS1307 rtc module
*
* !!! VBAT PIN must be connected to +3V of the lithium battery
*/
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 rtc;
DateTime myTime, openTime, lastTime;
bool openTimeNotSet = true;
int openCount = 0;
byte digitID[4] = {B00000010, B00000100, B00001000, B00010000}; //pins 1-4 of shift register II to Mosfet IRF520N gates, Byte = {0, 0, 0, D4, D3, D2, D1, 0}
byte digitValue[4];
byte number[11] = {B11010111, B00010001, B11001101, B01011101, B00011011, B01011110, B11011110, B00010101, B11011111, B01011111, B00001000}; //pins 15 & 1-8 of shift register I, Byte = {E, D, DP, C, G, A, F, B}
byte dotValue = B00100000;
int k = 0;
int minuteHigh, minuteLow, hourHigh, hourLow, lastMinute = 1;
bool myClockON = true;
unsigned long startTime, actualTime;
void InitializeMyClock() {
InitializeShiftRegister();
if (!rtc.begin()) {
DisplayText("ClkERR", false, true);
while(1);
}
//rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); //sets the RTC to the date & time from the PC
digitValue[0] = number[10];
digitValue[1] = number[10];
digitValue[2] = number[10] | dotValue;
digitValue[3] = number[10];
TurnMyClockOFF();
startTime = millis();
}
void DisplayMyClock() {
if (myClockON) {
actualTime = millis();
if (((actualTime - startTime) > 10000) || (actualTime < startTime)) {
GetTime();
startTime = millis();
}
SetShiftRegister(digitID[k], digitValue[k]);
k++; if (k > 3) k = 0;
}
}
void GetTime() {
myTime = rtc.now();
if (myTime.minute() != lastMinute) {
lastMinute = myTime.minute();
minuteHigh = lastMinute / 10;
minuteLow = lastMinute - (minuteHigh * 10);
hourHigh = myTime.hour() / 10;
hourLow = myTime.hour() - (hourHigh * 10);
digitValue[0] = number[minuteLow];
digitValue[1] = number[minuteHigh];
digitValue[2] = number[hourLow] | dotValue;
digitValue[3] = number[hourHigh];
}
}
void RecordOpen() {
if (openTimeNotSet) {
openTime = rtc.now();
openCount = 0;
openTimeNotSet = false;
}
lastTime = rtc.now();
int actualHour = lastTime.hour();
int actualDay = lastTime.day();
int lastHour = openTime.hour();
int lastDay = openTime.day();
if ((actualHour >= 22) && (actualHour <= 6)) {
if ( ((actualHour >= 22) && (actualHour < 24) && ((lastHour < 22) || (lastDay < actualDay)))
|| ((actualHour >= 0) && (actualHour <= 6) && ( ((lastHour < 22) && (lastDay < actualDay)) || ((lastHour >= 22) && (lastDay < (actualDay -1))) ) ) ) {
openTime = rtc.now();
openCount = 0;
}
openCount++;
}
}
void TurnMyClockOFF() {
if (myClockON) {
ClearShiftRegister();
myClockON = false;
}
}
void TurnMyClockON() {
if (!myClockON) myClockON = true;
}
/*
* OLED display 128x32
* SDA - pin A4
* SCK - pin A5
* VCC - 3V-5V
*/
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
bool displayON;
void InitializeOledDisplay() {
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while(1);
}
//display.ssd1306_command(SSD1306_SETCONTRAST);
//display.ssd1306_command(255);
//display.dim(true); // set contrast to 31 (reduces consumption to half)
//display.display();
display.setTextColor(WHITE);
DisplayText("WELCOME", false, false);
}
#define vertical 12
bool holdDisplay = false;
char holdString[32];
void DisplayText(char text[32], bool showReceived, bool setHoldDisplay) {
display.clearDisplay();
TurnDisplayON();
if (setHoldDisplay) {
strcpy(holdString,text);
holdDisplay = true;
} else {
if (showReceived) {
display.setCursor(0,0);
display.setTextSize(1);
display.print("Received:");
}
display.setCursor(0,vertical);
display.setTextSize(3);
display.print(text);
display.display();
delay(2000);
}
if (holdDisplay) {
display.clearDisplay();
display.setCursor(0,vertical);
display.setTextSize(3);
display.print(holdString);
display.display();
}
else TurnDisplayOFF();
}
void DisplayLockedInfo() {
if (lockedInfo == 0) {
display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(1);
display.print("Open at 22:00-6:00");
display.setCursor(0,vertical);
display.setTextSize(3);
display.print(openCount);
display.print("x");
display.display();
}
if (lockedInfo == 1) {
display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(1);
display.print("Last open:");
if (!openTimeNotSet) {
display.setCursor(0,vertical);
display.setTextSize(2);
display.print(lastTime.day());
display.print(".");
display.print(lastTime.month());
display.print(".");
display.print(lastTime.hour());
display.print(":");
display.print(lastTime.minute());
}
display.display();
}
if (lockedInfo == 2) DisplayText("LOCKED", false, true);
lockedInfo++; if (lockedInfo > 2) lockedInfo = 0;
}
void DisplayOpenClose() {
if (openWindow) DisplayText("OPEN", false, true);
else DisplayText("CLOSE", false, true);
}
void TurnDisplayOFF() {
if (displayON) {
displayON = false;
display.ssd1306_command(SSD1306_DISPLAYOFF);
}
}
void TurnDisplayON() {
if (!displayON) {
displayON = true;
display.ssd1306_command(SSD1306_DISPLAYON);
}
}
/* Movement unit
PIN 0 - (RX0)
PIN 1 - (TX1) DO NOT USE!
PIN 2 - closeButton
PIN 3 - releaseButton (interrupt)
PIN 4 - INT1 stepper motor module ULN2003
PIN 5 - INT2 stepper motor module ULN2003
PIN 6 - INT3 stepper motor module ULN2003
PIN 7 - INT4 stepper motor module ULN2003
PIN 8 - CSN nRF24L01
PIN 9 - CE nRF24L01
PIN 10 - RESET PIN shiftregister I & II
PIN 11 - MOSI nRF24L01
PIN 12 - MISO nRF24L01
PIN 13 - SCK nRF24L01
PIN A0 - Fan MOSFET ON/OFF pin
PIN A1 - CLOCK PIN shiftregister I & II
PIN A2 - DATA PIN shiftregister I
PIN A3 - LATCH PIN shiftregister I & II
PIN A4 - (SDA) OLED display, DS1307
PIN A5 - (SCL) OLED display, DS1307
PIN A6 - (INPUT only)
PIN A7 - (INPUT only)
VCC 12V - Fan, StepMotor
VCC 5V - OLED display, DS1307, shift register I & II, RESET PIN of shift register I & II
VCC 3.3V - nRF24L01
GND - nRF24L01, StepMotor, Fan, OLED display, DS1307, closeButton, releaseButton
*/
/*
* nRF24L01 Receiver
*/
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 receiver(9, 8); // CE, CSN
const byte address[6] = "openw"; //address through which two modules communicate
void InitializeReceiver() {
if (!receiver.begin()) {
DisplayText("ComERR", false, true);
while(1);
}
receiver.setPALevel(RF24_PA_MIN); //RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, and RF24_PA_max
receiver.setDataRate(RF24_250KBPS); //RF24_250KBPS, RF24_1MBPS, RF24_2MBPS
receiver.openReadingPipe(0, address);
receiver.startListening(); //Set module as receiver
}
void GetSignal() {
receiver.powerUp();
if (receiver.available()) {
TurnFanOFF();
TurnStepMotorOFF();
TurnMyClockOFF();
char text[32] = {0};
receiver.read(&text, sizeof(text));
if (strcmp(text,"open") == 0) { openWindow = true; RecordOpen(); if (!released && !locked) DisplayOpenClose(); }
if (strcmp(text,"close") == 0) { openWindow = false; if (!released && !locked) DisplayOpenClose(); }
DisplayText(text, true, false);
}
}
/*
* 12V Stepper motor 28BYJ-48 with ULN2003 driver
*/
#include <Stepper.h>
#define fullOpenWindowSteps 100000
#define releaseSteps 15000
#define closeButton 2
#define isClosed ((PIND & B100) == 0)
const int stepsPerRevolution = 2048;
const int rpm = 14;
long steps = fullOpenWindowSteps;
bool wasOpen = false;
Stepper stepMotor = Stepper(stepsPerRevolution, 4, 6, 5, 7);
void InitializeStepMotor() {
pinMode(closeButton, INPUT_PULLUP);
stepMotor.setSpeed(rpm);
TurnStepMotorOFF();
}
void CloseWindow() {
if (isClosed) {
if (wasOpen) {
for (int k = 0; k < 50; k++) stepMotor.step(-1); //noise reduction of the closeButton
wasOpen = false;
}
TurnStepMotorOFF();
if (!locked) TurnDisplayOFF();
TurnMyClockON();
steps = 0;
if (!set) {
set = true;
DisplayText("READY", false, false);
}
}
else {
TurnMyClockOFF();
if (steps > 0) steps--;
stepMotor.step(-1);
wasOpen = true;
}
}
void OpenWindow() {
if (steps < fullOpenWindowSteps) {
stepMotor.step(1);
steps++;
}
else {
TurnStepMotorOFF();
TurnDisplayOFF();
TurnMyClockON();
TurnFanON();
}
}
void ReleaseWindow() {
if (steps < releaseSteps) {
stepMotor.step(1);
steps++;
}
else {
TurnStepMotorOFF();
TurnMyClockON();
}
}
void TurnStepMotorOFF() {
PORTD &= B00001111;
}










Comments