Witam Was w ostatniej części swojego cyklu. Wykorzystamy w niej przejściówkę UART<->USB i napiszemy program pozwalający nam na przestawianie zegara z poziomu komputera, a także zmianę jego ustawień wewnętrznych. Zaczynamy!
Po pierwsze potrzebujemy (oczywiście) wspomnianej już przejściówki UART<->USB. Przejściówkę tego typu możemy nabyć np. za pośrednictwem eBay’a z Chin (około 5 złotych z przesyłką do kraju, czas oczekiwania ok. 4 tygodnie), lub (identyczną) na Allegro (ok. 12-13 zł, jednak jest u nas po 3-4 dniach).
Większość przejściówek, oprócz komunikacji z układem zapewnia także możliwość zasilania układu z portu USB – ja projektując płytkę zdecydowałem, że nie chcę korzystać z tej opcji. Aby wykorzystywać przejściówkę tylko w celu komunikacji musimy połączyć trzy piny – pin TX układu z pinem RX mikrokontrolera, odwrotnie – pin RX układu do pinu TX mikrokontrolera oraz musimy połączyć masy układów.
Po podpięciu przejściówki do układu podpinamy ją do portu USB (ja w tym celu użyłem przedłużacza USB, ale można też zrobić odwrotnie – przejściówkę wpiąć do USB i „na kablach” podłączyć ją do układu).
Po podłączeniu wypadałoby sprawdzić działanie przejściówki i jej podpięcia. W tym celu wgrywamy prosty program, który inicjalizuje port szeregowy i nadaje przez niego komunikat służący nam za potwierdzenie działania konwertera.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <Wire.h> void setup() { Serial.begin(9600); Serial.println("Hello world!"); } void loop() { Serial.println("Hello!"); delay(1000); } |
Po wgraniu kodu wybieramy w programie Arduino port (Narzędzia->Port szeregowy), pod którym znajduje się przejściówka (numer portu możemy sprawdzić w menadżerze urządzeń). Po wybraniu portu uruchamiamy Monitor portu szeregowego (również w menu Narzędzia) i (o ile prędkości transmisji w poleceniu Serial.begin oraz w oknie monitora portu szeregowego będą takie same) to powinniśmy zobaczyć komunikaty nadawane przez nasz mikrokontroler. Po odtańczeniu tańca zwycięstwa bierzemy się do dalszej roboty.
Oczywiście moglibyśmy zadowolić się konfiguracją za pomocą odpowiednio wpisywanych komend w oknie monitora portu szeregowego, ale zdecydowanie bardziej eleganckim rozwiązaniem będzie napisanie aplikacji okienkowej, która ubierze wszystkie komendy w przyjazny dla oka interfejs.
Postanowiłem skorzystać z programu Microsoft Visual Studio 2013 (dostępna jest w Internecie bezpłatna, 30 dniowa wersja próbna o pełnej funkcjonalności). Od razu uprzedzam – niniejszy opis nie jest nauką programowania w C i opisując kod i poszczególne czynności zakładam, że osoby czytające mają chociaż jako-takie pojęcie o programowaniu w C i miały już kiedyś styczność z tworzeniem aplikacji okienkowych.
Po pobraniu i zainstalowaniu programu uruchamiamy go i tworzymy nowy projekt (ja wybrałem projekt C#, ale w przypadku naszego w miarę prostego programu nie zrobi większej różnicy wybranie C++ czy innego języka z rodziny C). Następnym krokiem jest rozmieszczenie na ekranie naszej aplikacji wszystkich kontrolek, które chcemy na nim zawrzeć.
Wygląd naszego programu to oczywiście kwestia indywidualna. Ja, po uwzględnieniu wszystkich funkcjonalności, które chciałem zawrzeć w programie i zebraniu tego do kupy otrzymałem program o takim wyglądzie:
Ważne są komponenty SerialPort, Timer (a właściwie dwa) oraz statusStrip znajdujące się POD samym oknem aplikacji. Oprócz nich program składa się z EditBox’ów (pola służące do wprowadzania danych), Label’i (czyli tekstu), CheckBox’ów (pola z ptaszkami do zaznaczania), ComboBox’ów (wprowadzanie danych, lub ich wybór ze zdefiniowanej listy), Buttonów (przycisków), a całość jest ogarnięta „tematycznie” przy pomocy GroupBox’ów.
Podstawą naszego programu będzie oczywiście odbieranie i wysyłanie danych z portu COM, dlatego pierwszym komponentem, który oprogramujemy będzie komponent SerialPort (który nazwałem po prostu „port”). Jednak zanim zabierzemy się za programowanie musimy zaplanować, w jaki sposób komputer i układ będą się z sobą porozumiewać. Po przemyśleniu i przyjrzeniu się podobnym programom w Internecie postanowiłem zrobić to następująco:
Komputer wysyła flagę, informującą układ o tym, czego komputer od niego oczekuje (w przypadku chęci odczytu danych), lub jakie dane zaraz mu wyśle (w przypadku wysyłania danych). Jeżeli mamy do czynienia z odczytem danych z układu układ odpowiada tą samą flagą, którą otrzymał z komputera, a po fladze nadaje żądane dane, jeżeli natomiast dane są wysyłane do układu – układ wie ilu danych jakiego typu się spodziewać i co z nimi zrobić.
Przykładowo:
Komputer wysyła flagę „1”.
Układ po otrzymaniu danych odczytuje flagę i sprawdza co ona oznacza (w tym przypadku jest to pobranie aktualnych ustawień zegara).
Układ wie już, że komputer oczekuje na dane od niego, więc tym razem to on wysyła flagę „1”.
Po nadaniu flagi układ nadaje żądane od niego dane.
Mając zaplanowaną obsługę danych możemy napisać funkcję, która będzie wywoływana w momencie odebrania danych przez komputer, a także funkcje dalej obsługujące odebrane dane.
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 |
private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) { data = Convert.ToInt16(port.ReadLine()); this.Invoke(new EventHandler(update)); switch (data) { case 1: { try { while (port.BytesToRead == 0) { } daylight_value = Convert.ToInt16(port.ReadLine()); data = daylight_value; this.Invoke(new EventHandler(update)); while (port.BytesToRead == 0) { } nightlight_value = Convert.ToInt16(port.ReadLine()); data = nightlight_value; this.Invoke(new EventHandler(update)); while (port.BytesToRead == 0) { } nightstart_value = Convert.ToInt16(port.ReadLine()); data = nightstart_value; this.Invoke(new EventHandler(update)); while (port.BytesToRead == 0) { } nightstop_value = Convert.ToInt16(port.ReadLine()); data = nightstop_value; this.Invoke(new EventHandler(update)); this.Invoke(new EventHandler(u_daynight)); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } break; case 2: { try { while (port.BytesToRead == 0) { } light_value = Convert.ToInt16(port.ReadLine()); data = light_value; this.Invoke(new EventHandler(update)); this.Invoke(new EventHandler(u_current)); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } break; case 4: { try { while (port.BytesToRead == 0) { } hour = Convert.ToInt16(port.ReadLine()); data = hour; this.Invoke(new EventHandler(update)); while (port.BytesToRead == 0) { } min = Convert.ToInt16(port.ReadLine()); data = min; this.Invoke(new EventHandler(update)); while (port.BytesToRead == 0) { } sec = Convert.ToInt16(port.ReadLine()); data = sec; this.Invoke(new EventHandler(update)); this.Invoke(new EventHandler(u_time)); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } break; } } private void update(object sender, EventArgs e) { richTextBox1.Text+="in:"+data.ToString()+"\n"; } private void u_daynight(object sender, EventArgs e) { daylight.Text= daylight_value.ToString(); nightlight.Text = nightlight_value.ToString(); nightstart_h.Text = nightstart_value.ToString().PadLeft(4, '0').Substring(0, 2); nightstart_m.Text = nightstart_value.ToString().PadLeft(4, '0').Substring(2, 2); nightstop_h.Text = nightstop_value.ToString().PadLeft(4, '0').Substring(0, 2); nightstop_m.Text = nightstop_value.ToString().PadLeft(4, '0').Substring(2, 2); if(!daylight.Enabled) { daylight.Enabled = true; nightlight.Enabled = true; nightstart_h.Enabled = true; nightstart_m.Enabled = true; nightstop_h.Enabled = true; nightstop_m.Enabled = true; setconf.Enabled = true; } } private void u_current(object sender, EventArgs e) { current.Text = light_value.ToString(); } private void u_time(object sender, EventArgs e) { timeclock.Text = hour.ToString().PadLeft(2, '0') + ":" + min.ToString().PadLeft(2, '0') + ":" + sec.ToString().PadLeft(2, '0'); } |
Wyjaśnienia może wymagać wywoływanie funkcji w następujący sposób – this.Invoke(new EventHandler(update)). Z poziomu funkcji DataRecived nie mamy niestety możliwości bezpośredniego wpływania na elementy naszego arkusza (zmienianie napisów itd.) – wynika to z tego, że obsługa portu COM jest wykonywana w wątku niezależnym od obsługi samej formatki. Najprostszym sposobem obejścia tego problemu jest wpisanie danych, które chcemy przesłać do formatki do zmiennej globalnej i wywołanie (przy użyciu metody Invoke) funkcji mającej za zadanie przepisanie wartości tej zmiennej w pożądane miejsce.
Wiemy już, jak obsłużyć dane, które przyjdą do komputera, ale nasz komputer nie otrzyma żadnych danych, dopóki układ nie będzie ich nadawał, a on nie będzie ich nadawał dopóki nie otrzyma z komputera takiego polecenia. Wobec tego pod trzy przyciski (odpowiednio „Pobierz dane”, „Pobierz aktualny” i „Wyślij dane”) podpinamy następujące funkcje:
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 |
private void getconf_Click(object sender, EventArgs e) { if (port.IsOpen) { port.Write("1"); richTextBox1.AppendText("out:1\n"); } } private void getcurrent_Click(object sender, EventArgs e) { if (port.IsOpen) { port.Write("2"); richTextBox1.AppendText("out:2\n"); } } private void setconf_Click(object sender, EventArgs e) { try { daylight_value = Convert.ToInt16(daylight.Text.ToString()); if (daylight_value > 1024) { daylight_value = 1024; daylight.Text = daylight_value.ToString(); } if (daylight_value < 0) { daylight_value = 0; daylight.Text = daylight_value.ToString(); } nightlight_value = Convert.ToInt16(nightlight.Text.ToString()); if (nightlight_value > 1024) { nightlight_value = 1024; nightlight.Text = nightlight_value.ToString(); } if (nightlight_value < 0) { nightlight_value = 0; nightlight.Text = nightlight_value.ToString(); } int temp; temp = Convert.ToInt16(nightstart_h.Text.ToString()); if ((temp > 23) || (temp < 0)) { nightstart_h.Text = "22"; } nightstart_value = Convert.ToInt16(nightstart_h.Text.ToString()) * 100; temp = Convert.ToInt16(nightstart_m.Text.ToString()); if ((temp > 59) || (temp < 0)) { nightstart_m.Text = "00"; } nightstart_value += Convert.ToInt16(nightstart_m.Text.ToString()); temp = Convert.ToInt16(nightstop_h.Text.ToString()); if ((temp > 23) || (temp < 0)) { nightstop_h.Text = "07"; } nightstop_value = Convert.ToInt16(nightstop_h.Text.ToString()) * 100; temp = Convert.ToInt16(nightstop_m.Text.ToString()); if ((temp > 59) || (temp < 0)) { nightstop_m.Text = "00"; } nightstop_value += Convert.ToInt16(nightstop_m.Text.ToString()); string data = ""; data = "3"; data += daylight_value.ToString().PadLeft(4, '0'); data += nightlight_value.ToString().PadLeft(4, '0'); data += nightstart_value.ToString().PadLeft(4, '0'); data += nightstop_value.ToString().PadLeft(4, '0'); if ((data.Length == 17)&&(port.IsOpen)) { port.Write(data); richTextBox1.Text += "out:" + data + "\n"; } } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } |
Ostatnia z funkcji będzie podpięta nie pod przycisk, a pod akcję CheckedChanged pola CheckBox o nazwie “ZEGAR” (w sekcji GODZINA).
1 2 3 4 5 6 7 |
private void checkBox1_CheckedChanged(object sender, EventArgs e) { if(port.IsOpen) { port.Write("4"); } } |
Jak widać kod (w pierwszych dwóch i w ostatnim przypadku) ma za zadanie sprawdzić, czy port służący do komunikacji z układem został otwarty prawidłowo i, jeżeli tak, nadanie odpowiedniej flagi i zapisanie informacji o wysłanych danych do okna richTextBox1 (które będzie nam służyło do podglądania informacji przechodzących przez port z i do komputera). W trzecim przypadku sprawa jest trochę bardziej skomplikowana – będziemy tutaj nadawali dane, które najpierw przygotowywane są do wysłania w stringu „data”, a dopiero po jego przygotowaniu, sprawdzeniu poprawności jego długości i faktu otwarcia portu – nadane. Ale zaraz, zaraz. Napisałem że powyższy kod sprawdzi, czy port komunikacji został już otwarty, a nigdzie go nie otwieram. Wobec tego szybko dodajemy kod do przycisku „POŁĄCZ”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private void port_connect_Click(object sender, EventArgs e) { try { port.PortName = port_name.Text.ToString(); port.BaudRate = Convert.ToInt32(port_speed.Text.ToString()); port.Open(); richTextBox1.AppendText("Otwarto port " + port.PortName + " z prędkością " + port.BaudRate + "\n"); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } |
Kod ten pobierze nazwę portu z pola port_name, pożądaną prędkość z pola port_speed, wpisze te dane do konfiguracji naszego portu i spróbuje go otworzyć. Spróbuje? Tak, spróbuje – port może być niedostępny z wielu powodów (może być zajęty, chociażby przez monitor portu szeregowego Arduino), lub też zwyczajnie może być podana zła jego nazwa – z tego właśnie powodu cały kod ujęty jest w sekcji try{(…)}catch{(…)}. Taka konstrukcja działa następująco – wykonywany zostaje kod z sekcji try, a jeżeli przy jego wykonywaniu zostanie napotkany jakiś błąd to wykonana zostaje sekcja catch. W moim przypadku jeżeli nie uda się z jakiegoś powodu połączyć z układem kod błędu zostanie umieszczony w polu richTextBox1 (o którym już wspominałem). Nasz program potrafi już nadawać i odbierać dane, teraz musimy tej samej sztuki nauczyć nasz mikrokontroler. W tym celu modyfikujemy kod naszego zegara do następującej postaci:
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 |
#define NO_PORTD_PINCHANGES // to indicate that port b will not be used for pin change interrupts #define NO_PORTC_PINCHANGES // to indicate that port c will not be used for pin change interrupts #include <PinChangeInt.h> //biblioteka przerwań pin change #include <Wire.h> #include <DS1307RTC.h> #include <Time.h> //biblioteki do obsługi zegara RTC #include <EEPROM.h> //Needed to access the eeprom read write functions const short int PHOTORES_PIN=A0; //pin, pod który podłączony jest fotorezystor const short int LIGHT_PIN=5; //pin, pod który podłączona jest baza tranzystora PNP const short int HOUR_PIN=3; //pin, pod który podłączony jest woltomierz pokazujący godzinę const short int MIN_PIN=6; //pin, pod który podłączony jest woltomierz pokazujący minuty const short int BUT1_PIN=13; //przycisk +1H const short int BUT2_PIN=10; //przycisk -1H const short int BUT3_PIN=12; //przycisk +1M const short int BUT4_PIN=11; //przycisk -1M const short int BUT5_PIN=9; //przycisk włączenia trybu zmiany godziny short int DAYLIGHT_VALUE; //do jakiego odczytu traktujemy jako "dzień" - czyli nie zapalamy diod short int NIGHTLIGHT_VALUE;//jaką wartość minimalną osiąga odczyt - wartość w całkowicie ciemnym pomieszczeniu short int NIGHTSTART; short int NIGHTSTOP; bool change_time=false; //flaga trybu zmiany czasu bool updatetoRTC=false; //flaga przesłania nowej godziny do zegara bool getfromRTC=false; //flaga odczytania godziny z zegara do zmiennej short int h_temp=0; //tymczasowa godzina - do trybu zmiany czasu short int m_temp=0; //tymczasowa minuta - do trybu zmiany czasu bool sendtime=false; tmElements_t prev_tm; //This function will write a 2 byte integer to the eeprom at the specified address and address + 1 void EEPROMWriteInt(int p_address, int p_value) { byte lowByte = ((p_value >> 0) & 0xFF); byte highByte = ((p_value >> 8) & 0xFF); EEPROM.write(p_address, lowByte); EEPROM.write(p_address + 1, highByte); } //This function will read a 2 byte integer from the eeprom at the specified address and address + 1 unsigned int EEPROMReadInt(int p_address) { byte lowByte = EEPROM.read(p_address); byte highByte = EEPROM.read(p_address + 1); return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00); } void wait(int miliseconds) {//funkcja do debounce, nie korzystająca z przerwań for(int i=0;i<miliseconds;i++) { delayMicroseconds(1000); } } void enabletimechange() {//obsługa przycisku 5 wait(50);//debounce if(change_time) {//jeżeli jesteśmy w trybie zmiany godziny change_time=false; //wyłącz tryb zmiany godziny digitalWrite(LIGHT_PIN,HIGH); updatetoRTC=true; //ustaw flagę sygnalizującą wgranie nowej godziny do układu } else {//jeżeli nie jesteśmy w trybie zmiany godziny change_time=true; //włącz tryb zmiany godziny getfromRTC=true; //ustaw flagę sygnalizującą konieczność odczytania godziny z układu do zmiennej lokalnej } } void plushour() {//obsługa przycisku 1 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu h_temp++; //zmień godzinę if(h_temp>23) {//zapewnia zapętlenie, 22<->23<->0<->1 h_temp=0; } } } void minushour() {//obsługa przycisku 2 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu h_temp--; //zmień godzinę if(h_temp<0) {//zapewnia zapętlenie, 22<->23<->0<->1 h_temp=23; } } } void plusminute() {//obsługa przycisku 3 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu m_temp++; //zmień godzinę if(m_temp>59) {//zapewnia zapętlenie, 58<->59<->0<->1 m_temp=0; } } } void minusminute() {//obsługa przycisku 4 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu m_temp--; //zmień godzinę if(m_temp<0) {//zapewnia zapętlenie, 58<->59<->0<->1 m_temp=59; } } } int SerialRead4Int() { char temp; short int val; while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { val=temp-'0'; val=val*10; } else { return -1; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { val+=(temp-'0'); val=val*10; } else { return -1; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { val+=(temp-'0'); val=val*10; } else { return -1; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { val+=(temp-'0'); } else { return -1; } return val; } void setup() {//deklaracje pinów, podpięcia przerwań pod piny przycisków DAYLIGHT_VALUE=EEPROMReadInt(0); if((DAYLIGHT_VALUE==65535)||(DAYLIGHT_VALUE==-1)) { DAYLIGHT_VALUE=850; EEPROMWriteInt(0, DAYLIGHT_VALUE); } NIGHTLIGHT_VALUE=EEPROMReadInt(2); if((NIGHTLIGHT_VALUE==65535)||(NIGHTLIGHT_VALUE==-1)) { NIGHTLIGHT_VALUE=600; EEPROMWriteInt(2, NIGHTLIGHT_VALUE); } NIGHTSTART=EEPROMReadInt(4); if((NIGHTSTART==65535)||(NIGHTSTART==-1)) { NIGHTSTART=2200; EEPROMWriteInt(4, NIGHTSTART); } NIGHTSTOP=EEPROMReadInt(6); if((NIGHTSTOP==65535)||(NIGHTSTOP==-1)) { NIGHTSTOP=700; EEPROMWriteInt(6, NIGHTSTOP); } pinMode(PHOTORES_PIN,INPUT); pinMode(LIGHT_PIN,OUTPUT); pinMode(HOUR_PIN,OUTPUT); pinMode(MIN_PIN,OUTPUT); pinMode(BUT1_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT1_PIN, &plushour, FALLING); pinMode(BUT2_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT2_PIN, &minushour, FALLING); pinMode(BUT3_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT3_PIN, &plusminute, FALLING); pinMode(BUT4_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT4_PIN, &minusminute, FALLING); pinMode(BUT5_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT5_PIN, &enabletimechange, FALLING); digitalWrite(LIGHT_PIN,HIGH); Serial.begin(115200); } void loop() { if(sendtime) { Serial.println(4); tmElements_t tm; if (RTC.read(tm)) { Serial.println(tm.Hour); Serial.println(tm.Minute); Serial.println(tm.Second); } else { Serial.println(-1); Serial.println(-1); Serial.println(-1); } delay(100); } if(getfromRTC) {//jeżeli należy pobrać godzinę z układu do zmiennej lokalnej getfromRTC=false; //reset flagi tmElements_t tm; RTC.read(tm); //pobranie godziny h_temp=tm.Hour; m_temp=tm.Minute; //podstawienie jej do zmiennych lokalnych } if(updatetoRTC) {//jeżeli należy wysłać nową godzinę do układu updatetoRTC=false; //reset flagi tmElements_t tm; tm.Hour=h_temp; tm.Minute=m_temp; //przygotowanie danych do wysłania RTC.write(tm); //wysłanie danych do układu } if(change_time) {//jesteśmy w trybie zmiany czasu digitalWrite(LIGHT_PIN,LOW); //zapal diodę - sygnalizacja trybu zmiany godziny show(h_temp,m_temp); //wyświetl tymczasową godzinę } else {//jeżeli jesteśmy w trybie wyświetlania czasu if(Serial.available()>0) { int recived=Serial.read(); switch(recived) { case '1': { Serial.println(1); Serial.println(DAYLIGHT_VALUE); Serial.println(NIGHTLIGHT_VALUE); Serial.println(NIGHTSTART); Serial.println(NIGHTSTOP); } break; case '2': { Serial.println(2); Serial.println(analogRead(PHOTORES_PIN)); } break; case '3': { while(Serial.available()==0) {} DAYLIGHT_VALUE=SerialRead4Int(); if(DAYLIGHT_VALUE!=EEPROMReadInt(0)) { EEPROMWriteInt(0, DAYLIGHT_VALUE); } NIGHTLIGHT_VALUE=SerialRead4Int(); if(NIGHTLIGHT_VALUE!=EEPROMReadInt(2)) { EEPROMWriteInt(2, NIGHTLIGHT_VALUE); } NIGHTSTART=SerialRead4Int(); if(NIGHTSTART!=EEPROMReadInt(4)) { EEPROMWriteInt(4, NIGHTSTART); } NIGHTSTOP=SerialRead4Int(); if(NIGHTSTOP!=EEPROMReadInt(6)) { EEPROMWriteInt(6, NIGHTSTOP); } } break; case '4': { sendtime=!sendtime; } break; } } update(); //wyświetl aktualną godzinę setlight(); //dostosuj podświetlenie } } void update(void) {//funkcja pobiera godzinę z RTC i przekazuje ją do funkcji show do wyświetlenia tmElements_t tm; if (RTC.read(tm)) { if(tm.Minute!=prev_tm.Minute) { show(tm.Hour,tm.Minute); prev_tm=tm; } } } void show(int h,int m) {//funkcja wyświetlająca godzinę h=h%12; h=h*10; h=h+(map(m,0,60,0,10)); h-=5; if(h<0) { h+=5; h=abs(h); h+=115; } analogWrite(HOUR_PIN,map(h,0,120,0,255)); analogWrite(MIN_PIN,map(m,0,60,0,255)); } void setlight(void) {//funkcja ustalająca podświetlenie zegarów tmElements_t tm; bool podswietl=false; if (RTC.read(tm)) { int akt=((tm.Hour)*100+tm.Minute); if((akt<NIGHTSTART)&&(akt>NIGHTSTOP)) { podswietl=true; } } int light=analogRead(PHOTORES_PIN); if((light<DAYLIGHT_VALUE)&&(podswietl)) {//jeżeli odczytana wartość jest poniżej progu zapalającego diody - wylicz wartość PWM i podaj ją na diody if(light<=NIGHTLIGHT_VALUE) { digitalWrite(LIGHT_PIN,LOW); } else { analogWrite(LIGHT_PIN,map(light,NIGHTLIGHT_VALUE,DAYLIGHT_VALUE,0,220)); } } else {//jeżeli wartość jest powyżej progu - wyłącz diody digitalWrite(LIGHT_PIN,HIGH); } } |
W kodzie nastąpiło parę zmian (pomijając dopisanie kodu obsługującego Serial Port) od jego poprzedniej wersji, która zamykała drugą część mojego artykułu. Zmieniły się piny, pod które podpięte są przyciski (zmiana ta wyniknęła ze względów praktycznych – łatwiej mi było zaprojektować płytkę drukowaną, a dodatkowo zwolniło to piny RX oraz TX układu), niektóre ze zmiennych globalnych utraciły status „const”, zmienił się także sposób wyświetlania godziny oraz wyliczania podświetlenia. Przy wyświetlaniu godziny układ sprawdza, czy godzina właśnie pobrana z zegara RTC różni się od tej, aktualnie wyświetlonej i zmienia wyświetlaną godzinę, tylko jeżeli faktycznie nastąpiła zmiana. Zmiana podświetlenia wyniknęła ze względów praktycznych – zegar stoi na biurku i jest skierowany frontem w kierunku łóżka – ciągłe jego podświetlenie przez całą noc trochę mi przeszkadzało, wobec tego dodałem konfigurowalny przedział godzin, w trakcie których podświetlenie w ogóle się nie uruchamia.
W głównej pętli loop(){(…)} pojawiła się część odpowiedzialna za obsługę komunikacji z komputerem. Po odebraniu danych (do zmiennej int) program porównuje jej wartość z oczekiwanymi znakami – po odczytaniu któregoś z pożądanych znaków układ oczekuje na kolejne dane, albo sam rozpoczyna ich nadawanie. Co do odczytu danych – Arduino standardowo pozwala na odczytywanie danych (np. liczb) po jednym znaku (dokładnie po jednym bajcie), dlatego napisałem prostą funkcję składającą cztery kolejne odebrane znaki w jedną wartość int – jest to funkcja int SerialRead4Int(){(…)} sposób jej działania jest dość prosty, a w przypadku odebrania nieprawidłowych danych funkcja zwróci w wyniku -1.
W związku z możliwością konfigurowania niektórych parametrów zegara pojawiła się konieczność zachowywania tych ustawień, nawet w momencie odłączenia zasilania układu. Z pomocą przychodzi nam tutaj wbudowana w mikrokontroler pamięć EEPROM, która zachowuje zapisane dane nawet w przypadku zaniku zasilania. Podobnie jak w przypadku odczytywania danych z Serial Port’u – dane do EEPROMU zapisywane są po jednym bajcie na raz. Krótkie poszukiwania w Internecie naprowadziły mnie na gotowy kod, który służy do zapisywania tam zmiennych int do wielkości 16bitów (dla wartości bez znaku – 65535, co nam w zupełności wystarczy) – są to funkcje EEPROMWriteInt oraz EEPROMReadInt. Przy używaniu tych funkcji musimy pamiętać o tym, że każda wartość zapełnia dwie komórki danych, czyli pierwszy zapis robimy na komórkę 0, a drugi na komórkę 2!
Trochę inaczej od pozostałych flag, obsługiwana jest flaga 4 – służy ona do przekazania układowi informacji, że komputer oczekuje od niego ciągłego przesyłania aktualnej godziny. Po odebraniu tej flagi układ zmienia sobie globalną flagę, której stan jest osobno sprawdzany w głównej pętli i jeżeli jest ona ustawiona – następuje wysłanie godziny. Ponowne otrzymanie flagi 4 powoduje przestawienie globalnej flagi i zatrzymanie nadawania godziny.
Po wgraniu powyższego kodu nie powinniśmy zauważyć różnicy w działaniu zegara, powinniśmy za to być już w stanie „porozmawiać” z całym układem przy pomocy naszego programu. Wobec tego uruchamiamy nasz program i sprawdzamy poprawność jego działania.
Następnym elementem, który oprogramujemy będzie możliwość przesłania aktualnej godziny do układu. Będzie można w tym celu albo skorzystać z czasu w zegarze systemowym, albo pójść na całość i zaprząc do tego atomowy zegar cezowy. Brzmi fajnie, ale podejrzewam, że niewielu z nas ma w domu taki zegar. Na szczęście w dobie Internetu nie stanowi to żadnego problemu – możemy skorzystać z tzw. serwerów czasu, które służą do synchronizowania zegarów przy użyciu specjalnego protokołu NTP.
Najpierw oprogramujemy jednak pobieranie czasu z systemu, jako że jest to prostsze. Pod metodę CheckedChanged pola CheckBox o opisie SYSTEM podpinamy następujący kod:
1 2 3 4 5 6 7 8 9 10 11 |
private void gettimesystem_CheckedChanged(object sender, EventArgs e) { if(gettimesystem.Checked) { gettimesystem_ovf.Enabled = true; } else { gettimesystem_ovf.Enabled = false; } } |
Jak widać, kod ten, w zależności od stanu pola CheckBox włącza lub wyłącza komponent o nazwie gettimesystem_ovf. Komponentem tym jest zwykły Timer, który wywołuje zdarzenie Tick() co 100ms (10 razy na sekundę). Kod dla zdarzenia Tick tego timera wygląda następująco:
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 |
private void gettime_Tick(object sender, EventArgs e) { if(gettimesystem.Checked) { try { DateTime now = DateTime.Now; timesys.Text = now.Hour.ToString().PadLeft(2, '0') + ":" + now.Minute.ToString().PadLeft(2, '0') + ":" + now.Second.ToString().PadLeft(2, '0'); TimeSpan sys = new TimeSpan(now.Hour, now.Minute, now.Second); TimeSpan clock = new TimeSpan(hour, min, sec); sys = clock - sys; if (sys.Seconds < 0) { clockminsys.ForeColor = Color.Red; } else { clockminsys.ForeColor = Color.Green; } clockminsys.Text = Math.Abs(sys.Hours).ToString().PadLeft(2, '0') + ":" + Math.Abs(sys.Minutes).ToString().PadLeft(2, '0') + ":" + Math.Abs(sys.Seconds).ToString().PadLeft(2, '0'); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } } |
Kod ten (oprócz odświeżania zegara na formatce) pokazuje także na osobnym polu tekstowym różnicę czasu między zegarem systemowym, a czasem pobranym z układu.
Wiemy już która dokładnie jest godzina na naszym zegarze systemowym, teraz bierzemy się za pobieranie czasu z Internetu.
Wspomniałem już, że do tego celu służy protokół NTP. Moglibyśmy oczywiście oprogramować cały ten protokół we własnym zakresie, ale zajęłoby nam to sporo czasu i duplikowalibyśmy rozwiązania już dostępne w Internecie. Po niedługich poszukiwaniach zdecydowałem się wykorzystać bardzo wygodną i dopracowaną klasę służącą właśnie do pobierania czasu z serwerów NTP napisaną przez Pana Valera Bocana, którą można pobrać w formie paczki zip (wraz z przykładowym programem, pokazującym sposób obsługi) z jego strony internetowej.
Po przeanalizowaniu załączonego przykładu, skutecznym dołączeniu pliku zawierającego wspomnianą już klasę do całego projektu Visual Studia doszedłem do następującego kodu zdarzenia CheckedChanged pola INTERNET, oraz pola Tick przewidzianego dla niego Timera:
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 |
private void gettimeweb_CheckedChanged(object sender, EventArgs e) { if(gettimeweb.Checked) { gettimeweb_ovf.Enabled = true; } else { gettimeweb_ovf.Enabled = false; } } private void gettimeweb_ovf_Tick(object sender, EventArgs e) { if(!gottime) { if (webtimeserver.Text == string.Empty) { richTextBox1.AppendText("------\nBŁĄD: BRAK SERWERA!\n------\n"); gettimeweb_ovf.Enabled = false; gottime = false; gettimeweb.Checked = false; } else { try { DateTime temp; SNTPClient gettime = new SNTPClient(webtimeserver.Text); gettime.Connect(5000, false); richTextBox1.AppendText("------\nPOBRANO CZAS: serwer " + webtimeserver.Text + ", pobrany czas " + gettime.TransmitTimestamp.Hour+":"+gettime.TransmitTimestamp.Minute+":"+gettime.TransmitTimestamp.Second+ "\n------\n"); temp = DateTime.Now; recivedtime = new TimeSpan(gettime.TransmitTimestamp.Hour, gettime.TransmitTimestamp.Minute, gettime.TransmitTimestamp.Second); whenrecived = new TimeSpan(temp.Hour,temp.Minute,temp.Second); gottime = true; } catch (Exception ex) { richTextBox1.AppendText("------\nBŁĄD: " + ex.Message + "\n------\n"); gettimeweb_ovf.Enabled = false; gottime = false; gettimeweb.Checked = false; } } } else { try { DateTime temp1 = DateTime.Now; TimeSpan temp = new TimeSpan(temp1.Hour, temp1.Minute, temp1.Second); temp = recivedtime + (temp - whenrecived); timeweb.Text = temp.Hours.ToString().PadLeft(2, '0') + ":" + temp.Minutes.ToString().PadLeft(2, '0') + ":" + temp.Seconds.ToString().PadLeft(2, '0'); TimeSpan clock = new TimeSpan(hour, min, sec); temp = clock - temp; if (temp.Seconds < 0) { clockminweb.ForeColor = Color.Red; } else { clockminweb.ForeColor = Color.Green; } clockminweb.Text = Math.Abs(temp.Hours).ToString().PadLeft(2, '0') + ":" + Math.Abs(temp.Minutes).ToString().PadLeft(2, '0') + ":" + Math.Abs(temp.Seconds).ToString().PadLeft(2, '0'); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } } |
Kod działa następująco – podobnie jak w przypadku czasu z systemu, zaznaczenie pola powoduje uruchomienie Timera, który w swoim kodzie sprawdza (poprzez zmienną globalną bool gottime), czy został już pobrany prawidłowo czas z Internetu. Jeżeli nie – zostaje on pobrany z serwera o nazwie znajdującej się w polu webtimeserver i wpisany do zmiennej globalnej TimeSpan recivedtime. Oprócz tego zostaje też zapisana godzina, o której nastąpił poprawny odczyt czasu z Internetu. W jakim celu? Otóż serwery NTP nie lubią ciągłego odpytywania o aktualną godzinę (a nasz kod robiłby to 10 razy na sekundę) – sugerowaną praktyką jest więc pobranie dokładnego czasu raz, zapisanie dokładnej godziny, o której nastąpiło pobranie tego czasu i dalej używanie różnicy zapisanej godziny z godziną aktualną (w ten sposób wiemy ile czasu upłynęło od pobrania czasu) i dodania tej wartości do poprzednio pobranej godziny. Dokładność, dla naszego rozwiązania, niemalże identyczna, a serwer nie jest niepotrzebnie zarzucany zapytaniami 10 razy na sekundę. Podobnie jak w przypadku czasu z systemu, tutaj także wyliczana jest różnica pomiędzy wzorcem z Internetu i aktualnie ustawioną godziną na zegarze.
Wiemy już (bardzo) dokładnie, która jest godzina, do pełni szczęścia brakuje nam możliwości podzielenia się tą informacją z naszym zegarem. W tym celu oprogramowujemy dwa przyciski SEND (jeden będzie wysyłał godzinę systemową, a drugi – godzinę z Internetu):
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 |
private void syssend_Click(object sender, EventArgs e) { try { string data = ""; data = "5"; DateTime now = DateTime.Now; now += new TimeSpan(0, 0, 1); data += now.Hour.ToString().PadLeft(2, '0') + now.Minute.ToString().PadLeft(2, '0') + now.Second.ToString().PadLeft(2, '0'); while (DateTime.Now.Second == (now.Second - 1)) { } if ((data.Length == 7) && (port.IsOpen)) { port.Write(data); richTextBox1.AppendText("out: " + data + "\n"); } } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } private void websend_Click(object sender, EventArgs e) { try { if (gottime) { DateTime temp1 = DateTime.Now; TimeSpan temp = new TimeSpan(temp1.Hour, temp1.Minute, temp1.Second); temp = recivedtime + (temp - whenrecived); string data = ""; data = "5"; temp += new TimeSpan(0, 0, 1); data += temp.Hours.ToString().PadLeft(2, '0') + temp.Minutes.ToString().PadLeft(2, '0') + temp.Seconds.ToString().PadLeft(2, '0'); while (DateTime.Now.Second == (temp.Seconds - 1)) { } if ((data.Length == 7) && (port.IsOpen)) { port.Write(data); richTextBox1.AppendText("out: " + data + "\n"); } } } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } |
Kod, podobnie jak jeden z poprzednich zbiera dane do wysłania do jednego stringa i dopiero po skompletowaniu danych wysyła je do układu. Zastosowałem tutaj jeszcze jedną małą sztuczkę. Dane przygotowuję, dodając do nich jedną sekundę i wysyłam je dopiero w momencie gdy będą one poprawne (czyli gdy minie ta jedna sekunda) – dzięki temu uzyskuję (z grubsza, oczywiście) synchronizację co do dziesiątych części sekundy.
Kod aplikacji przewiduje już możliwość przesyłania do układu godziny, ale kod układu jeszcze nie wie, co z tak otrzymanymi danymi powinien zrobić. Szybko to poprawiamy modyfikując kod do następującej postaci: (część kodu usunąłem, ponieważ jest identyczny z tą z tym zamieszczonym powyżej)
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 |
#define NO_PORTD_PINCHANGES // to indicate that port b will not be used for pin change interrupts #define NO_PORTC_PINCHANGES // to indicate that port c will not be used for pin change interrupts #include <PinChangeInt.h> //biblioteka przerwań pin change #include <Wire.h> #include <DS1307RTC.h> #include <Time.h> //biblioteki do obsługi zegara RTC #include <EEPROM.h> //Needed to access the eeprom read write functions const short int PHOTORES_PIN=A0; //pin, pod który podłączony jest fotorezystor const short int LIGHT_PIN=5; //pin, pod który podłączona jest baza tranzystora PNP const short int HOUR_PIN=3; //pin, pod który podłączony jest woltomierz pokazujący godzinę const short int MIN_PIN=6; //pin, pod który podłączony jest woltomierz pokazujący minuty const short int BUT1_PIN=13; //przycisk +1H const short int BUT2_PIN=10; //przycisk -1H const short int BUT3_PIN=12; //przycisk +1M const short int BUT4_PIN=11; //przycisk -1M const short int BUT5_PIN=9; //przycisk włączenia trybu zmiany godziny short int DAYLIGHT_VALUE; //do jakiego odczytu traktujemy jako "dzień" - czyli nie zapalamy diod short int NIGHTLIGHT_VALUE;//jaką wartość minimalną osiąga odczyt - wartość w całkowicie ciemnym pomieszczeniu short int NIGHTSTART; short int NIGHTSTOP; bool change_time=false; //flaga trybu zmiany czasu bool updatetoRTC=false; //flaga przesłania nowej godziny do zegara bool getfromRTC=false; //flaga odczytania godziny z zegara do zmiennej short int h_temp=0; //tymczasowa godzina - do trybu zmiany czasu short int m_temp=0; //tymczasowa minuta - do trybu zmiany czasu bool sendtime=false; tmElements_t prev_tm; void EEPROMWriteInt(int p_address, int p_value) {(…)} unsigned int EEPROMReadInt(int p_address) {(…)} void wait(int miliseconds) {(…)} void enabletimechange() {(…)} void plushour() {(…)} void minushour() {(…)} void plusminute() {(…)} void minusminute() {(…)} int SerialRead4Int() {(…)} tmElements_t SerialReadTime() { int temp,temp2; tmElements_t out; while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2=(temp-'0')*10; temp=0; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2+=temp-'0'; temp=0; } out.Hour=temp2; while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2=(temp-'0')*10; temp=0; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2+=temp-'0'; temp=0; } out.Minute=temp2; while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2=(temp-'0')*10; temp=0; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2+=temp-'0'; temp=0; } out.Second=temp2; return out; } void setup() {(…)} void loop() { if(sendtime) { Serial.println(4); tmElements_t tm; if (RTC.read(tm)) { Serial.println(tm.Hour); Serial.println(tm.Minute); Serial.println(tm.Second); } else { Serial.println(-1); Serial.println(-1); Serial.println(-1); } delay(100); } if(getfromRTC) {(…)} if(updatetoRTC) {(…)} if(change_time) {(…)} else {//jeżeli jesteśmy w trybie wyświetlania czasu if(Serial.available()>0) { int recived=Serial.read(); switch(recived) { case '1': { Serial.println(1); Serial.println(DAYLIGHT_VALUE); Serial.println(NIGHTLIGHT_VALUE); Serial.println(NIGHTSTART); Serial.println(NIGHTSTOP); } break; case '2': { Serial.println(2); Serial.println(analogRead(PHOTORES_PIN)); } break; case '3': { while(Serial.available()==0) {} DAYLIGHT_VALUE=SerialRead4Int(); if(DAYLIGHT_VALUE!=EEPROMReadInt(0)) { EEPROMWriteInt(0, DAYLIGHT_VALUE); } NIGHTLIGHT_VALUE=SerialRead4Int(); if(NIGHTLIGHT_VALUE!=EEPROMReadInt(2)) { EEPROMWriteInt(2, NIGHTLIGHT_VALUE); } NIGHTSTART=SerialRead4Int(); if(NIGHTSTART!=EEPROMReadInt(4)) { EEPROMWriteInt(4, NIGHTSTART); } NIGHTSTOP=SerialRead4Int(); if(NIGHTSTOP!=EEPROMReadInt(6)) { EEPROMWriteInt(6, NIGHTSTOP); } } break; case '4': { sendtime=!sendtime; } break; case '5': { while(Serial.available()==0) {} tmElements_t tm=SerialReadTime(); RTC.write(tm); } break; } } update(); //wyświetl aktualną godzinę setlight(); //dostosuj podświetlenie } } void update(void) {(…)} void show(int h,int m) {(…)} void setlight(void) {(…)} |
Jak widać, pojawiła się nowa funkcja tmElements_t SerialReadTime(){(…)} – służy ona do odczytania z Serial Portu 6 kolejnych cyfr w formacie HHMMSS i stworzenia z nich obiektu klasy tmElements_t, który może zostać wysłany do układu RTC w celu przestawienia ustawionej w nim godziny. Funkcja ta jest wywoływana w pętli loop() w sposób widoczny powyżej.
Wgrywamy program do układu, uruchamiamy nasz program i sprawdzamy czy wszystko działa. Działa? Działa!
Ostatnią rzeczą, którą postanowiłem dodać do swojego projektu jest ukłon w stronę wrześniowej tematyki. Załóżmy, że w naszej pracy często współpracujemy z firmami zza oceanu, lub z kraju kwitnącej wiśni. W ramach naszej współpracy często przeprowadzamy konferencje telefoniczne, bądź wideo. Wygodnie byłoby móc szybko sprawdzić która jest „tam u nich” godzina, więc dodamy możliwość uwzględnienia przesunięcia strefy czasowej.
Po przemyśleniu kwestii postanowiłem załatwić to przy pomocy 4 kontrolek – jednego pola CheckBox, w którym zaznaczymy, czy uwzględnienie strefy czasowej naprawdę nas interesuje, dwóch pól ComboBox (do wyboru aktualnej strefy czasowej i strefy docelowej) i pola tekstowego, które wyświetli nam różnicę czasu między wybranymi strefami.
Kod dla metody SelectedIndexChanged obu pól (oba pola wywołują ten sam kod!) prezentuje się następująco:
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 |
private void curtz_SelectedIndexChanged(object sender, EventArgs e) { try { string cur, goal; TimeSpan start, stop; cur = curtz.SelectedItem.ToString(); goal = goaltz.SelectedItem.ToString(); start = new TimeSpan(Convert.ToInt16(cur.Substring(4, 3)), Convert.ToInt16(cur.Substring(8, 2)), 0); start += new TimeSpan(12, 0, 0); stop = new TimeSpan(Convert.ToInt16(goal.Substring(4, 3)), Convert.ToInt16(goal.Substring(8, 2)), 0); stop += new TimeSpan(12, 0, 0); goal = ""; stop = stop - start; if ((stop.Minutes < 0)||(stop.Hours<0)) { goal = "-"; tztimedif.ForeColor = Color.Red; } else { if((stop.Minutes==0)&&(stop.Hours==0)) { goal = " "; tztimedif.ForeColor = Color.Black; } else { goal = "+"; tztimedif.ForeColor = Color.Green; } } goal += Math.Abs(stop.Hours).ToString().PadLeft(2, '0') + ":"; goal += Math.Abs(stop.Minutes).ToString().PadLeft(2, '0'); tztimedif.Text = goal; timezonemod = stop; } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } |
Kod ten ogranicza się do prostej matematyki i wylicza różnicę czasu pomiędzy dwoma wybranymi strefami czasowymi, a następnie zapisuje ją jako zmienną typu TimeSpan do zmiennej globalnej timezonemod. W celu uwzględnienia ustawień strefy czasowej w każdym miejscu, w którym robimy coś z czasem (wyświetlamy go, wysyłamy itp.) musimy dodać kod, który sprawdzi, czy pole CheckBox informujące o konieczności uwzględnienia strefy czasowej jest zaznaczone i (jeżeli tak) dodaniu do przetwarzanej godziny wartości zmiennej timezonemod.
Oprócz tego, cała reszta rzeczy uwzględnionych w kodzie programu to już kosmetyka – dodałem dolną belkę w oknie programu, na której od razu widać, czy program jest połączony z portem COM, którym i z jaką prędkością. Dodałem też kod, który przy uruchomieniu programu sprawdzi w systemie jakie porty COM są dostępne i doda ich listę do rozwijalnego menu (domyślnie wybierając z tej listy pierwszy z nich). Do rozwijalnej listy dopisałem także parę polskich serwerów NTP (domyślnie ustawiłem serwer znajdujący się pod Poznaniem – w trakcie testów nie miałem z nim żadnych problemów, a z innymi potrafiły takie problemy być). Ostateczny kod całego programu wygląda następująco:
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 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 |
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO.Ports; namespace WindowsFormsApplication1 { public partial class Form1 : Form { int data; int daylight_value; int nightlight_value; int light_value; int nightstart_value; int nightstop_value; int hour = 0; int min = 0; int sec = 0; TimeSpan recivedtime; TimeSpan whenrecived; TimeSpan timezonemod; bool gottime = false; public Form1() { InitializeComponent(); curtz.Items.Add("UTC -12:00"); curtz.Items.Add("UTC -11:00"); curtz.Items.Add("UTC -10:00"); curtz.Items.Add("UTC -09:30"); curtz.Items.Add("UTC -09:00"); curtz.Items.Add("UTC -08:00"); curtz.Items.Add("UTC -07:00"); curtz.Items.Add("UTC -06:00"); curtz.Items.Add("UTC -05:00"); curtz.Items.Add("UTC -04:30"); curtz.Items.Add("UTC -04:00"); curtz.Items.Add("UTC -03:30"); curtz.Items.Add("UTC -03:00"); curtz.Items.Add("UTC -02:00"); curtz.Items.Add("UTC -01:00"); curtz.Items.Add("UTC +00:00"); curtz.Items.Add("UTC +01:00"); curtz.Items.Add("UTC +02:00"); curtz.Items.Add("UTC +03:00"); curtz.Items.Add("UTC +03:30"); curtz.Items.Add("UTC +04:00"); curtz.Items.Add("UTC +04:30"); curtz.Items.Add("UTC +05:00"); curtz.Items.Add("UTC +05:30"); curtz.Items.Add("UTC +05:45"); curtz.Items.Add("UTC +06:00"); curtz.Items.Add("UTC +06:30"); curtz.Items.Add("UTC +07:00"); curtz.Items.Add("UTC +08:00"); curtz.Items.Add("UTC +09:00"); curtz.Items.Add("UTC +09:30"); curtz.Items.Add("UTC +10:00"); curtz.Items.Add("UTC +10:30"); curtz.Items.Add("UTC +11:00"); curtz.Items.Add("UTC +11:30"); curtz.Items.Add("UTC +12:00"); curtz.Items.Add("UTC +12:45"); curtz.Items.Add("UTC +13:00"); curtz.Items.Add("UTC +14:00"); curtz.SelectedItem="UTC +00:00"; goaltz.Items.Add("UTC -12:00"); goaltz.Items.Add("UTC -11:00"); goaltz.Items.Add("UTC -10:00"); goaltz.Items.Add("UTC -09:30"); goaltz.Items.Add("UTC -09:00"); goaltz.Items.Add("UTC -08:00"); goaltz.Items.Add("UTC -07:00"); goaltz.Items.Add("UTC -06:00"); goaltz.Items.Add("UTC -05:00"); goaltz.Items.Add("UTC -04:30"); goaltz.Items.Add("UTC -04:00"); goaltz.Items.Add("UTC -03:30"); goaltz.Items.Add("UTC -03:00"); goaltz.Items.Add("UTC -02:00"); goaltz.Items.Add("UTC -01:00"); goaltz.Items.Add("UTC +00:00"); goaltz.Items.Add("UTC +01:00"); goaltz.Items.Add("UTC +02:00"); goaltz.Items.Add("UTC +03:00"); goaltz.Items.Add("UTC +03:30"); goaltz.Items.Add("UTC +04:00"); goaltz.Items.Add("UTC +04:30"); goaltz.Items.Add("UTC +05:00"); goaltz.Items.Add("UTC +05:30"); goaltz.Items.Add("UTC +05:45"); goaltz.Items.Add("UTC +06:00"); goaltz.Items.Add("UTC +06:30"); goaltz.Items.Add("UTC +07:00"); goaltz.Items.Add("UTC +08:00"); goaltz.Items.Add("UTC +09:00"); goaltz.Items.Add("UTC +09:30"); goaltz.Items.Add("UTC +10:00"); goaltz.Items.Add("UTC +10:30"); goaltz.Items.Add("UTC +11:00"); goaltz.Items.Add("UTC +11:30"); goaltz.Items.Add("UTC +12:00"); goaltz.Items.Add("UTC +12:45"); goaltz.Items.Add("UTC +13:00"); goaltz.Items.Add("UTC +14:00"); goaltz.SelectedItem = "UTC +00:00"; try { string[] ports = SerialPort.GetPortNames(); foreach (string port in ports) { port_name.Items.Add(port); } port_name.SelectedItem = ports[0]; } catch { } } private void port_connect_Click(object sender, EventArgs e) { try { port.PortName = port_name.Text.ToString(); port.BaudRate = Convert.ToInt32(port_speed.Text.ToString()); port.Open(); port_connect.Enabled = false; port_disconnect.Enabled = true; getconf.Enabled = true; getcurrent.Enabled = true; richTextBox1.AppendText("Otwarto port " + port.PortName + " z prędkością " + port.BaudRate + "\n"); portstatus.Text = "POŁĄCZONO Z PORTEM " + port.PortName + ", PRĘDKOŚĆ " + port.BaudRate; portstatus.Image = WindowsFormsApplication1.Properties.Resources.connect; gettimeclock.Enabled = true; websend.Enabled = true; syssend.Enabled = true; } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } private void port_disconnect_Click(object sender, EventArgs e) { try { if (port.IsOpen) { if (gettimeclock.Checked) { gettimeclock.Checked = false; System.Threading.Thread.Sleep(50); } port.Close(); } daylight.Enabled = false; nightlight.Enabled = false; nightstart_h.Enabled = false; nightstart_m.Enabled = false; nightstop_h.Enabled = false; nightstop_m.Enabled = false; port_disconnect.Enabled = false; port_connect.Enabled = true; getconf.Enabled = false; setconf.Enabled = false; getcurrent.Enabled = false; richTextBox1.AppendText("Rozłączono"); portstatus.Text = "BRAK POŁĄCZENIA"; portstatus.Image = WindowsFormsApplication1.Properties.Resources.noconnect; gettimeclock.Enabled = false; websend.Enabled = false; syssend.Enabled = false; } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } private void update(object sender, EventArgs e) { richTextBox1.Text+="in:"+data.ToString()+"\n"; } private void u_daynight(object sender, EventArgs e) { daylight.Text= daylight_value.ToString(); nightlight.Text = nightlight_value.ToString(); nightstart_h.Text = nightstart_value.ToString().PadLeft(4, '0').Substring(0, 2); nightstart_m.Text = nightstart_value.ToString().PadLeft(4, '0').Substring(2, 2); nightstop_h.Text = nightstop_value.ToString().PadLeft(4, '0').Substring(0, 2); nightstop_m.Text = nightstop_value.ToString().PadLeft(4, '0').Substring(2, 2); if(!daylight.Enabled) { daylight.Enabled = true; nightlight.Enabled = true; nightstart_h.Enabled = true; nightstart_m.Enabled = true; nightstop_h.Enabled = true; nightstop_m.Enabled = true; setconf.Enabled = true; } } private void u_current(object sender, EventArgs e) { current.Text = light_value.ToString(); } private void u_time(object sender, EventArgs e) { timeclock.Text = hour.ToString().PadLeft(2, '0') + ":" + min.ToString().PadLeft(2, '0') + ":" + sec.ToString().PadLeft(2, '0'); } private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) { data = Convert.ToInt16(port.ReadLine()); this.Invoke(new EventHandler(update)); switch (data) { case 1: { try { while (port.BytesToRead == 0) { } daylight_value = Convert.ToInt16(port.ReadLine()); data = daylight_value; this.Invoke(new EventHandler(update)); while (port.BytesToRead == 0) { } nightlight_value = Convert.ToInt16(port.ReadLine()); data = nightlight_value; this.Invoke(new EventHandler(update)); while (port.BytesToRead == 0) { } nightstart_value = Convert.ToInt16(port.ReadLine()); data = nightstart_value; this.Invoke(new EventHandler(update)); while (port.BytesToRead == 0) { } nightstop_value = Convert.ToInt16(port.ReadLine()); data = nightstop_value; this.Invoke(new EventHandler(update)); this.Invoke(new EventHandler(u_daynight)); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } break; case 2: { try { while (port.BytesToRead == 0) { } light_value = Convert.ToInt16(port.ReadLine()); data = light_value; this.Invoke(new EventHandler(update)); this.Invoke(new EventHandler(u_current)); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } break; case 4: { try { while (port.BytesToRead == 0) { } hour = Convert.ToInt16(port.ReadLine()); data = hour; this.Invoke(new EventHandler(update)); while (port.BytesToRead == 0) { } min = Convert.ToInt16(port.ReadLine()); data = min; this.Invoke(new EventHandler(update)); while (port.BytesToRead == 0) { } sec = Convert.ToInt16(port.ReadLine()); data = sec; this.Invoke(new EventHandler(update)); this.Invoke(new EventHandler(u_time)); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } break; } } private void getconf_Click(object sender, EventArgs e) { if (port.IsOpen) { port.Write("1"); richTextBox1.AppendText("out:1\n"); } } private void getcurrent_Click(object sender, EventArgs e) { if (port.IsOpen) { port.Write("2"); richTextBox1.AppendText("out:2\n"); } } private void setconf_Click(object sender, EventArgs e) { try { daylight_value = Convert.ToInt16(daylight.Text.ToString()); if (daylight_value > 1024) { daylight_value = 1024; daylight.Text = daylight_value.ToString(); } if (daylight_value < 0) { daylight_value = 0; daylight.Text = daylight_value.ToString(); } nightlight_value = Convert.ToInt16(nightlight.Text.ToString()); if (nightlight_value > 1024) { nightlight_value = 1024; nightlight.Text = nightlight_value.ToString(); } if (nightlight_value < 0) { nightlight_value = 0; nightlight.Text = nightlight_value.ToString(); } int temp; temp = Convert.ToInt16(nightstart_h.Text.ToString()); if ((temp > 23) || (temp < 0)) { nightstart_h.Text = "22"; } nightstart_value = Convert.ToInt16(nightstart_h.Text.ToString()) * 100; temp = Convert.ToInt16(nightstart_m.Text.ToString()); if ((temp > 59) || (temp < 0)) { nightstart_m.Text = "00"; } nightstart_value += Convert.ToInt16(nightstart_m.Text.ToString()); temp = Convert.ToInt16(nightstop_h.Text.ToString()); if ((temp > 23) || (temp < 0)) { nightstop_h.Text = "07"; } nightstop_value = Convert.ToInt16(nightstop_h.Text.ToString()) * 100; temp = Convert.ToInt16(nightstop_m.Text.ToString()); if ((temp > 59) || (temp < 0)) { nightstop_m.Text = "00"; } nightstop_value += Convert.ToInt16(nightstop_m.Text.ToString()); string data = ""; data = "3"; data += daylight_value.ToString().PadLeft(4, '0'); data += nightlight_value.ToString().PadLeft(4, '0'); data += nightstart_value.ToString().PadLeft(4, '0'); data += nightstop_value.ToString().PadLeft(4, '0'); if ((data.Length == 17)&&(port.IsOpen)) { port.Write(data); richTextBox1.Text += "out:" + data + "\n"; } } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } private void richTextBox1_TextChanged(object sender, EventArgs e) { richTextBox1.SelectionStart = richTextBox1.Text.Length; richTextBox1.ScrollToCaret(); } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { if (port.IsOpen) { port.Close(); } } private void button1_Click(object sender, EventArgs e) { if(COMpeek.Visible) { COMpeek.Visible = false; } else { COMpeek.Visible = true; } } private void checkBox1_CheckedChanged(object sender, EventArgs e) { if(port.IsOpen) { port.Write("4"); } } private void gettimesystem_CheckedChanged(object sender, EventArgs e) { if(gettimesystem.Checked) { gettimesystem_ovf.Enabled = true; } else { gettimesystem_ovf.Enabled = false; } } private void gettimeweb_CheckedChanged(object sender, EventArgs e) { if(gettimeweb.Checked) { gettimeweb_ovf.Enabled = true; } else { gettimeweb_ovf.Enabled = false; } } private void gettime_Tick(object sender, EventArgs e) { if(gettimesystem.Checked) { try { DateTime now = DateTime.Now; if ((strefalicz.Checked) && (timezonemod != null)) { now += timezonemod; } timesys.Text = now.Hour.ToString().PadLeft(2, '0') + ":" + now.Minute.ToString().PadLeft(2, '0') + ":" + now.Second.ToString().PadLeft(2, '0'); TimeSpan sys = new TimeSpan(now.Hour, now.Minute, now.Second); TimeSpan clock = new TimeSpan(hour, min, sec); sys = clock - sys; if (sys.Seconds < 0) { clockminsys.ForeColor = Color.Red; } else { clockminsys.ForeColor = Color.Green; } clockminsys.Text = Math.Abs(sys.Hours).ToString().PadLeft(2, '0') + ":" + Math.Abs(sys.Minutes).ToString().PadLeft(2, '0') + ":" + Math.Abs(sys.Seconds).ToString().PadLeft(2, '0'); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } } private void syssend_Click(object sender, EventArgs e) { try { string data = ""; data = "5"; DateTime now = DateTime.Now; if ((strefalicz.Checked) && (timezonemod != null)) { now += timezonemod; } now += new TimeSpan(0, 0, 1); data += now.Hour.ToString().PadLeft(2, '0') + now.Minute.ToString().PadLeft(2, '0') + now.Second.ToString().PadLeft(2, '0'); while (DateTime.Now.Second == (now.Second - 1)) { } if ((data.Length == 7) && (port.IsOpen)) { port.Write(data); richTextBox1.AppendText("out: " + data + "\n"); } } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } private void gettimeweb_ovf_Tick(object sender, EventArgs e) { if(!gottime) { if (webtimeserver.Text == string.Empty) { richTextBox1.AppendText("------\nBŁĄD: BRAK SERWERA!\n------\n"); gettimeweb_ovf.Enabled = false; gottime = false; gettimeweb.Checked = false; } else { try { DateTime temp; SNTPClient gettime = new SNTPClient(webtimeserver.Text); gettime.Connect(5000, false); richTextBox1.AppendText("------\nPOBRANO CZAS: serwer " + webtimeserver.Text + ", pobrany czas " + gettime.TransmitTimestamp.Hour+":"+gettime.TransmitTimestamp.Minute+":"+gettime.TransmitTimestamp.Second+ "\n------\n"); temp = DateTime.Now; recivedtime = new TimeSpan(gettime.TransmitTimestamp.Hour, gettime.TransmitTimestamp.Minute, gettime.TransmitTimestamp.Second); whenrecived = new TimeSpan(temp.Hour,temp.Minute,temp.Second); gottime = true; } catch (Exception ex) { richTextBox1.AppendText("------\nBŁĄD: " + ex.Message + "\n------\n"); gettimeweb_ovf.Enabled = false; gottime = false; gettimeweb.Checked = false; } } } else { try { DateTime temp1 = DateTime.Now; TimeSpan temp = new TimeSpan(temp1.Hour, temp1.Minute, temp1.Second); temp = recivedtime + (temp - whenrecived); if ((strefalicz.Checked) && (timezonemod != null)) { temp += timezonemod; } timeweb.Text = temp.Hours.ToString().PadLeft(2, '0') + ":" + temp.Minutes.ToString().PadLeft(2, '0') + ":" + temp.Seconds.ToString().PadLeft(2, '0'); TimeSpan clock = new TimeSpan(hour, min, sec); temp = clock - temp; if (temp.Seconds < 0) { clockminweb.ForeColor = Color.Red; } else { clockminweb.ForeColor = Color.Green; } clockminweb.Text = Math.Abs(temp.Hours).ToString().PadLeft(2, '0') + ":" + Math.Abs(temp.Minutes).ToString().PadLeft(2, '0') + ":" + Math.Abs(temp.Seconds).ToString().PadLeft(2, '0'); } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } } private void websend_Click(object sender, EventArgs e) { try { if (gottime) { DateTime temp1 = DateTime.Now; TimeSpan temp = new TimeSpan(temp1.Hour, temp1.Minute, temp1.Second); temp = recivedtime + (temp - whenrecived); if ((strefalicz.Checked) && (timezonemod != null)) { temp += timezonemod; } string data = ""; data = "5"; temp += new TimeSpan(0, 0, 1); data += temp.Hours.ToString().PadLeft(2, '0') + temp.Minutes.ToString().PadLeft(2, '0') + temp.Seconds.ToString().PadLeft(2, '0'); while (DateTime.Now.Second == (temp.Seconds - 1)) { } if ((data.Length == 7) && (port.IsOpen)) { port.Write(data); richTextBox1.AppendText("out: " + data + "\n"); } } } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } private void curtz_SelectedIndexChanged(object sender, EventArgs e) { try { string cur, goal; TimeSpan start, stop; cur = curtz.SelectedItem.ToString(); goal = goaltz.SelectedItem.ToString(); start = new TimeSpan(Convert.ToInt16(cur.Substring(4, 3)), Convert.ToInt16(cur.Substring(8, 2)), 0); start += new TimeSpan(12, 0, 0); stop = new TimeSpan(Convert.ToInt16(goal.Substring(4, 3)), Convert.ToInt16(goal.Substring(8, 2)), 0); stop += new TimeSpan(12, 0, 0); goal = ""; stop = stop - start; if ((stop.Minutes < 0)||(stop.Hours<0)) { goal = "-"; tztimedif.ForeColor = Color.Red; } else { if((stop.Minutes==0)&&(stop.Hours==0)) { goal = " "; tztimedif.ForeColor = Color.Black; } else { goal = "+"; tztimedif.ForeColor = Color.Green; } } goal += Math.Abs(stop.Hours).ToString().PadLeft(2, '0') + ":"; goal += Math.Abs(stop.Minutes).ToString().PadLeft(2, '0'); tztimedif.Text = goal; timezonemod = stop; } catch (Exception ex) { richTextBox1.AppendText("----------\nERROR: " + ex.Message + "\n----------\n"); } } } } |
A kod dla mikrokontrolera:
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 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 |
#define NO_PORTD_PINCHANGES // to indicate that port b will not be used for pin change interrupts #define NO_PORTC_PINCHANGES // to indicate that port c will not be used for pin change interrupts #include <PinChangeInt.h> //biblioteka przerwań pin change #include <Wire.h> #include <DS1307RTC.h> #include <Time.h> //biblioteki do obsługi zegara RTC #include <EEPROM.h> //Needed to access the eeprom read write functions const short int PHOTORES_PIN=A0; //pin, pod który podłączony jest fotorezystor const short int LIGHT_PIN=5; //pin, pod który podłączona jest baza tranzystora PNP const short int HOUR_PIN=3; //pin, pod który podłączony jest woltomierz pokazujący godzinę const short int MIN_PIN=6; //pin, pod który podłączony jest woltomierz pokazujący minuty const short int BUT1_PIN=13; //przycisk +1H const short int BUT2_PIN=10; //przycisk -1H const short int BUT3_PIN=12; //przycisk +1M const short int BUT4_PIN=11; //przycisk -1M const short int BUT5_PIN=9; //przycisk włączenia trybu zmiany godziny short int DAYLIGHT_VALUE; //do jakiego odczytu traktujemy jako "dzień" - czyli nie zapalamy diod short int NIGHTLIGHT_VALUE;//jaką wartość minimalną osiąga odczyt - wartość w całkowicie ciemnym pomieszczeniu short int NIGHTSTART; short int NIGHTSTOP; bool change_time=false; //flaga trybu zmiany czasu bool updatetoRTC=false; //flaga przesłania nowej godziny do zegara bool getfromRTC=false; //flaga odczytania godziny z zegara do zmiennej short int h_temp=0; //tymczasowa godzina - do trybu zmiany czasu short int m_temp=0; //tymczasowa minuta - do trybu zmiany czasu bool sendtime=false; tmElements_t prev_tm; //This function will write a 2 byte integer to the eeprom at the specified address and address + 1 void EEPROMWriteInt(int p_address, int p_value) { byte lowByte = ((p_value >> 0) & 0xFF); byte highByte = ((p_value >> 8) & 0xFF); EEPROM.write(p_address, lowByte); EEPROM.write(p_address + 1, highByte); } //This function will read a 2 byte integer from the eeprom at the specified address and address + 1 unsigned int EEPROMReadInt(int p_address) { byte lowByte = EEPROM.read(p_address); byte highByte = EEPROM.read(p_address + 1); return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00); } void wait(int miliseconds) {//funkcja do debounce, nie korzystająca z przerwań for(int i=0;i<miliseconds;i++) { delayMicroseconds(1000); } } void enabletimechange() {//obsługa przycisku 5 wait(50);//debounce if(change_time) {//jeżeli jesteśmy w trybie zmiany godziny change_time=false; //wyłącz tryb zmiany godziny digitalWrite(LIGHT_PIN,HIGH); updatetoRTC=true; //ustaw flagę sygnalizującą wgranie nowej godziny do układu } else {//jeżeli nie jesteśmy w trybie zmiany godziny change_time=true; //włącz tryb zmiany godziny getfromRTC=true; //ustaw flagę sygnalizującą konieczność odczytania godziny z układu do zmiennej lokalnej } } void plushour() {//obsługa przycisku 1 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu h_temp++; //zmień godzinę if(h_temp>23) {//zapewnia zapętlenie, 22<->23<->0<->1 h_temp=0; } } } void minushour() {//obsługa przycisku 2 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu h_temp--; //zmień godzinę if(h_temp<0) {//zapewnia zapętlenie, 22<->23<->0<->1 h_temp=23; } } } void plusminute() {//obsługa przycisku 3 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu m_temp++; //zmień godzinę if(m_temp>59) {//zapewnia zapętlenie, 58<->59<->0<->1 m_temp=0; } } } void minusminute() {//obsługa przycisku 4 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu m_temp--; //zmień godzinę if(m_temp<0) {//zapewnia zapętlenie, 58<->59<->0<->1 m_temp=59; } } } int SerialRead4Int() { char temp; short int val; while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { val=temp-'0'; val=val*10; } else { return -1; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { val+=(temp-'0'); val=val*10; } else { return -1; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { val+=(temp-'0'); val=val*10; } else { return -1; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { val+=(temp-'0'); } else { return -1; } return val; } tmElements_t SerialReadTime() { int temp,temp2; tmElements_t out; while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2=(temp-'0')*10; temp=0; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2+=temp-'0'; temp=0; } out.Hour=temp2; while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2=(temp-'0')*10; temp=0; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2+=temp-'0'; temp=0; } out.Minute=temp2; while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2=(temp-'0')*10; temp=0; } while(Serial.available()==0) {} temp=Serial.read(); if((temp>='0')&&(temp<='9')) { temp2+=temp-'0'; temp=0; } out.Second=temp2; return out; } void setup() {//deklaracje pinów, podpięcia przerwań pod piny przycisków DAYLIGHT_VALUE=EEPROMReadInt(0); if((DAYLIGHT_VALUE==65535)||(DAYLIGHT_VALUE==-1)) { DAYLIGHT_VALUE=850; EEPROMWriteInt(0, DAYLIGHT_VALUE); } NIGHTLIGHT_VALUE=EEPROMReadInt(2); if((NIGHTLIGHT_VALUE==65535)||(NIGHTLIGHT_VALUE==-1)) { NIGHTLIGHT_VALUE=600; EEPROMWriteInt(2, NIGHTLIGHT_VALUE); } NIGHTSTART=EEPROMReadInt(4); if((NIGHTSTART==65535)||(NIGHTSTART==-1)) { NIGHTSTART=2200; EEPROMWriteInt(4, NIGHTSTART); } NIGHTSTOP=EEPROMReadInt(6); if((NIGHTSTOP==65535)||(NIGHTSTOP==-1)) { NIGHTSTOP=700; EEPROMWriteInt(6, NIGHTSTOP); } pinMode(PHOTORES_PIN,INPUT); pinMode(LIGHT_PIN,OUTPUT); pinMode(HOUR_PIN,OUTPUT); pinMode(MIN_PIN,OUTPUT); pinMode(BUT1_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT1_PIN, &plushour, FALLING); pinMode(BUT2_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT2_PIN, &minushour, FALLING); pinMode(BUT3_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT3_PIN, &plusminute, FALLING); pinMode(BUT4_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT4_PIN, &minusminute, FALLING); pinMode(BUT5_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT5_PIN, &enabletimechange, FALLING); digitalWrite(LIGHT_PIN,HIGH); Serial.begin(115200); } void loop() { if(sendtime) { Serial.println(4); tmElements_t tm; if (RTC.read(tm)) { Serial.println(tm.Hour); Serial.println(tm.Minute); Serial.println(tm.Second); } else { Serial.println(-1); Serial.println(-1); Serial.println(-1); } delay(100); } if(getfromRTC) {//jeżeli należy pobrać godzinę z układu do zmiennej lokalnej getfromRTC=false; //reset flagi tmElements_t tm; RTC.read(tm); //pobranie godziny h_temp=tm.Hour; m_temp=tm.Minute; //podstawienie jej do zmiennych lokalnych } if(updatetoRTC) {//jeżeli należy wysłać nową godzinę do układu updatetoRTC=false; //reset flagi tmElements_t tm; tm.Hour=h_temp; tm.Minute=m_temp; //przygotowanie danych do wysłania RTC.write(tm); //wysłanie danych do układu } if(change_time) {//jesteśmy w trybie zmiany czasu digitalWrite(LIGHT_PIN,LOW); //zapal diodę - sygnalizacja trybu zmiany godziny show(h_temp,m_temp); //wyświetl tymczasową godzinę } else {//jeżeli jesteśmy w trybie wyświetlania czasu if(Serial.available()>0) { int recived=Serial.read(); switch(recived) { case '1': { Serial.println(1); Serial.println(DAYLIGHT_VALUE); Serial.println(NIGHTLIGHT_VALUE); Serial.println(NIGHTSTART); Serial.println(NIGHTSTOP); } break; case '2': { Serial.println(2); Serial.println(analogRead(PHOTORES_PIN)); } break; case '3': { while(Serial.available()==0) {} DAYLIGHT_VALUE=SerialRead4Int(); if(DAYLIGHT_VALUE!=EEPROMReadInt(0)) { EEPROMWriteInt(0, DAYLIGHT_VALUE); } NIGHTLIGHT_VALUE=SerialRead4Int(); if(NIGHTLIGHT_VALUE!=EEPROMReadInt(2)) { EEPROMWriteInt(2, NIGHTLIGHT_VALUE); } NIGHTSTART=SerialRead4Int(); if(NIGHTSTART!=EEPROMReadInt(4)) { EEPROMWriteInt(4, NIGHTSTART); } NIGHTSTOP=SerialRead4Int(); if(NIGHTSTOP!=EEPROMReadInt(6)) { EEPROMWriteInt(6, NIGHTSTOP); } } break; case '4': { sendtime=!sendtime; } break; case '5': { while(Serial.available()==0) {} tmElements_t tm=SerialReadTime(); RTC.write(tm); } break; } } update(); //wyświetl aktualną godzinę setlight(); //dostosuj podświetlenie } } void update(void) {//funkcja pobiera godzinę z RTC i przekazuje ją do funkcji show do wyświetlenia tmElements_t tm; if (RTC.read(tm)) { if(tm.Minute!=prev_tm.Minute) { show(tm.Hour,tm.Minute); prev_tm=tm; } } } void show(int h,int m) {//funkcja wyświetlająca godzinę h=h%12; h=h*10; h=h+(map(m,0,60,0,10)); h-=5; if(h<0) { h+=5; h=abs(h); h+=115; } analogWrite(HOUR_PIN,map(h,0,120,0,255)); analogWrite(MIN_PIN,map(m,0,60,0,255)); } void setlight(void) {//funkcja ustalająca podświetlenie zegarów tmElements_t tm; bool podswietl=false; if (RTC.read(tm)) { int akt=((tm.Hour)*100+tm.Minute); if((akt<NIGHTSTART)&&(akt>NIGHTSTOP)) { podswietl=true; } } int light=analogRead(PHOTORES_PIN); if((light<DAYLIGHT_VALUE)&&(podswietl)) {//jeżeli odczytana wartość jest poniżej progu zapalającego diody - wylicz wartość PWM i podaj ją na diody if(light<=NIGHTLIGHT_VALUE) { digitalWrite(LIGHT_PIN,LOW); } else { analogWrite(LIGHT_PIN,map(light,NIGHTLIGHT_VALUE,DAYLIGHT_VALUE,0,220)); } } else {//jeżeli wartość jest powyżej progu - wyłącz diody digitalWrite(LIGHT_PIN,HIGH); } } |
Trochę tego wyszło. Udostępniam także paczkę z plikiem exe, oraz plikami programu Microsoft Visual Studio mojej wersji programu (tak jak pisałem – programowałem w wersji 2013, także mogą występować jakieś problemy w przypadku uruchamiania rozwiązania w starszych wersjach programu Visual Studio).
Mam nadzieję, że podobał się Wam cały mój (niekrótki jednak, wbrew początkowym zapowiedziom) poradnik i jestem bardzo ciekawy Waszych komentarzy – może zapomniałem o jakiejś oczywistej funkcjonalności dla programu? Jeżeli ktoś na podstawie mojego opisu postanowi „popełnić” podobny projekt to czekam na fotki i informacje co ew. postanowił zmienić w moim (niedoskonałym, oczywiście) projekcie.
PS. Myślę, że to nie mój ostatni wpis na Majsterkowie, parę pomysłów już mi chodzi po głowie ;)
EDYCJA:
Odpowiadając na prośbę z komentarzy wrzucam filmik pokazujący działanie zegara. Na filmie zegar jest przyspieszony – jedna minuta trwa 750ms. W kodzie opublikowanym na stronie zegar reaguje natychmiast na zmianę natężenia światła – tutaj szybko pisałem kod przyspieszający zegar i reakcja jest opóźniona.
Jeżeli ktoś mi podpowie jak to zrobić to chętnie ostatnie dwie sekcje kodu wrzuciłbym w rozwijalną sekcję więcej… bo trochę ten kod ma, a duża jego część już jest powyżej.
Wystarczy tutaj wpisać maksymalną wysokość bloku z kodem: http://screen.majsterkowo.pl/2013-09-29_193644.png :)
Niniejszym oświadczam, że nie doceniałem pracy programistów.
Do oświadczenia dołączam najszczersze wyrazy podziwu i uznania przeplatane szokiem i niedowierzaniem.
Chylę czoła! :)
Projekt zacny, trzymam kciuki za twoją wygraną w tym miesiącu.
Przejściówkę zamówiłeś z chińskiego ebaya czy z allegro??
z Allegro – zależało mi na czasie ;)
ale tak jak napisałem – na Allegro dostępne są identyczne przejściówki (ktoś po prostu zamawia po 100 sztuk i rozprowadza z trzykrotną przebitką w kraju)
Dzięki.
Czy dało by radę umieścić filmik z pracy zegara?
Popieram – też chciałbym zobaczyć ten zegar w akcji :)
Postaram się jutro coś wrzucić, skoro taka wola czytelników ;)
Proszę bardzo.
Cześć,
“Postanowiłem skorzystać z programu Microsoft Visual Studio 2013 (dostępna jest w Internecie bezpłatna, 30 dniowa wersja próbna o pełnej funkcjonalności)”. Skąd taki pomysł? Dlaczego nie Visual Studio Express?
Taką wersję miałem zainstalowaną, wcześniej używałem jej do innych celów.
a gdzie godziny???
;P 5
Nie zostały Ci gdzieś przypadkiem gotowe do wydrukowania tarcze do naklejenia na zegar minuty i godziny?
Fajny opis. Zacząłem powoli składać swoją kopię.
Muszę jeszcze zwalczyć problem z biblioteką EEPROM. Niby powinna być w standardzie, a mój IDE 1.6.4 twierdzi, że jej brak. Nie ma za bardzo jak ją dodać ręcznie, a upgrade do 1.6.5 generuje inne błędy. Mam nadzieję, że problem rozwiążę i wszystko ruszy.
Miałem zmieścić na ATmega88P, ale po dodaniu funkcjonalności komunikacji z komputerem już widać, że zmiana na 328 będzie konieczna ;)
Fajny projekt. Sebastian mam dla Ciebie propozycje prosze o kontatk na: andrzej.drozd1@o2.pl