OBS! All installation som innefattar starkström/230V skall utföras av elektriker! I detta fall handlar det om reläet.

Det finns nu en ny version av detta inlägg som är mer detaljerat och välbeskrivet!

IMG_7161

Hårdvaran

Fjärrstyrning i all ära, men vad gör man om man vill ha en hyfsat simpel automatisk lösning för att styra exempelvis en lampa eller fläkt?

I mitt fall handlade det om min gamle far, vars verkstad är utrustad med en mindre 230v-fläkt. Denna suger ut luft i verkstaden och drar in ny luft från gästrumsvåningen ovanför. Tanken var att denna automatiskt skulle starta när temperaturen utomhus understiger den inomhus (inom rimliga gränser, såklart).

Jag bestämde mig för att en relativt billig (och lärorik) lösning vore att använda en Arduino tillsammans med två 1-Wire-temperatursensorer av modell DS18B20+. Ibland kan man vilja köra fläkten oberoende av sensorerna, så därför behövdes också en knapp med vilken man kan skifta läge mellan manuellt och automatiskt.

Sen har vi såklart reläet. Eftersom Arduino har en +5V-koppling kan vi använda HRM-S DC5V, som kan styras av denna. Dellistan så här långt:

https://www.m.nu/arduino-uno-r3-orginal-p-964.html

https://www.m.nu/temperatursensor-pa-kabel-ds18b20-p-44.html

https://www.m.nu/rela-hrms-dc5v-inklusive-sockel-p-153.html

https://www.m.nu/tactile-switch-buttons-12mm-square-6mm-tall-x-10-pack-p-702.html

Utöver dessa delar behövs också några smådelar. Till att börja med innehåller reläets base (brytaren, kan man säga) en spole, som bildar ett magnetfält när ström förs igenom den. När strömmen bryts kollapsar magnetfältet tillbaka in i spolen vilket kan bilda en “spänningsspik” mångdubbelt större än den ursprungliga spänningen. Detta är såklart ganska tråkigt om det sker i en krets som normalt sett tycker om 5V, men då har någon fiffig ingenjör kommit fram till att man kan sätta en diod parallellt med spolen för att motverka dessa spikar och rädda kretsarna. Diod: https://www.m.nu/1n4001-diode-10-pack-p-853.html

Sen behövs också några motstånd. Till knappen behövs ett s.k. “pull-down”-motstånd, vilket sätter knappens utdata till “0” så länge knappen inte är intryckt. Temperatursensorerna, som vi kör i ett 1-Wire-nät, kan köras i parasitläge vilket är smidigt. Då ansluts ben 1 och 3 på temperatursensorn till Ground, medan ben 2 ansluts till +5V genom ett motstånd. Ben 2 ansluts också till en digitalport på Arduinon, som kommer samla in sensordatan därifrån. Vi säljer ett paket med alla nödvändiga motstånd (och fler därtill!): https://www.m.nu/kit-med-motstand-metallfilm-025w-1-30-varden-20-av-varje-600-motstand-totalt-p-925.html

Reläet är som tidigare nämnt anslutet till +5V. Den porten har högre Ampere än digitalportarna. Hur gör man då för att slå av och på reläet? Jo, man använder en transistor: https://www.m.nu/npn-bipolar-transistors-pn2222-10-pack-p-852.html för att öppna och sluta kretsen.

+5V som kommer från reläet ska alltså gå in i transistorns Collector, “insamlare”, och Emitter, “sändare”, kopplas till Arduinons Ground. Sedan styrs dess Base med en av Arduinons digitalportar. När porten är satt till LOW/0 är anslutningen bruten, och reläet är avstängt. När porten sätts till HIGH/1 aktiveras reläet och startar fläkten.

Här är ett litet fint Paint-diagram över hur allt ska vara anslutet (knappen exkluderad, ska fixa ett finare diagram):

fläktstyrning

Arduino-koden

När vi nu har alla delar och har anslutit allt rätt, måste vi programmera Arduinon för att göra det vi vill. Arduinon använder ett relativt simpelt språk som påminner om C, och det finns många färdiga kodbibliotek för olika tillämpningar. I vårt fall kommer vi använda oss av två kodbibliotek, för 1-Wire respektive DS18B20. Dessa måste installeras i Arduino IDE:n innan man kan kompilera koden.

Denna första version av koden ska egentligen modifieras, då den i nuläget har tre olika lägen (av, på, automatisk). Av-läget behövs inte, då det finns en separat brytare för fläkten. Men eftersom jag inte vet vad andra kan tänkas ha för nytta av koden bjuder jag på det läget också!

Kan kort nämna lite om hur koden är uppbyggd. Eftersom multitasking tydligen är bökigt på Arduino, tittar jag på den inbyggda millisekunds-räknarens värde för att avgöra när en viss syssla ska utföras. Sen kan man definiera intervall för de olika uppgifterna och jämföra “senast uppgiften gjordes” minus “nuvarande tid” >= “intervall”.  Har försökt kommentera väl och ställa upp saker så det är lätt att avläsa vad som görs var. De tre tillstånden är tydligt markerade.

OBS! Notera att jag hårdkodat 1-Wire-adresserna till de sensorer jag använde (med DeviceAddress-objekt). För att använda koden måste man ansluta två sensorer och använda denna (eller någon motsvarande) applikation för att hämta in adresserna med hjälp av Arduinon, och lägga in de nya värdena istället. Nu till koden (nedladdningsbar källkodsfil finns längst ner):

#include <OneWire.h>
#include <DallasTemperature.h>

/* Fan control using 5V controlled relay
 * This code controls a relay (which, in turn, controls a 220v fan or whatever you wish).
 * It has a button to set state (Off, Always on, Temperature based).
 * For the temperature based state, two 1-Wire DS18B20+ temperature sensors are used
 * in parasitic mode to determine whether the fan should be on or not.
 * The LED on pin 13 is used to display which state is active (off, on, blinking).
 * I have used some code from the following guides:
 
 * http://www.arduino.cc/en/Tutorial/Switch
 * http://arduino.cc/en/Tutorial/BlinkWithoutDelay
 * http://www.strangeparty.com/2010/12/13/arduino-1-wire-temperature-sensors/
 *
 * Jonas Lundblad @ www.m.nu
 * August 2014
 * Version 1.0
 */

//The 1-Wire bus is defined instead of initialized to a variable
//int tempCollectorPin = 3; // the number of the pin which recieves temperature data
#define ONE_WIRE_BUS 3
#define TEMPERATURE_PRECISION 9

// Setup a oneWire instance to communicate with any OneWire devices
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

// Arrays representing the 1-Wire device addresses
DeviceAddress outsideThermometer = { 0x28, 0xC6, 0x2B, 0x84, 0x05, 0x00, 0x00, 0x59 };
DeviceAddress insideThermometer  = { 0x28, 0xBE, 0xA2, 0x95, 0x04, 0x00, 0x00, 0x44 };

int buttonInPin = 2;         // the number of the input pin for the button
int statusPin = 13;       // the number of the output pin, builtin on pin 13
int relayControlPin = 5; // the number of the pin controlling the relay


// controller state, default is 2 (using temp sensors)
// Other states are 0 (off) and 1 (always on)
int controllerState = 2;

//This value is lowered to 2 if sensors are not found correctly,
// thereby only allowing on or off state.
int numberOfStates = 3;


// These booleans are used to minimize DigitalWrites in the different states
boolean on = true;       // True if in controllerState 1 (on)
boolean autoOn = false;
boolean relayOn = false; // True if in relay mode and relay should be active

int ledState = HIGH; // LED state used to toggle the LED
int buttonRead;           // the current reading from the input pin
int prevButtonState = LOW;    // the previous reading from the input pin

// the follow variables are long's because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long buttonTime = 0;         // the last time the button was pressed
long debounce = 200;   // the debounce time, increase if the output flickers


long prevMillisTemp = 0;        // will store last time temp was updated
long prevMillisBlink = 0;       // last time LED state was changed


//Intervals at which to update temperature and blink the LED in controller state 2
long tempInterval = 600000; //Ten minutes
long lightInterval = 1000;

void setup()
{
  Serial.begin(9600);
  Serial.println("1-Wire DS18B20 relay controller started.");

  sensors.begin();

  delay(5000);
  
  // locate devices on the bus (must be more than one)
  if (sensors.getDeviceCount() > 1) {
    Serial.println("Sensor count seem OK!");
    
    if (!sensors.getAddress(outsideThermometer, 0))
      Serial.println("Unable to find address for Device 0"); 
    if (!sensors.getAddress(insideThermometer, 1))
      Serial.println("Unable to find address for Device 1");
      
    sensors.setResolution(insideThermometer, TEMPERATURE_PRECISION);
    sensors.setResolution(outsideThermometer, TEMPERATURE_PRECISION);
  }
  else {
    //If sensors not found correctly, disables sensor mode.
    Serial.println("Sensors not OK! Disabling sensor state...");
    controllerState = 1;
    numberOfStates = 2;
  }
  // Setup the pins for input or output, respectively
  pinMode(buttonInPin, INPUT);
  pinMode(statusPin, OUTPUT);
  pinMode(relayControlPin, OUTPUT);
}

void loop()
{
  //Save current millis() value (number of milliseconds since boot)
  unsigned long currentMillis = millis();
  
  //Read button state (every iteration)
  //#########################################################################
  buttonRead = digitalRead(buttonInPin);

  // if the input just went from LOW and HIGH and we've waited long enough
  // to ignore any noise on the circuit, change controller state and
  // remember the time
  if (buttonRead == HIGH && prevButtonState == LOW && currentMillis - buttonTime > debounce) {

    //Modulo on the controller state integer, as it has only 3 states
    ++controllerState;
    controllerState = (controllerState % numberOfStates);
    Serial.print("Button pressed (?)! The new state is: ");
    Serial.println(controllerState);
    buttonTime = currentMillis;    
  }
  prevButtonState = buttonRead;

  //#########################################################################

  // State 0, relay off, LED off
  // Added a "On" boolean which makes sure digitalWrite is not done when
  //it is unnecessary.
  //#########################################################################
  if (controllerState == 0) {
    if (on) {
      Serial.println("Stopping activity (state 0)");
      digitalWrite(relayControlPin, LOW);
      digitalWrite(statusPin, LOW);
      on = false;
    }
  }
  //#########################################################################

  //State 1, relay on, LED on
  //#########################################################################
  else if (controllerState == 1) {
    if (!on) {
      Serial.println("Starting activity (state 1)");
      digitalWrite(relayControlPin, HIGH);
      digitalWrite(statusPin, HIGH);
      on = true;
    }
  }
  //#########################################################################


  //State 2, relay on if temperature outside is low enough, LED blinking
  //#########################################################################
  else {
    
    //State 2, step 1 - toggle LED when applicable
    //######################################################

    //Reset if millis() has reset (happens after ~50 days apparently)
    if(currentMillis < prevMillisBlink) {
      prevMillisBlink = 0;
    }

    if(currentMillis - prevMillisBlink > lightInterval) {
      // save the last time you blinked the LED 
      prevMillisBlink = currentMillis;   

      // if the LED is off turn it on and vice-versa:
      if (ledState == LOW)
        ledState = HIGH;
      else
        ledState = LOW;

      // set the LED with the ledState of the variable:
      digitalWrite(statusPin, ledState);
    }
    //######################################################

    //State 2, step 2 - check whether relay should be on
    //######################################################
    
    //Reset if millis() has reset (happens after ~50 days apparently)
    if (currentMillis < prevMillisTemp) {
      prevMillisTemp = 0;
    }
    
    // Also want to check when the unit has just been started
    if (currentMillis - prevMillisTemp > tempInterval) {
      //Get temperatures from sensors
      sensors.requestTemperatures();
      //sensors.getTempC(outsideThermometer);
      float insideTemp, outsideTemp;
      int tries = 0;
      
      // Gets temperatures from specific sensors.
      // It is also possible to iterate over all 1-Wire
      // sensors, see examples in other guides
      insideTemp = sensors.getTempC(insideThermometer);
      outsideTemp = sensors.getTempC(outsideThermometer);
      
      // The below while-loop can be used if the temperature sensor returns
      // error codes (85.00, -127.00 and 127.49) some of the time.
      // It fetches temperature values again until both are valid.
      // Of course, it won't work if only error codes are returned
      //(incorrect connection of parasitic mode may cause this)
      
      //TemperatureOK(float) is defined at the bottom
      while (!temperatureOK(insideTemp)
              || !temperatureOK(outsideTemp)) {
        sensors.requestTemperatures();
        if (!temperatureOK(insideTemp)) {
          insideTemp = sensors.getTempC(insideThermometer);
        }
        if (!temperatureOK(outsideTemp)) {
          outsideTemp = sensors.getTempC(outsideThermometer);
        }
        tries++;
      }
      
      Serial.print("Outside: ");
      Serial.print(outsideTemp);
      Serial.print(", Inside: ");
      Serial.print(insideTemp);
      Serial.print(", After ");
      Serial.print(tries);
      Serial.println(" retries.");

      //Is it colder outside? And is it warmer than 18 degrees inside?
      if (insideTemp+1 > outsideTemp && insideTemp > 18.00) {
        Serial.println("Fan should be on!");
        if (!relayOn) {
          relayOn = true;
          Serial.println("Starting fan!");
          digitalWrite(relayControlPin, HIGH);
        }
      }
      else {
        Serial.println("Fan shouldn't be on.");
        if (relayOn) {
          Serial.println("Stopping fan!");
          relayOn = false;
          digitalWrite(relayControlPin, LOW);
        }
      }
      //digitalWrite(relayControlPin, HIGH);
      
      prevMillisTemp = currentMillis;
    }
    
    //######################################################
  }
  //#########################################################################
  //End of states :o
}

//This method checks whether the provided temperature
//is within the correct interval and returns a boolean.
boolean temperatureOK(float temp) {
  if (temp > 50.00 || temp < -40.00) {
    return false;
  }
  else return true;
}

 

Att stoppa in koden här känns som ett dåligt alternativ då formateringen blev en aning kass och det är mycket kod. Kanske gör det senare när jag får det att se rätt ut.

Slutresultatet

Jag är nybörjare på att löda, och hade fel lödtenn, så ha överseende med att det ser bedrövligt ut 🙂

Ett problem uppdagades när allt var anslutet. Systemet verkade låsa sig efter en godtycklig tidsperiod, och det var svårt att felsöka. Då vi råkade ansluta en trafo med 12V växelström ut (vi visste inte ens att det fanns sådana), funderar vi på om det kan ha pajat Arduinon. Ska testa med en ny i Oktober, så uppdaterar inlägget med ny info då!

Här är hur som helst några bilder på underverket:

IMG_7160

Knappen. Röd tråd går till +5V, Svart tråd med pull-down-motstånd går till Ground och den blå kabeln går till Arduinons digitalingång som registrerar knapptryck. Även temperatursensorerna syns i bild, där den orange:a kabeln går till Arduinon och de andra kablarna har samma funktion som på knappen.

IMG_7229

Temperatursensorn som sitter på utsidan. Denna är den vattentäta varianten, för säkerhets skull. Båda sensorerna är anslutna på samma ingång till Arduinon.

Och så källkodsfilen för nedladdning:

Arduino fan control (2605 nedladdningar)