Kevin Lavlund Hansen – kehan18@student.sdu.dk
Rasmus Sjøholm Stamm – rasta17@student.sdu.dk
Benjamin Clement Klerens – bekle18@student.sdu.dk

Vi ville skabe et automatiseret hjem, hvor fokus har været på at gøre dagligdagen nemmere for folk i hustanden. Vi har derfor valgt at inkorporerer 3 forskellige løsninger:

  • Automatic door – Dør der åbner når der er bevægelse foran
  • Air conditioning – Temperatur aktiveret blæser
  • Futuristic alarm clock – Alarm ur

Disse løsninger er beskrevet i hvert sit afsnit og kan bygges på samme breadboard for at få det samlede system.
De kan derfor også fungere uafhængigt af hinanden.

Samlet materiale liste

  • Arduino UNO
  • PIR sensor (HC-SR501)
  • Servo motor (SG90)
  • Termistor
  • Modstand
    • 10k Ohm
    • 2x 220 Ohm
  • NPN Transistor (PN2222)
  • DC Motor 
  • Diode (1N4007)
  • Kondensator
    • 100nf keramisk
  • Rotary Encoder
  • DS1307 (RTC) 
  • LCD1602A
  • Potmeter
    • 10k Ohm
  • Aktiv buzzer

Delløsninger

Automatic door

Materialer

  • Arduino UNO
  • PIR sensor (HC-SR501)
  • Servo motor (SG90)

Komponenter og kredsløb

Denne delløsning automatiserer døråbning som resultat af fysisk bevægelse. Dette opnås vha. En servo motor til at dreje døren om dens aksel, samt en Passive Infrared sensor (PIR), som giver Arduinoen signal, når den registrerer bevægelse. Arduinoen vil herefter give signal til servoen om at åbne døren, samt lukke den efter en sat tidsperiode.

Servo motor

Servoen kan typisk (afhængig af model) rotere 180 grader. Den styres af Arduinoens PWM-pins, hvor signalets duty-cycle afgør motorens vinkel. I denne løsning bruges Arduinoens indbyggede bibliotek Servo.h til at styre servoen, hvilket betyder at vi ikke skal bekymre os om at definere duty cycles, men i stedet bare angive den ønskede vinkelgrad.

PIR sensor

PIR-sensoren virker ved at have en udgående signal-pin som, når bevægelse registreres, sættes højt i et bestemt tidsinterval. Dette signal sættes på en indgående pin på Arduinoen, så den kan aflæses og reageres på. Vi har valgt at bruge en af Arduinoens analoge inputs, så signalet kan aflæses præcist (0-1023), frem for høj/lav.

Sensoren kan indstilles vha. potentiometre, samt tre pins der skal kortsluttes i én af to kombinationer, som sidder på PCB’et. Hvordan disse virker, afhænger af komponentens model, men i vores tilfælde gælder jf. datasheet:

Time delay adjust

Time delay, eller forsinkelse, afgør hvor lang tid signal-pinnen bevares høj ved bevægelse.
Med uret: Højere forsinkelse, maksimum = ca. 5 minutter
Mod uret: Lavere forsinkelse, minimum = ca. 3 sekunder
Indstil på laveste forsinkelse.

Sensitivity adjust

Sensitivity, eller følsomhed, afgør hvor langt sensoren vil registrere bevægelse.
Med uret: Lavere følsomhed, minimum = ca. 3 meter
Mod uret: Højere følsomhed, maksimum = ca. 7 meter
Indstil på laveste følsomhed.

Trigger selection jumper


Indstil på Repeatable Trigger Mode.

Kredsløb

I AutoDesk Tinkercad kan løsningen opstilles og testes som vist:

Løsningens kredsløbsdiagram ser således ud:

Kode

Løsningens kodning er langt fra kompliceret, så her er der ikke meget at snakke om. Den mest essentielle del af programmet ligger i funktionen handleDoor(), hvor PIR-sensoren checkes for højt signal via analogRead(). Hvis dette er tilfældet, åbnes døren med Servo.write() (på doorServo objektet), og lukkes derefter igen efter fem sekunder (ligeledes med Servo.write()).

Ovenstående beskrivelse er opsummeret i følgende flow-diagram:

door.h

#include <Servo.h>

Servo doorServo;

int doorServoPin = 5;
int PIRPin = A3;

boolean isOpen = false;
unsigned long previousTime;


int sensMvmt;
int noiseThreshold = 100; // Threshold for recognizing PIR trigger

void readSensor() {
  sensMvmt = analogRead(PIRPin);
}

void openDoor() {
  doorServo.write(0);
  isOpen = true;
}

void closeDoor() {
  doorServo.write(180);
  isOpen = false;
}

// setup
void setupDoor() {
  doorServo.attach(doorServoPin);
  previousTime = millis();
  closeDoor();
}

// loop
void handleDoor() {
  readSensor();

  if (sensMvmt >= noiseThreshold && isOpen == false) {
    openDoor();
    previousTime = millis();
  } else if ((millis() - previousTime) >= 5000) {
    closeDoor();
  }
}

Air conditioning

Materialer

  • Arduino UNO
  • Termistor
  • Modstand
    • 10k Ohm
    • 220 Ohm
  • NPN Transistor (PN2222)
  • DC Motor 
  • Diode (1N4007)
  • Kondensator
    • 100nf keramisk

Komponenter og kredsløb

Denne delløsning aktiverer en DC motor, når at temperaturen, der er målt gennem thermistoren, bliver høj nok. På denne måde virker komponenterne tilsammen som et mini AC-anlæg.

Thermistor

Da modstanden i termistoren afhænger af temperaturen, kan vi derfor beregne rummets temperatur med dette komponent. Dog kommer komponentens output ikke direkte i celsius eller lignende enhed, men kan dog beregnes ud Steinhart-Hart ligningen.

Steinhart-Hart ligningen approksimerer en temperatur i Kelvin-enheden, ud fra konstanter der varierer fra hvor stor modstand termistoren er på. Konstanterne i kode-eksemplet er baseret ud fra en 10k termistor.

Steinhart-Hart ligningen kræver at vi kender den svingende modstand i termistoren, som afhænger af temperaturen. For at kende modstanden, bruger vi spændingsdeler-formlen, hvor vi isolere R2. Når vi allerede kender R1 som er 10k Ohm,  VIn er 5V og Vout måler vi i kredsløbet på Arduinoen. R2 er selvfølgelig termistorens modstand, som vi vil bruge i Steinhart-Hart ligningen til at finde en approksimerende værdi for temperaturen.

Efter vores værdier er gået igennem formlen, så er vores output i Kelvin, og vi kan derfor anvende denne omregning:

Endelig har vi vores output i celsius, som er en mere relaterbar værdi og nu kan refereres til i koden.

DC motor

DC-motoren styres af temperaturen som måles igennem termistoren. Hvis at rummets temperatur måles til at være over 30-graders celsius, så sender Arduinoen en spænding igennem til motoren. Derimod hvis temperaturen falder under 30-graders celsius, så slukkes der for spændingen.

Til DC-motoren kobles en diode, som sikrer at strømmen løber den samme retning. Dette gøres for at undgå en modsat strøm fra motoren, hvor strømmen vil løbe i den modsatte retning og potentielt vil forvolde skade på komponenterne. Derudover er derudover er der loddet en keramisk kondensator på tværs af motorens poler for at reducere støj.

Kredsløb

Kredsløbet opsat på breadboard:

Løsningens kredsløbsdiagram ser således ud:

Kode

Koden bag denne løsning er en anelse kompliceret, primært pga. af brugen af Steinhart-Hart ligningen. Herudover er der tale om et simpelt check på thermistorens måling (omregnet til celius via Steinhart-Hart). Måler den over en sat grænseværdi (e.g. 20 grader celsius), bruges digitalWrite() funktionen til at sætte en pin høj, som åbner for transistoren til DC motoren. Ligeledes bruges digitalWrite() til at lukke transistoren, når temperaturen er under grænseværdien.

Ovenstående beskrivelse er opsummeret i følgende flow-diagram:

fan.h

#define MOTOR_PIN 13

int ThermistorPin = A0;
int Vo;
float R1 = 10000;
float logR2, R2, T, Tc, Tf;
float c1 = 0.001125308852122, c2 = 0.000234711863267, c3 = 0.000000085663516;

boolean fanRunning = false;

void setupFan() {
  pinMode(MOTOR_PIN, OUTPUT);
}

float readTemp() {
  Vo = analogRead(ThermistorPin);
  R2 = R1 * (1023.0 / (float)Vo - 1.0);
  logR2 = log(R2);
  T = (1.0 / (c1 + c2 * logR2 + c3 * logR2 * logR2 * logR2));
  Tc = T - 273.15;
  return Tc;

}

void runFan() {
  digitalWrite(MOTOR_PIN, 1);
  fanRunning = true;
}

void stopFan() {
  digitalWrite(MOTOR_PIN, 0);
  fanRunning = false;
}

void handleFan() {
  if (readTemp() > 30.00 && !fanRunning) {
    runFan();
  } else if (readTemp() < 30.00 && fanRunning){
    stopFan();
  }
}

Futuristic alarm clock

Materialer

  • Arduino UNO
  • Rotary Encoder
  • DS1307 (RTC) 
  • LCD1602A
  • Potmeter
    • 10k Ohm
  • Modstand
    • 220 Ohm
  • Activ buzzer

Komponenter og kredsløb

LCD

Et Liquid Crystal Display (LCD) bruges til at vise hvad klokken er, samt til at indstille alarmen. 

Der er 16 forbindelser på displayet, men eftersom vi benytter os af 4-bit mode kan vi ignorere 4 af dem. 

Arduino LCD Tutorial | How To Connect an LCD to Arduino
GNDEr en jordforbindelse
VCCForbindes til 5 volt
VoSluttes til et potmeter – Denne forbindelse bruges til at regulere kontrasten i displayet
RSForbindes til pin 6 på arduinoen – Register select
R/WStår for Read/Write, for at kunne skrive til displayet skal denne forbindelse sættes lav
EnForbindes til pin 7 på arduinoen – Står for Enable og skal sættes høj når der bliver sendt data til displayet
D0 – D3Bliver ikke benyttet eftersom vi benytter os af 4-bit mode
D4 – D7Forbindes til pin 8 – 11 i stigende rækkefølge – Data pins
AForbindes til 5 volt gennem en modstand – Anoden på baggrundslyset
CForbindes til jord – Katoden på baggrundslyset

DS1307 (RTC)

En Real Time Clock (RTC) er, som navnet antyder, et ur. Modulet indeholder selve DS1307 chippen samt et knapcelle batteri. Dette batteri er ansvarligt for at holde gang i chippen, selv efter VCC er blevet frakoblet. Arduinoen kan gennem I2C interfacet kommunikere med dette komponent og derved holde styr på hvad klokken er.

GNDForbindes til jord
VCCForbindes til 5 volt
SDASluttes til SDA pinen på arduinoen (eller A4) – Data input/output
SCLSluttes til SCL pinen på arduinoen (eller A5) – Clock input, bruges til at synkronisere data der bliver sendt
SQWBliver ikke benyttet – Square wave frequency

Rotary Encoder

Der bliver anvendt en rotary encoder til at sætte alarmen i systemet. Knappen bliver brugt til at skifte mellem at vise klokken og sætte alarmen. For at indstille alarmen skal brugeren dreje på akslen, drejes der mod uret reduceres timeren for alarm og drejes der med uret bliver den forøget.

CLKForbindes til pin 3 på arduinoen – Primære output puls, sender en puls hver gang akslen bliver roteret et klik
DTForbindes til pin 4 på arduinoen – Identisk med CLK, men med et 90 grader faseskift
SWForbindes til pin 2 på arduinoen – En knap, er lav når akslen bliver presset ned
VCCForbindes til 5 volt
GNDForbindes til jord

En rotary encoder virker ved at have to outputs, CLK og DT (også kendt som output A og B), der begge sender pulse. Disse outputs har et mellemrum mellem sig:

Tag udgangspunkt i illustrationen ovenfor, når vi roterer akslen vil det ene output altid blive forbundet med jord før det andet. Roterer vi med uret bliver output A sat lav før output B, roterer vi derimod mod uret bliver output B sat lav før output A.

Med denne viden kan vi nu med registrere hvilken vej brugeren drejer himstergimsen.

Kredsløb

Eftersom flere af komponenterne i dette kredsløb ikke er tilgængelige i AutoDesk Tinkercad er kredsløbet blevet opstillet i programmet Fritzing i stedet. Desuden er den rotary encoder brugt i det fysiske kredsløb ikke tilgængeligt, og er derfor blevet erstattet af en med kun jordforbindelse, CLK og DT samt en knap ved siden af.

Løsningens kredsløbsdiagram ser således ud:

Kode

Koden der er ansvarlig for denne løsning kan virke en smule kompliceret, men kan forklares forholdsvist simpelt. Der eksistere to interrupts, en for akslen og en for knappen fra encoderen. Dette sikre at systemet ikke går glip af en tryk på knappen eller et klik på akslen. Functionen for knappen er også ansvarlig for at skrive timeren til EEPROM, således at timeren bliver gemt. Derudover er der en handle function som checker om alarmen skal starte.

Eksterne Biblioteker

  • RTClib

Koden der er ansvarlig for alarmen bruger RTClib fra adafruit til at kommunikere med den Real Time Clock (RTC). Dette bibliotek kan blive downloadet i Arduino IDE gennem den integreret Library Manager.

Følgende flow-diagram opsummere den tidligere beskrevet kode.

alarm.h

#include <LiquidCrystal.h>
#include <RTClib.h>
#include <EEPROM.h>

#define SW 2
#define BUZZER_PIN 12
#define CLK 3
#define DT 4

RTC_DS1307 rtc;

int stateCLK, lastStateCLK;

int alarmHour_addr = 0;
int alarmMinute_addr = 1;
int sens = 5;
int alarmDuration = 30;
int alarmHour, alarmMinute;

boolean alarmActive = true;

boolean alarmChanged = false;

DateTime now;

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(11, 10, 9, 8, 7, 6);

// Print the current time to the LCD
void printLCDTime() {
  lcd.setCursor(0, 1);
  if (now.hour() < 10) {
    lcd.print("0");
  }
  lcd.print(now.hour(), DEC);
  lcd.print(":");
  if (now.minute() < 10) {
    lcd.print("0");
  }
  lcd.print(now.minute(), DEC);
}

// Print the current alarm timer to the LCD
void printLCDAlarm() {
  lcd.setCursor(0, 1);
  if (alarmHour < 10) {
    lcd.print("0");
  }
  lcd.print(alarmHour, DEC);
  lcd.print(":");
  if (alarmMinute < 10) {
    lcd.print("0");
  }
  lcd.print(alarmMinute, DEC);
}

void printLCD() {
  if (!alarmActive) {
    printLCDAlarm();
  } else {
    printLCDTime();
  }
}

// Decrease alarm timer by the value of 'sens'
void decreaseAlarm() {
  if (alarmMinute - sens < 0) {
    if (alarmHour > 0) {
      alarmHour--;
      alarmMinute = 60 - sens;
    }
  } else {
    alarmMinute = alarmMinute - sens;
  }
}

// Increase alarm timer by the value of 'sens'
void increaseAlarm() {
  if  (alarmMinute + sens >= 60) {
    if (alarmHour < 23) {
      alarmHour++;
      alarmMinute = 0;
    }
  } else {
    alarmMinute = alarmMinute + sens;
  }
}

void rotate() {
  if (!alarmActive) {
    stateCLK = digitalRead(CLK);
    // If the previous and the current state of CLK are different, that means a Pulse has occured
    if (stateCLK != lastStateCLK && stateCLK == 1) {
      // If DT state is different to CLK state, that means the encoder is rotating clockwise
      if (digitalRead(DT) != stateCLK) {
        decreaseAlarm();
      } else {
        increaseAlarm();
      }
      printLCDAlarm();
      alarmChanged = true;
    }
    lastStateCLK = stateCLK; // Updates the previous state of the CLK with the current state
  }
}

void saveAlarm() {
  if (EEPROM.read(alarmHour_addr) != alarmHour) {
    EEPROM.write(alarmHour_addr, alarmHour);
  }
  if (EEPROM.read(alarmMinute_addr) != alarmMinute) {
    EEPROM.write(alarmMinute_addr, alarmMinute);
  }
  Serial.println("wrote alarm to eeprom!");
}

void button() {
  alarmActive = !alarmActive;
  lcd.clear();
  if (alarmActive) {
    saveAlarm();
    lcd.print("Current time:");
    printLCDTime();
  }
  else {
    lcd.print("Alarm time:");
    printLCDAlarm();
  }
}

void runAlarm() {
  delay(500);
  digitalWrite(BUZZER_PIN, 1);
  delay(500);
  digitalWrite(BUZZER_PIN, 0);
}

void setupAlarm() {
  if (!rtc.begin()) {
    Serial.println("Could not find RTC!");
  }


  if (!rtc.isrunning()) {
    Serial.println("RTC not running, setting time");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  alarmHour = EEPROM.read(alarmHour_addr);
  alarmMinute = EEPROM.read(alarmMinute_addr);

  // Uncomment to reset clock
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  // Uncomment to reset EEPROM
  // EEPROM.write(alarmHour_addr, 8);
  // EEPROM.write(alarmMinute_addr, 0);

  // Using the internal pullup resistors for the rotary encoder
  pinMode(SW, INPUT_PULLUP);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(DT, INPUT_PULLUP);

  pinMode(BUZZER_PIN, OUTPUT);

  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);

  lastStateCLK = digitalRead(CLK);

  // Attaching interrupt to CLK pin of rotary encoder
  // (This is needed to make sure each change is registered)
  attachInterrupt(digitalPinToInterrupt(SW), button, FALLING);
  attachInterrupt(digitalPinToInterrupt(CLK), rotate, CHANGE);
  lcd.print("Current time:");
}

void handleAlarm() {

  // Fetch new time info from RTC
  now = rtc.now();

  // Check if alarm is active, run alarm if time is equal to the time of the alarm
  if (alarmActive == true) {
    printLCDTime();
    if (alarmHour == now.hour() && alarmMinute == now.minute() && (now.second() < alarmDuration)) {
      runAlarm();
    }
  }
}

PF2.ino

De tre header files (door.h, fan.h og alarm.h) inkluderes i sketch filen hvor deres setup functions og loop functions bliver kørt fra. Her bliver de tre states fra hvert subsystem også printet til seriel porten hvis der sker ændringer.

#include "fan.h"
#include "door.h"
#include "alarm.h"

boolean lastFanRunning;
boolean lastIsOpen;
boolean lastAlarmActive;

void printStates() {
  if (lastFanRunning != fanRunning) {
    Serial.print("Is the fan running: ");
    Serial.println(fanRunning);
  }

  if (lastIsOpen != isOpen) {
    Serial.print("Is the door open: ");
    Serial.println(isOpen);
  }

  if (lastAlarmActive != alarmActive) {
    Serial.print("Is the alarm active: ");
    Serial.println(alarmActive);
  }

  lastFanRunning = fanRunning;
  lastIsOpen = isOpen;
  lastAlarmActive = alarmActive;
}

void setup() {
  Serial.begin(9600);
  Serial.println("smart home booting");
  setupFan();
  setupDoor();
  setupAlarm();
  Serial.println("smart home running");
}

void loop() {
  printStates();
  handleFan();
  handleDoor();
  handleAlarm();
  delay(50);
}

Konklusion

Blog-indlæggets smart-home installation opfylder de krav, som projektet stiller, men opfylder ikke helt de krav for oprindelig havde sat for løsning. Samlet set havde vi sat øjnene på at installationen havde mere interkommunikation mellem de forskellige moduler, så vores automatiske dør kunne være låst i bestemte tidsrum, eksempelvis kl. 22:00 til 07:00. Dog bidrager hvert modul til at gøre hjemmet smartere på hver sin måde. Døren gør det nemmere at komme ind/ud for, hvis man f.eks. har handlet ind, og blæser-systemet hjælper med at forbedre indeklimaet. Alarmen-displayet fungerer både som ur og alarm, som medvirker til at gøre hjemmet smartere.

Perspektivering

Motoren viste sig at genere for meget støj for at displayet kunne fungere uden fejl, vi var derfor nødt til at lave ændringer på blæser kredsløbet for at reducere støjen.

Som systemet er lavet, opdateres displayet hver loop-cycle, havde der været plads i systemet, kunne SQW pinden på RTC’en været brugt til at signalere til arduinoen at et minut var passeret. Arduinoen kunne herefter opdatere displayet uden behov for at gøre det i hvert loop.

Github side med relevante filer:

https://github.com/benjaminklerens/PF2_robottek

Leave a Reply