Ovládání ventilátoru teplovzdušného rozvodu

Z MAM wiki

Přejít na: navigace, hledání

Tato stránka se věnuje řešení semestrální práce Ovládání ventilátoru teplovzdušného rozvodu, kterou vytvořili studenti Bc.Miroslav Knedla a Bc.Marek Václavík během letního semestru roku 2012. po Kristu.

Obsah

[editovat] Zadání

Ventilátor 220 V/100 W má být spínán při nárůstu teploty nad nastavenou mez, čidlem je libovolný termistor nebo PN přechod, vypnut při poklesu pod vypínací teplotu určenou požadovanou hysterezí cca 10 K pod zapínací teplotou. Ke spínání použijte optotriak, nejlépe spínaný v nule. Snažte se o minimalizaci spotřeby alespoň v době, kdy triak nespíná.

[editovat] Rešerše dostupných prací

Bohužel až po navržení schématu jsme objevili práci věnovanou stejnému tématu uveřejněnou na stránce Petra Vičara. Bohužel odkazy na zapojení, které s kolegou použil, jsou již neplatné a nemůžeme tedy přesně ověřit, jak se naše zapojení od jejich liší. Podle nepřímých důkazů se zdá, že pro zobrazování na displejích použili dvojici dekodérů BCD -> 7segment, což je rozdíl oproti našemu řešení, kdy budíme dispeje v multiplexovaném režimu přímo pomocí portu procesoru. Oproti návrhu kolegů tak ušetříme dva integrované obvody. To by nebylo reálné, kdybychom potřebovali k procesoru připojit více periferí, ale to v tomto případě nehrozí a piny použité pro buzení displeje nám chybět nebudou. Použít jiný snímací prvek než lineární senzor nebo jiný princip "čtení" teploty než AD převodník nám nepřijde praktické, proto další vylepšení naší konstrukce spočívá již jen v detekování dlouhého stisku tlačítka a zrychlené ink/dekrementaci nastavené teploty, pokud k němu dojde...

[editovat] Schéma zapojení

Soubor:MAM Semestralka res.png

[editovat] Popis zapojení

Jako teplotní čidlo využíváme senzor LM335 pracující v rozsahu teplot -40°C až 100°C. Jako spínací prvek je použito SSR S202S02, které obsahuje cross zero detekci a hardwarově tak zajišťuje spínání v nule. Na 7 segmentovém displeji je v multiplexovaném režimu zobrazována teplota a tlačítkem "set/real" lze volit, zda se zobrazuje teplota nastavená nebo skutečná. Zobrazení skutečné teploty zároveň indikuje rozsvícení diody LED1. Kvůli minimalizaci spotřeby by nebylo vhodné, aby dispej zobrazoval teplotu nepřetržitě. Ta se tedy zobrazí pouze na 60s po stisknutí libovolného tlačítka. Při stisku tlačítka "set_plus" dojde ke zvýšení nastavené teploty o 1°C a při stisku "set_minus" je snížení o stejný krok.

[editovat] Vývojové diagramy

Vývojové diagramy, na kterých je vidět, jakým způsobem jsme se ubírali při programování funkce Main() a Tlacitka().

Funkce Main Funkce Tlačítka

[editovat] Kód v jazyce C

V následujících odstavcích jsou popsány a uvedeny zásadní části programu, kompletní otestovaný (a dokonce funkční) kód je možno stáhnout na konci stránky...

V úvodu je nutné připojit k programu knihovny obsaahující definice a makra, která chceme používat. Také jsou zde definovány konstanty.

#define F_CPU 1000000UL //pro knihovnu delay.h

//vlozeni knihoven
#include <avr/io.h>
#include <util/delay.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>

#define TMR2_INIT_CNT 100 //konstanta pro zkraceni TMR2
#define DLOUHY_STISK 120 //cekej DLOUHY_STISK*10ms nez se vyhodnoti dlouhe drzeni tlac.
#define DS_OPAK 10 //pri dlouhem stisku inc/dec T_set po DS_OPAK*10ms
#define SVITI_VAL 400 //SVITI_VAL*10ms = cas, po ktery bude svitit displej

#define anodaDes 0 //PB0 FUNGOVALO by i PB0?
#define anodaJed 1 //PB1

Protože přímo k portu D jsou připojeny jednotlivé segmenty zobrazovačů, musíme při zobrazování hodnot nastavit odpovídající piny, abychom zobrazili správný údaj. Nebylo by vhodné totto řešit pokaždé zvlášť, proto jsme si do pole char_gen uložili jednotlivé kombinace. Např., pokud chceme později na displeji zobrazit číslici 2, provedeme to bitovým součtem PORTD a char_gen[2].

#define a 0x01
#define b 0x02
#define c 0x04
#define d 0x08
#define e 0x10
#define f 0x20
#define g 0x40

const char char_gen[] = {
     a+b+c+d+e+f,   // Displays "0"
     b+c,           // Displays "1"
     a+b+d+e+g,     // Displays "2"
     a+b+c+d+g,     // Displays "3"
     b+c+f+g,       // Displays "4"
     a+c+d+f+g,     // Displays "5"
     a+c+d+e+f+g,   // Displays "6"
     a+b+c,         // Displays "7"
     a+b+c+d+e+f+g, // Displays "8"
     a+b+c+d+f+g,   // Displays "9"
     a+b+c+e+f+g,   // Displays "A"
     0x00,          // Displays  Blank
     a+d+e+f,       // Displays "C"
     a+b+f+g,       // Displays "degrees" o
     a+d+e+f+g,     // Displays "E"
     a+e+f+g        // Displays "F"  
};

#undef a
#undef b
#undef c
#undef d
#undef e
#undef f
#undef g
#undef h

Následuje již inicializace samotného procesoru a jeho periferií. Na nastavení portů není nic zajímavého, ani na nastavení AD převodníku nic nepřekvapí. Přerušení od modulu Timer2 využíváme z toho důvodu, že může vyvolat probuzení procesoru při jeeho uspání a tím procesor probudit. Tím šetříme energii, neboť procesor většinu času spí a spotřebovává jen malé množství energie. Periodicky se budí, provádí nezbytné úkony a opět usíná.

void initPorts(){
    PORTD = 0x00; //nuly na PORTD
    DDRD = 0xFF; //a vsechny vystupy
    PORTC = 0x00; //nuly na PORTC
    DDRC = 0xFE; //PC0 = vstup pro ADC, jinak vystupy
    PORTB = 0x38; //pull-upy pro piny 3,4,5, ostatni vystupy v nule
    DDRB = 0x07; //piny 0,1,2 vystupy, piny 3,4,5 vstupy pro tlacitka

	PORTB |= (1 << anodaJed); //na zacatku musi byt jedna cislice rozsvicena!
}

void initADC(){
    ADMUX = 0xC0; //internal reference 1.1V, right adjusted result, ADC0
    ADCSRA = 0x84; //ADC enabled, int. off, autotriger off, prescaler 16 (62,5kHz)
    ADCSRB = 0x00; //autotrigger source
    DIDR0 = 0x01; //disable digital input buffer for pin ADC0 (snizeni spotreby)
}

void initTMR2(){
    TCNT2 = TMR2_INIT_CNT; //zkraceni citani TMR2 na 156 (*64) => cca 10ms
    TCCR2A = 0x00; //normal port operation, no waveform generation (PWM) mode
    TCCR2B = 0x04; //64 prescaler
    TIMSK2 = (1 << TOIE2); //TMR2 overflow interruption
}

Dále zadefinujeme vektor přerušení od přetečení TMR2. V jeho obsluze se nic zvláštního neděje, pouze se zkrátí cyklus čítání TMR2.

ISR(TIMER2_OVF_vect){
    TCNT2 = TMR2_INIT_CNT; //zkraceni citani TMR2 na 156*64 => cca 10ms
}

Dále se zmíníme, možná ne nejlogičtějším pořadí, o obslužných procedurách. A začneme funkcí, která zprostředkovává samotný AD převod. Ta spustí konverzi napětí přivedeného na pin ADC0 a po jejím skončení uloží hodnotu do proměnné T_real, přepočítá načtenou hodnotu na skutečnou teplotu ve °C a pokud teplota překračuje meze pro zobrazení, omezí ji. Původně odvozená rovnice pro vypočet hodnoty napětí na teplotu obsahovala desetiná čísla. Program při překladu tak značně nabyl na "objemu", proto jsme provedli jeji úpravu na násobení celými čísly a delení mocninou dvou. Tyto operace je možno provést značně efektivněji.

signed int T_real = 0;
signed int T_set = 20;

signed int ADprevod(void){
    ADCSRA |= (1 << ADSC); //start konverze
    while( (ADCSRA & (1 << ADSC)) != 0){ //cekej, dokud neskonci
	}
    T_real = ADCW; //ADCW uklada obe casti vysledku ve spravnem poradi
    	signed long int Temp = ((((signed long int)T_real*47) >> 7) - 273);
	T_real = (signed int) Temp;
    if(T_real > 99) T_real = 99; 
    if(T_real < 0) T_real = 0;
    return (T_real); //vrat vysledek
}

Protože provádění teploty provádíme pomocí tlačítek a tato operace je poměrně náchylná k chybám vlivem jejich zákmitů, museli jsme se při testování tlačítek s tímto faktem vypořádat a zákmity ošetřit. Podle námi navrženého modelu je každé tlačítko testováno 3x po sobě (po přerušeních po 10ms) a teprve přii třetím úspěšném testu je tlačítko uznáno jako stisknuté. Pro nastavení teploty jsme také použili detekci dlouhého stisku, kdy dochází k zrychlené inkrementaci nebo dekrementaci její hodnoty.

unsigned char zobrazTreal = 0; //else zobraz T_set (po resetu)
unsigned char count_setReal = 0;
unsigned char count_set_plus = 0;
unsigned char count_set_minus = 0;
unsigned int sviti = SVITI_VAL;

void Tlacitka(){
 	if((PINB & (1 << PB3)) == 0){ //stisknuto set_plus	
   		count_set_plus++;
        if(count_set_plus == 3){
			zobrazTreal = 0;
			sviti = SVITI_VAL;
            if(T_set < 99) T_set += 1;
        }
        if(count_set_plus == DLOUHY_STISK){
            count_set_plus = (DLOUHY_STISK - DS_OPAK);
			sviti = SVITI_VAL;
            if(T_set < 99) T_set += 1;
        }
   	}else count_set_plus = 0;
    if((PINB & (1 << PB4)) == 0){ //stisknuto set_minus
        count_set_minus++;
        if(count_set_minus == 3){
			sviti = SVITI_VAL;
			zobrazTreal = 0;
            if(T_set > 0) T_set -= 1;
        }
        if(count_set_minus == DLOUHY_STISK){
            count_set_minus = (DLOUHY_STISK - DS_OPAK);
			sviti = SVITI_VAL;
        	if(T_set > 0) T_set -= 1;
        }
   	}else count_set_minus = 0;
    if((PINB & (1 << PB5)) == 0){ //stisknuto set/real
        count_setReal++;
        if(count_setReal == 3){
			sviti = SVITI_VAL;
    	 	if(zobrazTreal) zobrazTreal = 0;
            else zobrazTreal = 1;
        }
		if(count_setReal > 3) count_setReal = 3; //aby nedoslo k preteceni
    }else count_setReal = 0; 
}

Pomalu uzrál čas na to, abychom se zmínili, jakým způsobem jsme vyřešili zobrazování hodnot na sedmisegmenotém displeji. Tedy: Protože kvůli úspoře energie displej nemá svítit neustále, ale jen okamžik po stisku libovolného tlačítka, nejprve se provede kontrola, zda se má na displeji něco zobrazovat, pokud ne, nastaví se prázdné znaky. Dále se invertuje nastavení společných katod a podle toho, která katoda je sepnutá, dékoduje se příslušný řád a jeho hodnota se přes port D zobrazi na číslicovce.

void zobrazTeplotu(signed int Teplota){
    unsigned char Desitky;
    unsigned char Jednotky;
	if(sviti != 0){
		Desitky = (unsigned char)(Teplota/10);
		Jednotky = (unsigned char)(Teplota%10);
		sviti--; 
	}else{
		Desitky = 11; //11 = blank displej
		Jednotky = 11;
		/*Pri vypnuti displeje se nastavi, ze se zobrazuje T_set, aby zhasla 
		LEDka. Nic moc reseni, ale ucel splnuje.*/ 
		zobrazTreal = 0;
	}
	if(Desitky == 0) Desitky = 11; //11 odpovida na displeji BLANK

    PORTB ^= 0b00000011; //prehozeni rozsvicenych cislic po 10ms
	_delay_us(30); //pockej chvili, nez se na vystupu ustali log uroven
    if(PINB & (1 << anodaDes)){ //PB0 adresuje desitky
        PORTD &= 0b10000000;
        PORTD |= char_gen[Desitky];
    }  //melo by stacit "else", ale kvuli citelnosti...
    if(PINB & (1 << anodaJed)){ //PB1 adresuje jednotky
		PORTD &= 0b10000000;
        PORTD |= char_gen[Jednotky];
    }
}

Poslední obslužnou funkcí v našem seznamu je ta, která je zodpovědná za spínání, resp. vypínání ventilátoru pomocí SSR. Zajišťuje táké potřebnou hysterezi.

void vystupOnOff(void){
    if(T_real > T_set){
        PORTB |= (1 << PB2);
    }
    if(T_real < T_set - 10){
        PORTB &= ~(1 << PB2);
    }
}

A nyní již s velkou slávou uvádíme naši slavnou funkci s přiléhavým názvem main, která je zodpovědná za spojení a svázání všech předešlých kousků do jednoho funkčního celku.

int main(){
    initPorts();
    initADC();
    initTMR2();
    sei();
	
    set_sleep_mode(SLEEP_MODE_PWR_SAVE); //jako powerdown mode, ale  TMR2 enabled
    sleep_mode(); //spi

    while(1){
        ADprevod();
        vystupOnOff();
        if(zobrazTreal){
            zobrazTeplotu(T_real);
            PORTD |= (1 << PD7); //sbi PORTD, 7
        }
		else{
         	zobrazTeplotu(T_set);
            PORTD &= ~(1 << PD7); //cbi PORTD, 7
        }
        Tlacitka();
        set_sleep_mode(SLEEP_MODE_PWR_SAVE); //jako powerdown mode, ale  TMR2 enabled
        sleep_mode(); //spi
	}
} 

[editovat] Realizace

Dokonalé ověření a vyzkoušení žádného programu pro mikroprocesor není možné bez jeho otestování přímo v aplikaci, pro kterou byl napsán. Proto jsem na nepájivém kontektním poli sestavili celý navržený obvod s výjimkou výkonové části. Ta by měla být tvořena SSR, tedy vlastně optotriakem, proto jsme ji pro indikační účely nahradili LED 10mm (Protože zařízení je původně určené ke spínání velkého větráku, použili jsme velkou LEDku!).

Pro programování jsme použili programátor USBtiny, který umožňuje připojení k počítači pomocí USB, lze s ním pracovat přímo v AVR studiu a obsahuje několik programovacích rozhraní, včetně námi využívaného ISP.

Díky těmto prostředkům se nám podařilo najít některé chyby a vyladit kód tak, aby co nejlépe vyhovoval obsluze zařízení.

Soubor:KnedlaReall1.jpg

Soubor:KnedlaReall2.jpg

Soubor:KnedlaReall3.jpg

[editovat] Náměty na vylepšení

  • Nastavenou hodnotu teploty pro sepnutí by bylo možné ukládat do EPROM, aby se zachovávala při vypnutí napájení.
  • Velikost hystereze by bylo možné nechat nastavit uživatelem.
  • Softwarová filtrace signálu (např. pomocí průměrování několika posledních hodnot)

[editovat] Závěr

Co říci závěrem...? V rámci naší seméstrální práce se nám podařilo vytvořit požadovaný kód a díky sestavení obvodu na nepájivém poli jsme ověřili jeho funkčnost a vyladili ho s ohledem na nároky běžného uživatele.

[editovat] Soubory ke stažení

[editovat] Zdroje

Osobní nástroje