Vorige: Spanningsreferentie.   Omhoog: Meettoestellen.   Volgende: Vochtsensor.
Inhoudsopgave   Index


Vochtmeter.


Voorwoord.

Onlangs heb ik me enige stuks GY-HR002 aangeschaft. De datasheet is erg beknopt, en laat vermoeden dat het om een vocht-afhankelijke weerstand gaat.
Zo eenvoudig is het echter niet. Een eerste poging om de GY-HR002 op te nemen in een R/C-oscillator, zodat de frequentie hiervan een maat zou zijn voor de relatieve vochtigheid, mislukte jammerlijk.
Al snel werd duidelijk, dat er echt een meetbrug aan te pas moest komen. Zoiets kan je bouwen met enkele opamps, maar een kleine microcontroller kan het net zo goed.


Het schema.

GY-HR002-4.png Laten we beginnen met PB0. Deze pin levert een blokspanning met een frequentie van (ongeveer) 1kHz. R5 en R6 doen dienst als spanningsdeler, en C3 maakt van de gedeelde blokspanning een echte wisselspanning.

Links in het schema zien we een tweede spanningsdeler: op het knoopunt R2/R3 komt 2.5V te staan. C2 maakt deze spanning “hard” voor wisselstromen.

Meer is niet nodig voor onze meetbrug. De GY-HR002 wordt belast met een weerstand van 31k, en krijgt een wisselspanning van 1.14V aangeboden. PB3 doet dienst als ADC-ingang, zodat we de spanning over R4 kunnen meten. Als we die spanning kennen, kunnen we de weerstand van de GY-HR002 berekenen.

Tenslotte zijn er nog PB1 en PB2. Deze pinnen worden gebruikt om meetresultaten door te geven aan een andere AVR. PB1 levert een aantal klokpulsen, waarna PB2 één puls levert om het einde te markeren.


De firmware.

/*
  RH002 - vochtsensor-sturing met een ATtiny45

  Pros 2013
*/



#define F_CPU 4000000UL

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

volatile unsigned int adch = 527adcl = 527;

ISR(ADC_vect)
{
    unsigned int    myadc = ADC;

    if (myadc > 527) {
        adch -= (adch / 8);
        adch += (myadc / 8);                                    // Uitmiddelen
    } else {                                                    // myadc < 527
        adcl -= (adcl / 8);
        adcl += (myadc / 8);
    }
}

void pulsen(unsigned int aantal)
{
    unsigned int    cnt;

    for (cnt = 0cnt < aantalcnt++) {
        PORTB &= ~(_BV(PB1));
        _delay_us(100);
        PORTB |= _BV(PB1);
        _delay_us(100);
    }

    PORTB &= ~(_BV(PB2));                                       // Een neergaande flank geeft aan
    _delay_ms(100);                                             // dat alle pulsen doorgegeven zijn
    PORTB |= _BV(PB2);
}

int main(void)
{
    unsigned int    myadchmyadcl;

    CLKPR = _BV(CLKPCE);
    CLKPR = _BV(CLKPS0);                                        // CLK = 9.6MHz / 2 = 4.8MHz

    ADMUX = _BV(REFS1) | _BV(REFS2) | 3;                        // Ref = 2.56V; PB3 = ADC IN
    // Start de ADC in free-running mode, met interrupt toegestaan
    // CLK = 4MHz / 64 = 62500Hz = 4807 ADC-samples/sec
    ADCSRA = _BV(ADEN) | _BV(ADIE) | _BV(ADATE) | _BV(ADPS1) | _BV(ADPS2);
    ADCSRB = 0;
    ADCSRA |= _BV(ADSC);                                        // 1 ADC-bit = 2.5mV
    _delay_ms(1000);

    TCCR0A = _BV(COM0A0) | _BV(WGM01);                          // Toggle OC0A on Compare Match - CTC mode
    TCCR0B = _BV(CS00) | _BV(CS01);                             // CLK = 4MHz / 64 = 62500z
    OCR0A = 30;                                                 // OC0A = 62500 / (2 * 31) = 1008Hz

    DDRB = _BV(PB0) | _BV(PB1) | _BV(PB2);
    PORTB = _BV(PB1) | _BV(PB2);
    MCUCR |= _BV(SM0);                                          // Sleep-mode = ADC noise-reduction
    sei();

    while (1) {
        _delay_ms(1000);                                        // Een seconde wachten
        MCUCR |= _BV(SE);                                       // Wachten tot de volgende ADC-omzetting ten einde is
        sleep_cpu();                                            // Dutje doen
        MCUCR &= ~(_BV(SE));
        myadch = adch;                                          // Hoogste spanning noteren
        _delay_ms(1000);
        MCUCR |= _BV(SE);
        sleep_cpu();
        MCUCR &= ~(_BV(SE));
        myadcl = adcl;                                          // Laagste spanning noteren
        myadch -= myadcl;                                       // myadch = verschil tussen max- en min-waarde
        pulsen(myadch);                                         // Resultaat doorgeven
    }
}


De code is vrij eenvoudig.
Allereerst wordt de ADC-convertor in free-running mode gestart. Telkens een conversie afgelopen is, wordt er naar een interrupt-routine gesprongen, waar het gemiddelde van zowel de hoogste als van de laagste ingangsspanningen wordt bijgehouden.
Al wat de hoofdroutine nu nog moet doen, is het verschil tussen deze gemiddelden berekenen om te weten welke wisselspanning er over R4 staat, en deze door te geven via PB1 en PB2.

Van spanning naar relatieve vochtigheid.

We hebben nu wel een spanning, maar hoe moeten we dat vertalen naar een percentage? De HY-RH002 is verre van lineair - berekenen is zowat uitgesloten.
Dan maar vergelijken met een tabel. Nu kunnen we hiervoor de tabel uit de datasheet gebruiken, maar als we die eerst omzetten naar een serie spanningen, nemen we de AVR een boel werk uit zijn handen.


unsigned int    RH_cnt;         // De spanning in mV
unsigned int    temperatuur;    // De temperatuur in C
unsigned char   RH;             // De relatieve vochtigheid in percent

const unsigned int U[7][15PROGMEM = {
    {917295597147227295380491579673763858908},      //  5C
    {9213971115179255330433535624717807884929},     // 10C
    {15285292138218285368487579668758847908962},    // 15C
    {193669111172247320421528630713801875941989},   // 20C
    {2448911342012803574785706807558419029711013},  // 25C
    {32641091622443224105196147138018769409931032}, // 30C
    {448513119527636846556165674484590897410131050// 35C
};


void calc_rh(void)
{
    unsigned int    cntstepbaserowrhcnt;
    unsigned char   myrh;

    rhcnt = RH_cnt;                                             // Spanning overnemen
    RH_cnt = 0;                                                 // Terug op 0 zetten

    row = temperatuur + 2;                                      // Voorbeeld: temperatuur = 18 + 2 = 20
    row /= 5;                                                   // 20 / 5 = 4
    if (row > 0) {
        row―;                                                  // 4 - 1 = 3, want eerste row = 0
    }
    if (row > 6) {                                              // Er zijn maar 7 rows
        row = 6;
    }

    myrh = 20;
    for (cnt = 0cnt < 14cnt++) {
        if (rhcnt < pgm_read_word(&U[row][cnt])) {
            break;
        }
        myrh += 5;
    }

    // We kennen nu de RH met een resolutie van 5%, dat willen we omzetten naar 1%

    base = pgm_read_word(&U[row][cnt]);
    step = (base - pgm_read_word(&U[row][cnt - 1])) / 5;        // 

    for (cnt = 0cnt < 5cnt++) {
        base -= step;
        if (rhcnt > base) {
            break;
        }
        myrh―;
    }
    RH = myrh;
}

Het spreekt voor zich, dat de waarden in de tabel niet met de hand zijn uitgerekend. Ook daar hebben we een brokje code voor geschreven, dat op de PC uitgevoerd wordt.


Het printje.

HR002PrintBoven.JPG HR002PrintOnder.JPG



Vorige: Spanningsreferentie.   Omhoog: Meettoestellen.   Volgende: Vochtsensor.
Inhoudsopgave   Index

Pros Robaer - 2014