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