State Machines II.

pohybový (PIR) senzor, pulzne šírková modulácia (PWM)

Záznam z prednášky

Introduction

  • (slide) Na poslednej prednáške sme si ukázali niekoľko nových vecí - hovorili sme o stavových strojoch, ukázali sme si sériovú linku a sériovú komunikáciu, analógové vstupy, hovorili sme o analógovo-digitálnom prevodníku, predstavili sme si projekt nočného svetla, ktorý nás bude chvíľu sprevádzať, a na ktorom sme si všetky spomínané veci prezentovali.

  • Zatiaľ sme si predstavili správanie nočného bezpečnostného svetla len v dvoch stavoch: Day a Night, kedy sa svetlo rovno zaplo pri prechode do stavu Night. Dnes však aktuálnu verziu zariadenia rozšírime o senzor pohybu a zabezpečíme, aby sa svetlo rozsvietilo len vtedy, keď bude nie len noc, ale aj niekto prejde okolo.

State 3: The Light

  • (slide) Tretím stavom, ktorý bude naše zariadenie obsahovať, bude Light. Do neho zariadenie prejde zo stavu Night v prípade, že bude detekovaný pohyb. Späť do stavu Night sa zariadenie vráti potom, ako pohyb ustane.

    Night Security Light: State Diagram

The PIR Sensor

  • (slide) Na detekciu pohybu k doske Arduino UNO pripojíme PIR senzor. Ten bude generovať na výstupe logickú úroveň HIGH, keď zistí pohyb. V opačnom prípade sa na výstupe senzora bude nachádzať logická úroveň LOW.

  • PIR senzor má tri piny:

    • VCC - napájanie
    • GND - zem
    • OUT - digitálny výstup
    PIR Sensor: Pinout (zdroj)
  • Výstup z PIR senzoru bude pripojený k pinu č. 2, ktorý bude nastavený ako vstupný. Jednoduchá ukážka toho, ako PIR senzor funguje, môže vyzerať takto:

    #define LED 6
    #define PIR 2
    
    int main(){
      // taken from original main.cpp
      init();
    
      // setup
      pinMode(LED, OUTPUT);
      pinMode(PIR, INPUT);
    
      // superloop
      for(;;){
        digitalWrite(LED, digitalRead(PIR));
        delay(1000);
      }
    }

Refactoring: Reading the Movement from PIR Sensor

  • Aktualizujeme teda náš kód reprezentujúci správanie nočného svetla. Začneme tým, že rozšírime enumeračný typ enum state o nový stav LIGHT:

    enum state {
      DAY,
      NIGHT,
      LIGHT
    };

    zadefinujeme makro PIR, kde uvedieme číslo pin-u, ku ktorému je PIR senzor pripojený:

    #define PIR 2

    a nastavíme pin, ku ktorému je PIR senzor pripojený, na vstupný:

    // setup
    pinMode(PIR, INPUT);
  • Následne vytvoríme funkciu state_light(), ktorá bude reprezentovať správanie zariadenia v stave LIGHT:

    enum state state_light(){
      // on enter
      Serial.println(">> State Light Entered.");
      digitalWrite(LED, HIGH);
    
      // loop
      while(digitalRead(PIR) == HIGH){
        delay(1000);
      }
    
       // on exit
      digitalWrite(LED, LOW);
      return NIGHT;
    }
  • A opravíme správanie v hlavnej slučke pridaním ošetrenia správania v prípade stavu LIGHT:

    case LIGHT:
      state = state_light();
      break;
  • Zároveň však treba opraviť správanie v stave NIGHT, kedy sa nebude automaticky svetlo zapínať pri vstupe do stavu a budeme sledovať dve udalosti: stúpnutie intenzity svetla a vznik pohybu:

    enum state state_night(){
      // on enter
      Serial.println(">> State Night Entered.");
      digitalWrite(LED, LOW);
    
      // main loop
      enum state next_state = NIGHT;
    
      do{
        delay(1000);
    
        // check LDR
        int value = analogRead(LDR);
        value = map(value, 0, 1023, 0, 100);
        if(value > 45){
          next_state = DAY;
        }
    
        // check PIR
        if(digitalRead(PIR) == HIGH){
          next_state = LIGHT;
        }
      }while(next_state == NIGHT);
    
      // on leave
      return next_state;
    }

Well Done!

  • A je to! Ak teraz spustíme kód so všetkými pripojenými komponentmi, tak sa naše zariadenie naozaj bude správať, ako sme naplánovali podľa stavového diagramu.

  • Tu by sme mohli skončiť. Ale miesto toho sa s týmto zariadením budeme hrať ďalej, pretože sa dá rozšíriť niekoľkými spôsobmi:

    • svetlo sa bude rozsvecovať a zhasínať postupne a nie naraz
    • pre trvalú inštaláciu pridáme ešte aj vypínač, ktorým funkcionalitu budeme vedieť zapnúť resp. vypnúť
    • svetlo môže jemne svietiť aj bez toho, aby šiel niekto okolo
    • miesto LED diódy je možné pripojiť aj normálnu žiarovku na 220V, ktorú od Arduina oddelíme relé; to ale nebudeme robiť kvôli nebezpečiu úrazu elektrickým prúdom
    • zariadenie nemusí pracovať ako nočné svetlo so senzorom pohybu, ale napr. ako automatické svetlo, ktoré sa rozsvieti po otvorení dverí (do špajze alebo pivnice) v prípade, že je noc (takže to vlastne je nočné svetlo ;)
  • No a zo všetkých uvedených možností sa pozrieme na to, ako je možné svetlo stmievať a rozsvecovať. A budeme na to používať pulzne šírkovú moduláciu (z angl. Pulse Width Modulation), skrátene označovanú aj ako PWM.

Pulse-Width Modulation

Introduction

  • (slide) Zjednodušene môžeme povedať, že podstatou PWM je (rýchle) zapínanie a vypínanie zdroja napätia. Ak k takémuto zdroju pripojíme LED diódu, tak uvidíme, že bliká. Ak však budeme blikať dostatočne rýchlo, tak pri frekvencii 18 bliknutí za sekundu naše oko začne blikanie vnímať ako súvislé svetlo. Čiže v prípade blikania sme urobili akýsi podvod pre naše oko ;)

  • PWM teda môže byť použité na vytvorenie ilúzie, že LED dióda môže mať niekoľko úrovní jasu. Jas, ktorý dosiahneme, je stanovený z množstva času, v ktorom je LED dióda zapnutá voči času, v ktorom je LED dióda vypnutá. Čím bude dlhší pracovný cyklus (dlhší čas bude PIN nastavený na hodnotu 1 než na hodnotu 0), tým vyšší jas bude LED dióda mať.

  • (slide) Pozrime sa teda na to, ako taký pracovný cyklus vyzerá.

    Duty Cycle (zdroj)
  • (slide) A pozrime sa tiež na niekoľko rozličných pracovných cyklov. Čím je teda šírka pulzu menšia, tým je aj menší celkový výsledný výkon, resp. v našom prípade to bude úroveň jasu. Čím je naopak šírka pulzu väčšia, tým bude väčší aj výsledný výkon, resp. úroveň jasu.

    Various Duty Cycles (zdroj)

PWM and Arduino UNO

  • (slide) Pozrime sa teraz na to, ako je to s podporou PWM na doske Arduino UNO. Nie každý pin je možné totiž použiť ako PWM. Pulzne šírkovú moduláciu podporujú tie, ktoré sú označené symbolom “~”. Konkrétne sa jedná sa len o digitálne PIN-y 3, 5, 6, 9, 10 a 11.

  • (slide) Ak chceme pracovať s PWM na príslušnom pine, budeme používať funkciu analogWrite(), ktorá má dva parametre:

    1. číslo digitálneho PIN-u
    2. šírka pulzu pracovného cyklu v rozsahu od 0 (0% šírka pracovného cyklu) po 255 (100% šírka pracovného cyklu).

Enhancing the Security Night Light with PWM

  • Vráťme sa teda k našej prípadovej štúdii, na ktorej pracujeme - k nočnému svetlu. Ako som spomínal, urobíme tieto úpravy:

    • rozsvietenie svetla v stave LIGHT nebude skokové, ale postupné; rovnako tak to bude pri jeho zhasínaní
    • pri prechode do stavu NIGHT zvýšime jas na nízku úroveň, čím v danom mieste nebude totálna tma; pri prechode do stavu DAY zasa postupne jas znížime

Dim/light in the LIGHT State

  • Začneme teda s postupným rozsvietením a následne zhasnutím pri prejdení do stavu LIGHT resp. pri jeho opúšťaní:

    enum state state_light(){
      // on enter
      Serial.println(">> State Light Entered.");
      for(int level = 0; level < 255; level++){
          analogWrite(LED, level);
        delay(10);
      }
    
      // loop
      while(digitalRead(PIR) == HIGH){
        delay(1000);
      }
    
       // on exit
      for(int level = 255; level >= 0; level--){
          analogWrite(LED, level);
        delay(10);
      }
      return NIGHT;
    }

Dim/light in the NIGHT State

  • Použitie v prípade stavu NIGHT bude podobné. Hlavný rozdiel bude v tom, že nebudeme rozsvecovať svetlo naplno, ale len po úroveň, ktorú si zadefinujeme pomocou makra LIGHT_LOW:

    #define LIGHT_LOW 100
    
    enum state state_night(){
      // on enter
      Serial.println(">> State Night Entered.");
      for(int level = 0; level < LIGHT_LOW; level++){
          analogWrite(LED, level);
        delay(10);
      }
    
      // main loop
      enum state next_state = NIGHT;
      do{
        delay(1000);
        // check LDR
        int value = analogRead(LDR);
        value = map(value, 0, 1023, 0, 100);
        if(value > 45){
          next_state = DAY;
        }
    
        // check PIR
        if(digitalRead(PIR) == HIGH){
          next_state = LIGHT;
        }
      }while(next_state == NIGHT);
    
      // on exit
      if(next_state == DAY){
        for(int level = LIGHT_LOW; level >= 0; level--){
          analogWrite(LED, level);
          delay(10);
        }
      }
      return next_state;
    }
  • Samozrejme, aby všetko fungovalo tak, ako má, je potrebné upraviť aj ošetrenie stavu LIGHT. Rozsvietenie ako aj zhasnutie v tomto prípade nebude začínať od hodnoty 0, ale už len od hodnoty makra LIGHT_LOW.

Changing the Frequency

  • Výpočet zmeny frekvencie:

    register = register & mask | mode

    kde:

    • register je TCCR0B pre piny 5 a 6, TCCR1B pre piny 9 a 10 a TCCR2B pre piny 2 a 11
    • maska je 0b11111000
    • mode je hodnota deliča
  • Príklad zmeny frekvencie:

    // for PWM frequency of 30.64 Hz
    TCCR1B = TCCR1B & B11111000 | B00000101;

PWM Usages

  • (slide)

  • dimming of RGB LEDs

  • brightness of LEDs

  • control the direction of a servo motors

  • in telecomumunications (encoding/decoding data)

  • audio effects and amplification

Looking Forward

  • Nabudúce sa ešte pozrieme minimálne na komunikáciu po zbernici I2C a ak nám zvýši čas, povieme si niečo aj o prerušení a jeho ošetrení.