Servo twitch when utilising millis counter for alternating LCD display & DHT-22

There is no end purpose for this project other than to experiment various ideas. What I’ve done is hook up a 16x2 LCD which displays temperature and humidity on first line and alternates between the 2 using the millis counter. I’ve then attached some LDR’s to a standard servo to create a light tracker. The LDR readings are displayed on the 2nd line.

The code I wrote for this works perfectly apart from a minor niggle. The servo twitches randomly but roughly every 5 or so seconds but sometimes not at all for a period of time. I tried removing all the code for the alternating temp/hum display and this solved the problem. So it’s clear the issue is with using the millis counter with some basic maths to create the alternating display. Most will say just use a delay but this stops the rest of the program running until the delay is done. Anyone have any experience with this?

Here is the code:

#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
#include "DHT.h"
#define DHTPIN 12     
#define DHTTYPE DHT22  
DHT dht(DHTPIN, DHTTYPE);

int LDR1 = A0; 
int LDR2 = A1;
int LDRRead1;
int LDRRead2;

#include <Servo.h>
int servoPin = 8;
Servo servo;
int angle = 90;

int interval = 4000;                                                  // LCD screen alternating delay
unsigned long previousMillis=0;                                       // Tracks the time since last event fired

void setup()
{
 servo.attach(servoPin); 

 servo.write(angle);
 
 lcd.begin(16, 2);
 lcd.print("Loading...");
 dht.begin();
    delay(500);
 lcd.clear();
}

void LCDrefresh()                                                     //Function, to avoid repeating code later on
{
  LDRRead1 = analogRead(LDR1); 
  LDRRead2 = analogRead(LDR2);                                     
  
    lcd.setCursor(0, 1);                                              //Print readings on LCD
    lcd.print("LDR 1     2     ");
    lcd.setCursor(6, 1);                                              
    lcd.print(LDRRead1);
    lcd.setCursor(12,1);
    lcd.print(LDRRead2);
  }
  
void loop() {
    
  float h = dht.readHumidity();                                       //Read Humidity
  float t = dht.readTemperature();                                    //Read Temperature
  
  if (isnan(t) || isnan(h))                                           //Check for faults
  { 
    lcd.clear();
    lcd.print("Sensor Failure");
    delay(1000);
    lcd.clear();
  }
    
  unsigned long currentMillis = millis();                             //Alternating display - snapshot of time
 
  if ((currentMillis - previousMillis) > interval)                    //Check difference between snapshot and previous time.
  {                                                                   //If it's greater than the interval it displays Temperature, but
    lcd.setCursor(0, 0);                                              //to stop the display from instantly returning to Humidity,
    lcd.print("Temp: ");                                              //we add this next line which allows temperature it's turn on
    lcd.print(t,1);                                                   //the display, without using a delay holding up the program.
    lcd.print(" C");                                                  //We started displaying temp at the 'interval', so now temp gets
    if ((currentMillis - previousMillis) >= interval*2)               //it's turn from the 'interval' to the next interval = 'interval x 2'.
    previousMillis = currentMillis;                                   //Now we reset previous time to current time to use on the next loop
  }
   
  else                                                                //if it's not displaying temperature, display humidity
  {
    lcd.setCursor(0, 0);
    lcd.print("Hum:  ");
    lcd.print(h,1);
    lcd.print(" %");
  }
  
  LCDrefresh();                                                       //Calls the LCD refresh function which updates the LDR readings on the LCD
  
  if (LDRRead1 - LDRRead2 > 10)                                       //If the readings vary significantly in one direction we enter this stage
  {
       while ((LDRRead1 - LDRRead2 > 2) && (servo.read() < 170))      //Now we enter a while loop containing even tighter parameters.
       {                                                              //This avoids the servo moving to just inside the initial parameter,
       servo.write(angle + 1);                                        //causing the servo to keep adjusting
       angle = angle + 1;
       delay(30);                                                     //We need to allow some time for the LDR readings to stabilize 
       LCDrefresh();                                                  //Calling this function again here otherwise nothing will update so long
       }                                                              //as we're stuck in the while loop
  }
  
  
  if (LDRRead2 - LDRRead1 > 10)                                       //If the readings vary significantly in the other direction we enter this stage
  {
       while ((LDRRead2 - LDRRead1 > 2) && (servo.read() > 0))        //Opposite of the above
       {                                                              //We need to check the servo is within 0 to 170 degrees in both these while loops
       servo.write(angle - 1);                                        //otherwise the program will continue to increase or decrease the value
       angle = angle - 1;                                             //of 'angle' despite the servo being unable to move past it's extremes.
       delay(30);                                                     //Then if servo is called to move the other way nothing would happen
       LCDrefresh();                                                  //until the value of 'angle' returned to within 0 and 170.
       }
  } 
  }

I tried removing all the code for the alternating temp/hum display and this solved the problem. So it's clear the issue is with using the millis counter with some basic maths to create the alternating display.

It does not follow from the above that the problem has anything at all to do with mills.

Most will say just use a delay but this stops the rest of the program running until the delay is done. Anyone have any experience with this?

No will tell you to use delay. About 1 in 10 of the answers on this forum are to tell posters NOT to use delay. It the third most popular response to a question.

Here is the code for the alternating display part, this is only a section of the full code so no void setup or other variables shown here, only the problem area to avoid posting a huge amount:

Your conclusion about mills is wrong. Telling people to post ALL there code is the second mist popular response to posters.

But you did use code tags!

Mark

Ok, I've updated my first post to show the entire code. I have tried to include as much detail including a photo and text explaining the code rather than what most people do on their first post by saying "i need help coz it won't light up..."

I do fail to see how my process of elimination is incorrect. By removing the section of code using the millis, the problem was resolved...

I don’t understand why you have a function LCDrefresh() and also have LCD code within loop() - it’s very confusing. Why not put all the LCD code in a function?

I don’t understand why you have a WHILE loop within loop() to adjust the position of the servo. Has this anything to do with your jitter? Why not simply move the servo one step on each iteration through loop()?

I would also put the servo code and the LDR reading code into their own functions so that loop() is like this

void loop() {
  readLDR();
  LCDrefresh();
  moveServo();
}

If each piece of activity is self contained it will be easier to see what affects what.

…R

 if (LDRRead1 - LDRRead2 > 10)                                       //If the readings vary significantly in one direction we enter this stage

and

  if (LDRRead2 - LDRRead1 > 10)                                       //If the readings vary significantly in the other direction we enter this stage

A difference of 10 is not a lot for the LDRs. It's only 1%. Try 50 it should stop your twitch. I suspect that small changes in the voltage to the LDR's caused by the servo drawing power when is moving are causing your root problem.

Mark

I suspect that small changes in the voltage to the LDR's caused by the servo drawing power when is moving are causing your root problem.

+1 to that, particularly as the servo is powered directly from the Arduino and possibly drawing more current from the voltage regulator than is wise.

I don't understand why you have a function LCDrefresh() and also have LCD code within loop() - it's very confusing. Why not put all the LCD code in a function?

The code in LCDrefresh is purely for the LDR's as these need to be update regularly, the other code you see elsewhere is simply for the temp and humidty so don't need to be updated as often.

I don't understand why you have a WHILE loop within loop()

I tried using just an if statement but when the servo moved it was jittery, by that i mean you could see the 'lag' as it had to run through the entire program each time to move 1 degree, the while loop allows the arduino to quickly process that section resulting in smooth servo movement.

A difference of 10 is not a lot for the LDRs. It's only 1%. Try 50 it should stop your twitch.

I've tried different values and you're right 10 isn't much but I can also watch the values change and they hold fairly steady +/-2 indoors so this does not cause a twitch. As I said before, the program runs perfectly without twitching when I remove the temp and humidity alternating display using the millis counter.

+1 to that, particularly as the servo is powered directly from the Arduino and possibly drawing more current from the voltage regulator than is wise.

I have also tried a seperate power supply with common grounds but it doesn't help. Again like before, it works fine and doesn't twitch when powered solely from the arduino board if the milli counter is removed.

If just reading mills was making a mess of servo then this forum would be covered with posts about it. 10,000 if 100,000 of people have used then together over the years.

Why not try the solution offered?

Mark

Why not try the solution offered?

Really!!, are you deliberately being obtuse? I said I tried 'if' statements, I tried a separate power supply, I tried different values! If you can't help then thanks for attempting to but clearly you don't understand what I'm saying

I've taken a coy of your code and I will try to fiddle with it later today.

I will be surprised if the problem has anything to do with millis().

I will also be surprised if it is necessary to use the WHILE loop to move the servos.

...R

Thanks R,

I’ve done some more research and found a another similar issue although with far more servo’s.

https://forum.sparkfun.com/viewtopic.php?f=14&t=32898

Some mentioned needing to refresh the servo’s every 20ms although this is with a different library. I’ve tried adding servo.write(angle); into my code outside of the while loops to act as a refresh but this hasn’t stopped the random twitch.

It is weird though because it can go for 5 or 10 minutes without twitching but then starts and goes on for another random period of time, when it does I can see the LDR readings are holding steady and I added the servo angle to the LCD display so can see that when the servo twitches the angle being written to the servo isn’t changing.

Guesses:

  1. Servo library has a defect (bug) that is not updating the servos at a regular rate.
  2. Interrupts on your processor preventing the Servo library from updating properly
  3. Glitch in Servo library that occurs when you write the same value to the servo. If it was in the middle of updating that servo, you may have generated a runt pulse that causes the jitter. What if you write the value once and never update it again? If this solves the problem, then just keep track of the previous value you wrote to a servo and skip the new write if the old value is the same as what you want to write.

I’m going to try and find that other servo library and use that

so can see that when the servo twitches the angle being written to the servo isn't changing.

That would seem to exonerate the Arduino from causing the problem unless the change happens so fast that you do not catch it on the LCD.

Can you change your code to display the maximum and minimum values written to the servo whilst it is in theory idle ?

I have tried out your code and I haven’t seen any twitching. I don’t have the DHT devices so I have simulated them with fixed values. I can’t check whether the DHT library causes the problem.

I haven’t gone to the trouble of making the servo move the LDRs. I just used a very small servo that runs happily from the Arduino 5v supply.

The LDRs that I have go out of sync when the light level changes - that might be a source of unexpected servo movement.

I have taking the liberty of reorganizing the code. This version behaves the same way as yours.
Also note that I used different LCD pins (in both versions) but I doubt if that matters.

// http://forum.arduino.cc/index.php?topic=235765.0


#include <LiquidCrystal.h>
// LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
LiquidCrystal lcd(12, 11, 10,  5, 4, 3, 2);
//     #include "DHT.h"
//     #define DHTPIN 12     
//     #define DHTTYPE DHT22  
//     DHT dht(DHTPIN, DHTTYPE);

int LDR1 = A0; 
int LDR2 = A1;
int LDRRead1;
int LDRRead2;

#include <Servo.h>
int servoPin = 8;
Servo servo;
int angle = 90;

int interval = 4000;        
unsigned long previousMillis = 0;
unsigned long currentMillis;

float humid;
float temp;
char displaySetting = 'h'; // alternative is 't'

//=======================

void setup()
{
 servo.attach(servoPin); 

 servo.write(angle);
 
 lcd.begin(16, 2);
 lcd.print("Loading...");
//        dht.begin();
    delay(500);
 lcd.clear();
}

//=======================

void loop() {
  currentMillis = millis();
  readLDRs();
  readTempHumid();
  LCDrefresh();
  moveServo();
}

//=======================

void readLDRs() 
{
  LDRRead1 = analogRead(LDR1); 
  LDRRead2 = analogRead(LDR2);                                     
}

//=======================

void LCDrefresh() {
  // temp and humidity
  if (currentMillis - previousMillis > interval) {
    previousMillis += interval;
  
    if (displaySetting == 't') {
      lcd.setCursor(0, 0); 
      lcd.print("Temp: ");
      lcd.print(temp,1);
      lcd.print(" C");
      displaySetting = 'h';
    }
    else {
      lcd.setCursor(0, 0);
      lcd.print("Hum:  ");
      lcd.print(humid,1);
      lcd.print(" %");
      displaySetting = 't';
    }
  }
  
  // LDR values
  lcd.setCursor(0, 1);
  lcd.print("LDR 1     2     ");
  lcd.setCursor(6, 1);                                              
  lcd.print(LDRRead1);
  lcd.setCursor(12,1);
  lcd.print(LDRRead2);
}
 
//=======================

void readTempHumid() {
//  humid = dht.readHumidity();    
//  temp = dht.readTemperature();  
  humid = 62.7;
  temp = 16.8;
  
  if (isnan(temp)) 
  { 
    temp = 0.0;
  }
  
  if (isnan(humid))   
  { 
    humid = 0.0;
  }
}

//=======================

void moveServo() {
  if (LDRRead1 - LDRRead2 > 10) {
     angle = angle + 1;
     if (angle > 170) {
       angle = 170;
     }
  }
  if (LDRRead2 - LDRRead1 > 10) {
     angle = angle - 1;
     if (angle < 10) {
       angle = 10;
     }
   }
     
   servo.write(angle);
   delay(30);   
}
   
//=======================

…R

Wow Robin, thank you so much for your time working on this. Using your code I've made some interesting findings.

To start with, I copied your code exactly (changed the LCD pins only) and ran it on my arduino. It ran fine and the servo moved smoothly and quickly as it should. It seemed to run A LOT more smoothly.

I then altered your code to include the real DHT readings but changed nothing else. This time the servo was incredibly slow to move. It moved maybe 3 degree's a second maximum. This must mean that when the arduino has to read the DHT it takes a lot time. This is also why I used a while loop but was unable to say what caused the slow servo movement, now we know. Also the while loop allowed me to include tighter parameters to avoid the servo simply moving just inside the boundary. I also tried removing the DHT stuff from my own code and the program runs significantly faster.

Finally, the twitching, as I said at the start, when I remove the alternating display code the twitching stops and this time, when I keep the alternating display and remove the DHT it also stops so I can only assume it's a strange combination of factors that causes it.

Thanks again for all of your time, as this is simply an experiment and out of interest I don't want to spend any more time on such a trivial matter. It's interesting to hear your thoughts though and we have at least found some answers.

L333z

It would be easy to write a very short sketch that reads the DHT thingummy and records the time with micros() before and after to find out how long it takes. Sounds like the library code is suboptimal. Is the library really necessary?

I guess a simple solution would be just to read the DHT thing every 10 seconds or so.

...R

Thought I’d quickly give it a go although I used millis not micros!!!

#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
#include "DHT.h"
#define DHTPIN 12     
#define DHTTYPE DHT22  
DHT dht(DHTPIN, DHTTYPE);


unsigned long startMillis=0; 
unsigned long stopMillis=0;
int time =0;

void setup()
{

 lcd.begin(16, 2);
 dht.begin();
 delay(50);
 lcd.clear();
}

void loop() 
  {
  
  startMillis = millis();  
  
  float h = dht.readHumidity();                                      
  float t = dht.readTemperature(); 
  
  stopMillis = millis(); 
  
             
  (time = stopMillis - startMillis);
    
    lcd.setCursor(0, 0);                                              
    lcd.print("Temp: ");                                             
    lcd.print(t,1);                                                   
    lcd.print(" C");                                                 
                                                                                           
    lcd.setCursor(0, 1);
    lcd.print("Time:  ");
    lcd.print(time);
    lcd.print(" millis");
  
  }

HALF a second!!! was the result - it hovered between 500 and 522 millis.

Yeh you’re right, reading the DHT every so often would be the solution here. I just wanted to experiment, out of interest.

HALF a second!!!!! was the result - it hovered between 500 and 522 millis.

This page http://playground.arduino.cc/Main/DHTLib says

A call to read Temperature and humidity takes between 24 and 25 milliseconds from which 20 milliseconds is a "chip wake up" call. Worth investigating if this can be reduced.

and

Note: the max frequency the sensor can be sampled is about once per 2 seconds

So how come I'm getting a delay of half a second while it reads it. Surely one of two things should happen:

1 - I get a delay of 2 seconds while it tries to read the sensor 2 -I get a delay of 25ms but with old values (up to 2 secs old) as it hasn't had a chance to update

any thoughts?

Does your reading of the sensor ever return DHTLIB_ERROR_TIMEOUT ?

I don’t have the device so all I can do is speculate.

It seems like the library blocks until data is available. It is probably possible to write some DIY code instead of the library that separates the wake-up from the reading. Even 25ms is very slow for transferring data to the Arduino.

…R