Timing issues with DS18B20 sensor and 4dig7segment display (multiplex+ shiftreg)

Hi agian,

I thought I had a working multiplexing script until I gave it an input that isnt continious… I thought I understood multiplexing but clearly there’s a lot more going on with timing and updating the display constantly.

my problem:
the temp sensor is working, it reads temps accurately but I cant figure out how to keep the values displayed while the sensor is reading… when the display is reading the display just goes dark. I tried putting the reading part in an if statement that only runs every 750ms as stated in the datasheet and letting the display update constantly in the main loop. Clearly doesnt work. I need to run the writeDisplay() constantly while once in a while updating the reading value.

How is this done?

my setup:
DS18B20 temp sensor (works)
74HC595N shift reg (works)
4 digit 7 segment display (works)
arduino UNO (works)

#include <OneWire.h>
#include <DallasTemperature.h>
OneWire oneWire(2); //onewire pin selection
DallasTemperature sensors(&oneWire);//dallas temp lib sensor detection
DeviceAddress tempSensor = {0x28, 0xFF, 0xE7, 0xF3, 0x75, 0x18, 0x01, 0x24}; //adress of DS18B20


//shift register interfacing 74HC595N
int DS_pin = 8;
int latch_pin = 9;
int shift_pin = 10;

//reading maniupulation
float reading;
float readingten;


int const numOfDigits = 4;
int d1 = 5;   //1000
int d2 = 13;  //0100
int d3 = 11;  //0010
int d4 = 7;   //0001
int digitPins[numOfDigits] = {d1, d2, d3, d4};
int digits[numOfDigits] = {20, 20, 20, 20};

int const numOfDecimals = 1;
int const floatPoint = numOfDigits - 1 - numOfDecimals;

//reading interval 
unsigned long lastReadingTime = 0;
const int processingTime = 750; //max as specified in datasheet (depends on resolution)


int const numOfOutputs = 7;
int outputPins[numOfOutputs] = {d1, d2, d3, d4, DS_pin, latch_pin, shift_pin};


//      AAAAA
//     F     B
//     F     B
//      GGGGG
//     E     C
//     E     C
//      DDDDD   DP

//{F, A, B, G, E, D, C, DP} for every number
const byte registers[21] = {
  B11101110,//0
  B00100010,//1
  B01111100,//2
  B01110110,//3
  B10110010,//4
  B11010110,//5
  B11011110,//6
  B01100010,//7
  B11111110,//8
  B11110110,//9
  B11101111,//0.
  B00100011,//1.
  B01111101,//2.
  B01110111,//3.
  B10110011,//4.
  B11010111,//5.
  B11011111,//6.
  B01100011,//7.
  B11111111,//8.
  B11110111,//9.
  B00000000//empty
};


void setup() {
  
  for (int i = 0; i < numOfOutputs; i++) {
    pinMode(outputPins[i], OUTPUT);
  }
  
  pinMode(DS_pin, OUTPUT);
  pinMode(latch_pin, OUTPUT);
  pinMode(shift_pin, OUTPUT);
  Serial.begin(9600);
  sensors.setResolution(tempSensor, 12);//set resolution
}

void setDigits() {
  readingten = reading * (float)pow(10, numOfDecimals); //987.6 to 9876
  digits[0] = (((int)readingten) % 10000) / 1000;  //9
  digits[1] = (((int)readingten) % 1000) / 100;    //8
  digits[2] = (((int)readingten) % 100) / 10;      //7
  digits[3] = (((int)readingten) % 10) / 1;        //6

  digits[floatPoint] = digits[floatPoint] + 10; //puts decimalpoint on the digit left from the decimals

  int i = 0;
  while (digits[i] == 0 && i < (numOfDigits - 1)) {
    digits[i] = 20; //001.3 to 1.3 removes uneeded zeros, registe[20] is B00000000
    i++;
  }
}

void writeDisplay() {
  for (int j = 0; j < numOfDigits; j++) {
    digitalWrite(latch_pin, LOW);// opens register and locks outputs
    shiftOut(DS_pin, shift_pin, LSBFIRST, registers[digits[j]]);
    digitalWrite(latch_pin, HIGH);//latches from register to outputs
    digitalWrite(digitPins[j], LOW); //selects digit
    delayMicroseconds(500);
    digitalWrite(digitPins[j], HIGH);
  }
}

void loop() {
  if ( (millis() - lastReadingTime) > processingTime) {
    sensors.requestTemperaturesByAddress(tempSensor); //read temps
    reading = sensors.getTempC(tempSensor);
    lastReadingTime = millis();
  }
  setDigits();
  writeDisplay();
}

I need to run the writeDisplay() constantly while once in a while updating the reading value.

The processor can only do one thing at a time. The one wire protocol is a time hog, so yes it is difficult. You could try calling the display refresh routine from an interrupt service routine called automatically from a timer. However that might interfere with the workings of the one wire protocol.

The only sure fire way is to relegate the multiplexing tasking to hardware, like connecting the display to a MAX7219 chip.

Grumpy_Mike: The one wire protocol is a time hog, so yes it is difficult.

Might have just found a simple workaround using a built in function of the library: sensors.setWaitForConversion(false)

This way I can time it myself right? EDIT: this works, however there is still a flicker. Not as bad as the on and off every second though.

Grumpy_Mike: You could try calling the display refresh routine from an interrupt service routine called automatically from a timer.

I've read about those interrupts and have been putting it off since I thought it would be difficult. Maybe my time has come to learn....

You probably don’t need to use interrupts. Just make sure you send all requests immediately after the multiplexing code has started a new period.

This example shows how to do it, but uses delay():

 // Request temperature conversion - non-blocking / async
  Serial.println("Before NON-blocking/async requestForConversion");
  start = millis();       
  sensors.setWaitForConversion(false);  // makes it async
  sensors.requestTemperatures();
  sensors.setWaitForConversion(true);
  stop = millis();
  Serial.println("After NON-blocking/async requestForConversion");
  Serial.print("Time used: ");
  Serial.println(stop - start); 
  
  
  // 9 bit resolution by default 
  // Note the programmer is responsible for the right delay
  // we could do something usefull here instead of the delay
  int resolution = 9;
  delay(750/ (1 << (12-resolution)));
  
  // get temperature
  Serial.print("Temperature: ");
  Serial.println(sensors.getTempCByIndex(0));  
  Serial.println("\n\n\n\n");

Just noticed your code is written, errr... badly (sorry!)

The reason I say that is:

delayMicroseconds(500);

This blocks the code from performing other tasks, such as communicating with the sensors, while the display is lit. If you re-write that, you can probably leave each digit lit for 2 or 3ms, giving plenty of time to request temperature readings while one digit is lit, and read them back while another digit is lit.

Maybe like this:

byte writeDisplay() {
  static byte j;
  static unsigned long lastUpdate;

  if (millis() - lastUpdate >= 3) {
    digitalWrite(digitPins[j], HIGH); //unselect previous digit
    if (++j >= 4) j = 0;
    digitalWrite(latch_pin, LOW);// opens register and locks outputs
    shiftOut(DS_pin, shift_pin, LSBFIRST, registers[digits[j]]);
    digitalWrite(latch_pin, HIGH);//latches from register to outputs
    digitalWrite(digitPins[j], LOW); //selects new digit
    lastUpdate = millis();
    return 1;
  }
  else return 0;
}

This version will not wait while the digits are lit one at a time, it will light one digit and return. The next time is it called, it will do nothing if 3ms has not passed. If 3ms has passed, it will switch off the old digit and light the next one. In addition, it will return a 1 when the digit has just changed, so that's the best time to send the request for readings or read the results in a subsequent digit's period (once the required time has passed for the readings to be ready).

Thanks for the reply. I've implemented the sensors.setWaitForConversion(false); in my code now but it stil gives a flicker when it's requesting data (Is my assumption). Previously the display was off for a sec then on for a sec, now its just a blink.

PaulRB: Just noticed your code is written, errr... badly (sorry!)

The reason I say that is:

delayMicroseconds(500);

No need to say sorry! I'm learning to program and started with a LED then a poteniometer, 7seg disp, 4dig disp with potentiometer and now this... but now al my old sketches seem to be written poorly.

I read about using millis() but I cant seem to get it to work. I wrote a sketch without using any delays and it did the strangest of things: every digit was dim exept the last one and on another iteration of that script I kept having a dim copy of every digit on the next one.

Sorry, I updated my previous post with suggested code before I realised you had responded.

PaulRB: Maybe like this:

byte writeDisplay() {
  static byte j;
  static unsigned long lastUpdate;

 if (millis() - lastUpdate >= 3) {    digitalWrite(digitPins[j], HIGH); //unselect previous digit    if (++j >= 4) j = 0;    digitalWrite(latch_pin, LOW);// opens register and locks outputs    shiftOut(DS_pin, shift_pin, LSBFIRST, registers[digits[j]]);    digitalWrite(latch_pin, HIGH);//latches from register to outputs    digitalWrite(digitPins[j], LOW); //selects new digit    lastUpdate = millis();    return 1;  }  else return 0; }

thats a nice way to do it indeed. Just a couple of questions: - that function returns a 0 or a 1, but to where? how do I acces that? - ++j is (j+1) I guess but does it alter the value of j or is it just evaluating it cause I dont see a j++ like I would use? - the 'static unsigned long lastUpdate' is a local variable right?

The function returns the 0 or 1 to the line where it is called. So rather than

  writeDisplay();

you would write

  if (writeDisplay() == 1) {
  //a new digit has just been lit, so send temp request or whatever now
}

"++j" is just like "j++" except that the value if j is read after it has been incremented, not before.

"lastUpdate" is a local variable, yes, and cannot be read from outside writeDidsplay(), but the "static" keyword means that it does not loose its value from call to call, so more like a global variable in that respect. It could have made it a global variable, but local & static is neater.

Changed the code with everything you said but I stil get a flicker every 750ms (the display goes dark very briefly). Did I do the main loop correctly? Also one thing that I changed was the multiplex time. 4ms was too much, could see a sort of scanning happening.

byte writeDisplay() {
  static byte j;
  static unsigned long lastUpdate;

  if (millis() - lastUpdate >= 1) {
    digitalWrite(digitPins[j], HIGH); //unselect previous digit
    if (++j >= 4) j = 0;
    digitalWrite(latch_pin, LOW);// opens register and locks outputs
    shiftOut(DS_pin, shift_pin, LSBFIRST, registers[digits[j]]);
    digitalWrite(latch_pin, HIGH);//latches from register to outputs
    digitalWrite(digitPins[j], LOW); //selects new digit
    lastUpdate = millis();
    return 1;
  }
  else return 0;
}

void loop() {
  if (writeDisplay() == 1) {
    if ( (millis() - lastReadingTime) > processingTime) {
      reading = sensors.getTempC(tempSensor);
      sensors.requestTemperaturesByAddress(tempSensor); //read temps
      lastReadingTime = millis();
      setDigits();
    }
  }
}

Not quite as I imagined, but close.

This line

  if (millis() - lastUpdate >= 1) {

means that each digit is only lit for one millisecond, which does not give much time for communicating with the temp sensor. Try 2 or 3, that should be fast enough to avoid visible scanning.

Also, you are reading the temps and issuing another request in the same digit period. Maybe declare a boolean global variable called "tempsRequested" and rename "lastReadingTime" to "lastRequestTime". After a new digit is lit, check the boolean. If its false, issue the temperature request, set the boolean to true and set "lastRequestTime" to millis(). if the boolean is true, check to see if the processing time has now elapsed, and if it has, read the temperature and set the boolean back to false.

Thanks will try the last thing later. I think something is different with my multiplex script cause when I put 3 or 2 ms it starts scanning. Is there a problem with the display being common kathode?

Moshtaraq: when I put 3 or 2 ms it starts scanning. Is there a problem with the display being common kathode?

No, common cathode/anode is irrelevant to the timing. Post your full updated sketch.

With 4ms per-digit, the display should be updated every 16ms, which is 62.5 updates per second. Old TV sets refreshed at a similar speed, and I remember you could see the flicker, but you soon got used to it.

With 3ms per digit, the display should be updated every 12ms, which is 83.3 updates per second. That should look pretty smooth.

With 2ms per digit, the display should be updated every 8ms, which is 125 updates per second. You should not be able to see that at all.

So I wonder if there is some other problem with your code, or maybe even 3ms is not enough to both read temps and request new temps. Maybe my boolean flag idea would fix that.

Try commenting out the lines that read the temp and request the temp. Assign a fixed temp value like "123.4", set the display update to 3 or 2ms and see if you can still see the scanning.

PaulRB:
With 3ms per digit, the display should be updated every 12ms, which is 83.3 updates per second. That should look pretty smooth.

With 2ms per digit, the display should be updated every 8ms, which is 125 updates per second. You should not be able to see that at all.

3ms did not look smooth, 2 ms is unoticable

PaulRB:
Post your full updated sketch.

Here ya go, note that it’s not updated with your last reply

#include <OneWire.h>
#include <DallasTemperature.h>
OneWire oneWire(2); //onewire pin selection
DallasTemperature sensors(&oneWire);//dallas temp lib sensor detection
DeviceAddress tempSensor = {0x28, 0xFF, 0xE7, 0xF3, 0x75, 0x18, 0x01, 0x24}; //adress of DS18B20


//shift register interfacing 74HC595N
int DS_pin = 8;
int latch_pin = 9;
int shift_pin = 10;

//reading maniupulation
float reading;
float readingten;
float tempC;


int const numOfDigits = 4;
int d1 = 5;   //1000
int d2 = 13;  //0100
int d3 = 11;  //0010
int d4 = 7;   //0001
int digitPins[numOfDigits] = {d1, d2, d3, d4};
int digits[numOfDigits] = {20, 20, 20, 20};

int const numOfDecimals = 1;
int const floatPoint = numOfDigits - 1 - numOfDecimals;

//reading interval
unsigned long lastReadingTime = 0;
const int processingTime = 750; //max as specified in datasheet (depends on resolution)


int const numOfOutputs = 7;
int outputPins[numOfOutputs] = {d1, d2, d3, d4, DS_pin, latch_pin, shift_pin};


//      AAAAA
//     F     B
//     F     B
//      GGGGG
//     E     C
//     E     C
//      DDDDD   DP

//{F, A, B, G, E, D, C, DP} for every number
const byte registers[21] = {
  B11101110,//0
  B00100010,//1
  B01111100,//2
  B01110110,//3
  B10110010,//4
  B11010110,//5
  B11011110,//6
  B01100010,//7
  B11111110,//8
  B11110110,//9
  B11101111,//0.
  B00100011,//1.
  B01111101,//2.
  B01110111,//3.
  B10110011,//4.
  B11010111,//5.
  B11011111,//6.
  B01100011,//7.
  B11111111,//8.
  B11110111,//9.
  B00000000//empty
};


void setup() {

  for (int i = 0; i < numOfOutputs; i++) {
    pinMode(outputPins[i], OUTPUT);
  }

  pinMode(DS_pin, OUTPUT);
  pinMode(latch_pin, OUTPUT);
  pinMode(shift_pin, OUTPUT);
  Serial.begin(9600);
  sensors.setWaitForConversion(false);
  sensors.setResolution(tempSensor, 10);//set resolution
}

void setDigits() {
  readingten = reading * (float)pow(10, numOfDecimals); //987.6 to 9876
  digits[0] = (((int)readingten) % 10000) / 1000;  //9
  digits[1] = (((int)readingten) % 1000) / 100;    //8
  digits[2] = (((int)readingten) % 100) / 10;      //7
  digits[3] = (((int)readingten) % 10) / 1;        //6

  digits[floatPoint] = digits[floatPoint] + 10; //puts decimalpoint on the digit left from the decimals

  int i = 0;
  while (digits[i] == 0 && i < (numOfDigits - 1)) {
    digits[i] = 20; //001.3 to 1.3 removes uneeded zeros, registe[20] is B00000000
    i++;
  }
}


byte writeDisplay() {
  static byte j;
  static unsigned long lastUpdate;

  if (millis() - lastUpdate >= 1) {
    digitalWrite(digitPins[j], HIGH); //unselect previous digit
    if (++j >= 4) j = 0;
    digitalWrite(latch_pin, LOW);// opens register and locks outputs
    shiftOut(DS_pin, shift_pin, LSBFIRST, registers[digits[j]]);
    digitalWrite(latch_pin, HIGH);//latches from register to outputs
    digitalWrite(digitPins[j], LOW); //selects new digit
    lastUpdate = millis();
    return 1;
  }
  else return 0;
}

void loop() {
  if (writeDisplay() == 1) {
    if ( (millis() - lastReadingTime) > processingTime) {
      reading = sensors.getTempC(tempSensor);
      sensors.requestTemperaturesByAddress(tempSensor); //read temps
      lastReadingTime = millis();
      setDigits();
    }
  }
}

when I put 3 or 2 ms it starts scanning

2 ms is unoticable

Now I'm confused. Is it ok or not?

If it's still flickering every 750ms, I have a couple more ideas.

Switch to using SPI.transfer() instead of shiftOut(). This may mean changing pins.

Improve setDigits() by using dtostrf().

PaulRB:
Now I’m confused. Is it ok or not?

I get your confusion, when you put 1 in the statement it waits til it’s more than 1 so 2ms… with 1 in the statement it doesn’t scan.

PaulRB:
If it’s still flickering every 750ms, I have a couple more ideas.

Switch to using SPI.transfer() instead of shiftOut(). This may mean changing pins.

Improve setDigits() by using dtostrf().

I updated the code with your previous suggestions (the boolean values). And I must say there is an improvement on the time the display is dark, but there’s still a flicker every 750ms, although faster. I’ll look into SPI.transfer and dtostrf but a quick google made my jaw drop…

Here is the improved code if you’re interested or want implement those functions yourself or explain how I go about doing that. I prefer the latter.

#include <OneWire.h>
#include <DallasTemperature.h>
OneWire oneWire(2); //onewire pin selection
DallasTemperature sensors(&oneWire);//dallas temp lib sensor detection
DeviceAddress tempSensor = {0x28, 0xFF, 0xE7, 0xF3, 0x75, 0x18, 0x01, 0x24}; //adress of DS18B20


//shift register interfacing 74HC595N
int DS_pin = 8;
int latch_pin = 9;
int shift_pin = 10;

//reading maniupulation
float reading;
float readingten;
float tempC;
boolean tempRequest = 0;
unsigned long lastRequestTime = 0;


int const numOfDigits = 4;
int d1 = 5;   //1000
int d2 = 13;  //0100
int d3 = 11;  //0010
int d4 = 7;   //0001
int digitPins[numOfDigits] = {d1, d2, d3, d4};
int digits[numOfDigits] = {20, 20, 20, 20};

int const numOfDecimals = 1;
int const floatPoint = numOfDigits - 1 - numOfDecimals;

//reading interval
unsigned long lastReadingTime = 0;
const int processingTime = 750; //max as specified in datasheet (depends on resolution)


int const numOfOutputs = 7;
int outputPins[numOfOutputs] = {d1, d2, d3, d4, DS_pin, latch_pin, shift_pin};


//      AAAAA
//     F     B
//     F     B
//      GGGGG
//     E     C
//     E     C
//      DDDDD   DP

//{F, A, B, G, E, D, C, DP} for every number
const byte registers[21] = {
  B11101110,//0
  B00100010,//1
  B01111100,//2
  B01110110,//3
  B10110010,//4
  B11010110,//5
  B11011110,//6
  B01100010,//7
  B11111110,//8
  B11110110,//9
  B11101111,//0.
  B00100011,//1.
  B01111101,//2.
  B01110111,//3.
  B10110011,//4.
  B11010111,//5.
  B11011111,//6.
  B01100011,//7.
  B11111111,//8.
  B11110111,//9.
  B00000000//empty
};


void setup() {

  for (int i = 0; i < numOfOutputs; i++) {
    pinMode(outputPins[i], OUTPUT);
  }

  pinMode(DS_pin, OUTPUT);
  pinMode(latch_pin, OUTPUT);
  pinMode(shift_pin, OUTPUT);
  Serial.begin(9600);
  sensors.setWaitForConversion(false);
  sensors.setResolution(tempSensor, 10);//set resolution
}

void setDigits() {
  readingten = reading * (float)pow(10, numOfDecimals); //987.6 to 9876
  digits[0] = (((int)readingten) % 10000) / 1000;  //9
  digits[1] = (((int)readingten) % 1000) / 100;    //8
  digits[2] = (((int)readingten) % 100) / 10;      //7
  digits[3] = (((int)readingten) % 10) / 1;        //6

  digits[floatPoint] = digits[floatPoint] + 10; //puts decimalpoint on the digit left from the decimals

  int i = 0;
  while (digits[i] == 0 && i < (numOfDigits - 1)) {
    digits[i] = 20; //001.3 to 1.3 removes uneeded zeros, registe[20] is B00000000
    i++;
  }
}


byte writeDisplay() {
  static byte j;
  static unsigned long lastUpdate;

  if (millis() - lastUpdate >= 1) {
    digitalWrite(digitPins[j], HIGH); //unselect previous digit
    if (++j >= 4) j = 0;
    digitalWrite(latch_pin, LOW);// opens register and locks outputs
    shiftOut(DS_pin, shift_pin, LSBFIRST, registers[digits[j]]);
    digitalWrite(latch_pin, HIGH);//latches from register to outputs
    digitalWrite(digitPins[j], LOW); //selects new digit
    lastUpdate = millis();
    return 1;
  }
  else return 0;
}

void loop() {
  if (writeDisplay() == 1) {
    if ( tempRequest == 1) {
      sensors.requestTemperaturesByAddress(tempSensor); //read temps
      lastRequestTime = millis();
      tempRequest = 0;
    }
    if ( (millis() - lastRequestTime) > processingTime) {
      reading = sensors.getTempC(tempSensor);
      setDigits();
      tempRequest = 1;
    }
  }
}

Did you try my suggestion from post #13? i.e. comment out this line:

      //sensors.requestTemperaturesByAddress(tempSensor); //read temps

and change this line:

      reading = 12.3; //sensors.getTempC(tempSensor);

Do you still get the flicker every 750ms?

If no flicker, this tells you that the communications with the sensor are the cause.

If you still see the flicker, put these lines in setup()

      reading = 12.3;
      setDigits();

and comment out the call to setDigits() in loop().

If no flicker after that, this tells you the problem could be the setDigits() function. Restore the first two lines above but leave setDigits() commented out in loop().

If the flicker does not come back, that confirms the problem is setDigits().