Operácie

Interaktívna lampička II: Rozdiel medzi revíziami

Zo stránky SensorWiki

StudentMIPS (diskusia | príspevky)
Bez shrnutí editace
StudentMIPS (diskusia | príspevky)
 
(12 medziľahlých úprav od rovnakého používateľa nie je zobrazených.)
Riadok 48: Riadok 48:


1. NÁPAD:
1. NÁPAD:
Projekt som realizoval u seba v izbe ako funkčné riešenie. Princíp zadania spočíval v tom, že moje svetlo nad stolom (inteligentná lampička) sa zažne pri prejdení rukou ponad skrytý senzor. Pri dlhšom podržaní ruky (približne 1.5 sekundy) sa rozsvieti LED pás prilepený za monitorom a vytvorí tak ambientné podsvietenie. Zároveň som pridal aj osvetlenie skrinky, na ktoré som použil iný senzor. Konkrétne išlo o Hallov senzor, ktorý sníma magnetické pole. V tomto prípade išlo o to že senzor nasnímal magnet ktorý som prilepil na dvierka skrinky. Pri oddialení dvierok senzor prestal detegovať magnetické pole od magnetu a svetlo sa rozsvietilo.
Projekt som realizoval u seba v izbe ako funkčné riešenie. Princíp zadania spočíval v tom, že moje svetlo nad stolom (inteligentná lampička) sa zažne pri prejdení rukou ponad skrytý senzor. Pri dlhšom podržaní ruky (približne 1 sekunda) sa rozsvieti LED pás prilepený za monitorom a vytvorí tak ambientné podsvietenie. Zároveň som pridal aj osvetlenie skrinky, na ktoré som použil iný senzor. Konkrétne išlo o Hallov senzor, ktorý sníma magnetické pole. V tomto prípade išlo o to že senzor nasnímal magnet ktorý som prilepil na dvierka skrinky. Pri oddialení dvierok senzor prestal detegovať magnetické pole od magnetu a svetlo sa rozsvietilo.




Riadok 79: Riadok 79:
=== Algoritmus a program ===
=== Algoritmus a program ===


Algoritmus programu využíva toto a toto, základné funkcie sú takéto a voláma ich tuto...  
Čo program robí:
Ovláda 3 svetlá pomocou dvoch senzorov. IR senzor reaguje na pohyb ruky a Hallov senzor reaguje na otvorenie skrinky.
 
 
Hardvér ktorý program využíva:
 
Vstupy:
 
- IR senzor (PD2) – deteguje ruku
 
- Hallov senzor (PD5) – deteguje magnet na dverách skrinky
 
Výstupy:
 
- Relé 1 – hlavná lampa (PD3)
 
- Relé 2 – druhé svetlo (PD4)
 
- Relé 3 – svetlo v skrinke (PD6)
 
 
 
Základné funkcie a čo robia:
 
timer0_init() – nastaví hardvérový časovač aby každú 1 ms spustil prerušenie. Bez toho by program nevedel merať čas.
 
ISR(TIMER0_COMPA_vect) – automaticky sa zavolá každú 1 ms a zvýši čítač ms_ticks o 1. Program ju nevolá sám, spúšťa ju hardvér.
 
millis() – vráti počet milisekúnd od štartu programu. Používa sa na meranie debounce aj dlhého podržania.
 
main() – hlavná funkcia, obsahuje celú logiku. Beží v nekonečnej slučke a stále kontroluje senzory.
Výpis kódu je nižšie...
Výpis kódu je nižšie...
Hlavné mechanizmy:
Debounce – filter šumu. Senzory pri zmene stavu chvíľu kmitajú, debounce to ignoruje a uzná zmenu až keď je stav stabilný 50 ms.
Krátke mávnutie – ruka príde a odíde pred 1 sekundou → prepne sa hlavná lampa (Relé 1).
Dlhé podržanie – ruka zostane pred senzorom aspoň 1 sekundu → prepne sa druhé svetlo (Relé 2).
Hallov senzor – priamo riadi svetlo v skrinke. Dvere otvorené = svetlo zapnuté, dvere zatvorené = svetlo vypnuté.
Knižnice ktoré program používa:
avr/io.h :          Názvy registrov čipu (DDRD, PORTD...)
avr/interrupt.h :  Práca s prerušeniami (sei, cli, ISR)
stdint.h :          Typy uint8_t, uint32_t
stdbool.h :        Typ bool (true/false)
Pre lepšiu prehladnosť a jednoduchší popis fungovania je celé fungovanie programu popísane nižšie v komentároch pri konkrétnych príkazoch, na druhom okne je čistý program bez komentárov. Knižnicu .h som nevytváral, miesto toho som vložil všetko do "main.c" programu keďže išlo iba o pár príkazov.




<tabs>
<tabs>
<tab name="AVR C-code"><syntaxhighlight  lang="c++" style="background: LightYellow;">
<tab name="program_s_komentarom.c"><syntaxhighlight  lang="c++" style="background: LightYellow;">
 
#include <avr/io.h>  // Definície registrov AVR (DDRD, PORTD, PIND, atd.)
/*
/*


Riadok 91: Riadok 150:
  */
  */


#include <avr/io.h>        // Definície registrov AVR (DDRD, PORTD, PIND, atd.)
     
#include <avr/interrupt.h>  // Makrá pre prerušenia: sei(), cli(), ISR()
#include <avr/interrupt.h>  // Makrá pre prerušenia: sei(), cli(), ISR()
#include <stdint.h>        // Typy s pevnou šírkou: uint8_t, uint32_t, atd.
#include <stdint.h>        // Typy s pevnou šírkou: uint8_t, uint32_t, atd.
Riadok 138: Riadok 197:
  *
  *
  * Konfigurácia:
  * Konfigurácia:
  *   • CTC mód (Clear Timer on Compare Match):
  *   -CTC mód (Clear Timer on Compare Match):
  *      čítač sa resetuje na 0 vždy, keď dosiahne hodnotu OCR0A
  *      čítač sa resetuje na 0 vždy, keď dosiahne hodnotu OCR0A
  *   • Prescaler 64:
  *   -Prescaler 64:
  *      hodinový signál 16 MHz sa vydelí 64 → 250 000 tikov/s
  *      hodinový signál 16 MHz sa vydelí 64 → 250 000 tikov/s
  *   • OCR0A = 249:
  *   -OCR0A = 249:
  *      prerušenie nastane po 250 tikoch → 250 000 / 250 = 1 000×/s = každú 1 ms
  *      prerušenie nastane po 250 tikoch → 250 000 / 250 = 1 000×/s = každú 1 ms
  *
  *
Riadok 164: Riadok 223:
   
   
   Prečo som použil cli()/SREG?
   Prečo som použil cli()/SREG?
    Tento trik mi poradilo AI, najprv som to mal bez toho, no občas sa stalo že celý systém robil chyby a lampička sa z ničoho nič zapla alebo preblikla a podobne.
Po tom ako som implementoval tuto funkciu tak všetko ide bez problémov. Nie je to potrebné a program v 90% času funguje správne, ale keďže to každodenne používam tak som to chcel čo najspoľahlivejšie.
     ms_ticks má 32 bitov, ale AVR je 8-bitový procesor.
     ms_ticks má 32 bitov, ale AVR je 8-bitový procesor.
     Čítanie prebehne v 4 krokoch. Keby nastalo prerušenie uprostred čítania, dostali by sme nekonzistentnú hodnotu (napr. spodné 2 bajty zo starého čísla a vrchné 2 bajty z nového).
     Čítanie prebehne v 4 krokoch. Keby nastalo prerušenie uprostred čítania, dostali by sme nekonzistentnú hodnotu (napr. spodné 2 bajty zo starého čísla a vrchné 2 bajty z nového).
     Riešenie: počas čítania dočasne zakážeme prerušenia (cli), uložíme hodnotu a obnovíme pôvodný stav (SREG).  
     Riešenie: počas čítania dočasne zakážeme prerušenia (cli), uložíme hodnotu a obnovíme pôvodný stav (SREG).  
Tento trik mi poradilo AI, najprv som to mal bez toho, no občas sa stalo že celý sistém robil chyby a lampička sa z ničoho nič zapla alebo preblikla a podobne.
Po tom ako som implementoval tuto funkciu tak všetko ide bez problémov. Nie je to potrebné a program v 90% času funguje správne, ale keďže to každodenne používam tak som to chcel čo najspoľahlivejšie.
  */
  */
static inline uint32_t millis(void) {
static inline uint32_t millis(void) {
Riadok 184: Riadok 244:
     TCCR0B = (1 << CS01) | (1 << CS00);  // CS01+CS00 → prescaler 64 (delič hodinového signálu)
     TCCR0B = (1 << CS01) | (1 << CS00);  // CS01+CS00 → prescaler 64 (delič hodinového signálu)
     OCR0A  = 249;                        // Hodnota zhody: prerušenie každých 250 tikov = každú 1 ms
     OCR0A  = 249;                        // Hodnota zhody: prerušenie každých 250 tikov = každú 1 ms
     TIMSK0 = (1 << OCIE0A);              // OCIE0A=1 → povol prerušenie pri zhode s OCR0A
     TIMSK0 = (1 << OCIE0A);              // OCIE0A=1 → povolí prerušenie pri zhode s OCR0A
}
}


Riadok 353: Riadok 413:


     return 0;  // Tento je nedosiahnuteľná – MCU nikdy neskončí main()
     return 0;  // Tento je nedosiahnuteľná – MCU nikdy neskončí main()
}


</syntaxhighlight ></tab>
<tab name="program.c"><syntaxhighlight  lang="c++" style="background: LightYellow;">
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include <stdbool.h>


Pridajte sem aj zbalený kompletný projekt, napríklad takto (použite jednoznačné pomenovanie, nemôžeme mať na serveri 10x ''zdrojaky.zip'':
#define IR_PIN        PD2
#define RELE_LAMPA    PD3
#define RELE_DRUHE    PD4
#define HALL_PIN      PD5
#define RELE_SKRINKA  PD6


Zdrojový kód: [[Médiá:projektMenoPriezvisko.zip|zdrojaky.zip]]
#define DEBOUNCE_DELAY        50UL
#define LIMIT_DLHE_PODRZANIE 1000UL
 
#define PIN_HIGH(port, pin)  ((port) |=  (1 << (pin)))
#define PIN_LOW(port, pin)    ((port) &= ~(1 << (pin)))
#define PIN_READ(port, pin)  (((port) >> (pin)) & 1)
#define PIN_TOGGLE(port, pin) ((port) ^=  (1 << (pin)))
 
volatile uint32_t ms_ticks = 0;
 
ISR(TIMER0_COMPA_vect) {
    ms_ticks++;
}
 
static inline uint32_t millis(void) {
    uint32_t t;
    uint8_t sreg = SREG;
    cli();
    t = ms_ticks;
    SREG = sreg;
    return t;
}
 
static void timer0_init(void) {
    TCCR0A = (1 << WGM01);
    TCCR0B = (1 << CS01) | (1 << CS00);
    OCR0A  = 249;
    TIMSK0 = (1 << OCIE0A);
}
 
int main(void) {
 
    DDRD &= ~(1 << IR_PIN);
    DDRD &= ~(1 << HALL_PIN);
    PORTD |= (1 << HALL_PIN);
    DDRD |= (1 << RELE_LAMPA) | (1 << RELE_DRUHE) | (1 << RELE_SKRINKA);
 
    PIN_LOW(PORTD, RELE_LAMPA);
    PIN_LOW(PORTD, RELE_DRUHE);
    PIN_LOW(PORTD, RELE_SKRINKA);
 
    timer0_init();
    sei();
 
    bool    rukaJeTam                  = false;
    bool    akciaDlhePodrzanieVykonana = false;
    uint32_t casZmenyIR                = 0;
    uint8_t  poslednyStabilnyStavIR    = 1;
    uint32_t startIR                    = 0;
 
    uint8_t  poslednyStabilnyStavHall  = 1;
    uint32_t casZmenyHall              = 0;
 
    bool stavLampa    = false;
    bool stavDruheRele = false;
 
    while (1) {
        uint32_t aktualnyCas = millis();
 
        uint8_t aktualnyStavIR = PIN_READ(PIND, IR_PIN);
 
        if (aktualnyStavIR != poslednyStabilnyStavIR) {
            casZmenyIR            = aktualnyCas;
            poslednyStabilnyStavIR = aktualnyStavIR;
        }
 
        if ((aktualnyCas - casZmenyIR) > DEBOUNCE_DELAY) {
 
            if (aktualnyStavIR == 0 && !rukaJeTam) {
                rukaJeTam                  = true;
                akciaDlhePodrzanieVykonana = false;
                startIR                    = aktualnyCas;
            }
 
            if (aktualnyStavIR == 0 && rukaJeTam && !akciaDlhePodrzanieVykonana) {
                if ((aktualnyCas - startIR) >= LIMIT_DLHE_PODRZANIE) {
                    stavDruheRele = !stavDruheRele;
                    if (stavDruheRele) PIN_HIGH(PORTD, RELE_DRUHE);
                    else              PIN_LOW (PORTD, RELE_DRUHE);
                    akciaDlhePodrzanieVykonana = true;
                }
            }
 
            if (aktualnyStavIR == 1 && rukaJeTam) {
                if (!akciaDlhePodrzanieVykonana) {
                    stavLampa = !stavLampa;
                    if (stavLampa) PIN_HIGH(PORTD, RELE_LAMPA);
                    else          PIN_LOW (PORTD, RELE_LAMPA);
                }
                rukaJeTam = false;
            }
        }
 
        uint8_t aktualnyStavHall = PIN_READ(PIND, HALL_PIN);
 
        if (aktualnyStavHall != poslednyStabilnyStavHall) {
            casZmenyHall            = aktualnyCas;
            poslednyStabilnyStavHall = aktualnyStavHall;
        }
 
        if ((aktualnyCas - casZmenyHall) > DEBOUNCE_DELAY) {
            if (aktualnyStavHall == 1) PIN_HIGH(PORTD, RELE_SKRINKA);
            else                      PIN_LOW (PORTD, RELE_SKRINKA);
        }
    }
 
    return 0;
}
 
 
</syntaxhighlight ></tab>
</tabs>
 
 
 
Zdrojový kód: [[Médiá:Inteligentna_Lampicka_II.zip.zip|Inteligentná lampička.zip]]


=== Overenie ===
=== Overenie ===
Riadok 365: Riadok 551:


'''Video:'''
'''Video:'''
<center><youtube>D0UnqGm_miA</youtube></center>
 
<center><youtube>CcsgkNgMwo8</youtube></center>


== Čo by som urobil inak ==
== Čo by som urobil inak ==

Aktuálna revízia z 20:40, 28. máj 2026

Záverečný projekt predmetu MIPS / LS2026 - Michal Čavojský


Zadanie

Zadaním môjho semestrálneho projektu bolo vytvoriť a naprogramovať interaktívnu inteligentnú lampičku. Zadanie som realizoval doma s vlastnými komponentmi a vytvoril som si osvetlenie procovného stolíka a podsvietenie monitora, ovládané pomocou IR senzora, dosky kopie Arduino UNO (v mojom prípade TZTUNO R3+wifi, funkčnosťou totožné s vývojovou doskou arduino UNO alebo ACROB), 5V relé a dvoch LED pásov.

Použitá doska Arduino UNO



Analýza a opis riešenia

Použité súčiastky:

- TZTUNO R3: Riadiaca jednotka

- 4-Kanálový 5V relé modul: Spínanie

- FC-51 IR senzor: Snímač pohybu ruky

- SEN-KY003HMS Hallov senzor: Snímač otvorenia dvierok

- 12V LED pás: Svetlo, funkčný prvok ktorý ovládame

- vodiče: Prepojenie periférií

- napájacie zdroje: Zabezpečenie stabilného napájanie súčiastok


Datasheety a dokumenty:

Schéma zapojenia:

Schéma zapojenia projektu
Schéma zapojenia projektu


1. NÁPAD: Projekt som realizoval u seba v izbe ako funkčné riešenie. Princíp zadania spočíval v tom, že moje svetlo nad stolom (inteligentná lampička) sa zažne pri prejdení rukou ponad skrytý senzor. Pri dlhšom podržaní ruky (približne 1 sekunda) sa rozsvieti LED pás prilepený za monitorom a vytvorí tak ambientné podsvietenie. Zároveň som pridal aj osvetlenie skrinky, na ktoré som použil iný senzor. Konkrétne išlo o Hallov senzor, ktorý sníma magnetické pole. V tomto prípade išlo o to že senzor nasnímal magnet ktorý som prilepil na dvierka skrinky. Pri oddialení dvierok senzor prestal detegovať magnetické pole od magnetu a svetlo sa rozsvietilo.


2. HARDWAREOVÁ REALIZÁCIA: Ako prvé som potreboval zabezpečiť hardware pomocou ktorého všetko bude fungovať tak ako som si naplánoval. Bolo potrebné kúpiť všetky hore uvedené súčiastky. Projekt som najprv riešil iba jednoduchým prepojením dielov na stolíku (tz. na kolene).Po teste funkčnosti celého systému som sa pustil do hardwarovej realizácie. Keďže LED pásiky sú napájané 12V a napájanie z arduina by nebolo dostatočné ani napätím a ani prúdom, zvolil som napájanie všetkého pomocou jedného napájacieho zdroja, ktorý už bol k lampičke, informácie k nemu som nedohľadal no základné veci sú na obrázku nižšie. Relé boli zapojené 2x ako NC (tj. normaly closed) a 1x ako NO (normaly open), NC boli zapojenia pri lampičkách nad stolíkom a NO bolo pre skrinku, z jednoduchého dôvodu. Keďže Hallov snímač snímal 0 alebo 1 signál od reakcie magnetického poľa a svetlo malo byť vypnuté vtedy ak je magnet priblížený (tj. senzor ukazuje hodnotu 1) je chytrejšie relé zapojiť tak že pri ukázanej 1 je zatvorené a pri 0 sa zopne. Dalo by sa to vyriešiť aj softwarovo, no mohlo by dochádzať k mýleniu a zbytočným chybám v kóde. Relé aj s riadiacou jednotkou (ďalej nazývané skrátene ako RJ) som pripojil pod stolíkom, napájanie zabezpečovali napájacie zdroje (5V pre RJ, 12V pre LED pásiky). Na pripojenie pri stolíku som využil reproduktorový kábel a vodiče ktoré boli už s lampičkou. Do skrinky som použil 8 vodičový kábel, keďže bolo potrebné pripojiť 2x osvetlenie (3ks vodičov) a senzor (3ks vodičov) a tento 8 žilový kábel som našiel doma, zároveň ak by som chcel rozšíriť tento systém o ďaľšiu perifériu, nemusím pridávať vodiče. Spoje boli spájkované a zároveň zaliate do plastu pomocou tavnej pištole, prípadne ošetrené zmršťovacímy páskami pre bezpečné používanie. IR senzor som prichytil vedľa stolíka na pohodlné miesto, pre uchytenie som namodeloval a vytlačil plastový úchyt, do ktorého som tento senzor vložil a taktiež ošetril zaliatím do plastu.


Prvé skúšky zapájania pred finálnou inštaláciou


3. SOFTWAROVÁ REALIZÁCIA Program som vytváral na základe podkladov z cvičení a súborov z nich, no použil som aj rady na githube alebo od AI. Všetko ku kódu je spomenuté v nasledujúcej časti spolu so samotným kódom.

12V napájací zdroj na LED pásiky
Zapojená riadiaca jednotka
Zapojený hallov senzor v skrinke na snímanie magnetu
IR senzor nainštalovaný na stolíku v 3D vytlačenom púzdre
Zapojené relé
Funknčne zapojený LED pás (lampička), prívody sú prispájkované a zaliate


Algoritmus a program

Čo program robí: Ovláda 3 svetlá pomocou dvoch senzorov. IR senzor reaguje na pohyb ruky a Hallov senzor reaguje na otvorenie skrinky.


Hardvér ktorý program využíva:

Vstupy:

- IR senzor (PD2) – deteguje ruku

- Hallov senzor (PD5) – deteguje magnet na dverách skrinky

Výstupy:

- Relé 1 – hlavná lampa (PD3)

- Relé 2 – druhé svetlo (PD4)

- Relé 3 – svetlo v skrinke (PD6)


Základné funkcie a čo robia:

timer0_init() – nastaví hardvérový časovač aby každú 1 ms spustil prerušenie. Bez toho by program nevedel merať čas.

ISR(TIMER0_COMPA_vect) – automaticky sa zavolá každú 1 ms a zvýši čítač ms_ticks o 1. Program ju nevolá sám, spúšťa ju hardvér.

millis() – vráti počet milisekúnd od štartu programu. Používa sa na meranie debounce aj dlhého podržania.

main() – hlavná funkcia, obsahuje celú logiku. Beží v nekonečnej slučke a stále kontroluje senzory. Výpis kódu je nižšie...


Hlavné mechanizmy:

Debounce – filter šumu. Senzory pri zmene stavu chvíľu kmitajú, debounce to ignoruje a uzná zmenu až keď je stav stabilný 50 ms.

Krátke mávnutie – ruka príde a odíde pred 1 sekundou → prepne sa hlavná lampa (Relé 1).

Dlhé podržanie – ruka zostane pred senzorom aspoň 1 sekundu → prepne sa druhé svetlo (Relé 2).

Hallov senzor – priamo riadi svetlo v skrinke. Dvere otvorené = svetlo zapnuté, dvere zatvorené = svetlo vypnuté.


Knižnice ktoré program používa:

avr/io.h : Názvy registrov čipu (DDRD, PORTD...)

avr/interrupt.h : Práca s prerušeniami (sei, cli, ISR)

stdint.h : Typy uint8_t, uint32_t

stdbool.h : Typ bool (true/false)


Pre lepšiu prehladnosť a jednoduchší popis fungovania je celé fungovanie programu popísane nižšie v komentároch pri konkrétnych príkazoch, na druhom okne je čistý program bez komentárov. Knižnicu .h som nevytváral, miesto toho som vložil všetko do "main.c" programu keďže išlo iba o pár príkazov.


#include <avr/io.h>  // Definície registrov AVR (DDRD, PORTD, PIND, atd.)
/*

        Inteligentná lampička II v AVR C (ATmega328P) Michal Čavojský         
 
 */

       
#include <avr/interrupt.h>  // Makrá pre prerušenia: sei(), cli(), ISR()
#include <stdint.h>         // Typy s pevnou šírkou: uint8_t, uint32_t, atd.
#include <stdbool.h>        // Typ bool a hodnoty true/false (1/0)

/* ****************************
 * DEFINÍCIA PINOV
 * Všetky piny sú na Porte D (PD2–PD6).
 * Používame čísla bitov, nie absolútne adresy.
 ****************************************************
 */
#define IR_PIN        PD2   // Vstup: IR senzor (detekcia ruky)
#define RELE_LAMPA    PD3   // Výstup: Relé 1 – hlavná lampa
#define RELE_DRUHE    PD4   // Výstup: Relé 2 – druhé svetlo (dlhé podržanie)
#define HALL_PIN      PD5   // Vstup: Hallov senzor (dvere)
#define RELE_SKRINKA  PD6   // Výstup: Relé 3 – svetlo v skrinke

/* ****************************************************
 * ČASOVÉ KONŠTANTY
 * UL = unsigned long (32-bit), aby nedošlo k pretečeniu pri výpočtoch
 * ***************************************************** */
#define DEBOUNCE_DELAY        50UL   // 50 ms – čas, počas ktorého musí byť stav stabilný, aby sme ho uznali ako platný (filtrovanie zákmitov)                                  
#define LIMIT_DLHE_PODRZANIE 1000UL  // 1000 ms = 1 s – minimálny čas podržania ruky pred IR senzorom pre "dlhú akciu"
                                     

/************************************************** 
  POMOCNÉ MAKRÁ PRE PRÁCU S PINMI
 
  Namiesto priameho písania bitových operácií všade v kóde som si pomenoval makrá aby bol kód čitateľnejší a jednoduchší  (s týmto mi pomohol internet)
  Princíp bitových operácií:
    (1 << pin)       – maska: bit na pozícii 'pin' je 1, ostatné 0
    port |=  maska   – nastav bit na 1 (ostatné nemení)   → HIGH
    port &= ~maska   – nastav bit na 0 (ostatné nemení)   → LOW
    port ^=  maska   – prehoď bit (XOR)                   → TOGGLE
    (port >> pin)&1  – prečítaj hodnotu bitu (0 alebo 1)  → READ
 ********************************************************* */

#define PIN_HIGH(port, pin)   ((port) |=  (1 << (pin)))   // Nastavime pin na HIGH (log. 1)
#define PIN_LOW(port, pin)    ((port) &= ~(1 << (pin)))   // Nastavime pin na LOW  (log. 0)
#define PIN_READ(port, pin)   (((port) >> (pin)) & 1)     // Prečítaj stav pinu (0 alebo 1)
#define PIN_TOGGLE(port, pin) ((port) ^=  (1 << (pin)))   // Prehoď stav pinu ( z 1 na 0, z 0 na 1)

/*************************************************************
TIMER0
 * Musíme si ju vytvoriť sami pomocou hardvérového časovača Timer0, tak ako sme to robili na cvičení. Celý postup je napísaný nižšie
 *
 * Konfigurácia:
 *    -CTC mód (Clear Timer on Compare Match):
 *       čítač sa resetuje na 0 vždy, keď dosiahne hodnotu OCR0A
 *    -Prescaler 64:
 *       hodinový signál 16 MHz sa vydelí 64 → 250 000 tikov/s
 *    -OCR0A = 249:
 *       prerušenie nastane po 250 tikoch → 250 000 / 250 = 1 000×/s = každú 1 ms
 *
 * Výpočet: 16 000 000 Hz / 64 / 250 = 1 000 Hz → T = 1 ms ✓
 * ************************************************************ */

/* Globálny čítač milisekúnd.
 * 'volatile' je povinné – premenná sa mení v ISR (prerušení),
 * kompilátor ju preto nesmie cachovať v registri. */
volatile uint32_t ms_ticks = 0;

/* ISR = Interrupt Service Routine (obslužná rutina prerušenia).
 * Táto funkcia sa automaticky zavolá každú 1 ms, keď Timer0 dosiahne hodnotu OCR0A. Jednoducho inkrementujeme čítač.
 */
ISR(TIMER0_COMPA_vect) {
    ms_ticks++;  // +1 každú milisekundu
}

/* **************************************************************
	Funkcia millis() – vracia počet milisekúnd od štartu programu.
 
  Prečo som použil cli()/SREG?
    Tento trik mi poradilo AI, najprv som to mal bez toho, no občas sa stalo že celý systém robil chyby a lampička sa z ničoho nič zapla alebo preblikla a podobne.
	Po tom ako som implementoval tuto funkciu tak všetko ide bez problémov. Nie je to potrebné a program v 90% času funguje správne, ale keďže to každodenne používam tak som to chcel čo najspoľahlivejšie.
    ms_ticks má 32 bitov, ale AVR je 8-bitový procesor.
    Čítanie prebehne v 4 krokoch. Keby nastalo prerušenie uprostred čítania, dostali by sme nekonzistentnú hodnotu (napr. spodné 2 bajty zo starého čísla a vrchné 2 bajty z nového).
    Riešenie: počas čítania dočasne zakážeme prerušenia (cli), uložíme hodnotu a obnovíme pôvodný stav (SREG). 
	
	  */
static inline uint32_t millis(void) {
    uint32_t t;
    uint8_t sreg = SREG;  // Ulož aktuálny stav príznakového registra (vrátane bitu I = global interrupt enable)
    cli();                // Zakáž všetky prerušenia (aby ISR neprerušil naše čítanie)
    t = ms_ticks;         // Bezpečne prečítaj 32-bitovú hodnotu
    SREG = sreg;          // Obnov pôvodný stav prerušení (nezáleží, či boli povolené alebo nie)
    return t;
}

/* Inicializácia Timer0 pre generovanie 1 ms prerušení */
static void timer0_init(void) {
    TCCR0A = (1 << WGM01);               // WGM01=1 → CTC mód (čítač sa resetuje pri zhode s OCR0A)
    TCCR0B = (1 << CS01) | (1 << CS00);  // CS01+CS00 → prescaler 64 (delič hodinového signálu)
    OCR0A  = 249;                         // Hodnota zhody: prerušenie každých 250 tikov = každú 1 ms
    TIMSK0 = (1 << OCIE0A);              // OCIE0A=1 → povolí prerušenie pri zhode s OCR0A
}

/********************************************************************
   HLAVNÝ PROGRAM
**********************************************************************/
int main(void) {

    /* ── NASTAVENIE SMEROV PINOV  ──
     * Každý pin môže byť buď vstup (0) alebo výstup (1). */

    DDRD &= ~(1 << IR_PIN);    // IR pin = VSTUP (bit na 0)
    DDRD &= ~(1 << HALL_PIN);  // Hall pin = VSTUP (bit na 0)

    /* **************************************************************
	 Interný pull-up pre Hallov senzor:
     Keď je pin vstupný a nastavíme PORTD bit na 1, zapne sa interný pull-up rezistor.
     Pin je teda v pokoji HIGH, senzor ho prepne na LOW pri aktivácii. Takže pri priblíženom magnete nám vykazuje hodnotu 1, pri oddialení magnetu prepne na hodnotu 0, aj keď realita je opačná a pri hodnote 0 sa svetlo zažne, no to sme ošetrili zapojením relé */
    PORTD |= (1 << HALL_PIN);

    /* Relé piny = VÝSTUPY (bit na 1) */
    DDRD |= (1 << RELE_LAMPA) | (1 << RELE_DRUHE) | (1 << RELE_SKRINKA);

    // Pri zapnutí vypneme všetky relé (LOW = relé rozopnuté). Slúži to ako jednoduché zabezpečenie toho že na začiatku vieme v akej polohe relé sú, pretože po resete môžu byť v nedefinovanom stave
     
    PIN_LOW(PORTD, RELE_LAMPA);
    PIN_LOW(PORTD, RELE_DRUHE);
    PIN_LOW(PORTD, RELE_SKRINKA);

    /* Spustime Timer0 a povolíme globálne prerušenia.
     * sei() = Set Enable Interrupts – nastaví bit I v SREG.
     * Bez sei() by ISR(TIMER0_COMPA_vect) nikdy nenastala. */
	
    timer0_init();
    sei();

    /* **************************** STAVOVÉ PREMENNÉ – IR SENZOR ******************************************************
		keďže tento vstup je digitálny a môže nadobudnúť len dve hodnoty (0/1) tak používame premennú bool kde false=0 a true=1
	 */

    bool rukaJeTam = false;
    /* ak je true = ruka je aktuálne pred senzorom (IR blokovaný).
     * Slúži nám ako "pamäť" – vieme, že sme detekovali príchod ruky a čakáme na jej odchod. */

    bool akciaDlhePodrzanieVykonana = false;
    /* true = počas aktuálneho podržania ruky sme už prepli Relé 2.
     * Zabráni opakovanému prepínaniu aj opakovanému spusteniu relé 1 pri odchode ruky. */

    uint32_t casZmenyIR = 0;
    /* Čas (ms), kedy sme naposledy zaznamenali zmenu stavu IR pinu. uint32_t používame z dôvodu uloženia časovej hodnoty a keďže nevieme ako bude dlhá tak pre istotu použijeme najdlhší formát pre uloženie 32bit.
     * Používa sa pre debounce – stav musí byť stabilný aspoň DEBOUNCE_DELAY ms, aby bol uznaný za platný. */

    uint8_t poslednyStabilnyStavIR = 1;
    /* Posledný uznaný (stabilný) stav IR pinu. Tu použivame uint8_t, pretože hodnota nebude iná ako 0 alebo 1, no nemožeme použiť bool, lebo PIN_READ vracia hodnotu v uint8_t.
     * Inicializujeme na 1 (HIGH = senzor voľný, žiadna ruka). Porovnávame s aktuálnym stavom – ak sa líšia, stav sa zmenil. */

    uint32_t startIR = 0;
    /* Čas (ms), kedy ruka prišla pred senzor (začiatok blokovania). uint32_t tu používame z rovnakeho dôvodu ako pri premennej cazZmenyIR
     * Od tohto momentu meriame, ako dlho ruka zostáva – či je to krátke mávnutie alebo dlhé podržanie. */
	

    /* ********************  STAVOVÉ PREMENNÉ – HALLOV SENZOR  ***********************************************************************/

    uint8_t poslednyStabilnyStavHall = 1;
    /* Posledný uznaný stav Hallovho senzora (HIGH = skrinka otvorená). rovnako tu používame uint8_t ako pri IR senzore*/

    uint32_t casZmenyHall = 0;
    /* Čas poslednej zmeny stavu Hallovho senzora – pre debounce. */

    /*************************** STAVY RELÉ ************************************* */

    bool stavLampa = false;
    /* Aktuálny logický stav Relé 1 (true = zapnuté, false = vypnuté).
     * Udržujeme ho v pamäti, aby sme vedeli na aký stav prepnúť. */

    bool stavDruheRele = false;
    /* Aktuálny logický stav Relé 2 (true = zapnuté, false = vypnuté). */

    /* *******************
     * HLAVNÁ SLUČKA
     * Beží nepretržite (nekonečná slučka = štandardný vzor pre MCU ako sme používali doteraz).
     * Každou iteráciou skontrolujeme senzory a zareagujeme.
     * *********************** */
    while (1) {

        /* Aktuálny čas v ms – použijeme ho pri všetkých časových porovnaniach v tejto iterácii, aby boli konzistentné. */
        uint32_t aktualnyCas = millis();

   
        /* Prečítaj aktuálny fyzický stav IR pinu z registra PIND.
         * PIND = Port D Input register – obsahuje aktuálne stavy všetkých vstupných pinov na Porte D. */
        uint8_t aktualnyStavIR = PIN_READ(PIND, IR_PIN);

        /* DEBOUNCE – filtrovanie zákmitov signálu:
         * Ak sa fyzický stav líši od posledného uznaného stavu, zaznamenaj čas tejto zmeny a aktualizuj "posledný stav".
         * Hodiny debounce sa tým reštartujú – musíme počkať ďalších DEBOUNCE_DELAY ms bez ďalšej zmeny. Toto nám stabilizuje systém  */
        if (aktualnyStavIR != poslednyStabilnyStavIR) {
            casZmenyIR             = aktualnyCas;   // Reset debounce časovača
            poslednyStabilnyStavIR = aktualnyStavIR; // Aktualizuj "sledovaný" stav
        }

        /* Spracuj IR stav iba ak je stabilný dlhšie ako DEBOUNCE_DELAY.
         * Rozdiel (aktualnyCas - casZmenyIR) rastie každou ms. Pokiaľ pin stále "kmitá", casZmenyIR sa stále resetujea podmienka nie je splnená. Toto spolupracuje s debounce časťou ktorá je o riadok vyššie, celý tento debounce "mechanizmus" som zisťoval najdlhšie, pretože bez neho sa systém chová absolutne nepredvídateľne a nespoľahlivo hlavne pri dlhom podržaní */
        if ((aktualnyCas - casZmenyIR) > DEBOUNCE_DELAY) {

            /* *** PRÍCHOD RUKY ****
             * Podmienky: pin je LOW (ruka blokuje) A ešte sme nezaregistrovali príchod (rukaJeTam == false).
             * Dvojitá podmienka zabráni opakovanému spusteniu. */
            if (aktualnyStavIR == 0 && !rukaJeTam) {
                rukaJeTam                  = true;       // Označ: ruka je pred senzorom
                akciaDlhePodrzanieVykonana = false;      // Reset – dlhá akcia ešte nenastala
                startIR                    = aktualnyCas; // Zapamätaj čas príchodu ruky
            }

            /* ***** KONTROLA DLHÉHO PODRŽANIA ******
             * Podmienky: ruka je stále tam, dlhá akcia ešte nebola vykonaná a uplynul aspoň LIMIT_DLHE_PODRZANIE od príchodu. To nám zabezpečí že sa relé nezopne skôr ako má
             * Meriame čas od startIR (príchod ruky), nie od casZmenyIR, pretože casZmenyIR sa zmenilo na "LOW" pri príchode a odvtedy sa nemenilo (ruka stále blokuje). */
            if (aktualnyStavIR == 0 && rukaJeTam && !akciaDlhePodrzanieVykonana) {
                if ((aktualnyCas - startIR) >= LIMIT_DLHE_PODRZANIE) {
                    stavDruheRele = !stavDruheRele;  // Prehoď logický stav (true↔false)
                   
				    // Fyzicky nastav výstupný pin podľa nového stavu: 
                    if (stavDruheRele) PIN_HIGH(PORTD, RELE_DRUHE);
                    else               PIN_LOW (PORTD, RELE_DRUHE);
                    akciaDlhePodrzanieVykonana = true;  // Označ: dlhá akcia prebehla, môžeme zopnúť rele 
                }
            }

            /*************** ODCHOD RUKY ******************
             * Podmienky: pin je HIGH (lúč voľný) a predtým sme zaregistrovali príchod (rukaJeTam = true).
             * Ak dlhá akcia NEBOLA vykonaná tak ide o krátke mávnutie takže systém prepne Relé 1 (LAMPA).
             * Ak dlhá akcia BOLA vykonaná systém už nič nerobí pretože relé 2 už bolo prepnuté skôr. */
            if (aktualnyStavIR == 1 && rukaJeTam) {
                if (!akciaDlhePodrzanieVykonana) {
                    stavLampa = !stavLampa;  // Prehoď logický stav lampy 
                    if (stavLampa) PIN_HIGH(PORTD, RELE_LAMPA);
                    else           PIN_LOW (PORTD, RELE_LAMPA);
                }
                rukaJeTam = false;  // Reset – čakáme na ďalší príchod ruky
            }
        }

        /******************************************
         *  HALLOV SENZOR (DVERE SKRINKY)
         ******************************************* */

        /* Prečítaj aktuálny stav Hallovho senzora */
        uint8_t aktualnyStavHall = PIN_READ(PIND, HALL_PIN);

        /* DEBOUNCE – rovnaký princíp ako pri IR senzore:
         * Ak sa stav zmenil, resetujeme časovač. */
        if (aktualnyStavHall != poslednyStabilnyStavHall) {
            casZmenyHall             = aktualnyCas;
            poslednyStabilnyStavHall = aktualnyStavHall;
        }

        /* Spracuj stav Hallovho senzora iba ak je stabilný
         * dlhšie ako DEBOUNCE_DELAY ms, rovnaký postup stabilizácie ako pri IR senzore. */
        if ((aktualnyCas - casZmenyHall) > DEBOUNCE_DELAY) {
            /* Priame riadenie relé podľa stavu senzora.
             * HIGH (dvere otvorené) - zapni svetlo v skrinke.
             * LOW  (dvere zatvorené) - vypni svetlo v skrinke. */
            if (aktualnyStavHall == 1) PIN_HIGH(PORTD, RELE_SKRINKA);
            else                       PIN_LOW (PORTD, RELE_SKRINKA);
        }

    } /* koniec while(1), koniec programu */

    return 0;  // Tento je nedosiahnuteľná – MCU nikdy neskončí main()
}
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include <stdbool.h>

#define IR_PIN        PD2
#define RELE_LAMPA    PD3
#define RELE_DRUHE    PD4
#define HALL_PIN      PD5
#define RELE_SKRINKA  PD6

#define DEBOUNCE_DELAY        50UL
#define LIMIT_DLHE_PODRZANIE 1000UL

#define PIN_HIGH(port, pin)   ((port) |=  (1 << (pin)))
#define PIN_LOW(port, pin)    ((port) &= ~(1 << (pin)))
#define PIN_READ(port, pin)   (((port) >> (pin)) & 1)
#define PIN_TOGGLE(port, pin) ((port) ^=  (1 << (pin)))

volatile uint32_t ms_ticks = 0;

ISR(TIMER0_COMPA_vect) {
    ms_ticks++;
}

static inline uint32_t millis(void) {
    uint32_t t;
    uint8_t sreg = SREG;
    cli();
    t = ms_ticks;
    SREG = sreg;
    return t;
}

static void timer0_init(void) {
    TCCR0A = (1 << WGM01);
    TCCR0B = (1 << CS01) | (1 << CS00);
    OCR0A  = 249;
    TIMSK0 = (1 << OCIE0A);
}

int main(void) {

    DDRD &= ~(1 << IR_PIN);
    DDRD &= ~(1 << HALL_PIN);
    PORTD |= (1 << HALL_PIN);
    DDRD |= (1 << RELE_LAMPA) | (1 << RELE_DRUHE) | (1 << RELE_SKRINKA);

    PIN_LOW(PORTD, RELE_LAMPA);
    PIN_LOW(PORTD, RELE_DRUHE);
    PIN_LOW(PORTD, RELE_SKRINKA);

    timer0_init();
    sei();

    bool     rukaJeTam                  = false;
    bool     akciaDlhePodrzanieVykonana = false;
    uint32_t casZmenyIR                 = 0;
    uint8_t  poslednyStabilnyStavIR     = 1;
    uint32_t startIR                    = 0;

    uint8_t  poslednyStabilnyStavHall   = 1;
    uint32_t casZmenyHall               = 0;

    bool stavLampa     = false;
    bool stavDruheRele = false;

    while (1) {
        uint32_t aktualnyCas = millis();

        uint8_t aktualnyStavIR = PIN_READ(PIND, IR_PIN);

        if (aktualnyStavIR != poslednyStabilnyStavIR) {
            casZmenyIR             = aktualnyCas;
            poslednyStabilnyStavIR = aktualnyStavIR;
        }

        if ((aktualnyCas - casZmenyIR) > DEBOUNCE_DELAY) {

            if (aktualnyStavIR == 0 && !rukaJeTam) {
                rukaJeTam                  = true;
                akciaDlhePodrzanieVykonana = false;
                startIR                    = aktualnyCas;
            }

            if (aktualnyStavIR == 0 && rukaJeTam && !akciaDlhePodrzanieVykonana) {
                if ((aktualnyCas - startIR) >= LIMIT_DLHE_PODRZANIE) {
                    stavDruheRele = !stavDruheRele;
                    if (stavDruheRele) PIN_HIGH(PORTD, RELE_DRUHE);
                    else               PIN_LOW (PORTD, RELE_DRUHE);
                    akciaDlhePodrzanieVykonana = true;
                }
            }

            if (aktualnyStavIR == 1 && rukaJeTam) {
                if (!akciaDlhePodrzanieVykonana) {
                    stavLampa = !stavLampa;
                    if (stavLampa) PIN_HIGH(PORTD, RELE_LAMPA);
                    else           PIN_LOW (PORTD, RELE_LAMPA);
                }
                rukaJeTam = false;
            }
        }

        uint8_t aktualnyStavHall = PIN_READ(PIND, HALL_PIN);

        if (aktualnyStavHall != poslednyStabilnyStavHall) {
            casZmenyHall             = aktualnyCas;
            poslednyStabilnyStavHall = aktualnyStavHall;
        }

        if ((aktualnyCas - casZmenyHall) > DEBOUNCE_DELAY) {
            if (aktualnyStavHall == 1) PIN_HIGH(PORTD, RELE_SKRINKA);
            else                       PIN_LOW (PORTD, RELE_SKRINKA);
        }
    }

    return 0;
}


Zdrojový kód: Inteligentná lampička.zip

Overenie

Na overenie funkčnosti som zapojil všetky periférie a prešiel rukou ponad IR senzor či sa lampička zapne, takisto som prešiel magnetom ponad hallov senzor a sledoval odozvu LED pásika, výsledné fungovanie je odprezentované vo videu nižšie.


Video:

Čo by som urobil inak

Nemám nič čo by som robil inak, všetko sa mi podarilo podľa očakávania, jediné vylepšenie čo mi napadá by mohlo byť prepojenie pomocou konektorov a nie "natvrdo" pripojenými káblami.


Kľúčové slová 'Category', ktoré sú na konci stránky nemeňte.