Witam
Mam na imię Krzysiek. Na Majsterkowie jestem już od jakiegoś czasu. Projekt, którym pragnę się z Wami podzielić będzie służył do sterowania oświetleniem w moim pokoju. Jakiś czas temu wykonałem już sterowanie światłem za pomocą pilota na podstawie projektu Łukasza. Do rozwiązania, które zaprezentował Łukasz dołożyłem możliwość sterowania światłem za pomocą przycisków. Rozwiązanie miało jednak małą wadę – nigdy nie miałem pod ręką pilota! Sytuacja wyglądała mniej więcej tak, że jak byłem na kanapie to pilot leżał przed kompem pilot przy kompie ja na kanapie. Zawsze kończyło się to tak, że trzeba było wstać do przełączników i włączyć/wyłączyć światło. Do już obecnie zaimplementowanych rozwiązań postanowiłem dodać możliwość włączania i wyłączania oświetlenia bezpośrednio z aplikacji okienkowej napisanej w C++. Przedstawiony kod zarówno dla mC jak i aplikacji komputera będę starał się wytłumaczyć w całości aby każdy czytelnik mógł bez problemu wykonać podobny projekt w domu rozumiejąc za co odpowiada każda linijka w kodzie. Uzbrojony w tą wiedzę będzie mógł zaadoptować projekt do swoich potrzeb.
Funkcję w aplikacji na komputer:
- Włączanie i wyłączanie oświetlenia w pokoju jednym kliknięciem
- Zapisywanie i odczytywanie kodów pilota do pliku .txt
- Możliwość ustawienia parametrów komunikacji (nr portu COM oraz prędkość transmisji)
- Możliwość ustawienia nowego kodu pilota dla każdego światła w pokoju bez konieczności ponownego programowania mC.
- Zapisywanie kodów pilota do pamięci EEPROM w mC aby nie trzeba było ich ustawiać na nowo po zaniku zasilania mC (aplikacja będzie wysyłać tylko polecenie a resztą zajmie się sam mC)
- Aplikacja ma być schowana w trayu i nie przeszkadzać kiedy jest nie potrzebna.
Funkcje mC
- Odbieranie kodów pilota IR i wysyłanie ich do komputera.
- Sterowanie czterema światłami w pokoju
- Po resecie/zaniku zasilania samoczynne odczytanie kodów IR zapisanych w EEPROM
- Możliwość tradycyjnego włączania/wyłączania światła
Artykuł zdecydowałem się podzielić na trzy części ponieważ w jednej nikt by nie był w stanie dotrzeć do końca ;-) W pierwszej części zajmiemy się złożeniem wszystkiego na płytce stykowej oraz kodem mC a w drugiej kodem dla aplikacji na komputer a w ostatniej przeniesieniem całości na wytrawioną płytkę i instalacją.
Do projektu potrzebować będziemy:
- Atmel ATMEGA328P-PU
- Na obecnym etapie płytka stykowa
- Programator USB-APS
- Przejściówkę USB-UART
- Trochę drobnicy elektronicznej (rezystory, kondensatory, dławik, diody)
Na początek cały kod programu dla mC:
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 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 |
#include <IRremote.h> //biblioteka kodów IR #include <EEPROM.h> //biblioteka do zapisywania odczytywania kodów IR #include <PinChangeInt.h> // biblioteka do obsługi przerwań (możliwość podpięcia przerwania do dowolnego pinu mC) #define irPin 14 //pin czujki IR IRrecv irrecv(irPin); decode_results results; #define p1 5 //swiatlo 1 #define p2 6 //swiatlo 2 #define p3 7 //swiatlo 3 #define p4 8 //swiatlo 4 #define IR_d 9 //dioda odczytu IR mruga jak przyjdzie nowy kod #define but_1 19 #define but_2 18 #define but_3 17 #define but_4 16 boolean d1; // stan światła 1 boolean d2; // stan światła 2 boolean d3; // stan światła 3 boolean d4; // stan światła int x; // zmienna do sterowania odczytem portu COM int y; // zmienna do sterowania odczytem portu COM int ktory_kod; // zmienna do sterowania odczytem EEPROM unsigned long kod; // zmienna przechowuje ostatni wczytany kod unsigned long Kod1; // zmienna z kodem IR wl/wy światło 1 unsigned long Kod2; // zmienna z kodem IR wl/wy światło 2 unsigned long Kod3; // zmienna z kodem IR wl/wy światło 3 unsigned long Kod4; // zmienna z kodem IR wl/wy światło 4 //______________________________________________________________ void button_1() //kod przycisku 1 { if(digitalRead(but_1)==LOW) { delay(20); d1=!d1; while(digitalRead(but_1)==LOW) delay(20); wyslij(); } } void button_2() //kod przycisku 2 { if(digitalRead(but_2)==LOW) { delay(20); d2=!d2; while(digitalRead(but_2)==LOW) delay(20); wyslij(); } } void button_3() //kod przycisku 3 { if(digitalRead(but_3)==LOW) { delay(20); d3=!d3; while(digitalRead(but_3)==LOW) delay(20); wyslij(); } } void button_4() //kod przycisku 4 { if(digitalRead(but_4)==LOW) { delay(20); d4=!d4; while(digitalRead(but_4)==LOW) delay(20); wyslij(); } } //______________________________________________________________ void wyslij() //wysyła ostatni kod z czujki { Serial.print(kod,HEX); // wysyła ostatni wczytany kod z czujki Serial.print("Q"); //znak końca lini } //______________________________________________________________ unsigned long poteguj(int wykladnik, int pds) //funkcja do podniesienia liczby do //potęgi podczas wywoływania przesyłamy jej podstawę i wykładnik potęgi { unsigned long liczba=1; for (int i=0 ; i<wykladnik; i++) { liczba*=pds; } return (liczba); } //______________________________________________________________ unsigned long jaki_kod() { unsigned long hexx[8]; char kod_odb[8]; int i; while (Serial.available())//pętla która znak po znaku składa odebrany kod w systemie szesnastkowym do tablicy char. { kod_odb[i]=Serial.read(); i++; } for (int h=0 ; h<i ; h++)//pętla zamienia odebrany kod który w tym momencie jest tylko tekstem na tablice zawierająca cyfry. { if (kod_odb[h]=='0')hexx[h]=0; else if (kod_odb[h]=='1')hexx[h]=1; else if (kod_odb[h]=='2')hexx[h]=2; else if (kod_odb[h]=='3')hexx[h]=3; else if (kod_odb[h]=='4')hexx[h]=4; else if (kod_odb[h]=='5')hexx[h]=5; else if (kod_odb[h]=='6')hexx[h]=6; else if (kod_odb[h]=='7')hexx[h]=7; else if (kod_odb[h]=='8')hexx[h]=8; else if (kod_odb[h]=='9')hexx[h]=9; else if (kod_odb[h]=='A')hexx[h]=10; else if (kod_odb[h]=='B')hexx[h]=11; else if (kod_odb[h]=='C')hexx[h]=12; else if (kod_odb[h]=='D')hexx[h]=13; else if (kod_odb[h]=='E')hexx[h]=14; else if (kod_odb[h]=='F')hexx[h]=15; } unsigned long potegi[i]; for (int h=0 ; h<i ; h++) { int podstawa=16; potegi[h]=poteguj(i-h-1, podstawa); } unsigned long suma=0; for (int h=0 ; h<i ; h++) { suma+=hexx[h]*potegi[h]; } return suma; } //______________________________________________________________ void mrugnij() // mruga dioda IR // jeśli chcemy coś zasygnalizować dioda ir to wywołujemy ta funkcje { for (int i=0;i<3;i++) { digitalWrite(IR_d, HIGH); delay(20); digitalWrite(IR_d, LOW); delay(50); } } //______________________________________________________________ int ile_cyfr(unsigned long liczba) // funkcja zwraca ilość cyfr w przesłanej liczbie { int i=0; while(liczba>0) { liczba/=10; i++; } return (i); } //______________________________________________________________ void wirte_eprom(unsigned long eKod) // zapisuje kody do EEPROM { int i=ile_cyfr(eKod); int ile=i; unsigned long tablica[i]; while(eKod>0) { tablica[i-1]=eKod%10; eKod/=10; i--; } switch(ktory_kod) { case 1: EEPROM.write(0,ile); for (int u=1 ; u<ile+1 ; u++) { EEPROM.write(u,tablica[u-1]); } break; case 2: EEPROM.write(20,ile); for (int u=1 ; u<ile+1 ; u++) { EEPROM.write(u+20,tablica[u-1]); } break; case 3: EEPROM.write(40,ile); for (int u=1 ; u<ile+1 ; u++) { EEPROM.write(u+40,tablica[u-1]); } break; case 4: EEPROM.write(60,ile); for (int u=1 ; u<ile+1 ; u++) { EEPROM.write(u+60,tablica[u-1]); } break; } mrugnij(); } //______________________________________________________________ unsigned long Read_eprom()// oczytuje kody z EEPROM { unsigned long tablica[10]; int ile; switch (ktory_kod) { case 1: ile=EEPROM.read(0); if (ile!=255) { for (int i=1 ; i<ile+1 ; i++) { tablica[i-1]=EEPROM.read(i); } } break; case 2: ile=EEPROM.read(20); if (ile!=255) { for (int i=1 ; i<ile+1 ; i++) { tablica[i-1]=EEPROM.read(i+20); } } break; case 3: ile=EEPROM.read(40); if (ile!=255) { for (int i=1 ; i<ile+1 ; i++) { tablica[i-1]=EEPROM.read(i+40); } } break; case 4: ile=EEPROM.read(60); if (ile!=255) { for (int i=1 ; i<ile+1 ; i++) { tablica[i-1]=EEPROM.read(i+60); } } break; } unsigned long potegi[ile]; for (int h=0 ; h<ile ; h++) { int podstawa=10; potegi[h]=poteguj(ile-h-1, podstawa); } unsigned long suma=0; for (int i=0 ; i<ile ; i++) { suma+=tablica[i]*potegi[i]; } return suma; mrugnij(); } //______________________________________________________________ void run_read() { ktory_kod=1; Kod1=Read_eprom(); ktory_kod++; Kod2=Read_eprom(); ktory_kod++; Kod3=Read_eprom(); ktory_kod++; Kod4=Read_eprom(); } //___________________________________________________________ void setup() { Serial.begin(9600, SERIAL_8N1); pinMode(p1, OUTPUT); pinMode(p2, OUTPUT); pinMode(p3, OUTPUT); pinMode(p4, OUTPUT); pinMode(IR_d, OUTPUT); pinMode(but_1, INPUT_PULLUP); PCintPort::attachInterrupt(but_1, button_1, FALLING); pinMode(but_2, INPUT_PULLUP); PCintPort::attachInterrupt(but_2, button_2, FALLING); pinMode(but_3, INPUT_PULLUP); PCintPort::attachInterrupt(but_3, button_3, FALLING); pinMode(but_4, INPUT_PULLUP); PCintPort::attachInterrupt(but_4, button_4, FALLING); irrecv.enableIRIn(); run_read(); Serial.println("Program to sterowania swiatlem w pokoju"); } //______________________________________________________________ void loop() { if(Serial.available()) // jeżeli coś dotarło do arduino przez COM { x=Serial.read(); switch (x) { case 49: // komputer przysłał 1 zmiana statusu światła (włącza lub wyłącza) d1=!d1; break; case 50: // komputer przysłał 2 zmiana statusu światła (włącza lub wyłącza) d2=!d2; break; case 51: // komputer przysłał 3 d3=!d3; break; case 52: // komputer przysłał 4 zmiana statusu światła (włącza lub wyłącza) d4=!d4; break; case 53: //komputer przysłał 5 zmiana statusu światła (wyłącza wszystkie) d1=LOW; d2=LOW; d3=LOW; d4=LOW; break; case 54: //komputer przysłał 6 //diagnostyka eeprom// // for (int i=0 ; i<101 ; i++) // { // Serial.print(i); // Serial.print(" : "); // Serial.print(EEPROM.read(i)); // Serial.println(); // } Serial.print(kod, HEX); Serial.print('Q'); break; case 55: // komputer przysłał 7 //diagnostyka kody zapisane// Serial.println(Kod1); Serial.println(Kod2); Serial.println(Kod3); Serial.println(Kod4); break; case 57: // komputer przysłał 9 - wywoływanie funkcji zapisu do eeprom dla każdego z 4 kodów zmienna który kod przełącza instrukcje switch ktory_kod=1; wirte_eprom(Kod1); ktory_kod++; wirte_eprom(Kod2); ktory_kod++; wirte_eprom(Kod3); ktory_kod++; wirte_eprom(Kod4); break; case 48: // komputer przysłał 0 y=Serial.read(); // w zależności czy po 0 mamy 1,2,3 lub 4 przysłany kod zapisujemy do zmiennej Kod1, Kod2, Kod3 lub Kod4 switch(y) { case 49: // komputer przysłał 1 Kod1=jaki_kod(); mrugnij(); break; case 50: // komputer przysłał 2 Kod2=jaki_kod(); mrugnij(); break; case 51: // komputer przysłał 3 Kod3=jaki_kod(); mrugnij(); break; case 52: // komputer przysłał 4 Kod4=jaki_kod(); mrugnij(); break; } break; } } //______________________________________________________________ if (irrecv.decode(&results))//jezeli czujka odbierze kod { kod=(results.value);//zapisz kod do zmiennej kod if (kod==Kod1)//jeżeli odebrany kod odpowiada któremuś z zapisanych kodów w zmiennych Kod1, Kod2 zmień status światła { d1=!d1; delay(250); } if (kod==Kod2) { d2=!d2; delay(250); } if (kod==Kod3) { d3=!d3; delay(250); } if (kod==Kod4) { d4=!d4; delay(250); } mrugnij(); irrecv.resume(); } //______________________________________________________________ digitalWrite(p1,d1); //ustawia aktualny stan pinu cyfrowego digitalWrite(p2,d2); //ustawia aktualny stan pinu cyfrowego digitalWrite(p3,d3); //ustawia aktualny stan pinu cyfrowego digitalWrite(p4,d4); //ustawia aktualny stan pinu cyfrowego delay(250); } |
Zacznijmy od poprawnego podłączenia Atmegi do zasilania. Podłączenie Atmegi zgodnie z notą katalogową daje nam gwarancje, że mC będzie pracował prawidłowo bez problemów z samoczynnym restartowaniem się lub nawet z zawieszaniem się. W internecie można znaleźć wiele poradników dotyczących prawidłowego zasilania mC i na samym Majsterkowie wiele o tym było. Ja podłączyłem swoją wg. poniższego schematu:
Szczególnie ważny jest rezystor podciągający pod pin reset stan wysoki (niski resetuje mC) oraz kondensatory filtrujące zasilania mC.
Skoro układ ma gadać z komputerem do pinu TX mC podłączamy pin RXD przejściówki a do pinu RX pin TXD. Musimy także podłączyć masy układów. Przejściówka której ja używam ma także możliwość zasilania układu. Ja jednak zdecydowałem się na zasilanie układu z ładowarki do telefonu więc pin VCC zostawiłem nie podłączony. Aby sprawdzić czy wszystko się udało wgrywamy do atmegi następujący kod:
1 2 3 4 5 6 7 8 9 10 |
void setup() { Serial.begin(9600); } void loop() { Serial.println("Hello!"); delay(500); } |
Po włączeniu Serial Monitora powinniśmy zauważyć napis Hello! nadawany przez nasz mC. Jeżeli wszystko jest ok przechodzimy dalej.
Za pomocą pinów cyfrowych będziemy sterować naszym oświetleniem – stan wysoki na pinie cyfrowym będzie zapalał światło na a niski je gasił. Potrzebujemy także przycisków do ręcznego sterowania oświetleniem. Do pinów 28, 27, 26, 25 podłączamy więc microswitche. Zdefiniowanie ich jako INPUT_PULLUP ułatwiam nam sprawę. Dzięki temu przycisk wystarczy podłączyć do pinu mC a z drugiej strony do masy. Musimy jednak pamiętać, że po takiej modyfikacji kodu odczyt pinu da nam stan wysoki w momencie kiedy przycisk jest zwolniony a niski kiedy jest wciśnięty. Jako, że w miarę dokładania linijek kodów do naszego mC będzie on coraz bardziej zapracowany i na zmianę światła musielibyśmy chwile zaczekać a my chcemy mieć to zrobione natychmiast dlatego skorzystamy z przerwań. Przerwanie zmusza mC do rzucenia wszystkiego i wykonania fragmentu kodu. Atmega umożliwia podłączenia przerwania pod każdy pin mC opisany jako PCINTxx. Szybkie zerknięcie na grafikę z rozpisanymi pinami i już wiemy, że przerwania można podpiąć do 23 różnych pinów! Niestety Arduino IDE brakuje możliwości wskazania które to mają być piny. Dlatego posłużymy się biblioteką o nazwie PinChangeInt. Wystarczy teraz, że do każdej definicji pinu podłączonego do przycisku dodamy: PCintPort::attachInterrupt(nazwa_pinu, nazwa_funkcji, zmiana_statu); Przeczytać to możemy jako “Po zmianie stanu na nazwa_pinu z wysokiego na niski rzuć wszystko i odpal funkcję nazwa_funkcji”. Czy chodzi nam o zmianę stanu z wysokiego na niski czy odwrotnie, możemy doprecyzować komendą FALLING lub RISING. Funkcje odpowiadające za sterowanie ręcznie sterowanie światłem to void button_1/2/3/4() – po jednej dla każdego przycisku. No to mamy z głowy ręczną obsługę światła. Żeby nasz projekt różnił się co nieco od zwykłego włącznika światła dodajemy obsługę odbiornika podczerwieni. Jako odbiornika podczerwieni użyłem popularnej czujki IR o nazwie TSOP2236. Podłączamy ją wg poniższego obrazka:
Niestety w programie do schematów nie mogłem odszukać odpowiedniego elementu wiec użyłem jako “zamiennika” tranzystora. Pin kolektora (nr 1) to masa. Do bazy (nr 2) przez rezystor 100 ohm zasilanie. Ostatni pin podłączamy do atmegi(23). Aby filtrować zakłócenia pomiędzy zasilanie a masę podłączamy kondensator ceramiczny 4,7uF. Kolejna niezbędna biblioteka to IRremote. Tworzymy 8 nowych zmiennych cztery typu unsigned long o nazie Kod1/2/3/4 oraz boolean d1/2/3/4. W zmiennych tych będziemy przechowywać zapisane kody IR pilota odpowiadające zmianom statu światła oraz sam ich stan. Kiedy kod odczytany przez czujkę pokryje się z kodem w zmiennej Kod1/2/3/4 zmieni zawartość zmiennej d1/2/3/4 na przeciwny. W ten oto sposób jednym przyciskiem pilota będziemy gasić lub zapalać jedno światło. Zmienna Kod1/2/3/4 zaraz po zdefiniowaniu nie ma jeszcze żadnej konkretnej wartości i musimy dodać funkcję która umieści nam w naszym pojemniku pożarną wartość. Oczywiście można by założyć, że raz wybrany pilot/guzik pilota nigdy nam się nie znudzi i na sztywno zdefiniować jaki to ma być kod. Ja jednak zamierzam umieścić docelową wytrawioną płytkę w ścianie (dokładnie w puszcze) i więcej tam nie zaglądać :-). Mamy możliwość rozmowy z naszym mC przez Serial Port i właśnie z niej skorzystamy. Na sam początek trzeba się zastanowić co dokładnie kryje się pod stwierdzeniem “kod przycisku pilota”. Otóż okazuje się po prostu, że jest to liczba całkowita zapisana w systemie szesnastkowym. Liczba ta może być duża (np 1711591382) lub też stosunkowo niewielka (np 3622) – wszystko zależy od pilota. My chcemy aby nasz układ działał z każdym pilotem niezależnie czy pod kodem przycisku kryje się cyfra 4 czy 10 cyfrowa. Poniżej przykłady z moich pilotów (sprawdziłem 7 różnych):
6604CFD6 – 1711591382, 6604CFE6 – 1711591398, 6126 – 24870, E26 – 3622.
Mamy zapisane te same liczby tyle, że w różnych systemach. Po lewej stronie myśnika system szesnastkowy a po prawej dziesiętne tak jak nas nauczyli w szkole. Założenie jest następujące: kod odebrany przez Serial Port powinien być zamieniony z jednego systemu na drugi i zapisany do odpowiedniej zmiennej. Po chwili zastanowienia zdecydowałem się na przesyłanie kodów uprzednio poprzedzając je dwoma cyframi. Pierwsza z nich to 0 aby dać znać mC, że raz otrzyma informację z kodem kolejna cyfra to 1,2,3 lub 4 aby mógł wiedzieć do której zmiennej ma zapisać otrzymany kod. Reasumując cała wiadomość będzie się wyglądała tak np. tak “01E26” albo tak 036604CFF6. Dane zostały wysłane a my musimy coś z nimi zrobić. Zakładamy więc, że chcemy zaprogramować kod do sterowania światłem nr 1 więc kod czujki pilota powinien wylądować w zmiennej o nazwie Kod1. Kod który chcemy zaprogramować to szesnastkowo zapisany 6604CFF6. Jak wspominałem wcześniej nasz kod pilot jest poprzedzony znakami sterującymi znak pierwszy czyli 0 ląduje w zmiennej x. Pierwsza instrukcja warunkowa switch(x) odsyła nas do przypadku case 48. Dlaczego 48 skoro pierwszą nadesłaną cyfrą jest 0? Już wyjaśniam. Funkcja Serial.read() odczytuje znak z bufora i traktuje go dokładnie jako znak. Czyli, jeżeli odebranym znakiem będzie np ‘a’ w efekcie działania funkcji Serial.read() dostaniemy odpowiednik tej litery w tablicy ASCII czyli 65. Szybko sprawdzany, że kod cyfry 0 w tablicy ASCII to 48. Kolejnym znakiem który już czeka w buforze jest cyfra 1 (kod ASCII to 49) i pakujemy ją do zmiennej pomocniczej y która przełącza nam kolejną instrukcję warunkową switch(y). My mamy jedynkę więc zostajemy przełączeni do przypadku case 49. Zauważmy, że w przepadkach case: 49,50,51,52 użyjemy tego samego kodu do zamiany pojedynczych znaków napływających do bufora na konkretna liczbę. Aby uniknąć redundancji (powtarzania tego samego kodu) zdecydowałem się na wycięcie tego konkretnego fragmentu do podprogramu (funkcji) która zwróci nam gotowy wynik wystarczy więc napisać Kod1=jaki_kod(). mC po natrafieniu na funkcję jaki_kod() na chwilę zatrzyma główny program wykona polecenia z funkcji jaki_kod() i zwróci nam gotowy wynik. Do zamiany znaków z bufora potrzebne nam będą dodatkowe zmienne. Zaletą funkcji którą wywołujemy jest to, że zmienne te na potrzeby funkcji jaki_kod() zostaną zarezerwowane w pamięci a po wykonaniu niezbędnych obliczeń zostaną usunięte a pamięć zwolniona. Rezerwujemy za tym tablicę ośmiu elementów typu unsigned long o nazwie hexx[8], tablicę ośmiu elementów typu char o nazie kod_odb[8] oraz zmienną pomocniczą int i. Funkcja Serial.available() zwróci nam prawdę jeżeli w buforze znajdziemy jakiś znak do odbioru to doskonały warunek dla funkcji while. Jako, że nie wiemy ile znaków trafi do bufora wypadało by je policzyć do tego wykorzystamy zmienną int i. Będziemy zwiększać ją o jeden za każdym obiegiem pętli while. Zwiększanie zmiennej i o jeden wykorzystamy także to zmiany aktualnie zapisywanej szufladki w tablicy kod_odb. Przypominam, że szufladki w tablicach numerujemy od zera. Dla naszego przykładu po wykonaniu się pętli while mamy tablicę z znakami char wyglądającą tak: [6][6][0][4][C][F][F][6]. Z oczywistych względów nie jest to liczba więc obrabiamy dalej. Na kolejnym etapie musimy się zastanowić co faktycznie oznaczają te znaki zerknijmy więc do ściągawki:
0 | 0 |
1 | 1 |
2 | 2 |
3 | 3 |
4 | 4 |
5 | 5 |
6 | 6 |
7 | 7 |
8 | 8 |
9 | 9 |
A | 10 |
B | 11 |
C | 12 |
D | 13 |
E | 14 |
F | 15 |
Do dalszych obliczeń znak char trzeba zamienić na odpowiadającą jej cyfrę piszemy więc pętlę for. Z zmiennej int i wiem, że pętla powinna zostać powtórzona 8 razy. Zapisujemy więc for (int h=0 ; h<i ; h++ ) w pętli for porównujemy znak char do znaku char i jeżeli się zgadza do tablicy hexx zapisujemy jej cyfrowy odpowiednik. Po zakończeniu pętli mamy tablicę hexx wyglądającą tak: [6][6][0][4][12][15][15][6]. Nie jest to jeszcze nasz wynik ale w komórkach tablicy mam już zmienne całkowite które możemy dodawać odejmować mnożyć itp. Aby zamienić tak przygotowaną tablicę na liczbę w formacie dziesiętnym musimy wziąć ostatnią cyfrę i pomnożyć ją przez kolejną potęgę liczby 16. Otrzymane w ten sposób liczby należy do siebie dodać. Dla naszego przykładu wygląda to tak:
Numery cyfr od końca | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
Cyfra z tablicy | 6 | 6 | 0 | 4 | 12 | 15 | 15 | 6 |
Do której potęgi | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Po podniesieniu | 268435456 | 16777216 | 1048576 | 65536 | 4096 | 256 | 16 | 1 |
Po pomnożeniu | 1610612736 | 100663296 | 0 | 262144 | 49152 | 3840 | 240 | 6 |
Gotowa liczba | 1711591414 |
Do przeliczenia w ten sposób liczby w systemie szesnastkowym na liczbę dziesiętną będziemy potrzebować potęg liczby 16. Rezerwujemy sobie tablicę unsigned long potegi. Skoro mamy już gdzie przechowywać nowe dane czas naszą tablicę wypełnić. Do tego zadania zatrudnimy funkcję o nazwie poteguj(). Po wysłaniu do niej w argumencie funkcji informacji do której potęgi chcemy podnieść liczbę (wykładnik) oraz o jaką liczbę nam chodzi (podstawa) funkcja zwróci nam gotową wartość. Dla naszego przykładu (8 cyfrowa liczba hex) pierwszy obieg pętli for przekaże do funkcji potęgi wykładnik i-h-1=7 (8-0-1) oraz podstawę 16, dla drugiego obiegu 6 (8-1-1) i tak dalej. Otrzymywane liczby zapisywane są w szufladkach od zera licząc. Sama funkcja potęgi jest banalnie prosta – mnoży liczbę 16 przez siebie samą tyle razy ile zostanie o to poproszona. Zapis liczba=pds znaczy to samo co liczba=liczbapodstawa. Mamy już gotową rozpisaną liczbę w tablicy hexx[] oraz potęgi liczby 16 w tablicy potegi[] wystarczy przemnożyć odpowiadające komórki tablic potegi[] i hexx[] przez siebie oraz wszystkie wyrazy ciągu zsumować. Funkcja return suma zwróci nam gotową liczbę do miejsca gdzie wywołaliśmy funkcję jaki_kod() – czyli prosto do zmiennej Kod1. Było to troszkę pisania ale upewniliśmy się, że mC właściwie zareaguje na kod pilota składający się z dowolnej ilości znaków nie większej niż 8. Maksymalną liczbą jaką dzięki tym funkcjom możemy uzyskać jest 4294967295 (hex FFFFFFFF). Dlaczego nie więcej? Głownie z dwóch powodów. Ponieważ nie trafił w moje łapki pilot który wysyłał by w kodzie więcej niż 8 znaków oraz maksymalna pojemność typu unsigned long to właśnie 4294967295. Na pewno wielu z was zapyta po co cały ten system szesnastkowy skoro można by przysłać gotową liczbę (dla naszego przykładu 1711591414). Odpowiedź jest prosta. Za młodu bawiłem się programem Girder i tam kody były zapisywane właśnie jako liczby w systemie szesnastkowym oraz każdy przykład wykorzystania biblioteki IR dla aruino wysyłający kody IR do serial monitora robi to w systemie szesnastkowym poza tym liczba 1711591414 wygląda bardziej jak stan mojego debetu niż kod pilota. Przyznacie przecież, że liczba 6604CFF6 jest dużo bardziej milsza dla oka niż jej dziesiętny odpowiednik 1711591414.
Umiemy obsługiwać już dane przychodzące do mC. Czas wysłać sygnał sterujący z komputera który będzie nam zapalał i gasił światło. Aby zbędnie nie kombinować jeżeli w buforze pojawi się cyfra 1 zmienia się status światła nr 1 jeżeli 2 status światła 2 itd. W jaki sposób instrukcja swtich jest przełączana wyjaśniałem już przy okazji odczytywania przysyłanych kodów. Wyjątkiem jest 5, która wyłączy wszystkie światła nie zależnie ile ich było włączonych (takie natychmiastowe zaciemnienie na wypadek nalotu naszych przyjaciół za wschodniej granicy :-))
Zmienne Kod1/2/3/4 mamy już zapisane ale w przypadku braku prądu lub restartu mC cały proces trzeba będzie powtórzyć od nowa – nie, nie tak nie może być! Musimy gdzieś przechowywać zmienne najlepiej w takim miejscu gdzie ocaleją na wypadek blackoutu. Miejscem takim jest oczywiście EEPROM mC. Pakujemy więc tam liczby z zmiennych Kod1/2/3/4 i po sprawie. Niby proste ale nie do końca. Szybko okazuje się, że pojedyncza komórka pamięci EEPROM możne pomieścić maksymalnie liczbę 254. Licząc od zera to 255 możliwych cyfr no a my mamy przedział od 0 do 4294967295. No troszkę nam brakuje. Najprostszym z możliwych rozwiązań jest rozbicie liczby 4294967295 na pojedyncze cyfry. Podczas czytania o pamięci EEPROM mC Atmega dowiedziałem się, że jest to pamięć która możemy zapisywać tylko określoną liczbę razy. Wydaje mi się, że rozsądnie jest zapisywać tam dane tylko kiedy tego na pewno potrzebujemy a nie przy każdej zmianie kodu pilota. Zasada jest taka mamy 4 kody w zmiennych i już wiemy, że chcemy akurat z nich korzystać więc wciskamy przycisk i już nam nie uciekną. Wysyłając do mC cyfrę 9 nastąpi zapis wszystkich kodów pilota tylko na nasze indywidualne życzenie. Jako, że artykuł znacznie się wydłużył niż się na początku spodziewałem ponownie posłużę się przykładem zmiennej Kod1. Ponownie wywołamy napisaną samodzielnie funkcję.
Przy wywołaniu (eprom_write(Kod1)) funkcji musimy przekazać jej w argumencie kod który chcemy zapisać. W naszym przypadku jest to zmienna Kod1. Do pojedynczej szufladki EEPROM będziemy zapisywać jedną cyfrę więc na pewno musimy wiedzieć ile cyfr ma nasza zmienna. Do tego skorzystamy z funkcji do której trzeba będzie wysłać nasz kod. Funkcja działa na zasadzie dzielenia naszej liczby przez dziesięć do momentu kiedy osiągnie ona zero. W zmiennej int i zliczamy liczbę obiegów pętli. Gotową wartość przekazujemy do do funkcji write_eprom() i zapisujemy do zmiennej i. Jako, że podczas dalszej pracy zmienna i ostatecznie nam się zeruje a liczba cyfr przyda się jeszcze tworzymy nową zmienną int ile i tam zapisujemy wartość zmiennej i . Skoro dokładnie już wiemy ile cyfr mamy w naszej liczbie możemy napisać pętle while(eKod>0). Która do odpowiedniej komórki tablicy o wdzięcznej nazwie tablica zapisze nam resztę z dzielenia. Tak więc przykładowo kiedy zmienna eKod ma wartość 7894 wyrażenie eKod%10 da nam wartość 4 a i-1=3, podzielenie liczby przez 10 utnie nam ostatnią cyfrę (typ całkowity zmiennej nie przechowuje wartości po przecinku). Po wykonaniu tego obiegu pętli while wartość i zmniejsza się o 1. Cały obieg pętli daje nam tablicę: tablica[7][8][9][4]. Jako, że funkcja modulo 10 zwraca nam ostatnią cyfrę (skrajną prawą) zmiennej i użyliśmy do odwrócenia kolejności zapisywania danych do tablicy. Cel osiągnięty. Zaczynając od komórki [1] pamięci eeprom zwiększając adres komórki o jeden zapisujemy każdą cyfrę na swoje miejsce. Przy odczytywaniu z pamięci eeprom przyda nam się wiedza ile dla danego kodu musimy odczytać komórek. Dlatego też zostawiliśmy sobie wolne miejsce przez kodem eeprom[0]. Instrukcja switch za pomocą zmiennej globalnej który kod przełącza nam “miesca w eeprom” do któych zapisujemy dany kod. Jako, że zmienna ktory_kod jest zmienną globalną dostępną zarówno dla funkcji loop() jak i wirte_eprom() to przed wywołaniem funkcji ustwiamy odpowiednią wartość zmiennej ktory_kod. Poniżej tabela w jaki sposób planuje zapisywać kody do eeprom:
1 | 2 | 3 | 4 | ||||
0 | ile | 20 | ile | 40 | ile | 60 | ile |
1 | x | 21 | x | 41 | x | 61 | x |
2 | x | 22 | x | 42 | x | 62 | x |
3 | x | 23 | x | 43 | x | 63 | x |
4 | x | 24 | x | 44 | x | 64 | x |
5 | x | 25 | x | 45 | x | 65 | x |
6 | x | 26 | x | 46 | x | 66 | x |
7 | x | 27 | x | 47 | x | 67 | x |
8 | x | 28 | x | 48 | x | 68 | x |
9 | x | 29 | x | 49 | x | 69 | x |
10 | x | 30 | x | 50 | x | 70 | x |
11 | x | 31 | x | 51 | x | 71 | x |
12 | x | 32 | x | 52 | x | 72 | x |
13 | x | 33 | x | 53 | x | 73 | x |
14 | x | 34 | x | 54 | x | 74 | x |
15 | x | 35 | x | 55 | x | 75 | x |
16 | x | 36 | x | 56 | x | 76 | x |
17 | x | 37 | x | 57 | x | 77 | x |
18 | x | 38 | x | 58 | x | 78 | x |
19 | x | 39 | x | 59 | x | 79 | x |
W tabeli znakiem x zaznaczyłem miejsca gdzie zapisujemy kod w polach ile przechowujemy liczbę znaków danego kodu.
Dla zmiennej Kod1 zaczynamy więc od u=1 dla zmiennej Kod2 zaczynamy od u+20 dla zmiennej Kod3 zaczynamy od u+40 a dla zmiennej Kod4 u+60.
No to wszelkie zaniki zasilania nam nie straszne. Zmienne z kodami pilotów resetują się tylko w przypadku zaniku zasilania lub też resetu mC. Jak wykryć reset mC? Wcale nie musimy ponieważ mamy do dyspozycji blok konfiguracyjny void setup() uruchamiany jednorazowo podczas startu mC. Wspaniale!. Wywołamy w nim funkcję run_read(), która odczyta i zapisze kody z EEPROM do zmiennych Kod1/2/3/4. Jako, że musimy także zależnie od wczytywanego kodu przełączać zmienną ktory_kod z pętli void setup wywołamy sobie funkcję, która już dalej posteruje odczytem EEPROM. Do odczytu będzie nam potrzebna tablica, która na chwile przechowa nam odczytaną wartość z komórki EEPROM deklarujemy nową tablicę unsigned long tablica[10]. Przyda się też zmienna do której zapiszemy odczytaną liczbę znaków int ile. Na początek sprawdzamy ile znaków trzeba odczytać dla pierwszego kodu informację o tym zapisaliśmy do pierwszej (zerowej) komórki pamięci. Przy odczycie pamięci z EEPROM musimy pamiętać, że w niektórych przypadkach może się zdarzyć, że nic tam aktualnie nie będzie – sytuacja ma miejsce np kiedy mC włącza się pierwszy raz po pierwszy po zaprogramowaniu (programator czyści pamięć). Skorzystamy z faktu iż taka dziewicza komórka ma wartość 255. Prosty warunek if (ile!=255) sprawdzi nam czy dana komórka była już wcześniej zapisana (spodziewamy się tam wartości od 0 do 10 a już na pewno nie 255. Sprawdziliśmy więc no i nie trafiliśmy na 255 warunek z ifa spełniony program przechodzi do pętli for. Pierwszą cyfrę mamy w komórce 1 tam też zaczynamy mamy ich odczytać i razy i przy każdym obiegu pętli zwiększamy wartość o jeden. Dla kodu 1711591414 mamy więc zapisaną tablicę o nazwie tablica[1][7][1][1][5][9][1][4][1][4] (przy wymyślaniu nazw zmiennych kreatywność mnie opuszcza tak samo jak przy wymyślaniu haseł). Trzeba to jeszcze poskładać do kupy. Zauważmy iż wystarczy ostatnią komórę tablicy pomnożyć przez jedności przed ostatnia przez dziesiątki następną przez setki i tak dalej aż do miliardów a na koniec zsumować. Zauważymy, że mnożymy przez potęgi liczby 10 no no jedności to 10^0 dziesiątki do 10^1 setki do 10^2 i tak dalej. Mamy gotową funkcję do potęgowania liczb (dlatego właśnie chciałem aby była uniwersalna) skorzystajmy więc z niej. Ponownie tabelka z przykładem:
Numer cyfry od końca | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
Cyfra z tablicy | 1 | 7 | 1 | 1 | 5 | 9 | 1 | 4 | 1 | 4 |
Do któej potęgi | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Po podniesieniu | 1000000000 | 100000000 | 10000000 | 1000000 | 100000 | 10000 | 1000 | 100 | 10 | 1 |
Po pomnożeniu | 1000000000 | 700000000 | 10000000 | 1000000 | 500000 | 90000 | 1000 | 400 | 10 | 4 |
Gotowa liczba | 1711591414 |
Prawda, że proste? Funkcja eprom_read() zwraca nam gotową wartość do miejsca gdzie została wywołana czyli do zmiennej Kod1.
Aby przetestować działanie układu do pinów 11,12,13,14 przez rezystor 330 ohm podłączamy cztery diody. Na chwile obecną będziemy musieli wysyłać i odbierać kody ręcznie (za pomocą Serial Monitora) wbudowanego w Arduino IDE.
Poniżej schemat całego układu:
Jeżeli pomysł się spodoba kolejną część artykułu (program dla PC) przewiduje do końca przyszłego tygodnia. Kolejna część
Kawał dobrego artykułyu :) Leci mocne 5 i czekam na kolejne wpisy :)
Dziękuje za ciepłe słowa. Prace na częścią 2 już trwają. Troszeczkę przeraża mnie ilość informacji jaką tam planuje zawrzeć :-)
Im więcej informacji, tym lepiej :) Tylko jak już dodasz kolejne części, to edytuj tą pierwszą część i dodaj coś w rodzaju małego spisu treści z linkami do kolejnych części (to samo w kolejnych częściach:)
Rewelacja :-) Oby poziom został utrzymany, zapowiada się najlepsza seria na masterkowo
Dziękuje
Qrka wodna, tyle się rozpisałeś. Przyznam szczerze, jeszcze nie programuję mC, dlatego sobie tylko przejrzałem na chybił trafił i przewinąłem na koniec.
Mikro kontroler zamierzasz podłączać kablami do wszystkich lamp, czy wmontujesz w każdej osobny chip? Dlaczego byłoby to lepsze, niż zwykły “klaskacz”?
Tak bardzo lubisz swoją kanapę, że światła nie możesz zapalić ręcznie? A jak będzie trzeba iść po browara, albo padnie laptop, to co? :)
Sama płytka będzie zamocowana w puszcze gdzie znajdował się stary włącznik. Modułem wykonawczym będzie duet zaproponowany przez Łukasza optotriak MOC3021 oraz triak BT138. Druga lampka znajduje się w rogu pokoju to jakieś 2 metry od włącznika wiec w bezprzewodowość nie ma co się bawić. Tam będzie druga para. Czyli dwa na suficie i dwa w wolno stojącej lampce. Klaskacz może faktycznie był by lepszy dla jednej lamy. Ja tych punktów mam 4 i każdy praktycznie innej mocy. Poza tym z budowy klaskacza nie miał bym tyle frajdy co z tego.
Wpis zajął trochę miejsca bo starałem się rozpisać i omówić ważne funkcję. Myślę, że komuś mogą się przydać czy to do obsługi EEPROM albo konwersji systemów liczbowych. Przeczytałem ostatnio bardzo dużo artykułów nie tylko na Majsterkowie w stylu “tu macie kod a tutaj schemat” powinno działać. Tylko, że później w komentarzach pojawia się masa pytań jak coś zmodyfikować do własnych potrzeb :-). A kanapę mam do wymiany strasznie nie wygodna ;]
Słaby ten spam. “Znaleziono 0 ofert spełniających podane kryteria.” plus Notice: unserialize(): Error at offset 64179 of 65535 bytes in /home/energiadirect/public_html/web/application/controller/Porownanie.class.php on line 41
Pingback: walter
Pingback: Steve