Ciljevi učenja
- Zadati cilj od 20 do 30 °C potenciometrom.
- Objasniti pragove uključivanja i isključivanja sa histerezom.
- Razlikovati zahtjev regulatora, stvarni relej i oznaku HOLD.
- Obraditi nevažeće očitavanje uz trenutno isključivanje izlaza.
- Povezati relejni modul preko dodatnog tranzistora i provjeriti kontaktni LED model.
Prije početka
Završi kurseve 10, 11 i 16: DHT11 očitavanje, OLED adresa i relejni modul sa tranzistorskim drajverom.
Referentna postavka
- Ploča u Arduino IDE-u
- ESP32 Dev Module
- Arduino-ESP32
- 3.3.12
- Serial Monitor
- 115200 baud
Ovo je referentni profil, a ne identifikacija ploče sa fotografije. Za ESP32-C3/S2/S3 ili drugačije označenu ploču treba prilagoditi GPIO i provjeriti šemu prije spajanja.
Šta ćemo napraviti
Napravićemo model temperaturnog regulatora: potenciometar zadaje željenu temperaturu, DHT11 mjeri uslove, OLED prikazuje stanje, a relej uključuje signalnu LED. LED predstavlja stvarno uključen izlaz za hlađenje; u ovom sklopu nema ventilatora, pumpe, grijača ni mrežnog napona. Zato uključivanje ne snižava stvarnu sobnu temperaturu.
Šta ćeš naučiti
- Podesiti temperaturni cilj analognim ulazom.
- Primijeniti histerezu i minimalni razmak između promjena releja.
- Razlikovati zahtjev regulatora od stvarnog izlaza.
- Dati grešci senzora prednost nad vremenskim ograničenjem.
- Povezati odvojene zadatke mjerenja, odlučivanja i prikaza.
Predznanje
Završi kurseve 10, 11 i 16. Potrebni su DHT11 očitavanje, OLED adresa i tranzistorsko upravljanje relejnim modulom. Referenca je klasični ESP32/ESP32-WROOM-32. GPIO oznake nisu fizički redni brojevi izvoda; druge ESP32 porodice zahtijevaju prilagođavanje.
Potrebna oprema i provjera varijanti
| Komponenta | Količina | Specifikacija i napomene |
|---|---|---|
| ESP32 razvojna ploča sa klasičnim ESP32-WROOM-32 modulom | 1 | GPIO nazivi moraju odgovarati referentnom profilu; fizički položaj izvoda provjeriti na ploči. |
| USB kabl za podatke i napajanje | 1 | Priključak prilagoditi ploči; komplet na slici navodi Micro-USB. |
| Eksperimentalna ploča | 1 | Komplet navodi 830 kontaktnih tačaka. Provjeriti eventualni prekid naponskih vodova. |
| Dvokanalni relejski modul 5 V — aktivno LOW | 1 | Referentni modul ima povlačenje IN na HIGH, ugrađene drajvere i diode zavojnica. Tačne osobine provjeri; na fotografiji je potvrđena samo kategorija 5 V/dva kanala. Koristi se jedan kanal. |
| Dodatni NPN tranzistor PN2222A | 1Obavezno dodatno — nije na slici kompleta | DODATNO, nije naveden u kompletu. Identifikuj B/C/E prema tačnom proizvođaču; PN2222A i P2N2222A mogu imati različit raspored. DODATNO: tranzistor nije naveden na fotografiji kompleta. |
| Otpornik 1 kΩ | 1 | Otpornik baze; u kursu 14 i paralelni otpornik piezo elementa. Količina ove vrijednosti u kompletu nije potvrđena. |
| Otpornik 10 kΩ | 2 | R2 je pull-down baze Q1 prema GND; R4 je pull-up DHT DATA prema 3V3. |
| Crvena LED dioda | 1 | Anoda A i katoda K; provjeriti polaritet stvarne komponente. |
| Otpornik 220 Ω | 1 | Po jedan otpornik za svaku LED granu; 220 Ω iz kompleta. |
| DHT11 modul temperature i relativne vlažnosti | 1 | Tropriključni VCC/GND/DATA modul potvrđen za 3,3 V; DATA pull-up samo prema 3V3. Neke varijante ne rade pouzdano na 3,3 V. |
| OLED displej 0,96″ — referentni I2C SSD1306 128×64 | 1 | VCC/GND/SDA/SCL, napajanje 3,3 V, pull-up otpornici na modulu prema 3V3. Kontroler, interfejs i rezolucija sa fotografije nisu potvrđeni. |
| Potenciometar 10 kΩ | 1 | Dva krajnja izvoda i klizač W; raspored utvrditi prema stvarnoj komponenti. |
| Spojni kablovi | 22 | M-M ili F-M prema priključcima ploče. Okvirna količina; zavisi od fizičkog rasporeda. |
PN2222A tranzistor je dodatna obavezna komponenta koja nije prikazana u kompletu. Provjeri B, C i E za tačnog proizvođača i kućište; sličan naziv tranzistora ne potvrđuje isti raspored. onsemi — PN2222A
Relejni modul mora biti deklarisan za 5 V, aktivan na LOW, sa podizanjem ulaza na HIGH kada je otpušten i ugrađenim drajverima i zaštitom zavojnica. Fotografija ne potvrđuje njegovu šemu. DHT11 modul i SSD1306 OLED 128 × 64 moraju podržavati napajanje i logiku 3,3 V. OLED sa SH1106 kontrolerom zahtijeva drugi drajver.
Zašto postoje dva praga
Za zadati cilj od 24 °C zahtjev za hlađenjem uključuje se na 25 °C ili više, a isključuje na 23 °C ili manje. Između pragova ostaje prethodno stanje zahtjeva. To je histereza: male promjene oko cilja ne izazivaju stalno prebacivanje.
Drugi mehanizam je MIN_SWITCH_MS = 5000: između običnih promjena stvarnog releja mora proći najmanje pet sekundi. Zahtjev se može promijeniti prije isteka, ali izlaz tada čeka. Ovo je nastavna vrijednost, a ne zaštita ili specifikacija za kompresor ili drugi stvarni uređaj.
Šema i redoslijed spajanja
Šema povezivanja
Isti relejski LED krug kao u kursu 16; nijedan stvarni potrošač hlađenja se ne spaja. DHT11 i OLED su na 3,3 V, a relejski modul na provjerenom USB5V vodu. R2 je pull-down baze, R4 je DATA pull-up. Q1 je dodatna komponenta.
| Od | Do | Opis veze |
|---|---|---|
ESP32.GPIO25 | R1.1 | Otpornik R1 od 1 kΩ ograničava struju baze. |
R1.2 | Q1.B | Baza dodatnog PN2222A tranzistora. |
Q1.B | R2.1 | R2 od 10 kΩ drži bazu nisko dok GPIO ne upravlja. |
R2.2 | ESP32.GND | Drugi kraj pull-down otpornika. |
Q1.E | ESP32.GND | Emiter prema zajedničkoj masi; provjeriti stvarni B/C/E raspored. |
Q1.C | K1.IN1 | Kolektor obara IN1 na LOW kada je GPIO25 HIGH. |
ESP32.USB5V | K1.VCC | Napajanje referentnog modula 5 V; JD-VCC kratkospojnik ako postoji prema dokumentaciji. |
ESP32.GND | K1.GND | Zajednička masa modula i upravljačkog tranzistora. |
ESP32.USB5V | K1.IN2 | Neiskorišćeni aktivno-LOW ulaz drugog kanala drži se HIGH, odnosno isključeno. |
ESP32.3V3 | K1.COM1 | Zajednički kontakt prenosi samo 3,3 V LED krug. |
K1.NO1 | R3.1 | Normalno otvoreni kontakt prema R3 od 220 Ω. |
R3.2 | D1.A | Serijski otpornik prema anodi LED. |
D1.K | ESP32.GND | Katoda indikacione LED prema masi; NC1 ostaje nepovezan. |
ESP32.3V3 | U1.VCC | DHT11 modul potvrđen za rad na 3,3 V. |
ESP32.GND | U1.GND | Zajednička masa. |
U1.DATA | ESP32.GPIO26 | DHT11 DATA prema GPIO26. |
ESP32.3V3 | R4.1 | R4 od 10 kΩ povlači DATA na 3V3. |
R4.2 | U1.DATA | Drugi kraj R4 na DATA/GPIO26 čvor. |
ESP32.3V3 | OLED1.VCC | Referentni I2C SSD1306 128×64 na 3,3 V. |
ESP32.GND | OLED1.GND | Zajednička masa. |
ESP32.GPIO21 | OLED1.SDA | I2C podaci. |
ESP32.GPIO22 | OLED1.SCL | I2C takt. |
ESP32.3V3 | RV1.END_A | Jedan krajnji izvod potenciometra 10 kΩ. |
ESP32.GND | RV1.END_B | Drugi krajnji izvod. |
RV1.W | ESP32.GPIO34 | Klizač prema ulazu ADC1. |
- Isključi USB. Poveži zajedničku masu ESP32, senzora, ekrana i relejnog modula.
- Spoji GPIO25 preko R1 od 1 kΩ na Q1.B; R2 od 10 kΩ između B i GND; Q1.E na GND; Q1.C na IN1 modula.
- VCC modula i neiskorišćeni IN2 spoji na provjereni USB 5 V izvod ploče. Ako postoji JD-VCC, zadrži samo dokumentovani spoj za zajedničko napajanje.
- Kontaktnu granu spoji: 3V3 na COM1, NO1 preko R3 od 220 Ω na LED anodu, a katodu na GND. NC1 i kontakti drugog kanala ostaju prazni.
- DHT11 VCC spoji na 3V3, GND na GND, DATA na GPIO26. R4 od 10 kΩ ide između DATA i 3V3, i kada modul već ima svoj pull-up.
- OLED: VCC na 3V3, GND na GND, SDA na GPIO21, SCL na GPIO22. Njegovi pull-up otpornici moraju voditi na 3,3 V.
- Krajeve potenciometra spoji na 3V3 i GND, a klizač na GPIO34. Pregledaj sve grane prije USB napajanja.
GPIO HIGH uključuje Q1, Q1 spušta IN1 na LOW i relej zatvara COM1–NO1. Tranzistor upravlja ulazom modula; ne upravlja golom zavojnicom. Ne dodaj zaštitnu diodu preko IN1. Provjeri ukupan USB kapacitet, uključujući struju zavojnice; ne spajaj drugo 5 V napajanje paralelno USB-u. Espressif — napajanje ploče
Priprema Arduino okruženja
Koristi Arduino IDE 2.x, esp32 by Espressif Systems 3.3.12, ploču ESP32 Dev Module i Serial Monitor na 115200 baud. Instaliraj sljedeće verzije kroz Library Manager:
| Biblioteka | Verzija |
|---|---|
| DHT sensor library by Adafruit | 1.4.7 |
| Adafruit Unified Sensor | 1.1.15 |
| Adafruit SSD1306 | 2.5.17 |
| Adafruit GFX Library | 1.12.6 |
| Adafruit BusIO | 1.17.4 |
Wire dolazi sa ESP32 paketom. Raspakuj Arduino ZIP, otvori cooling_controller.ino iz istoimenog foldera, izaberi port i pokreni Verify pa Upload.
| Biblioteka i izdanje | Verzija | Zavisnosti |
|---|---|---|
| DHT sensor library | 1.4.7 | Adafruit Unified Sensor 1.1.15 |
| Adafruit Unified Sensor | 1.1.15 | Nema |
| Adafruit SSD1306 | 2.5.17 | Adafruit GFX Library 1.12.6 |
| Adafruit GFX Library | 1.12.6 | Adafruit BusIO 1.17.4 |
| Adafruit BusIO | 1.17.4 | Nema |
Wire je uključen u ESP32 core; ne instalira se zasebno kroz Library Manager.
Kompletan program
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#include <math.h>
constexpr uint8_t RELAY_DRIVER_PIN = 25;
constexpr uint8_t DHT_PIN = 26;
constexpr uint8_t POT_PIN = 34;
constexpr uint8_t SDA_PIN = 21;
constexpr uint8_t SCL_PIN = 22;
constexpr uint8_t OLED_ADDRESS = 0x3C;
constexpr uint32_t SAMPLE_MS = 2500;
constexpr uint32_t POT_SAMPLE_MS = 100;
constexpr uint32_t DISPLAY_MS = 250;
constexpr uint32_t MIN_SWITCH_MS = 5000;
Adafruit_SSD1306 display(128, 64, &Wire, -1, 100000UL, 100000UL);
DHT dht(DHT_PIN, DHT11);
bool ready = false;
bool sampleAttempted = false;
bool currentValid = false;
bool coolingDemand = false;
bool relayOn = false;
float currentTemperature = NAN;
float currentHumidity = NAN;
uint16_t rawValue = 0;
uint8_t targetC = 20;
uint32_t lastSampleAt = 0;
uint32_t lastPotAt = 0;
uint32_t lastDisplayAt = 0;
uint32_t lastRelayChangeAt = 0;
void scanI2CBus() {
Serial.println("I2C scan: ACK identifies an address, not the display controller.");
uint8_t found = 0;
for (uint8_t address = 1; address < 127; ++address) {
Wire.beginTransmission(address);
if (Wire.endTransmission() == 0) {
Serial.print("ACK at 0x"); Serial.println(address, HEX);
++found;
}
}
if (found == 0) Serial.println("No I2C device acknowledged.");
}
bool initializeDisplay() {
if (!Wire.begin(SDA_PIN, SCL_PIN)) {
Serial.println("OLED ERROR: I2C initialization failed; reset to retry.");
return false;
}
Wire.setClock(100000);
Wire.setTimeOut(50);
scanI2CBus();
Wire.beginTransmission(OLED_ADDRESS);
if (Wire.endTransmission() != 0) {
Serial.println("OLED ERROR: configured address did not ACK; check wiring/address and reset.");
return false;
}
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS, false, false)) {
Serial.println("OLED ERROR: buffer initialization failed; reset to retry.");
return false;
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setTextWrap(false);
return true;
}
uint8_t targetFromRaw(uint16_t raw) {
return static_cast<uint8_t>(20UL + static_cast<uint32_t>(raw) * 10UL / 4095UL);
}
void evaluateDemand() {
if (!currentValid) coolingDemand = false;
else if (currentTemperature >= static_cast<float>(targetC) + 1.0f) coolingDemand = true;
else if (currentTemperature <= static_cast<float>(targetC) - 1.0f) coolingDemand = false;
}
void setRelay(bool on, uint32_t now) {
if (on == relayOn) return;
relayOn = on;
digitalWrite(RELAY_DRIVER_PIN, relayOn ? HIGH : LOW);
lastRelayChangeAt = now;
}
void applyRelay(uint32_t now) {
if (!ready || !currentValid) {
coolingDemand = false;
setRelay(false, now); // A fault overrides the normal minimum interval.
return;
}
if (coolingDemand != relayOn && static_cast<uint32_t>(now - lastRelayChangeAt) >= MIN_SWITCH_MS) {
setRelay(coolingDemand, now);
}
}
void sampleEnvironment() {
sampleAttempted = true;
const float humidity = dht.readHumidity();
const float temperature = dht.readTemperature();
if (!isfinite(humidity) || !isfinite(temperature)) {
currentValid = false;
currentTemperature = currentHumidity = NAN;
} else {
currentValid = true;
currentTemperature = temperature;
currentHumidity = humidity;
}
}
void reportSample() {
if (!currentValid) Serial.print("SENSOR ERROR: no current reading");
else {
Serial.print("T_C="); Serial.print(currentTemperature, 1);
Serial.print(" RH_pct="); Serial.print(currentHumidity, 1);
}
Serial.print(" target_C="); Serial.print(targetC);
Serial.print(" demand="); Serial.print(coolingDemand ? "ON" : "OFF");
Serial.print(" relay="); Serial.print(relayOn ? "ON" : "OFF");
Serial.println(currentValid && coolingDemand != relayOn ? " HOLD" : "");
}
void drawController() {
display.clearDisplay();
display.setCursor(0, 0); display.print("Cooling LED model");
display.setCursor(0, 12);
if (!sampleAttempted) display.print("Waiting for DHT11");
else if (!currentValid) display.print("SENSOR ERROR");
else {
display.print("T: "); display.print(currentTemperature, 1); display.print(" C");
display.setCursor(0, 22); display.print("RH: "); display.print(currentHumidity, 1); display.print(" %");
}
display.setCursor(0, 34); display.print("Target: "); display.print(targetC); display.print(" C");
display.setCursor(0, 44); display.print("Relay: "); display.print(relayOn ? "ON" : "OFF");
display.setCursor(0, 54);
if (currentValid && coolingDemand != relayOn) display.print("HOLD: minimum 5 s");
else if (!currentValid) display.print("Relay forced OFF");
else display.print("Demand: ");
if (currentValid && coolingDemand == relayOn) display.print(coolingDemand ? "ON" : "OFF");
display.display();
}
void setup() {
Serial.begin(115200);
pinMode(RELAY_DRIVER_PIN, OUTPUT);
digitalWrite(RELAY_DRIVER_PIN, LOW);
analogReadResolution(12);
analogSetPinAttenuation(POT_PIN, ADC_11db);
rawValue = analogRead(POT_PIN);
targetC = targetFromRaw(rawValue);
dht.begin();
ready = initializeDisplay();
if (!ready) return;
drawController();
Serial.println("Cooling LED model: first DHT sample after 2.5 s; initial relay OFF for at least 5 s.");
const uint32_t now = millis();
lastSampleAt = lastPotAt = lastDisplayAt = lastRelayChangeAt = now;
}
void loop() {
if (!ready) return;
uint32_t now = millis();
if (static_cast<uint32_t>(now - lastPotAt) >= POT_SAMPLE_MS) {
lastPotAt = now;
rawValue = analogRead(POT_PIN);
targetC = targetFromRaw(rawValue);
}
bool newSample = false;
if (static_cast<uint32_t>(now - lastSampleAt) >= SAMPLE_MS) {
lastSampleAt = now;
sampleEnvironment();
newSample = true;
}
now = millis();
evaluateDemand();
applyRelay(now);
if (newSample) reportSample();
if (newSample || static_cast<uint32_t>(now - lastDisplayAt) >= DISPLAY_MS) {
lastDisplayAt = now;
drawController();
}
}
initializeDisplay() koristi I2C na 100 kHz, skenira adrese i provjerava OLED_ADDRESS, podrazumijevano 0x3C. Vrijednost 0x3D postavi samo ako je potvrđena. Odgovor na adresi ne dokazuje tip kontrolera. Pri neuspješnoj pripremi ekrana redovni rad ne počinje i relej ostaje isključen.
Od potenciometra do zadate temperature
Program očitava GPIO34 na 100 ms, uz 12-bitni ADC i ADC_11db. targetFromRaw() računa 20 + raw * 10 / 4095: sirovo 0 daje 20 °C, 2048 daje 25 °C, a 4095 daje 30 °C. To je korisničko podešavanje, ne mjerenje napona niti temperature.
Promjena cilja odmah se razmatra uz posljednju važeću temperaturu, bez čekanja narednog DHT ciklusa. Cilj blizu granice koraka može oscilovati zbog šuma ADC-a; histereza i vremensko ograničenje zato imaju različite uloge.
Mjerenje, zahtjev i stvarni izlaz
sampleEnvironment() očitava DHT na 2500 ms, prvi put nakon tog intervala od pripreme prikaza. drawController() osvježava OLED približno na 250 ms. Brže osvježavanje ne stvara nov uzorak. Razlika vremena millis() omogućava raspoređivanje zadataka bez dugih delay() pauza.
evaluateDemand() računa coolingDemand iz važećeg T/RH para; applyRelay() upravlja stvarnim stanjem relayOn. U međuprostoru pragova zahtjev zadržava svoje stanje čak i kada stvarni relej još čeka. Oznaka HOLD znači da se važeći zahtjev razlikuje od izlaza, ali minimalni interval nije istekao. Početni OFF period takođe traje najmanje pet sekundi.
Šta se događa pri grešci
Ako temperatura ili vlažnost nije konačan broj, npr. NaN, currentValid postaje netačan, zahtjev se gasi, a relej se isključuje odmah po obradi tog pokušaja. Pet sekundi ne odlaže ovo isključivanje. Greška se otkriva tokom očitavanja, ne samim trenutkom nastanka fizičkog kvara. Adafruit — obrada očitavanja
Ako je relej stvarno prešao ON→OFF, od tog trenutka počinje novi minimalni OFF period. Ponavljane greške pri već ugašenom releju ne pokreću taj period iznova. Oporavak vraća mjerenja; ponovno uključivanje zahtijeva odgovarajući temperaturni prag i proteklo vrijeme. Nula se ne prikazuje kao zamjena za neuspjelo mjerenje.
Prvo pokretanje i praktični eksperiment
Posmatraj čekanje prvog uzorka, izmjerenu temperaturu i cilj. Polako okreni potenciometar ispod, zatim iznad sobne temperature, svaki put sačekaj više od pet sekundi. Prati kontaktno povezanu LED, a ne samo lampicu na relejnom modulu. Zapiši svoja opažanja; tabela sadrži očekivanja.
| Uslov uz važeći uzorak | Očekivanje | Tvoje opažanje |
|---|---|---|
| T ≥ cilj + 1; OFF traje najmanje 5 s | Zahtjev ON, relej ON | Dopuni |
| T ≤ cilj − 1; ON traje kraće od 5 s | Zahtjev OFF, relej ON, HOLD | Dopuni |
| Isti zahtjev nakon isteka minimuma | Relej OFF | Dopuni |
| T između pragova | Zahtjev zadržava prethodno stanje | Dopuni |
| Nema važećeg uzorka | Čekanje/greška; relej OFF | Dopuni |
Grešku početnog očitavanja provjeri isključivanjem USB-a, odspajanjem DHT DATA, pa ponovnim uključivanjem. Vrati spoj uz ugašeno napajanje. Ovaj postupak resetuje tajmere; ne dokazuje vremenski oporavak tokom istog rada.
Najčešći problemi
| Problem | Provjera |
|---|---|
| Relej reaguje suprotno | Potvrdi aktivni LOW modul i tranzistorsku inverziju. |
| Modul klikne, LED ne svijetli | Provjeri COM1, NO1, R3 i polaritet LED. |
| Temperatura nedostaje | Provjeri DHT11 tip, GPIO26, R4 i 3,3 V varijantu. |
| OLED ne počinje rad | Provjeri 21/22, adresu, kontroler i napajanje. |
| Cilj se mijenja, relej miruje | Provjeri oba praga, važenje uzorka i HOLD. |
| Ploča se resetuje pri prebacivanju | Provjeri USB kapacitet, masu i priključke modula. |
Samostalni izazov i razrađeno rješenje
Postavi cilj 24 °C. Pretpostavi važeće uzorke i početni OFF od t = 0: u t = 2,5 s temperatura je 26 °C; u t = 5 s i dalje 26 °C; u t = 7,5 s je 22 °C; u t = 10 s ostaje 22 °C. Odredi zahtjev i relej.
Rješenje: 2,5 s — zahtjev ON, relej OFF/HOLD; 5 s — ON/ON; 7,5 s — OFF/ON/HOLD; 10 s — OFF/OFF. Ako se umjesto niske temperature u 7,5 s pojavi greška, relej se gasi tada. Za novi ON mora proći najmanje do 12,5 s, uz važeći uzorak i ON zahtjev. Ovo su zadati primjeri, ne izmjerene vrijednosti.
Preuzimanja
Sačuvaj dnevnik opažanja. Računarska provjera logike, stvarna kompilacija i fizički eksperiment predstavljaju odvojene rezultate.
