KY-040 does not respond // Arduino Dehydrator

Hey, I'm trying to build a Dehydrator with Arduino as my first self-sufficient Arduino project (go easy on me :'D) and I wanted to use a KY-040 to set a temperature value which is also printed onto an LCD. Everything works except for the recognition of turns from the rotary encoder and accordingly the "Soll." (set temperature) Value doesn't rises.

I found a few routines of a KY-040 in use that were to complicated for my understanding and accordingly i couldn't frankenstein the usefull parts into my patch.

I based mine on this KY-040 code example
https://www.linkerkit.de/index.php?title=KY-040_Kodierter_Drehschalter_(Rotary_Encoder)

It prints into the serial monitor, but its easy to comprehend and I thought I made a good job adjusting it to my situation but obviously that wasn't the case.

Anyway I would like to stick to this procedure if possible because its easy to comprehend and I prefer understanding the stuff that I'm doing and not just copy-paste magic into my sketch that I have no clue of.

Anyway here is my Sketch, thanks for help in advance (sorry for the german worde here and there):

// Doofenschmirtz Dörrinator
// Mads Duggen
// Interactive Prototyping 2021
// Muthesius Kunsthochschule

// ARDUINO PIN USAGE

// LCD with I2C module
// A5 - SCL
// A4 - SDA
// 5v - VCC
// GND - GND

// DHT 11 sensor
// 8 - OUTPUT
// 5v - VCC
// GND - GND

// Heater and fan relay CH1 & CH2
// 10 - CH1
// 11 - CH2
// 5v - DC+
// GND - DC-

// KY-040 rotary encoder
// 3 - CLK
// 4 - DT
// 5 - SW
// 5v - +
// GND - GND

//Utilised libarys
#include <DHT.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>

// Data-Pin and sensortype
#define DHTPIN 8
#define DHTTYPE DHT11

// KY-040 pins
int CLK = 3;
int DT = 4;
int SW = 5;

int solltemperatur;
int temperatur;
float luftfeuchtigkeit;
int clkletzter;
int clkaktuell;

// LCD und DHT objects
DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {

// initialising relay pins
pinMode(10, OUTPUT);
pinMode(11, OUTPUT);

// initialising KY-040 pins
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
pinMode(SW,INPUT);

digitalWrite(CLK, true);
digitalWrite(DT, true);
digitalWrite(SW, true);

//initial read CLK pin
clkletzter = digitalRead(CLK);

// Welcoming Screen

dht.begin(); // initialising DHT11 sensor
lcd.backlight(); // Start backlight
lcd.begin(); // initialising LCD

delay(2000);

lcd.setCursor( 0, 0);
lcd.print(" Doofenschmirtz");
lcd.setCursor( 0, 1);
lcd.print(" Doerrinator");

delay(5000);

}

void loop() {

// Read temperature und humidity from DHT11 and Print to LCD

solltemperatur = 0; // The manually set temperature
temperatur = dht.readTemperature(); // The environment temperature
luftfeuchtigkeit = dht.readHumidity(); // The environment humidity (not in use atm)

lcd.clear();

// read the "now" value
clkaktuell = digitalRead(CLK);

// notice turns
if(clkaktuell != clkletzter) {
// count up if CLK is triggered first
if(digitalRead(DT) != clkaktuell){
solltemperatur ++;
}
// count down if DT is triggered first
else {
solltemperatur --;
}
}

// low end limit of set temperature
if(solltemperatur < 25) {
solltemperatur = 25;
}
// high end limit of set temperature
if(solltemperatur > 70) {
solltemperatur = 70;
}

//print the environment temperature to the first row of the LCD
lcd.setCursor( 0, 0);
lcd.print("Temp. :");
lcd.print(temperatur);
lcd.print("C");

//print the environment humidity to the second row of the LCD
lcd.setCursor( 0, 1);
lcd.print("Soll. :");
lcd.print(solltemperatur); // Changeable Value representing the set temperature of the Dehydrator
lcd.print("C");

// turn the relay on if the environment temperature is lower
// than the set temperature
if(solltemperatur < temperatur){
digitalWrite(10, HIGH);
digitalWrite(11, HIGH);
}
// turn of if not
else {
digitalWrite(10, LOW);
digitalWrite(11, LOW);
}

// update Sequenz DHT11
delay(2000);

lcd.clear();

}

Have you checked that the LCD works with its "hello world" program?
That is the first thing to do.

Do not clear the LCD and write data to it each time through the loop, but only when you detect the information has changed from the last time you have displayed it.

Yes I tested the LCD, the DHT11, and the KY-040 Rotary Encoder with test programs (The rotary with the serial monitor sketch from the link of my initial request).

All parts (exept for the rotary) are also working accordingly, the DHT-11 notices the environment temperature which is also printed on the LCD. The relais for the Heating Element and the Fan are powered on because the temperature in my room is lower than 25C ( as shown on the linked picture)

So have you tried printing to the serial monitor when the code is also suppose to write to the LCD?

No I adjusted it to my LCD Situation and got rid of the the part that was printing to the serial monitor.

I got it to print into the serial monitor in a separate program.

Would there be an advantage, if I integrate the serial monitor part into the program shown above ? How would the transition to the LCD look ?

I think he just wants to isolate where the problem is. Is it reading the decoder, or is it displaying the result.

Anyway, the KY-040 code you're working from has an additional instruction which you don't have. I think it would look like this:

// notice turns
if(clkaktuell != clkletzter) {
// count up if CLK is triggered first
if(digitalRead(DT) != clkaktuell){
solltemperatur ++;
}
// count down if DT is triggered first
else {
solltemperatur --;
}
clkletzter =  clkaktuell;   // <---------------
}

But I think this method of decoding the KY-040 will give you two increments or decrements per detent. And it doesn't provide for switch debouncing. So it turns out that there are reasons for the more complicated decoder versions.

Ah ok, I tried that but that wasn't the problem (still doesn't work).

I think I probably scrap the rotary encoder idea and use 2 push buttons (one for counting up and one for counting down instead)

Does the example program you mentioned work with your encoder? It's not unusual to get a bad encoder, so make sure it's working first. Also, does your KY-040 have the pullup resistors on the board or not. Sometime you need to use the pinmode() function to set the pullups.

Yes that is right, divide and conquer.

It is hardly integration it is just a few serial print statements to see what is going on. It is a standard debugging technique.

So what didn't work? Please describe what happened.
Do you get the encoder to change the numbers you print to the monitor but still not to the display?
Or something else?

You should post code by using code-tags
There is an automatic function for doing this in the Arduino-IDE
just three steps

  1. press Ctrl-T for autoformatting your code
  2. do a rightclick with the mouse and choose "copy for forum"
  3. paste clipboard into write-window of a posting

best regards Stefan

If I'm reading your code correctly, you have a 2-second delay on each iteration of the loop(). That's not going to work reading an encoder, or switches.

Yes this is true
at the bottom of your loop there is a delay(2000);

  // update Sequenz DHT11
  delay(2000);

  lcd.clear();
}

This means that all and every action only happends after 2 seconds.

Your code is one of that very often occuring cases where the command delay is delaying the development-process much more than taking time to learn non-blocking timing.
@Arduino-Team: take out the command delay() out of EVERY example-code!
take in a non-blocking version of timing.

There are very good reasons to execute parts of the code only once every two seconds or even longer time-intervals. This should be programmed using non-blocking timing.

There might be users that don't like my version of non-blocking timing
my opinion:
The advantages far outweigh the disadvantages because the external function is fairly easy to understand. And this is what is important for beginners.

As your programming knowledge grows, you will also understand the inner workings.

And I made a very conscious decision by posting my version very often to try to make it the new de-facto-standard against the common used version.
If somebody feels the need to go against this. Of course you are free to do so. I claim: most users are just used to use the "standard"-nonblocking timing and are too lazy to learn my version. Users you will have to give up lazyness and take the effort of posting your version including easy to understand explanations each time.

Back to programming:
the basic principle of non-blocking timing is to check if a defined timeinterval
has passed by.

This can be done by using the function millis()
The function millis() gives back the amount of milliseconds (hence the name millis)
that have passed by since power-up of the microcontroller.
It counts up to 2^32 which means reaching the max-value is reached after 49 days.
There is a calculation-technique that even "rollover" from max to zero is handled
automatically the right way

This non-blocking timing needs a timer-variable which is used for taking
snapshots of time as a comparison-point

The variable-type for this variable MUST be of type unsigned long
to make it work reliably all the time

unsigned long myLcdUpdateTimer;

now the following construction executes the code inside the if-condition
only once every two seconds

  if ( TimePeriodIsOver(myLcdUpdateTimer,2000) ) {
     // time for timed action
  }  

Here is the modified version of your function loop()

void loop() {

  // Read temperature und humidity from DHT11 and Print to LCD
  solltemperatur = 0; // The manually set temperature
  temperatur = dht.readTemperature(); // The environment temperature
  luftfeuchtigkeit = dht.readHumidity(); // The environment humidity (not in use atm)

  //lcd.clear();

  // read the "now" value
  clkaktuell = digitalRead(CLK);

  // notice turns
  if (clkaktuell != clkletzter) {
    // count up if CLK is triggered first
    if (digitalRead(DT) != clkaktuell) {
      solltemperatur ++;
    }
    // count down if DT is triggered first
    else {
      solltemperatur --;
    }
  }

  // low end limit of set temperature
  if (solltemperatur < 25) {
    solltemperatur = 25;
  }
  // high end limit of set temperature
  if (solltemperatur > 70) {
    solltemperatur = 70;
  }

  // turn the relay on if the environment temperature is lower
  // than the set temperature
  if (solltemperatur < temperatur) {
    digitalWrite(10, HIGH);
    digitalWrite(11, HIGH);
  }
  // turn of if not
  else {
    digitalWrite(10, LOW);
    digitalWrite(11, LOW);
  }

  // update Sequenz DHT11
  //delay(2000);
  if ( TimePeriodIsOver(myLcdUpdateTimer, 2000) ) {
    printToLCD(); // update LCD only once every 2000 milliseconds
  }

the difference is at the bottom: This code makes the loop "looping at high speed but updates the LCD only once every 2 seconds. This enables to detect turning the rotary-encoder again.

But I want to add something: The basic intention of understanding code is good. But If a part of the code works 100% reliably and is used in always the same manner it can be hided away.
Example

digitalWrite(10,HIGH);

there is a lt of going on "under the hood" to switch an IO-pin HIGH
but you simply don't know the details and you don't feel any need to do so.

It is the same thing with libraries.
Did you analyse a single line of code inside the files
LiquidCrystal_I2C.h // LiquidCrystal_I2C.cpp
I'm pretty sure you didn't even know that there is a second file LiquidCrystal_I2C.cpp

So with the timing function "IfTimePeriodIsOver() that I inserted into your code it is the same. Still understanding this function is 90% easy. (There are 10% left caused by the "&"-symbol in front of the first parameter)

Here is the full code that compiles. I have marked each part that has been modified by me with a looooong line like this

// ********************************************************************
// Doofenschmirtz Dörrinator
// Mads Duggen
// Interactive Prototyping 2021
// Muthesius Kunsthochschule

// ARDUINO PIN USAGE

// LCD with I2C module
// A5 - SCL
// A4 - SDA
// 5v - VCC
// GND - GND

// DHT 11 sensor
// 8 - OUTPUT
// 5v - VCC
// GND - GND

// Heater and fan relay CH1 & CH2
// 10 - CH1
// 11 - CH2
// 5v - DC+
// GND - DC-

// KY-040 rotary encoder
// 3 - CLK
// 4 - DT
// 5 - SW
// 5v - +
// GND - GND

//Utilised libarys
#include <DHT.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>

// Data-Pin and sensortype
#define DHTPIN 8
#define DHTTYPE DHT11

// KY-040 pins
int CLK = 3;
int DT = 4;
int SW = 5;

int solltemperatur;
int temperatur;
float luftfeuchtigkeit;
int clkletzter;
int clkaktuell;

// LCD und DHT objects
DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);

//***********************************************
unsigned long myLcdUpdateTimer;

//*****************************************************************
// helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - expireTime >= TimePeriod )
  {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}
// ********************************************************************


void setup() {

  // initialising relay pins
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);

  // initialising KY-040 pins
  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT);

  digitalWrite(CLK, true);
  digitalWrite(DT, true);
  digitalWrite(SW, true);

  //initial read CLK pin
  clkletzter = digitalRead(CLK);

  // Welcoming Screen

  dht.begin(); // initialising DHT11 sensor
  lcd.backlight(); // Start backlight
  lcd.begin(); // initialising LCD

  delay(2000);

  lcd.setCursor( 0, 0);
  lcd.print(" Doofenschmirtz");
  lcd.setCursor( 0, 1);
  lcd.print(" Doerrinator");

  delay(5000);
}

//**************************************************
// each part of the code that form a senseful unit
// should be inside its own function
void printToLCD() {
  lcd.clear();

  //print the environment temperature to the first row of the LCD
  lcd.setCursor( 0, 0);
  lcd.print("Temp. :");
  lcd.print(temperatur);
  lcd.print("C");

  //print the environment humidity to the second row of the LCD
  lcd.setCursor( 0, 1);
  lcd.print("Soll. :");
  lcd.print(solltemperatur); // Changeable Value representing the set temperature of the Dehydrator
  lcd.print("C");
}
// **********************************************************************

void loop() {

  // Read temperature und humidity from DHT11 and Print to LCD
  solltemperatur = 0; // The manually set temperature
  temperatur = dht.readTemperature(); // The environment temperature
  luftfeuchtigkeit = dht.readHumidity(); // The environment humidity (not in use atm)

  //lcd.clear();

  // read the "now" value
  clkaktuell = digitalRead(CLK);

  // notice turns
  if (clkaktuell != clkletzter) {
    // count up if CLK is triggered first
    if (digitalRead(DT) != clkaktuell) {
      solltemperatur ++;
    }
    // count down if DT is triggered first
    else {
      solltemperatur --;
    }
  }

  // low end limit of set temperature
  if (solltemperatur < 25) {
    solltemperatur = 25;
  }
  // high end limit of set temperature
  if (solltemperatur > 70) {
    solltemperatur = 70;
  }

  // turn the relay on if the environment temperature is lower
  // than the set temperature
  if (solltemperatur < temperatur) {
    digitalWrite(10, HIGH);
    digitalWrite(11, HIGH);
  }
  // turn of if not
  else {
    digitalWrite(10, LOW);
    digitalWrite(11, LOW);
  }

  // update Sequenz DHT11
  //delay(2000);
  if ( TimePeriodIsOver(myLcdUpdateTimer, 2000) ) {
    printToLCD(); // update LCD only once every 2000 milliseconds
  }

}

best regards Stefan

1 Like

Thanks sooo much for the detailed, time intense, answer Stefan, that was very enlightening.

I already suspected that there could be a problem with the delay, but felt obligated to leave it in because it was suggested in every of the DHT11 examples, because the refresh rate of the Sensor is pretty low. And as i read thru the debugged version of my sketch that was suggested by you I thought that could may cause trouble, but that part works just fine.

However the set Value (solltemperatur) still doesn't rises (or lowers) when I turn on the rotary. I thought it's probably toasted after all that trial and error so I tested it again with the KY-040 example program and the modified version (sirial monitor version) of my program but both times I was able get a result in the serial monitor, so it seems to be fine.

Unfortunately I have an exam tomorrow so I don't have enough time for a deep dive into debugging right now. I will try to figure it out tomorrow with a little more time and a little more focused state of mind.

Thanks again

Mads

So I figured it out. The worst flaw was the

solltemperatur = 0;

part at the beginning of my loop. It basically resettet the set temperature value to 0 immediately so no wonder that it wouldn't rise. I redirected this part to the setup.

To get it to work i had to set the Millis in the non blocking version of Timing suggested by Stefan to <100, but from an angle you could see the display flicker because of the fast update rate, and such a high update rate wasn't recommended for the DHT-11 anyways (It still worked tho ...). So I made the display update whenever a turn of the encoder was detected (as suggested by Grumpy_Mike) and also every 2 seconds to get an Updated environment temperature from the DHT-11.

The KY-040 wasn't working very reliably, it skipped over quite a few numbers and didn't felt very smooth. I changed to a Grove-Encoder from Seeed and used the Encoder.h and TimerOne.h library as suggested on the Seeed Wiki (it was pretty straight forward) and now it runs almost perfectly. (For some reason the higest value that can be displayed is 71 and the lowest is 24 even tho it should actually be 70 and 25 but that gives me no headaches tbh)

Anyways thanks for the help guys. If anyone is interested in my final program, here you go:
(Sorry for the German comments but I'm too lazy to translate right now, also i doubt someone gives a shit)

// Doofenschmirtz Dörrinator
// Mads Duggen
// Interactive Prototyping 2021 
// Unter der Leitung von Martin Fischbock
// Muthesius Kunsthochschule
// Tips für non-blocking timing von StefanL38 aus dem Aduino Forum

// ARDUINO PIN-BELEGUNG

// LCD mit I2C Modul
// A5 - SCL
// A4 - SDA
// 5v - VCC
// GND - GND

// DHT 11 Sensor mit Platine
// 8 - OUTPUT
// 5v - VCC
// GND - GND

// Heiz und Lüft Relais Channel 1 und 2
// 10 - CH1
// 11 - CH2
// 5v - DC+
// GND - DC-

// Grove Rotary Encoder
// 2 -  SIGB
// 3 -  SIGA
// 5v - VCC
// GND - GND


// Genutzte Bibiliotheken

#include <Encoder.h>
#include <TimerOne.h>

#include <DHT.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>

// Data-Pin und Sensortyp Definierung

#define DHTPIN 8
#define DHTTYPE DHT11

// Zuordnung der Variablen als Integere

int solltemperatur;
int temperatur;
int luftfeuchtigkeit;

// LCD und DHT Objekte erstellen und definieren von Pins, Adresse, Spalten & Zeilen

DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);


unsigned long myLcdUpdateTimer;


// Hilfsfunktion für non-blcoking-timing von StefanL38

boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - expireTime >= TimePeriod )
  {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}


void setup() {

  // Initialisierung Relais Pins
  
  pinMode(10, OUTPUT); // Relais Heizelement
  pinMode(11, OUTPUT); // Relais Lüfter

  //Initialisieren des encoders
  
  encoder.Timer_init();

  // Solltemperatur Grundwert auf 0 setzten
  
  solltemperatur = 0; 

  // Begrüßungssequenz

  dht.begin();                          // Initialisierung des DHT11 Sensors
  lcd.backlight();                      // Anschalten der LCD Hintergrundbeleuchtung
  lcd.begin();                          // Intialisierung des LCD

  delay(2000);

  lcd.setCursor( 0, 0);                 // Auswählen der 1. Zeile
  lcd.print(" Doofenschmirtz");         // Text für die 1. Zeile
  lcd.setCursor( 0, 1);                 // Auswählen der 2. Zeile
  lcd.print("  Doerrinator");           // Text für die 2. Zeile

  delay(5000);

}

void printToLCD() {
  lcd.clear();

  delay(10);

  //Die Umgebungstemperatur wird in der ersten Zeile angezeigt
  lcd.setCursor( 0, 0);
  lcd.print("Temp. :");
  lcd.print(temperatur);
  lcd.print("C");

  //Die eingestellte Temperatur wird in der zweiten Zeile angezeigt
  lcd.setCursor( 0, 1);
  lcd.print("Soll. :");
  lcd.print(solltemperatur); // Einstellbare Variable zum Feststellen des Temperatur-Sollwertes
  lcd.print("C");
  
}

void loop() {

  temperatur = dht.readTemperature(); // Umgebungstemperatur 
  luftfeuchtigkeit = dht.readHumidity(); // Luftfeuchtigkeit (bisher nicht in Benutzung)

  // Wenn an dem Drehgeber gedreht wird
  if (encoder.rotate_flag == 1)
  {
    //Wenn gegen den Uhrzeigersinn gedreht wird, solltemperatur verringern
    if (encoder.direct == 1)
    {
      solltemperatur --;
      printToLCD();                  //Der LCD wird nur geupdatet wenn an dem Encoder gedreht wird
    }
    //Wenn mit dem Uhrzeigersinn gedreht wird, solltemperatur erhöhen
    else
    {
      solltemperatur ++;
      printToLCD();                  //Der LCD wird nur geupdatet wenn an dem Encoder gedreht wird
    }
    // Encoder auf 0 zurücksetzten 
    encoder.rotate_flag = 0;
  }

  if (temperatur != temperatur) {
    printToLCD();
  }

  // Unteres Temperaturlimit // Irgendwie ist 24 das Minimm ... Kein Plan wieso
  if (solltemperatur <= 15) {
    solltemperatur = 15;
  }
  // Oberes Temperaturlimit // Irgediwe ist 71 das Maximum ... Kein Plan wieso
  if (solltemperatur >= 70) {
    solltemperatur = 70;
  }

  // Das Relais wird angeschaltet, wenn die Außentemperatur niedriger ist als der eingestellte Wert 
  if (solltemperatur > temperatur) {                             
    digitalWrite(10, LOW);
    digitalWrite(11, LOW);
  }
  // Falls nicht wird es ausgeschaltet
  else {
    digitalWrite(10, HIGH);
    digitalWrite(11, LOW);
  }

  if ( TimePeriodIsOver(myLcdUpdateTimer, 2000) ) {
    printToLCD(); // LCD-Update zur Erfassung des Aktuellen Temperaturwertes über den DHT-11 Sensor
  }

}




// Genutzte Anleitungen und Tutorials
// Q1. https://wiki.seeedstudio.com/Grove-Encoder/
// Q2. https://github.com/hduijn/arduino-dehydrator/blob/master/arduinocode/dehydrator.ino
// Information zu Betrieb von I2C LCD und DHT11 aus den Beispielsketches der Bibiliotheken <DHT.h> und <LiquidCrystal_I2C.h>

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.