Operácie

Zámok na kód s IR ovládaním: Rozdiel medzi revíziami

Zo stránky SensorWiki

StudentMIPS (diskusia | príspevky)
Bez shrnutí editace
StudentMIPS (diskusia | príspevky)
Bez shrnutí editace
 
(Jedna medziľahlá úprava od rovnakého používateľa nie je zobrazená.)
Riadok 374: Riadok 374:
== Čo by som urobila inak ==
== Čo by som urobila inak ==
   
   
Aktuálne je kód napevno uložený v programe ako konštanta, ale vhodnejšie by bolo ho uložiť do EEPROM pamäte, aby ho bolo možné meniť bez opätovného nahrávania programu do mikrokontroléra.
Kód je v aktuálnej verzii uložený napevno v programe ako konštanta. Vhodnejšie by bolo uložiť ho do EEPROM pamäte, aby sa dal meniť bez opätovného nahrávania programu do mikrokontroléra.  


Do budúcna by sa dal pridať LCD displej alebo klávesnica pre priamu interakciu.
Do budúcna by sa dal pridať LCD displej alebo klávesnica pre priamu interakciu bez pripojenia k počítaču.


[[Category:AVR]] [[Category:MIPS]]
[[Category:AVR]] [[Category:MIPS]]

Aktuálna revízia z 08:04, 6. jún 2026

Záverečný projekt predmetu MIPS / LS2026 - Mia Dudášová


Zadanie

Cieľom projektu bolo vytvoriť zámok na kód a rozšíriť ho o ovládanie infračerveným (IR) diaľkovým ovládačom. Používateľ zadá číselný kód, ak je kód správny, servomotor sa otočí do polohy "odomknuté", čím sa simuluje mechanické odomknutie zámku. Kód je možné zadať dvomi spôsobmi, cez sériovú linku UART alebo po jednotlivých čísliciach z IR diaľkového ovládača. Stav zámku signalizujú dve LED diódy. Červená svieti pri zamknutom zámku a zelená pri odomknutom.

Vývojová doska Arduino UNO.

Literatúra:


Analýza a opis riešenia

Systém je postavený na vývojovej doske Arduino UNO R3 s mikrokontrolérom ATmega328P (16 MHz). Použité súčiastky sú servomotor SG90, IR prijímač VS1838B, dve LED diódy (červená a zelená) a dva rezistory 220 Ω. Súčiastky sú prepojené na nepájivom poli.

Riešenie je naprogramované v jazyku C v prostredí avr-gcc bez použitia cudzích knižníc, okrem knižnice UART, ktorú sme používali na cvičeniach.

V programe je uložený kód ako štvormiestne číslo. Používateľ zadáva číslice stláčaním tlačidiel na IR diaľkovom ovládači alebo písaním v sériovom termináli pomocou klávesnice. Číslice sa postupne ukladajú, keď počet zadaných číslic dosiahne dĺžku kódu, program ho porovná s uloženým kódom. Ak sa zhoduje, rozsvieti sa zelená LED dióda a servomotor sa otočí do polohy "odomknuté". Po určenej dobe 4 sekundy sa vráti naspäť do polohy "zamknuté" a opäť sa rozsvieti červená LED dióda.

Servomotor SG90 je analógový servomotor. Riadi sa PWM signálom s frekvenciou 50Hz. Poloha závisí od šírky impulzu, kde impulzy 1ms a 2ms zodpovedajú krajným polohám, predstavujúce stavy "zamknuté" a "odomknuté".

Servo motor SG90.

IR prijímač VS1838B prijíma infračervený signál modulovaný na frekvencii 38 kHz. Prijímač signál demoduluje a na svojom výstupe poskytuje digitálny signál, ktorý je privedený na pin INT0.

IR prijímač VS1838B.

K mikrokontroléru sú pripojené tri typy súčiastok: servomotor, IR prijímač a dvojica LED diód. Servomotor je riadený z pinu D9, IR prijímač privádza signál na pin D2 a každá LED dióda má vlastný pin A0, A1. Servomotor aj IR prijímač sú napájané z 5V vývodu dosky a majú spoločnú zem s Arduinom. Obe LED diódy sú zapojené cez sériový rezistor.

Schéma zapojenia.


Algoritmus a program

Diaľkový ovládač vysiela kód modulovaným IR signálom. Prijímač ho demoduluje a na svojom výstupe ho prevedie na digitálnu úroveň. Použitý ovládač pracuje s protokolom NEC, ktorý informáciu kóduje do dĺžky medzier medzi impulzmi. V programe meriame čas medzi hranami signálu pomocou Timer0, kde jeden tik zodpovedá 64 µs. Bity sa skladajú do 32-bitového rámca, ktorý kontrolujeme, ak príkaz a jeho negácia dajú hodnotu 0xFF je platný.

Program sa skladá z hlavnej časti sem_projekt.c a knižnice uart.c s hlavičkou uart.h, ktorú sme používali na cvičeniach na sériovú komunikáciu. Timer1 vyrába PWM signál pre servomotor, Timer0 s externým prerušením INT0 meria čas medzi hranami IR signálu.

  • main() - inicializácia periférií a spracovávanie vstupu z IR ovládača alebo UART komunikácie
  • nastav_servo() - Timer1 s frekvenciou 50Hz ovláda PWM s výstupom OC1A
  • sirka_impulzu_serva() - zapíše šírku impulzu do OCR1A, čím nastaví polohu serva
  • ISR(INT0_vect) - meria čas a skladá 32-bitový NEC rámec
  • ISR(TIMER0_OVF_vect) - pretečenie, zruší rozpracovaný príjem po dlhej pauze
  • led_zamknute() / led_odomknute() - prepína indikačné LED diódy
  • odomkni() - otočí servo do odomknutej polohy, počká 4s a vráti sa späť do zamknutej polohy
  • over_kod() - porovná zadaný kód so správnym kódom, podľa čoho spustí funkciu odomkni() alebo vypíše chybu
  • spracuj_znak() - pridá číslicu, potvrdí alebo zmaže kód
  • znak_pre_kod() - preloží kód tlačidla ovládača na znak podľa definovanej tabuľky tabulka_tlacidiel[]
/*
 *     Timer1 -> PWM signal pre servo
 *     Timer0 -> meranie casu medzi hranami IR signalu
 *     INT0   -> externe prerusenie z IR prijimaca D2
 */

#ifndef F_CPU
#define F_CPU 16000000UL
#endif

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <util/atomic.h>
#include <stdio.h>
#include <string.h>
#include "uart.h"

#define KOD          "1234"
#define DLZKA_KODU     4      

#define ZAMKNUTE_SERVO_US  1000     /* sirka impulzu zamknuteho serva */
#define ODOMKNUTE_SERVO_US 2000     /* sirka impulzu odomknuteho serva */
#define CAS_ODOMKNUTIA_MS  4000

#define SERVO_PIN    PB1      /* D9 */
#define IR_PIN       PD2      /* INT0 */

#define LED_CERVENA  PC0      /* A0 */
#define LED_ZELENA   PC1      /* A1 */

/* sirka impulzu z mikrosekund na tiky Timera1 */
#define US_NA_TIKY(us)  ((uint16_t)((uint32_t)(us) * 2UL))

/*  Timer0 1 tik = 64 us  */
#define IR_TIKY_START   150  /* zaciatok spravy */
#define IR_TIKY_KONIEC  26   /* pretecenie spravy */
#define IR_TIKY_MAX     60

/*  tabulka znakov IR ovladaca  */
typedef struct {
    uint8_t kod;
    char    znak;
} tlacidlo;

static const tlacidlo tabulka_tlacidiel[] = {
    { 0x19, '0' },
    { 0x45, '1' },
    { 0x46, '2' },
    { 0x47, '3' },
    { 0x44, '4' },
    { 0x40, '5' },
    { 0x43, '6' },
    { 0x07, '7' },
    { 0x15, '8' },
    { 0x09, '9' },
    { 0x16, '*' }, /* zmazat kod */
    { 0x0D, '#' }, /* potvrdit kod */
};
#define POCET_TLACIDIEL (sizeof(tabulka_tlacidiel) / sizeof(tabulka_tlacidiel[0]))

volatile uint32_t prijaty_ramec = 0;
volatile uint8_t  ramec_pripraveny = 0;

volatile uint8_t  prijima_ramec = 0;
volatile uint8_t  pocet_bitov_ramca = 0;
volatile uint32_t rozpracovany_ramec = 0;

static char    zadany_kod[DLZKA_KODU + 1];  
static uint8_t dlzka_zadaneho_kodu = 0;

FILE uart_vystup = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE);

ISR(INT0_vect)
{
    uint8_t trvanie = TCNT0;   /* tiky od minulej hrany */
    TCNT0 = 0;

    if (trvanie >= IR_TIKY_START) {   /* nova sprava */
        prijima_ramec      = 1;
        pocet_bitov_ramca  = 0;
        rozpracovany_ramec = 0;
        return;
    }

    if (!prijima_ramec) return;       

    if (trvanie >= IR_TIKY_MAX) {     /* chyba */
        prijima_ramec = 0;
        return;
    }

    rozpracovany_ramec >>= 1;
    if (trvanie >= IR_TIKY_KONIEC) rozpracovany_ramec |= 0x80000000UL;
    pocet_bitov_ramca++;

    if (pocet_bitov_ramca >= 32) {    
        prijima_ramec    = 0;
        prijaty_ramec    = rozpracovany_ramec;
        ramec_pripraveny = 1;
    }
}

/*  timeout  */
ISR(TIMER0_OVF_vect)
{
    prijima_ramec = 0;
}

/*  externe prerusenie INT0 + Timer0  */
static void nastav_ir(void)
{
    DDRD  &= ~(1 << IR_PIN);
    PORTD &= ~(1 << IR_PIN);

    EICRA = (1 << ISC01);
    EIMSK = (1 << INT0);

    TCCR0A = 0x00;
    TCCR0B = (1 << CS02) | (1 << CS00);
    TCNT0  = 0;
    TIMSK0 = (1 << TOIE0);
}

static void nastav_servo(void)
{
    DDRB |= (1 << SERVO_PIN);
    TCCR1A = (1 << COM1A1) | (1 << WGM11);
    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11);
    ICR1 = 40000;
}

static void sirka_impulzu_serva(uint16_t us)
{
    OCR1A = US_NA_TIKY(us);
}

static void led_zamknute(void)
{
    PORTC |=  (1 << LED_CERVENA);
    PORTC &= ~(1 << LED_ZELENA);
}

static void led_odomknute(void)
{
    PORTC |=  (1 << LED_ZELENA);
    PORTC &= ~(1 << LED_CERVENA);
}

static void odomkni(void)
{
    led_odomknute();
    sirka_impulzu_serva(ODOMKNUTE_SERVO_US);
    _delay_ms(CAS_ODOMKNUTIA_MS);

    sirka_impulzu_serva(ZAMKNUTE_SERVO_US);
    _delay_ms(700);
    led_zamknute();
}

static void over_kod(void)
{
    if (strcmp(zadany_kod, KOD) == 0) {
        printf("\nSpravny kod - ODOMYKAM.\n");
        odomkni();
        printf("Zamykam.\n");
    } else {
        printf("\nNespravny kod.\n");
    }

    dlzka_zadaneho_kodu = 0;
    zadany_kod[0]       = '\0';
    printf("Zadajte kod: ");
}

static void spracuj_znak(char znak)
{
    if (znak == '#' || znak == '\r' || znak == '\n') {   /* potvrdenie kodu */
        if (dlzka_zadaneho_kodu > 0) over_kod();
    }
    else if (znak == '*' || znak == 0x08 || znak == 0x7F) {  /* zmazanie kodu */
        dlzka_zadaneho_kodu = 0;
        zadany_kod[0]       = '\0';
        printf("\n[zmazane] Zadajte kod: ");
    }
    else if (znak >= '0' && znak <= '9') {
        if (dlzka_zadaneho_kodu < DLZKA_KODU) {
            zadany_kod[dlzka_zadaneho_kodu++] = znak;
            zadany_kod[dlzka_zadaneho_kodu]   = '\0';
            uart_putc(znak);
        }
        if (dlzka_zadaneho_kodu == DLZKA_KODU) over_kod();
    }
}

static char znak_pre_kod(uint8_t kod)
{
    for (uint8_t i = 0; i < POCET_TLACIDIEL; i++)
        if (tabulka_tlacidiel[i].kod == kod)
            return tabulka_tlacidiel[i].znak;
    return 0;
}

int main(void)
{
    uart_init();
    stdout = &uart_vystup;

    nastav_servo();
    sirka_impulzu_serva(ZAMKNUTE_SERVO_US);

    DDRC |= (1 << LED_CERVENA) | (1 << LED_ZELENA);
    led_zamknute();

    nastav_ir();
    sei();

    printf("Zamok na kod\n");
    printf("Zadajte kod: ");

    for (;;)
    {
        /* vstup z IR */
        if (ramec_pripraveny) {
            uint32_t ramec;
            ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {  /* bez prerusenia */
                ramec            = prijaty_ramec;
                ramec_pripraveny = 0;
            }

            uint8_t prikaz          = (ramec >> 16) & 0xFF;
            uint8_t negovany_prikaz = (ramec >> 24) & 0xFF;

            /* kontrola (prikaz a negacia musia spolu dat 0xFF) */
			if ((uint8_t)(prikaz ^ negovany_prikaz) == 0xFF) {
				char znak = znak_pre_kod(prikaz);
				if (znak) spracuj_znak(znak);
			}
        }

        /* vstup z UART */
        if (UCSR0A & (1 << RXC0)) {
            char znak = UDR0;
            spracuj_znak(znak);
        }
    }

    return 0;
}
#define F_CPU 16000000UL   
#define BAUD 9600        
 
#include <avr/io.h>       
#include <util/setbaud.h> 
 
void uart_init(void)
{
    UBRR0H = UBRRH_VALUE;   
    UBRR0L = UBRRL_VALUE; 
 
#if USE_2X
    UCSR0A |= _BV(U2X0);         
#else
    UCSR0A &= ~(_BV(U2X0));     
#endif
 
    UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);  
    UCSR0B = _BV(RXEN0) | _BV(TXEN0);    
}

void uart_putc(char c) 
{
   if (c == '\n') 
    {
       uart_putc('\r');
    }
   loop_until_bit_is_set(UCSR0A, UDRE0); 
   UDR0 = c;
   return 0;
}
#ifndef UART_H_
#define UART_H_

void uart_init( void );
void uart_putc( char c );

#endif /* UART_H_ */


Zdrojový kód: dudasova_sem_projekt.zip

Overenie

Zapojenie.
Sériový monitor.

Program bol demonštrovaný na videu.

Video:


Čo by som urobila inak

Kód je v aktuálnej verzii uložený napevno v programe ako konštanta. Vhodnejšie by bolo uložiť ho do EEPROM pamäte, aby sa dal meniť bez opätovného nahrávania programu do mikrokontroléra.

Do budúcna by sa dal pridať LCD displej alebo klávesnica pre priamu interakciu bez pripojenia k počítaču.