Ads_700x200

piątek, 19 października 2012

IR TOUCH - wyłącznik bezdotykowy

Witam,

Ponieważ mnie także zainspirował niesamowicie temat konkursu jaki zorganizował na forum, kolega NIEBO!,
to już tak całkowicie poza konkursem i po jego zakończeniu postanowiłem też zrobić sobie we własnym zakresie podobny wyłącznik ;) W zasadzie uznałem, że bardzo przydatne do tego celu okażą się sposoby do obsługi (wprawdzie zwykłych) klawiszy, to jednak - przecież tematem zadania jest także klawisz - tyle że bezdotykowy a dokładniej mówiąc taki na podczerwień. Popełniłem zatem króciutki kod źródłowy, wgrałem do mikrokontrolera i przetestowałem. Ku mojej uciesze okazało się, że prześlicznie zaczęło to działać. Hmmm jedyne czego jeszcze nie sprawdzałem - to fakt czy np przelatująca mucha lub komar będą w stanie włączyć taki klawisz ;) ... ale spokojnie, będę musiał założyć jakąś małą hodowlę owadów, przetresować je do zadań specjalnych i jak śmigłowce wysyłać w okolice wyłącznika ;) .... o tym jeszcze w razie czego poinformuję, tymczasem przejdźmy do rzeczy.



Na początek film prezentujący działanie wyłącznika bezdotykowego "w praniu" ;)



Nie będę długo się rozwodził na temat kodu programu, bo każdy zapewne zauważy, że wykorzystałem tutaj WPROST bez żadnych sentymentów funkcję do obsługi klawisza, którą stworzyłem w 3 części artykułu na temat rzekomych efektów drgań styków ;)


Kod źródłowy wzbogaciłem jednak o generowanie nośnej 36 kHz na potrzeby odbiornika podczerwieni TSOP ;) .... To od razu mówi jakiego sposobu użyłem do realizacji zadania. Prościutko:


  1. TSOP 36 kHz podłączony do pinu PD6 (ICP1) mikrokontrolera ATmega32 (zgodnie ze sztuką jego podłączania)
  2. Dioda nadawcza IR podłączona do pinu PB3 (OC0) mikrokontrolera ATmega32 tyle że za pomocą tranzystora PNP

Mniej więcej tak wygląda podłączenie diody i odbiornika TSOP do procesora:




Najważniejsze to tylko odpowiednie zaizolowanie diody nadawczej IR. Do tego celu użyłem po prostu czarnej rurki termokurczliwej, którą nasunąłem na diodę ;) .... Poniżej prezentuję cały kompletny kod źródłowy, i nie będę go tutaj już opisywał. Każdy może zajrzeć do części III artykułu do której podałem wyżej link. Ale jeśli pojawią się jeszcze jakieś pytania to oczywiście chętnie odpowiem.
--------------------------------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
 * main.c
 *
 *  Created on: 19-10-2012
 *      Author: Mirosław Kardaś
 *      F_CPU = 11,0592 MHz
 *
 *      IR TOUCH - button
 */
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
 
#include "LCD/lcd44780.h"
 
// definicje dla obsługi diody LED
#define IRLED_PORT PORTB
#define IRLED_PIN PINB
#define IRLED_DDR DDRB
#define IRLED_PIN_NR 3
#define IRLED (1<<IRLED_PIN_NR)
 
#define NOSNA_ON TCCR0 |= (1<<COM00)
#define NOSNA_OFF TCCR0 &= ~(1<<COM00)
 
 
#define LED1 (1<<PC5)
#define LED2 (1<<PC4)
 
typedef struct {
 volatile uint8_t *KPIN;
 uint8_t key_mask;
 uint8_t wait_time_s;
 void (*kfun1)(void);
 void (*kfun2)(void);
 uint16_t klock;
 uint8_t flag;
} TBUTTON;
 
// timery programowe
volatile uint16_t Timer1;
 
uint8_t kk_cnt, lk_cnt;
 
 
// funkcja obsługi pojedynczych klawiszy
void key_press( TBUTTON * btn );
 
 
// własne funkcje użytkownika
void change_led1(void) {
 PORTC ^= LED1;
 kk_cnt++;
 lcd_locate(0,0);
 lcd_str("short click: ");
 lcd_int( kk_cnt );
 lcd_str("  ");
 
}
 
void change_led2(void) {
 PORTC ^= LED2;
 lk_cnt++;
 lcd_locate(1,0);
 lcd_str(" long click: ");
 lcd_int( lk_cnt );
 lcd_str("  ");
}
 
 
TBUTTON button; // definicja KLAWISZA
 
//------------------------------------------------------------------
int main(void) {
 
 DDRA |= (1<<PA7);
 PORTA |= (1<<PA7);
 
 DDRD |= (1<<PD7);
 PORTD |= (1<<PD7);
 
 DDRC |= LED1|LED2;   // piny LED jako WYjścia
 
 
 // ****** DIODA LED i IR
 // ustawienie PORTB.3 (OC0A) jako wyjście
 IRLED_DDR  |=  (IRLED);
 IRLED_PORT |= (IRLED);
 
 // **************************************************************************************
 // TIMER0
 // - używany do generowania fali nośnej dla IR
 // załadowanie OCR0A wartością do generowania nośnej ok 36kHz = 110
 OCR0 =  153;
 // ustawienie Timer0 w tryb = 2 - CTC - CompareA
 TCCR0 |= (1<<WGM01); // tryb CTC
 TCCR0 |= (1<<CS00); // preskaler = 1
 //****************************************************************************************
 
 /* Timer2 – konfigurcajca silnika timerów programowych */
 TCCR2  |= (1<<WGM21);   // tryb pracy CTC
 TCCR2  |= (1<<CS22)|(1<<CS21)|(1<<CS20); // preskaler = 1024
 OCR2  = 108;   // przerwanie porównania co 10ms (100Hz)
 TIMSK  = (1<<OCIE2); // Odblokowanie przerwania CompareMatch
 
 lcd_init();
 lcd_str("start...");
 
 
 button.KPIN = &PIND;
 button.key_mask = (1<<PD6);
 button.wait_time_s = 2;  // ustalamy opóźnienie 2 s dla "long click"
 button.kfun1 = change_led1;
 button.kfun2 = change_led2;
 
 sei();
 
 NOSNA_ON;
 
 //-----------  PĘTLA GŁÓWNA
 while(1) {
 
  key_press( &button );
 
 }
}
 
 
 
void key_press( TBUTTON * btn ) {
 
 register uint8_t key_press = (*btn->KPIN & btn->key_mask);
 
 if( !btn->klock && !key_press ) {
  btn->klock=40000;
 
  // reakcja na PRESS krótkie wcinięcie klawisza
  if(btn->kfun1) btn->kfun1();
  btn->flag=1;
  Timer1 = (btn->wait_time_s*1000)/10;
 
 }
 else if( btn->klock && key_press ) {
  (btn->klock)++;
  if( !btn->klock ) {
   Timer1=0;
   btn->flag=0;
  }
 } else if( btn->flag && !Timer1 ) {
  // reakcja na dłuższe wcinięcie klawisza
  if(btn->kfun2) btn->kfun2();
  btn->flag=0;
 }
 
}
 
 
//*** przerwanie Timer2 CompareM
ISR(TIMER2_COMP_vect) {
 
 uint16_t n;
 
 n = Timer1;  /* 100Hz Timer1 */
 if (n) Timer1 = --n;
 
}
--------------------------------------------

Za to na koniec zwrócę uwagę na pewną ciekawostkę. Otóż mam zrzut ekranów z oscyloskopu podczas operacji wciśnięcia i zwolnienia takiego klawisza. Spójrzcie, przypomina to nomen omen, wprost jakby oscylogramy z wciskania typowego klawisza mechanicznego tyle że jakie piękne przebiegi drgań styków ;) szczególnie widoczne są te "ala drgania" przy "wciśnięciu" bezdotykowego klawisza. Ich piękny charakter, tzn równe poziomy logiczne oraz strome zbocza opadające i narastające wywołane są oczywiście zadziałaniem tak na prawdę jakichś szczątkowych odbić nośnej od obiektu, który wchodzi w zakres widoczności czujnika podczerwieni ;) ... A wiemy przecież z poprzednich artykułów - szczególnie z tego


że w świecie prawdziwych mechanicznych klawiszy nie ma takich idealnie równych przebiegów ;) to wygląda wręcz jak po prostu modelowy przykład drgań styków. Na takim modelu zwykle się je omawia. A zatem spójrzmy:


20 komentarzy:

  1. Zastanawiam się, co z problemem reagowania np. na pilota telewizora, czy nie powinno to być w jakiś sposób modulowane?

    OdpowiedzUsuń
    Odpowiedzi
    1. To jest oczywiście tylko jakiś wstępny przykład - a rozwijać można dalej wg uznania. Jeśli chodzi o zakłócanie pilotów podczerwieni to nie powinno być problemu jeśli się właśnie ograniczy zasięg tegoż wyłącznika do takich odległości o jakich piszę i pokazuję na tym filmiku ;)

      Usuń
  2. To jest to samo co SHARP - GP2D120XJ00F
    Idea działania jest taka sama
    Nie wiem po co tyle zachodu

    OdpowiedzUsuń
    Odpowiedzi
    1. Przepraszam ale mało wiesz na temat tego Sharpa o którym wspomniałeś. To co tu zaprezentowałem nie jest nawet namiastką lub odpowiednikiem takiego modułu. Sharp jest po stokroć lepszy jeśli chodzi o stabilność działania od prezentowanej tu metody. Jest przede wszystkim bardzo dobrze skalibrowany i na 100% nie działa na tej samej zasadzie.

      A to co tu pokazałem nie jest żadnym zachodem czy chęcią zastąpienia takich Sharpów, tylko pokazaniem ciekawej metody jak można wykorzystać podczerwień w prosty sposób mając pod ręką procesor oraz odbiornik TFMS z diodą nadawczą podczerwieni. Przy okazji biorąc nawet pod uwagę procesor to i tak wyjdzie o wiele taniej niż zakup tego Sharpa.

      Jeśli nie lubisz majsterkować, programować procesorów, nie jest to twoim hobby - to daj sobie spokój z komentowaniem tylko idź do sklepu i kup sobie sharpa.

      Usuń
  3. Malkontenci zawsze się znajdą niestety... Mnie artykuł się podoba bo można zobaczyć jak w interesujący i w sumie nietuzinkowy sposób, wykorzystać funkcję pierwotnie obsługującą klawisze, co obrazuje też jak pisać kod uniwersalny.
    A przecież wychodząc z takiego prostego projektu już każdy, kto ma odrobinę chęci i samozaparcia, może "wykombinować" coś co będzie mu w 100% odpowiadało.
    Dzięki Mirek za Twoje artykuły i wkład pracy w to co prezentujesz.

    OdpowiedzUsuń
    Odpowiedzi
    1. Aaaaa tu Cię mam panie Mirek ;) ... ja też dziękuję jeszcze raz tobie za cenną uwagę na Youtube do jednej z części kursu o wskaźnikach, strukturach itp ...

      Z takimi ludźmi to można podyskutować.

      Usuń
  4. Mam takie pytanie czy na schemacie z tsopem jest kondensator ceramiczny 4,7 uF?
    Jeśli tak ,to jest taki , i czy można go zamienić na elektrolitycznego?
    Pewnie pytanie jest dosyć głupie ale się dopiero uczę.

    OdpowiedzUsuń
    Odpowiedzi
    1. Dlaczego głupie pytanie? nie rozumiem ? ... kto pyta nie błądzi ;) .... oczywiście że można zamienić go na elektrolityczny 4,7uF czy 2,2uF - jak najbardziej.

      Usuń
  5. Witam. Jaki prąd ma płynąć przez led ir ?

    OdpowiedzUsuń
    Odpowiedzi
    1. zależy jaki chcesz? zależy jaka dioda? zależy jaki ma być zasięg ? ... tu nikt nic nie narzuca - sam musisz go sobie dobrać do projektu

      Usuń
  6. Super sprawa tego właśnie wszędzie szukałem :)

    Jedna rzecz jest jedynie dla mnie nie jasna ,otóż w przerwaniu ISR jest tworzona zmienna

    uint16_t n;

    ,następnie jest zmniejszana --n.

    Dlaczego jest tworzona w przerwaniu? ,czy nie będzie tak ,że za każdym razem jak wywoła się przerwanie to uint16_t n; ,będzie tworzone na nowo i inicjowane 0 ,nie lepiej byłoby założyć zmienną volatile przed main ?

    Widzę ,że projekt jednak działa ,więc to z moim myśleniem coś nie tak ,rozumiem jedynie ,że to zmienna tworzona "w locie" na potrzeby przerwania. Ale dlaczego akurat tak ? ,może myślę zbyt liniowo :) ,mógłby ktoś przybliżyć zagadnienie, pozdrawiam.

    OdpowiedzUsuń
    Odpowiedzi
    1. Bo zmienna ta ma specyfikator static panie kolego ;) ... Jeśli posiadasz Bluebooka?

      http://atnel.pl/mikrokontrolery-avr-jezyk-c.html

      to rzuć proszę okiem do książki .... wyjaśniam w niej w czym rzecz ...

      a jeśli nie masz to tutaj krótkie wyjaśnienie, specyfikator static powoduje że zmienna w funkcji/przrewaniu zachowuje się tak jak globalna. Ale pamiętaj to bardzo uproszczone tłumaczenie i na szybko. Warto to doczytać w książce żeby dobrze zrozumieć.

      Usuń
  7. Mam bluebooka tam jest przykład na 4 timerach, tam z kolei jest uint16_t x;

    ,ale również nie ma przed nią słówka static.

    Czy to oznacza ,że w przerwaniu jest automatycznie tworzona zmienna static?

    Nie powinno być tak?

    ISR(TIMER2_COMP_vect) {

    static uint16_t n;

    n = Timer1; /* 100Hz Timer1 */
    if (n) Timer1 = --n;

    }

    ,może to literówka i się czepiam ,ale bardzo chciałbym zrozumieć.

    Czy jest to związane może z AVR GCC ,który dopuszcza taką pisownię , przypomina mi się też przykład z const char ,gdzie Atmel toolchain nie popuści ,a AVR GCC ,nic nie jąknie. To coś w tym rodzaju?

    OdpowiedzUsuń
    Odpowiedzi
    1. automatycznie ? jak to ? to kto to wpisuje w kodzie ? kompilator ? ;)

      Panie kolego jeśli masz bluebooka to zajrzyj proszę chociażby do rozdziału:

      "3.5.5.3. Zmienne i funkcje statyczne"

      numer rozdziału może być inny jeśli masz starsze wydanie książki (w miękkiej oprawie"

      Usuń
    2. No i też to pytanie twoje o static mówi mi - że na pewno po zakupieniu książki nie przysiadłeś RAZ - żeby ją tak w całości przeczytać, a to bardzo ważne - bo jest napisana w specyficzny sposób, i później już można czytać na wyrywki ... Tyle że nawet jak nie pamiętasz o co chodzi np ze static - to w głowie (zapewniam cię) będziesz miał - że hmmm było gdzieś o tym w książce - tylko wystarczy poszukać ...

      A jak się jej nie przeczytało RAZ w całości - no to później ma się takie luki.

      Usuń
    3. ach widzisz - czyli to moja wina bo z doskoku spojrzałem na pytanie - często tak mam jak wciąż pracuję - a teraz też jestem w pracy ...

      Już wyjaśniam - tutaj w tym konkretnym przypadku nie potrzeba specyfikatora static dla zmiennej o nazwie n

      jest to tylko zmienna tymczasowa i służy do optymalizacji - szybkości wykonywania się kodu. TAK będzie ona za każdym razem tworzona na nowo ale tak ma być ;)

      tzn można byłoby się obejść i bez niej - można byłoby w przerwaniu pisać sobie wprost tak:

      if (Timer1) Timer1 = --Timer1;

      sprawdź to też zadziała ;) wydaje się prostsze do zrozumienia i nie wywołuje w głowie pytania - po co ta zmienna n ? zgadza się ?

      ale teraz porównaj sobie jak obydwa kody ten ze zmienną n i bez niej - jak wyglądają w asemblerze .... Wtedy zobaczysz dlaczego ten ze zmienną n wykonywać się będzie dużo szybciej

      Usuń
  8. U mnie jest to rozdział 4.5.5.3 ,czytałem kilkukrotnie ,teraz sobie też powtórzyłem i rozumiem znaczenie słówka static ,chodzi mi o to ,że tu w kodzie przerwania go nie ma,

    //*** przerwanie Timer2 CompareM
    ISR(TIMER2_COMP_vect) {

    uint16_t n;

    n = Timer1; /* 100Hz Timer1 */
    if (n) Timer1 = --n;

    }

    ,dlatego zapytałem czy nie powinno wyglądać tak:

    //*** przerwanie Timer2 CompareM
    ISR(TIMER2_COMP_vect) {

    static uint16_t n;

    n = Timer1; /* 100Hz Timer1 */
    if (n) Timer1 = --n;

    }

    Nie chodzi mi o to by się kłócić ,bo kod działa jak widać na filmiku na Youtubie.

    Tutaj tego słówka nie ma ,dlatego mam problem ze zrozumieniem jak to może działać ,pozdrawiam.

    OdpowiedzUsuń
  9. Oj asembler to jeszcze za wysokie progi, ale dziękuję za wyjaśnienie. :)

    Faktycznie if (Timer1) Timer1 = --Timer1; ,brzmi przyjaźniej. I ma dla mnie większy sens.
    Jeszcze raz dziękuję za pomoc i owocnej pracy życzę!!! (nad 3 książką może? :) ) ,pozdrawiam i dziękuję.

    OdpowiedzUsuń
    Odpowiedzi
    1. Brzmi przyjaźniej ale proszę pamiętać że trwa sporo dłużej a w przypadku obsługi timerów programowych to wcale nie jest korzystne aby trwało dłużej.

      A niestety jeszcze nie nad 3 książką ... na nią przyjdzie nam jeszcze poczekać "troszkę"

      Usuń
  10. Ok już kapuję ,może nie rozumiem asemblera ,ale rozwiązanie pojawiło się na stronie 92 przy przykładzie predekrementacji.
    Te minusiki --n ,leżą przecież przed zmienną ,więc to jest klucz do zrozumienia.

    Chodzi o to ,że do zmiennej n jest przypisana zawartość Timera ,a potem w linijce z predekrementacją jest zmniejszana o 1.

    Zmienna volatile Timer1 tak naprawdę przechowuje zawartość timera2 więc nie istotne ,że mnienna n jest tworzona i zerowana na nowo (służy przecież tylko do zmniejszania o jeden),
    bo w kolejej linijce kodu znów będzie przyrównana do Timera programowego tylko ,że mniejsza o 1.

    Pisżę ,bo pewnie nie jednej osobie się przyda.



    Już zaznaczam sobię na żółto mazaczkiem ,coraz więcej tego żółtego w książce :) ,na przyszłość będę wiedział gdzie zajrzeć.


    Cieszę się ,że zapytałem ,bo ciężko było na to wpaść ,jeszcze raz dziękuję.

    OdpowiedzUsuń