Odczyt wilgotności z użyciem czujnika HCH-1000

Jak podłączyć oraz odczytać wilgotność z czujnika HCH-1000 przy pomocy Arduino.

Czujnik wilgoci

Każda amatorska jak i profesjonalna stacja meteo do pełni szczęścia wymaga czujnika wilgotności. Czujnik ten wraz z termometrem pozwala przewidzieć tzw. „punkt rosy”.
Punktem rosy jest temperatura, w której przy odpowiedniej wilgotności skrapla się para wodna zawarta w powietrzu. Zwykle powoduje to powstanie rosy na trawie, lecz w pewnych warunkach pozwala także przewidywać powstanie mgły, obliczyć wysokość na jakiej są chmury lub stwierdzić czemu w kuchni odpada tynk z sufitu ;-).

Kondensator

Urządzenie dostępne w Nettigo jest pojemnościowym czujnikiem wilgotności HCH-1000. Pojemnościowy oznacza, że sensor jest kondensatorem w którym pojemność zależy wprost proporcjonalnie od wilgotności. Średnia pojemność tego kondensatora to około 330 pF.

Kondensator to w najprostszym tłumaczeniu element, który przechowuje ładunek elektryczny. Jeszcze prościej (wybaczcie prawdziwi elektronicy) to taki akumulator o bardzo małej pojemności.
Wyznaczanie pojemności kondensatora

Każdy kto miał do czynienia z akumulatorem (choćby w telefonie komórkowym) wie, że jego załadowanie i rozładowanie wymaga odpowiedniej ilości czasu. Ilość tego czasu zależy od pojemności akumulatora i zapotrzebowania na prąd przez urządzenie. Nie inaczej jest w przypadku kondensatorów.

Fachowo nazywamy to szeregowym obwodem RC (dlatego, że składa się z rezystora i kondensatora połączonych szeregowo).

Wykres ten przedstawia czas w jakim napięcie na kondensatorze (V kondensatora) w obwodzie RC osiągnie procent wartości napięcia zasilania obwodu (V zasilania). Można z niego wywnioskować, że 63,2% napięcia osiągnie w jednej jednostce czasu, którą można wyznaczyć ze wzoru:

t = RC

gdzie:

    t – czas w sekundach
    R – Rezystancja rezystora w Ω
    C – Pojemność kondensatora w Faradach

Zakładając, że użyjemy rezystora 10 MΩ, a czujnik wilgotności ma około 330 pF, ładowanie do 63,2% napięcia będzie trwało:

t = RC = 10 MΩ 330 pF = 10000000 Ω 0,00000000033 F = 0,0033 s

Łatwiej obliczać używając potęg jednostek

t = 10 10^6 Ω 330 10^-12 F = 10 330 10^-6 = 3300 10^-6 = 3300 µs.

Ładowanie do większych części napięcia zasilania to, zgodnie z wykresem wielokrotność tego czasu. Np. dla 95% jest to 3 razy dłuższy czas.

t = 3RC = 3 3300 10^-6 = 9900 µs

Rozładowywanie jest odwrotnym procesem, w którym przydział czasu rozładowania do wartości napięcia przebiega w ten sposób:

Realizacja w Arduino

Podstawowy model działania układu w projekcie będzie musiał realizować następujące punkty programu:

    Ładowanie Kondensatora
    Badanie stopnia naładowania
    Mierzenie czasu ładowania
    Rozładowywanie kondensatora

Układ elektryczny podstawowego modelu przedstawia poniższy rysunek.

PIN 3 odpowiada za ładowanie kondensatora, PIN 2 za rozładowywanie. Analogowy PIN 0 mierzy napięcie kondensatora. Do PIN 3 podłączony jest rezystor ładujący 10 MΩ, Do PIN 2 – rezystor rozładowujący 220 Ω.
Procedura pomiaru pojemności składa się z dwóch następujących po sobie podprogramów.

Rozładowywania:

W tym podprogramie PIN 3 ustawiony jest w tryb „INPUT” i ustawiony w stan „LOW”, natomiast PIN 2 jest w trybie „OUTPUT” i ustawiony w stan „LOW”.
Powoduje to przepływ prądu z kondensatora do rezystora rozładowującego. Osiągnięcie stanu rozładowania sprawdzane jest za pomocą Analogowego wejścia PIN 0. Po osiągnięciu 0V następuje koniec działania podprogramu.

void discharge()
{
  // Ustawienie PINów w tryb rozładowywania
  pinMode(CHARGE_PIN, INPUT);
  digitalWrite(CHARGE_PIN, LOW);
  pinMode(DISCHARGE_PIN, OUTPUT);
  digitalWrite(DISCHARGE_PIN, LOW);
  // Oczekiwanie na osiągnięcie przez kondensator 0 V
  while (analogRead(VOLTAGE_CHECK_PIN) > 0);
}

Ładowania:

Podprogram ładowania ustawia PIN 3 w tryb „OUTPUT” i stan „HIGH”, natomiast PIN2 w stan „INPUT”. Analogowe wejście PIN 0 bada osiągnięcie odpowiedniego stanu naładowania kondensatora. Jednocześnie mierzony jest czas ładowania za pomocą funkcji „micros”.
Powoduje to przepływ prądu z PIN 3 przez rezystor ładowania do kondensatora.

Wejścia „Analog Input” są przetwornikami analogowo – cyfrowymi (przetwarzają analogowe napięcie na binarną wartość). Przetworniki te mają rozdzielczość 10-bitową. Znaczy to, że mogą przyjąć jedną z (2^10) 1024 wartości, gdzie 0 to 0 V, a 1023 to 5 V. Ponieważ 5 V to napięcie zasilania naszego układu RC możemy przyjąć, że 63,2% tego napięcia to będzie wartość

1023 * 0.632 = 646,535

Zaokrąglamy tą liczbę do 647.

unsigned long charge()
{
// Ustawienie PINów a tryb ładowania
pinMode(DISCHARGE_PIN, INPUT);
unsigned long begin_time = micros(); // Pobranie początkowego czasu
pinMode(CHARGE_PIN, OUTPUT);
digitalWrite(CHARGE_PIN, HIGH);
// Oczekiwanie na osiągnięcie przez kondensator 63,2% napięcia zasilania
while (analogRead(VOLTAGE_CHECK_PIN) < 647);
unsigned long end_time = micros(); // Pobranie końcowego czasu
return end_time - begin_time; // Obliczenie czasu ładowania
}

Funkcja ta zwraca czas ładowania kondensatora do 63,2%. Czas liczony jest w mikrosekundach.
Oczywiście można zwiększyć stopień ładowania. Spowoduje to zwiększenie dokładności oraz zmniejszenie częstotliwości pomiarów.

Zakłócenia

Jeśli prawidłowo zmontowałeś układ uruchom na nim pierwszy przykład.
Zwraca on przez „Serial Monitor” czas ładowania kondensatora. Co ciekawe zamiast planowanych 3300 µs jest około 4300. Jeśli zwracane wartości są bardziej losowe spróbuj dotknąć czegoś metalowego i uziemnionego (np. kaloryfera). Może się okazać, że sam wprowadzasz zakłócenia.

Skąd taka duża wartość? Wprowadza je sam program (opóźnieniami w wykonywaniu poszczególnych instrukcji) oraz sam układ elektryczny. On też ma pojemność – jest kondensatorem. Kondensator tworzą nawet przewody prowadzone obok siebie. Aby sprawdzić jak wielki wpływ ma pojemność układu na wynik, wyjmij czujnik z płytki i uruchom ponownie. Tym razem wynik wynosi 684 µs. Oznacza to, że układ ma najprawdopodobniej taką pojemność:

t = RC
C = t/R = 684 10^-6 s / 10 10^6 = 68,4 * 10^-12 = 68,4 pF

Aby sprawdzić jaki wpływ na układ mają przewody doprowadzające sygnały z Arduino, możesz w ramach eksperymentu zbliżać je do siebie i sprawdzić jak to zmienia wynik. U mnie zwiększa się do ponad 1000 µs. Chciałbym przez to zwrócić uwagę na dokładność, jakość i stabilność wykonanego obwodu.

Możemy założyć, że pojemność układu jest równolegle połączonym kondensatorem z naszym czujnikiem, a równoległe połączenie kondensatorów sumuje pojemność. Dlatego wynik możemy odjąć od wyniku działania programu, co da zbliżoną rzeczywistą pojemność czujnika w danej chwili.

Kalibracja

Oczywiście za nic w świecie nie można przyjąć, że program zwraca dokładną pojemność z katalogu producenta. Zawsze trzeba mieć jakieś punkty odniesienia. Na tym właśnie polega kalibracja.
Chodzi o to by wprowadzić czujnik w środowisko w którym znamy dokładne parametry. Dla lepszego zrozumienia przedstawię kalibrację na przykładzie czujnika temperatury.

Jeśli podłączymy czujnik temperatury do wejścia analogowego, nie możemy przyjąć, że dane napięcie podane przez jego producenta oznacza dokładnie taką temperaturę. Trzeba być sceptycznym z tego samego powodu, dla którego nasz układ ma jakieś swoje pojemności. Układ z termometrem może mieć podobne problemy z własną opornością (np przez długi przewód do czujnika).

Dlatego musimy go skalibrować czyli wytworzyć mu parametry w których znamy dokładnie temperaturę. Jak wiemy z fizyki, woda ma 2 stany w których znamy temperaturę niemal w 100%. Pierwszy to lód utrzymujący 0 °C, drugi wrząca woda o temperaturze 100 °C.

Ochraniamy czujnik, by woda nie dostała się do wyprowadzeń i umieszczamy go na przemian w tych środowiskach. Spisujemy w każdym z nich ustabilizowaną wartość zwróconą przez „analogRead” i mamy punkty odniesienia.

Zakładając, że czujnik jest liniowy (tzn. wzrost temperatury jest wprost proporcjonalny do napięcia jakie wysyła do Arduino) i że znasz się na matematyce, zapewne wiesz, że wzór na funkcje liniową to y = ax + b. Na jego podstawie dokonamy kalibracji.
Przyjmijmy, że:

    y to temperatura rzeczywista w °C
    x to wynik działania funkcji „analogRead”
    a, b to współczynniki kalibracji czyli 2 niewiadome równania

Skoro mamy 2 wartości pomiarów i 2 temperatury, możemy ułożyć układ równań w wyniku którego otrzymamy współczynniki „a” i „b” potrzebne do obliczenia dokładnej temperatury.
Oczywiście opisałem to dla większego zrozumienia. Arduino IDE zawiera odpowiednią funkcję realizującą to zadanie.

map(value, minvalue, maxvalue, minoutput, maxoutput)

value – to wartość wejściowa pomiaru
minvalue – to minimalna wartość pomiaru, możemy tu podać wartość dla 0 °C
maxvalue – to maksymalna wartość pomiaru, możemy tu podać wartość przetwornika dla 100 °C
minoutput – minimalna wartość jaką zwróci funkcja, gdy argument value osiągnie wartość minvalue, można wpisać 0
maxoutput – maksymalna wartość jaką zwróci funkcja, gdy argument value osiągnie wartość maxvalue, można wpisać 100

Funkcja nie ogranicza się do zakresu podanego w argumentach. Wyjście po za zakres także zostanie odpowiednio przeliczone.

Kalibracja czujnika wilgotności

Domowymi sposobami dość łatwo uzyskać wartości przybliżone do 0% i 100% wilgotności. Składniki, jakie będą w tym potrzebne to:

Kilka kawałków papierowego ręcznika kuchennego
Torebka foliowa
Gumka recepturka

Przepis na 0%

Ręcznik kuchenny umieść w piecu na godzinę. Musi pozbyć się całej wilgoci. Po wygrzaniu włóż ręczniki do torebki razem z podłączonym czujnikiem. Odczekaj, aż czujnik wskaże najniższy stabilny wynik i zanotuj go.

Przepis na 100%

Polej ręcznik wrzątkiem i umieść w torebce razem z czujnikiem. Wilgoć nie może dostać się do wyprowadzeń ani układów, bo sfałszuje wyniki. Poczekaj aż czujnik osiągnie najwyższy możliwy wynik i zapisz go.

Uwagi:

Najlepiej umieścić w torebce sam czujnik, szczególnie przy wilgotności 100%.
Jeśli czujnik jest na przewodach przedłużających trzeba zapewnić ich stabilność, by nie zmieniały pojemności układu.
Wyprowadzenia czujnika trzeba dobrze zaizolować, by nie dostała się tam para wodna.

Gdy już mamy wyniki, możemy stworzyć funkcję zwracającą wilgotność w prawidłowych jednostkach.

unsigned int get_humidity()
{
discharge(); // rozładowywanie
unsigned long charge_time = charge(); // Ładowanie z mierzeniem czasu
// Skalowanie
unsigned int humidity = map(charge_time, HUMIDITY_0, HUMIDITY_100, 0, 100);
// Tryb kalibracji (1) lub tryb pomiaru (0)
if (CALIBRATION == 1)
return charge_time;
else
return humidity;
}

Po dodaniu funkcji program może zmienić swoją charakterystykę i wymagać ponownego kalibrowania.

Inne sposoby podłączenia czujnika

Powyższy układ jest dość „akademickim” rozwiązaniem. Można go rozstroić dopisując kolejne elementy programu. W dokumentacji czujnika wilgotności można znaleźć dwa przykładowe schematy oparte na układzie 555. Pierwszy generuje częstotliwość w zależności od wilgotności, drugi generuje impulsy PWM, gdzie czas trwania impulsu zależy od wilgotności.

My zajmiemy się kolejną aplikacją opartą na chipie 74hc4060. Układ ten jest zintegrowanym generatorem i dzielnikiem częstotliwości.

Jak widać schemat jest równie prosty jak poprzedni. Tym razem ładowaniem i rozładowywaniem kondensatora zajmuje się układ scalony.
Z układu RC „scalak” generuje częstotliwość. Szerokość impulsu tej częstotliwości można obliczyć ze wzoru:

t = 2,5 R1 C

Prawda, że coś nam to przypomina?
Częstotliwość jak wiemy z fizyki, oblicza się przez odwrotność czasu:

f = 1/t

W naszym układzie częstotliwością bazową będzie:

f = 1 / (2,5 16 10^3 Ω 330 10^-12) = 1 / (13200 10^-9) = 0,000075758 10^9 = 75758 Hz

Wspomniałem, że 74HC4060 jest też dzielnikiem częstotliwości. Oznacza to, że układ ma kilka wyjść o pomniejszonej częstotliwości niż bazowa. Są to piny:

    7 – częstotliwość / 16
    5 – częstotliwość / 32
    4 – częstotliwość / 64
    6 – częstotliwość / 128
    14 – częstotliwość / 256
    13 – częstotliwość / 512
    15 – częstotliwość / 1024
    1 – częstotliwość / 4096
    2 – częstotliwość / 8192
    3 – częstotliwość / 16384

Podział częstotliwości służy do dobrania potrzebnej lub odpowiadającej nam częstotliwości. Ja użyłem nogi 15, czyli podział częstotliwości przez 1024. Da nam to około:

75758 / 1024 = 74 Hz

Można oczywiście wybrać inną. Im mniejszy dzielnik, tym większa dokładność.

Odczytu częstotliwości w tak skonfigurowanym układzie można dokonać za pomocą biblioteki „FreqCounter”.

unsigned long get_humidity()
{
FreqCounter::f_comp= 8;
FreqCounter::start(100);
while (FreqCounter::f_ready == 0);
long int freq = FreqCounter::f_freq;
unsigned long humidity = map(freq, HUMIDITY_0, HUMIDITY_100, 0, 100);
if (CALIBRATION == 1)
return freq;
else
return humidity;
}

Kalibracji układu dokonujemy tak jak w poprzednim przypadku spisując częstotliwości dla znanych skrajnych poziomów wilgotności i skalujemy do normalnych jednostek za pomocą funkcji „map”.
Tym razem żadne dodatkowe zmiany w programie nie powinny rozstrajać kalibracji.

Dla wielbicieli staroci

Znalazłem w szafie stary układ scalony UCY 7404 w technologii TTL, nieistniejącej już firmy CEMI. Z niego oczywiście też można zrobić generator.

Częstotliwość obliczamy podobnie jak poprzednio ze wzoru:

t = 3RC
f = 1/t

Zastosowałem w nim rezystor 330 Ω.

Wykorzystujemy ten sam program zwracając uwagę na dużo wyższą częstotliwość układu.

Kody źródłowe

Literatura: