//============================================ // Ovladani serva pomoci napetoveho vstupu // verze 8 // (19.1.2019) //============================================ // // // Zapojeni: //============= // ATtiny13 // +-\_/-+ // nezapojeno (RESET) - PB5 1| |8 Vcc // tlacitko pro simulaci MIN - PB3 2| |7 PB2 (ADC1) - vstupni napeti pro ovladani serva // pomocny trimr pro nastaveni uhlu v servisnim rezimu - (ADC2) PB4 3| |6 PB1 - tlacitko pro simulaci MAX // GND 4| |5 PB0 - ovladani ridiciho pinu serva // +-----+ // // // nastaveni FUSE bajtu: //======================== // LowFUSE : 0x7A nebo 0x3A (nastaven bit EESAVE = nemazat EEPROM pri nahravani programu) // HighFUSE : 0xFF // // velikost prelozeneho programu 942 Bajtu (vyuzito 91% pameti) // // // // KONFIGURACE PREVODU // ----------------------- // Prevod z napeti na uhel je mozne nastavit rucne pomoci prvnich 4 bajtu v EEPROM // // adresa 0 ..... minimalni uhel natoceni serva (0 az 254 pro -90 stupnu az +89 stupnu); jedno cislo je tedy asi 0.7 stupnu // adresa 1 ..... napeti, pri kterem bude servo natoceno na minimalni uhel (0 az 255 pro 0V az Vcc); pri napajeni 5V je tedy jedno cislo asi 20mV // pokud bude na vstupu nizsi napeti, zustane servo natocene na minimalni uhel // adresa 2 ..... maximalni uhel natoceni serva ([adresa 0]+1 az 255 pro -89 stupnu az +90 stupnu); jedno cislo je tedy asi 0.7 stupnu // je treba zajistit, ze maximalni uhel bude vzdycky vetsi, nez minimalni! // adresa 3 ..... napeti, pri kterem bude servo natoceno na maximalni uhel (0 az 255 pro 0V az Vcc); pri napajeni 5V je jedno cislo asi 20mV // pokud bude na vstupu vyssi napeti, zustane servo natocene na maximalni uhel // pokud bude toto cislo vyssi, nez cislo na adrese 1, bude se servo natacet opacnym smerem (pri zvysovani napeti se bude natacet k minimalnimu dorazu) // // // Pomoci tlacitek je mozne simulovat nataceni serva k minimalnimu a maximalnimu nastavnemu uhlu. // // // SERVISNI REZIM // ------------------ // Pokud je pri startu napajeni stisknute tlacitko MIN, je mozne pomocnym trimrem natocit servo do pozadovaneho uhlu pro MIN bod. // Na hlavnim napetovem vstupu (PB2) v tu dobu musi byt takove napeti, ktere bude pozadovano pro natoceni na MIN uhel. // Pri uvolneni tlacitka dojde k zapisu nastavenych parametru pro MIN bod do EEPROM. // // Pokud je pri startu napajeni stisknute tlacitko MAX, je mozne pomocnym trimrem natocit servo do pozadovaneho uhlu pro MAX bod. // Na hlavnim napetovem vstupu (PB2) v tu dobu musi byt takove napeti, ktere bude pozadovano pro natoceni na MAX uhel. // Pri uvolneni tlacitka dojde k zapisu nastavenych parametru pro MAX bod do EEPROM. // // Zmena proti verzi 7: // Novy prekladac nedokaze pouzivat funkci "delayMicroseconds()" s parametrem jako promenna. Nove musi byt pouzita konstanta. // Z toho duvodu byla tato pauza nahrazena smyckou "for". Protoze ale samotna smycka trva srovnatelnou dobu, musela byt pauza zkracena. // Nakonec se experimentalne zjistilo, ze puvodni: // --------------------------------------- // delayMicroseconds(mikropauza) // --------------------------------------- // // je mozne celkem presne nahradit casovaci smyckou: // --------------------------------------- // for (int mp = 0 ; mp < (mikropauza/4) ; mp++) // { // delayMicroseconds(2); // } // --------------------------------------- // //============================================================================================================= #define F_CPU 9600000 // nastaveni vnitrni frekvence procesoru na 9.6 MHz #define __DELAY_BACKWARD_COMPATIBLE__ #include // deklarace pouzitych promennych byte uhel_min; // minimalni uhel natoceni serva 0-254 (po prepoctu odpovida asi -90 az +89 stupnu) (!!!!!) byte napeti_min; // napeti na vstupu pro minimalni vychylku (0V az Vcc) byte uhel_max; // maximalni uhel natoceni serva (uhel_min + 1) az 255 (po prepoctu odpovida asi -89 az +90 stupnu) (!!!!!) byte napeti_max; // napeti na vstupu pro maximalni vychylku (0V az Vcc) // (!!!!!) MUSI BYT ZARUCENO, ZE "uhel_min" JE MENSI, NEZ "uhel_max" (!!!!!) // v pripade, ze tato podminka neplati, dojde k resetu do defaultniho nastaveni byte reverze; // 0= pri stoupajicim napeti se zvetsuje uhel natoceni; 1= pri stoupajicim napeti klesa uhel natoceni byte vstup; // merene napeti (napeti na A/D prevodniku - pin PB2 = ADC1) byte n; // pomocna promenna pro prepocet pozadovaneho uhlu natoceni serva na sirku PWM impulzu unsigned int mikropauza; // sirka HIGH impulzu pri generovani PWM (500us az 2500us), prepoctena z 0 az 255 kroku // **************** P O D P R O G R A M Y ****************** // podprogram pro cteni analogove hodnoty z nastaveneho A/D prevodniku byte adc_read (byte kanal) { ADMUX = 0b00100000 | kanal; // zakladni nastaveni A/D prevodniku - ADLAR na "1" (=nejvyssich 8 bitu je pohromade) a nastaveni prislusneho vstupu ADCSRA |= 0b01000000; // Start bit se nastavi do "1" while (ADCSRA & 0b01000000); // cekani na ukonceni prevodu (bit ADSC v registru ADSRA padne do "0") return ADCH; // V ADCH je 8 nejvyssich bitu z namerene hodnoty } // podprogram pro cteni dat z EEPROM byte eeprom_read(byte adresa) { while (EECR & 0b00000010); // cekani na dokonceni predchozi operace (nez bit EEPE v registru EECR padne do '0') EEARL = adresa; // do registru EEARL ulozit adresu (0 az 63) EECR = 0b00000001; // bit EERE (Read Enable) nastavit na '1' return EEDR; // v registru EEDR se nachazi obsah pametove bunky ze zadane adresy } // podprogram pro zapis dat do EEPROM void eeprom_write(byte adresa,byte data) { while (EECR & 0b00000010); // cekani na dokonceni predchozi operace (nez bit EEPE v registru EECR padne do '0') EECR = 0b00000000; // nastavit rezim zapisu na 'atomic' (smazat a zapsat) EEARL = adresa; // do registru EEARL ulozit adresu (0 az 63) EEDR = data; // do registru EEDR ulozit jednobajtovou hodnotu EECR = 0b00000100; // bit EEMPE (Master Program Enable) nastavit do '1' (povoleni zapisu) EECR |= 0b00000010; // k tomu jeste bit EEPE nastavit do '1' - tim zacne zapis. // (je treba ty dva bity EEMPE a EEPE nastavovat postupne) } // podprogram pro generovani jednoho impulzu do serva s presne vypoctenou delkou trvani logicke "1" void pwm_impulz(void) { mikropauza = ((69 * n) + 3200) / 8; // prepocet delky trvani impulzu na 400 az 2600 mikrosekund pro krajni polohy n=0 az n=255 // funkce "ZRCADLO" se zapne zakomentovanim nasledujici radky // prohozeni delky trvani pulzu z intervalu [400us az 2600us] na interval [2600us az 400us] mikropauza = 3000 - mikropauza; // celkove zrcadleni krajnich poloh serva (prohozeni leve a prave strany) PORTB |= 0b00000001; // PWM vystup na PB0 do HIGH, ostatni bity beze zmeny for (int mp = 0 ; mp < (mikropauza/4) ; mp++) { delayMicroseconds(2); // pauza v mikrosekundach } PORTB &= 0b11111110; // PWM vystup na PB0 do LOW, ostatni bity beze zmeny for (int mp = 0 ; mp < ((19000 - mikropauza)/4) ; mp++) { delayMicroseconds(2); // pauza v mikrosekundach } } // **************** Z A C A T E K P R O G R A M U ****************** void setup (void) { delay(50); // chvilku pauza po zapnuti napajeni DDRB = 0b00000001; // nastaveni smeru signalu na portu B ("1" = vystup ; "0" = vstup) - PB0=PWM rizeni serva PORTB = 0b00001010; // Pull-Upy na tlacitka ADCSRA = 0b10000111; // ADC enable bit na "1" a nastaveni delice frekvence pro ADC na 1:128 (=75kHz) uhel_min = eeprom_read(0); // nacteni minimalniho uhlu 0 az "uhel_max"-1 uhel_max = eeprom_read(2); // nacteni maximalniho uhlu "uhel_min"+1 az 255 // ------------------ vstup do servisniho rezimu ----------------- byte bude_save = 0; // nastaveni znacky, ze se nebude ukladat do EEPROM while ((PINB & 0b00001010) == 0b00000010) // kdyz je po resetu (po zapnuti napajeni) stisknute jen tlacitko MIN, vstup do servisniho rezimu pro nastaveni MIN bodu { n = adc_read(2); // zjisteni napeti na pomocnem trimru if (n >= uhel_max) { n = uhel_max - 1 ; // zajisteni podminky, ze "uhel_min" musi byt v kazdem pripade mensi, nez "uhel_max" } pwm_impulz(); // vygenerovani impulzu do serva podle nastaveni pomocneho trimru bude_save = 1; // pri opousteni servisniho rezimu se budou ukladat parametry pro MIN bod do EEPROM } // pri uvolneni tlacitka program pokracuje ulozenim nastavenych parametru pro bod MIN do EEPROM while ((PINB & 0b00001010) == 0b00001000) // kdyz je po resetu (po zapnuti napajeni) stisknute jen tlacitko MAX, vstup do servisniho rezimu pro nastaveni MAX bodu { n = adc_read(2); // zjisteni napeti na pomocnem trimru if (n <= uhel_min) { n = uhel_min + 1 ; // zajisteni podminky, ze "uhel_max" musi byt v kazdem pripade vetsi, nez "uhel_min" } pwm_impulz(); // vygenerovani impulzu do serva podle nastaveni pomocneho trimru bude_save = 2; // pri opousteni servisniho rezimu se budou ukladat parametry pro MAX bod do EEPROM } // pri uvolneni tlacitka program pokracuje ulozenim nastavenych parametru pro bod MAX do EEPROM if (bude_save == 1) // kdyz je nastavena znacka, ze se maji ukladat parametry pro MIN bod do EEPROM { eeprom_write(0, n ); // ulozi se aktualni hodnota natoceni pomocneho trimru na adresu 0 eeprom_write(1,adc_read(1)); // a zaroven aktualni hodnota hlavniho napetoveho vstupu na adresu 1 } if (bude_save == 2) // kdyz je nastavena znacka, ze se maji ukladat parametry pro MAX bod do EEPROM { eeprom_write(2, n ); // ulozi se aktualni hodnota natoceni pomocneho trimru na adresu 2 eeprom_write(3,adc_read(1)); // a zaroven aktualni hodnota hlavniho napetoveho vstupu na adresu 3 } // --------------- konec servisniho rezimu ------------------- // pri startu se nactou hodnoty z EEPROM uhel_min = eeprom_read(0); // nacteni minimalniho uhlu 0-255 napeti_min = eeprom_read(1); // nacteni napeti, pri kterem se ma nastavit minimalni uhel 0-255 uhel_max = eeprom_read(2); // nacteni maximalniho uhlu 0-255 napeti_max = eeprom_read(3); // nacteni napeti, pri kterem se ma nastavit maximalni uhel 0-255 // Pri prvnim spusteni je EEPROM vymazana, takze jsou vsechny bunky nastavene na 0xFF // Kvuli vypoctum se nesmi stat, aby "uhel_min" byl stejny (nebo vetsi), nez "uhel_max". // Pokud by se to stalo, nastavi se defaultni hodnoty techto uhlu na -90 a +90 stupnu // a rozsah napeti na 0V az Vcc if (uhel_min >= uhel_max) { // defaultni nastaveni promennych uhel_min = 0; // minimalni uhel bude -90 stupnu napeti_min = 0; // napeti pro minimalni uhel bude 0V uhel_max = 255; // maximalni uhel bude maximum (+90 stupnu) napeti_max = 255; // napeti pro maximalni uhel bude Vcc (5V) // prvotni zapis techto promennych do EEPROM eeprom_write(0,uhel_min); eeprom_write(1,napeti_min); eeprom_write(2,uhel_max); eeprom_write(3,napeti_max); } if (napeti_min < napeti_max) // kdyz jsou v EEPROM nastaveny hodnoty napeti tak, ze napeti pro "uhel_min" je mensi, nez napeti pro "uhel_max" ... { reverze = 0; // ... nic se nedeje, vsechny vypocty se provadeji normalne } else // kdyz je ale napeti pro "uhel_min" vetsi, nez napeti pro "uhel_max", dochazi k reverzaci smeru otaceni { napeti_min = eeprom_read(3); // kvuli vypoctum se minimalni a maximalni napeti prohodi napeti_max = eeprom_read(1); reverze = 1; // a nastavi se znacka pro reverzni prepocet } } // **************** N E K O N E C N A S M Y C K A ****************** void loop() { if ((PINB & 0b00001010) == 0b00001010) // kdyz neni stisknute zadne tlacitko (normalni rezim) ... { vstup = adc_read(1); // zjisteni 8-bitove hodnoty z A/D prevodniku na pinu PB2 (=ADC1) if ( (vstup >= napeti_min) && (vstup <= napeti_max)) // vstupni napeti je v povolenych mezich { byte procnap = ((unsigned int)255 * (vstup - napeti_min)) / (napeti_max - napeti_min) ; // v kolika % povoleneho rozsahu napeti je aktualni napeti (100 % = cislo 255) if (reverze == 1) // kdyz je nastavena reverzni znacka, bude se servo otacet opacnym smerem - pri zvysovani napeti k minimalnimu dorazu { procnap = 255 - procnap; // "procnap" se prevede na doplnek do 100% (100%=255) } n = (((unsigned int)procnap * (uhel_max - uhel_min)) / 255) + uhel_min ; // prepocet na pozadovany uhel natoceni 0 az 255 } if (vstup < napeti_min) // kdyz je vstupni napeti nizsi, nez minimalni povolene, { if (reverze == 0) // a kdyz neni reverzni rezim, tak pod hodnotou minimalniho napeti ... { n = uhel_min; // ... zustane servo natocene na minimalni uhel } else // pri reverznim rezimu bude pod hodnotou minimalniho napeti ... { n = uhel_max; // ... servo natocene na maximalni uhel } } if (vstup > napeti_max) // kdyz je vstupni napeti vyssi, nez maximalni povolene { if (reverze == 0) // a kdyz neni reverzni rezim, tak nad hodnotou maximalniho napeti ... { n = uhel_max; // ... zustane servo natocene na maximalni uhel } else // pri reverznim rezimu bude nad hodnotou maximalniho napeti ... { n = uhel_min; // ... servo natocene na minimalni uhel } } pwm_impulz(); } if ((PINB & 0b00001010) == 0b00000010) // kdyz je stisknute jen tlacitko "MIN" ... { n = uhel_min; // vypocet mikropauzy bude provaden pro minimalni uhel natoceni serva pwm_impulz(); } if ((PINB & 0b00001010) == 0b00001000) // kdyz je stisknute jen tlacitko "MAX" ... { n = uhel_max; // vypocet mikropauzy bude provaden pro maximalni uhel natoceni serva pwm_impulz(); } }