Projekt zrealizowany z użytkownikiem @Rafał

Link do symulacji:
https://wokwi.com/projects/402925149826920449
Opis projektu
Projekt ma na celu automatyczne sterowanie procesem wędzenia potraw w domowej wędzarni. Sterownik kontroluje temperaturę za pomocą czujnika DS18B20, reguluje grzanie przez przekaźnik oraz prędkość wentylatora PWM. Użytkownik może ręcznie wybierać różne etapy procesu (ustawienia temperatury, czasu i wentylatora) przyciskiem, a cały przebieg jest wizualizowany na wyświetlaczu LCD I²C.
Uwaga: Na schemacie zamiast wentylatora wykorzystano diodę LED (wentylator nie był dostępny w bibliotece elementów). W tym miejscu należy podłączyć sterownik lub tranzystor wentylatora — nie podłączaj wentylatora bezpośrednio do pinów Arduino, ponieważ grozi to ich uszkodzeniem!
Podłączenie do Arduino
| Nazwa | Pin | Opis |
|---|---|---|
| czujnik_DS18B20 | 7 | OneWire – pomiar temperatury |
| przekaźnik_grzanie | 5 | Wyjście do sterowania elementem grzewczym |
| wentylator_PWM | 3 | Wyjście PWM – regulacja prędkości wentylatora |
| przycisk_tryb | 2 | Wejście z przycisku wyboru trybu (INPUT_PULLUP, PCINT18) |
| LCD_SDA | A4 | I²C SDA – linia danych dla wyświetlacza LCD |
| LCD_SCL | A5 | I²C SCL – linia zegara dla wyświetlacza LCD |
Kod programu:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
//******************************************************************************************** //**********************PROGRAM STEROWNIKA WĘDZARNI******************************************* //**************************WWW.ARDUINOWO.PL************************************************** //******************************************************************************************** #include <LiquidCrystal_I2C.h> // Dołączenie biblioteki LiquidCrystal_I2C dla wyświetlacza LCD #include <OneWire.h> // Dołączenie biblioteki OneWire dla komunikacji z czujnikiem temperatury #include <DallasTemperature.h> // Dołączenie biblioteki DallasTemperature do obsługi czujnika temperatury #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // Makro do ustawiania bitów w rejestrach volatile boolean flaga = 0; // Zmienna typu boolean do sterowania flagą // Tworzenie obiektu dla wyświetlacza LCD z adresem I2C, liczba kolumn i wierszy LiquidCrystal_I2C lcd(0x27, 20, 4); volatile byte tryb = 0; // Zmienna do zliczania naciśnięć przycisku const byte opoznienie = 25; // Opóźnienie odsprzęgania w milisekundach unsigned long ostatni_czas1 = millis(); // Czas ostatniego zmienia stanu pierwszego przycisku boolean poprzedni_stan1 = LOW; // Poprzedni stan pierwszego przycisku int etap = 1; // Zmienna etapu long czas; // Zmienna czasu float temperatura_aktualna=0; // Zmienna aktualnej temperatury byte wentylator_aktualny; // Zmienna aktualnej prędkości wentylatora float temperatura_zadana; // Ustawienie żądanej temperatury boolean grzanie=0; // Zmienna sterująca przekaźnikiem (0 - wyłączony, 1 - włączony) //******************************************************************************************** //*******************************KONFIGURACJA************************************************* //******************************************************************************************** // Deklaracja i inicjalizacja zmiennej globalnej - użycie pinu nr 7 dla czujnika temperatury const byte nr_pinu = 7; // Definicja numeru pinu, do którego podłączony jest przekaźnik const int pin_przek = 5; const int pwmPin = 3; // Definicja numeru pinu PWM // Tablica z ustawieniami temperatury dla poszczególnych etapów w stopniach C float temperatura[] = {40.00, 50.00, 60.00, 35.00}; // Tablica z czasami trwania poszczególnych etapów w sekundach unsigned int sekundnik[] = {13, 14, 15, 16}; // Tablica z ustawieniami prędkości wentylatora dla poszczególnych etapów w procentach byte wentylator[] = {10, 20, 100, 23}; float histereza = 2; // Ustawienie wartości histerezy dla temperatury // Tworzenie obiektu dla protokołu OneWire OneWire oneWire(nr_pinu); // Tworzenie obiektu dla czujnika temperatury DallasTemperature sensor(&oneWire); //******************************************************************************************** //*******************************FUNKCJA SETUP************************************************ //******************************************************************************************** void setup() { //Ustawienie wartości początkowych czas = sekundnik[0]; wentylator_aktualny = wentylator[0]; temperatura_zadana = temperatura[0]; // TIMER 1 for interrupt frequency 1 Hz: cli(); // stop interrupts TCCR1A = 0; // set entire TCCR1A register to 0 TCCR1B = 0; // same for TCCR1B TCNT1 = 0; // initialize counter value to 0 // set compare match register for 1 Hz increments OCR1A = 62499; // = 16000000 / (256 * 1) - 1 (must be <65536) // turn on CTC mode TCCR1B |= (1 << WGM12); // Set CS12, CS11 and CS10 bits for 256 prescaler TCCR1B |= (1 << CS12) | (0 << CS11) | (0 << CS10); // enable timer compare interrupt TIMSK1 |= (1 << OCIE1A); sei(); // allow interrupts lcd.init(); // Inicjalizacja wyświetlacza LCD lcd.backlight(); // Włączenie podświetlenia wyświetlacza sensor.begin(); // Inicjalizacja czujnika temperatury Serial.begin(9600); // Rozpoczęcie komunikacji szeregowej z prędkością 9600 bps pinMode(2, INPUT_PULLUP); // Ustawienie pinu 2 jako wejście z podciągnięciem do VCC sbi(PCICR, PCIE2); // Włączenie przerwań pin-change dla grupy PCINT2 sbi(PCMSK2, PCINT18); // Włączenie przerwania dla pinu PCINT18 (pin 2) pinMode(pin_przek, OUTPUT); // Ustawienie pinu przekaźnika jako wyjście digitalWrite(pin_przek, grzanie); // Ustawienie początkowego stanu przekaźnika pinMode(pwmPin, OUTPUT); // Ustawienie pinu PWM jako wyjście } //******************************************************************************************** //*******************************FUNKCJA LOOP************************************************* //******************************************************************************************** void loop() { sensor.requestTemperatures(); // Żądanie odczytu temperatury z czujnika temperatura_aktualna = sensor.getTempCByIndex(0); // Pobranie odczytu temperatury // Wyświetlanie trybu pracy na wyświetlaczu LCD if (tryb == 0) { wyswietlanie_trybu(tryb, temperatura_aktualna, czas, wentylator_aktualny); } else { wyswietlanie_trybu(tryb, temperatura[tryb - 1], sekundnik[tryb - 1], wentylator[tryb - 1]); } // Implementacja logiki histerezy dla grzania if (temperatura_aktualna < temperatura_zadana - histereza && grzanie == 0) { grzanie = 1; // Włączenie grzania, gdy temperatura jest znacznie poniżej żądanej } else if (temperatura_aktualna > temperatura_zadana + histereza && grzanie == 1) { grzanie = 0; // Wyłączenie grzania, gdy temperatura przekracza żądaną wartość } digitalWrite(pin_przek, grzanie); // Sterowanie przekaźnikiem // Sterowanie prędkością wentylatora za pomocą PWM analogWrite(pwmPin, (wentylator_aktualny * 255) / 100); } //******************************************************************************************** //*******************************PRZERWANIE PIN*********************************************** //******************************************************************************************** ISR(PCINT2_vect) { boolean stan1 = !bitRead(PIND, 2); // Odczytanie bieżącego stanu trzeciego przycisku if (stan1 != poprzedni_stan1) // Sprawdzenie, czy stan trzeciego przycisku się zmienił { // Sprawdzenie, czy przycisk jest wciśnięty i czy upłynęło odpowiednio // dużo czasu od ostatniego wciśnięcia if (stan1 == LOW && millis() - ostatni_czas1 > opoznienie) { tryb++; // Inkrementacja zmiennej "tryb" if (tryb > 4) { tryb = 0; // Reset wartości "tryb" po przekroczeniu 2 } // lcd.clear(); } // Aktualizacja czasu ostatniego wciśnięcia, gdy przycisk jest zwolniony if (stan1 == HIGH) ostatni_czas1 = millis(); } poprzedni_stan1 = stan1; // Aktualizacja poprzedniego stanu trzeciego przycisku } //******************************************************************************************** //*******************************PRZERWANIE ZEGARA******************************************** //******************************************************************************************** ISR(TIMER1_COMPA_vect) { czas--; // Dekrementacja zmiennej czasu if (czas < 0) { // Sprawdzenie, czy czas jest mniejszy niż 0 if (etap != 5) { // Sprawdzenie, czy etap nie jest równy 5 etap++; // Inkrementacja etapu } if (etap == 2) { // Sprawdzenie, czy etap jest równy 2 czas = sekundnik[1]; // Ustawienie czasu na wartość z tablicy sekundnik dla etapu 2 wentylator_aktualny = wentylator[1]; // Ustawienie prędkości wentylatora dla etapu 2 temperatura_zadana = temperatura[1]; // Ustawienie temperatury zadanej dla etapu 2 } if (etap == 3) { // Sprawdzenie, czy etap jest równy 3 czas = sekundnik[2]; // Ustawienie czasu na wartość z tablicy sekundnik dla etapu 3 wentylator_aktualny = wentylator[2]; // Ustawienie prędkości wentylatora dla etapu 3 temperatura_zadana = temperatura[2]; // Ustawienie temperatury zadanej dla etapu 3 } if (etap == 4) { // Sprawdzenie, czy etap jest równy 4 czas = sekundnik[3]; // Ustawienie czasu na wartość z tablicy sekundnik dla etapu 4 wentylator_aktualny = wentylator[3]; // Ustawienie prędkości wentylatora dla etapu 4 temperatura_zadana = temperatura[3]; // Ustawienie temperatury zadanej dla etapu 4 } if (etap == 5) { // Sprawdzenie, czy etap jest równy 5 czas = 0; // Ustawienie czasu na 0 wentylator_aktualny = 0; // Wyłączenie wentylatora temperatura_zadana = 0; // Ustawienie temperatury zadanej na 0 } } } //******************************************************************************************** //*******************************FUNKCJE****************************************************** //******************************************************************************************** void wyswietlanie_trybu(byte tryb_f, float temp, unsigned long sekundy, byte went) { lcd.setCursor(0, 0); // Ustawienie kursora na początku pierwszego wiersza if (tryb_f != 0) { // Sprawdzenie, czy tryb jest różny od 0 lcd.print(" Ust. Etap_"); // Wyświetlenie tekstu "Ust. Etap_" lcd.print(tryb); // Wyświetlenie numeru aktualnego trybu lcd.print(" "); // Wyświetlenie dwóch spacji } else { // Jeśli tryb jest równy 0 lcd.print("Proces: "); // Wyświetlenie tekstu "Proces: " if (etap != 5) { // Sprawdzenie, czy etap jest różny od 5 lcd.print("Etap_"); // Wyświetlenie tekstu "Etap_" lcd.print(etap); // Wyświetlenie numeru aktualnego etapu } else { // Jeśli etap jest równy 5 lcd.print("KONIEC"); // Wyświetlenie tekstu "KONIEC" } } lcd.setCursor(2, 1); // Ustawienie kursora lcd.print("Temp: "); // Wyświetlenie tekstu "Temp:" lcd.print(temp + 0.05, 1); // Wyświetlenie temperatury z dokładnością do 1 miejsca po przecinku lcd.write(0b11011111); // Symbol stopnia lcd.print("C"); // Symbol stopnia Celsjusza lcd.print(" "); // Czyszczenie niepotrzebnych znaków lcd.setCursor(2, 2); // Ustawienie kursora lcd.print("Czas: "); // Wyświetlenie tekstu "Czas:" lcd.print(sekundy); // Wyświetlenie pozostałego czasu lcd.print("s"); // Wyświetlenie symbolu sekund lcd.print(" "); // Czyszczenie niepotrzebnych znaków lcd.setCursor(2, 3); // Ustawienie kursora lcd.print("Went: "); // Wyświetlenie tekstu "Went:" lcd.print(went); // Wyświetlenie aktualnej prędkości wentylatora lcd.print("%"); // Wyświetlenie symbolu procenta lcd.print(" "); // Czyszczenie niepotrzebnych znaków } |
Wytłumaczenie kodu programu:
1. Nagłówki bibliotek i makra
W tej sekcji dołączamy niezbędne biblioteki oraz definiujemy makro do ustawiania bitów w rejestrach AVR.
|
1 2 3 4 5 6 |
#include <LiquidCrystal_I2C.h> // Sterowanie LCD przez I²C #include <OneWire.h> // Protokół 1-Wire dla DS18B20 #include <DallasTemperature.h> // Obsługa DS18B20 #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // Ustawianie bitów volatile boolean flaga = 0; // Flaga do przerwań |
2. Zmienne globalne i obiekty sprzętowe
Tu deklarujemy wszystkie zmienne sterujące trybem pracy, tablice z parametrami etapów oraz obiekty dla wyświetlacza i sensora.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
volatile byte tryb = 0; // 0=auto, 1–4=ręczne etapy const byte opoznienie = 25; // Debounce przycisku (ms) unsigned long ostatni_czas1 = millis(); boolean poprzedni_stan1 = LOW; int etap = 1; // Numer bieżącego etapu long czas; // Licznik sekund float temperatura_aktualna = 0; // Odczyt z DS18B20 float temperatura_zadana; // Cel histerezy boolean grzanie = 0; // Stan przekaźnika byte wentylator_aktualny; // PWM wentylatora const byte nr_pinu = 7; // Pin DS18B20 const int pin_przek = 5; // Przekaźnik grzałki const int pwmPin = 3; // Wyjście PWM float temperatura[] = {40.0,50.0,60.0,35.0}; unsigned int sekundnik[] = {13,14,15,16}; byte wentylator[] = {10,20,100,23}; float histereza = 2.0; // Bufor ±2°C LiquidCrystal_I2C lcd(0x27, 20, 4); // LCD 20×4, adres 0x27 OneWire oneWire(nr_pinu); DallasTemperature sensor(&oneWire); |
3. Funkcja setup()
Konfiguracja początkowa: Timer1 co sekundę, LCD, sensor, przycisk z przerwaniami oraz wyjścia.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void setup() { // 1) Pierwsze wartości czas = sekundnik[0]; wentylator_aktualny = wentylator[0]; temperatura_zadana = temperatura[0]; // 2) Timer1 – CTC 1 Hz cli(); TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; OCR1A = 62499; // 16MHz/256/1Hz – 1 TCCR1B |= (1 << WGM12) | (1 << CS12); TIMSK1 |= (1 << OCIE1A); sei(); // 3) LCD i sensor lcd.init(); lcd.backlight(); sensor.begin(); Serial.begin(9600); // 4) Przycisk PCINT na pinie 2 pinMode(2, INPUT_PULLUP); sbi(PCICR, PCIE2); sbi(PCMSK2, PCINT18); // 5) Wyjścia pinMode(pin_przek, OUTPUT); digitalWrite(pin_przek, grzanie); pinMode(pwmPin, OUTPUT); } |
4. Główna pętla loop()
Odczyt temperatury, wyświetlanie trybu, algorytm histerezy i sterowanie wentylatorem przez PWM.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void loop() { // A) Odczyt temperatury sensor.requestTemperatures(); temperatura_aktualna = sensor.getTempCByIndex(0); // B) Wyświetlanie (auto vs ręczne) if (tryb == 0) wyswietlanie_trybu(0, temperatura_aktualna, czas, wentylator_aktualny); else wyswietlanie_trybu(tryb, temperatura[tryb-1], sekundnik[tryb-1], wentylator[tryb-1]); // C) Histereza grzałki if (temperatura_aktualna < temperatura_zadana - histereza && !grzanie) grzanie = 1; else if (temperatura_aktualna > temperatura_zadana + histereza && grzanie) grzanie = 0; digitalWrite(pin_przek, grzanie); // D) PWM wentylatora analogWrite(pwmPin, (wentylator_aktualny * 255) / 100); } |
5. Przerwanie Pin-Change (PCINT2_vect)
Obsługa przycisku na pinie 2 z filtrem drgań styków (debounce).
|
1 2 3 4 5 6 7 8 9 10 11 |
ISR(PCINT2_vect) { boolean stan1 = !bitRead(PIND, 2); if (stan1 != poprzedni_stan1) { if (stan1 == LOW && millis() - ostatni_czas1 > opoznienie) { tryb++; if (tryb > 4) tryb = 0; } if (stan1 == HIGH) ostatni_czas1 = millis(); } poprzedni_stan1 = stan1; } |
6. Przerwanie Timer1 (TIMER1_COMPA_vect)
Wywoływane co sekundę – dekrementacja licznika i przejście do kolejnych etapów.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
ISR(TIMER1_COMPA_vect) { czas--; if (czas < 0) { etap++; if (etap <= 4) { int i = etap - 1; czas = sekundnik[i]; wentylator_aktualny = wentylator[i]; temperatura_zadana = temperatura[i]; } else { // etap 5 = KONIEC czas = 0; wentylator_aktualny = 0; temperatura_zadana = 0; } } } |
7. Funkcja wyświetlania wyswietlanie_trybu()
Rysuje na LCD aktualny proces lub ręczne ustawienia etapu.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
void wyswietlanie_trybu(byte tryb_f, float temp, unsigned long sekundy, byte went) { lcd.setCursor(0, 0); if (tryb_f) { lcd.print(" Ust. Etap_"); lcd.print(tryb_f); lcd.print(" "); } else { lcd.print("Proces: "); if (etap < 5) { lcd.print("Etap_"); lcd.print(etap); } else { lcd.print("KONIEC"); } } lcd.setCursor(2, 1); lcd.print("Temp: "); lcd.print(temp + 0.05, 1); lcd.write(0b11011111); lcd.print("C "); lcd.setCursor(2, 2); lcd.print("Czas: "); lcd.print(sekundy); lcd.print("s "); lcd.setCursor(2, 3); lcd.print("Went: "); lcd.print(went); lcd.print("% "); } |
Instrukcja zmiany parametrów wędzenia
Poniżej instrukcja krok po kroku wraz z przykładami, jak zmienić ustawienia etapów w oryginalnym kodzie sterownika.
- Otwórz plik z kodem w Arduino IDE (lub innym edytorze).
-
Zlokalizuj definicje tablic zawierających parametry etapów:
Kod_009 – oryginał 123float temperatura[] = {40.00, 50.00, 60.00, 35.00};unsigned int sekundnik[] = { 13, 14, 15, 16 };byte wentylator[] = {10, 20, 100, 23 }; -
Przykład 1: zmiana parametrów etapu 3
Chcemy, aby etap 3 pracował w 55 °C przez 600 s z wentylatorem 40 %:Kod_010 – przykład dla etapu 3 123float temperatura[] = {40.00, 50.00, 55.00, 35.00}; <!-- etap 3 = 55 °C -->unsigned int sekundnik[] = { 13, 14, 600, 16 }; <!-- etap 3 = 600 s -->byte wentylator[] = {10, 20, 40, 23 }; <!-- etap 3 = 40 % --> -
Przykład 2: zmiana parametrów etapów 1 i 2
Chcemy, aby etap 1 trwał 20 s przy 45 °C i wentylator 15 %, a etap 2 – 18 s przy 55 °C i wentylator 30 %:Kod_011 – przykład dla etapów 1 i 2 123float temperatura[] = {45.00, 55.00, 60.00, 35.00}; <!-- etap 1 = 45 °C, etap 2 = 55 °C -->unsigned int sekundnik[] = { 20, 18, 15, 16 }; <!-- etap 1 = 20 s, etap 2 = 18 s -->byte wentylator[] = {15, 30, 100, 23 }; <!-- etap 1 = 15 %, etap 2 = 30 % --> - Zapisz zmiany i wgraj program na płytkę Arduino.
-
Weryfikacja w trakcie pracy:
- Uruchom sterownik.
- Przejdź przyciskiem „Tryb” do „Ust. Etap_X” odpowiadającego zmienionemu etapowi.
- Sprawdź na LCD nowe wartości temperatury, czasu i wentylatora.
- Wciśnij „Tryb” ponownie, aby wrócić do automatycznego cyklu („Proces: Etap_X”).
W ten sposób możesz precyzyjnie dostosować dowolny etap wędzenia, zmieniając tylko odpowiednie elementy w tablicach bez ingerencji w inne fragmenty kodu.

Dalsze kroki i możliwe usprawnienia
Poniżej kilka pomysłów, jak rozbudować i udoskonalić sterownik wędzarni:
- Dodanie przycisków – umożliwi nawigację po menu i ręczne zmienianie parametrów bez potrzeby podłączania Arduino do komputera.
- Dodanie pamięci EEPROM – pozwoli zachować wprowadzone ustawienia (temperatury, czasy etapów, prędkość wentylatora) nawet po wyłączeniu zasilania.
- Regulacja PID zamiast prostej histerezy – pozwoli na płynniejsze i bardziej precyzyjne utrzymanie zadanej temperatury z mniejszymi odchyleniami.
- Logowanie danych na kartę SD lub do wewnętrznej pamięci – archiwizacja przebiegu temperatury, czasu i ustawień wentylatora do późniejszej analizy.
- Rozbudowane menu na LCD – stworzenie hierarchicznego interfejsu, w którym użytkownik może definiować i zapisywać własne profile wędzenia.
- Łączność bezprzewodowa (Bluetooth/Wi-Fi) – zdalne monitorowanie i sterowanie z poziomu smartfona lub komputera.
- Alarmy i powiadomienia – wizualne i dźwiękowe sygnały ostrzegawcze lub wysyłanie powiadomień SMS/e-mail po zakończeniu cyklu lub w razie krytycznego odchylenia temperatury.
- Kalibracja czujnika – możliwość ręcznego lub automatycznego dostrajania czujnika DS18B20 w celu zwiększenia dokładności pomiarów.
- Funkcje bezpieczeństwa – automatyczne wyłączenie grzałki w razie awarii wentylatora, zwarcia lub braku detekcji temperatury.
- Dynamiczne profile etapów – możliwość definiowania dowolnej liczby etapów oraz zapisywania ich do późniejszego użycia.
Wprowadzenie tych usprawnień pozwoli na bardziej precyzyjną i komfortową obsługę wędzarni oraz na zbieranie wartościowych danych do dalszej optymalizacji procesu.