// Logovaci automat pro 2 tlacitka s ATtiny85, EEPROM a RTC obvodem //======================================================================== // Verze: 2017-12-28 // // Velikost prelozeneho programu: 3052 B (37%) // // Zapojeni: //============= // ATtiny85 // +--\__/--+ // (RESET) - PB5 1| |8 Vcc - napajeni (4,5V baterie) // Tlacitko 1 - PB3 2| |7 PB2 - SCL - propojeni na RTC a EEPROM // Signalizacni LED proti Vcc + Tlacitko 2 - PB4 3| |6 PB1 - zapinani napajeni do RTC a do EEPROM // GND 4| |5 PB0 - SDA - propojeni na RTC a EEPROM // +--------+ // // // 24LC1026 (EEPROM) // +--\__/--+ // (GND = "0") NC 1| |8 Vcc2 = PB1 od ATtiny // (GND = "0") A1 2| |7 WP (GND = "0") // (GND = "0") A2 3| |6 SCL // GND 4| |5 SDA // +--------+ // // // DS1672 (RTC) // +--\__/--+ // X1(krystal 32768Hz) 1| |8 Vcc2 = PB1 od ATtiny // X2 2| |7 /RST (nepouzito) // V_backup (baterie 3V) 3| |6 SCL // GND 4| |5 SDA // +--------+ // // LM92 (teplomer) // +--\__/--+ // SDA 1| |8 Vcc2 = PB1 od ATtiny // SCL 2| |7 A0 (Vcc2 = "1") // T_Crit_A (nezapojeno) 3| |6 A1 (Vcc2 = "1") // GND 4| |5 INT (nezapojeno) // +--------+ // // // SDA a SCL potrebuje vnejsi Pull-Up odpory (12k) // Tlacitka spinaji proti GND a tlacitko 2 ma v serii navic odpr 1k (kvuli oboustranne funkci pinu PB4) //======================================================================================================================================= #include // knihovna pro praci s I2C pro ATtiny85 // https://playground.arduino.cc/Code/USIi2c // https://playground.arduino.cc/uploads/Code/TinyWireM.zip #include // knihovna pro probouzeni ATtiny zmenou stavu pinu #include // knihovna pro sleep rezim byte i2c_eeprom; // I2C adresa pametove banky v externi EEPROM (0x50, nebo 0x51 pri obou adresovacich pinech v LOW) byte i2c_RTC = 0x68; // I2C adresa RTC obvodu byte data_eeprom; // pomocna promenna pro obsah bunky v EEPROM unsigned long free_addr; // ukazatel na prvni prazdnou bunku v EEPROM, do ktere se bude ukladat dalsi zaznam byte pole_RTC[5]; // pole, do ktereho se pripravi aktualni obsah RTC (4 registry s casem a jeden registr se stavem generatoru) byte tep8b_cast; // hlavni cast teploty (rozsah 0.00'C az 31.75'C bez znamenka s rozlisenim 0.25'C) byte tep_dopl; // doplneni pro teplotu (nejnizsi 2 bity od teploty - to je rozliseni 0,0625'C a znamenko poskladane do formatu 0bxxZddxxx) //============================================================================================================ void setup() { pinMode(3,INPUT_PULLUP); // tlacitko 1 je vzdycky jako vstup pinMode(4,OUTPUT); // tlacitko 2 je na zacatku nastavene jako vystup kvuli signalizaci volneho prostoru v EEPROM, nebo signalizaci "Servisniho rezimu" digitalWrite(4,HIGH); // zhasnuti signalizacni LED pinMode(1,OUTPUT); // rizeni napajeni pro RTC a EEPROM digitalWrite(1,HIGH); // zapnuti napajeni pro RTC a EEPROM delay(2000); // kratka pauza pro moznost vstupu do "Servisniho rezimu" TinyWireM.begin(); // start I2C komunikace s EEPROM a RTC // ------------------ Servisni rezim ------------------------ if (digitalRead(3) == LOW) // kdyz bude 2 sekundy po zapnuti napajeni (nebo po resetu) stisknute tlacitko 1, prejde se do "Servisniho rezimu" { // Servisni rezim slouzi jen k nastaveni I2C pinu do vstupniho stavu, aby bylo mozne stahnout data pres externi I2C ctecku pinMode(0,INPUT); // pin ma v normalnim rezimu funkci I2C-SDA, ted se ale nastavi na vstup s vysokou impedanci pinMode(2,INPUT); // pin ma v normalnim rezimu funkci I2C-SCL, ted se ale nastavi na vstup s vysokou impedanci pinMode(1,INPUT); // pin ma v normalnim rezimu funkci zapinani napajeni k RTC a EEPROM, ted se ale nastavi na vstup s vysokou impedanci while (true) // tady se procesor zacykli v nekonecne smycce, ze ktere je mozne vystoupit pouze resetem { for (byte i = 0; i<30 ; i++) // 15 sekund po vstupu do servisniho rezimu blika LED (odber je asi 7mA) { kratky_blik(); // kratke blikani 2Hz delay(460); } sleep(); // po 15 sekundach se ATtiny uspi, takze v servisnim rezimu se neodebira zadny proud z baterie. // Komunikaci vcetne napajeni RTC a EEPROM zajistuje ctecka. // Pri probuzeni ATtiny nejakym tlacitkem se pokracuje v dalsim 15-sekundovem blikani } } // ------------------ konec servisniho rezimu --------------- // Kdyz tlacitko 1 nebude pri startu stisknute, spusti se "Normalni rezim". // Nejdriv se najde prvni volna pametova bunka v EEPROM pro zaznam dat // Kazdy zaznam je dlouhy 6 bajtu, prvni zacina na adrese 10. // Obsazeny prostor ma v nevyssich 2 bitech prvniho bajtu sekvenci "0b10xxxxxx". // Pokud je na techto bitech cokoliv jineho, znamena to prazdne misto. for (free_addr = 10 ; free_addr < 131070 ; free_addr = free_addr + 6) { data_eeprom = cti_eeprom(free_addr); if ((data_eeprom & 0b11000000) != 0b10000000) break; if ((free_addr % 1800) == 0) // kazdych 1800 bajtu (300 vyhledavacich cyklu) blikne kratce LEDkou na PB4 { kratky_blik(); } } delay (1000); // odliseni kratkeho blikani LED pri hledani volneho mista a vyblikani informace o obsazenem prostoru blik(free_addr / 13107); // blikne min. 1x az max. 10x podle obsazeneho prostoru v EEPROM delay(500); pinMode(4,INPUT_PULLUP); // pred uspanim se nastavi pin s tlacitkem 2 na vstup } //============================================================================================================ // Zacatek hlavni smycky void loop() { digitalWrite(1,LOW); // vypnuti napajeni pro RTC a EEPROM delay(100); // a kratka pauza pred uspanim sleep(); // uspani do doby, nez dojde k impulzu na pinu PB3 nebo PB4 digitalWrite(1,HIGH); // zapnuti napajeni pro RTC a EEPROM delay(50); // po probuzeni chvili pockat, aby se odrusily zakmity tlacitek a bylo mozno zjistit pripadne sepnuti obou tlacitek zaroven // po probuzeni zjistit aktualni stav dvou tlacitek a na nejvyssi bit hodit znacku, ktera zajisti informaci o obsazene pametove bunce v EEPROM byte tlacitka = (0b10000000 | ((PINB & 0b00011000) >> 3)); // vysledkem bude: // cislo 131 (0b10000011) pro obe rozepnuta tlacitka (probouzeci impulz byl kratsi nez 50ms ) // cislo 130 (0b10000010) pro sepnute tlacitko 1 (PB3) // cislo 129 (0b10000001) pro sepnute tlacitko 2 (PB4) // cislo 128 (0b10000000) pro obe sepnuta tlacitka if (tlacitka != 131) // nejake tlacitko je sepnute ... { pinMode(4,OUTPUT); // kvuli bliknuti (informace o ukladani do pameti) se na chvili prepne pin PB4 na vystup kratky_blik(); // kratce se blikne LEDkou (signalizace, ze bylo stisknuto tlacitko) delay(1000); // 1 sekunda je nutna pauza mezi zapnutim napajeni RTC a EEPROM a zacatkem I2C komunikace cti_RTC(); // Precti do promenne pole_RTC[] vsech 5 RTC registru cti_teplotu(); // precte vnitrni teplotu ATtiny85 if (pole_RTC[4] != 0) // Tento registr v RTC by mel mit hodnotu 0 { // Pokud je ale tento registr nastaveny na neco jineho, znamena to, ze hodiny stoji (asi doslo k vypadku zalozniho napajeni). run_RTC(); // Cas sice uz neni spravny, ale provede se alepon pokus o rozbehnuti hodin nastavenim registru zpatky na hodnotu 0 } tlacitka = (pole_RTC[4] >> 5) | tlacitka ; // priprava 1.bajtu noveho zaznamu: 0b10000syx (s... stav RTC; y... tlacitko 2; x... tlacitko 1) tlacitka = tlacitka | tep_dopl; // do 1. bajtu se jeste pridaji doplnujici informace o teplote // ulozeni do EEPROM zapis_eeprom(free_addr, tlacitka); // stav RTC, stav tlacitek a znacka, ze je pamet obsazena (dohromady je to tzv. "stavovy bajt") zapis_eeprom(free_addr + 1, pole_RTC[0]); // LSB z RTC casu zapis_eeprom(free_addr + 2, pole_RTC[1]); zapis_eeprom(free_addr + 3, pole_RTC[2]); zapis_eeprom(free_addr + 4, pole_RTC[3]); // MSB z RTC casu zapis_eeprom(free_addr + 5, tep8b_cast); // hlavni 8-bitova cast zmerene teploty 0 az 31.75'C bez znamenka s rozlisenim 0.25'C free_addr = free_addr +6UL; // ukazatel na prvni volnou adresu v EEPROM se posune o 6 bajtu dal data_eeprom = cti_eeprom(free_addr); // Precti "stavovy bajt" (prvni bajt v prostoru pro dalsi zaznam) zapis_eeprom(free_addr, (data_eeprom & 0b00111111)); // ... a zapis ho zpatky s vymazanymi nejvyssimi dvema bity. // Stara data tedy v EEPROM zustavaji a je mozne je v pripade potreby obnovit // - pokud jeste nebyla kompletne prepsana dalsim zaznamem kratky_blik(); // kratce se blikne LEDkou (signalizace, ze byla data ulozena ) pinMode(4,INPUT_PULLUP); // a po bliknuti se prepne pin zpatky na funkci tlacitka delay(3000); // po dobu 3 sekund se bude ignorovat dalsi spinani tlacitek } } // konec hlavni smycky //============================================================================================================ void blik(byte pocet) { for (byte i=0; i< pocet+1; i++) { digitalWrite(4,LOW); delay (200); digitalWrite(4,HIGH); delay (400); } } void kratky_blik(void) { digitalWrite(4,LOW); delay (10); digitalWrite(4,HIGH); delay (30); } //------------------------------------------------------------- // precteni jednoho bajtu z EEPROM (adresa v EEPROM je v rozsahu 0 az 131071) // vytvoreno specialne pro pamet 24LC1026 ( http://ww1.microchip.com/downloads/en/DeviceDoc/22270A.pdf ) byte cti_eeprom(unsigned long adresa_eeprom) { if (adresa_eeprom > 0xFFFF) // pro adresu vetsi, nez 65535 se musi prepnout pametova banka { i2c_eeprom = 0x51; adresa_eeprom = adresa_eeprom & 0xFFFF; } else { i2c_eeprom = 0x50; } byte adr_high = adresa_eeprom >> 8; byte adr_low = adresa_eeprom & 0xFF; TinyWireM.beginTransmission(i2c_eeprom); TinyWireM.send(adr_high); TinyWireM.send(adr_low); TinyWireM.endTransmission(); delay(5); TinyWireM.requestFrom(i2c_eeprom,1); data_eeprom = TinyWireM.receive(); return data_eeprom; } //------------------------------------------------------------- // zapis jednoho bajtu do EEPROM void zapis_eeprom(unsigned long adresa_eeprom, byte hodnota) { if (adresa_eeprom > 0xFFFF) // pro adresu vetsi, nez 65535 se musi prepnout pametova banka { i2c_eeprom = 0x51; adresa_eeprom = adresa_eeprom & 0xFFFF; } else { i2c_eeprom = 0x50; } byte adr_high = adresa_eeprom >> 8; byte adr_low = adresa_eeprom & 0xFF; TinyWireM.beginTransmission(i2c_eeprom); TinyWireM.send(adr_high); TinyWireM.send(adr_low); TinyWireM.send(hodnota); TinyWireM.endTransmission(); delay(5); } //------------------------------------------------------------- // precteni 5 bajtu z RTC (4x casove registry + 1x stav RTC) // vytvoreno specialne pro RTC DS1672 ( https://datasheets.maximintegrated.com/en/ds/DS1672.pdf ) void cti_RTC(void) { TinyWireM.beginTransmission(i2c_RTC); TinyWireM.send(0); TinyWireM.endTransmission(); delay(20); TinyWireM.requestFrom(i2c_RTC,5); // bude se cist 5 registru delay(20); pole_RTC[0] = TinyWireM.receive(); // LSB delay(20); pole_RTC[1] = TinyWireM.receive(); delay(20); pole_RTC[2] = TinyWireM.receive(); delay(20); pole_RTC[3] = TinyWireM.receive(); // MSB delay(20); pole_RTC[4] = TinyWireM.receive(); // test, jestli RTC bezi (spravne by tu mela byt hodnota 0. Kdyz je tu 128, generator stoji) delay(20); } //------------------------------------------------------------- // Zapis hodnoty 0 do registru c.4 v RTC obvodu // (pokus o spusteni zastaveneho generatoru) void run_RTC(void) { TinyWireM.beginTransmission(i2c_RTC); TinyWireM.send(4); // do registru 4 TinyWireM.send(0); // uloz hodnotu 0 TinyWireM.endTransmission(); delay(10); } //------------------------------------------------------------- // mereni teploty pomoci LM92 a prevod na 9-bitove cislo + znamenko void cti_teplotu(void) { TinyWireM.beginTransmission(0x4B); TinyWireM.send(0x00); TinyWireM.endTransmission(); delay(20); TinyWireM.requestFrom(0x4B, 2); byte high_tep = TinyWireM.receive(); byte low_tep = TinyWireM.receive(); unsigned int tep16b = (high_tep << 8) | low_tep; tep16b = tep16b >> 3; // odseknuti stavovych bitu na nejnizsich 3 pozicich // (teplota ma ted 12 bitu + znamenko) s rozlisenim 1LSB = 0.0625'C tep8b_cast = tep16b >> 2; // oo=znacka obsazeneho prostoru; // Z=znamenko teploty; // dd=nejnizsi desetiny teploty; // R=stav RTC; // a=tlac1; // b=tlac2 // ooZddRab tep_dopl = (tep16b & 0b0001000000000000) >> 7 ; // znamenko na bit5 tep_dopl = tep_dopl | ((tep16b & 0b0000000000000011) << 3); // nejnizsi bity od teploty } //------------------------------------------------------------- // Podprogram pro signalizaci jednoho bajtu pomoci LED // Rozlozi cislo (0 az 9999) na jednotlive desitkove // rady a ty pak vyblika pomoci LED. Ke kazdemu radu pricte +1. // Priklady: // pro cislo 2981 blikne v sekvenci 3 - 10 - 9 - 2 // pro cislo 153 blikne v sekvenci 1 - 2 - 6 - 4 // pro cislo 18 blikne v sekvenci 1 - 1 - 2 - 9 // pro cislo 190 blikne v sekvenci 1 - 2 - 10 - 1 void blik_cislo (unsigned int cislo) { unsigned int pomprom; if (cislo >999) { pomprom = cislo / 1000; blik (pomprom); cislo = cislo - (1000* pomprom); } else { blik (0); } delay(1500); if (cislo >99) { pomprom = cislo / 100; blik (pomprom); cislo = cislo - (100* pomprom); } else { blik (0); } delay(1500); if (cislo > 9) { pomprom = cislo / 10; blik (pomprom); cislo = cislo - (10 * pomprom); } else { blik (0); } delay(1500); blik (cislo); } // =================================================================== // Podprogram pro uspani a opetovne probuzeni ATtiny85 // Opsano ze stranek : // https://bigdanzblog.wordpress.com/2014/08/10/attiny85-wake-from-sleep-on-pin-state-change-code-example/ void sleep() { GIMSK |= _BV(PCIE); // Enable Pin Change Interrupts PCMSK |= _BV(PCINT3); // Use PB3 as interrupt pin PCMSK |= _BV(PCINT4); // Use PB4 as interrupt pin ADCSRA &= ~_BV(ADEN); // ADC off set_sleep_mode(SLEEP_MODE_PWR_DOWN); // replaces above statement sleep_enable(); // Sets the Sleep Enable bit in the MCUCR Register (SE BIT) sei(); // Enable interrupts sleep_cpu(); // sleep cli(); // Disable interrupts PCMSK &= ~_BV(PCINT3); // Turn off PB3 as interrupt pin PCMSK &= ~_BV(PCINT4); // Turn off PB4 as interrupt pin sleep_disable(); // Clear SE bit ADCSRA |= _BV(ADEN); // ADC on sei(); // Enable interrupts } // sleep ISR(PCINT0_vect) { // This is called when the interrupt occurs, but I don't need to do anything in it }